@jxsuite/studio 4.1.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/data/elements-meta.json +5 -15
- package/data/html-meta.json +51 -6
- package/dist/chunks/{2020-n6tfwnda.js → 2020-g79n01v9.js} +14 -14
- package/dist/chunks/action-group-sp6tna8v.js +118 -0
- package/dist/chunks/adapter-nj7tnjfn.js +13 -0
- package/dist/chunks/cf-account-picker-fyf63pmy.js +495 -0
- package/dist/chunks/cf-settings-71bv5zn9.js +20 -0
- package/dist/chunks/color-area-v17df7b8.js +154 -0
- package/dist/chunks/color-field-anwybnkn.js +208 -0
- package/dist/chunks/color-slider-44ez96fc.js +79 -0
- package/dist/chunks/color-xgg3kfw7.js +35 -0
- package/dist/chunks/combobox-5e7jev23.js +236 -0
- package/dist/chunks/components-2w1f35qs.js +18 -0
- package/dist/chunks/content-source-3wqeas45.js +46 -0
- package/dist/chunks/convert-to-repeater-d0cgnb6d.js +639 -0
- package/dist/chunks/dialog-s890b918.js +85 -0
- package/dist/chunks/editor-3qhbg51x.js +35 -0
- package/dist/chunks/entry-commands-d6g50j7s.js +42 -0
- package/dist/chunks/extension-sections-fy7q4fe9.js +651 -0
- package/dist/chunks/file-ops-3btj6st0.js +47 -0
- package/dist/chunks/files-ef8fgs3w.js +72 -0
- package/dist/chunks/format-host-3c33nwr8.js +40 -0
- package/dist/chunks/fs-events-13tj6sex.js +18 -0
- package/dist/chunks/git-panel-4wp2rn9b.js +49 -0
- package/dist/chunks/icons-yy10wk9r.js +402 -0
- package/dist/chunks/iframe-entry-15g3n6xv.js +114 -0
- package/dist/chunks/iframe-entry-b27vq81n.js +203 -0
- package/dist/chunks/iframe-entry-nbyxee1y.js +154 -0
- package/dist/chunks/iframe-host-jgesd924.js +115 -0
- package/dist/chunks/{index-wcnt3r9g.js → index-mdmyx6s2.js} +11 -11
- package/dist/chunks/index-n4j7qxyr.js +109 -0
- package/dist/chunks/{javascript-q8kpmfah.js → javascript-xvrfrpkb.js} +8 -9
- package/dist/chunks/{jsonMode-dkr040ga.js → jsonMode-tyx2kj6p.js} +526 -527
- package/dist/chunks/jx-validate-j0wxk72w.js +15 -0
- package/dist/chunks/layers-1nq5evsh.js +29 -0
- package/dist/chunks/layers-panel-ar2gh90g.js +40 -0
- package/dist/chunks/listbox-6v2ac08j.js +44 -0
- package/dist/chunks/lit-html-m244w9nh.js +17 -0
- package/dist/chunks/media-picker-2qbetq73.js +33 -0
- package/dist/chunks/menu-q99sy9rf.js +285 -0
- package/dist/chunks/menu-rf7n72mf.js +14 -0
- package/dist/chunks/{monaco-binding-rj6xpx9y.js → monaco-binding-sw6en4dw.js} +12 -15
- package/dist/chunks/monaco-buffer-bhy3hb6m.js +24 -0
- package/dist/chunks/{monaco-setup-vx9bhp1d.js → monaco-setup-8r62fj6y.js} +27 -37
- package/dist/chunks/notify-628t0anh.js +21 -0
- package/dist/chunks/number-field-5cndyft8.js +72 -0
- package/dist/chunks/parse-0zx14y15.js +10 -0
- package/dist/chunks/platform-ts9cg1a4.js +15 -0
- package/dist/chunks/popover-47z3ytva.js +25 -0
- package/dist/chunks/publish-panel-98f7sr3g.js +27 -0
- package/dist/chunks/quick-search-srzz83k2.js +45 -0
- package/dist/chunks/references-n553f0e1.js +26 -0
- package/dist/chunks/right-panel-rrgx2dn3.js +45 -0
- package/dist/chunks/section-registry-5pq32t0q.js +25 -0
- package/dist/chunks/select-2r16ez41.js +49 -0
- package/dist/chunks/split-g7nm1rgb.js +191 -0
- package/dist/chunks/studio-0hqbpbd0.js +62 -0
- package/dist/chunks/studio-18avnhjp.js +2430 -0
- package/dist/chunks/{studio-kryvsw6j.js → studio-1k325pwf.js} +48 -48
- package/dist/chunks/{studio-msd847zn.js → studio-25hkdrkw.js} +357 -86
- package/dist/chunks/studio-26d7myd1.js +258 -0
- package/dist/chunks/{studio-4r1pv6zf.js → studio-38emantv.js} +22 -5
- package/dist/chunks/studio-3gxv60tn.js +422 -0
- package/dist/chunks/{studio-fbf92ckf.js → studio-3hxkd92t.js} +275 -163
- package/dist/chunks/studio-4m0haseg.js +185 -0
- package/dist/chunks/{studio-ynxwkrzd.js → studio-4wbb9ct0.js} +1953 -1785
- package/dist/chunks/{studio-1wagb550.js → studio-5523bpzt.js} +11 -11
- package/dist/chunks/{studio-x4qkdat5.js → studio-57mf66w2.js} +1749 -2481
- package/dist/chunks/studio-5jy81t3s.js +17463 -0
- package/dist/chunks/studio-5rt7sqa0.js +1168 -0
- package/dist/chunks/studio-5te5tqng.js +81 -0
- package/dist/chunks/{studio-p4ngc046.js → studio-6ewqv390.js} +9 -9
- package/dist/chunks/{studio-9ab8gqsm.js → studio-6nzaktvy.js} +7 -7
- package/dist/chunks/studio-6xa21ej9.js +28 -0
- package/dist/chunks/{studio-5ehhackm.js → studio-727xh0e3.js} +19 -19
- package/dist/chunks/{studio-ahn3pdgx.js → studio-79y5sz51.js} +52 -38
- package/dist/chunks/{studio-gcye7qka.js → studio-7e11jpa9.js} +21 -21
- package/dist/chunks/{studio-q7v10cg2.js → studio-7r42c6ca.js} +17 -7
- package/dist/chunks/studio-7xptrg0j.js +954 -0
- package/dist/chunks/{studio-cft9r4r7.js → studio-81390n6f.js} +22 -22
- package/dist/chunks/{studio-q43a5y9h.js → studio-8jn5yhyn.js} +16 -16
- package/dist/chunks/studio-8qtrpzr3.js +839 -0
- package/dist/chunks/{studio-dg7zyt5x.js → studio-98b3z07n.js} +1 -1
- package/dist/chunks/studio-9xxz1q69.js +1356 -0
- package/dist/chunks/studio-akbww0j5.js +13615 -0
- package/dist/chunks/{studio-x813zp48.js → studio-arfpe389.js} +30 -30
- package/dist/chunks/{studio-trxrabqf.js → studio-az3zd49t.js} +475 -283
- package/dist/chunks/studio-cpvb2ed1.js +1171 -0
- package/dist/chunks/{studio-f3wqjtk5.js → studio-d0300636.js} +5076 -18
- package/dist/chunks/{studio-bgxt93ga.js → studio-d055c37e.js} +13 -13
- package/dist/chunks/studio-d1ja2x9r.js +362 -0
- package/dist/chunks/{studio-b9b6t1dg.js → studio-e1f24eff.js} +5664 -5666
- package/dist/chunks/{studio-3bqpr0zh.js → studio-em9nw8eh.js} +299 -110
- package/dist/chunks/studio-erdatzvp.js +1190 -0
- package/dist/chunks/{studio-g1wdche0.js → studio-esrxgwh6.js} +80 -80
- package/dist/chunks/studio-fabxk6r9.js +2811 -0
- package/dist/chunks/{studio-3n4ndrzh.js → studio-h4nvnjvz.js} +303 -303
- package/dist/chunks/{studio-dm19q43r.js → studio-hhvs4sdg.js} +10 -10
- package/dist/chunks/{studio-q9jd1a60.js → studio-kcgff4r0.js} +15 -18
- package/dist/chunks/{studio-685s0ytq.js → studio-nqgawfks.js} +572 -572
- package/dist/chunks/{studio-xrapxwwc.js → studio-p1dw8wfw.js} +55 -29
- package/dist/chunks/studio-pm6hgy7r.js +4763 -0
- package/dist/chunks/studio-q0a3yk4n.js +33 -0
- package/dist/chunks/{studio-wcy8trfx.js → studio-q3x06vmj.js} +23 -22
- package/dist/chunks/studio-qahxvnan.js +111 -0
- package/dist/chunks/{studio-53n8bp04.js → studio-r6xmmtfh.js} +1 -1
- package/dist/chunks/{studio-2b946z26.js → studio-rkstz940.js} +3658 -6134
- package/dist/chunks/{studio-tvgyw9xw.js → studio-rnyf3f8e.js} +83 -250
- package/dist/chunks/studio-rpyah2ew.js +2805 -0
- package/dist/chunks/studio-rs76k67r.js +12 -0
- package/dist/chunks/{studio-jkftxyvz.js → studio-s6z853aq.js} +5858 -2152
- package/dist/chunks/{studio-6yw4y3my.js → studio-sgdc5pfc.js} +8 -8
- package/dist/chunks/studio-tb3ev01j.js +763 -0
- package/dist/chunks/{studio-t5wqwzdh.js → studio-tjjk7jvk.js} +1 -1
- package/dist/chunks/studio-vhv5tgxm.js +51 -0
- package/dist/chunks/{studio-qjy2z9bv.js → studio-vz68yn9e.js} +32 -137
- package/dist/chunks/studio-wqpngpsk.js +174 -0
- package/dist/chunks/{studio-b55y6ewx.js → studio-x3rty6tr.js} +58 -58
- package/dist/chunks/studio-xsykpvsh.js +8448 -0
- package/dist/chunks/{studio-92ab2jqe.js → studio-y9zedyjn.js} +14 -14
- package/dist/chunks/studio-yxa81f45.js +12 -0
- package/dist/chunks/swatch-group-t0n6h2rn.js +139 -0
- package/dist/chunks/tabs-wbp1cdws.js +156 -0
- package/dist/chunks/textfield-abk6qyee.js +73 -0
- package/dist/chunks/toast-780pvfbv.js +27 -0
- package/dist/chunks/toast-host-3sqv46sn.js +116 -0
- package/dist/chunks/toolbar-xbh5bhtr.js +194 -0
- package/dist/chunks/tooltip-eptb614w.js +369 -0
- package/dist/chunks/tree-a73p0pqm.js +367 -0
- package/dist/chunks/{tsMode-76kr53q0.js → tsMode-jy4m86rv.js} +83 -85
- package/dist/chunks/workspace-wrew8zk4.js +69 -0
- package/dist/chunks/{ws-client-6shc5rze.js → ws-client-ny4zc51r.js} +20 -22
- package/dist/iframe-entry.js +1780 -667
- package/dist/studio.js +52819 -36480
- package/dist/workers/editor.worker.js +1040 -1085
- package/dist/workers/json.worker.js +1946 -1991
- package/dist/workers/ts.worker.js +4960 -5005
- package/index.html +1 -0
- package/package.json +18 -52
- package/src/about/about-modal.ts +72 -123
- package/src/browse/library-commands.ts +20 -49
- package/src/browse/library-layouts.ts +87 -218
- package/src/browse/library-pane.ts +406 -438
- package/src/canvas/canvas-helpers.ts +0 -49
- package/src/canvas/canvas-live-render.ts +25 -2
- package/src/canvas/canvas-render.ts +666 -231
- package/src/canvas/canvas-surface.ts +0 -1
- package/src/canvas/canvas-utils.ts +310 -117
- package/src/canvas/dialog-path.ts +76 -0
- package/src/canvas/dialog-state.ts +67 -0
- package/src/canvas/diff-marks.ts +252 -0
- package/src/canvas/diff-toolbar.ts +349 -0
- package/src/canvas/diff-view.ts +123 -0
- package/src/canvas/edit-width-drag.ts +187 -0
- package/src/canvas/edit-width.ts +150 -0
- package/src/canvas/iframe-entry.ts +125 -8
- package/src/canvas/iframe-host.ts +271 -15
- package/src/canvas/iframe-interaction.ts +133 -13
- package/src/canvas/iframe-patch.ts +7 -1
- package/src/canvas/iframe-position.ts +5 -6
- package/src/canvas/iframe-protocol.ts +106 -5
- package/src/canvas/iframe-render.ts +373 -5
- package/src/canvas/iframe-slash.ts +2 -2
- package/src/canvas/iframe-subtree.ts +5 -8
- package/src/canvas/path-mapping.ts +25 -3
- package/src/canvas/popover-path.ts +106 -0
- package/src/canvas/popover-state.ts +155 -0
- package/src/canvas/surface-registry.ts +21 -10
- package/src/collab/presence-chips.ts +51 -73
- package/src/commands/app-commands.ts +10 -0
- package/src/commands/budget.ts +12 -0
- package/src/commands/command-args.ts +369 -6
- package/src/commands/defaults.ts +44 -14
- package/src/commands/live-context.ts +11 -5
- package/src/commands/reference.ts +12 -2
- package/src/commands/registry.ts +165 -8
- package/src/content/entry-commands.ts +16 -23
- package/src/content/entry-editor.ts +144 -101
- package/src/editor/canvas-slash-bridge.ts +2 -2
- package/src/editor/context-menu.ts +49 -200
- package/src/editor/convert-to-component.ts +42 -82
- package/src/editor/convert-to-repeater.ts +132 -173
- package/src/editor/inline-edit.ts +11 -0
- package/src/editor/shortcuts.ts +65 -26
- package/src/editor/slash-menu.ts +150 -157
- package/src/files/file-ops.ts +117 -11
- package/src/files/files.ts +943 -660
- package/src/files/json-layout.ts +474 -0
- package/src/files/serialize-document.ts +8 -1
- package/src/format/convert-file.ts +16 -10
- package/src/format/format-host.ts +50 -1
- package/src/github/github-auth.ts +71 -29
- package/src/github/github-publish.ts +57 -65
- package/src/grid/cell-editors.ts +46 -4
- package/src/grid/cell-popovers.ts +65 -62
- package/src/grid/grid-open.ts +86 -64
- package/src/grid/grid-panel.ts +390 -469
- package/src/grid/redirects-grid.ts +40 -44
- package/src/hosting/layout.ts +2 -0
- package/src/i18n/i18n-commands.ts +127 -62
- package/src/media/media-pane.ts +83 -133
- package/src/new-project/add-repo-modal.ts +125 -176
- package/src/new-project/import-tab.ts +181 -252
- package/src/new-project/location-fields.ts +93 -160
- package/src/new-project/new-project-modal.ts +376 -367
- package/src/packages/jxsuite-update.ts +76 -18
- package/src/panels/activity-panel.ts +137 -104
- package/src/panels/ai-chat/chat-markdown.ts +15 -13
- package/src/panels/ai-chat/chat-view.ts +307 -472
- package/src/panels/ai-chat/composer.ts +175 -163
- package/src/panels/ai-chat/sessions-view.ts +31 -62
- package/src/panels/ai-panel.ts +210 -185
- package/src/panels/block-action-bar.ts +437 -643
- package/src/panels/bottom-dock.ts +112 -47
- package/src/panels/chat-panel.ts +19 -14
- package/src/panels/data-explorer.ts +141 -102
- package/src/panels/data-grid.ts +183 -150
- package/src/panels/dnd.ts +75 -20
- package/src/panels/editors.ts +26 -19
- package/src/panels/elements-panel.ts +243 -186
- package/src/panels/empty-state.ts +26 -42
- package/src/panels/events-panel.ts +950 -729
- package/src/panels/formula-workspace.ts +434 -207
- package/src/panels/frontmatter-fields.ts +185 -153
- package/src/panels/frontmatter-panel.ts +411 -187
- package/src/panels/git-diff-open.ts +148 -0
- package/src/panels/git-panel.ts +679 -605
- package/src/panels/head-panel.ts +617 -435
- package/src/panels/i18n-panel.ts +153 -133
- package/src/panels/imports-panel.ts +388 -414
- package/src/panels/jump-bar.ts +174 -147
- package/src/panels/layers-panel.ts +785 -671
- package/src/panels/left-panel.ts +166 -94
- package/src/panels/navigator-panels.ts +1 -1
- package/src/panels/pane-context.ts +529 -677
- package/src/panels/pane-grid.ts +265 -241
- package/src/panels/panel-registry.ts +15 -4
- package/src/panels/problems-panel.ts +177 -99
- package/src/panels/properties-panel.ts +1525 -966
- package/src/panels/provenance.ts +31 -113
- package/src/panels/quick-search.ts +217 -172
- package/src/panels/right-panel.ts +135 -169
- package/src/panels/seo-modal.ts +346 -167
- package/src/panels/settings-menu.ts +109 -646
- package/src/panels/settings-pane.ts +102 -86
- package/src/panels/signals-panel.ts +1318 -1172
- package/src/panels/statement-editor.ts +556 -428
- package/src/panels/style-panel.ts +1367 -630
- package/src/panels/style-utils.ts +36 -32
- package/src/panels/stylebook-doc.ts +30 -73
- package/src/panels/stylebook-layers-panel.ts +202 -109
- package/src/panels/stylebook-panel.ts +124 -88
- package/src/panels/tab-strip.ts +385 -287
- package/src/platforms/cloud.ts +106 -16
- package/src/platforms/devserver.ts +13 -0
- package/src/publish/deploy-checklist.ts +124 -78
- package/src/publish/publish-commands.ts +4 -12
- package/src/publish/publish-panel.ts +134 -328
- package/src/services/a11y-report.ts +100 -43
- package/src/services/ai-command-tools.ts +468 -0
- package/src/services/ai-session-store.ts +6 -0
- package/src/services/ai-system-prompt.ts +110 -18
- package/src/services/ai-tools.ts +28 -143
- package/src/services/ai-write-report.ts +174 -0
- package/src/services/document-assistant.ts +50 -2
- package/src/services/idle.ts +16 -14
- package/src/services/live-surfaces.ts +237 -0
- package/src/services/model-uri.ts +75 -0
- package/src/services/notify.ts +63 -6
- package/src/services/popover-report.ts +325 -0
- package/src/services/surface-registry.ts +56 -0
- package/src/services/tool-executor.ts +6 -0
- package/src/settings/contexts-section.ts +301 -299
- package/src/settings/contributed-section.ts +361 -222
- package/src/settings/css-vars-editor.ts +320 -325
- package/src/settings/defs-editor.ts +572 -535
- package/src/settings/dependencies-editor.ts +79 -107
- package/src/settings/extension-commands.ts +379 -0
- package/src/settings/extension-rows.ts +113 -0
- package/src/settings/extensions-section.ts +287 -0
- package/src/settings/general-settings.ts +130 -181
- package/src/settings/head-editor.ts +10 -305
- package/src/settings/locales-section.ts +146 -196
- package/src/settings/preferences-accounts.ts +1 -1
- package/src/settings/preferences-dialog.ts +350 -330
- package/src/settings/project-sections.ts +108 -181
- package/src/settings/schema-field-ui.ts +11 -399
- package/src/settings/section-registry.ts +8 -1
- package/src/settings/settings-document.ts +31 -29
- package/src/shell/tree.ts +42 -87
- package/src/shell.ts +48 -36
- package/src/studio.ts +90 -67
- package/src/style/token-ref.ts +0 -39
- package/src/surfaces/about.json +263 -0
- package/src/surfaces/about.ts +163 -0
- package/src/surfaces/add-repo.json +486 -0
- package/src/surfaces/add-repo.ts +236 -0
- package/src/surfaces/ai-chat.json +2185 -0
- package/src/surfaces/ai-chat.ts +549 -0
- package/src/surfaces/ai-credentials-form.json +332 -0
- package/src/surfaces/ai-credentials-form.ts +151 -0
- package/src/surfaces/ai-managed-connect.json +108 -0
- package/src/surfaces/ai-managed-connect.ts +123 -0
- package/src/surfaces/ai-model-picker.json +114 -0
- package/src/surfaces/ai-model-picker.ts +139 -0
- package/src/surfaces/block-action-bar.json +640 -0
- package/src/surfaces/block-action-bar.ts +368 -0
- package/src/surfaces/bottom-dock.json +154 -0
- package/src/surfaces/bottom-dock.ts +125 -0
- package/src/surfaces/canvas-stage.json +444 -0
- package/src/surfaces/canvas-stage.ts +253 -0
- package/src/surfaces/cf-account-picker.json +300 -0
- package/src/surfaces/cf-account-picker.ts +214 -0
- package/src/surfaces/commandbar.json +984 -0
- package/src/{panels/toolbar.ts → surfaces/commandbar.ts} +414 -389
- package/src/surfaces/convert-repeater.json +309 -0
- package/src/surfaces/convert-repeater.ts +276 -0
- package/src/surfaces/data-actions.json +157 -0
- package/src/surfaces/data-actions.ts +115 -0
- package/src/surfaces/dialog.json +289 -0
- package/src/surfaces/dialog.ts +343 -0
- package/src/surfaces/diff-toolbar.json +266 -0
- package/src/surfaces/diff-toolbar.ts +139 -0
- package/src/surfaces/doc-header.json +739 -0
- package/src/surfaces/doc-header.ts +223 -0
- package/src/surfaces/empty-state.json +152 -0
- package/src/surfaces/empty-state.ts +173 -0
- package/src/surfaces/entry-editor.json +281 -0
- package/src/surfaces/entry-editor.ts +191 -0
- package/src/surfaces/expression-editor.json +977 -0
- package/src/surfaces/expression-editor.ts +266 -0
- package/src/surfaces/files-panel.json +592 -0
- package/src/surfaces/files-panel.ts +301 -0
- package/src/surfaces/formula-palette.json +364 -0
- package/src/surfaces/formula-palette.ts +340 -0
- package/src/surfaces/git-panel.json +1822 -0
- package/src/surfaces/git-panel.ts +272 -0
- package/src/surfaces/github-auth.json +112 -0
- package/src/surfaces/github-auth.ts +139 -0
- package/src/surfaces/github-publish.json +206 -0
- package/src/surfaces/github-publish.ts +169 -0
- package/src/surfaces/grid-cell.json +218 -0
- package/src/surfaces/grid-cell.ts +120 -0
- package/src/surfaces/grid-open.json +147 -0
- package/src/surfaces/grid-open.ts +87 -0
- package/src/surfaces/grid-panel.json +706 -0
- package/src/surfaces/grid-panel.ts +206 -0
- package/src/surfaces/grid-replace.json +179 -0
- package/src/surfaces/grid-replace.ts +89 -0
- package/src/surfaces/grid-views.json +446 -0
- package/src/surfaces/grid-views.ts +172 -0
- package/src/surfaces/inspector-dock.json +187 -0
- package/src/surfaces/inspector-dock.ts +161 -0
- package/src/surfaces/jump-bar.json +243 -0
- package/src/surfaces/jump-bar.ts +129 -0
- package/src/surfaces/jxsuite-update.json +146 -0
- package/src/surfaces/jxsuite-update.ts +149 -0
- package/src/surfaces/library-pane.json +1509 -0
- package/src/surfaces/library-pane.ts +363 -0
- package/src/surfaces/logic-panel.json +1949 -0
- package/src/surfaces/logic-panel.ts +369 -0
- package/src/surfaces/logic-workspace.json +685 -0
- package/src/surfaces/logic-workspace.ts +238 -0
- package/src/surfaces/media-browser.json +303 -0
- package/src/surfaces/media-browser.ts +145 -0
- package/src/surfaces/media-field.json +163 -0
- package/src/surfaces/media-field.ts +116 -0
- package/src/surfaces/media-pane.json +513 -0
- package/src/surfaces/media-pane.ts +197 -0
- package/src/surfaces/menu.json +232 -0
- package/src/surfaces/menu.ts +375 -0
- package/src/surfaces/navigator-dock.json +113 -0
- package/src/surfaces/navigator-dock.ts +130 -0
- package/src/surfaces/new-project.json +1654 -0
- package/src/surfaces/new-project.ts +411 -0
- package/src/surfaces/palette.json +535 -0
- package/src/surfaces/pane-context.json +1497 -0
- package/src/surfaces/pane-context.ts +513 -0
- package/src/surfaces/pane-grid.json +188 -0
- package/src/surfaces/pane-grid.ts +250 -0
- package/src/surfaces/panel-activity.json +563 -0
- package/src/surfaces/panel-activity.ts +187 -0
- package/src/surfaces/panel-data.json +141 -0
- package/src/surfaces/panel-data.ts +156 -0
- package/src/surfaces/panel-deploy-checklist.json +283 -0
- package/src/surfaces/panel-deploy-checklist.ts +175 -0
- package/src/surfaces/panel-elements.json +367 -0
- package/src/surfaces/panel-elements.ts +175 -0
- package/src/surfaces/panel-i18n.json +569 -0
- package/src/surfaces/panel-i18n.ts +212 -0
- package/src/surfaces/panel-imports.json +780 -0
- package/src/surfaces/panel-imports.ts +209 -0
- package/src/surfaces/panel-outline.json +734 -0
- package/src/surfaces/panel-outline.ts +326 -0
- package/src/surfaces/panel-page.json +1017 -0
- package/src/surfaces/panel-page.ts +240 -0
- package/src/surfaces/panel-problems.json +497 -0
- package/src/surfaces/panel-problems.ts +157 -0
- package/src/surfaces/panel-signals.json +1973 -0
- package/src/surfaces/panel-signals.ts +436 -0
- package/src/surfaces/panel-stylebook-layers.json +317 -0
- package/src/surfaces/panel-stylebook-layers.ts +155 -0
- package/src/surfaces/preferences.json +850 -0
- package/src/surfaces/preferences.ts +332 -0
- package/src/surfaces/progress-modal.json +110 -0
- package/src/surfaces/progress-modal.ts +150 -0
- package/src/surfaces/properties-panel.json +1578 -0
- package/src/surfaces/properties-panel.ts +337 -0
- package/src/surfaces/publish.json +945 -0
- package/src/surfaces/publish.ts +392 -0
- package/src/surfaces/push-plan.json +110 -0
- package/src/surfaces/push-plan.ts +87 -0
- package/src/surfaces/rail.json +255 -0
- package/src/surfaces/rail.ts +206 -0
- package/src/surfaces/reference-field.json +191 -0
- package/src/surfaces/reference-field.ts +168 -0
- package/src/surfaces/schema-builder.json +1011 -0
- package/src/surfaces/schema-builder.ts +181 -0
- package/src/surfaces/schema-form.json +1013 -0
- package/src/surfaces/schema-form.ts +287 -0
- package/src/surfaces/secret-field.json +60 -0
- package/src/surfaces/secret-field.ts +146 -0
- package/src/surfaces/seo.json +932 -0
- package/src/surfaces/seo.ts +334 -0
- package/src/surfaces/settings-contexts.json +538 -0
- package/src/surfaces/settings-contexts.ts +196 -0
- package/src/surfaces/settings-contributed.json +489 -0
- package/src/surfaces/settings-contributed.ts +199 -0
- package/src/surfaces/settings-css-vars.json +820 -0
- package/src/surfaces/settings-css-vars.ts +199 -0
- package/src/surfaces/settings-defs.json +1363 -0
- package/src/surfaces/settings-defs.ts +232 -0
- package/src/surfaces/settings-deploy.json +108 -0
- package/src/surfaces/settings-deploy.ts +135 -0
- package/src/surfaces/settings-extensions.json +486 -0
- package/src/surfaces/settings-extensions.ts +201 -0
- package/src/surfaces/settings-head.json +779 -0
- package/src/surfaces/settings-head.ts +397 -0
- package/src/surfaces/settings-locales.json +420 -0
- package/src/surfaces/settings-locales.ts +137 -0
- package/src/surfaces/settings-overview.json +423 -0
- package/src/surfaces/settings-overview.ts +218 -0
- package/src/surfaces/settings-packages.json +549 -0
- package/src/surfaces/settings-packages.ts +158 -0
- package/src/surfaces/settings-pane.json +147 -0
- package/src/surfaces/settings-pane.ts +150 -0
- package/src/surfaces/settings-rawjson.json +104 -0
- package/src/surfaces/settings-rawjson.ts +107 -0
- package/src/surfaces/shell.json +99 -0
- package/src/surfaces/shell.ts +30 -0
- package/src/surfaces/slash-menu.json +180 -0
- package/src/surfaces/slash-menu.ts +272 -0
- package/src/surfaces/statements.json +607 -0
- package/src/surfaces/statements.ts +238 -0
- package/src/surfaces/statusbar.json +94 -0
- package/src/{panels → surfaces}/statusbar.ts +163 -90
- package/src/surfaces/style-panel.json +1643 -0
- package/src/surfaces/style-panel.ts +338 -0
- package/src/surfaces/stylebook-chrome.json +122 -0
- package/src/surfaces/stylebook-chrome.ts +122 -0
- package/src/surfaces/tab-strip.json +448 -0
- package/src/surfaces/tab-strip.ts +200 -0
- package/src/surfaces/target-line.json +419 -0
- package/src/surfaces/target-line.ts +349 -0
- package/src/surfaces/toasts.json +189 -0
- package/src/surfaces/welcome.json +571 -0
- package/src/surfaces/welcome.ts +338 -0
- package/src/tabs/project-config.ts +71 -15
- package/src/tabs/tab.ts +52 -1
- package/src/types.ts +22 -0
- package/src/ui/ai-credentials-form.ts +104 -100
- package/src/ui/ai-managed-connect.ts +54 -36
- package/src/ui/ai-model-picker.ts +75 -47
- package/src/ui/cf-account-picker.ts +76 -141
- package/src/ui/color-selector.ts +80 -301
- package/src/ui/dynamic-slot.ts +43 -283
- package/src/ui/expression-editor.ts +797 -576
- package/src/ui/field-input.ts +8 -181
- package/src/ui/form-controls.ts +838 -366
- package/src/ui/formula-chips.ts +80 -85
- package/src/ui/kit.ts +33 -0
- package/src/ui/layers.ts +293 -593
- package/src/ui/media-picker.ts +203 -284
- package/src/ui/panel-resize.ts +251 -64
- package/src/ui/popover-surface.ts +153 -0
- package/src/ui/progress-modal.ts +22 -60
- package/src/ui/regions.ts +13 -7
- package/src/ui/schema-form.ts +906 -440
- package/src/ui/surface.ts +122 -0
- package/src/ui/unit-selector.ts +15 -131
- package/src/ui/value-source.ts +14 -0
- package/src/ui/virtual-window.ts +88 -9
- package/src/utils/canvas-media.ts +72 -0
- package/src/utils/edit-display.ts +7 -2
- package/src/utils/element-selectors.ts +69 -0
- package/src/utils/link-target.ts +2 -2
- package/src/utils/studio-utils.ts +0 -10
- package/src/view.ts +15 -0
- package/src/workspace/pane-derive.ts +36 -14
- package/src/workspace/workspace.ts +15 -6
- package/styles/canvas.css +72 -467
- package/styles/forced-colors.css +48 -82
- package/styles/forced-colors.json +41 -0
- package/styles/inspector.css +2 -1301
- package/styles/overlays.css +96 -2823
- package/styles/panels.css +6 -1423
- package/styles/shell-frame.css +272 -0
- package/styles/shell-frame.json +227 -0
- package/styles/shell.css +0 -1568
- package/styles/tokens.css +65 -136
- package/styles/tokens.json +84 -0
- package/dist/chunks/Overlay.dev-etqjwyf9.js +0 -23
- package/dist/chunks/adapter-jkcyhgtr.js +0 -14
- package/dist/chunks/cf-account-picker-en86v7x7.js +0 -163
- package/dist/chunks/cf-settings-45fgyx8y.js +0 -21
- package/dist/chunks/components-zmx10t62.js +0 -21
- package/dist/chunks/content-source-48xg86wj.js +0 -49
- package/dist/chunks/convert-to-repeater-43tfn7cs.js +0 -268
- package/dist/chunks/editor-cptq5jp1.js +0 -37
- package/dist/chunks/entry-commands-e63f1xc0.js +0 -46
- package/dist/chunks/extension-sections-txv08xxg.js +0 -311
- package/dist/chunks/file-ops-wbh4hhsx.js +0 -48
- package/dist/chunks/files-3kfkwhk4.js +0 -93
- package/dist/chunks/flow-qbfgrpeh.js +0 -634
- package/dist/chunks/focus-trap.esm-9t2drp0g.js +0 -1025
- package/dist/chunks/focus-visible-6eg70z18.js +0 -167
- package/dist/chunks/format-host-w16q81tf.js +0 -43
- package/dist/chunks/fs-events-hrrg328g.js +0 -21
- package/dist/chunks/git-panel-532whr1y.js +0 -59
- package/dist/chunks/index-zqxmqxmh.js +0 -109
- package/dist/chunks/jx-validate-8er6gbxy.js +0 -19
- package/dist/chunks/layers-panel-07hm7g5j.js +0 -67
- package/dist/chunks/layers-q75tj2mf.js +0 -45
- package/dist/chunks/lit-html-j055mkh8.js +0 -22
- package/dist/chunks/monaco-buffer-dqw0xxpx.js +0 -25
- package/dist/chunks/notify-yjm79n4s.js +0 -30
- package/dist/chunks/parse-24d0gay3.js +0 -19
- package/dist/chunks/platform-ba1j1gbh.js +0 -16
- package/dist/chunks/publish-panel-v9st96ja.js +0 -29
- package/dist/chunks/quick-search-zccjp6gv.js +0 -66
- package/dist/chunks/references-3qx8fkyp.js +0 -29
- package/dist/chunks/right-panel-vm3b6kwy.js +0 -46
- package/dist/chunks/section-registry-sx1ykvkw.js +0 -30
- package/dist/chunks/sp-overlay.dev-qgvhnxf4.js +0 -18
- package/dist/chunks/sp-popover.dev-n2etmqdp.js +0 -9
- package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +0 -15
- package/dist/chunks/sp-tooltip.dev-4sastdnw.js +0 -15
- package/dist/chunks/sp-tray.dev-tkbscm06.js +0 -199
- package/dist/chunks/studio-0a5qrh01.js +0 -3
- package/dist/chunks/studio-0bq6eh7v.js +0 -12
- package/dist/chunks/studio-18j3bmcz.js +0 -14
- package/dist/chunks/studio-1ngbejft.js +0 -181
- package/dist/chunks/studio-4b5z1x43.js +0 -424
- package/dist/chunks/studio-4n2b5gzb.js +0 -357
- package/dist/chunks/studio-4rzyrtjz.js +0 -426
- package/dist/chunks/studio-5fx3g9jg.js +0 -4325
- package/dist/chunks/studio-5myknm0a.js +0 -1903
- package/dist/chunks/studio-7afd21nw.js +0 -67
- package/dist/chunks/studio-7nte2hj4.js +0 -613
- package/dist/chunks/studio-8jpzqk5s.js +0 -128
- package/dist/chunks/studio-8q517dfr.js +0 -24
- package/dist/chunks/studio-950k0dx6.js +0 -6939
- package/dist/chunks/studio-9n4mrmnb.js +0 -184
- package/dist/chunks/studio-a0tswy0w.js +0 -58
- package/dist/chunks/studio-ahnzd9rg.js +0 -23
- package/dist/chunks/studio-cmb03bg5.js +0 -13890
- package/dist/chunks/studio-d1ks410h.js +0 -175
- package/dist/chunks/studio-ef49pnzx.js +0 -387
- package/dist/chunks/studio-eqq1w638.js +0 -113
- package/dist/chunks/studio-ew0djmhg.js +0 -341
- package/dist/chunks/studio-gfgb8pe6.js +0 -1652
- package/dist/chunks/studio-h4rxscfc.js +0 -10
- package/dist/chunks/studio-jc994dnr.js +0 -1066
- package/dist/chunks/studio-kyh9avvv.js +0 -28
- package/dist/chunks/studio-m3hwn7y1.js +0 -120
- package/dist/chunks/studio-mtfnaqs9.js +0 -659
- package/dist/chunks/studio-n6j9t10q.js +0 -318
- package/dist/chunks/studio-n8b9wqwy.js +0 -291
- package/dist/chunks/studio-qcy05dp8.js +0 -1743
- package/dist/chunks/studio-qq6jym04.js +0 -53
- package/dist/chunks/studio-rdx8jcxy.js +0 -83
- package/dist/chunks/studio-rn9ta2mz.js +0 -111
- package/dist/chunks/studio-t7qb9db8.js +0 -665
- package/dist/chunks/studio-tmc5emrw.js +0 -8
- package/dist/chunks/studio-z6nw2ek2.js +0 -18
- package/dist/chunks/studio-zczps70q.js +0 -378
- package/dist/chunks/studio-zqazsct2.js +0 -1823
- package/dist/chunks/workspace-fjx52ryv.js +0 -89
- package/src/panels/activity-bar.ts +0 -251
- package/src/panels/panel-scheduler.ts +0 -219
- package/src/panels/style-inputs.ts +0 -237
- package/src/panels/target-line.ts +0 -274
- package/src/panels/welcome-screen.ts +0 -346
- package/src/ui/button-group.ts +0 -102
- package/src/ui/choice-field.ts +0 -72
- package/src/ui/field-row.ts +0 -249
- package/src/ui/formula-palette.ts +0 -213
- package/src/ui/icons.ts +0 -52
- package/src/ui/jx-theme.ts +0 -130
- package/src/ui/spectrum.ts +0 -340
- package/src/ui/value-selector.ts +0 -174
- package/src/ui/widgets.ts +0 -172
- /package/dist/chunks/{javascript-s56a8dpk.css → javascript-4abzjcy9.css} +0 -0
- /package/dist/chunks/{jsonMode-zr9sw0c7.css → jsonMode-vvmpmk34.css} +0 -0
- /package/dist/chunks/{monaco-setup-pav68602.css → monaco-setup-8vbyqnhr.css} +0 -0
- /package/dist/chunks/{tsMode-ef187txx.css → tsMode-hnxxb73h.css} +0 -0
package/src/panels/head-panel.ts
CHANGED
|
@@ -1,45 +1,61 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* The Navigator's Page panel — everything the open document says about itself before anybody reads
|
|
4
|
+
* its body: its frontmatter, the layout it is poured into, its title, description, viewport and
|
|
5
|
+
* icon, its OpenGraph card, and the raw `$head` tags no structured control owns.
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
+
* This is the FLOW. The markup is `surfaces/panel-page.json` and the scope that feeds it is
|
|
8
|
+
* `surfaces/panel-page.ts`; what stays here is every decision — what a `$head` entry means, which
|
|
9
|
+
* of them a structured control already owns, which realm a title lives in, what the layout cascade
|
|
10
|
+
* resolves to, and what each row commits into.
|
|
11
|
+
*
|
|
12
|
+
* **The drafts live here, not in the DOM.** The add-a-tag form used to be three `ref()` handles
|
|
13
|
+
* read at submit time, which is a spelling a document does not have: a binding writes only when the
|
|
14
|
+
* SCOPE moves, so every field's setter states what the control now holds before anything is decided
|
|
15
|
+
* about it, and clearing one after a successful add is then a real change the runtime carries back.
|
|
16
|
+
* `sync()` — not `renderLeftPanel()` — is what an echo calls: it assigns into the standing scope in
|
|
17
|
+
* the same turn, where a Navigator repaint is a frame away and would rebuild far more than one
|
|
18
|
+
* field.
|
|
19
|
+
*
|
|
20
|
+
* **What a frontmatter row looks like is `panels/frontmatter-fields.ts`'s answer.** The Document
|
|
21
|
+
* Header card draws the same field set from the same schemas; the two surfaces deciding
|
|
22
|
+
* independently that a `$ref` is a picker or that an array is a comma-separated line is how they
|
|
23
|
+
* came to disagree about `title` in the first place.
|
|
24
|
+
*
|
|
25
|
+
* The other half of this module has no surface at all: the merged-`$head` preview model that
|
|
26
|
+
* `panels/seo-modal.ts` and the Document Header card both read. It is pure, and pinned against
|
|
27
|
+
* `packages/compiler/src/site/head-merger.ts` by `tests/head-panel.test.ts`.
|
|
7
28
|
*/
|
|
8
29
|
|
|
9
|
-
import {
|
|
10
|
-
import { createRef, ref } from "lit-html/directives/ref.js";
|
|
11
|
-
import { renderFieldRow } from "../ui/field-row";
|
|
12
|
-
import { spTextArea, spTextField } from "../ui/field-input";
|
|
13
|
-
import { renderMediaPicker } from "../ui/media-picker";
|
|
30
|
+
import { nothing } from "lit-html";
|
|
14
31
|
import { projectState, renderOnly } from "../store";
|
|
15
32
|
import type { DirEntry, JsonValue } from "../types";
|
|
16
33
|
import { activeTab } from "../workspace/workspace";
|
|
17
34
|
import { activeRegistry } from "../commands/active-registry";
|
|
18
35
|
import { clearProblems, notify } from "../services/notify";
|
|
19
|
-
import { renderEmptyState } from "./empty-state";
|
|
20
36
|
import { registerPanel } from "./panel-registry";
|
|
21
|
-
import { mutateUpdateFrontmatter, transact } from "../tabs/transact";
|
|
22
|
-
import { collectFmFields,
|
|
37
|
+
import { mutateUpdateFrontmatter, transact, transactDoc } from "../tabs/transact";
|
|
38
|
+
import { collectFmFields, projectFmField } from "./frontmatter-fields";
|
|
23
39
|
import { isGoogleFontEntry, isGoogleFontPreconnect } from "../utils/google-fonts";
|
|
24
40
|
import { getEffectiveLayoutPath, invalidateLayoutCache, resolveLayoutDoc } from "../site-context";
|
|
25
41
|
import { getPlatform } from "../platform";
|
|
26
42
|
import { pageRoute } from "./tab-strip";
|
|
43
|
+
import { LIVE_PREVIEW } from "../ui/timing";
|
|
44
|
+
import { previewAssetSrc } from "../canvas/asset-refs";
|
|
45
|
+
import { IMAGE_EXTENSIONS, extensionOf } from "../files/media-upload";
|
|
46
|
+
import { renderPagePanelSurface } from "../surfaces/panel-page";
|
|
27
47
|
|
|
28
48
|
import type { JxHeadEntry, JxMutableNode } from "@jxsuite/schema/types";
|
|
29
49
|
import type { Tab } from "../tabs/tab";
|
|
30
|
-
import type
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
*/
|
|
40
|
-
const _addTag = createRef<HTMLInputElement>();
|
|
41
|
-
const _addAttr = createRef<HTMLInputElement>();
|
|
42
|
-
const _addVal = createRef<HTMLInputElement>();
|
|
50
|
+
import type * as MediaPickerModule from "../ui/media-picker";
|
|
51
|
+
import type {
|
|
52
|
+
PageChoice,
|
|
53
|
+
PagePanelActions,
|
|
54
|
+
PagePanelView,
|
|
55
|
+
PageRow,
|
|
56
|
+
PageSection,
|
|
57
|
+
PageTagRow,
|
|
58
|
+
} from "../surfaces/panel-page";
|
|
43
59
|
|
|
44
60
|
interface MetaField {
|
|
45
61
|
label: string;
|
|
@@ -84,6 +100,24 @@ export function invalidateLayoutPickerCache() {
|
|
|
84
100
|
invalidateLayoutHeadCache();
|
|
85
101
|
}
|
|
86
102
|
|
|
103
|
+
/**
|
|
104
|
+
* The layouts the picker offers, or `null` while the directory is still being listed.
|
|
105
|
+
*
|
|
106
|
+
* The listing BOTH surfaces draw their Layout picker from, handed over as DATA. The Page panel
|
|
107
|
+
* (`src/surfaces/panel-page.json`) and the Document Header card (`src/surfaces/doc-header.json`)
|
|
108
|
+
* are documents and neither can interpolate the other's markup — but a second LISTING would be a
|
|
109
|
+
* second cache with a second lifetime, and {@link invalidateLayoutPickerCache} would then forget
|
|
110
|
+
* only one of them. Asking starts the read; it repaints both surfaces when it lands.
|
|
111
|
+
*
|
|
112
|
+
* @returns {{ name: string; path: string }[] | null}
|
|
113
|
+
*/
|
|
114
|
+
export function layoutPickerEntries(): { name: string; path: string }[] | null {
|
|
115
|
+
if (layoutEntries === null) {
|
|
116
|
+
void loadLayoutEntries();
|
|
117
|
+
}
|
|
118
|
+
return layoutEntries;
|
|
119
|
+
}
|
|
120
|
+
|
|
87
121
|
// ─── Field definitions ───────────────────────────────────────────────────
|
|
88
122
|
|
|
89
123
|
export const PAGE_FIELDS: MetaField[] = [
|
|
@@ -128,7 +162,7 @@ export const RESERVED_FM_KEYS = new Set(["title"]);
|
|
|
128
162
|
* @param {string} key
|
|
129
163
|
* @returns {JxHeadEntry | undefined}
|
|
130
164
|
*/
|
|
131
|
-
function findMetaEntry(head: JxHeadEntry[], attr: "name" | "property", key: string) {
|
|
165
|
+
export function findMetaEntry(head: JxHeadEntry[], attr: "name" | "property", key: string) {
|
|
132
166
|
if (!head) {
|
|
133
167
|
return;
|
|
134
168
|
}
|
|
@@ -182,7 +216,12 @@ export function isManagedEntry(entry: JxHeadEntry) {
|
|
|
182
216
|
* @param {string} key
|
|
183
217
|
* @param {string} content
|
|
184
218
|
*/
|
|
185
|
-
function upsertMeta(
|
|
219
|
+
export function upsertMeta(
|
|
220
|
+
doc: JxMutableNode,
|
|
221
|
+
attr: "name" | "property",
|
|
222
|
+
key: string,
|
|
223
|
+
content: string,
|
|
224
|
+
) {
|
|
186
225
|
if (!doc.$head) {
|
|
187
226
|
doc.$head = [];
|
|
188
227
|
}
|
|
@@ -784,281 +823,6 @@ export function seoPreviewFor(tab: Tab | null, doc: JxMutableNode): SeoPreview {
|
|
|
784
823
|
);
|
|
785
824
|
}
|
|
786
825
|
|
|
787
|
-
// ─── Field renderers ─────────────────────────────────────────────────────
|
|
788
|
-
|
|
789
|
-
/**
|
|
790
|
-
* Render a meta field row using renderFieldRow.
|
|
791
|
-
*
|
|
792
|
-
* @param {MetaField} field
|
|
793
|
-
* @param {JxHeadEntry[]} head
|
|
794
|
-
* @param {(fn: (doc: JxMutableNode) => void) => void} applyMutation
|
|
795
|
-
* @returns {import("lit-html").TemplateResult}
|
|
796
|
-
*/
|
|
797
|
-
export function renderMetaFieldRow(
|
|
798
|
-
field: MetaField,
|
|
799
|
-
head: JxHeadEntry[],
|
|
800
|
-
applyMutation: (fn: (doc: JxMutableNode) => void) => void,
|
|
801
|
-
) {
|
|
802
|
-
const entry = findMetaEntry(head, field.attr, field.key);
|
|
803
|
-
const val = String(entry?.attributes?.content ?? "");
|
|
804
|
-
|
|
805
|
-
if (field.media) {
|
|
806
|
-
return renderFieldRow({
|
|
807
|
-
hasValue: Boolean(val),
|
|
808
|
-
label: field.label,
|
|
809
|
-
onClear: () => applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, "")),
|
|
810
|
-
prop: field.key,
|
|
811
|
-
widget: renderMediaPicker(field.key, val, (v: string) => {
|
|
812
|
-
applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, v || ""));
|
|
813
|
-
}),
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
const commit = (v: string) =>
|
|
818
|
-
applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, v.trim()));
|
|
819
|
-
const placeholder =
|
|
820
|
-
field.key === "viewport" ? "width=device-width, initial-scale=1" : `${field.label}…`;
|
|
821
|
-
const widget = field.multiline
|
|
822
|
-
? spTextArea(`head:${field.key}`, val, commit, {
|
|
823
|
-
placeholder: `${field.label}…`,
|
|
824
|
-
})
|
|
825
|
-
: spTextField(`head:${field.key}`, val, commit, { placeholder });
|
|
826
|
-
|
|
827
|
-
return renderFieldRow({
|
|
828
|
-
hasValue: Boolean(val),
|
|
829
|
-
label: field.label,
|
|
830
|
-
onClear: () => applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, "")),
|
|
831
|
-
prop: field.key,
|
|
832
|
-
widget,
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
// ─── Template ────────────────────────────────────────────────────────────
|
|
837
|
-
|
|
838
|
-
/**
|
|
839
|
-
* @param {{
|
|
840
|
-
* document: JxMutableNode;
|
|
841
|
-
* applyMutation: (fn: (doc: JxMutableNode) => void) => void;
|
|
842
|
-
* renderLeftPanel: () => void;
|
|
843
|
-
* }} ctx
|
|
844
|
-
* @returns {import("lit-html").TemplateResult}
|
|
845
|
-
*/
|
|
846
|
-
export function renderHeadTemplate({
|
|
847
|
-
document: doc,
|
|
848
|
-
applyMutation,
|
|
849
|
-
renderLeftPanel,
|
|
850
|
-
}: {
|
|
851
|
-
document: JxMutableNode;
|
|
852
|
-
applyMutation: (fn: (doc: JxMutableNode) => void) => void;
|
|
853
|
-
renderLeftPanel: () => void;
|
|
854
|
-
}) {
|
|
855
|
-
const head = doc.$head ?? [];
|
|
856
|
-
const title = doc.title ?? "";
|
|
857
|
-
|
|
858
|
-
// Icon (favicon) link
|
|
859
|
-
const iconEntry = findLinkEntry(head, "icon");
|
|
860
|
-
const iconHref = String(iconEntry?.attributes?.href ?? "");
|
|
861
|
-
|
|
862
|
-
// Custom entries not managed by structured forms, fonts, or preconnects
|
|
863
|
-
const customEntries = head.filter(
|
|
864
|
-
(e: JxHeadEntry) => !isManagedEntry(e) && !isGoogleFontEntry(e) && !isGoogleFontPreconnect(e),
|
|
865
|
-
);
|
|
866
|
-
|
|
867
|
-
// Frontmatter section (content mode only)
|
|
868
|
-
const tab = activeTab.value;
|
|
869
|
-
const isContent = tab?.doc.mode === "content";
|
|
870
|
-
const frontmatterSection = isContent ? renderFrontmatterSection() : nothing;
|
|
871
|
-
|
|
872
|
-
// `tab` is the FOCUSED tab, and legitimately so: this template is the Navigator's Page panel,
|
|
873
|
-
// Which is drawn once for the shell and follows the focus by design. Spelling it here is what
|
|
874
|
-
// Lets `renderLayoutSection` and everything under it take a tab instead of asking.
|
|
875
|
-
const layoutSection = renderLayoutSection(tab, doc, applyMutation);
|
|
876
|
-
|
|
877
|
-
return html`
|
|
878
|
-
<div class="imports-panel">
|
|
879
|
-
${frontmatterSection} ${layoutSection}
|
|
880
|
-
|
|
881
|
-
<!-- Page section -->
|
|
882
|
-
<div class="imports-section">
|
|
883
|
-
<div class="imports-section-header">
|
|
884
|
-
<span class="imports-section-title">Page</span>
|
|
885
|
-
<!-- The SECOND door to Search appearance; the Document Header card has the other. Two
|
|
886
|
-
moments, one capability — both run document.openSeo, so neither surface owns it and
|
|
887
|
-
the palette has it by name. -->
|
|
888
|
-
<sp-action-button
|
|
889
|
-
quiet
|
|
890
|
-
size="xs"
|
|
891
|
-
class="head-seo-btn"
|
|
892
|
-
title="Preview how this page appears in search and when shared"
|
|
893
|
-
@click=${() => {
|
|
894
|
-
void activeRegistry()?.run("document.openSeo");
|
|
895
|
-
}}
|
|
896
|
-
>
|
|
897
|
-
Search appearance…
|
|
898
|
-
</sp-action-button>
|
|
899
|
-
</div>
|
|
900
|
-
<div class="head-section-body">
|
|
901
|
-
${renderFieldRow({
|
|
902
|
-
hasValue: Boolean(title),
|
|
903
|
-
label: "Title",
|
|
904
|
-
onClear: () =>
|
|
905
|
-
applyMutation((d: JxMutableNode) => {
|
|
906
|
-
delete d.title;
|
|
907
|
-
}),
|
|
908
|
-
prop: "title",
|
|
909
|
-
widget: spTextField(
|
|
910
|
-
"head:title",
|
|
911
|
-
title,
|
|
912
|
-
(v: string) =>
|
|
913
|
-
applyMutation((d: JxMutableNode) => {
|
|
914
|
-
const val = v.trim();
|
|
915
|
-
if (val) {
|
|
916
|
-
d.title = val;
|
|
917
|
-
} else {
|
|
918
|
-
delete d.title;
|
|
919
|
-
}
|
|
920
|
-
}),
|
|
921
|
-
{ placeholder: "Page title…" },
|
|
922
|
-
),
|
|
923
|
-
})}
|
|
924
|
-
${PAGE_FIELDS.map((field) => renderMetaFieldRow(field, head, applyMutation))}
|
|
925
|
-
${renderFieldRow({
|
|
926
|
-
hasValue: Boolean(iconHref),
|
|
927
|
-
label: "Icon",
|
|
928
|
-
onClear: () => applyMutation((d: JxMutableNode) => upsertLink(d, "icon", "")),
|
|
929
|
-
prop: "icon",
|
|
930
|
-
widget: renderMediaPicker("icon", iconHref, (v: string) => {
|
|
931
|
-
applyMutation((d: JxMutableNode) => upsertLink(d, "icon", v || ""));
|
|
932
|
-
}),
|
|
933
|
-
})}
|
|
934
|
-
</div>
|
|
935
|
-
</div>
|
|
936
|
-
|
|
937
|
-
<!-- OpenGraph section -->
|
|
938
|
-
<div class="imports-section">
|
|
939
|
-
<div class="imports-section-header">
|
|
940
|
-
<span class="imports-section-title">OpenGraph</span>
|
|
941
|
-
</div>
|
|
942
|
-
<div class="head-section-body">
|
|
943
|
-
${OG_FIELDS.map((field) => renderMetaFieldRow(field, head, applyMutation))}
|
|
944
|
-
</div>
|
|
945
|
-
</div>
|
|
946
|
-
|
|
947
|
-
<!-- Custom $head entries -->
|
|
948
|
-
<div class="imports-section">
|
|
949
|
-
<div class="imports-section-header">
|
|
950
|
-
<span class="imports-section-title">Custom Tags</span>
|
|
951
|
-
<span class="imports-count">${customEntries.length}</span>
|
|
952
|
-
</div>
|
|
953
|
-
${
|
|
954
|
-
customEntries.length > 0
|
|
955
|
-
? html`
|
|
956
|
-
<div class="imports-list">
|
|
957
|
-
${customEntries.map((entry: JxHeadEntry) => {
|
|
958
|
-
const label = entryLabel(entry);
|
|
959
|
-
const value = entryValue(entry);
|
|
960
|
-
return html`
|
|
961
|
-
<div class="import-row">
|
|
962
|
-
<span class="import-name" title=${value}>${label}</span>
|
|
963
|
-
<span class="import-path">${value}</span>
|
|
964
|
-
<sp-action-button
|
|
965
|
-
quiet
|
|
966
|
-
size="xs"
|
|
967
|
-
title="Remove"
|
|
968
|
-
@click=${() => {
|
|
969
|
-
applyMutation((d: JxMutableNode) => {
|
|
970
|
-
if (!d.$head) {
|
|
971
|
-
return;
|
|
972
|
-
}
|
|
973
|
-
const idx = d.$head.indexOf(entry);
|
|
974
|
-
if (idx !== -1) {
|
|
975
|
-
d.$head.splice(idx, 1);
|
|
976
|
-
}
|
|
977
|
-
});
|
|
978
|
-
renderLeftPanel();
|
|
979
|
-
}}
|
|
980
|
-
>
|
|
981
|
-
<sp-icon-close slot="icon" size="xs"></sp-icon-close>
|
|
982
|
-
</sp-action-button>
|
|
983
|
-
</div>
|
|
984
|
-
`;
|
|
985
|
-
})}
|
|
986
|
-
</div>
|
|
987
|
-
`
|
|
988
|
-
: renderEmptyState({
|
|
989
|
-
compact: true,
|
|
990
|
-
message:
|
|
991
|
-
"Custom tags add your own meta, link and script elements to this page — " +
|
|
992
|
-
"analytics, verification, a webfont. Add one below.",
|
|
993
|
-
})
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
<!-- Add custom tag form -->
|
|
997
|
-
<div class="head-add-form">
|
|
998
|
-
<sp-picker size="s" label="Tag" class="head-add-tag" ${ref(_addTag)}>
|
|
999
|
-
<sp-menu-item value="meta">meta</sp-menu-item>
|
|
1000
|
-
<sp-menu-item value="link">link</sp-menu-item>
|
|
1001
|
-
<sp-menu-item value="script">script</sp-menu-item>
|
|
1002
|
-
</sp-picker>
|
|
1003
|
-
<sp-textfield
|
|
1004
|
-
placeholder="Attribute (e.g. name)"
|
|
1005
|
-
size="s"
|
|
1006
|
-
class="head-add-attr"
|
|
1007
|
-
${ref(_addAttr)}
|
|
1008
|
-
></sp-textfield>
|
|
1009
|
-
<sp-textfield
|
|
1010
|
-
placeholder="Value"
|
|
1011
|
-
size="s"
|
|
1012
|
-
class="head-add-val"
|
|
1013
|
-
${ref(_addVal)}
|
|
1014
|
-
></sp-textfield>
|
|
1015
|
-
<sp-action-button
|
|
1016
|
-
quiet
|
|
1017
|
-
size="xs"
|
|
1018
|
-
title="Add tag"
|
|
1019
|
-
@click=${() => {
|
|
1020
|
-
/* Handles, not a closest() walk back up to a form this same template drew three lines
|
|
1021
|
-
above. The fields are uncontrolled on purpose — the draft is not state anything
|
|
1022
|
-
else reads — so a ref is exactly what reading and clearing them needs. */
|
|
1023
|
-
const tagName = _addTag.value?.value || "meta";
|
|
1024
|
-
const attrKey = _addAttr.value?.value?.trim();
|
|
1025
|
-
const attrVal = _addVal.value?.value?.trim();
|
|
1026
|
-
if (!attrKey || !attrVal) {
|
|
1027
|
-
return;
|
|
1028
|
-
}
|
|
1029
|
-
if (_addAttr.value) {
|
|
1030
|
-
_addAttr.value.value = "";
|
|
1031
|
-
}
|
|
1032
|
-
if (_addVal.value) {
|
|
1033
|
-
_addVal.value.value = "";
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
const entry: JxHeadEntry = { attributes: {}, tagName };
|
|
1037
|
-
if (tagName === "meta") {
|
|
1038
|
-
entry.attributes = { content: attrVal, name: attrKey };
|
|
1039
|
-
} else if (tagName === "link") {
|
|
1040
|
-
entry.attributes = { href: attrVal, rel: attrKey };
|
|
1041
|
-
} else if (tagName === "script") {
|
|
1042
|
-
entry.attributes = { [attrKey]: attrVal };
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
applyMutation((d: JxMutableNode) => {
|
|
1046
|
-
if (!d.$head) {
|
|
1047
|
-
d.$head = [];
|
|
1048
|
-
}
|
|
1049
|
-
d.$head.push(entry);
|
|
1050
|
-
});
|
|
1051
|
-
renderLeftPanel();
|
|
1052
|
-
}}
|
|
1053
|
-
>
|
|
1054
|
-
<sp-icon-add slot="icon" size="xs"></sp-icon-add>
|
|
1055
|
-
</sp-action-button>
|
|
1056
|
-
</div>
|
|
1057
|
-
</div>
|
|
1058
|
-
</div>
|
|
1059
|
-
`;
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
826
|
// ─── Layout picker ───────────────────────────────────────────────────────
|
|
1063
827
|
|
|
1064
828
|
/**
|
|
@@ -1088,131 +852,6 @@ export function isPageDocument(tab: Tab | null): boolean {
|
|
|
1088
852
|
);
|
|
1089
853
|
}
|
|
1090
854
|
|
|
1091
|
-
/**
|
|
1092
|
-
* The layout picker as ONE field row, so the Page panel and the Document Header card render the
|
|
1093
|
-
* same control rather than two that drift.
|
|
1094
|
-
*
|
|
1095
|
-
* Returns `nothing` while the layouts directory is still being listed; the listing schedules a
|
|
1096
|
-
* re-render of both surfaces when it lands.
|
|
1097
|
-
*
|
|
1098
|
-
* @param {JxMutableNode} doc
|
|
1099
|
-
* @param {(fn: (doc: JxMutableNode) => void) => void} applyMutation
|
|
1100
|
-
* @returns {TemplateResult | typeof nothing}
|
|
1101
|
-
*/
|
|
1102
|
-
export function renderLayoutPickerRow(
|
|
1103
|
-
doc: JxMutableNode,
|
|
1104
|
-
applyMutation: (fn: (doc: JxMutableNode) => void) => void,
|
|
1105
|
-
): TemplateResult | typeof nothing {
|
|
1106
|
-
if (layoutEntries === null) {
|
|
1107
|
-
void loadLayoutEntries();
|
|
1108
|
-
return nothing;
|
|
1109
|
-
}
|
|
1110
|
-
const currentLayout = doc.$layout;
|
|
1111
|
-
const defaultLayout = projectState?.projectConfig?.defaults?.layout;
|
|
1112
|
-
const displayValue = currentLayout === false ? "__none__" : currentLayout || "__default__";
|
|
1113
|
-
const defaultLabel = defaultLayout
|
|
1114
|
-
? defaultLayout
|
|
1115
|
-
.replace(/^\.\/layouts\//, "")
|
|
1116
|
-
.replace(/\.json$/, "")
|
|
1117
|
-
.replaceAll(/[-_]+/g, " ")
|
|
1118
|
-
.replaceAll(/\b\w/g, (c: string) => c.toUpperCase())
|
|
1119
|
-
: "";
|
|
1120
|
-
const entries = layoutEntries;
|
|
1121
|
-
return renderFieldRow({
|
|
1122
|
-
hasValue: currentLayout !== undefined,
|
|
1123
|
-
label: "Layout",
|
|
1124
|
-
onClear: () =>
|
|
1125
|
-
applyMutation((d: JxMutableNode) => {
|
|
1126
|
-
delete d.$layout;
|
|
1127
|
-
}),
|
|
1128
|
-
prop: "layout",
|
|
1129
|
-
widget: html`
|
|
1130
|
-
<sp-picker
|
|
1131
|
-
size="s"
|
|
1132
|
-
value=${displayValue}
|
|
1133
|
-
@change=${(e: Event) => {
|
|
1134
|
-
const val = (e.target as HTMLInputElement).value;
|
|
1135
|
-
applyMutation((d: JxMutableNode) => {
|
|
1136
|
-
if (val === "__default__") {
|
|
1137
|
-
delete d.$layout;
|
|
1138
|
-
} else if (val === "__none__") {
|
|
1139
|
-
d.$layout = false;
|
|
1140
|
-
} else {
|
|
1141
|
-
d.$layout = val;
|
|
1142
|
-
}
|
|
1143
|
-
});
|
|
1144
|
-
invalidateLayoutCache();
|
|
1145
|
-
}}
|
|
1146
|
-
>
|
|
1147
|
-
<sp-menu-item value="__default__"
|
|
1148
|
-
>Default${defaultLabel ? ` (${defaultLabel})` : ""}</sp-menu-item
|
|
1149
|
-
>
|
|
1150
|
-
<sp-menu-item value="__none__">None</sp-menu-item>
|
|
1151
|
-
<sp-menu-divider></sp-menu-divider>
|
|
1152
|
-
${entries.map(
|
|
1153
|
-
(l: { name: string; path: string }) =>
|
|
1154
|
-
html`<sp-menu-item value=${l.path}>${l.name}</sp-menu-item>`,
|
|
1155
|
-
)}
|
|
1156
|
-
</sp-picker>
|
|
1157
|
-
`,
|
|
1158
|
-
});
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
/** The Page panel's boxed wrapper around {@link renderLayoutPickerRow}. */
|
|
1162
|
-
function renderLayoutSection(
|
|
1163
|
-
tab: Tab | null,
|
|
1164
|
-
doc: JxMutableNode,
|
|
1165
|
-
applyMutation: (fn: (doc: JxMutableNode) => void) => void,
|
|
1166
|
-
): TemplateResult | typeof nothing {
|
|
1167
|
-
if (!isPageDocument(tab)) {
|
|
1168
|
-
return nothing;
|
|
1169
|
-
}
|
|
1170
|
-
const row = renderLayoutPickerRow(doc, applyMutation);
|
|
1171
|
-
if (row === nothing) {
|
|
1172
|
-
return nothing;
|
|
1173
|
-
}
|
|
1174
|
-
return html`
|
|
1175
|
-
<div class="imports-section">
|
|
1176
|
-
<div class="imports-section-header">
|
|
1177
|
-
<span class="imports-section-title">Layout</span>
|
|
1178
|
-
</div>
|
|
1179
|
-
<div class="head-section-body">${row}</div>
|
|
1180
|
-
</div>
|
|
1181
|
-
`;
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
// ─── Frontmatter section ────────────────────────────────────────────────
|
|
1185
|
-
|
|
1186
|
-
function renderFrontmatterSection() {
|
|
1187
|
-
const tab = activeTab.value;
|
|
1188
|
-
if (!tab) {
|
|
1189
|
-
return nothing;
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
const { collection, fields, hasSchema, requiredFields } = collectFmFields(
|
|
1193
|
-
tab,
|
|
1194
|
-
projectState?.projectConfig,
|
|
1195
|
-
RESERVED_FM_KEYS,
|
|
1196
|
-
);
|
|
1197
|
-
|
|
1198
|
-
if (fields.length === 0 && !hasSchema) {
|
|
1199
|
-
return nothing;
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
return html`
|
|
1203
|
-
<div class="imports-section">
|
|
1204
|
-
<div class="imports-section-header">
|
|
1205
|
-
<span class="imports-section-title"
|
|
1206
|
-
>${collection ? `Frontmatter (${collection.name})` : "Frontmatter"}</span
|
|
1207
|
-
>
|
|
1208
|
-
</div>
|
|
1209
|
-
<div class="head-section-body">
|
|
1210
|
-
${fields.map((f) => renderFmField(tab, f.field, f.entry, f.value, requiredFields))}
|
|
1211
|
-
</div>
|
|
1212
|
-
</div>
|
|
1213
|
-
`;
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
855
|
/** Overlay content-mode frontmatter title/`$head` onto the document the panel edits. */
|
|
1217
856
|
export function buildHeadDoc(doc: JxMutableNode, fm: Record<string, unknown>): JxMutableNode {
|
|
1218
857
|
const title = fm.title as string | undefined;
|
|
@@ -1231,7 +870,7 @@ export function buildHeadDoc(doc: JxMutableNode, fm: Record<string, unknown>): J
|
|
|
1231
870
|
* adapts one to the other in the module that owns both, instead of in the Navigator orchestrator
|
|
1232
871
|
* that owns neither.
|
|
1233
872
|
*
|
|
1234
|
-
* **`tab` is a parameter for the same reason
|
|
873
|
+
* **`tab` is a parameter for the same reason every other frontmatter helper's is.** It resolved
|
|
1235
874
|
* `activeTab.value` itself, and the Document Header card calls it for the CONTENT branch of every
|
|
1236
875
|
* mutation it makes — Title, Clear title, the Layout picker — as does Search appearance, which is
|
|
1237
876
|
* where the head fields went and which takes its tab the same way. The card is drawn per pane, so
|
|
@@ -1267,6 +906,550 @@ export function applyContentMutation(
|
|
|
1267
906
|
rerender();
|
|
1268
907
|
}
|
|
1269
908
|
|
|
909
|
+
// ─── The Page panel's flow ───────────────────────────────────────────────
|
|
910
|
+
|
|
911
|
+
/** What one row of the panel writes. The document knows the control; this knows the document. */
|
|
912
|
+
interface RowCommit {
|
|
913
|
+
/** Remove the value this row shows. */
|
|
914
|
+
clear: () => void;
|
|
915
|
+
/** Commit what the control settled on — a string from every kind but the checkbox. */
|
|
916
|
+
commit: (raw: string | boolean) => void;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/** What the panel is drawn against. `left-panel.ts`'s context, reduced to what this reads. */
|
|
920
|
+
export interface PagePanelContext {
|
|
921
|
+
/** The head-bearing view of the open document — `buildHeadDoc` for a content page. */
|
|
922
|
+
document: JxMutableNode;
|
|
923
|
+
/** How a change to that view is written back into whichever realm it belongs to. */
|
|
924
|
+
applyMutation: (fn: (doc: JxMutableNode) => void) => void;
|
|
925
|
+
renderLeftPanel: () => void;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/** Where the surface is mounted, and the context it was last drawn against. */
|
|
929
|
+
let _host: HTMLElement | null = null;
|
|
930
|
+
let _ctx: PagePanelContext | null = null;
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* The add-a-tag draft — three parts of one entry, so none of them is submitted alone.
|
|
934
|
+
*
|
|
935
|
+
* State rather than `ref()` handles read at submit time: a document binding writes only when the
|
|
936
|
+
* scope moves, so emptying a field after a successful add has to BE a scope change or the added
|
|
937
|
+
* text is left sitting in the control.
|
|
938
|
+
*/
|
|
939
|
+
let _addTag = "meta";
|
|
940
|
+
let _addAttr = "";
|
|
941
|
+
let _addValue = "";
|
|
942
|
+
|
|
943
|
+
/** This projection's rows, by key. Rebuilt with each projection. */
|
|
944
|
+
const _commits = new Map<string, RowCommit>();
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* This projection's custom `$head` entries, by the key they were drawn with.
|
|
948
|
+
*
|
|
949
|
+
* A Remove hands back a key and nothing else, because the rows are a NESTED map and a row inside
|
|
950
|
+
* the inner one cannot reach the section it is under. This is where that key is spent — on the
|
|
951
|
+
* ENTRY OBJECT, so the splice is still an `indexOf` against the document's own array rather than a
|
|
952
|
+
* position that a concurrent edit could have moved.
|
|
953
|
+
*/
|
|
954
|
+
const _entries = new Map<string, JxHeadEntry>();
|
|
955
|
+
|
|
956
|
+
/** Text edits waiting out {@link LIVE_PREVIEW}, one per row key. */
|
|
957
|
+
const _pending = new Map<string, { commit: () => void; timer: ReturnType<typeof setTimeout> }>();
|
|
958
|
+
|
|
959
|
+
/** The elements the add form offers, in the order a reader meets them. */
|
|
960
|
+
const TAG_OPTIONS: PageChoice[] = [
|
|
961
|
+
{ label: "meta", value: "meta" },
|
|
962
|
+
{ label: "link", value: "link" },
|
|
963
|
+
{ label: "script", value: "script" },
|
|
964
|
+
];
|
|
965
|
+
|
|
966
|
+
/** A blank row, so every kind carries every field the document's bindings read. */
|
|
967
|
+
function blankRow(key: string, prop: string, label: string): PageRow {
|
|
968
|
+
return {
|
|
969
|
+
checked: false,
|
|
970
|
+
clearLabel: `Clear ${prop}`,
|
|
971
|
+
hasNote: false,
|
|
972
|
+
hasThumb: false,
|
|
973
|
+
isSet: false,
|
|
974
|
+
key,
|
|
975
|
+
kind: "text",
|
|
976
|
+
label,
|
|
977
|
+
note: "",
|
|
978
|
+
options: [],
|
|
979
|
+
placeholder: "",
|
|
980
|
+
prop,
|
|
981
|
+
thumb: "",
|
|
982
|
+
value: "",
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/** A media row's thumbnail, or the honest absence of one. */
|
|
987
|
+
function thumbFor(row: PageRow, value: string): void {
|
|
988
|
+
row.hasThumb = value !== "" && IMAGE_EXTENSIONS.has(extensionOf(value));
|
|
989
|
+
row.thumb = row.hasThumb ? previewAssetSrc(value) : "";
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/** The one head value you type while writing, in both realms. */
|
|
993
|
+
function titleRow(doc: JxMutableNode, apply: PagePanelContext["applyMutation"]): PageRow {
|
|
994
|
+
const title = typeof doc.title === "string" ? doc.title : "";
|
|
995
|
+
_commits.set("title", {
|
|
996
|
+
clear: () =>
|
|
997
|
+
apply((d) => {
|
|
998
|
+
delete d.title;
|
|
999
|
+
}),
|
|
1000
|
+
commit: (raw) =>
|
|
1001
|
+
apply((d) => {
|
|
1002
|
+
const val = String(raw).trim();
|
|
1003
|
+
if (val) {
|
|
1004
|
+
d.title = val;
|
|
1005
|
+
} else {
|
|
1006
|
+
delete d.title;
|
|
1007
|
+
}
|
|
1008
|
+
}),
|
|
1009
|
+
});
|
|
1010
|
+
return {
|
|
1011
|
+
...blankRow("title", "title", "Title"),
|
|
1012
|
+
clearLabel: "Clear title",
|
|
1013
|
+
isSet: Boolean(title),
|
|
1014
|
+
placeholder: "Page title…",
|
|
1015
|
+
value: title,
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/** One structured `<meta>` field — the four OpenGraph keys, plus description and viewport. */
|
|
1020
|
+
function metaRow(
|
|
1021
|
+
field: MetaField,
|
|
1022
|
+
head: JxHeadEntry[],
|
|
1023
|
+
apply: PagePanelContext["applyMutation"],
|
|
1024
|
+
): PageRow {
|
|
1025
|
+
const value = String(findMetaEntry(head, field.attr, field.key)?.attributes?.content ?? "");
|
|
1026
|
+
const key = `meta:${field.attr}:${field.key}`;
|
|
1027
|
+
_commits.set(key, {
|
|
1028
|
+
clear: () => apply((d) => upsertMeta(d, field.attr, field.key, "")),
|
|
1029
|
+
/* A media field is not trimmed: a path is committed exactly as it was chosen, and the browser
|
|
1030
|
+
and the upload both hand one back already clean. */
|
|
1031
|
+
commit: (raw) =>
|
|
1032
|
+
apply((d) =>
|
|
1033
|
+
upsertMeta(d, field.attr, field.key, field.media ? String(raw) : String(raw).trim()),
|
|
1034
|
+
),
|
|
1035
|
+
});
|
|
1036
|
+
const row = blankRow(key, field.key, field.label);
|
|
1037
|
+
row.isSet = Boolean(value);
|
|
1038
|
+
row.value = value;
|
|
1039
|
+
if (field.media) {
|
|
1040
|
+
row.kind = "media";
|
|
1041
|
+
thumbFor(row, value);
|
|
1042
|
+
return row;
|
|
1043
|
+
}
|
|
1044
|
+
row.kind = field.multiline ? "textarea" : "text";
|
|
1045
|
+
row.placeholder =
|
|
1046
|
+
field.key === "viewport" ? "width=device-width, initial-scale=1" : `${field.label}…`;
|
|
1047
|
+
return row;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/** The favicon, which is a `<link rel="icon">` rather than a `<meta>`. */
|
|
1051
|
+
function iconRow(head: JxHeadEntry[], apply: PagePanelContext["applyMutation"]): PageRow {
|
|
1052
|
+
const value = String(findLinkEntry(head, "icon")?.attributes?.href ?? "");
|
|
1053
|
+
_commits.set("link:icon", {
|
|
1054
|
+
clear: () => apply((d) => upsertLink(d, "icon", "")),
|
|
1055
|
+
commit: (raw) => apply((d) => upsertLink(d, "icon", String(raw))),
|
|
1056
|
+
});
|
|
1057
|
+
const row = blankRow("link:icon", "icon", "Icon");
|
|
1058
|
+
row.isSet = Boolean(value);
|
|
1059
|
+
row.kind = "media";
|
|
1060
|
+
row.value = value;
|
|
1061
|
+
thumbFor(row, value);
|
|
1062
|
+
return row;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* The Layout picker, or `null` when this document takes none — a component has no layout, and a
|
|
1067
|
+
* page whose layouts directory is still being listed has nothing to offer yet.
|
|
1068
|
+
*
|
|
1069
|
+
* The listing is {@link layoutPickerEntries}', which the Document Header card reads too: creating a
|
|
1070
|
+
* layout invalidates one cache rather than two.
|
|
1071
|
+
*/
|
|
1072
|
+
function layoutSection(
|
|
1073
|
+
tab: Tab | null,
|
|
1074
|
+
doc: JxMutableNode,
|
|
1075
|
+
apply: PagePanelContext["applyMutation"],
|
|
1076
|
+
): PageSection | null {
|
|
1077
|
+
if (!isPageDocument(tab)) {
|
|
1078
|
+
return null;
|
|
1079
|
+
}
|
|
1080
|
+
const entries = layoutPickerEntries();
|
|
1081
|
+
if (entries === null) {
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
const current = doc.$layout;
|
|
1085
|
+
const defaultPath = projectState?.projectConfig?.defaults?.layout;
|
|
1086
|
+
const defaultLabel = defaultPath ? layoutDisplayName(defaultPath) : "";
|
|
1087
|
+
_commits.set("__layout", {
|
|
1088
|
+
clear: () =>
|
|
1089
|
+
apply((d) => {
|
|
1090
|
+
delete d.$layout;
|
|
1091
|
+
}),
|
|
1092
|
+
commit: (raw) => {
|
|
1093
|
+
const val = String(raw);
|
|
1094
|
+
apply((d) => {
|
|
1095
|
+
if (val === "__default__") {
|
|
1096
|
+
delete d.$layout;
|
|
1097
|
+
} else if (val === "__none__") {
|
|
1098
|
+
d.$layout = false;
|
|
1099
|
+
} else {
|
|
1100
|
+
d.$layout = val;
|
|
1101
|
+
}
|
|
1102
|
+
});
|
|
1103
|
+
invalidateLayoutCache();
|
|
1104
|
+
},
|
|
1105
|
+
});
|
|
1106
|
+
const row = blankRow("__layout", "layout", "Layout");
|
|
1107
|
+
row.kind = "select";
|
|
1108
|
+
row.isSet = current !== undefined;
|
|
1109
|
+
row.options = [
|
|
1110
|
+
{ label: defaultLabel ? `Default (${defaultLabel})` : "Default", value: "__default__" },
|
|
1111
|
+
{ label: "None", value: "__none__" },
|
|
1112
|
+
...entries.map((l) => ({ label: l.name, value: l.path })),
|
|
1113
|
+
];
|
|
1114
|
+
row.value = current === false ? "__none__" : current || "__default__";
|
|
1115
|
+
return { hasSeo: false, key: "layout", rows: [row], title: "Layout" };
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
/**
|
|
1119
|
+
* The schema-and-frontmatter field list, or `null` when this document has none to show.
|
|
1120
|
+
*
|
|
1121
|
+
* Content documents only: a JSON page's head material lives on the root node, and the section that
|
|
1122
|
+
* would draw it is Page.
|
|
1123
|
+
*/
|
|
1124
|
+
function frontmatterSection(tab: Tab | null): PageSection | null {
|
|
1125
|
+
if (!tab || tab.doc.mode !== "content") {
|
|
1126
|
+
return null;
|
|
1127
|
+
}
|
|
1128
|
+
const { collection, fields, hasSchema, requiredFields } = collectFmFields(
|
|
1129
|
+
tab,
|
|
1130
|
+
projectState?.projectConfig,
|
|
1131
|
+
RESERVED_FM_KEYS,
|
|
1132
|
+
);
|
|
1133
|
+
if (fields.length === 0 && !hasSchema) {
|
|
1134
|
+
return null;
|
|
1135
|
+
}
|
|
1136
|
+
const rows = fields.map((f) => {
|
|
1137
|
+
const key = `fm:${f.field}`;
|
|
1138
|
+
const { parse, row } = projectFmField(f.field, f.entry, f.value, requiredFields, {
|
|
1139
|
+
rerender: sync,
|
|
1140
|
+
});
|
|
1141
|
+
_commits.set(key, {
|
|
1142
|
+
clear: () => transactDoc(tab, (t) => mutateUpdateFrontmatter(t, f.field)),
|
|
1143
|
+
commit: (raw) =>
|
|
1144
|
+
transactDoc(tab, (t) => mutateUpdateFrontmatter(t, f.field, parse(raw) as JsonValue)),
|
|
1145
|
+
});
|
|
1146
|
+
return Object.assign(blankRow(key, f.field, row.label), row, {
|
|
1147
|
+
clearLabel: `Clear ${f.field}`,
|
|
1148
|
+
});
|
|
1149
|
+
});
|
|
1150
|
+
return {
|
|
1151
|
+
hasSeo: false,
|
|
1152
|
+
key: "frontmatter",
|
|
1153
|
+
rows,
|
|
1154
|
+
title: collection ? `Frontmatter (${collection.name})` : "Frontmatter",
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/** The `$head` entries no structured control owns, and the keys a Remove hands back. */
|
|
1159
|
+
function customEntries(head: JxHeadEntry[]): PageTagRow[] {
|
|
1160
|
+
_entries.clear();
|
|
1161
|
+
return head
|
|
1162
|
+
.filter(
|
|
1163
|
+
(e: JxHeadEntry) => !isManagedEntry(e) && !isGoogleFontEntry(e) && !isGoogleFontPreconnect(e),
|
|
1164
|
+
)
|
|
1165
|
+
.map((entry, index) => {
|
|
1166
|
+
const key = `tag:${index}`;
|
|
1167
|
+
_entries.set(key, entry);
|
|
1168
|
+
const label = entryLabel(entry);
|
|
1169
|
+
return { key, label, removeLabel: `Remove ${label}`, value: entryValue(entry) };
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* Everything the panel says about one document, and the commit table behind it.
|
|
1175
|
+
*
|
|
1176
|
+
* `activeTab.value` is read HERE and nowhere below it: the Navigator's Page panel is an app-level
|
|
1177
|
+
* surface that shows the focused document by definition (§3.2), and spelling that out once at the
|
|
1178
|
+
* top is what lets every helper under it take a tab instead of asking for one.
|
|
1179
|
+
*/
|
|
1180
|
+
function view(ctx: PagePanelContext): PagePanelView {
|
|
1181
|
+
_commits.clear();
|
|
1182
|
+
const tab = activeTab.value;
|
|
1183
|
+
const doc = ctx.document;
|
|
1184
|
+
const head = doc.$head ?? [];
|
|
1185
|
+
const sections: PageSection[] = [];
|
|
1186
|
+
|
|
1187
|
+
const frontmatter = frontmatterSection(tab);
|
|
1188
|
+
if (frontmatter) {
|
|
1189
|
+
sections.push(frontmatter);
|
|
1190
|
+
}
|
|
1191
|
+
const layout = layoutSection(tab, doc, ctx.applyMutation);
|
|
1192
|
+
if (layout) {
|
|
1193
|
+
sections.push(layout);
|
|
1194
|
+
}
|
|
1195
|
+
sections.push(
|
|
1196
|
+
{
|
|
1197
|
+
hasSeo: true,
|
|
1198
|
+
key: "page",
|
|
1199
|
+
rows: [
|
|
1200
|
+
titleRow(doc, ctx.applyMutation),
|
|
1201
|
+
...PAGE_FIELDS.map((field) => metaRow(field, head, ctx.applyMutation)),
|
|
1202
|
+
iconRow(head, ctx.applyMutation),
|
|
1203
|
+
],
|
|
1204
|
+
title: "Page",
|
|
1205
|
+
},
|
|
1206
|
+
{
|
|
1207
|
+
hasSeo: false,
|
|
1208
|
+
key: "opengraph",
|
|
1209
|
+
rows: OG_FIELDS.map((field) => metaRow(field, head, ctx.applyMutation)),
|
|
1210
|
+
title: "OpenGraph",
|
|
1211
|
+
},
|
|
1212
|
+
);
|
|
1213
|
+
|
|
1214
|
+
const entries = customEntries(head);
|
|
1215
|
+
return {
|
|
1216
|
+
addAttr: _addAttr,
|
|
1217
|
+
addTag: _addTag,
|
|
1218
|
+
addValue: _addValue,
|
|
1219
|
+
customCount: String(entries.length),
|
|
1220
|
+
customEntries: entries,
|
|
1221
|
+
customState: entries.length === 0 ? "empty" : "listed",
|
|
1222
|
+
sections,
|
|
1223
|
+
tagOptions: TAG_OPTIONS,
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/** Push the current projection into the standing surface, in this turn. */
|
|
1228
|
+
function sync(): void {
|
|
1229
|
+
if (!_host || !_ctx) {
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
renderPagePanelSurface(_host, view(_ctx), ACTIONS);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// ─── The verbs ───────────────────────────────────────────────────────────
|
|
1236
|
+
|
|
1237
|
+
/** Cancel every waiting text edit, without committing any of them. */
|
|
1238
|
+
function dropPending(): void {
|
|
1239
|
+
for (const { timer } of _pending.values()) {
|
|
1240
|
+
clearTimeout(timer);
|
|
1241
|
+
}
|
|
1242
|
+
_pending.clear();
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/** Drop a text edit that was still waiting out its debounce. */
|
|
1246
|
+
function flushPending(key: string): void {
|
|
1247
|
+
const waiting = _pending.get(key);
|
|
1248
|
+
if (waiting) {
|
|
1249
|
+
clearTimeout(waiting.timer);
|
|
1250
|
+
_pending.delete(key);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
/** Commit now, cancelling whatever was waiting for the same row. */
|
|
1255
|
+
function commitNow(key: string, raw: string | boolean): void {
|
|
1256
|
+
flushPending(key);
|
|
1257
|
+
_commits.get(key)?.commit(raw);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
/**
|
|
1261
|
+
* Add the drafted tag to `$head`.
|
|
1262
|
+
*
|
|
1263
|
+
* The three parts are validated together and the two typed ones are cleared BEFORE the write, so a
|
|
1264
|
+
* repaint caused by the mutation finds an empty form rather than the text that has just been
|
|
1265
|
+
* committed. The tag picker keeps its choice: adding two `<link>`s in a row is the common case.
|
|
1266
|
+
*/
|
|
1267
|
+
function addEntry(): void {
|
|
1268
|
+
const ctx = _ctx;
|
|
1269
|
+
const attrKey = _addAttr.trim();
|
|
1270
|
+
const attrVal = _addValue.trim();
|
|
1271
|
+
if (!ctx || !attrKey || !attrVal) {
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const tagName = _addTag || "meta";
|
|
1275
|
+
_addAttr = "";
|
|
1276
|
+
_addValue = "";
|
|
1277
|
+
sync();
|
|
1278
|
+
|
|
1279
|
+
const entry: JxHeadEntry = { attributes: {}, tagName };
|
|
1280
|
+
if (tagName === "meta") {
|
|
1281
|
+
entry.attributes = { content: attrVal, name: attrKey };
|
|
1282
|
+
} else if (tagName === "link") {
|
|
1283
|
+
entry.attributes = { href: attrVal, rel: attrKey };
|
|
1284
|
+
} else if (tagName === "script") {
|
|
1285
|
+
entry.attributes = { [attrKey]: attrVal };
|
|
1286
|
+
}
|
|
1287
|
+
ctx.applyMutation((d: JxMutableNode) => {
|
|
1288
|
+
if (!d.$head) {
|
|
1289
|
+
d.$head = [];
|
|
1290
|
+
}
|
|
1291
|
+
d.$head.push(entry);
|
|
1292
|
+
});
|
|
1293
|
+
ctx.renderLeftPanel();
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
/** Take one custom `$head` entry away, by the entry the key was drawn for. */
|
|
1297
|
+
function removeEntry(key: string): void {
|
|
1298
|
+
const ctx = _ctx;
|
|
1299
|
+
const entry = _entries.get(key);
|
|
1300
|
+
if (!ctx || !entry) {
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
ctx.applyMutation((d: JxMutableNode) => {
|
|
1304
|
+
if (!d.$head) {
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
const idx = d.$head.indexOf(entry);
|
|
1308
|
+
if (idx !== -1) {
|
|
1309
|
+
d.$head.splice(idx, 1);
|
|
1310
|
+
}
|
|
1311
|
+
});
|
|
1312
|
+
ctx.renderLeftPanel();
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
/**
|
|
1316
|
+
* The media picker's two behaviours, imported on the first press.
|
|
1317
|
+
*
|
|
1318
|
+
* ONE cached promise, deliberately: two dynamic imports of a module in flight at once is the shape
|
|
1319
|
+
* that loses its coverage record (see the note in the repository's agent guide), and the file also
|
|
1320
|
+
* pulls in the upload pipeline and the media metadata cache — neither of which a panel nobody has
|
|
1321
|
+
* pressed Browse in should ever load.
|
|
1322
|
+
*/
|
|
1323
|
+
let _mediaPicker: Promise<typeof MediaPickerModule> | null = null;
|
|
1324
|
+
|
|
1325
|
+
function mediaPicker(): Promise<typeof MediaPickerModule> {
|
|
1326
|
+
_mediaPicker ??= import("../ui/media-picker");
|
|
1327
|
+
return _mediaPicker;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
/**
|
|
1331
|
+
* What the reader may do. One set for the module, because the panel's whole state is the module's —
|
|
1332
|
+
* one focused document, and one draft of each field across every section.
|
|
1333
|
+
*
|
|
1334
|
+
* The three `edit*` entries are ECHOES: the scope has to be told what a field now holds even though
|
|
1335
|
+
* nothing else about the panel changes, because emptying it afterwards is otherwise a write of `""`
|
|
1336
|
+
* over a scope that already said `""` — no change, no binding, and the submitted text left sitting
|
|
1337
|
+
* in the field.
|
|
1338
|
+
*/
|
|
1339
|
+
const ACTIONS: PagePanelActions = {
|
|
1340
|
+
addEntry,
|
|
1341
|
+
browse: (key, anchor) => {
|
|
1342
|
+
flushPending(key);
|
|
1343
|
+
void mediaPicker().then((m) => {
|
|
1344
|
+
if (anchor instanceof HTMLElement) {
|
|
1345
|
+
m.showMediaPickerPopover(anchor, (val: string) => {
|
|
1346
|
+
_commits.get(key)?.commit(val);
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
});
|
|
1350
|
+
},
|
|
1351
|
+
clear: (key) => {
|
|
1352
|
+
flushPending(key);
|
|
1353
|
+
_commits.get(key)?.clear();
|
|
1354
|
+
},
|
|
1355
|
+
commitText: (key, value) => commitNow(key, value),
|
|
1356
|
+
editAttr: (value) => {
|
|
1357
|
+
_addAttr = value;
|
|
1358
|
+
sync();
|
|
1359
|
+
},
|
|
1360
|
+
/* Debounced, so the canvas follows the typing without a document write per keystroke. The control
|
|
1361
|
+
is NOT reset in the meantime: the projection this commit causes resolves to the text already in
|
|
1362
|
+
the field, and a document binding skips a write equal to what the element holds. */
|
|
1363
|
+
editText: (key, value) => {
|
|
1364
|
+
flushPending(key);
|
|
1365
|
+
const commit = () => {
|
|
1366
|
+
_pending.delete(key);
|
|
1367
|
+
_commits.get(key)?.commit(value);
|
|
1368
|
+
};
|
|
1369
|
+
_pending.set(key, { commit, timer: setTimeout(commit, LIVE_PREVIEW) });
|
|
1370
|
+
},
|
|
1371
|
+
editTag: (value) => {
|
|
1372
|
+
_addTag = value;
|
|
1373
|
+
sync();
|
|
1374
|
+
},
|
|
1375
|
+
editValue: (value) => {
|
|
1376
|
+
_addValue = value;
|
|
1377
|
+
sync();
|
|
1378
|
+
},
|
|
1379
|
+
openSeo: () => {
|
|
1380
|
+
/* The COMMAND, not a local open() — the Document Header card offers the same door and neither
|
|
1381
|
+
of them owns it, so the palette has it by name. */
|
|
1382
|
+
void activeRegistry()?.run("document.openSeo");
|
|
1383
|
+
},
|
|
1384
|
+
removeEntry,
|
|
1385
|
+
setBoolean: (key, checked) => commitNow(key, checked),
|
|
1386
|
+
setChoice: (key, value) => commitNow(key, value),
|
|
1387
|
+
setNumber: (key, value) => commitNow(key, value),
|
|
1388
|
+
upload: (key) => {
|
|
1389
|
+
flushPending(key);
|
|
1390
|
+
void mediaPicker().then((m) => {
|
|
1391
|
+
m.pickAndUpload((val: string) => {
|
|
1392
|
+
_commits.get(key)?.commit(val);
|
|
1393
|
+
});
|
|
1394
|
+
});
|
|
1395
|
+
},
|
|
1396
|
+
};
|
|
1397
|
+
|
|
1398
|
+
/**
|
|
1399
|
+
* Draw the panel into `host`, or bring the one already there up to date.
|
|
1400
|
+
*
|
|
1401
|
+
* @param {HTMLElement} host The panel's content area.
|
|
1402
|
+
* @param {PagePanelContext} ctx What the panel is drawn against.
|
|
1403
|
+
*/
|
|
1404
|
+
export function renderPagePanel(host: HTMLElement, ctx: PagePanelContext): void {
|
|
1405
|
+
if (host !== _host) {
|
|
1406
|
+
/* A new content area is a new panel, and a half-typed tag belongs to the surface that was
|
|
1407
|
+
showing it. Switching panels and back is NOT this — lit reuses the same `.panel-content`, so
|
|
1408
|
+
the draft survives exactly as the `ref()` handles it replaced used to. */
|
|
1409
|
+
_addTag = "meta";
|
|
1410
|
+
_addAttr = "";
|
|
1411
|
+
_addValue = "";
|
|
1412
|
+
dropPending();
|
|
1413
|
+
}
|
|
1414
|
+
if (ctx.document !== _ctx?.document) {
|
|
1415
|
+
/* A debounced edit belongs to the DOCUMENT it was typed into, and the host does not change when
|
|
1416
|
+
the reader switches tabs — so cancelling only on a new host let a queued commit outlive its
|
|
1417
|
+
subject and land, `LIVE_PREVIEW` later, on whatever document was open by then. It surfaced as
|
|
1418
|
+
a test that received another test's half-typed `content`, which is the same event with the
|
|
1419
|
+
tabs replaced by test cases. */
|
|
1420
|
+
dropPending();
|
|
1421
|
+
}
|
|
1422
|
+
_host = host;
|
|
1423
|
+
_ctx = ctx;
|
|
1424
|
+
sync();
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
/**
|
|
1428
|
+
* The Navigator's content area inside a panel body.
|
|
1429
|
+
*
|
|
1430
|
+
* `afterRender` is handed the `.panel-body`, and the panel's own content goes one level in — which
|
|
1431
|
+
* is where the document must be mounted rather than beside it, because that child is lit's part and
|
|
1432
|
+
* clearing it is how switching to another panel takes this surface down. A body drawn by the
|
|
1433
|
+
* fallback path has no content area; mounting into the body itself is then still correct.
|
|
1434
|
+
*/
|
|
1435
|
+
function contentArea(body: HTMLElement): HTMLElement {
|
|
1436
|
+
return body.querySelector<HTMLElement>(".panel-content") ?? body;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
* The Navigator no longer draws this panel with lit.
|
|
1441
|
+
*
|
|
1442
|
+
* The record below returns `nothing` and mounts its document in `afterRender`, so this is a stub:
|
|
1443
|
+
* it survives only because `NavigatorPanelDeps` still declares the injection and `studio.ts` still
|
|
1444
|
+
* passes it. Both go in the change that deletes this.
|
|
1445
|
+
*
|
|
1446
|
+
* @deprecated The panel is `surfaces/panel-page.json`; call {@link renderPagePanel}.
|
|
1447
|
+
* @returns {typeof nothing}
|
|
1448
|
+
*/
|
|
1449
|
+
export function renderHeadTemplate(_against: PagePanelContext): typeof nothing {
|
|
1450
|
+
return nothing;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1270
1453
|
/**
|
|
1271
1454
|
* Contribute the Page panel.
|
|
1272
1455
|
*
|
|
@@ -1281,18 +1464,17 @@ export function registerPagePanel(): void {
|
|
|
1281
1464
|
title: "Page",
|
|
1282
1465
|
level: "document",
|
|
1283
1466
|
dock: "navigator",
|
|
1284
|
-
icon: "
|
|
1467
|
+
icon: "file",
|
|
1285
1468
|
requiresDocument: "Open a page to edit its title, description and social preview.",
|
|
1286
|
-
render: (
|
|
1469
|
+
render: () => nothing,
|
|
1470
|
+
afterRender: (ctx, host) => {
|
|
1287
1471
|
const doc = ctx.doc!;
|
|
1288
1472
|
const isContent = doc.mode === "content";
|
|
1289
1473
|
const fm = doc.content?.frontmatter ?? {};
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
// Definition, so it spells that out at the call site now instead of leaving it to a helper
|
|
1295
|
-
// Two other surfaces share.
|
|
1474
|
+
renderPagePanel(contentArea(host), {
|
|
1475
|
+
/* The Navigator's Page panel IS an app-level surface: it shows the focused document by
|
|
1476
|
+
definition, so it spells that out at the call site instead of leaving it to a helper two
|
|
1477
|
+
other surfaces share. */
|
|
1296
1478
|
applyMutation: isContent
|
|
1297
1479
|
? (fn) => applyContentMutation(activeTab.value, ctx.rerender, fn)
|
|
1298
1480
|
: (fn) => {
|