@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
|
@@ -1,31 +1,372 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* The Palette — one omnibox, several modes (UX-REDESIGN-PLAN §5.4).
|
|
4
|
+
*
|
|
5
|
+
* This file used to be a filename-substring finder with one hidden domain swap in it: with no
|
|
6
|
+
* project open the same trigger, the same chrome and the same placeholder silently listed recent
|
|
7
|
+
* PROJECTS instead. It is now the surface the whole shell shrinks into — the place a capability
|
|
8
|
+
* goes when it is retired from the chrome (§2 principle 9), which only works if everything is
|
|
9
|
+
* reachable here by name, with its chord printed beside it.
|
|
10
|
+
*
|
|
11
|
+
* Four properties earn that:
|
|
12
|
+
*
|
|
13
|
+
* 1. **Modes are named, prefixed and enumerable.** `>` commands, `@` document nodes, plain text files,
|
|
14
|
+
* and `?` lists them. The mode is echoed as a REMOVABLE CHIP, so the no-project case is a stated
|
|
15
|
+
* `Recent Projects` mode rather than a swap you have to infer. `PALETTE_MODES` is the namespace:
|
|
16
|
+
* P4's Problems and P7's content search are new members, not a new widget.
|
|
17
|
+
* 2. **Files are matched by fuzzy subsequence over the FULL PATH.** `pgblog` finds
|
|
18
|
+
* `pages/blog/index.md`. The backend glob (`**\/*q*.{json,md}`) can only do a basename
|
|
19
|
+
* substring, so the palette asks it once for the document set and ranks locally — which is also
|
|
20
|
+
* what lets the ranking prefer a basename hit, a word boundary and a consecutive run, in that
|
|
21
|
+
* order.
|
|
22
|
+
* 3. **Every row prints its binding.** That is how a large surface stays discoverable without adding
|
|
23
|
+
* chrome, and it is the mechanism by which the palette teaches the keyboard it replaced.
|
|
24
|
+
* 4. **An unavailable command is GREYED, not hidden**, with its `requires` sentence as the subtitle
|
|
25
|
+
* (§2 principle 4). "Why can't I" is the question a palette is uniquely good at answering, and a
|
|
26
|
+
* row that vanishes answers it with silence.
|
|
27
|
+
*
|
|
28
|
+
* The exported names still read `…QuickSearch` because `src/studio.ts` and `src/panels/empty-state.
|
|
29
|
+
* ts` import them and neither is this workstream's file. They are the palette's; the rename is one
|
|
30
|
+
* find-replace in those two call sites.
|
|
31
|
+
*/
|
|
32
|
+
|
|
2
33
|
import { html, render as litRender, nothing } from "lit-html";
|
|
3
34
|
import { classMap } from "lit-html/directives/class-map.js";
|
|
4
35
|
import { live } from "lit-html/directives/live.js";
|
|
5
36
|
import { ref } from "lit-html/directives/ref.js";
|
|
6
37
|
import { getPlatform } from "../platform";
|
|
7
|
-
import { projectState } from "../store";
|
|
38
|
+
import { flattenTree, nodeLabel, projectState } from "../store";
|
|
8
39
|
import { documentExtensions, formatByExtension, loadFormats } from "../format/format-host";
|
|
9
40
|
import { openFileInTab } from "../files/files";
|
|
10
41
|
import { getRecentFiles, getRecentProjects, trackRecentFile } from "../recent-projects";
|
|
11
42
|
import { getLayerSlot } from "../ui/layers";
|
|
43
|
+
import { activeTab } from "../workspace/workspace";
|
|
44
|
+
import { activeRegistry } from "../commands/active-registry";
|
|
45
|
+
import type { PaletteMode } from "../commands/defaults";
|
|
46
|
+
import type { AnyCommand, CommandRegistry } from "../commands/registry";
|
|
47
|
+
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
48
|
+
import type { JxPath } from "../state";
|
|
49
|
+
|
|
50
|
+
export type { PaletteMode } from "../commands/defaults";
|
|
51
|
+
|
|
52
|
+
// ─── The mode namespace ───────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
/** One palette mode: its prefix, the chip it echoes, and what it says when it is empty. */
|
|
55
|
+
export interface PaletteModeSpec {
|
|
56
|
+
mode: PaletteMode;
|
|
57
|
+
/** The one character that switches into this mode from any other. `""` = the plain mode. */
|
|
58
|
+
prefix: string;
|
|
59
|
+
/** The removable chip's text, and the row label in `?`. */
|
|
60
|
+
chip: string;
|
|
61
|
+
placeholder: string;
|
|
62
|
+
/** What `?` prints under the mode's name. One sentence, no noun phrases (§2 principle 6). */
|
|
63
|
+
description: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Every mode, in the order `?` lists them.
|
|
68
|
+
*
|
|
69
|
+
* Declared as data rather than as a switch because three consumers read it: the prefix parser, the
|
|
70
|
+
* chip, and the `?` listing itself — which is what makes the palette able to teach its own
|
|
71
|
+
* namespace instead of hiding it in a docs page.
|
|
72
|
+
*/
|
|
73
|
+
export const PALETTE_MODES: readonly PaletteModeSpec[] = [
|
|
74
|
+
{
|
|
75
|
+
mode: "files",
|
|
76
|
+
prefix: "",
|
|
77
|
+
chip: "Files",
|
|
78
|
+
placeholder: "Go to a file…",
|
|
79
|
+
description: "Open a file by any part of its path — pgblog finds pages/blog/index.md.",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
mode: "commands",
|
|
83
|
+
prefix: ">",
|
|
84
|
+
chip: "Commands",
|
|
85
|
+
placeholder: "Run a command…",
|
|
86
|
+
description: "Run anything Studio can do, with its keyboard shortcut printed beside it.",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
mode: "nodes",
|
|
90
|
+
prefix: "@",
|
|
91
|
+
chip: "Symbols",
|
|
92
|
+
placeholder: "Go to an element in this document…",
|
|
93
|
+
description: "Select an element in the open document by its Outline name.",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
mode: "projects",
|
|
97
|
+
prefix: "",
|
|
98
|
+
chip: "Recent Projects",
|
|
99
|
+
placeholder: "Open a recent project…",
|
|
100
|
+
description: "Reopen a project you have had open before.",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
mode: "picker",
|
|
104
|
+
prefix: "?",
|
|
105
|
+
chip: "Modes",
|
|
106
|
+
placeholder: "Type ? for modes, > for commands, @ for elements…",
|
|
107
|
+
description: "List these modes.",
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const MODE_SPEC = new Map(PALETTE_MODES.map((spec) => [spec.mode, spec]));
|
|
112
|
+
|
|
113
|
+
/** The modes a prefix character switches into. `files` and `projects` have no prefix. */
|
|
114
|
+
const PREFIXED_MODES = PALETTE_MODES.filter((spec) => spec.prefix !== "");
|
|
115
|
+
|
|
116
|
+
/** The spec for a mode. Total: every {@link PaletteMode} is declared above. */
|
|
117
|
+
export function modeSpec(mode: PaletteMode): PaletteModeSpec {
|
|
118
|
+
return MODE_SPEC.get(mode) ?? PALETTE_MODES[0]!;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The mode a raw input actually addresses.
|
|
123
|
+
*
|
|
124
|
+
* Three rules, in order:
|
|
125
|
+
*
|
|
126
|
+
* - A leading prefix character wins, whatever the palette was opened as — that is what `>` and `@`
|
|
127
|
+
* are FOR, and it is why the prefix is stripped out of the query and into the chip.
|
|
128
|
+
* - `picker` with an empty query lists the modes; `picker` with anything typed means files, because
|
|
129
|
+
* ⌘K then typing a filename is the gesture every omnibox has trained people to expect.
|
|
130
|
+
* - Files with no project open is `Recent Projects`. This is the one substitution the old widget made
|
|
131
|
+
* silently; here the chip says so, and `Project: Open Recent…` names it from the outside.
|
|
132
|
+
*/
|
|
133
|
+
export function resolvePaletteMode(
|
|
134
|
+
requested: PaletteMode,
|
|
135
|
+
raw: string,
|
|
136
|
+
projectOpen: boolean,
|
|
137
|
+
): { mode: PaletteMode; query: string } {
|
|
138
|
+
const prefixed = PREFIXED_MODES.find((spec) => raw.startsWith(spec.prefix));
|
|
139
|
+
if (prefixed) {
|
|
140
|
+
return resolvePaletteMode(prefixed.mode, raw.slice(1), projectOpen);
|
|
141
|
+
}
|
|
142
|
+
let mode = requested;
|
|
143
|
+
if (mode === "picker" && raw.trim() !== "") {
|
|
144
|
+
mode = "files";
|
|
145
|
+
}
|
|
146
|
+
if (mode === "files" && !projectOpen) {
|
|
147
|
+
mode = "projects";
|
|
148
|
+
}
|
|
149
|
+
return { mode, query: raw };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ─── Fuzzy subsequence ranking ────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
/** Characters after which a match counts as starting a word — the boundaries a path has. */
|
|
155
|
+
const BOUNDARY = new Set(["/", "-", "_", ".", " "]);
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Score `text` against `query` as a subsequence, or `null` when it is not one.
|
|
159
|
+
*
|
|
160
|
+
* Higher is better. The weights encode what a person means when they type six characters at a file
|
|
161
|
+
* tree: a consecutive run is worth much more than scattered letters, a match that starts a path
|
|
162
|
+
* segment is worth more than one mid-word, and — decisively — a match inside the BASENAME beats one
|
|
163
|
+
* in a directory, so typing `index` does not bury `index.md` under `pages/index-partials/x.md`.
|
|
164
|
+
*
|
|
165
|
+
* An empty query scores 0 and matches everything, which is what makes "show me the recents" and
|
|
166
|
+
* "show me every file" the same code path.
|
|
167
|
+
*/
|
|
168
|
+
export function fuzzyScore(text: string, query: string): number | null {
|
|
169
|
+
if (query === "") {
|
|
170
|
+
return 0;
|
|
171
|
+
}
|
|
172
|
+
const haystack = text.toLowerCase();
|
|
173
|
+
const needle = query.toLowerCase();
|
|
174
|
+
const basenameStart = haystack.lastIndexOf("/") + 1;
|
|
175
|
+
let score = 0;
|
|
176
|
+
let cursor = 0;
|
|
177
|
+
let previousIndex = -2;
|
|
178
|
+
for (const char of needle) {
|
|
179
|
+
const index = haystack.indexOf(char, cursor);
|
|
180
|
+
if (index === -1) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
if (index === previousIndex + 1) {
|
|
184
|
+
score += 8;
|
|
185
|
+
}
|
|
186
|
+
if (index === 0 || BOUNDARY.has(haystack[index - 1] ?? "")) {
|
|
187
|
+
score += 6;
|
|
188
|
+
}
|
|
189
|
+
if (index >= basenameStart) {
|
|
190
|
+
score += 4;
|
|
191
|
+
}
|
|
192
|
+
previousIndex = index;
|
|
193
|
+
cursor = index + 1;
|
|
194
|
+
}
|
|
195
|
+
// Two tie-breaks, both small enough never to outrank a real structural signal: an earlier first
|
|
196
|
+
// Match, and a shorter path — `pages/index.md` over `pages/blog/drafts/index.md`.
|
|
197
|
+
return score - previousIndex * 0.01 - haystack.length * 0.001;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Rank candidates by {@link fuzzyScore}, dropping non-matches. Stable within equal scores. */
|
|
201
|
+
export function rankBy<T>(items: readonly T[], query: string, key: (item: T) => string): T[] {
|
|
202
|
+
const scored: { item: T; score: number; index: number }[] = [];
|
|
203
|
+
for (const [index, item] of items.entries()) {
|
|
204
|
+
const score = fuzzyScore(key(item), query);
|
|
205
|
+
if (score !== null) {
|
|
206
|
+
scored.push({ index, item, score });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return scored
|
|
210
|
+
.toSorted((a, b) => b.score - a.score || a.index - b.index)
|
|
211
|
+
.map((entry) => entry.item);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ─── Argument prompts ─────────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
/** One offered value for a command's single argument. */
|
|
217
|
+
export interface PaletteArgChoice {
|
|
218
|
+
value: unknown;
|
|
219
|
+
label: string;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** What the palette can do with a command's `args` schema. */
|
|
223
|
+
export type PaletteArgs =
|
|
224
|
+
| { kind: "none" }
|
|
225
|
+
| { kind: "choice"; name: string; choices: PaletteArgChoice[] }
|
|
226
|
+
| { kind: "unsupported" };
|
|
227
|
+
|
|
228
|
+
interface ArgsSchemaShape {
|
|
229
|
+
properties?: Record<string, { type?: string; enum?: readonly unknown[]; description?: string }>;
|
|
230
|
+
required?: readonly string[];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* How a command's arguments are asked for.
|
|
235
|
+
*
|
|
236
|
+
* The palette can prompt for exactly one closed value space — an `enum` or a boolean — because that
|
|
237
|
+
* is a LIST, and a list is the one thing this widget is. `canvas.setZoom { zoom: number }` and
|
|
238
|
+
* `selection.set { path }` are `"unsupported"`, so command mode does not render them: a row that
|
|
239
|
+
* cannot be completed is worse than an absent one, and both are reachable from the surface that
|
|
240
|
+
* owns them (the zoom cluster, the canvas). This is a property of the SCHEMA, not a list of ids, so
|
|
241
|
+
* a command that gains an enum argument becomes promptable without anyone editing this file.
|
|
242
|
+
*/
|
|
243
|
+
export function paletteArgs(command: AnyCommand): PaletteArgs {
|
|
244
|
+
const schema = command.args as ArgsSchemaShape | undefined;
|
|
245
|
+
const properties = schema?.properties;
|
|
246
|
+
if (!schema || !properties) {
|
|
247
|
+
return { kind: "none" };
|
|
248
|
+
}
|
|
249
|
+
const names = Object.keys(properties);
|
|
250
|
+
if (names.length !== 1) {
|
|
251
|
+
return { kind: "unsupported" };
|
|
252
|
+
}
|
|
253
|
+
const name = names[0]!;
|
|
254
|
+
const property = properties[name]!;
|
|
255
|
+
if (property.enum) {
|
|
256
|
+
return {
|
|
257
|
+
kind: "choice",
|
|
258
|
+
name,
|
|
259
|
+
choices: property.enum.map((value) => ({ label: String(value), value })),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
if (property.type === "boolean") {
|
|
263
|
+
return {
|
|
264
|
+
kind: "choice",
|
|
265
|
+
name,
|
|
266
|
+
choices: [
|
|
267
|
+
{ label: "on", value: true },
|
|
268
|
+
{ label: "off", value: false },
|
|
269
|
+
],
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
return { kind: "unsupported" };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ─── Recently-used commands ───────────────────────────────────────────────────
|
|
12
276
|
|
|
13
277
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
278
|
+
* The ids the combobox relationship is built from (WAI-ARIA APG, combobox with listbox popup).
|
|
279
|
+
*
|
|
280
|
+
* The input already said `role="combobox"`, but nothing connected it to the list beneath: no
|
|
281
|
+
* `aria-controls`, so a screen reader could not find the popup, and no `aria-activedescendant`, so
|
|
282
|
+
* arrowing through the results moved a visual highlight and announced nothing. `aria-expanded` was
|
|
283
|
+
* the literal string `"true"`, which claims a popup is showing even when the query matched
|
|
284
|
+
* nothing.
|
|
17
285
|
*/
|
|
18
|
-
|
|
286
|
+
const QUICK_SEARCH_LISTBOX_ID = "quick-search-listbox";
|
|
287
|
+
|
|
288
|
+
/** A stable per-row id, so `aria-activedescendant` has something to point at. */
|
|
289
|
+
function optionId(index: number): string {
|
|
290
|
+
return `quick-search-option-${index}`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const RECENT_COMMANDS_KEY = "jx-studio-recent-commands";
|
|
294
|
+
const RECENT_COMMANDS_MAX = 6;
|
|
295
|
+
|
|
296
|
+
/** The ids most recently run FROM THE PALETTE, newest first. Pinned above the rest of the list. */
|
|
297
|
+
export function getRecentCommands(): string[] {
|
|
298
|
+
try {
|
|
299
|
+
const raw: unknown = JSON.parse(localStorage.getItem(RECENT_COMMANDS_KEY) || "[]");
|
|
300
|
+
return Array.isArray(raw) ? raw.filter((id) => typeof id === "string") : [];
|
|
301
|
+
} catch {
|
|
302
|
+
return [];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function trackRecentCommand(id: string): void {
|
|
307
|
+
const next = [id, ...getRecentCommands().filter((known) => known !== id)].slice(
|
|
308
|
+
0,
|
|
309
|
+
RECENT_COMMANDS_MAX,
|
|
310
|
+
);
|
|
311
|
+
try {
|
|
312
|
+
localStorage.setItem(RECENT_COMMANDS_KEY, JSON.stringify(next));
|
|
313
|
+
} catch {
|
|
314
|
+
// Storage full or unavailable — the command still ran, it is just not remembered.
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ─── Rows ─────────────────────────────────────────────────────────────────────
|
|
319
|
+
|
|
320
|
+
interface FileRow {
|
|
19
321
|
kind: "file";
|
|
20
322
|
path: string;
|
|
21
323
|
name: string;
|
|
324
|
+
/** Directory part, shown as the subtitle. */
|
|
325
|
+
detail: string;
|
|
326
|
+
recent: boolean;
|
|
22
327
|
}
|
|
23
|
-
interface
|
|
328
|
+
interface ProjectRow {
|
|
24
329
|
kind: "project";
|
|
25
330
|
root: string;
|
|
26
331
|
name: string;
|
|
332
|
+
detail: string;
|
|
333
|
+
}
|
|
334
|
+
interface CommandRow {
|
|
335
|
+
kind: "command";
|
|
336
|
+
id: string;
|
|
337
|
+
/** `"View: Zen Mode"` — the palette's own naming convention (§5.1). */
|
|
338
|
+
name: string;
|
|
339
|
+
/** The `requires` sentence when disabled; otherwise empty. */
|
|
340
|
+
detail: string;
|
|
341
|
+
chord: string;
|
|
342
|
+
enabled: boolean;
|
|
343
|
+
recent: boolean;
|
|
344
|
+
}
|
|
345
|
+
interface NodeRow {
|
|
346
|
+
kind: "node";
|
|
347
|
+
path: JxPath;
|
|
348
|
+
name: string;
|
|
349
|
+
detail: string;
|
|
350
|
+
}
|
|
351
|
+
interface ModeRow {
|
|
352
|
+
kind: "mode";
|
|
353
|
+
mode: PaletteMode;
|
|
354
|
+
name: string;
|
|
355
|
+
detail: string;
|
|
356
|
+
chord: string;
|
|
27
357
|
}
|
|
28
|
-
|
|
358
|
+
interface ArgRow {
|
|
359
|
+
kind: "arg";
|
|
360
|
+
commandId: string;
|
|
361
|
+
argName: string;
|
|
362
|
+
value: unknown;
|
|
363
|
+
name: string;
|
|
364
|
+
detail: string;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
type PaletteRow = FileRow | ProjectRow | CommandRow | NodeRow | ModeRow | ArgRow;
|
|
368
|
+
|
|
369
|
+
// ─── State ────────────────────────────────────────────────────────────────────
|
|
29
370
|
|
|
30
371
|
interface QuickCtx {
|
|
31
372
|
openRecentProject: (root: string) => void | Promise<void>;
|
|
@@ -33,108 +374,279 @@ interface QuickCtx {
|
|
|
33
374
|
|
|
34
375
|
let _ctx: QuickCtx | null = null;
|
|
35
376
|
let _open = false;
|
|
377
|
+
/** The mode the palette was OPENED as; the effective one is derived with the query. */
|
|
378
|
+
let _requested: PaletteMode = "picker";
|
|
36
379
|
let _query = "";
|
|
37
|
-
let _results: FileItem[] = [];
|
|
38
380
|
let _selectedIndex = 0;
|
|
39
|
-
|
|
381
|
+
/** The project's document set, fetched once per open. `null` while it has never been asked for. */
|
|
382
|
+
let _files: FileRow[] | null = null;
|
|
383
|
+
let _loading = false;
|
|
384
|
+
/** The command awaiting its one argument, or `null`. */
|
|
385
|
+
let _pending: { command: AnyCommand; name: string; choices: PaletteArgChoice[] } | null = null;
|
|
40
386
|
|
|
41
|
-
/** @returns {HTMLElement} */
|
|
42
387
|
function getContainer() {
|
|
43
388
|
return getLayerSlot("popover", "quick-search");
|
|
44
389
|
}
|
|
45
390
|
|
|
46
|
-
/** @param {QuickCtx} [ctx] */
|
|
47
391
|
export function initQuickSearch(ctx?: QuickCtx) {
|
|
48
392
|
_ctx = ctx ?? null;
|
|
49
393
|
}
|
|
50
394
|
|
|
51
|
-
|
|
395
|
+
/**
|
|
396
|
+
* Open the palette in `mode`.
|
|
397
|
+
*
|
|
398
|
+
* The default is `"files"`, not the picker: every caller that names a mode names one (⌘K passes
|
|
399
|
+
* `"picker"`, the pill's segments pass theirs), so the bare call is the "open a page…" gesture an
|
|
400
|
+
* empty state offers — and that has to land on files rather than on a list of modes.
|
|
401
|
+
*
|
|
402
|
+
* @param mode Which mode to open in. `"picker"` (⌘K) lists the modes until something is typed.
|
|
403
|
+
*/
|
|
404
|
+
export function openQuickSearch(mode: PaletteMode = "files") {
|
|
52
405
|
_open = true;
|
|
406
|
+
_requested = mode;
|
|
53
407
|
_query = "";
|
|
54
|
-
_results = [];
|
|
55
408
|
_selectedIndex = 0;
|
|
409
|
+
_pending = null;
|
|
410
|
+
_files = null;
|
|
56
411
|
renderOverlay();
|
|
412
|
+
void ensureFiles();
|
|
57
413
|
}
|
|
58
414
|
|
|
59
415
|
export function closeQuickSearch() {
|
|
60
416
|
_open = false;
|
|
417
|
+
_pending = null;
|
|
61
418
|
renderOverlay();
|
|
62
419
|
}
|
|
63
420
|
|
|
64
|
-
/**
|
|
421
|
+
/** Whether the palette is on screen — the fact the Command Bar's ⌘K affordance reflects. */
|
|
422
|
+
export function isQuickSearchOpen(): boolean {
|
|
423
|
+
return _open;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/** The project root scoping the palette, or null when no project is open. */
|
|
65
427
|
function scopeRoot(): string | null {
|
|
66
428
|
return projectState ? (projectState.projectRoot ?? null) : null;
|
|
67
429
|
}
|
|
68
430
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
items: recent.map((f) => ({ kind: "file", name: f.name, path: f.path })),
|
|
91
|
-
showingRecent: true,
|
|
92
|
-
};
|
|
431
|
+
function hasProject(): boolean {
|
|
432
|
+
return projectState != null;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** The mode and query the current input resolves to. */
|
|
436
|
+
function current(): { mode: PaletteMode; query: string } {
|
|
437
|
+
return resolvePaletteMode(_requested, _query, hasProject());
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ─── Row sources ──────────────────────────────────────────────────────────────
|
|
441
|
+
|
|
442
|
+
function dirPart(path: string) {
|
|
443
|
+
const parts = path.split("/");
|
|
444
|
+
parts.pop();
|
|
445
|
+
return parts.length > 0 ? parts.join("/") : "";
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** Collapse a home-prefixed absolute path for compact display. */
|
|
449
|
+
function shortenPath(path: string) {
|
|
450
|
+
if (path.startsWith("/home/")) {
|
|
451
|
+
return `~/${path.split("/").slice(3).join("/")}`;
|
|
93
452
|
}
|
|
94
|
-
return
|
|
453
|
+
return path;
|
|
95
454
|
}
|
|
96
455
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
456
|
+
/**
|
|
457
|
+
* Fetch the project's document set once per open.
|
|
458
|
+
*
|
|
459
|
+
* The empty query is deliberate: `searchFiles`'s glob is `**\/*<query>*.{json,md,…}`, so an empty
|
|
460
|
+
* one IS "every document", and asking for it once buys a client-side fuzzy match over full paths
|
|
461
|
+
* that the glob could never express.
|
|
462
|
+
*/
|
|
463
|
+
async function ensureFiles(): Promise<void> {
|
|
464
|
+
if (!_open || _files !== null || _loading || !hasProject()) {
|
|
102
465
|
return;
|
|
103
466
|
}
|
|
467
|
+
_loading = true;
|
|
468
|
+
renderOverlay();
|
|
104
469
|
try {
|
|
105
|
-
const platform = getPlatform();
|
|
106
470
|
await loadFormats();
|
|
107
|
-
const hits = await
|
|
108
|
-
|
|
109
|
-
kind: "file",
|
|
110
|
-
|
|
111
|
-
|
|
471
|
+
const hits = await getPlatform().searchFiles("", documentExtensions());
|
|
472
|
+
_files = hits.map((hit) => ({
|
|
473
|
+
kind: "file" as const,
|
|
474
|
+
path: hit.path,
|
|
475
|
+
name: hit.name ?? hit.path.split("/").pop() ?? "",
|
|
476
|
+
detail: dirPart(hit.path),
|
|
477
|
+
recent: false,
|
|
112
478
|
}));
|
|
113
|
-
_selectedIndex = 0;
|
|
114
|
-
renderOverlay();
|
|
115
479
|
} catch {
|
|
116
|
-
|
|
117
|
-
|
|
480
|
+
// A search backend that is down leaves the palette usable in every other mode; the files mode
|
|
481
|
+
// Says "No results" rather than pretending the project is empty.
|
|
482
|
+
_files = [];
|
|
483
|
+
}
|
|
484
|
+
_loading = false;
|
|
485
|
+
renderOverlay();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function fileRows(query: string): FileRow[] {
|
|
489
|
+
if (query.trim() === "") {
|
|
490
|
+
return getRecentFiles(scopeRoot() ?? undefined).map((file) => ({
|
|
491
|
+
kind: "file" as const,
|
|
492
|
+
path: file.path,
|
|
493
|
+
name: file.name,
|
|
494
|
+
detail: dirPart(file.path),
|
|
495
|
+
recent: true,
|
|
496
|
+
}));
|
|
497
|
+
}
|
|
498
|
+
return rankBy(_files ?? [], query.trim(), (row) => row.path);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function projectRows(query: string): ProjectRow[] {
|
|
502
|
+
const rows = getRecentProjects().map((project) => ({
|
|
503
|
+
kind: "project" as const,
|
|
504
|
+
root: project.root,
|
|
505
|
+
name: project.name,
|
|
506
|
+
detail: shortenPath(project.root),
|
|
507
|
+
}));
|
|
508
|
+
return rankBy(rows, query.trim(), (row) => `${row.name}/${row.root}`);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Every command the registry holds, ranked — including the ones that cannot run right now.
|
|
513
|
+
*
|
|
514
|
+
* Visibility (`when`) still hides; enablement does not. That split is the whole of §2 principle 4:
|
|
515
|
+
* a command that does not apply to this app at all is absent, and one that does not apply to this
|
|
516
|
+
* MOMENT is greyed and says why.
|
|
517
|
+
*/
|
|
518
|
+
function commandRows(registry: CommandRegistry, query: string): CommandRow[] {
|
|
519
|
+
const recents = getRecentCommands();
|
|
520
|
+
const rows: CommandRow[] = [];
|
|
521
|
+
for (const command of registry.visible()) {
|
|
522
|
+
if (paletteArgs(command).kind === "unsupported") {
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
if (!(command.menus ?? ["palette"]).includes("palette")) {
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
rows.push({
|
|
529
|
+
kind: "command",
|
|
530
|
+
id: command.id,
|
|
531
|
+
name: `${command.category}: ${command.title}`,
|
|
532
|
+
detail: registry.disabledReason(command.id) ?? "",
|
|
533
|
+
chord: registry.keymap.formatBinding(command.id) ?? "",
|
|
534
|
+
enabled: registry.isEnabled(command.id),
|
|
535
|
+
recent: recents.includes(command.id),
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
const ranked = rankBy(rows, query.trim(), (row) => row.name);
|
|
539
|
+
if (query.trim() !== "") {
|
|
540
|
+
return ranked;
|
|
541
|
+
}
|
|
542
|
+
// Recents pin above the rest, newest first — the one ordering a palette owes a returning user.
|
|
543
|
+
const pinned = recents
|
|
544
|
+
.map((id) => ranked.find((row) => row.id === id))
|
|
545
|
+
.filter((row): row is CommandRow => row !== undefined);
|
|
546
|
+
return [...pinned, ...ranked.filter((row) => !pinned.includes(row))];
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function nodeRows(query: string): NodeRow[] {
|
|
550
|
+
const tab = activeTab.value;
|
|
551
|
+
if (!tab) {
|
|
552
|
+
return [];
|
|
553
|
+
}
|
|
554
|
+
const rows: NodeRow[] = flattenTree(tab.doc.document).map((row) => ({
|
|
555
|
+
kind: "node" as const,
|
|
556
|
+
path: row.path,
|
|
557
|
+
name:
|
|
558
|
+
row.nodeType === "text"
|
|
559
|
+
? String(row.node).slice(0, 60)
|
|
560
|
+
: nodeLabel(row.node as JxMutableNode),
|
|
561
|
+
detail: row.nodeType,
|
|
562
|
+
}));
|
|
563
|
+
return rankBy(rows, query.trim(), (row) => row.name);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function modeRows(): ModeRow[] {
|
|
567
|
+
return PALETTE_MODES.filter((spec) => spec.mode !== "picker").map((spec) => ({
|
|
568
|
+
kind: "mode" as const,
|
|
569
|
+
mode: spec.mode,
|
|
570
|
+
name: spec.chip,
|
|
571
|
+
detail: spec.description,
|
|
572
|
+
chord: spec.prefix,
|
|
573
|
+
}));
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function argRows(query: string): ArgRow[] {
|
|
577
|
+
const pending = _pending!;
|
|
578
|
+
const rows: ArgRow[] = pending.choices.map((choice) => ({
|
|
579
|
+
kind: "arg" as const,
|
|
580
|
+
commandId: pending.command.id,
|
|
581
|
+
argName: pending.name,
|
|
582
|
+
value: choice.value,
|
|
583
|
+
name: choice.label,
|
|
584
|
+
detail: `${pending.command.title} → ${pending.name}`,
|
|
585
|
+
}));
|
|
586
|
+
return rankBy(rows, query.trim(), (row) => row.name);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/** The rows for the current input, and whether they are a "recent" listing rather than a search. */
|
|
590
|
+
function currentRows(): { rows: PaletteRow[]; showingRecent: boolean } {
|
|
591
|
+
const { mode, query } = current();
|
|
592
|
+
if (_pending) {
|
|
593
|
+
return { rows: argRows(query), showingRecent: false };
|
|
594
|
+
}
|
|
595
|
+
switch (mode) {
|
|
596
|
+
case "commands": {
|
|
597
|
+
const registry = activeRegistry();
|
|
598
|
+
return {
|
|
599
|
+
rows: registry ? commandRows(registry, query) : [],
|
|
600
|
+
showingRecent: query.trim() === "",
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
case "nodes": {
|
|
604
|
+
return { rows: nodeRows(query), showingRecent: false };
|
|
605
|
+
}
|
|
606
|
+
case "projects": {
|
|
607
|
+
return { rows: projectRows(query), showingRecent: query.trim() === "" };
|
|
608
|
+
}
|
|
609
|
+
case "picker": {
|
|
610
|
+
return { rows: modeRows(), showingRecent: false };
|
|
611
|
+
}
|
|
612
|
+
default: {
|
|
613
|
+
return { rows: fileRows(query), showingRecent: query.trim() === "" };
|
|
614
|
+
}
|
|
118
615
|
}
|
|
119
616
|
}
|
|
120
617
|
|
|
618
|
+
// ─── Interaction ──────────────────────────────────────────────────────────────
|
|
619
|
+
|
|
121
620
|
function onInput(e: Event) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
621
|
+
const raw = (e.target as HTMLInputElement).value;
|
|
622
|
+
const prefixed = PREFIXED_MODES.find((spec) => raw.startsWith(spec.prefix));
|
|
623
|
+
if (prefixed) {
|
|
624
|
+
// The prefix moves OUT of the input and into the chip, so the query the user reads back is the
|
|
625
|
+
// Text they are searching for and Backspace has one obvious meaning at position zero.
|
|
626
|
+
_requested = prefixed.mode;
|
|
627
|
+
_query = raw.slice(1);
|
|
628
|
+
} else {
|
|
629
|
+
_query = raw;
|
|
127
630
|
}
|
|
128
631
|
_selectedIndex = 0;
|
|
129
632
|
renderOverlay();
|
|
633
|
+
void ensureFiles();
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/** Drop the mode chip: back to the neutral picker, keeping what was typed. */
|
|
637
|
+
function clearMode() {
|
|
638
|
+
_requested = "picker";
|
|
639
|
+
_pending = null;
|
|
640
|
+
_selectedIndex = 0;
|
|
641
|
+
renderOverlay();
|
|
130
642
|
}
|
|
131
643
|
|
|
132
644
|
function onKeydown(e: KeyboardEvent) {
|
|
133
|
-
const {
|
|
645
|
+
const { rows } = currentRows();
|
|
134
646
|
switch (e.key) {
|
|
135
647
|
case "ArrowDown": {
|
|
136
648
|
e.preventDefault();
|
|
137
|
-
_selectedIndex = Math.min(_selectedIndex + 1,
|
|
649
|
+
_selectedIndex = Math.min(_selectedIndex + 1, rows.length - 1);
|
|
138
650
|
renderOverlay();
|
|
139
651
|
break;
|
|
140
652
|
}
|
|
@@ -146,8 +658,17 @@ function onKeydown(e: KeyboardEvent) {
|
|
|
146
658
|
}
|
|
147
659
|
case "Enter": {
|
|
148
660
|
e.preventDefault();
|
|
149
|
-
if (
|
|
150
|
-
|
|
661
|
+
if (rows[_selectedIndex]) {
|
|
662
|
+
selectRow(rows[_selectedIndex]!);
|
|
663
|
+
}
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
case "Backspace": {
|
|
667
|
+
// At position zero the chip is the thing to the left of the caret, so this is the same
|
|
668
|
+
// Gesture as deleting a character — and it is why the chip is removable at all.
|
|
669
|
+
if (_query === "" && (_pending || _requested !== "picker")) {
|
|
670
|
+
e.preventDefault();
|
|
671
|
+
clearMode();
|
|
151
672
|
}
|
|
152
673
|
break;
|
|
153
674
|
}
|
|
@@ -162,16 +683,88 @@ function onKeydown(e: KeyboardEvent) {
|
|
|
162
683
|
}
|
|
163
684
|
}
|
|
164
685
|
|
|
165
|
-
function
|
|
686
|
+
function enterMode(mode: PaletteMode) {
|
|
687
|
+
_requested = mode;
|
|
688
|
+
_query = "";
|
|
689
|
+
_selectedIndex = 0;
|
|
690
|
+
renderOverlay();
|
|
691
|
+
void ensureFiles();
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function runCommand(command: AnyCommand, args?: Record<string, unknown>) {
|
|
695
|
+
const registry = activeRegistry();
|
|
696
|
+
if (!registry) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
trackRecentCommand(command.id);
|
|
166
700
|
closeQuickSearch();
|
|
167
|
-
|
|
168
|
-
|
|
701
|
+
const report = (error: unknown) => {
|
|
702
|
+
console.error(`palette: command "${command.id}" failed`, error);
|
|
703
|
+
};
|
|
704
|
+
// Both shapes, deliberately: `run` may throw synchronously (a coercion refusal from
|
|
705
|
+
// `command-args.ts` does) or reject later. A palette that let either escape would take the
|
|
706
|
+
// Keydown listener down with it, and the overlay is already closed by then.
|
|
707
|
+
try {
|
|
708
|
+
void Promise.resolve(registry.run(command.id, args)).catch(report);
|
|
709
|
+
} catch (error) {
|
|
710
|
+
report(error);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function selectRow(row: PaletteRow) {
|
|
715
|
+
switch (row.kind) {
|
|
716
|
+
case "project": {
|
|
717
|
+
closeQuickSearch();
|
|
718
|
+
void _ctx?.openRecentProject(row.root);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
case "file": {
|
|
722
|
+
closeQuickSearch();
|
|
723
|
+
trackRecentFile({ name: row.name, path: row.path, root: scopeRoot() ?? "" });
|
|
724
|
+
void openFileInTab(row.path);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
case "mode": {
|
|
728
|
+
enterMode(row.mode);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
case "node": {
|
|
732
|
+
const registry = activeRegistry();
|
|
733
|
+
closeQuickSearch();
|
|
734
|
+
void registry?.run("selection.set", { path: row.path });
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
case "arg": {
|
|
738
|
+
runCommand(_pending!.command, { [row.argName]: row.value });
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
default: {
|
|
742
|
+
selectCommandRow(row);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function selectCommandRow(row: CommandRow) {
|
|
748
|
+
const registry = activeRegistry();
|
|
749
|
+
const command = registry?.get(row.id);
|
|
750
|
+
if (!registry || !command || !row.enabled) {
|
|
751
|
+
// A greyed row is not a dead one: it stays on screen with its reason, and pressing Enter on it
|
|
752
|
+
// Does nothing rather than closing the palette on a refusal the user never asked to see.
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
const args = paletteArgs(command);
|
|
756
|
+
if (args.kind === "choice") {
|
|
757
|
+
_pending = { choices: args.choices, command, name: args.name };
|
|
758
|
+
_query = "";
|
|
759
|
+
_selectedIndex = 0;
|
|
760
|
+
renderOverlay();
|
|
169
761
|
return;
|
|
170
762
|
}
|
|
171
|
-
|
|
172
|
-
void openFileInTab(item.path);
|
|
763
|
+
runCommand(command);
|
|
173
764
|
}
|
|
174
765
|
|
|
766
|
+
// ─── Rendering ────────────────────────────────────────────────────────────────
|
|
767
|
+
|
|
175
768
|
function fileIcon(name: string) {
|
|
176
769
|
const ext = name.split(".").pop()?.toLowerCase();
|
|
177
770
|
if (ext === "json") {
|
|
@@ -183,18 +776,84 @@ function fileIcon(name: string) {
|
|
|
183
776
|
return html`<sp-icon-document size="s"></sp-icon-document>`;
|
|
184
777
|
}
|
|
185
778
|
|
|
186
|
-
function
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
779
|
+
function rowIcon(row: PaletteRow) {
|
|
780
|
+
switch (row.kind) {
|
|
781
|
+
case "project": {
|
|
782
|
+
return html`<sp-icon-folder-open size="s"></sp-icon-folder-open>`;
|
|
783
|
+
}
|
|
784
|
+
case "file": {
|
|
785
|
+
return fileIcon(row.name);
|
|
786
|
+
}
|
|
787
|
+
case "command": {
|
|
788
|
+
return html`<sp-icon-play size="s"></sp-icon-play>`;
|
|
789
|
+
}
|
|
790
|
+
case "node": {
|
|
791
|
+
return html`<sp-icon-layers size="s"></sp-icon-layers>`;
|
|
792
|
+
}
|
|
793
|
+
default: {
|
|
794
|
+
return html`<sp-icon-chevron-right size="s"></sp-icon-chevron-right>`;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
190
797
|
}
|
|
191
798
|
|
|
192
|
-
/**
|
|
193
|
-
function
|
|
194
|
-
if (
|
|
195
|
-
return
|
|
799
|
+
/** The right-aligned cell: a chord, a prefix, or the recent badge. */
|
|
800
|
+
function rowTrailing(row: PaletteRow, showingRecent: boolean) {
|
|
801
|
+
if (row.kind === "command" && row.chord) {
|
|
802
|
+
return html`<kbd class="palette-chord">${row.chord}</kbd>`;
|
|
803
|
+
}
|
|
804
|
+
if (row.kind === "mode" && row.chord) {
|
|
805
|
+
return html`<kbd class="palette-chord">${row.chord}</kbd>`;
|
|
806
|
+
}
|
|
807
|
+
if (row.kind === "command" && row.recent) {
|
|
808
|
+
return html`<span class="quick-search-badge">recent</span>`;
|
|
809
|
+
}
|
|
810
|
+
return showingRecent ? html`<span class="quick-search-badge">recent</span>` : nothing;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/** The chip echoing the active mode (or the command awaiting an argument), with its remove button. */
|
|
814
|
+
function chipTemplate(mode: PaletteMode) {
|
|
815
|
+
const label = _pending ? _pending.command.title : modeSpec(mode).chip;
|
|
816
|
+
if (!_pending && mode === "picker") {
|
|
817
|
+
return nothing;
|
|
818
|
+
}
|
|
819
|
+
return html`
|
|
820
|
+
<span class="palette-chip">
|
|
821
|
+
${label}
|
|
822
|
+
<button
|
|
823
|
+
class="palette-chip-remove"
|
|
824
|
+
type="button"
|
|
825
|
+
title="Clear mode (Backspace)"
|
|
826
|
+
aria-label="Clear mode"
|
|
827
|
+
@click=${clearMode}
|
|
828
|
+
>
|
|
829
|
+
×
|
|
830
|
+
</button>
|
|
831
|
+
</span>
|
|
832
|
+
`;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/** The sentence an empty result set shows. Never a noun phrase (§2 principle 6). */
|
|
836
|
+
function emptyHint(mode: PaletteMode, query: string): string {
|
|
837
|
+
if (_loading) {
|
|
838
|
+
return "Reading the project's files…";
|
|
839
|
+
}
|
|
840
|
+
if (query.trim() !== "") {
|
|
841
|
+
return "No results";
|
|
842
|
+
}
|
|
843
|
+
switch (mode) {
|
|
844
|
+
case "projects": {
|
|
845
|
+
return "No recent projects — open one to get started";
|
|
846
|
+
}
|
|
847
|
+
case "nodes": {
|
|
848
|
+
return "Open a document to jump to its elements";
|
|
849
|
+
}
|
|
850
|
+
case "commands": {
|
|
851
|
+
return "Type to find any command in Studio";
|
|
852
|
+
}
|
|
853
|
+
default: {
|
|
854
|
+
return modeSpec(mode).placeholder;
|
|
855
|
+
}
|
|
196
856
|
}
|
|
197
|
-
return path;
|
|
198
857
|
}
|
|
199
858
|
|
|
200
859
|
function renderOverlay() {
|
|
@@ -204,73 +863,83 @@ function renderOverlay() {
|
|
|
204
863
|
return;
|
|
205
864
|
}
|
|
206
865
|
|
|
207
|
-
const
|
|
208
|
-
const {
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
866
|
+
const { mode, query } = current();
|
|
867
|
+
const { rows, showingRecent } = currentRows();
|
|
868
|
+
const spec = modeSpec(mode);
|
|
869
|
+
const placeholder = _pending ? `Choose a ${_pending.name}…` : spec.placeholder;
|
|
870
|
+
const sectionLabel = _pending
|
|
871
|
+
? `${_pending.command.title} — ${_pending.name}`
|
|
872
|
+
: mode === "projects"
|
|
873
|
+
? "Recent projects"
|
|
874
|
+
: mode === "commands"
|
|
875
|
+
? "Recently used"
|
|
876
|
+
: "Recently opened";
|
|
216
877
|
|
|
217
878
|
const tpl = html`
|
|
218
879
|
<div class="quick-search-overlay" @click=${closeQuickSearch}>
|
|
219
880
|
<div class="quick-search-panel" @click=${(e: Event) => e.stopPropagation()}>
|
|
220
|
-
<input
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
881
|
+
<div class="palette-input-row">
|
|
882
|
+
${chipTemplate(mode)}
|
|
883
|
+
<input
|
|
884
|
+
class="quick-search-input"
|
|
885
|
+
type="text"
|
|
886
|
+
role="combobox"
|
|
887
|
+
aria-controls=${QUICK_SEARCH_LISTBOX_ID}
|
|
888
|
+
aria-expanded=${rows.length > 0}
|
|
889
|
+
aria-activedescendant=${rows.length > 0 ? optionId(_selectedIndex) : nothing}
|
|
890
|
+
aria-autocomplete="list"
|
|
891
|
+
aria-label=${placeholder}
|
|
892
|
+
placeholder=${placeholder}
|
|
893
|
+
.value=${live(_query)}
|
|
894
|
+
@input=${onInput}
|
|
895
|
+
@keydown=${onKeydown}
|
|
896
|
+
${ref((el) => {
|
|
897
|
+
if (el) {
|
|
898
|
+
requestAnimationFrame(() => (el as HTMLInputElement).focus());
|
|
899
|
+
}
|
|
900
|
+
})}
|
|
901
|
+
/>
|
|
902
|
+
</div>
|
|
903
|
+
<div class="quick-search-results" role="listbox" id=${QUICK_SEARCH_LISTBOX_ID}>
|
|
239
904
|
${
|
|
240
|
-
|
|
241
|
-
? html`<div class="quick-search-empty">${emptyHint}</div>`
|
|
905
|
+
rows.length === 0
|
|
906
|
+
? html`<div class="quick-search-empty">${emptyHint(mode, query)}</div>`
|
|
242
907
|
: nothing
|
|
243
908
|
}
|
|
244
909
|
${
|
|
245
|
-
showingRecent &&
|
|
910
|
+
showingRecent && rows.length > 0
|
|
246
911
|
? html`<div class="quick-search-section-label">${sectionLabel}</div>`
|
|
247
912
|
: nothing
|
|
248
913
|
}
|
|
249
|
-
${
|
|
250
|
-
|
|
251
|
-
item.kind === "project"
|
|
252
|
-
? html`<sp-icon-folder-open size="s"></sp-icon-folder-open>`
|
|
253
|
-
: fileIcon(item.name);
|
|
254
|
-
const pathText = item.kind === "project" ? shortenPath(item.root) : dirPart(item.path);
|
|
255
|
-
return html`
|
|
914
|
+
${rows.map(
|
|
915
|
+
(row, i) => html`
|
|
256
916
|
<div
|
|
257
917
|
class=${classMap({
|
|
258
918
|
"quick-search-item": true,
|
|
919
|
+
disabled: row.kind === "command" && !row.enabled,
|
|
259
920
|
selected: i === _selectedIndex,
|
|
260
921
|
})}
|
|
261
|
-
|
|
922
|
+
role="option"
|
|
923
|
+
id=${optionId(i)}
|
|
924
|
+
aria-selected=${i === _selectedIndex}
|
|
925
|
+
aria-disabled=${row.kind === "command" && !row.enabled}
|
|
926
|
+
@click=${() => selectRow(row)}
|
|
262
927
|
@mouseenter=${() => {
|
|
263
928
|
_selectedIndex = i;
|
|
264
929
|
renderOverlay();
|
|
265
930
|
}}
|
|
266
931
|
>
|
|
267
|
-
<span class="quick-search-icon">${
|
|
268
|
-
<span class="quick-search-name">${
|
|
269
|
-
<span class="quick-search-path">${
|
|
270
|
-
${showingRecent
|
|
932
|
+
<span class="quick-search-icon">${rowIcon(row)}</span>
|
|
933
|
+
<span class="quick-search-name">${row.name}</span>
|
|
934
|
+
<span class="quick-search-path">${row.detail}</span>
|
|
935
|
+
${rowTrailing(row, showingRecent)}
|
|
271
936
|
</div>
|
|
272
|
-
|
|
273
|
-
|
|
937
|
+
`,
|
|
938
|
+
)}
|
|
939
|
+
</div>
|
|
940
|
+
<div class="palette-hint">
|
|
941
|
+
<kbd>↑↓</kbd> move · <kbd>↵</kbd> run · <kbd>?</kbd> modes · <kbd>></kbd> commands ·
|
|
942
|
+
<kbd>@</kbd> elements
|
|
274
943
|
</div>
|
|
275
944
|
</div>
|
|
276
945
|
</div>
|