@jxsuite/studio 2.0.0 → 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-0hzentwq.js → components-2bpqp4mq.js} +5 -7
- package/dist/chunks/{components-0hzentwq.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-2c188n41.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-97qcgvdn.js → studio-adn0z1k3.js} +14 -8
- package/dist/chunks/{studio-97qcgvdn.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-hhj92gcr.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-ygzav7z5.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-n9zmpxjg.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 +23711 -35185
- 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 +34 -9
- package/src/new-project/location-fields.ts +9 -1
- package/src/new-project/new-project-modal.ts +270 -262
- 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 -120
- 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 +75 -2
- 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 +113 -0
- 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-qct0gt8f.js +0 -531
- package/dist/chunks/extension-sections-qct0gt8f.js.map +0 -12
- package/dist/chunks/files-02zn5y24.js +0 -50
- package/dist/chunks/files-02zn5y24.js.map +0 -9
- package/dist/chunks/git-panel-mbb0y0hz.js +0 -28
- package/dist/chunks/git-panel-mbb0y0hz.js.map +0 -9
- package/dist/chunks/layers-panel-9qekmqk1.js +0 -26
- package/dist/chunks/layers-panel-9qekmqk1.js.map +0 -9
- package/dist/chunks/settings-modal-gj0hrah4.js +0 -29
- package/dist/chunks/settings-modal-gj0hrah4.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-4k0m4fmp.js.map +0 -13
- package/dist/chunks/studio-568nx4t5.js +0 -469
- package/dist/chunks/studio-568nx4t5.js.map +0 -14
- package/dist/chunks/studio-9smfzqhc.js +0 -19
- package/dist/chunks/studio-9smfzqhc.js.map +0 -10
- package/dist/chunks/studio-asvf0j9e.js +0 -429
- package/dist/chunks/studio-asvf0j9e.js.map +0 -13
- package/dist/chunks/studio-d77qm3pk.js +0 -3427
- package/dist/chunks/studio-d77qm3pk.js.map +0 -26
- package/dist/chunks/studio-j6gqyjf4.js +0 -1854
- package/dist/chunks/studio-j6gqyjf4.js.map +0 -18
- package/dist/chunks/studio-n9zmpxjg.js.map +0 -115
- package/dist/chunks/studio-qkvt37c0.js.map +0 -9
- package/dist/chunks/studio-rq0vxb19.js +0 -3744
- package/dist/chunks/studio-rq0vxb19.js.map +0 -22
- package/dist/chunks/studio-wd6ewdvd.js +0 -853
- package/dist/chunks/studio-wd6ewdvd.js.map +0 -12
- 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-ygzav7z5.js.map +0 -46
- package/dist/chunks/studio-zmy7241e.js +0 -399
- package/dist/chunks/studio-zmy7241e.js.map +0 -12
- 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-2c188n41.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-hhj92gcr.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,3427 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
UPLOAD_ACCEPT,
|
|
3
|
-
flushCanvasEdits,
|
|
4
|
-
isImage,
|
|
5
|
-
rectOf,
|
|
6
|
-
setEditZoom,
|
|
7
|
-
uploadAssets,
|
|
8
|
-
validateComponentSlots
|
|
9
|
-
} from "./studio-ygzav7z5.js";
|
|
10
|
-
import {
|
|
11
|
-
loadComponentRegistry
|
|
12
|
-
} from "./studio-hhj92gcr.js";
|
|
13
|
-
import {
|
|
14
|
-
classMap
|
|
15
|
-
} from "./studio-cx91mnh8.js";
|
|
16
|
-
import {
|
|
17
|
-
autoSyncProjectOnOpen,
|
|
18
|
-
maybePromptJxsuiteUpdate,
|
|
19
|
-
showProgressModal
|
|
20
|
-
} from "./studio-568nx4t5.js";
|
|
21
|
-
import {
|
|
22
|
-
applyPanelCollapse,
|
|
23
|
-
errorMessage,
|
|
24
|
-
openModal,
|
|
25
|
-
renderPopover,
|
|
26
|
-
showConfirmDialog,
|
|
27
|
-
showPromptDialog,
|
|
28
|
-
statusMessage,
|
|
29
|
-
view
|
|
30
|
-
} from "./studio-asvf0j9e.js";
|
|
31
|
-
import {
|
|
32
|
-
activateTab,
|
|
33
|
-
activeTab,
|
|
34
|
-
closeAllTabs,
|
|
35
|
-
collabSave,
|
|
36
|
-
collabState,
|
|
37
|
-
createState,
|
|
38
|
-
effect,
|
|
39
|
-
findContentTypeSchema,
|
|
40
|
-
isCollabPath,
|
|
41
|
-
onScopeDispose,
|
|
42
|
-
openTab,
|
|
43
|
-
projectState,
|
|
44
|
-
reactive,
|
|
45
|
-
renameTab,
|
|
46
|
-
renderOnly,
|
|
47
|
-
replaceAllTabs,
|
|
48
|
-
requireProjectState,
|
|
49
|
-
setHistoryDelegate,
|
|
50
|
-
setProjectState,
|
|
51
|
-
setWorkspaceProject,
|
|
52
|
-
toRaw,
|
|
53
|
-
updateUi,
|
|
54
|
-
workspace
|
|
55
|
-
} from "./studio-rq0vxb19.js";
|
|
56
|
-
import {
|
|
57
|
-
ref
|
|
58
|
-
} from "./studio-4k0m4fmp.js";
|
|
59
|
-
import {
|
|
60
|
-
html,
|
|
61
|
-
nothing
|
|
62
|
-
} from "./studio-5x5774ak.js";
|
|
63
|
-
import {
|
|
64
|
-
defaultContentFormat,
|
|
65
|
-
documentExtensions,
|
|
66
|
-
formatByName,
|
|
67
|
-
formatForPath,
|
|
68
|
-
formatParse,
|
|
69
|
-
formatSerialize,
|
|
70
|
-
loadFormats,
|
|
71
|
-
noFormatError,
|
|
72
|
-
refreshExtensionUi,
|
|
73
|
-
refreshFormats,
|
|
74
|
-
splitFormatDocument
|
|
75
|
-
} from "./studio-97qcgvdn.js";
|
|
76
|
-
import {
|
|
77
|
-
getPlatform,
|
|
78
|
-
hasPlatform
|
|
79
|
-
} from "./studio-9smfzqhc.js";
|
|
80
|
-
import {
|
|
81
|
-
combine,
|
|
82
|
-
draggable,
|
|
83
|
-
dropTargetForElements,
|
|
84
|
-
monitorForElements
|
|
85
|
-
} from "./studio-kr88ryr7.js";
|
|
86
|
-
import {
|
|
87
|
-
__require
|
|
88
|
-
} from "./studio-md4gps17.js";
|
|
89
|
-
|
|
90
|
-
// src/project-list.ts
|
|
91
|
-
var cache = [];
|
|
92
|
-
function platformListsProjects() {
|
|
93
|
-
return hasPlatform() && typeof getPlatform().listProjects === "function";
|
|
94
|
-
}
|
|
95
|
-
async function hydrateProjectList() {
|
|
96
|
-
if (!platformListsProjects()) {
|
|
97
|
-
cache = [];
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
try {
|
|
101
|
-
cache = await getPlatform().listProjects?.() ?? [];
|
|
102
|
-
} catch {
|
|
103
|
-
cache = [];
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
function getProjectList() {
|
|
107
|
-
return cache;
|
|
108
|
-
}
|
|
109
|
-
function seedProjectList(entries) {
|
|
110
|
-
cache = entries;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// src/services/automation.ts
|
|
114
|
-
function shouldInstallAutomation(search) {
|
|
115
|
-
return new URLSearchParams(search).get("automation") === "1";
|
|
116
|
-
}
|
|
117
|
-
function createAutomationApi(deps) {
|
|
118
|
-
return {
|
|
119
|
-
editDef(defName) {
|
|
120
|
-
updateUi("editingFunction", { defName, type: "def" });
|
|
121
|
-
deps.render();
|
|
122
|
-
},
|
|
123
|
-
editFunction(path, eventKey) {
|
|
124
|
-
updateUi("editingFunction", { eventKey, path, type: "event" });
|
|
125
|
-
deps.render();
|
|
126
|
-
},
|
|
127
|
-
openBrowse() {
|
|
128
|
-
deps.openBrowseModal();
|
|
129
|
-
},
|
|
130
|
-
openDataGrid(options) {
|
|
131
|
-
deps.openConnectorGrid(options.connection, options.table);
|
|
132
|
-
},
|
|
133
|
-
openNewProject() {
|
|
134
|
-
deps.openNewProjectModal();
|
|
135
|
-
},
|
|
136
|
-
openQuickSearch() {
|
|
137
|
-
deps.openQuickSearchPalette();
|
|
138
|
-
},
|
|
139
|
-
openSettings(section) {
|
|
140
|
-
deps.openSettingsModal(section);
|
|
141
|
-
},
|
|
142
|
-
seedAssistant(options) {
|
|
143
|
-
deps.seedAssistantMessages(options.messages);
|
|
144
|
-
},
|
|
145
|
-
seedCollab(options) {
|
|
146
|
-
const tab = activeTab.value;
|
|
147
|
-
if (!tab) {
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
const state = collabState(tab);
|
|
151
|
-
state.status = "synced";
|
|
152
|
-
state.active = true;
|
|
153
|
-
state.peers = options.peers.map((peer) => ({
|
|
154
|
-
...peer,
|
|
155
|
-
state: {
|
|
156
|
-
...peer.state,
|
|
157
|
-
focusedPath: peer.state.focusedPath ?? tab.documentPath
|
|
158
|
-
}
|
|
159
|
-
}));
|
|
160
|
-
deps.render();
|
|
161
|
-
},
|
|
162
|
-
seedPublish(options) {
|
|
163
|
-
deps.seedPublishConnected(options);
|
|
164
|
-
},
|
|
165
|
-
showWelcome(options) {
|
|
166
|
-
closeAllTabs();
|
|
167
|
-
setProjectState(null);
|
|
168
|
-
if (options?.projects) {
|
|
169
|
-
seedProjectList(options.projects);
|
|
170
|
-
}
|
|
171
|
-
view.chatPanelCollapsed = true;
|
|
172
|
-
applyPanelCollapse();
|
|
173
|
-
deps.render();
|
|
174
|
-
},
|
|
175
|
-
getState() {
|
|
176
|
-
return {
|
|
177
|
-
activeTabId: workspace.activeTabId,
|
|
178
|
-
canvasMode: deps.getCanvasMode(),
|
|
179
|
-
canvasStatus: activeTab.value?.session.canvas.status ?? null,
|
|
180
|
-
leftTab: view.leftTab
|
|
181
|
-
};
|
|
182
|
-
},
|
|
183
|
-
select(path) {
|
|
184
|
-
const tab = activeTab.value;
|
|
185
|
-
if (tab) {
|
|
186
|
-
tab.session.selection = path;
|
|
187
|
-
}
|
|
188
|
-
deps.render();
|
|
189
|
-
},
|
|
190
|
-
setActivity(tab) {
|
|
191
|
-
view.leftTab = tab;
|
|
192
|
-
view.leftPanelCollapsed = false;
|
|
193
|
-
applyPanelCollapse();
|
|
194
|
-
renderOnly("leftPanel");
|
|
195
|
-
deps.renderActivityBar();
|
|
196
|
-
},
|
|
197
|
-
setCanvasMode(mode) {
|
|
198
|
-
deps.setCanvasMode(mode);
|
|
199
|
-
deps.render();
|
|
200
|
-
},
|
|
201
|
-
setRightTab(tab) {
|
|
202
|
-
if (tab === "assistant") {
|
|
203
|
-
view.chatPanelCollapsed = false;
|
|
204
|
-
applyPanelCollapse();
|
|
205
|
-
renderOnly("chatPanel");
|
|
206
|
-
deps.render();
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
updateUi("rightTab", tab);
|
|
210
|
-
deps.render();
|
|
211
|
-
},
|
|
212
|
-
setStatus(text) {
|
|
213
|
-
deps.statusMessage(text);
|
|
214
|
-
},
|
|
215
|
-
setTheme(color) {
|
|
216
|
-
document.querySelector("sp-theme")?.setAttribute("color", color);
|
|
217
|
-
},
|
|
218
|
-
setZoom(zoom) {
|
|
219
|
-
updateUi("zoom", zoom);
|
|
220
|
-
deps.render();
|
|
221
|
-
},
|
|
222
|
-
setEditZoom(zoom) {
|
|
223
|
-
setEditZoom(zoom);
|
|
224
|
-
},
|
|
225
|
-
waitForCanvasReady(timeoutMs = 30000) {
|
|
226
|
-
return new Promise((resolve, reject) => {
|
|
227
|
-
const started = Date.now();
|
|
228
|
-
const tick = () => {
|
|
229
|
-
if (activeTab.value?.session.canvas.status === "ready") {
|
|
230
|
-
resolve();
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
if (Date.now() - started > timeoutMs) {
|
|
234
|
-
reject(new Error(`canvas not ready after ${timeoutMs}ms`));
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
setTimeout(tick, 50);
|
|
238
|
-
};
|
|
239
|
-
tick();
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
function installAutomationHook(deps) {
|
|
245
|
-
if (!shouldInstallAutomation(location.search)) {
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
globalThis.__jxAutomation = createAutomationApi(deps);
|
|
249
|
-
return true;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// src/packages/ensure-deps.ts
|
|
253
|
-
async function ensureDependenciesInstalled() {
|
|
254
|
-
if (shouldInstallAutomation(location.search)) {
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
const platform = getPlatform();
|
|
258
|
-
if (!platform.dependenciesNeedInstall || !platform.installDependencies) {
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
let needs = false;
|
|
262
|
-
try {
|
|
263
|
-
needs = await platform.dependenciesNeedInstall();
|
|
264
|
-
} catch {
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
if (!needs) {
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
const progress = showProgressModal({
|
|
271
|
-
status: "Running bun install…",
|
|
272
|
-
title: "Installing dependencies"
|
|
273
|
-
});
|
|
274
|
-
try {
|
|
275
|
-
const result = await platform.installDependencies();
|
|
276
|
-
if (result.ok) {
|
|
277
|
-
progress.done();
|
|
278
|
-
} else {
|
|
279
|
-
progress.fail(result.log ?? "bun install failed");
|
|
280
|
-
}
|
|
281
|
-
} catch (error) {
|
|
282
|
-
progress.fail(error instanceof Error ? error.message : String(error));
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// src/files/fs-events.ts
|
|
287
|
-
var RECENT_MS = 1500;
|
|
288
|
-
var recentLocal = new Map;
|
|
289
|
-
var norm = (p) => p.replaceAll("\\", "/");
|
|
290
|
-
function markLocalMutation(...paths) {
|
|
291
|
-
const expiry = Date.now() + RECENT_MS;
|
|
292
|
-
for (const p of paths) {
|
|
293
|
-
if (p) {
|
|
294
|
-
recentLocal.set(norm(p), expiry);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
function isRecentLocal(path) {
|
|
299
|
-
const key = norm(path);
|
|
300
|
-
const expiry = recentLocal.get(key);
|
|
301
|
-
if (expiry === undefined) {
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
if (Date.now() > expiry) {
|
|
305
|
-
recentLocal.delete(key);
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
return true;
|
|
309
|
-
}
|
|
310
|
-
function parentDir(path) {
|
|
311
|
-
const i = path.lastIndexOf("/");
|
|
312
|
-
return i === -1 ? "." : path.slice(0, i);
|
|
313
|
-
}
|
|
314
|
-
function baseName(path) {
|
|
315
|
-
const i = path.lastIndexOf("/");
|
|
316
|
-
return i === -1 ? path : path.slice(i + 1);
|
|
317
|
-
}
|
|
318
|
-
function applyFsEvents(dirs, expanded, events) {
|
|
319
|
-
const changedDirs = new Set;
|
|
320
|
-
for (const ev of events) {
|
|
321
|
-
const parent = parentDir(ev.path);
|
|
322
|
-
if (ev.type === "add" || ev.type === "addDir") {
|
|
323
|
-
const entries = dirs.get(parent);
|
|
324
|
-
if (entries && !entries.some((e) => e.path === ev.path)) {
|
|
325
|
-
entries.push({
|
|
326
|
-
name: baseName(ev.path),
|
|
327
|
-
path: ev.path,
|
|
328
|
-
type: ev.isDir ? "directory" : "file"
|
|
329
|
-
});
|
|
330
|
-
changedDirs.add(parent);
|
|
331
|
-
}
|
|
332
|
-
if (ev.type === "addDir" && !dirs.has(ev.path)) {
|
|
333
|
-
dirs.set(ev.path, []);
|
|
334
|
-
}
|
|
335
|
-
} else if (ev.type === "unlink" || ev.type === "unlinkDir") {
|
|
336
|
-
const entries = dirs.get(parent);
|
|
337
|
-
if (entries) {
|
|
338
|
-
const idx = entries.findIndex((e) => e.path === ev.path);
|
|
339
|
-
if (idx !== -1) {
|
|
340
|
-
entries.splice(idx, 1);
|
|
341
|
-
changedDirs.add(parent);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
if (ev.type === "unlinkDir") {
|
|
345
|
-
dirs.delete(ev.path);
|
|
346
|
-
const nestedDirs = [];
|
|
347
|
-
for (const key of dirs.keys()) {
|
|
348
|
-
if (key.startsWith(`${ev.path}/`)) {
|
|
349
|
-
nestedDirs.push(key);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
for (const key of nestedDirs) {
|
|
353
|
-
dirs.delete(key);
|
|
354
|
-
}
|
|
355
|
-
const staleExpanded = [];
|
|
356
|
-
for (const key of expanded) {
|
|
357
|
-
if (key === ev.path || key.startsWith(`${ev.path}/`)) {
|
|
358
|
-
staleExpanded.push(key);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
for (const key of staleExpanded) {
|
|
362
|
-
expanded.delete(key);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
return changedDirs;
|
|
368
|
-
}
|
|
369
|
-
function startFsSync(ctx) {
|
|
370
|
-
const platform = getPlatform();
|
|
371
|
-
if (!platform.subscribeFileEvents) {
|
|
372
|
-
return () => {};
|
|
373
|
-
}
|
|
374
|
-
let pending = [];
|
|
375
|
-
let timer = null;
|
|
376
|
-
const flush = () => {
|
|
377
|
-
timer = null;
|
|
378
|
-
const state = projectState;
|
|
379
|
-
const batch = pending.filter((e) => !isRecentLocal(e.path));
|
|
380
|
-
pending = [];
|
|
381
|
-
if (!state || batch.length === 0) {
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
const changedDirs = applyFsEvents(state.dirs, state.expanded, batch);
|
|
385
|
-
if (ctx.onContentChange) {
|
|
386
|
-
for (const ev of batch) {
|
|
387
|
-
if (ev.type === "change") {
|
|
388
|
-
ctx.onContentChange(ev.path);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
if (changedDirs.size > 0) {
|
|
393
|
-
ctx.renderLeftPanel();
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
return platform.subscribeFileEvents((events) => {
|
|
397
|
-
pending.push(...events);
|
|
398
|
-
if (timer) {
|
|
399
|
-
clearTimeout(timer);
|
|
400
|
-
}
|
|
401
|
-
timer = setTimeout(flush, 50);
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// src/services/data-service.ts
|
|
406
|
-
function platformOrNull() {
|
|
407
|
-
try {
|
|
408
|
-
return getPlatform();
|
|
409
|
-
} catch {
|
|
410
|
-
return null;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
function dataSurfaceAvailable() {
|
|
414
|
-
return typeof platformOrNull()?.dataRows === "function";
|
|
415
|
-
}
|
|
416
|
-
async function fetchConnections() {
|
|
417
|
-
const platform = platformOrNull();
|
|
418
|
-
if (typeof platform?.dataConnections !== "function") {
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
421
|
-
return platform.dataConnections();
|
|
422
|
-
}
|
|
423
|
-
async function testConnection(connection) {
|
|
424
|
-
const platform = platformOrNull();
|
|
425
|
-
if (typeof platform?.dataConnectionTest !== "function") {
|
|
426
|
-
return { error: "Connection tests are not supported by this backend", ok: false };
|
|
427
|
-
}
|
|
428
|
-
try {
|
|
429
|
-
return await platform.dataConnectionTest(connection);
|
|
430
|
-
} catch (error) {
|
|
431
|
-
return { error: error instanceof Error ? error.message : String(error), ok: false };
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
async function pushSchema(opts) {
|
|
435
|
-
const platform = platformOrNull();
|
|
436
|
-
if (typeof platform?.dataPush !== "function") {
|
|
437
|
-
return { applied: false, errors: ["Schema push is not supported by this backend"], plan: [] };
|
|
438
|
-
}
|
|
439
|
-
try {
|
|
440
|
-
return await platform.dataPush(opts);
|
|
441
|
-
} catch (error) {
|
|
442
|
-
return {
|
|
443
|
-
applied: false,
|
|
444
|
-
errors: [error instanceof Error ? error.message : String(error)],
|
|
445
|
-
plan: []
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
function requireMember(name) {
|
|
450
|
-
const platform = platformOrNull();
|
|
451
|
-
const member = platform?.[name];
|
|
452
|
-
if (member === undefined || member === null) {
|
|
453
|
-
throw new Error(`The ${String(name)} surface is not supported by this backend`);
|
|
454
|
-
}
|
|
455
|
-
return member;
|
|
456
|
-
}
|
|
457
|
-
function fetchRows(query) {
|
|
458
|
-
return requireMember("dataRows")(query);
|
|
459
|
-
}
|
|
460
|
-
function insertRow(req) {
|
|
461
|
-
return requireMember("dataInsertRow")(req);
|
|
462
|
-
}
|
|
463
|
-
function updateRow(req) {
|
|
464
|
-
return requireMember("dataUpdateRow")(req);
|
|
465
|
-
}
|
|
466
|
-
function deleteRow(req) {
|
|
467
|
-
return requireMember("dataDeleteRow")(req);
|
|
468
|
-
}
|
|
469
|
-
function deriveSecretEnvName(sectionKey, entryKey, fieldKey) {
|
|
470
|
-
const constant = (raw) => raw.replaceAll(/([a-z0-9])([A-Z])/g, "$1_$2").replaceAll(/[^A-Za-z0-9]+/g, "_").replaceAll(/^_+|_+$/g, "").toUpperCase();
|
|
471
|
-
const parts = [entryKey ?? sectionKey, fieldKey.replace(/Env$/, "")].map((part) => constant(part)).filter(Boolean);
|
|
472
|
-
const name = parts.join("_") || "SECRET";
|
|
473
|
-
return /^\d/.test(name) ? `JX_${name}` : name;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// src/grid/grid-source.ts
|
|
477
|
-
function cellValuesEqual(a, b) {
|
|
478
|
-
if (a === b) {
|
|
479
|
-
return true;
|
|
480
|
-
}
|
|
481
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
482
|
-
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
483
|
-
}
|
|
484
|
-
if (a !== null && b !== null && typeof a === "object" && typeof b === "object" && !Array.isArray(a) && !Array.isArray(b)) {
|
|
485
|
-
return a.$ref === b.$ref;
|
|
486
|
-
}
|
|
487
|
-
return false;
|
|
488
|
-
}
|
|
489
|
-
var GRID_SCHEME = "grid://";
|
|
490
|
-
function makeGridTabId(ref2) {
|
|
491
|
-
switch (ref2.kind) {
|
|
492
|
-
case "pages": {
|
|
493
|
-
return `${GRID_SCHEME}pages`;
|
|
494
|
-
}
|
|
495
|
-
case "collection": {
|
|
496
|
-
return `${GRID_SCHEME}collection/${encodeURIComponent(ref2.name)}`;
|
|
497
|
-
}
|
|
498
|
-
default: {
|
|
499
|
-
return `${GRID_SCHEME}data/${encodeURIComponent(ref2.connection)}/${encodeURIComponent(ref2.table)}`;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
function isGridTabId(id) {
|
|
504
|
-
return id.startsWith(GRID_SCHEME);
|
|
505
|
-
}
|
|
506
|
-
function parseGridTabId(id) {
|
|
507
|
-
if (!isGridTabId(id)) {
|
|
508
|
-
return null;
|
|
509
|
-
}
|
|
510
|
-
const parts = id.slice(GRID_SCHEME.length).split("/");
|
|
511
|
-
if (parts[0] === "pages" && parts.length === 1) {
|
|
512
|
-
return { kind: "pages" };
|
|
513
|
-
}
|
|
514
|
-
if (parts[0] === "collection" && parts.length === 2 && parts[1]) {
|
|
515
|
-
return { kind: "collection", name: decodeURIComponent(parts[1]) };
|
|
516
|
-
}
|
|
517
|
-
if (parts[0] === "data" && parts.length === 3 && parts[1] && parts[2]) {
|
|
518
|
-
return {
|
|
519
|
-
connection: decodeURIComponent(parts[1]),
|
|
520
|
-
kind: "data",
|
|
521
|
-
table: decodeURIComponent(parts[2])
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
return null;
|
|
525
|
-
}
|
|
526
|
-
function gridTabLabel(id) {
|
|
527
|
-
const ref2 = parseGridTabId(id);
|
|
528
|
-
if (!ref2) {
|
|
529
|
-
return null;
|
|
530
|
-
}
|
|
531
|
-
switch (ref2.kind) {
|
|
532
|
-
case "pages": {
|
|
533
|
-
return "Pages · grid";
|
|
534
|
-
}
|
|
535
|
-
case "collection": {
|
|
536
|
-
return `${ref2.name} · grid`;
|
|
537
|
-
}
|
|
538
|
-
default: {
|
|
539
|
-
return `${ref2.table} @ ${ref2.connection} · grid`;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// src/grid/edit-buffer.ts
|
|
545
|
-
var _tempCounter = 0;
|
|
546
|
-
function createEditBuffer(opts) {
|
|
547
|
-
const coalesceMs = opts.coalesceMs ?? 800;
|
|
548
|
-
const now = opts.now ?? (() => Date.now());
|
|
549
|
-
const state = reactive({
|
|
550
|
-
deletes: new Set,
|
|
551
|
-
errors: new Map,
|
|
552
|
-
inserts: new Map,
|
|
553
|
-
pending: new Map,
|
|
554
|
-
redoStack: [],
|
|
555
|
-
stale: new Set,
|
|
556
|
-
undoStack: []
|
|
557
|
-
});
|
|
558
|
-
let groupOps = null;
|
|
559
|
-
const setError = (rowKey, field, message) => {
|
|
560
|
-
const row = state.errors.get(rowKey);
|
|
561
|
-
if (row) {
|
|
562
|
-
row.set(field, message);
|
|
563
|
-
} else {
|
|
564
|
-
state.errors.set(rowKey, new Map([[field, message]]));
|
|
565
|
-
}
|
|
566
|
-
};
|
|
567
|
-
const clearCellError = (rowKey, field) => {
|
|
568
|
-
const row = state.errors.get(rowKey);
|
|
569
|
-
row?.delete(field);
|
|
570
|
-
if (row?.size === 0) {
|
|
571
|
-
state.errors.delete(rowKey);
|
|
572
|
-
}
|
|
573
|
-
};
|
|
574
|
-
const pushOp = (op) => {
|
|
575
|
-
state.redoStack.length = 0;
|
|
576
|
-
if (groupOps) {
|
|
577
|
-
groupOps.push(op);
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
const last = state.undoStack.at(-1);
|
|
581
|
-
if (op.kind === "cell" && last?.kind === "cell" && last.rowKey === op.rowKey && last.field === op.field && op.at - last.at <= coalesceMs) {
|
|
582
|
-
last.after = op.after;
|
|
583
|
-
last.at = op.at;
|
|
584
|
-
return;
|
|
585
|
-
}
|
|
586
|
-
state.undoStack.push(op);
|
|
587
|
-
};
|
|
588
|
-
const writeCell = (rowKey, field, value) => {
|
|
589
|
-
const insertCells = state.inserts.get(rowKey);
|
|
590
|
-
if (insertCells) {
|
|
591
|
-
if (value === null) {
|
|
592
|
-
delete insertCells[field];
|
|
593
|
-
} else {
|
|
594
|
-
insertCells[field] = value;
|
|
595
|
-
}
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
if (cellValuesEqual(value, opts.resolveBaseline(rowKey, field))) {
|
|
599
|
-
const row2 = state.pending.get(rowKey);
|
|
600
|
-
row2?.delete(field);
|
|
601
|
-
if (row2?.size === 0) {
|
|
602
|
-
state.pending.delete(rowKey);
|
|
603
|
-
}
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
const row = state.pending.get(rowKey);
|
|
607
|
-
if (row) {
|
|
608
|
-
row.set(field, { value });
|
|
609
|
-
} else {
|
|
610
|
-
state.pending.set(rowKey, new Map([[field, { value }]]));
|
|
611
|
-
}
|
|
612
|
-
};
|
|
613
|
-
const effectiveValue = (rowKey, field) => {
|
|
614
|
-
const insertCells = state.inserts.get(rowKey);
|
|
615
|
-
if (insertCells) {
|
|
616
|
-
return insertCells[field] ?? null;
|
|
617
|
-
}
|
|
618
|
-
const pending = state.pending.get(rowKey)?.get(field);
|
|
619
|
-
return pending ? pending.value : opts.resolveBaseline(rowKey, field);
|
|
620
|
-
};
|
|
621
|
-
const cellError = (rowKey, field) => {
|
|
622
|
-
const row = state.errors.get(rowKey);
|
|
623
|
-
return row?.get(field) ?? row?.get("*") ?? null;
|
|
624
|
-
};
|
|
625
|
-
const applyOp = (op, direction) => {
|
|
626
|
-
switch (op.kind) {
|
|
627
|
-
case "cell": {
|
|
628
|
-
writeCell(op.rowKey, op.field, direction === "undo" ? op.before : op.after);
|
|
629
|
-
break;
|
|
630
|
-
}
|
|
631
|
-
case "insert": {
|
|
632
|
-
if (direction === "undo") {
|
|
633
|
-
state.inserts.delete(op.tempKey);
|
|
634
|
-
} else {
|
|
635
|
-
state.inserts.set(op.tempKey, { ...op.cells });
|
|
636
|
-
}
|
|
637
|
-
break;
|
|
638
|
-
}
|
|
639
|
-
case "drop-insert": {
|
|
640
|
-
if (direction === "undo") {
|
|
641
|
-
state.inserts.set(op.tempKey, { ...op.cells });
|
|
642
|
-
} else {
|
|
643
|
-
state.inserts.delete(op.tempKey);
|
|
644
|
-
}
|
|
645
|
-
break;
|
|
646
|
-
}
|
|
647
|
-
case "delete": {
|
|
648
|
-
if (direction === "undo") {
|
|
649
|
-
state.deletes.delete(op.rowKey);
|
|
650
|
-
} else {
|
|
651
|
-
state.deletes.add(op.rowKey);
|
|
652
|
-
}
|
|
653
|
-
break;
|
|
654
|
-
}
|
|
655
|
-
default: {
|
|
656
|
-
const ops = direction === "undo" ? op.ops.toReversed() : op.ops;
|
|
657
|
-
for (const child of ops) {
|
|
658
|
-
applyOp(child, direction);
|
|
659
|
-
}
|
|
660
|
-
break;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
};
|
|
664
|
-
const beginGroup = () => {
|
|
665
|
-
if (groupOps) {
|
|
666
|
-
throw new Error("edit-buffer: groups may not nest");
|
|
667
|
-
}
|
|
668
|
-
groupOps = [];
|
|
669
|
-
};
|
|
670
|
-
const endGroup = () => {
|
|
671
|
-
const ops = groupOps;
|
|
672
|
-
groupOps = null;
|
|
673
|
-
if (!ops) {
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
if (ops.length === 1) {
|
|
677
|
-
state.undoStack.push(ops[0]);
|
|
678
|
-
} else if (ops.length > 1) {
|
|
679
|
-
state.undoStack.push({ kind: "group", ops });
|
|
680
|
-
}
|
|
681
|
-
};
|
|
682
|
-
const pruneHistory = (dropRow) => {
|
|
683
|
-
const keep = (op) => {
|
|
684
|
-
switch (op.kind) {
|
|
685
|
-
case "cell": {
|
|
686
|
-
return dropRow(op.rowKey) ? null : op;
|
|
687
|
-
}
|
|
688
|
-
case "insert":
|
|
689
|
-
case "drop-insert": {
|
|
690
|
-
return dropRow(op.tempKey) ? null : op;
|
|
691
|
-
}
|
|
692
|
-
case "delete": {
|
|
693
|
-
return dropRow(op.rowKey) ? null : op;
|
|
694
|
-
}
|
|
695
|
-
default: {
|
|
696
|
-
const ops = op.ops.map((child) => keep(child)).filter((o) => o !== null);
|
|
697
|
-
return ops.length > 0 ? { kind: "group", ops } : null;
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
};
|
|
701
|
-
state.undoStack = state.undoStack.map(keep).filter((o) => o !== null);
|
|
702
|
-
state.redoStack = state.redoStack.map(keep).filter((o) => o !== null);
|
|
703
|
-
};
|
|
704
|
-
return {
|
|
705
|
-
applyCommitResult(result) {
|
|
706
|
-
const committedRows = new Set;
|
|
707
|
-
for (const cell of result.cells) {
|
|
708
|
-
if (cell.ok) {
|
|
709
|
-
const row = state.pending.get(cell.rowKey);
|
|
710
|
-
row?.delete(cell.field);
|
|
711
|
-
if (row?.size === 0) {
|
|
712
|
-
state.pending.delete(cell.rowKey);
|
|
713
|
-
}
|
|
714
|
-
clearCellError(cell.rowKey, cell.field);
|
|
715
|
-
} else {
|
|
716
|
-
setError(cell.rowKey, cell.field, cell.error ?? "Save failed");
|
|
717
|
-
if (cell.stale) {
|
|
718
|
-
state.stale.add(cell.rowKey);
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
for (const ins of result.inserts) {
|
|
723
|
-
if (ins.ok) {
|
|
724
|
-
state.inserts.delete(ins.tempKey);
|
|
725
|
-
state.errors.delete(ins.tempKey);
|
|
726
|
-
committedRows.add(ins.tempKey);
|
|
727
|
-
} else {
|
|
728
|
-
setError(ins.tempKey, "*", ins.error ?? "Insert failed");
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
for (const del of result.deletes) {
|
|
732
|
-
if (del.ok) {
|
|
733
|
-
state.deletes.delete(del.rowKey);
|
|
734
|
-
state.pending.delete(del.rowKey);
|
|
735
|
-
state.errors.delete(del.rowKey);
|
|
736
|
-
committedRows.add(del.rowKey);
|
|
737
|
-
} else {
|
|
738
|
-
setError(del.rowKey, "*", del.error ?? "Delete failed");
|
|
739
|
-
if (del.stale) {
|
|
740
|
-
state.stale.add(del.rowKey);
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
for (const row of committedRows) {
|
|
745
|
-
state.stale.delete(row);
|
|
746
|
-
}
|
|
747
|
-
if (committedRows.size > 0) {
|
|
748
|
-
pruneHistory((rowKey) => committedRows.has(rowKey));
|
|
749
|
-
}
|
|
750
|
-
},
|
|
751
|
-
buildBatch() {
|
|
752
|
-
const cells = [];
|
|
753
|
-
for (const [rowKey, fields] of state.pending) {
|
|
754
|
-
if (state.deletes.has(rowKey)) {
|
|
755
|
-
continue;
|
|
756
|
-
}
|
|
757
|
-
for (const [field, cell] of fields) {
|
|
758
|
-
cells.push({
|
|
759
|
-
baseline: opts.resolveBaseline(rowKey, field),
|
|
760
|
-
field,
|
|
761
|
-
rowKey,
|
|
762
|
-
value: cell.value
|
|
763
|
-
});
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
return {
|
|
767
|
-
cells,
|
|
768
|
-
deletes: [...state.deletes].map((rowKey) => ({ rowKey })),
|
|
769
|
-
inserts: [...state.inserts].map(([tempKey, insertCells]) => ({
|
|
770
|
-
cells: { ...insertCells },
|
|
771
|
-
tempKey
|
|
772
|
-
}))
|
|
773
|
-
};
|
|
774
|
-
},
|
|
775
|
-
canRedo: () => state.redoStack.length > 0,
|
|
776
|
-
canUndo: () => state.undoStack.length > 0,
|
|
777
|
-
cellError,
|
|
778
|
-
cellState(rowKey, field) {
|
|
779
|
-
if (cellError(rowKey, field) !== null) {
|
|
780
|
-
return "error";
|
|
781
|
-
}
|
|
782
|
-
if (state.stale.has(rowKey)) {
|
|
783
|
-
return "stale";
|
|
784
|
-
}
|
|
785
|
-
if (state.inserts.has(rowKey) || state.pending.get(rowKey)?.has(field)) {
|
|
786
|
-
return "dirty";
|
|
787
|
-
}
|
|
788
|
-
return "clean";
|
|
789
|
-
},
|
|
790
|
-
deleteRow(rowKey) {
|
|
791
|
-
if (state.deletes.has(rowKey)) {
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
const insertCells = state.inserts.get(rowKey);
|
|
795
|
-
if (insertCells) {
|
|
796
|
-
const cells = { ...insertCells };
|
|
797
|
-
state.inserts.delete(rowKey);
|
|
798
|
-
state.errors.delete(rowKey);
|
|
799
|
-
state.stale.delete(rowKey);
|
|
800
|
-
pushOp({ cells, kind: "drop-insert", tempKey: rowKey });
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
state.deletes.add(rowKey);
|
|
804
|
-
pushOp({ kind: "delete", rowKey });
|
|
805
|
-
},
|
|
806
|
-
dirtyCount() {
|
|
807
|
-
let count = state.inserts.size + state.deletes.size;
|
|
808
|
-
for (const [rowKey, fields] of state.pending) {
|
|
809
|
-
if (!state.deletes.has(rowKey)) {
|
|
810
|
-
count += fields.size;
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
return count;
|
|
814
|
-
},
|
|
815
|
-
discardRow(rowKey) {
|
|
816
|
-
state.pending.delete(rowKey);
|
|
817
|
-
state.deletes.delete(rowKey);
|
|
818
|
-
state.inserts.delete(rowKey);
|
|
819
|
-
state.errors.delete(rowKey);
|
|
820
|
-
state.stale.delete(rowKey);
|
|
821
|
-
pruneHistory((key) => key === rowKey);
|
|
822
|
-
},
|
|
823
|
-
effectiveValue,
|
|
824
|
-
beginGroup,
|
|
825
|
-
endGroup,
|
|
826
|
-
group(fn) {
|
|
827
|
-
beginGroup();
|
|
828
|
-
try {
|
|
829
|
-
fn();
|
|
830
|
-
} finally {
|
|
831
|
-
endGroup();
|
|
832
|
-
}
|
|
833
|
-
},
|
|
834
|
-
insertRow(cells = {}) {
|
|
835
|
-
_tempCounter += 1;
|
|
836
|
-
const tempKey = `__new-${_tempCounter}`;
|
|
837
|
-
state.inserts.set(tempKey, { ...cells });
|
|
838
|
-
pushOp({ cells: { ...cells }, kind: "insert", tempKey });
|
|
839
|
-
return tempKey;
|
|
840
|
-
},
|
|
841
|
-
isDirty: () => state.pending.size > 0 || state.inserts.size > 0 || state.deletes.size > 0,
|
|
842
|
-
isInsertKey: (rowKey) => state.inserts.has(rowKey),
|
|
843
|
-
markStale(rowKeys) {
|
|
844
|
-
for (const rowKey of rowKeys) {
|
|
845
|
-
state.stale.add(rowKey);
|
|
846
|
-
}
|
|
847
|
-
},
|
|
848
|
-
redo() {
|
|
849
|
-
const op = state.redoStack.pop();
|
|
850
|
-
if (!op) {
|
|
851
|
-
return false;
|
|
852
|
-
}
|
|
853
|
-
applyOp(op, "redo");
|
|
854
|
-
state.undoStack.push(op);
|
|
855
|
-
return true;
|
|
856
|
-
},
|
|
857
|
-
reset() {
|
|
858
|
-
state.pending.clear();
|
|
859
|
-
state.inserts.clear();
|
|
860
|
-
state.deletes.clear();
|
|
861
|
-
state.errors.clear();
|
|
862
|
-
state.stale.clear();
|
|
863
|
-
state.undoStack.length = 0;
|
|
864
|
-
state.redoStack.length = 0;
|
|
865
|
-
},
|
|
866
|
-
rowState(rowKey) {
|
|
867
|
-
if (state.deletes.has(rowKey)) {
|
|
868
|
-
return "pending-delete";
|
|
869
|
-
}
|
|
870
|
-
if (state.inserts.has(rowKey)) {
|
|
871
|
-
return "pending-insert";
|
|
872
|
-
}
|
|
873
|
-
if (state.stale.has(rowKey)) {
|
|
874
|
-
return "stale";
|
|
875
|
-
}
|
|
876
|
-
if (state.pending.has(rowKey) || state.errors.has(rowKey)) {
|
|
877
|
-
return "dirty";
|
|
878
|
-
}
|
|
879
|
-
return "clean";
|
|
880
|
-
},
|
|
881
|
-
setCell(rowKey, field, value) {
|
|
882
|
-
const before = effectiveValue(rowKey, field);
|
|
883
|
-
if (cellValuesEqual(before, value)) {
|
|
884
|
-
return;
|
|
885
|
-
}
|
|
886
|
-
writeCell(rowKey, field, value);
|
|
887
|
-
pushOp({ after: value, at: now(), before, field, kind: "cell", rowKey });
|
|
888
|
-
},
|
|
889
|
-
state,
|
|
890
|
-
undo() {
|
|
891
|
-
const op = state.undoStack.pop();
|
|
892
|
-
if (!op) {
|
|
893
|
-
return false;
|
|
894
|
-
}
|
|
895
|
-
applyOp(op, "undo");
|
|
896
|
-
state.redoStack.push(op);
|
|
897
|
-
return true;
|
|
898
|
-
}
|
|
899
|
-
};
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
// src/grid/grid-controller.ts
|
|
903
|
-
var ROW_KEY_FIELD = "__key";
|
|
904
|
-
var _controllers = new WeakMap;
|
|
905
|
-
function getGridController(tab) {
|
|
906
|
-
return tab ? _controllers.get(toRaw(tab)) ?? null : null;
|
|
907
|
-
}
|
|
908
|
-
function createGridController(tab, source) {
|
|
909
|
-
const state = reactive({
|
|
910
|
-
columns: [],
|
|
911
|
-
error: null,
|
|
912
|
-
loading: false,
|
|
913
|
-
query: {},
|
|
914
|
-
rows: [],
|
|
915
|
-
saving: false,
|
|
916
|
-
total: 0
|
|
917
|
-
});
|
|
918
|
-
let rowByKey = new Map;
|
|
919
|
-
const indexRows = (rows) => {
|
|
920
|
-
rowByKey = new Map(rows.map((row) => [row.key, row]));
|
|
921
|
-
};
|
|
922
|
-
const buffer = createEditBuffer({
|
|
923
|
-
resolveBaseline: (rowKey, field) => rowByKey.get(rowKey)?.cells[field] ?? null
|
|
924
|
-
});
|
|
925
|
-
const reloadRows = async () => {
|
|
926
|
-
const result = await source.rows(state.query);
|
|
927
|
-
state.rows = result.rows;
|
|
928
|
-
state.total = result.total;
|
|
929
|
-
indexRows(result.rows);
|
|
930
|
-
};
|
|
931
|
-
const requiredViolations = (batch) => {
|
|
932
|
-
const required = state.columns.filter((c) => c.required && c.editable);
|
|
933
|
-
if (required.length === 0) {
|
|
934
|
-
return null;
|
|
935
|
-
}
|
|
936
|
-
const cells = batch.cells.filter((cell) => cell.value === null && required.some((c) => c.field === cell.field)).map((cell) => ({ error: "Required", field: cell.field, ok: false, rowKey: cell.rowKey }));
|
|
937
|
-
const inserts = batch.inserts.filter((insert) => required.some((c) => insert.cells[c.field] === null || insert.cells[c.field] === undefined)).map((insert) => {
|
|
938
|
-
const missing = required.filter((c) => insert.cells[c.field] === null || insert.cells[c.field] === undefined).map((c) => c.field).join(", ");
|
|
939
|
-
return { error: `Missing required: ${missing}`, ok: false, tempKey: insert.tempKey };
|
|
940
|
-
});
|
|
941
|
-
if (cells.length === 0 && inserts.length === 0) {
|
|
942
|
-
return null;
|
|
943
|
-
}
|
|
944
|
-
return { cells, deletes: [], inserts };
|
|
945
|
-
};
|
|
946
|
-
let viewBinding = null;
|
|
947
|
-
const syncView = () => viewBinding?.refreshData();
|
|
948
|
-
const controller = {
|
|
949
|
-
addRow(cells = {}) {
|
|
950
|
-
const tempKey = buffer.insertRow(cells);
|
|
951
|
-
syncView();
|
|
952
|
-
return tempKey;
|
|
953
|
-
},
|
|
954
|
-
bindView(view2) {
|
|
955
|
-
viewBinding = view2;
|
|
956
|
-
},
|
|
957
|
-
buffer,
|
|
958
|
-
deleteRows(rowKeys) {
|
|
959
|
-
if (rowKeys.length === 0) {
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
buffer.group(() => {
|
|
963
|
-
for (const rowKey of rowKeys) {
|
|
964
|
-
buffer.deleteRow(rowKey);
|
|
965
|
-
}
|
|
966
|
-
});
|
|
967
|
-
syncView();
|
|
968
|
-
},
|
|
969
|
-
discardRow(rowKey) {
|
|
970
|
-
buffer.discardRow(rowKey);
|
|
971
|
-
syncView();
|
|
972
|
-
},
|
|
973
|
-
replaceAll(find, replace) {
|
|
974
|
-
if (find === "") {
|
|
975
|
-
return 0;
|
|
976
|
-
}
|
|
977
|
-
const textColumns = state.columns.filter((column) => column.editable && (column.kind === "string" || column.kind === "text"));
|
|
978
|
-
const rowKeys = [...state.rows.map((row) => row.key), ...buffer.state.inserts.keys()];
|
|
979
|
-
let changed = 0;
|
|
980
|
-
buffer.group(() => {
|
|
981
|
-
for (const rowKey of rowKeys) {
|
|
982
|
-
if (buffer.rowState(rowKey) === "pending-delete") {
|
|
983
|
-
continue;
|
|
984
|
-
}
|
|
985
|
-
for (const column of textColumns) {
|
|
986
|
-
const value = buffer.effectiveValue(rowKey, column.field);
|
|
987
|
-
if (typeof value === "string" && value.includes(find)) {
|
|
988
|
-
buffer.setCell(rowKey, column.field, value.replaceAll(find, replace));
|
|
989
|
-
changed += 1;
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
});
|
|
994
|
-
if (changed > 0) {
|
|
995
|
-
syncView();
|
|
996
|
-
}
|
|
997
|
-
return changed;
|
|
998
|
-
},
|
|
999
|
-
effectiveRows() {
|
|
1000
|
-
const plain = (value) => Array.isArray(value) ? [...value] : value !== null && typeof value === "object" ? { $ref: value.$ref } : value;
|
|
1001
|
-
const rowObject = (key) => {
|
|
1002
|
-
const cells = { [ROW_KEY_FIELD]: key };
|
|
1003
|
-
for (const column of state.columns) {
|
|
1004
|
-
cells[column.field] = plain(buffer.effectiveValue(key, column.field));
|
|
1005
|
-
}
|
|
1006
|
-
return cells;
|
|
1007
|
-
};
|
|
1008
|
-
const rows = state.rows.map((row) => rowObject(row.key));
|
|
1009
|
-
for (const [tempKey] of buffer.state.inserts) {
|
|
1010
|
-
rows.push(rowObject(tempKey));
|
|
1011
|
-
}
|
|
1012
|
-
return rows;
|
|
1013
|
-
},
|
|
1014
|
-
async load() {
|
|
1015
|
-
state.loading = true;
|
|
1016
|
-
state.error = null;
|
|
1017
|
-
try {
|
|
1018
|
-
state.columns = await source.columns();
|
|
1019
|
-
await reloadRows();
|
|
1020
|
-
} catch (error) {
|
|
1021
|
-
state.error = errorMessage(error);
|
|
1022
|
-
} finally {
|
|
1023
|
-
state.loading = false;
|
|
1024
|
-
}
|
|
1025
|
-
syncView();
|
|
1026
|
-
},
|
|
1027
|
-
async refresh() {
|
|
1028
|
-
if (buffer.isDirty()) {
|
|
1029
|
-
const confirmed = await showConfirmDialog("Refresh Grid", `Discard ${buffer.dirtyCount()} pending change(s) and reload?`, { confirmLabel: "Discard & Reload", destructive: true });
|
|
1030
|
-
if (!confirmed) {
|
|
1031
|
-
return;
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
buffer.reset();
|
|
1035
|
-
await source.refresh?.();
|
|
1036
|
-
await this.load();
|
|
1037
|
-
},
|
|
1038
|
-
async save() {
|
|
1039
|
-
if (state.saving) {
|
|
1040
|
-
return;
|
|
1041
|
-
}
|
|
1042
|
-
const batch = buffer.buildBatch();
|
|
1043
|
-
const changeCount = batch.cells.length + batch.inserts.length + batch.deletes.length;
|
|
1044
|
-
if (changeCount === 0) {
|
|
1045
|
-
statusMessage("No grid changes to save");
|
|
1046
|
-
return;
|
|
1047
|
-
}
|
|
1048
|
-
const violations = requiredViolations(batch);
|
|
1049
|
-
if (violations) {
|
|
1050
|
-
buffer.applyCommitResult(violations);
|
|
1051
|
-
statusMessage("Required cells are empty — fix the marked rows and save again", 5000);
|
|
1052
|
-
return;
|
|
1053
|
-
}
|
|
1054
|
-
if (batch.deletes.length > 0) {
|
|
1055
|
-
const confirmed = await showConfirmDialog("Delete Rows", `Save will permanently delete ${batch.deletes.length} row(s). Continue?`, { confirmLabel: "Delete & Save", destructive: true });
|
|
1056
|
-
if (!confirmed) {
|
|
1057
|
-
return;
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
const affectedRows = new Set(batch.cells.map((c) => c.rowKey)).size + batch.inserts.length + batch.deletes.length;
|
|
1061
|
-
const progress = affectedRows > 5 ? showProgressModal({ title: "Saving grid" }) : null;
|
|
1062
|
-
state.saving = true;
|
|
1063
|
-
try {
|
|
1064
|
-
const result = await source.commit(batch);
|
|
1065
|
-
const cellKey = (rowKey, field) => `${rowKey}\x00${field}`;
|
|
1066
|
-
const batchCells = new Map(batch.cells.map((c) => [cellKey(c.rowKey, c.field), c]));
|
|
1067
|
-
for (const outcome of result.cells) {
|
|
1068
|
-
const cell = batchCells.get(cellKey(outcome.rowKey, outcome.field));
|
|
1069
|
-
if (outcome.ok && cell) {
|
|
1070
|
-
const row = rowByKey.get(outcome.rowKey);
|
|
1071
|
-
if (row) {
|
|
1072
|
-
row.cells[outcome.field] = cell.value;
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
buffer.applyCommitResult(result);
|
|
1077
|
-
const structural = result.inserts.some((r) => r.ok) || result.deletes.some((r) => r.ok);
|
|
1078
|
-
if (structural) {
|
|
1079
|
-
await reloadRows();
|
|
1080
|
-
}
|
|
1081
|
-
const okCount = result.cells.filter((r) => r.ok).length + result.inserts.filter((r) => r.ok).length + result.deletes.filter((r) => r.ok).length;
|
|
1082
|
-
const failed = changeCount - okCount;
|
|
1083
|
-
const staleCount = result.cells.filter((r) => r.stale).length + result.deletes.filter((r) => r.stale).length;
|
|
1084
|
-
statusMessage(failed === 0 ? `Saved ${okCount} change(s)` : `Saved ${okCount} · ${failed} failed${staleCount ? ` (${staleCount} stale)` : ""} — kept pending`, failed === 0 ? 3000 : 6000);
|
|
1085
|
-
} catch (error) {
|
|
1086
|
-
statusMessage(`Save error: ${errorMessage(error)}`, 6000);
|
|
1087
|
-
} finally {
|
|
1088
|
-
state.saving = false;
|
|
1089
|
-
progress?.done();
|
|
1090
|
-
syncView();
|
|
1091
|
-
}
|
|
1092
|
-
},
|
|
1093
|
-
serializeForSource() {
|
|
1094
|
-
if (!source.serializeForSource) {
|
|
1095
|
-
return null;
|
|
1096
|
-
}
|
|
1097
|
-
return source.serializeForSource(buffer.buildBatch());
|
|
1098
|
-
},
|
|
1099
|
-
async setQuery(query) {
|
|
1100
|
-
state.query = query;
|
|
1101
|
-
state.loading = true;
|
|
1102
|
-
try {
|
|
1103
|
-
await reloadRows();
|
|
1104
|
-
} catch (error) {
|
|
1105
|
-
state.error = errorMessage(error);
|
|
1106
|
-
} finally {
|
|
1107
|
-
state.loading = false;
|
|
1108
|
-
}
|
|
1109
|
-
syncView();
|
|
1110
|
-
},
|
|
1111
|
-
source,
|
|
1112
|
-
state,
|
|
1113
|
-
tab
|
|
1114
|
-
};
|
|
1115
|
-
tab.scope.run(() => {
|
|
1116
|
-
effect(() => {
|
|
1117
|
-
tab.doc.dirty = buffer.isDirty();
|
|
1118
|
-
});
|
|
1119
|
-
setHistoryDelegate(tab, {
|
|
1120
|
-
canRedo: () => buffer.canRedo(),
|
|
1121
|
-
canUndo: () => buffer.canUndo(),
|
|
1122
|
-
redo: () => {
|
|
1123
|
-
buffer.redo();
|
|
1124
|
-
syncView();
|
|
1125
|
-
},
|
|
1126
|
-
undo: () => {
|
|
1127
|
-
buffer.undo();
|
|
1128
|
-
syncView();
|
|
1129
|
-
}
|
|
1130
|
-
});
|
|
1131
|
-
const platform = getPlatform();
|
|
1132
|
-
if (platform.subscribeFileEvents && source.backingPaths) {
|
|
1133
|
-
const unsubscribe = platform.subscribeFileEvents((events) => {
|
|
1134
|
-
const backing = source.backingPaths();
|
|
1135
|
-
const hitKeys = new Set;
|
|
1136
|
-
for (const event of events) {
|
|
1137
|
-
if (event.isDir) {
|
|
1138
|
-
continue;
|
|
1139
|
-
}
|
|
1140
|
-
const path = event.path.replaceAll("\\", "/");
|
|
1141
|
-
const rowKey = backing.get(path);
|
|
1142
|
-
if (rowKey !== undefined && !isRecentLocal(path)) {
|
|
1143
|
-
hitKeys.add(rowKey);
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
if (hitKeys.size === 0) {
|
|
1147
|
-
return;
|
|
1148
|
-
}
|
|
1149
|
-
if (buffer.isDirty()) {
|
|
1150
|
-
const keys = hitKeys.has("*") ? state.rows.map((row) => row.key) : [...hitKeys];
|
|
1151
|
-
buffer.markStale(keys);
|
|
1152
|
-
statusMessage("Grid rows changed on disk — marked stale (refresh to reload)", 5000);
|
|
1153
|
-
syncView();
|
|
1154
|
-
} else {
|
|
1155
|
-
controller.refresh();
|
|
1156
|
-
}
|
|
1157
|
-
});
|
|
1158
|
-
onScopeDispose(unsubscribe);
|
|
1159
|
-
}
|
|
1160
|
-
onScopeDispose(() => {
|
|
1161
|
-
setHistoryDelegate(tab, null);
|
|
1162
|
-
_controllers.delete(toRaw(tab));
|
|
1163
|
-
source.dispose?.();
|
|
1164
|
-
});
|
|
1165
|
-
});
|
|
1166
|
-
_controllers.set(toRaw(tab), controller);
|
|
1167
|
-
return controller;
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
// src/grid/csv-codec.ts
|
|
1171
|
-
function splitRecords(text) {
|
|
1172
|
-
const records = [];
|
|
1173
|
-
let fields = [];
|
|
1174
|
-
let field = "";
|
|
1175
|
-
let inQuotes = false;
|
|
1176
|
-
let fieldStarted = false;
|
|
1177
|
-
const endField = () => {
|
|
1178
|
-
fields.push(field);
|
|
1179
|
-
field = "";
|
|
1180
|
-
fieldStarted = false;
|
|
1181
|
-
};
|
|
1182
|
-
const endRecord = () => {
|
|
1183
|
-
endField();
|
|
1184
|
-
if (fields.length === 1 && fields[0] === "") {
|
|
1185
|
-
fields = [];
|
|
1186
|
-
return;
|
|
1187
|
-
}
|
|
1188
|
-
records.push(fields);
|
|
1189
|
-
fields = [];
|
|
1190
|
-
};
|
|
1191
|
-
for (let i = 0;i < text.length; i++) {
|
|
1192
|
-
const ch = text[i];
|
|
1193
|
-
if (inQuotes) {
|
|
1194
|
-
if (ch === '"') {
|
|
1195
|
-
if (text[i + 1] === '"') {
|
|
1196
|
-
field += '"';
|
|
1197
|
-
i += 1;
|
|
1198
|
-
} else {
|
|
1199
|
-
inQuotes = false;
|
|
1200
|
-
}
|
|
1201
|
-
} else {
|
|
1202
|
-
field += ch;
|
|
1203
|
-
}
|
|
1204
|
-
continue;
|
|
1205
|
-
}
|
|
1206
|
-
if (ch === '"' && !fieldStarted) {
|
|
1207
|
-
inQuotes = true;
|
|
1208
|
-
fieldStarted = true;
|
|
1209
|
-
} else if (ch === '"') {
|
|
1210
|
-
field += ch;
|
|
1211
|
-
} else if (ch === ",") {
|
|
1212
|
-
endField();
|
|
1213
|
-
} else if (ch === `
|
|
1214
|
-
`) {
|
|
1215
|
-
endRecord();
|
|
1216
|
-
} else if (ch === "\r") {
|
|
1217
|
-
if (text[i + 1] === `
|
|
1218
|
-
`) {
|
|
1219
|
-
i += 1;
|
|
1220
|
-
}
|
|
1221
|
-
endRecord();
|
|
1222
|
-
} else {
|
|
1223
|
-
field += ch;
|
|
1224
|
-
fieldStarted = true;
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
if (field !== "" || fields.length > 0) {
|
|
1228
|
-
endRecord();
|
|
1229
|
-
}
|
|
1230
|
-
return records;
|
|
1231
|
-
}
|
|
1232
|
-
function parseCsv(text) {
|
|
1233
|
-
const crlf = (text.match(/\r\n/g) ?? []).length;
|
|
1234
|
-
const bareLf = (text.match(/(?<!\r)\n/g) ?? []).length;
|
|
1235
|
-
const eol = crlf > bareLf ? `\r
|
|
1236
|
-
` : `
|
|
1237
|
-
`;
|
|
1238
|
-
const trailingNewline = text.endsWith(`
|
|
1239
|
-
`);
|
|
1240
|
-
const records = splitRecords(text);
|
|
1241
|
-
const headers = records[0] ?? [];
|
|
1242
|
-
const rows = records.slice(1);
|
|
1243
|
-
const width = Math.max(headers.length, ...rows.map((r) => r.length), 0);
|
|
1244
|
-
while (headers.length < width) {
|
|
1245
|
-
headers.push("");
|
|
1246
|
-
}
|
|
1247
|
-
for (const row of rows) {
|
|
1248
|
-
while (row.length < width) {
|
|
1249
|
-
row.push("");
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
return { eol, headers, rows, trailingNewline };
|
|
1253
|
-
}
|
|
1254
|
-
function encodeField(value) {
|
|
1255
|
-
if (/[",\n\r]/.test(value)) {
|
|
1256
|
-
return `"${value.replaceAll('"', '""')}"`;
|
|
1257
|
-
}
|
|
1258
|
-
return value;
|
|
1259
|
-
}
|
|
1260
|
-
function serializeCsv(doc) {
|
|
1261
|
-
const lines = [doc.headers, ...doc.rows].map((fields) => fields.map((field) => encodeField(field)).join(","));
|
|
1262
|
-
return lines.join(doc.eol) + (doc.trailingNewline ? doc.eol : "");
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
// src/grid/schema-columns.ts
|
|
1266
|
-
var KIND_WIDTHS = {
|
|
1267
|
-
array: 220,
|
|
1268
|
-
boolean: 70,
|
|
1269
|
-
date: 130,
|
|
1270
|
-
enum: 140,
|
|
1271
|
-
image: 180,
|
|
1272
|
-
number: 110,
|
|
1273
|
-
readonly: 200,
|
|
1274
|
-
reference: 200,
|
|
1275
|
-
string: 180,
|
|
1276
|
-
text: 340
|
|
1277
|
-
};
|
|
1278
|
-
function clampWidth(maxLen) {
|
|
1279
|
-
return Math.min(340, Math.max(90, Math.round(maxLen * 7.5)));
|
|
1280
|
-
}
|
|
1281
|
-
function kindForProp(prop) {
|
|
1282
|
-
if (prop.$ref) {
|
|
1283
|
-
return "reference";
|
|
1284
|
-
}
|
|
1285
|
-
if (prop.enum !== undefined && Array.isArray(prop.enum)) {
|
|
1286
|
-
return "enum";
|
|
1287
|
-
}
|
|
1288
|
-
const format = prop.type === "array" ? undefined : prop.format;
|
|
1289
|
-
if (format === "image") {
|
|
1290
|
-
return "image";
|
|
1291
|
-
}
|
|
1292
|
-
if (format === "date" || format === "date-time") {
|
|
1293
|
-
return "date";
|
|
1294
|
-
}
|
|
1295
|
-
switch (prop.type) {
|
|
1296
|
-
case "number":
|
|
1297
|
-
case "integer": {
|
|
1298
|
-
return "number";
|
|
1299
|
-
}
|
|
1300
|
-
case "boolean": {
|
|
1301
|
-
return "boolean";
|
|
1302
|
-
}
|
|
1303
|
-
case "array": {
|
|
1304
|
-
return "array";
|
|
1305
|
-
}
|
|
1306
|
-
case "object": {
|
|
1307
|
-
return "readonly";
|
|
1308
|
-
}
|
|
1309
|
-
default: {
|
|
1310
|
-
return (prop.maxLength ?? 0) > 200 || prop.format === "markdown" ? "text" : "string";
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
function titleForField(field) {
|
|
1315
|
-
const spaced = field.replaceAll(/([a-z0-9])([A-Z])/g, "$1 $2").replaceAll(/[_-]+/g, " ").trim();
|
|
1316
|
-
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
1317
|
-
}
|
|
1318
|
-
function columnsFromSchema(schema, opts = {}) {
|
|
1319
|
-
const props = Object.entries(schema?.properties ?? {});
|
|
1320
|
-
const required = new Set(schema?.required);
|
|
1321
|
-
const readonly = new Set(opts.readonlyFields);
|
|
1322
|
-
const columns = props.map(([field, prop]) => {
|
|
1323
|
-
const kind = readonly.has(field) ? "readonly" : kindForProp(prop);
|
|
1324
|
-
const enumLen = Array.isArray(prop.enum) ? Math.max(...prop.enum.map((v) => String(v).length), 4) : 0;
|
|
1325
|
-
return {
|
|
1326
|
-
editable: kind !== "readonly",
|
|
1327
|
-
field,
|
|
1328
|
-
kind,
|
|
1329
|
-
pk: field === opts.idField,
|
|
1330
|
-
required: required.has(field),
|
|
1331
|
-
schema: prop,
|
|
1332
|
-
title: titleForField(field),
|
|
1333
|
-
widthHint: kind === "enum" ? clampWidth(enumLen + 3) : KIND_WIDTHS[kind]
|
|
1334
|
-
};
|
|
1335
|
-
});
|
|
1336
|
-
columns.sort((a, b) => {
|
|
1337
|
-
const rank = (c) => c.pk ? 0 : c.required ? 1 : 2;
|
|
1338
|
-
const diff = rank(a) - rank(b);
|
|
1339
|
-
return diff === 0 ? 0 : diff;
|
|
1340
|
-
});
|
|
1341
|
-
return columns;
|
|
1342
|
-
}
|
|
1343
|
-
function inferColumnsFromRows(rows, sampleLimit = 50) {
|
|
1344
|
-
const sample = rows.slice(0, sampleLimit);
|
|
1345
|
-
const fields = [];
|
|
1346
|
-
for (const row of sample) {
|
|
1347
|
-
for (const key of Object.keys(row)) {
|
|
1348
|
-
if (!fields.includes(key)) {
|
|
1349
|
-
fields.push(key);
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
return fields.map((field) => {
|
|
1354
|
-
const values = sample.map((row) => row[field]).filter((v) => v !== null && v !== undefined && v !== "");
|
|
1355
|
-
const kind = inferKind(values);
|
|
1356
|
-
const maxLen = Math.max(...values.map((v) => cellToText(v).length), field.length, 4);
|
|
1357
|
-
return {
|
|
1358
|
-
editable: true,
|
|
1359
|
-
field,
|
|
1360
|
-
kind,
|
|
1361
|
-
title: titleForField(field),
|
|
1362
|
-
widthHint: kind === "string" || kind === "text" ? clampWidth(maxLen) : KIND_WIDTHS[kind]
|
|
1363
|
-
};
|
|
1364
|
-
});
|
|
1365
|
-
}
|
|
1366
|
-
function inferKind(values) {
|
|
1367
|
-
if (values.length === 0) {
|
|
1368
|
-
return "string";
|
|
1369
|
-
}
|
|
1370
|
-
if (values.some((v) => Array.isArray(v))) {
|
|
1371
|
-
return "array";
|
|
1372
|
-
}
|
|
1373
|
-
const texts = values.map(String);
|
|
1374
|
-
if (values.every((v) => typeof v === "boolean") || texts.every((t) => /^(true|false)$/i.test(t))) {
|
|
1375
|
-
return "boolean";
|
|
1376
|
-
}
|
|
1377
|
-
if (values.every((v) => typeof v === "number") || texts.every((t) => t.trim() !== "" && !Number.isNaN(Number(t.replaceAll(/[$€£¥,\s]/g, ""))))) {
|
|
1378
|
-
return "number";
|
|
1379
|
-
}
|
|
1380
|
-
if (texts.every((t) => /^\d{4}-\d{2}-\d{2}([T ].*)?$/.test(t))) {
|
|
1381
|
-
return "date";
|
|
1382
|
-
}
|
|
1383
|
-
if (texts.some((t) => t.length > 200)) {
|
|
1384
|
-
return "text";
|
|
1385
|
-
}
|
|
1386
|
-
return "string";
|
|
1387
|
-
}
|
|
1388
|
-
function coerceCellInput(raw, col) {
|
|
1389
|
-
if (raw === null || raw === undefined) {
|
|
1390
|
-
return null;
|
|
1391
|
-
}
|
|
1392
|
-
switch (col.kind) {
|
|
1393
|
-
case "number": {
|
|
1394
|
-
if (typeof raw === "number") {
|
|
1395
|
-
return Number.isNaN(raw) ? null : raw;
|
|
1396
|
-
}
|
|
1397
|
-
const cleaned = String(raw).trim().replaceAll(/[$€£¥,\s]/g, "");
|
|
1398
|
-
if (cleaned === "") {
|
|
1399
|
-
return null;
|
|
1400
|
-
}
|
|
1401
|
-
const n = Number(cleaned);
|
|
1402
|
-
return Number.isNaN(n) ? null : n;
|
|
1403
|
-
}
|
|
1404
|
-
case "boolean": {
|
|
1405
|
-
if (typeof raw === "boolean") {
|
|
1406
|
-
return raw;
|
|
1407
|
-
}
|
|
1408
|
-
return String(raw).trim().toLowerCase() === "true";
|
|
1409
|
-
}
|
|
1410
|
-
case "array": {
|
|
1411
|
-
if (Array.isArray(raw)) {
|
|
1412
|
-
return raw.map(String);
|
|
1413
|
-
}
|
|
1414
|
-
const text = String(raw).trim();
|
|
1415
|
-
return text === "" ? [] : text.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1416
|
-
}
|
|
1417
|
-
case "reference": {
|
|
1418
|
-
if (typeof raw === "object" && raw !== null && "$ref" in raw) {
|
|
1419
|
-
const ref2 = raw.$ref;
|
|
1420
|
-
return typeof ref2 === "string" && ref2 !== "" ? ref2 : null;
|
|
1421
|
-
}
|
|
1422
|
-
const text = String(raw).trim();
|
|
1423
|
-
return text === "" ? null : text;
|
|
1424
|
-
}
|
|
1425
|
-
default: {
|
|
1426
|
-
const text = String(raw);
|
|
1427
|
-
return text === "" ? null : text;
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
function cellToText(value) {
|
|
1432
|
-
if (value === null || value === undefined) {
|
|
1433
|
-
return "";
|
|
1434
|
-
}
|
|
1435
|
-
if (Array.isArray(value)) {
|
|
1436
|
-
return value.join(", ");
|
|
1437
|
-
}
|
|
1438
|
-
if (typeof value === "object") {
|
|
1439
|
-
return value.$ref;
|
|
1440
|
-
}
|
|
1441
|
-
return String(value);
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
// src/grid/sources/csv-file-source.ts
|
|
1445
|
-
var ID_FIELDS = ["id", "sku", "slug", "Slug"];
|
|
1446
|
-
function fieldNamesForHeaders(headers) {
|
|
1447
|
-
const used = new Set;
|
|
1448
|
-
return headers.map((header, i) => {
|
|
1449
|
-
let name = header.trim() === "" ? `column${i + 1}` : header;
|
|
1450
|
-
if (used.has(name)) {
|
|
1451
|
-
name = `${name}__${i + 1}`;
|
|
1452
|
-
}
|
|
1453
|
-
used.add(name);
|
|
1454
|
-
return name;
|
|
1455
|
-
});
|
|
1456
|
-
}
|
|
1457
|
-
function pickIdField(fields, doc) {
|
|
1458
|
-
for (const candidate of ID_FIELDS) {
|
|
1459
|
-
const idx = fields.indexOf(candidate);
|
|
1460
|
-
if (idx === -1) {
|
|
1461
|
-
continue;
|
|
1462
|
-
}
|
|
1463
|
-
const values = doc.rows.map((row) => row[idx] ?? "");
|
|
1464
|
-
if (values.every((v) => v !== "") && new Set(values).size === values.length) {
|
|
1465
|
-
return candidate;
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
|
-
return null;
|
|
1469
|
-
}
|
|
1470
|
-
function buildModel(text, path) {
|
|
1471
|
-
const doc = parseCsv(text);
|
|
1472
|
-
const fields = fieldNamesForHeaders(doc.headers);
|
|
1473
|
-
const idField = pickIdField(fields, doc);
|
|
1474
|
-
const idIndex = idField ? fields.indexOf(idField) : -1;
|
|
1475
|
-
const keys = doc.rows.map((row, i) => idIndex === -1 ? String(i) : row[idIndex]);
|
|
1476
|
-
const keyToIndex = new Map(keys.map((key, i) => [key, i]));
|
|
1477
|
-
const contentType = findContentTypeSchema(path, projectState?.projectConfig);
|
|
1478
|
-
const schemaProps = contentType?.schema?.properties ?? {};
|
|
1479
|
-
const sampleRows = doc.rows.slice(0, 50).map((row) => {
|
|
1480
|
-
const cells = {};
|
|
1481
|
-
for (const [i, field] of fields.entries()) {
|
|
1482
|
-
cells[field] = row[i] ?? "";
|
|
1483
|
-
}
|
|
1484
|
-
return cells;
|
|
1485
|
-
});
|
|
1486
|
-
const inferred = new Map(inferColumnsFromRows(sampleRows).map((c) => [c.field, c]));
|
|
1487
|
-
const typed = new Map(columnsFromSchema(contentType?.schema, { idField: idField ?? undefined }).map((c) => [
|
|
1488
|
-
c.field,
|
|
1489
|
-
c
|
|
1490
|
-
]));
|
|
1491
|
-
const columns = fields.map((field, i) => {
|
|
1492
|
-
const fromSchema = field in schemaProps ? typed.get(field) : undefined;
|
|
1493
|
-
const column = fromSchema ?? inferred.get(field) ?? {
|
|
1494
|
-
editable: true,
|
|
1495
|
-
field,
|
|
1496
|
-
kind: "string",
|
|
1497
|
-
title: doc.headers[i] || field
|
|
1498
|
-
};
|
|
1499
|
-
return { ...column, pk: field === idField, title: doc.headers[i]?.trim() || column.title };
|
|
1500
|
-
});
|
|
1501
|
-
return { columns, doc, fields, keys, keyToIndex, loadText: text };
|
|
1502
|
-
}
|
|
1503
|
-
function rowCells(model, rowIndex) {
|
|
1504
|
-
const cells = {};
|
|
1505
|
-
const row = model.doc.rows[rowIndex] ?? [];
|
|
1506
|
-
for (const [i, field] of model.fields.entries()) {
|
|
1507
|
-
const column = model.columns[i];
|
|
1508
|
-
const raw = row[i] ?? "";
|
|
1509
|
-
cells[field] = column.kind === "string" || column.kind === "text" || column.kind === "readonly" ? raw : coerceCellInput(raw, column);
|
|
1510
|
-
}
|
|
1511
|
-
return cells;
|
|
1512
|
-
}
|
|
1513
|
-
function applyBatch(model, batch) {
|
|
1514
|
-
const rows = model.doc.rows.map((row) => [...row]);
|
|
1515
|
-
const colFor = (field) => model.fields.indexOf(field);
|
|
1516
|
-
for (const cell of batch.cells) {
|
|
1517
|
-
const rowIndex = model.keyToIndex.get(cell.rowKey);
|
|
1518
|
-
const colIndex = colFor(cell.field);
|
|
1519
|
-
if (rowIndex === undefined || colIndex === -1) {
|
|
1520
|
-
continue;
|
|
1521
|
-
}
|
|
1522
|
-
rows[rowIndex][colIndex] = cellToText(cell.value);
|
|
1523
|
-
}
|
|
1524
|
-
for (const insert of batch.inserts) {
|
|
1525
|
-
const row = model.fields.map((field) => cellToText(insert.cells[field] ?? null));
|
|
1526
|
-
rows.push(row);
|
|
1527
|
-
}
|
|
1528
|
-
const dropIndexes = batch.deletes.map((del) => model.keyToIndex.get(del.rowKey)).filter((i) => i !== undefined).toSorted((a, b) => b - a);
|
|
1529
|
-
for (const index of dropIndexes) {
|
|
1530
|
-
rows.splice(index, 1);
|
|
1531
|
-
}
|
|
1532
|
-
return { ...model.doc, rows };
|
|
1533
|
-
}
|
|
1534
|
-
function uniformResult(batch, outcome) {
|
|
1535
|
-
return {
|
|
1536
|
-
cells: batch.cells.map((c) => ({ field: c.field, rowKey: c.rowKey, ...outcome })),
|
|
1537
|
-
deletes: batch.deletes.map((d) => ({ rowKey: d.rowKey, ...outcome })),
|
|
1538
|
-
inserts: batch.inserts.map((i) => ({
|
|
1539
|
-
error: outcome.error,
|
|
1540
|
-
ok: outcome.ok,
|
|
1541
|
-
tempKey: i.tempKey
|
|
1542
|
-
}))
|
|
1543
|
-
};
|
|
1544
|
-
}
|
|
1545
|
-
function createCsvFileSource(path) {
|
|
1546
|
-
let loadPromise = null;
|
|
1547
|
-
const load = (force = false) => {
|
|
1548
|
-
if (!force && loadPromise) {
|
|
1549
|
-
return loadPromise;
|
|
1550
|
-
}
|
|
1551
|
-
loadPromise = getPlatform().readFile(path).then((text) => buildModel(text, path));
|
|
1552
|
-
return loadPromise;
|
|
1553
|
-
};
|
|
1554
|
-
return {
|
|
1555
|
-
backingPaths: () => new Map([[path, "*"]]),
|
|
1556
|
-
capabilities: { delete: true, insert: true, remotePaging: false, remoteSort: false },
|
|
1557
|
-
async columns() {
|
|
1558
|
-
const current = await load();
|
|
1559
|
-
return current.columns;
|
|
1560
|
-
},
|
|
1561
|
-
async commit(batch) {
|
|
1562
|
-
const current = await load();
|
|
1563
|
-
const platform = getPlatform();
|
|
1564
|
-
let onDisk = null;
|
|
1565
|
-
try {
|
|
1566
|
-
onDisk = await platform.readFile(path);
|
|
1567
|
-
} catch {
|
|
1568
|
-
onDisk = null;
|
|
1569
|
-
}
|
|
1570
|
-
if (onDisk !== current.loadText && !isRecentLocal(path)) {
|
|
1571
|
-
return uniformResult(batch, {
|
|
1572
|
-
error: "File changed on disk — refresh to reload",
|
|
1573
|
-
ok: false,
|
|
1574
|
-
stale: true
|
|
1575
|
-
});
|
|
1576
|
-
}
|
|
1577
|
-
const nextDoc = applyBatch(current, batch);
|
|
1578
|
-
const text = serializeCsv(nextDoc);
|
|
1579
|
-
markLocalMutation(path);
|
|
1580
|
-
await platform.writeFile(path, text);
|
|
1581
|
-
loadPromise = Promise.resolve(buildModel(text, path));
|
|
1582
|
-
return uniformResult(batch, { ok: true });
|
|
1583
|
-
},
|
|
1584
|
-
id: path,
|
|
1585
|
-
label: path.split("/").pop() ?? path,
|
|
1586
|
-
async refresh() {
|
|
1587
|
-
await load(true);
|
|
1588
|
-
},
|
|
1589
|
-
async rows() {
|
|
1590
|
-
const current = await load();
|
|
1591
|
-
const rows = current.doc.rows.map((_, i) => ({
|
|
1592
|
-
cells: rowCells(current, i),
|
|
1593
|
-
key: current.keys[i]
|
|
1594
|
-
}));
|
|
1595
|
-
return { rows, total: rows.length };
|
|
1596
|
-
},
|
|
1597
|
-
async serializeForSource(batch) {
|
|
1598
|
-
const current = await load();
|
|
1599
|
-
return serializeCsv(applyBatch(current, batch));
|
|
1600
|
-
}
|
|
1601
|
-
};
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
// src/files/file-ops.ts
|
|
1605
|
-
async function parseFormatSource(format, source) {
|
|
1606
|
-
const doc = await formatParse(format.name, source);
|
|
1607
|
-
return splitFormatDocument(format, doc);
|
|
1608
|
-
}
|
|
1609
|
-
async function parseSourceForPath(path, source) {
|
|
1610
|
-
await loadFormats();
|
|
1611
|
-
const format = formatForPath(path);
|
|
1612
|
-
if (!format || !format.capabilities.parse) {
|
|
1613
|
-
throw noFormatError(path);
|
|
1614
|
-
}
|
|
1615
|
-
const result = await parseFormatSource(format, source);
|
|
1616
|
-
return { ...result, format };
|
|
1617
|
-
}
|
|
1618
|
-
function savedMessage(tab, savedMsg) {
|
|
1619
|
-
const doc = tab.doc.document;
|
|
1620
|
-
const warning = typeof doc.tagName === "string" && doc.tagName.includes("-") ? validateComponentSlots(doc) : null;
|
|
1621
|
-
if (warning) {
|
|
1622
|
-
statusMessage(`${savedMsg} — Warning: ${warning}`, 6000);
|
|
1623
|
-
} else {
|
|
1624
|
-
statusMessage(savedMsg);
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
async function saveFile() {
|
|
1628
|
-
const tab = activeTab.value;
|
|
1629
|
-
if (!tab) {
|
|
1630
|
-
return;
|
|
1631
|
-
}
|
|
1632
|
-
await flushCanvasEdits(tab.id);
|
|
1633
|
-
const grid = getGridController(tab);
|
|
1634
|
-
if (grid) {
|
|
1635
|
-
await grid.save();
|
|
1636
|
-
return;
|
|
1637
|
-
}
|
|
1638
|
-
try {
|
|
1639
|
-
if (await collabSave(tab)) {
|
|
1640
|
-
savedMessage(tab, "Synced");
|
|
1641
|
-
return;
|
|
1642
|
-
}
|
|
1643
|
-
const output = await serializeDocument(tab);
|
|
1644
|
-
if (tab.documentPath) {
|
|
1645
|
-
const platform = getPlatform();
|
|
1646
|
-
await platform.writeFile(tab.documentPath, output);
|
|
1647
|
-
tab.doc.dirty = false;
|
|
1648
|
-
savedMessage(tab, "Saved");
|
|
1649
|
-
} else if (tab.fileHandle && "createWritable" in tab.fileHandle) {
|
|
1650
|
-
const writable = await tab.fileHandle.createWritable();
|
|
1651
|
-
await writable.write(output);
|
|
1652
|
-
await writable.close();
|
|
1653
|
-
tab.doc.dirty = false;
|
|
1654
|
-
savedMessage(tab, "Saved");
|
|
1655
|
-
} else {
|
|
1656
|
-
statusMessage("No save target — use Export");
|
|
1657
|
-
}
|
|
1658
|
-
} catch (error) {
|
|
1659
|
-
if (!(error instanceof Error && error.name === "AbortError")) {
|
|
1660
|
-
statusMessage(`Save error: ${errorMessage(error)}`);
|
|
1661
|
-
}
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
function tabFormat(tab) {
|
|
1665
|
-
return formatByName(tab.doc.sourceFormat) ?? (tab.doc.mode === "content" ? defaultContentFormat() : undefined);
|
|
1666
|
-
}
|
|
1667
|
-
async function exportFile() {
|
|
1668
|
-
const tab = activeTab.value;
|
|
1669
|
-
if (!tab) {
|
|
1670
|
-
return;
|
|
1671
|
-
}
|
|
1672
|
-
try {
|
|
1673
|
-
await loadFormats();
|
|
1674
|
-
const format = tabFormat(tab);
|
|
1675
|
-
const output = await serializeDocument(tab);
|
|
1676
|
-
const mimeType = format ? format.mediaType ?? "text/plain" : "application/json";
|
|
1677
|
-
const ext = format ? format.extensions[0] : ".json";
|
|
1678
|
-
const description = format ? format.name : "Jx Component";
|
|
1679
|
-
const fallbackName = format ? `content${ext}` : "component.json";
|
|
1680
|
-
if ("showSaveFilePicker" in window) {
|
|
1681
|
-
const suggestedName = tab.documentPath ? tab.documentPath.split("/").pop() : fallbackName;
|
|
1682
|
-
const handle = await window.showSaveFilePicker({
|
|
1683
|
-
suggestedName,
|
|
1684
|
-
types: [{ accept: { [mimeType]: [ext] }, description }]
|
|
1685
|
-
});
|
|
1686
|
-
const writable = await handle.createWritable();
|
|
1687
|
-
await writable.write(output);
|
|
1688
|
-
await writable.close();
|
|
1689
|
-
tab.doc.dirty = false;
|
|
1690
|
-
savedMessage(tab, `Exported as ${handle.name}`);
|
|
1691
|
-
} else {
|
|
1692
|
-
const blob = new Blob([output], { type: mimeType });
|
|
1693
|
-
const url = URL.createObjectURL(blob);
|
|
1694
|
-
const a = document.createElement("a");
|
|
1695
|
-
a.href = url;
|
|
1696
|
-
a.download = fallbackName;
|
|
1697
|
-
a.click();
|
|
1698
|
-
URL.revokeObjectURL(url);
|
|
1699
|
-
tab.doc.dirty = false;
|
|
1700
|
-
savedMessage(tab, "Downloaded");
|
|
1701
|
-
}
|
|
1702
|
-
} catch (error) {
|
|
1703
|
-
if (!(error instanceof Error && error.name === "AbortError")) {
|
|
1704
|
-
statusMessage(`Export error: ${errorMessage(error)}`);
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
async function serializeDocument(tab) {
|
|
1709
|
-
const grid = getGridController(tab);
|
|
1710
|
-
const gridText = grid?.serializeForSource();
|
|
1711
|
-
if (gridText) {
|
|
1712
|
-
return gridText;
|
|
1713
|
-
}
|
|
1714
|
-
await loadFormats();
|
|
1715
|
-
const sourceFormat = formatByName(tab.doc.sourceFormat);
|
|
1716
|
-
if (sourceFormat?.capabilities.serialize) {
|
|
1717
|
-
const fm = tab.doc.content?.frontmatter || {};
|
|
1718
|
-
const doc = tab.doc.document;
|
|
1719
|
-
const fullDoc = { ...fm, ...doc, children: doc.children ?? [] };
|
|
1720
|
-
return formatSerialize(sourceFormat.name, fullDoc, { mode: "roundtrip" });
|
|
1721
|
-
}
|
|
1722
|
-
if (tab.doc.mode === "content") {
|
|
1723
|
-
const format = defaultContentFormat();
|
|
1724
|
-
if (format) {
|
|
1725
|
-
const fm = tab.doc.content?.frontmatter ?? {};
|
|
1726
|
-
const hasFrontmatter = Object.keys(fm).length > 0;
|
|
1727
|
-
const fullDoc = { ...fm, ...tab.doc.document };
|
|
1728
|
-
return formatSerialize(format.name, fullDoc, {
|
|
1729
|
-
frontmatter: hasFrontmatter,
|
|
1730
|
-
mode: "roundtrip"
|
|
1731
|
-
});
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
return JSON.stringify(tab.doc.document, null, 2);
|
|
1735
|
-
}
|
|
1736
|
-
|
|
1737
|
-
// src/grid/sources/content-source.ts
|
|
1738
|
-
var PATH_FIELD = "__path";
|
|
1739
|
-
function collectionInfo(typeName) {
|
|
1740
|
-
const content = projectState?.projectConfig?.content ?? {};
|
|
1741
|
-
const def = content[typeName];
|
|
1742
|
-
if (!def?.source) {
|
|
1743
|
-
return null;
|
|
1744
|
-
}
|
|
1745
|
-
const dir = def.source.replace(/^\.\//, "").replace(/\/$/, "");
|
|
1746
|
-
const ext = def.format === "json" ? ".json" : formatByName(def.format)?.extensions[0] ?? defaultContentFormat()?.extensions[0] ?? ".json";
|
|
1747
|
-
return { def, dir, ext, name: typeName };
|
|
1748
|
-
}
|
|
1749
|
-
function collectionDirs() {
|
|
1750
|
-
const content = projectState?.projectConfig?.content ?? {};
|
|
1751
|
-
return Object.entries(content).filter(([, def]) => {
|
|
1752
|
-
const source = def.source?.replace(/^\.\//, "") ?? "";
|
|
1753
|
-
return source !== "" && !/\.[a-z0-9]+$/i.test(source);
|
|
1754
|
-
}).map(([name, def]) => ({ dir: def.source.replace(/^\.\//, "").replace(/\/$/, ""), name }));
|
|
1755
|
-
}
|
|
1756
|
-
async function listEntryFiles(dir, exts) {
|
|
1757
|
-
const platform = getPlatform();
|
|
1758
|
-
const results = [];
|
|
1759
|
-
const walk = async (current) => {
|
|
1760
|
-
let children;
|
|
1761
|
-
try {
|
|
1762
|
-
children = await platform.listDirectory(current);
|
|
1763
|
-
} catch {
|
|
1764
|
-
return;
|
|
1765
|
-
}
|
|
1766
|
-
for (const entry of children) {
|
|
1767
|
-
if (entry.type === "directory") {
|
|
1768
|
-
await walk(entry.path);
|
|
1769
|
-
} else if (exts.some((ext) => entry.path.endsWith(ext))) {
|
|
1770
|
-
results.push(entry.path);
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
};
|
|
1774
|
-
await walk(dir);
|
|
1775
|
-
return results.toSorted();
|
|
1776
|
-
}
|
|
1777
|
-
async function listCollectionEntryIds(typeName) {
|
|
1778
|
-
await loadFormats().catch(() => {});
|
|
1779
|
-
const info = collectionInfo(typeName);
|
|
1780
|
-
if (!info) {
|
|
1781
|
-
return [];
|
|
1782
|
-
}
|
|
1783
|
-
const files = await listEntryFiles(info.dir, [info.ext]);
|
|
1784
|
-
return files.map((path) => {
|
|
1785
|
-
const base = path.split("/").pop() ?? path;
|
|
1786
|
-
return base.slice(0, base.length - info.ext.length);
|
|
1787
|
-
});
|
|
1788
|
-
}
|
|
1789
|
-
async function mapLimit(items, limit, fn) {
|
|
1790
|
-
const results = Array.from({ length: items.length });
|
|
1791
|
-
let next = 0;
|
|
1792
|
-
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
1793
|
-
while (next < items.length) {
|
|
1794
|
-
const index = next;
|
|
1795
|
-
next += 1;
|
|
1796
|
-
results[index] = await fn(items[index]);
|
|
1797
|
-
}
|
|
1798
|
-
});
|
|
1799
|
-
await Promise.all(workers);
|
|
1800
|
-
return results;
|
|
1801
|
-
}
|
|
1802
|
-
function toCellValue(value) {
|
|
1803
|
-
if (value === null || value === undefined) {
|
|
1804
|
-
return null;
|
|
1805
|
-
}
|
|
1806
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
1807
|
-
return value;
|
|
1808
|
-
}
|
|
1809
|
-
if (Array.isArray(value) && value.every((v) => typeof v !== "object" || v === null)) {
|
|
1810
|
-
return value.map(String);
|
|
1811
|
-
}
|
|
1812
|
-
return JSON.stringify(value);
|
|
1813
|
-
}
|
|
1814
|
-
function openTabFor(path) {
|
|
1815
|
-
for (const tab of workspace.tabs.values()) {
|
|
1816
|
-
if (tab.documentPath === path) {
|
|
1817
|
-
return tab;
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
|
-
return null;
|
|
1821
|
-
}
|
|
1822
|
-
function extraRowsAreStrings(rows, field) {
|
|
1823
|
-
return rows.every((row) => {
|
|
1824
|
-
const value = row[field];
|
|
1825
|
-
return value === null || value === undefined || typeof value !== "string" ? true : !value.startsWith("{") && !value.startsWith("[");
|
|
1826
|
-
});
|
|
1827
|
-
}
|
|
1828
|
-
function createEntryFileSource(opts) {
|
|
1829
|
-
const entries = new Map;
|
|
1830
|
-
let loadPromise = null;
|
|
1831
|
-
let columnsCache = null;
|
|
1832
|
-
const load = (force = false) => {
|
|
1833
|
-
if (!force && loadPromise) {
|
|
1834
|
-
return loadPromise;
|
|
1835
|
-
}
|
|
1836
|
-
loadPromise = (async () => {
|
|
1837
|
-
await loadFormats().catch(() => {});
|
|
1838
|
-
const platform = getPlatform();
|
|
1839
|
-
const files = await opts.list();
|
|
1840
|
-
entries.clear();
|
|
1841
|
-
columnsCache = null;
|
|
1842
|
-
await mapLimit(files, 8, async (path) => {
|
|
1843
|
-
try {
|
|
1844
|
-
const text = await platform.readFile(path);
|
|
1845
|
-
const parsed = await parseSourceForPath(path, text);
|
|
1846
|
-
entries.set(path, {
|
|
1847
|
-
document: parsed.document,
|
|
1848
|
-
formatName: parsed.format.name,
|
|
1849
|
-
frontmatter: parsed.frontmatter ?? {},
|
|
1850
|
-
rawText: text
|
|
1851
|
-
});
|
|
1852
|
-
} catch {}
|
|
1853
|
-
});
|
|
1854
|
-
})();
|
|
1855
|
-
return loadPromise;
|
|
1856
|
-
};
|
|
1857
|
-
const buildColumns = () => {
|
|
1858
|
-
if (columnsCache) {
|
|
1859
|
-
return columnsCache;
|
|
1860
|
-
}
|
|
1861
|
-
const pathColumn = {
|
|
1862
|
-
editable: false,
|
|
1863
|
-
field: PATH_FIELD,
|
|
1864
|
-
insertOnly: true,
|
|
1865
|
-
kind: "string",
|
|
1866
|
-
pk: true,
|
|
1867
|
-
required: true,
|
|
1868
|
-
title: "Path",
|
|
1869
|
-
widthHint: 220
|
|
1870
|
-
};
|
|
1871
|
-
const schemaColumns = columnsFromSchema(opts.schema());
|
|
1872
|
-
const known = new Set([PATH_FIELD, ...schemaColumns.map((c) => c.field)]);
|
|
1873
|
-
const extraRows = [...entries.values()].map((entry) => {
|
|
1874
|
-
const cells = {};
|
|
1875
|
-
for (const [key, value] of Object.entries(entry.frontmatter)) {
|
|
1876
|
-
if (!known.has(key) && !key.startsWith("$")) {
|
|
1877
|
-
cells[key] = toCellValue(value);
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
return cells;
|
|
1881
|
-
});
|
|
1882
|
-
const extraColumns = inferColumnsFromRows(extraRows);
|
|
1883
|
-
for (const column of extraColumns) {
|
|
1884
|
-
column.editable = column.kind !== "text" || extraRowsAreStrings(extraRows, column.field);
|
|
1885
|
-
}
|
|
1886
|
-
if (opts.priorityFields?.length) {
|
|
1887
|
-
const rank = (c) => {
|
|
1888
|
-
const index = opts.priorityFields.indexOf(c.field);
|
|
1889
|
-
return index === -1 ? opts.priorityFields.length : index;
|
|
1890
|
-
};
|
|
1891
|
-
extraColumns.sort((a, b) => rank(a) - rank(b));
|
|
1892
|
-
}
|
|
1893
|
-
columnsCache = [pathColumn, ...schemaColumns, ...extraColumns];
|
|
1894
|
-
return columnsCache;
|
|
1895
|
-
};
|
|
1896
|
-
const rowFor = (path, entry) => {
|
|
1897
|
-
const cells = { [PATH_FIELD]: path };
|
|
1898
|
-
for (const column of buildColumns()) {
|
|
1899
|
-
if (column.field === PATH_FIELD) {
|
|
1900
|
-
continue;
|
|
1901
|
-
}
|
|
1902
|
-
cells[column.field] = toCellValue(entry.frontmatter[column.field]);
|
|
1903
|
-
}
|
|
1904
|
-
return { cells, fingerprint: entry.rawText, key: path };
|
|
1905
|
-
};
|
|
1906
|
-
const writeEntry = async (path, entry, patches) => {
|
|
1907
|
-
const platform = getPlatform();
|
|
1908
|
-
const frontmatter = { ...entry.frontmatter };
|
|
1909
|
-
for (const patch of patches) {
|
|
1910
|
-
if (patch.value === null) {
|
|
1911
|
-
delete frontmatter[patch.field];
|
|
1912
|
-
} else {
|
|
1913
|
-
frontmatter[patch.field] = patch.value;
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
const doc = entry.document;
|
|
1917
|
-
const fullDoc = { ...frontmatter, ...doc, children: doc.children ?? [] };
|
|
1918
|
-
const text = await formatSerialize(entry.formatName, fullDoc, { mode: "roundtrip" });
|
|
1919
|
-
markLocalMutation(path);
|
|
1920
|
-
await platform.writeFile(path, text);
|
|
1921
|
-
entries.set(path, { ...entry, frontmatter, rawText: text });
|
|
1922
|
-
};
|
|
1923
|
-
return {
|
|
1924
|
-
backingPaths: () => new Map([...entries.keys()].map((path) => [path, path])),
|
|
1925
|
-
capabilities: { delete: true, insert: true, remotePaging: false, remoteSort: false },
|
|
1926
|
-
async columns() {
|
|
1927
|
-
await load();
|
|
1928
|
-
return buildColumns();
|
|
1929
|
-
},
|
|
1930
|
-
async commit(batch) {
|
|
1931
|
-
await load();
|
|
1932
|
-
const platform = getPlatform();
|
|
1933
|
-
const result = { cells: [], deletes: [], inserts: [] };
|
|
1934
|
-
const byRow = new Map;
|
|
1935
|
-
for (const cell of batch.cells) {
|
|
1936
|
-
const list = byRow.get(cell.rowKey) ?? [];
|
|
1937
|
-
list.push({ field: cell.field, value: cell.value });
|
|
1938
|
-
byRow.set(cell.rowKey, list);
|
|
1939
|
-
}
|
|
1940
|
-
for (const [path, patches] of byRow) {
|
|
1941
|
-
const outcome = (ok, error, stale) => {
|
|
1942
|
-
for (const patch of patches) {
|
|
1943
|
-
result.cells.push({ error, field: patch.field, ok, rowKey: path, stale });
|
|
1944
|
-
}
|
|
1945
|
-
};
|
|
1946
|
-
const entry = entries.get(path);
|
|
1947
|
-
if (!entry) {
|
|
1948
|
-
outcome(false, "Entry no longer loaded — refresh the grid");
|
|
1949
|
-
continue;
|
|
1950
|
-
}
|
|
1951
|
-
const openTab2 = openTabFor(path);
|
|
1952
|
-
if (openTab2?.doc.dirty) {
|
|
1953
|
-
outcome(false, "Open in a tab with unsaved changes — save or close it first");
|
|
1954
|
-
continue;
|
|
1955
|
-
}
|
|
1956
|
-
let onDisk = null;
|
|
1957
|
-
try {
|
|
1958
|
-
onDisk = await platform.readFile(path);
|
|
1959
|
-
} catch {
|
|
1960
|
-
onDisk = null;
|
|
1961
|
-
}
|
|
1962
|
-
if (onDisk !== entry.rawText && !isRecentLocal(path)) {
|
|
1963
|
-
outcome(false, "File changed on disk — refresh to reload", true);
|
|
1964
|
-
continue;
|
|
1965
|
-
}
|
|
1966
|
-
try {
|
|
1967
|
-
await writeEntry(path, entry, patches);
|
|
1968
|
-
outcome(true);
|
|
1969
|
-
if (openTab2) {
|
|
1970
|
-
const { reloadFileInTab } = await import("./files-02zn5y24.js");
|
|
1971
|
-
reloadFileInTab(path);
|
|
1972
|
-
}
|
|
1973
|
-
} catch (error) {
|
|
1974
|
-
outcome(false, error instanceof Error ? error.message : String(error));
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
for (const insert of batch.inserts) {
|
|
1978
|
-
const rawPath = insert.cells[PATH_FIELD];
|
|
1979
|
-
if (typeof rawPath !== "string" || rawPath.trim() === "") {
|
|
1980
|
-
result.inserts.push({ error: "Path is required", ok: false, tempKey: insert.tempKey });
|
|
1981
|
-
continue;
|
|
1982
|
-
}
|
|
1983
|
-
const path = opts.resolveInsertPath(rawPath);
|
|
1984
|
-
let exists = entries.has(path);
|
|
1985
|
-
if (!exists) {
|
|
1986
|
-
try {
|
|
1987
|
-
await platform.readFile(path);
|
|
1988
|
-
exists = true;
|
|
1989
|
-
} catch {
|
|
1990
|
-
exists = false;
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
if (exists) {
|
|
1994
|
-
result.inserts.push({
|
|
1995
|
-
error: `${path} already exists`,
|
|
1996
|
-
ok: false,
|
|
1997
|
-
tempKey: insert.tempKey
|
|
1998
|
-
});
|
|
1999
|
-
continue;
|
|
2000
|
-
}
|
|
2001
|
-
const formatName = opts.insertFormatName(path);
|
|
2002
|
-
if (!formatName) {
|
|
2003
|
-
result.inserts.push({
|
|
2004
|
-
error: `No format can serialize ${path}`,
|
|
2005
|
-
ok: false,
|
|
2006
|
-
tempKey: insert.tempKey
|
|
2007
|
-
});
|
|
2008
|
-
continue;
|
|
2009
|
-
}
|
|
2010
|
-
try {
|
|
2011
|
-
const frontmatter = {};
|
|
2012
|
-
for (const [field, value] of Object.entries(insert.cells)) {
|
|
2013
|
-
if (field !== PATH_FIELD && value !== null) {
|
|
2014
|
-
frontmatter[field] = value;
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
const text = await formatSerialize(formatName, { ...frontmatter, children: [] }, { frontmatter: Object.keys(frontmatter).length > 0, mode: "roundtrip" });
|
|
2018
|
-
markLocalMutation(path);
|
|
2019
|
-
await platform.writeFile(path, text);
|
|
2020
|
-
const parsed = await parseSourceForPath(path, text);
|
|
2021
|
-
entries.set(path, {
|
|
2022
|
-
document: parsed.document,
|
|
2023
|
-
formatName: parsed.format.name,
|
|
2024
|
-
frontmatter: parsed.frontmatter ?? {},
|
|
2025
|
-
rawText: text
|
|
2026
|
-
});
|
|
2027
|
-
result.inserts.push({ newKey: path, ok: true, tempKey: insert.tempKey });
|
|
2028
|
-
} catch (error) {
|
|
2029
|
-
result.inserts.push({
|
|
2030
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2031
|
-
ok: false,
|
|
2032
|
-
tempKey: insert.tempKey
|
|
2033
|
-
});
|
|
2034
|
-
}
|
|
2035
|
-
}
|
|
2036
|
-
for (const del of batch.deletes) {
|
|
2037
|
-
if (openTabFor(del.rowKey)) {
|
|
2038
|
-
result.deletes.push({
|
|
2039
|
-
error: "Open in a tab — close it first",
|
|
2040
|
-
ok: false,
|
|
2041
|
-
rowKey: del.rowKey
|
|
2042
|
-
});
|
|
2043
|
-
continue;
|
|
2044
|
-
}
|
|
2045
|
-
try {
|
|
2046
|
-
markLocalMutation(del.rowKey);
|
|
2047
|
-
await platform.deleteFile(del.rowKey);
|
|
2048
|
-
entries.delete(del.rowKey);
|
|
2049
|
-
result.deletes.push({ ok: true, rowKey: del.rowKey });
|
|
2050
|
-
} catch (error) {
|
|
2051
|
-
result.deletes.push({
|
|
2052
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2053
|
-
ok: false,
|
|
2054
|
-
rowKey: del.rowKey
|
|
2055
|
-
});
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
|
-
return result;
|
|
2059
|
-
},
|
|
2060
|
-
id: opts.id,
|
|
2061
|
-
label: opts.label,
|
|
2062
|
-
async refresh() {
|
|
2063
|
-
await load(true);
|
|
2064
|
-
},
|
|
2065
|
-
async rows() {
|
|
2066
|
-
await load();
|
|
2067
|
-
const rows = [...entries.entries()].map(([path, entry]) => rowFor(path, entry));
|
|
2068
|
-
return { rows, total: rows.length };
|
|
2069
|
-
}
|
|
2070
|
-
};
|
|
2071
|
-
}
|
|
2072
|
-
function createCollectionSource(typeName) {
|
|
2073
|
-
const requireInfo = () => {
|
|
2074
|
-
const info = collectionInfo(typeName);
|
|
2075
|
-
if (!info) {
|
|
2076
|
-
throw new Error(`No content collection named "${typeName}" in project.json`);
|
|
2077
|
-
}
|
|
2078
|
-
return info;
|
|
2079
|
-
};
|
|
2080
|
-
return createEntryFileSource({
|
|
2081
|
-
id: makeGridTabId({ kind: "collection", name: typeName }),
|
|
2082
|
-
insertFormatName: (path) => {
|
|
2083
|
-
const info = requireInfo();
|
|
2084
|
-
return (info.def.format ? formatByName(info.def.format)?.name : undefined) ?? formatForPath(path)?.name ?? defaultContentFormat()?.name ?? null;
|
|
2085
|
-
},
|
|
2086
|
-
label: typeName,
|
|
2087
|
-
list: () => {
|
|
2088
|
-
const info = requireInfo();
|
|
2089
|
-
return listEntryFiles(info.dir, [info.ext]);
|
|
2090
|
-
},
|
|
2091
|
-
resolveInsertPath: (raw) => {
|
|
2092
|
-
const info = requireInfo();
|
|
2093
|
-
let path = raw.trim().replace(/^\.\//, "").replace(/^\//, "");
|
|
2094
|
-
if (!path.includes("/")) {
|
|
2095
|
-
path = `${info.dir}/${path}`;
|
|
2096
|
-
}
|
|
2097
|
-
if (!path.endsWith(info.ext)) {
|
|
2098
|
-
path = `${path}${info.ext}`;
|
|
2099
|
-
}
|
|
2100
|
-
return path;
|
|
2101
|
-
},
|
|
2102
|
-
schema: () => requireInfo().def.schema
|
|
2103
|
-
});
|
|
2104
|
-
}
|
|
2105
|
-
function createPagesSource() {
|
|
2106
|
-
const pageExts = () => {
|
|
2107
|
-
const exts = documentExtensions("page").filter((ext) => ext !== ".json");
|
|
2108
|
-
return exts.length > 0 ? exts : [".md"];
|
|
2109
|
-
};
|
|
2110
|
-
return createEntryFileSource({
|
|
2111
|
-
id: makeGridTabId({ kind: "pages" }),
|
|
2112
|
-
insertFormatName: (path) => formatForPath(path)?.name ?? null,
|
|
2113
|
-
label: "Pages",
|
|
2114
|
-
list: () => listEntryFiles("pages", pageExts()),
|
|
2115
|
-
priorityFields: ["title", "description"],
|
|
2116
|
-
resolveInsertPath: (raw) => {
|
|
2117
|
-
let path = raw.trim().replace(/^\.\//, "").replace(/^\//, "");
|
|
2118
|
-
if (!path.startsWith("pages/")) {
|
|
2119
|
-
path = `pages/${path}`;
|
|
2120
|
-
}
|
|
2121
|
-
if (!pageExts().some((ext) => path.endsWith(ext))) {
|
|
2122
|
-
path = `${path}${pageExts()[0]}`;
|
|
2123
|
-
}
|
|
2124
|
-
return path;
|
|
2125
|
-
},
|
|
2126
|
-
schema: () => null
|
|
2127
|
-
});
|
|
2128
|
-
}
|
|
2129
|
-
|
|
2130
|
-
// src/grid/sources/connector-source.ts
|
|
2131
|
-
var DATA_PAGE_SIZE = 50;
|
|
2132
|
-
function kindForSqlType(type) {
|
|
2133
|
-
const t = type.toLowerCase();
|
|
2134
|
-
if (/(int|real|floa|doub|num|dec)/.test(t)) {
|
|
2135
|
-
return "number";
|
|
2136
|
-
}
|
|
2137
|
-
if (t.includes("bool")) {
|
|
2138
|
-
return "boolean";
|
|
2139
|
-
}
|
|
2140
|
-
if (/(timestamp|datetime|date)/.test(t)) {
|
|
2141
|
-
return "date";
|
|
2142
|
-
}
|
|
2143
|
-
return "string";
|
|
2144
|
-
}
|
|
2145
|
-
function editableColumn(column) {
|
|
2146
|
-
return !column.pk && column.name !== "created_at" && column.name !== "updated_at";
|
|
2147
|
-
}
|
|
2148
|
-
function toCellValue2(value) {
|
|
2149
|
-
if (value === null || value === undefined) {
|
|
2150
|
-
return null;
|
|
2151
|
-
}
|
|
2152
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
2153
|
-
return value;
|
|
2154
|
-
}
|
|
2155
|
-
return JSON.stringify(value);
|
|
2156
|
-
}
|
|
2157
|
-
function createConnectorSource(connection, table) {
|
|
2158
|
-
let columnsMeta = [];
|
|
2159
|
-
let pkName = "id";
|
|
2160
|
-
const pkValues = new Map;
|
|
2161
|
-
const applyMeta = (columns) => {
|
|
2162
|
-
if (columns.length > 0) {
|
|
2163
|
-
columnsMeta = columns;
|
|
2164
|
-
pkName = columns.find((c) => c.pk)?.name ?? "id";
|
|
2165
|
-
}
|
|
2166
|
-
};
|
|
2167
|
-
const toQuery = (query) => ({
|
|
2168
|
-
table,
|
|
2169
|
-
...connection ? { connection } : {},
|
|
2170
|
-
limit: query?.limit ?? DATA_PAGE_SIZE,
|
|
2171
|
-
offset: query?.offset ?? 0,
|
|
2172
|
-
...query?.orderBy ? { dir: query.dir ?? "asc", orderBy: query.orderBy } : {}
|
|
2173
|
-
});
|
|
2174
|
-
const rowKeyOf = (row, index) => {
|
|
2175
|
-
const pk = row[pkName];
|
|
2176
|
-
if (typeof pk === "string" || typeof pk === "number") {
|
|
2177
|
-
pkValues.set(String(pk), pk);
|
|
2178
|
-
return String(pk);
|
|
2179
|
-
}
|
|
2180
|
-
return `row-${index}`;
|
|
2181
|
-
};
|
|
2182
|
-
return {
|
|
2183
|
-
capabilities: { delete: true, insert: true, remotePaging: true, remoteSort: true },
|
|
2184
|
-
async columns() {
|
|
2185
|
-
if (columnsMeta.length === 0) {
|
|
2186
|
-
const result = await fetchRows(toQuery({ limit: 1, offset: 0 }));
|
|
2187
|
-
applyMeta(result.columns);
|
|
2188
|
-
}
|
|
2189
|
-
return columnsMeta.map((meta) => {
|
|
2190
|
-
const kind = editableColumn(meta) ? kindForSqlType(meta.type) : "readonly";
|
|
2191
|
-
return {
|
|
2192
|
-
editable: kind !== "readonly",
|
|
2193
|
-
field: meta.name,
|
|
2194
|
-
kind,
|
|
2195
|
-
pk: meta.pk === true,
|
|
2196
|
-
title: meta.name,
|
|
2197
|
-
widthHint: meta.pk ? 80 : undefined
|
|
2198
|
-
};
|
|
2199
|
-
});
|
|
2200
|
-
},
|
|
2201
|
-
async commit(batch) {
|
|
2202
|
-
const result = { cells: [], deletes: [], inserts: [] };
|
|
2203
|
-
const byRow = new Map;
|
|
2204
|
-
for (const cell of batch.cells) {
|
|
2205
|
-
const list = byRow.get(cell.rowKey) ?? [];
|
|
2206
|
-
list.push({ field: cell.field, value: cell.value });
|
|
2207
|
-
byRow.set(cell.rowKey, list);
|
|
2208
|
-
}
|
|
2209
|
-
for (const [rowKey, patches] of byRow) {
|
|
2210
|
-
const pk = pkValues.get(rowKey);
|
|
2211
|
-
const outcome = (ok, error) => {
|
|
2212
|
-
for (const patch of patches) {
|
|
2213
|
-
result.cells.push({ error, field: patch.field, ok, rowKey });
|
|
2214
|
-
}
|
|
2215
|
-
};
|
|
2216
|
-
if (pk === undefined) {
|
|
2217
|
-
outcome(false, "Row has no primary key — refresh the grid");
|
|
2218
|
-
continue;
|
|
2219
|
-
}
|
|
2220
|
-
try {
|
|
2221
|
-
const set = {};
|
|
2222
|
-
for (const patch of patches) {
|
|
2223
|
-
set[patch.field] = patch.value;
|
|
2224
|
-
}
|
|
2225
|
-
await updateRow({ pk, set, table, ...connection ? { connection } : {} });
|
|
2226
|
-
outcome(true);
|
|
2227
|
-
} catch (error) {
|
|
2228
|
-
outcome(false, error instanceof Error ? error.message : String(error));
|
|
2229
|
-
}
|
|
2230
|
-
}
|
|
2231
|
-
for (const insert of batch.inserts) {
|
|
2232
|
-
try {
|
|
2233
|
-
const values = {};
|
|
2234
|
-
for (const [field, value] of Object.entries(insert.cells)) {
|
|
2235
|
-
if (value !== null) {
|
|
2236
|
-
values[field] = value;
|
|
2237
|
-
}
|
|
2238
|
-
}
|
|
2239
|
-
const { row } = await insertRow({ table, values, ...connection ? { connection } : {} });
|
|
2240
|
-
const pk = row[pkName];
|
|
2241
|
-
const newKey = typeof pk === "string" || typeof pk === "number" ? String(pk) : undefined;
|
|
2242
|
-
if (newKey !== undefined && (typeof pk === "string" || typeof pk === "number")) {
|
|
2243
|
-
pkValues.set(newKey, pk);
|
|
2244
|
-
}
|
|
2245
|
-
result.inserts.push({ newKey, ok: true, tempKey: insert.tempKey });
|
|
2246
|
-
} catch (error) {
|
|
2247
|
-
result.inserts.push({
|
|
2248
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2249
|
-
ok: false,
|
|
2250
|
-
tempKey: insert.tempKey
|
|
2251
|
-
});
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
for (const del of batch.deletes) {
|
|
2255
|
-
const pk = pkValues.get(del.rowKey);
|
|
2256
|
-
if (pk === undefined) {
|
|
2257
|
-
result.deletes.push({
|
|
2258
|
-
error: "Row has no primary key — refresh the grid",
|
|
2259
|
-
ok: false,
|
|
2260
|
-
rowKey: del.rowKey
|
|
2261
|
-
});
|
|
2262
|
-
continue;
|
|
2263
|
-
}
|
|
2264
|
-
try {
|
|
2265
|
-
await deleteRow({ pk, table, ...connection ? { connection } : {} });
|
|
2266
|
-
result.deletes.push({ ok: true, rowKey: del.rowKey });
|
|
2267
|
-
} catch (error) {
|
|
2268
|
-
result.deletes.push({
|
|
2269
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2270
|
-
ok: false,
|
|
2271
|
-
rowKey: del.rowKey
|
|
2272
|
-
});
|
|
2273
|
-
}
|
|
2274
|
-
}
|
|
2275
|
-
return result;
|
|
2276
|
-
},
|
|
2277
|
-
id: makeGridTabId({ connection: connection ?? "default", kind: "data", table }),
|
|
2278
|
-
label: table,
|
|
2279
|
-
async refresh() {
|
|
2280
|
-
columnsMeta = [];
|
|
2281
|
-
pkValues.clear();
|
|
2282
|
-
},
|
|
2283
|
-
async rows(query) {
|
|
2284
|
-
const result = await fetchRows(toQuery(query));
|
|
2285
|
-
applyMeta(result.columns);
|
|
2286
|
-
const rows = result.rows.map((row, index) => {
|
|
2287
|
-
const key = rowKeyOf(row, index);
|
|
2288
|
-
const cells = {};
|
|
2289
|
-
for (const meta of columnsMeta) {
|
|
2290
|
-
cells[meta.name] = toCellValue2(row[meta.name]);
|
|
2291
|
-
}
|
|
2292
|
-
return { cells, key };
|
|
2293
|
-
});
|
|
2294
|
-
return { rows, total: result.total };
|
|
2295
|
-
}
|
|
2296
|
-
};
|
|
2297
|
-
}
|
|
2298
|
-
|
|
2299
|
-
// src/grid/grid-open.ts
|
|
2300
|
-
var GRID_STUB_DOCUMENT = { children: [], tagName: "div" };
|
|
2301
|
-
async function openCsvGridTab(path) {
|
|
2302
|
-
const existing = workspace.tabs.get(path);
|
|
2303
|
-
if (existing) {
|
|
2304
|
-
activateTab(path);
|
|
2305
|
-
return existing;
|
|
2306
|
-
}
|
|
2307
|
-
try {
|
|
2308
|
-
await loadFormats();
|
|
2309
|
-
} catch {}
|
|
2310
|
-
const source = createCsvFileSource(path);
|
|
2311
|
-
const tab = openTab({
|
|
2312
|
-
capabilities: { modes: ["grid", "source"] },
|
|
2313
|
-
document: structuredClone(GRID_STUB_DOCUMENT),
|
|
2314
|
-
documentPath: path,
|
|
2315
|
-
id: path,
|
|
2316
|
-
sourceFormat: formatForPath(path)?.name ?? null
|
|
2317
|
-
});
|
|
2318
|
-
const controller = createGridController(tab, source);
|
|
2319
|
-
controller.load();
|
|
2320
|
-
return tab;
|
|
2321
|
-
}
|
|
2322
|
-
function openVirtualGridTab(source) {
|
|
2323
|
-
const tab = openTab({
|
|
2324
|
-
capabilities: { modes: ["grid"] },
|
|
2325
|
-
document: structuredClone(GRID_STUB_DOCUMENT),
|
|
2326
|
-
documentPath: null,
|
|
2327
|
-
id: source.id
|
|
2328
|
-
});
|
|
2329
|
-
const controller = createGridController(tab, source);
|
|
2330
|
-
controller.load();
|
|
2331
|
-
return tab;
|
|
2332
|
-
}
|
|
2333
|
-
function openCollectionGrid(typeName) {
|
|
2334
|
-
const id = makeGridTabId({ kind: "collection", name: typeName });
|
|
2335
|
-
const existing = workspace.tabs.get(id);
|
|
2336
|
-
if (existing) {
|
|
2337
|
-
activateTab(id);
|
|
2338
|
-
return existing;
|
|
2339
|
-
}
|
|
2340
|
-
return openVirtualGridTab(createCollectionSource(typeName));
|
|
2341
|
-
}
|
|
2342
|
-
function openPagesGrid() {
|
|
2343
|
-
const id = makeGridTabId({ kind: "pages" });
|
|
2344
|
-
const existing = workspace.tabs.get(id);
|
|
2345
|
-
if (existing) {
|
|
2346
|
-
activateTab(id);
|
|
2347
|
-
return existing;
|
|
2348
|
-
}
|
|
2349
|
-
return openVirtualGridTab(createPagesSource());
|
|
2350
|
-
}
|
|
2351
|
-
function openConnectorGrid(connection, table) {
|
|
2352
|
-
const id = makeGridTabId({ connection: connection ?? "default", kind: "data", table });
|
|
2353
|
-
const existing = workspace.tabs.get(id);
|
|
2354
|
-
if (existing) {
|
|
2355
|
-
activateTab(id);
|
|
2356
|
-
return existing;
|
|
2357
|
-
}
|
|
2358
|
-
return openVirtualGridTab(createConnectorSource(connection, table));
|
|
2359
|
-
}
|
|
2360
|
-
async function openGridSourcePicker() {
|
|
2361
|
-
const collections = collectionDirs();
|
|
2362
|
-
let connections = [];
|
|
2363
|
-
if (dataSurfaceAvailable()) {
|
|
2364
|
-
const response = await fetchConnections().catch(() => null);
|
|
2365
|
-
connections = response?.connections ?? [];
|
|
2366
|
-
}
|
|
2367
|
-
const handle = openModal(html`<sp-dialog-wrapper
|
|
2368
|
-
open
|
|
2369
|
-
dismissable
|
|
2370
|
-
underlay
|
|
2371
|
-
headline="Open Grid"
|
|
2372
|
-
@close=${() => handle.close()}
|
|
2373
|
-
>
|
|
2374
|
-
<sp-menu class="jx-grid-picker">
|
|
2375
|
-
<sp-menu-group>
|
|
2376
|
-
<span slot="header">Project</span>
|
|
2377
|
-
<sp-menu-item
|
|
2378
|
-
@click=${() => {
|
|
2379
|
-
handle.close();
|
|
2380
|
-
openPagesGrid();
|
|
2381
|
-
}}
|
|
2382
|
-
>Pages</sp-menu-item
|
|
2383
|
-
>
|
|
2384
|
-
${collections.map(({ name }) => html`<sp-menu-item
|
|
2385
|
-
@click=${() => {
|
|
2386
|
-
handle.close();
|
|
2387
|
-
openCollectionGrid(name);
|
|
2388
|
-
}}
|
|
2389
|
-
>Collection: ${name}</sp-menu-item
|
|
2390
|
-
>`)}
|
|
2391
|
-
</sp-menu-group>
|
|
2392
|
-
${connections.map((conn) => html`<sp-menu-group>
|
|
2393
|
-
<span slot="header">Data · ${conn.name}</span>
|
|
2394
|
-
${conn.tables.length === 0 ? html`<sp-menu-item disabled>No tables — push a schema first</sp-menu-item>` : conn.tables.map((table) => html`<sp-menu-item
|
|
2395
|
-
@click=${() => {
|
|
2396
|
-
handle.close();
|
|
2397
|
-
openConnectorGrid(conn.name, table);
|
|
2398
|
-
}}
|
|
2399
|
-
>${table}</sp-menu-item
|
|
2400
|
-
>`)}
|
|
2401
|
-
</sp-menu-group>`)}
|
|
2402
|
-
</sp-menu>
|
|
2403
|
-
</sp-dialog-wrapper>`);
|
|
2404
|
-
}
|
|
2405
|
-
|
|
2406
|
-
// src/recent-projects.ts
|
|
2407
|
-
var STORAGE_KEY = "jx-studio-recent-projects";
|
|
2408
|
-
var FILES_STORAGE_KEY = "jx-studio-recent-files";
|
|
2409
|
-
var MAX_RECENT = 8;
|
|
2410
|
-
var MAX_RECENT_FILES = 10;
|
|
2411
|
-
var cache2 = [];
|
|
2412
|
-
function backend() {
|
|
2413
|
-
if (!hasPlatform()) {
|
|
2414
|
-
return null;
|
|
2415
|
-
}
|
|
2416
|
-
const platform = getPlatform();
|
|
2417
|
-
return platform.getRecentProjects && platform.saveRecentProjects ? platform : null;
|
|
2418
|
-
}
|
|
2419
|
-
function loadFromLocalStorage() {
|
|
2420
|
-
try {
|
|
2421
|
-
const raw = localStorage.getItem(STORAGE_KEY);
|
|
2422
|
-
if (!raw) {
|
|
2423
|
-
return [];
|
|
2424
|
-
}
|
|
2425
|
-
const parsed = JSON.parse(raw);
|
|
2426
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
2427
|
-
} catch {
|
|
2428
|
-
return [];
|
|
2429
|
-
}
|
|
2430
|
-
}
|
|
2431
|
-
function currentList() {
|
|
2432
|
-
return backend() ? cache2 : loadFromLocalStorage();
|
|
2433
|
-
}
|
|
2434
|
-
function commit(list) {
|
|
2435
|
-
cache2 = list;
|
|
2436
|
-
const store = backend();
|
|
2437
|
-
if (store) {
|
|
2438
|
-
store.saveRecentProjects(list);
|
|
2439
|
-
} else {
|
|
2440
|
-
try {
|
|
2441
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
|
|
2442
|
-
} catch {}
|
|
2443
|
-
}
|
|
2444
|
-
}
|
|
2445
|
-
async function hydrateRecentProjects() {
|
|
2446
|
-
const store = backend();
|
|
2447
|
-
if (!store) {
|
|
2448
|
-
return;
|
|
2449
|
-
}
|
|
2450
|
-
try {
|
|
2451
|
-
cache2 = await store.getRecentProjects();
|
|
2452
|
-
} catch {
|
|
2453
|
-
cache2 = [];
|
|
2454
|
-
}
|
|
2455
|
-
}
|
|
2456
|
-
function getRecentProjects() {
|
|
2457
|
-
return currentList().toSorted((a, b) => b.timestamp - a.timestamp);
|
|
2458
|
-
}
|
|
2459
|
-
function addRecentProject(name, root) {
|
|
2460
|
-
const projects = currentList().filter((p) => p.root !== root);
|
|
2461
|
-
projects.unshift({ name, root, timestamp: Date.now() });
|
|
2462
|
-
if (projects.length > MAX_RECENT) {
|
|
2463
|
-
projects.length = MAX_RECENT;
|
|
2464
|
-
}
|
|
2465
|
-
commit(projects);
|
|
2466
|
-
}
|
|
2467
|
-
function removeRecentProject(root) {
|
|
2468
|
-
commit(currentList().filter((p) => p.root !== root));
|
|
2469
|
-
}
|
|
2470
|
-
function clearRecentProjects() {
|
|
2471
|
-
cache2 = [];
|
|
2472
|
-
const store = backend();
|
|
2473
|
-
if (store) {
|
|
2474
|
-
store.saveRecentProjects([]);
|
|
2475
|
-
} else {
|
|
2476
|
-
localStorage.removeItem(STORAGE_KEY);
|
|
2477
|
-
}
|
|
2478
|
-
}
|
|
2479
|
-
function loadRecentFiles() {
|
|
2480
|
-
try {
|
|
2481
|
-
const raw = localStorage.getItem(FILES_STORAGE_KEY);
|
|
2482
|
-
if (!raw) {
|
|
2483
|
-
return [];
|
|
2484
|
-
}
|
|
2485
|
-
const parsed = JSON.parse(raw);
|
|
2486
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
2487
|
-
} catch {
|
|
2488
|
-
return [];
|
|
2489
|
-
}
|
|
2490
|
-
}
|
|
2491
|
-
function getRecentFiles(root) {
|
|
2492
|
-
const all = loadRecentFiles().toSorted((a, b) => b.timestamp - a.timestamp);
|
|
2493
|
-
return root == null ? all : all.filter((f) => f.root === root);
|
|
2494
|
-
}
|
|
2495
|
-
function trackRecentFile(file) {
|
|
2496
|
-
const all = loadRecentFiles().filter((f) => !(f.root === file.root && f.path === file.path));
|
|
2497
|
-
all.unshift({ name: file.name, path: file.path, root: file.root, timestamp: Date.now() });
|
|
2498
|
-
const perRoot = new Map;
|
|
2499
|
-
const kept = [];
|
|
2500
|
-
for (const f of all.toSorted((a, b) => b.timestamp - a.timestamp)) {
|
|
2501
|
-
const n = (perRoot.get(f.root) ?? 0) + 1;
|
|
2502
|
-
perRoot.set(f.root, n);
|
|
2503
|
-
if (n <= MAX_RECENT_FILES) {
|
|
2504
|
-
kept.push(f);
|
|
2505
|
-
}
|
|
2506
|
-
}
|
|
2507
|
-
localStorage.setItem(FILES_STORAGE_KEY, JSON.stringify(kept));
|
|
2508
|
-
}
|
|
2509
|
-
|
|
2510
|
-
// src/files/files.ts
|
|
2511
|
-
var fileIconMap = {
|
|
2512
|
-
"sp-icon-document": html`<sp-icon-document></sp-icon-document>`,
|
|
2513
|
-
"sp-icon-file-code": html`<sp-icon-file-code></sp-icon-file-code>`,
|
|
2514
|
-
"sp-icon-file-txt": html`<sp-icon-file-txt></sp-icon-file-txt>`,
|
|
2515
|
-
"sp-icon-folder": html`<sp-icon-folder></sp-icon-folder>`,
|
|
2516
|
-
"sp-icon-folder-open": html`<sp-icon-folder-open></sp-icon-folder-open>`,
|
|
2517
|
-
"sp-icon-image": html`<sp-icon-image></sp-icon-image>`
|
|
2518
|
-
};
|
|
2519
|
-
async function loadDirectory(dirPath) {
|
|
2520
|
-
if (!projectState) {
|
|
2521
|
-
return;
|
|
2522
|
-
}
|
|
2523
|
-
try {
|
|
2524
|
-
const platform = getPlatform();
|
|
2525
|
-
const entries = await platform.listDirectory(dirPath);
|
|
2526
|
-
projectState.dirs.set(dirPath, entries);
|
|
2527
|
-
} catch {
|
|
2528
|
-
projectState.dirs.set(dirPath, []);
|
|
2529
|
-
}
|
|
2530
|
-
}
|
|
2531
|
-
async function loadProject() {
|
|
2532
|
-
try {
|
|
2533
|
-
const platform = getPlatform();
|
|
2534
|
-
const result = await platform.probeRootProject();
|
|
2535
|
-
if (!result) {
|
|
2536
|
-
return;
|
|
2537
|
-
}
|
|
2538
|
-
const { meta, info } = result;
|
|
2539
|
-
refreshFormats();
|
|
2540
|
-
loadFormats();
|
|
2541
|
-
refreshExtensionUi(platform);
|
|
2542
|
-
setProjectState({
|
|
2543
|
-
dirs: new Map,
|
|
2544
|
-
expanded: new Set,
|
|
2545
|
-
isSiteProject: info.isSiteProject,
|
|
2546
|
-
name: info.isSiteProject ? info.projectConfig?.name || meta.name : meta.name,
|
|
2547
|
-
projectConfig: (info.isSiteProject ? info.projectConfig : null) || null,
|
|
2548
|
-
projectDirs: info.directories || [],
|
|
2549
|
-
projectRoot: ".",
|
|
2550
|
-
root: meta.root,
|
|
2551
|
-
searchQuery: "",
|
|
2552
|
-
selectedPath: null
|
|
2553
|
-
});
|
|
2554
|
-
if (info.isSiteProject) {
|
|
2555
|
-
setWorkspaceProject(meta.root || ".", info.projectConfig || null);
|
|
2556
|
-
}
|
|
2557
|
-
if (info.isSiteProject) {
|
|
2558
|
-
addRecentProject(requireProjectState().name, meta.root);
|
|
2559
|
-
await autoSyncProjectOnOpen();
|
|
2560
|
-
await ensureDependenciesInstalled();
|
|
2561
|
-
await loadDirectory(".");
|
|
2562
|
-
await loadComponentRegistry();
|
|
2563
|
-
await openHomePage();
|
|
2564
|
-
maybePromptJxsuiteUpdate(meta.root);
|
|
2565
|
-
}
|
|
2566
|
-
} catch {}
|
|
2567
|
-
}
|
|
2568
|
-
async function openProject({
|
|
2569
|
-
renderActivityBar,
|
|
2570
|
-
renderLeftPanel
|
|
2571
|
-
}) {
|
|
2572
|
-
try {
|
|
2573
|
-
const platform = getPlatform();
|
|
2574
|
-
const result = await platform.openProject();
|
|
2575
|
-
if (!result) {
|
|
2576
|
-
return;
|
|
2577
|
-
}
|
|
2578
|
-
const { config, handle } = result;
|
|
2579
|
-
replaceAllTabs({
|
|
2580
|
-
document: { children: [], tagName: "div" },
|
|
2581
|
-
id: "initial"
|
|
2582
|
-
});
|
|
2583
|
-
refreshFormats();
|
|
2584
|
-
loadFormats();
|
|
2585
|
-
refreshExtensionUi(platform);
|
|
2586
|
-
setProjectState({
|
|
2587
|
-
...projectState,
|
|
2588
|
-
dirs: new Map,
|
|
2589
|
-
expanded: new Set,
|
|
2590
|
-
isSiteProject: true,
|
|
2591
|
-
name: config.name || handle.name,
|
|
2592
|
-
projectConfig: config,
|
|
2593
|
-
projectRoot: handle.root,
|
|
2594
|
-
searchQuery: "",
|
|
2595
|
-
selectedPath: null
|
|
2596
|
-
});
|
|
2597
|
-
setWorkspaceProject(handle.root, config);
|
|
2598
|
-
await autoSyncProjectOnOpen();
|
|
2599
|
-
await ensureDependenciesInstalled();
|
|
2600
|
-
await loadDirectory(".");
|
|
2601
|
-
await loadComponentRegistry();
|
|
2602
|
-
const conventionalDirs = new Set([
|
|
2603
|
-
"pages",
|
|
2604
|
-
"layouts",
|
|
2605
|
-
"components",
|
|
2606
|
-
"content",
|
|
2607
|
-
"data",
|
|
2608
|
-
"public",
|
|
2609
|
-
"styles"
|
|
2610
|
-
]);
|
|
2611
|
-
const entries = requireProjectState().dirs.get(".") || [];
|
|
2612
|
-
const foundDirs = [];
|
|
2613
|
-
for (const e of entries) {
|
|
2614
|
-
if (e.type === "directory" && conventionalDirs.has(e.name)) {
|
|
2615
|
-
foundDirs.push(e.name);
|
|
2616
|
-
requireProjectState().expanded.add(e.path || e.name);
|
|
2617
|
-
await loadDirectory(e.path || e.name);
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
requireProjectState().projectDirs = foundDirs;
|
|
2621
|
-
view.leftTab = "files";
|
|
2622
|
-
addRecentProject(requireProjectState().name, requireProjectState().projectRoot);
|
|
2623
|
-
renderActivityBar();
|
|
2624
|
-
renderLeftPanel();
|
|
2625
|
-
statusMessage(`Opened project: ${requireProjectState().name}`);
|
|
2626
|
-
await openHomePage();
|
|
2627
|
-
maybePromptJxsuiteUpdate(requireProjectState().projectRoot);
|
|
2628
|
-
} catch (error) {
|
|
2629
|
-
statusMessage(`Error: ${errorMessage(error)}`);
|
|
2630
|
-
}
|
|
2631
|
-
}
|
|
2632
|
-
async function findHomePage() {
|
|
2633
|
-
await loadFormats();
|
|
2634
|
-
const exts = [...documentExtensions("page"), ".json"];
|
|
2635
|
-
let entries;
|
|
2636
|
-
try {
|
|
2637
|
-
entries = await getPlatform().listDirectory("pages");
|
|
2638
|
-
} catch {
|
|
2639
|
-
return null;
|
|
2640
|
-
}
|
|
2641
|
-
const files = new Set(entries.filter((e) => e.type === "file").map((e) => e.name));
|
|
2642
|
-
for (const ext of exts) {
|
|
2643
|
-
if (files.has(`index${ext}`)) {
|
|
2644
|
-
return `pages/index${ext}`;
|
|
2645
|
-
}
|
|
2646
|
-
}
|
|
2647
|
-
return null;
|
|
2648
|
-
}
|
|
2649
|
-
async function openHomePage() {
|
|
2650
|
-
const home = await findHomePage();
|
|
2651
|
-
if (home) {
|
|
2652
|
-
await openFileInTab(home);
|
|
2653
|
-
}
|
|
2654
|
-
}
|
|
2655
|
-
function fileTypeIconTpl(name, type) {
|
|
2656
|
-
let tag;
|
|
2657
|
-
if (type === "directory") {
|
|
2658
|
-
tag = projectState?.expanded?.has(name) ? "sp-icon-folder-open" : "sp-icon-folder";
|
|
2659
|
-
} else {
|
|
2660
|
-
const ext = name.split(".").pop()?.toLowerCase();
|
|
2661
|
-
switch (ext) {
|
|
2662
|
-
case "json": {
|
|
2663
|
-
tag = "sp-icon-file-code";
|
|
2664
|
-
break;
|
|
2665
|
-
}
|
|
2666
|
-
case "md": {
|
|
2667
|
-
tag = "sp-icon-file-txt";
|
|
2668
|
-
break;
|
|
2669
|
-
}
|
|
2670
|
-
case "js":
|
|
2671
|
-
case "ts": {
|
|
2672
|
-
tag = "sp-icon-file-code";
|
|
2673
|
-
break;
|
|
2674
|
-
}
|
|
2675
|
-
case "css": {
|
|
2676
|
-
tag = "sp-icon-file-code";
|
|
2677
|
-
break;
|
|
2678
|
-
}
|
|
2679
|
-
default: {
|
|
2680
|
-
tag = ext && isImage(ext) ? "sp-icon-image" : "sp-icon-document";
|
|
2681
|
-
break;
|
|
2682
|
-
}
|
|
2683
|
-
}
|
|
2684
|
-
}
|
|
2685
|
-
return fileIconMap[tag] || fileIconMap["sp-icon-document"];
|
|
2686
|
-
}
|
|
2687
|
-
function renderFilesTemplate({
|
|
2688
|
-
openProject: openProjectFn,
|
|
2689
|
-
openFileFromTree: openFileFn,
|
|
2690
|
-
renderLeftPanel
|
|
2691
|
-
}) {
|
|
2692
|
-
if (!projectState) {
|
|
2693
|
-
return html`<div class="file-tree-empty">No project loaded</div>`;
|
|
2694
|
-
}
|
|
2695
|
-
if (!projectState.isSiteProject && projectState.projectRoot === ".") {
|
|
2696
|
-
return html`<div class="file-tree-empty">
|
|
2697
|
-
<p style="margin:0 0 12px">Open a project folder to get started.</p>
|
|
2698
|
-
<sp-button variant="accent" size="s" @click=${openProjectFn}>Open Project</sp-button>
|
|
2699
|
-
</div>`;
|
|
2700
|
-
}
|
|
2701
|
-
return html`
|
|
2702
|
-
${projectState.isSiteProject ? html`
|
|
2703
|
-
<div class="project-header">
|
|
2704
|
-
<span class="project-name"
|
|
2705
|
-
>${projectState.projectConfig?.name || projectState.name}</span
|
|
2706
|
-
>
|
|
2707
|
-
</div>
|
|
2708
|
-
` : nothing}
|
|
2709
|
-
<div class="files-toolbar">
|
|
2710
|
-
<sp-action-group size="xs" compact quiet>
|
|
2711
|
-
<sp-action-button
|
|
2712
|
-
size="xs"
|
|
2713
|
-
label="New File"
|
|
2714
|
-
@click=${() => createNewFile(".", renderLeftPanel)}
|
|
2715
|
-
>
|
|
2716
|
-
<sp-icon-add slot="icon"></sp-icon-add>
|
|
2717
|
-
</sp-action-button>
|
|
2718
|
-
<sp-action-button
|
|
2719
|
-
size="xs"
|
|
2720
|
-
label="Refresh"
|
|
2721
|
-
@click=${async () => {
|
|
2722
|
-
requireProjectState().dirs.clear();
|
|
2723
|
-
await loadDirectory(".");
|
|
2724
|
-
for (const dir of requireProjectState().expanded) {
|
|
2725
|
-
await loadDirectory(dir);
|
|
2726
|
-
}
|
|
2727
|
-
renderLeftPanel();
|
|
2728
|
-
}}
|
|
2729
|
-
>
|
|
2730
|
-
<sp-icon-refresh slot="icon"></sp-icon-refresh>
|
|
2731
|
-
</sp-action-button>
|
|
2732
|
-
</sp-action-group>
|
|
2733
|
-
<sp-search
|
|
2734
|
-
size="s"
|
|
2735
|
-
quiet
|
|
2736
|
-
placeholder="Filter files…"
|
|
2737
|
-
value=${requireProjectState().searchQuery}
|
|
2738
|
-
@input=${(e) => {
|
|
2739
|
-
requireProjectState().searchQuery = e.target.value;
|
|
2740
|
-
renderLeftPanel();
|
|
2741
|
-
}}
|
|
2742
|
-
@submit=${(e) => e.preventDefault()}
|
|
2743
|
-
></sp-search>
|
|
2744
|
-
</div>
|
|
2745
|
-
<div class="file-tree" role="tree" aria-label="Project files">
|
|
2746
|
-
${renderTreeLevelTemplate(".", 0, { openFileFn, renderLeftPanel })}
|
|
2747
|
-
</div>
|
|
2748
|
-
`;
|
|
2749
|
-
}
|
|
2750
|
-
function renderTreeLevelTemplate(dirPath, depth, ctx) {
|
|
2751
|
-
const entries = requireProjectState().dirs.get(dirPath);
|
|
2752
|
-
if (!entries) {
|
|
2753
|
-
loadDirectory(dirPath).then(() => ctx.renderLeftPanel());
|
|
2754
|
-
return html`<div
|
|
2755
|
-
class="file-tree-item"
|
|
2756
|
-
style="padding-left:${8 + depth * 16}px;color:var(--fg-dim);font-style:italic"
|
|
2757
|
-
>
|
|
2758
|
-
Loading…
|
|
2759
|
-
</div>`;
|
|
2760
|
-
}
|
|
2761
|
-
const sorted = [...entries].toSorted((a, b) => {
|
|
2762
|
-
if (a.type === "directory" && b.type !== "directory") {
|
|
2763
|
-
return -1;
|
|
2764
|
-
}
|
|
2765
|
-
if (a.type !== "directory" && b.type === "directory") {
|
|
2766
|
-
return 1;
|
|
2767
|
-
}
|
|
2768
|
-
return a.name.localeCompare(b.name);
|
|
2769
|
-
});
|
|
2770
|
-
const query = requireProjectState().searchQuery.toLowerCase();
|
|
2771
|
-
const filtered = query ? sorted.filter((e) => e.type === "directory" || e.name.toLowerCase().includes(query)) : sorted;
|
|
2772
|
-
return filtered.map((entry) => {
|
|
2773
|
-
const isDir = entry.type === "directory";
|
|
2774
|
-
const isExpanded = requireProjectState().expanded.has(entry.path);
|
|
2775
|
-
const isSelected = requireProjectState().selectedPath === entry.path;
|
|
2776
|
-
return html`
|
|
2777
|
-
<div
|
|
2778
|
-
class=${classMap({ "file-tree-item": true, selected: isSelected })}
|
|
2779
|
-
style="padding-left:${8 + depth * 16}px"
|
|
2780
|
-
role="treeitem"
|
|
2781
|
-
aria-level=${depth + 1}
|
|
2782
|
-
tabindex="-1"
|
|
2783
|
-
data-path=${entry.path}
|
|
2784
|
-
data-type=${entry.type}
|
|
2785
|
-
aria-expanded=${isDir ? String(isExpanded) : nothing}
|
|
2786
|
-
@click=${async (e) => {
|
|
2787
|
-
e.stopPropagation();
|
|
2788
|
-
if (isDir) {
|
|
2789
|
-
if (isExpanded) {
|
|
2790
|
-
requireProjectState().expanded.delete(entry.path);
|
|
2791
|
-
} else {
|
|
2792
|
-
requireProjectState().expanded.add(entry.path);
|
|
2793
|
-
if (!requireProjectState().dirs.has(entry.path)) {
|
|
2794
|
-
await loadDirectory(entry.path);
|
|
2795
|
-
}
|
|
2796
|
-
}
|
|
2797
|
-
ctx.renderLeftPanel();
|
|
2798
|
-
} else {
|
|
2799
|
-
ctx.openFileFn(entry.path);
|
|
2800
|
-
}
|
|
2801
|
-
}}
|
|
2802
|
-
@contextmenu=${(e) => {
|
|
2803
|
-
e.preventDefault();
|
|
2804
|
-
e.stopPropagation();
|
|
2805
|
-
showFileContextMenu(e, entry, ctx);
|
|
2806
|
-
}}
|
|
2807
|
-
>
|
|
2808
|
-
${isDir ? html`<span class="file-tree-toggle">${isExpanded ? "▼" : "▶"}</span>` : html`<span class="file-tree-toggle empty"> </span>`}
|
|
2809
|
-
<span class="file-tree-icon">${fileTypeIconTpl(entry.path, entry.type)}</span>
|
|
2810
|
-
<span class="file-tree-name">${entry.name}</span>
|
|
2811
|
-
</div>
|
|
2812
|
-
${isDir && isExpanded ? html`<div role="group">${renderTreeLevelTemplate(entry.path, depth + 1, ctx)}</div>` : nothing}
|
|
2813
|
-
`;
|
|
2814
|
-
});
|
|
2815
|
-
}
|
|
2816
|
-
function setupTreeKeyboard(tree) {
|
|
2817
|
-
tree.addEventListener("keydown", (e) => {
|
|
2818
|
-
const items = [...tree.querySelectorAll(".file-tree-item")];
|
|
2819
|
-
const focused = tree.querySelector(".file-tree-item:focus");
|
|
2820
|
-
if (!focused || items.length === 0) {
|
|
2821
|
-
return;
|
|
2822
|
-
}
|
|
2823
|
-
const idx = items.indexOf(focused);
|
|
2824
|
-
let handled = true;
|
|
2825
|
-
switch (e.key) {
|
|
2826
|
-
case "ArrowDown": {
|
|
2827
|
-
if (idx < items.length - 1) {
|
|
2828
|
-
items[idx + 1].focus();
|
|
2829
|
-
}
|
|
2830
|
-
break;
|
|
2831
|
-
}
|
|
2832
|
-
case "ArrowUp": {
|
|
2833
|
-
if (idx > 0) {
|
|
2834
|
-
items[idx - 1].focus();
|
|
2835
|
-
}
|
|
2836
|
-
break;
|
|
2837
|
-
}
|
|
2838
|
-
case "ArrowRight": {
|
|
2839
|
-
if (focused.dataset.type === "directory") {
|
|
2840
|
-
const path = focused.dataset.path;
|
|
2841
|
-
if (!requireProjectState().expanded.has(path)) {
|
|
2842
|
-
requireProjectState().expanded.add(path);
|
|
2843
|
-
loadDirectory(path).then(() => {
|
|
2844
|
-
const panel = tree.closest(".panel-body");
|
|
2845
|
-
if (panel) {
|
|
2846
|
-
panel.querySelector(".file-tree-item:focus")?.click();
|
|
2847
|
-
}
|
|
2848
|
-
});
|
|
2849
|
-
}
|
|
2850
|
-
}
|
|
2851
|
-
break;
|
|
2852
|
-
}
|
|
2853
|
-
case "ArrowLeft": {
|
|
2854
|
-
if (focused.dataset.type === "directory") {
|
|
2855
|
-
const path = focused.dataset.path;
|
|
2856
|
-
if (requireProjectState().expanded.has(path)) {
|
|
2857
|
-
requireProjectState().expanded.delete(path);
|
|
2858
|
-
}
|
|
2859
|
-
}
|
|
2860
|
-
break;
|
|
2861
|
-
}
|
|
2862
|
-
case "Enter": {
|
|
2863
|
-
focused.click();
|
|
2864
|
-
break;
|
|
2865
|
-
}
|
|
2866
|
-
default: {
|
|
2867
|
-
handled = false;
|
|
2868
|
-
}
|
|
2869
|
-
}
|
|
2870
|
-
if (handled) {
|
|
2871
|
-
e.preventDefault();
|
|
2872
|
-
}
|
|
2873
|
-
});
|
|
2874
|
-
const first = tree.querySelector(".file-tree-item");
|
|
2875
|
-
if (first) {
|
|
2876
|
-
first.setAttribute("tabindex", "0");
|
|
2877
|
-
}
|
|
2878
|
-
}
|
|
2879
|
-
var _fileTreeDndCleanups = [];
|
|
2880
|
-
function isFileDrag(e) {
|
|
2881
|
-
return [...e.dataTransfer?.types ?? []].includes("Files");
|
|
2882
|
-
}
|
|
2883
|
-
function registerFileDropTarget(element, dir, activeClass, renderLeftPanel) {
|
|
2884
|
-
const onDragOver = (e) => {
|
|
2885
|
-
if (!isFileDrag(e)) {
|
|
2886
|
-
return;
|
|
2887
|
-
}
|
|
2888
|
-
e.preventDefault();
|
|
2889
|
-
e.stopPropagation();
|
|
2890
|
-
if (e.dataTransfer) {
|
|
2891
|
-
e.dataTransfer.dropEffect = "copy";
|
|
2892
|
-
}
|
|
2893
|
-
element.classList.add(activeClass);
|
|
2894
|
-
};
|
|
2895
|
-
const onDragLeave = () => element.classList.remove(activeClass);
|
|
2896
|
-
const onDrop = (e) => {
|
|
2897
|
-
element.classList.remove(activeClass);
|
|
2898
|
-
const files = e.dataTransfer?.files;
|
|
2899
|
-
if (!files?.length) {
|
|
2900
|
-
return;
|
|
2901
|
-
}
|
|
2902
|
-
e.preventDefault();
|
|
2903
|
-
e.stopPropagation();
|
|
2904
|
-
uploadFilesToDir(files, dir, renderLeftPanel);
|
|
2905
|
-
};
|
|
2906
|
-
element.addEventListener("dragover", onDragOver);
|
|
2907
|
-
element.addEventListener("dragleave", onDragLeave);
|
|
2908
|
-
element.addEventListener("drop", onDrop);
|
|
2909
|
-
return () => {
|
|
2910
|
-
element.removeEventListener("dragover", onDragOver);
|
|
2911
|
-
element.removeEventListener("dragleave", onDragLeave);
|
|
2912
|
-
element.removeEventListener("drop", onDrop);
|
|
2913
|
-
};
|
|
2914
|
-
}
|
|
2915
|
-
async function uploadFilesToDir(files, dir, renderLeftPanel) {
|
|
2916
|
-
const uploaded = await uploadAssets([...files], { dir });
|
|
2917
|
-
if (uploaded.length === 0) {
|
|
2918
|
-
return;
|
|
2919
|
-
}
|
|
2920
|
-
if (dir !== ".") {
|
|
2921
|
-
requireProjectState().expanded.add(dir);
|
|
2922
|
-
}
|
|
2923
|
-
renderLeftPanel();
|
|
2924
|
-
}
|
|
2925
|
-
function pickAndUploadTo(dir, renderLeftPanel) {
|
|
2926
|
-
const input = document.createElement("input");
|
|
2927
|
-
input.type = "file";
|
|
2928
|
-
input.multiple = true;
|
|
2929
|
-
input.accept = UPLOAD_ACCEPT;
|
|
2930
|
-
input.addEventListener("change", () => {
|
|
2931
|
-
if (input.files?.length) {
|
|
2932
|
-
uploadFilesToDir(input.files, dir, renderLeftPanel);
|
|
2933
|
-
}
|
|
2934
|
-
});
|
|
2935
|
-
input.click();
|
|
2936
|
-
}
|
|
2937
|
-
function registerFileTreeDnD({ renderLeftPanel }) {
|
|
2938
|
-
for (const fn of _fileTreeDndCleanups) {
|
|
2939
|
-
fn();
|
|
2940
|
-
}
|
|
2941
|
-
_fileTreeDndCleanups = [];
|
|
2942
|
-
requestAnimationFrame(() => {
|
|
2943
|
-
const tree = document.querySelector(".file-tree");
|
|
2944
|
-
if (!tree) {
|
|
2945
|
-
return;
|
|
2946
|
-
}
|
|
2947
|
-
const items = tree.querySelectorAll(".file-tree-item");
|
|
2948
|
-
for (const row of items) {
|
|
2949
|
-
const { path } = row.dataset;
|
|
2950
|
-
const { type } = row.dataset;
|
|
2951
|
-
if (!path) {
|
|
2952
|
-
continue;
|
|
2953
|
-
}
|
|
2954
|
-
const cleanups = [
|
|
2955
|
-
draggable({
|
|
2956
|
-
element: row,
|
|
2957
|
-
getInitialData() {
|
|
2958
|
-
return { entryType: type, path, type: "file-tree" };
|
|
2959
|
-
},
|
|
2960
|
-
onDragStart() {
|
|
2961
|
-
row.classList.add("dragging");
|
|
2962
|
-
},
|
|
2963
|
-
onDrop() {
|
|
2964
|
-
row.classList.remove("dragging");
|
|
2965
|
-
}
|
|
2966
|
-
}),
|
|
2967
|
-
registerFileDropTarget(row, type === "directory" ? path : parentDir2(path), "drag-over", renderLeftPanel)
|
|
2968
|
-
];
|
|
2969
|
-
if (type === "directory") {
|
|
2970
|
-
cleanups.push(dropTargetForElements({
|
|
2971
|
-
canDrop({ source }) {
|
|
2972
|
-
if (source.data.type !== "file-tree") {
|
|
2973
|
-
return false;
|
|
2974
|
-
}
|
|
2975
|
-
const srcPath = source.data.path;
|
|
2976
|
-
if (srcPath === path) {
|
|
2977
|
-
return false;
|
|
2978
|
-
}
|
|
2979
|
-
if (srcPath.startsWith(`${path}/`)) {
|
|
2980
|
-
return false;
|
|
2981
|
-
}
|
|
2982
|
-
const srcParent = parentDir2(srcPath);
|
|
2983
|
-
if (srcParent === path) {
|
|
2984
|
-
return false;
|
|
2985
|
-
}
|
|
2986
|
-
return true;
|
|
2987
|
-
},
|
|
2988
|
-
element: row,
|
|
2989
|
-
getData() {
|
|
2990
|
-
return { targetDir: path, type: "file-tree-target" };
|
|
2991
|
-
},
|
|
2992
|
-
onDrag() {
|
|
2993
|
-
if (!row.classList.contains("drag-over")) {
|
|
2994
|
-
row.classList.add("drag-over");
|
|
2995
|
-
}
|
|
2996
|
-
},
|
|
2997
|
-
onDragEnter() {
|
|
2998
|
-
row.classList.add("drag-over");
|
|
2999
|
-
},
|
|
3000
|
-
onDragLeave() {
|
|
3001
|
-
row.classList.remove("drag-over");
|
|
3002
|
-
},
|
|
3003
|
-
onDrop() {
|
|
3004
|
-
row.classList.remove("drag-over");
|
|
3005
|
-
}
|
|
3006
|
-
}));
|
|
3007
|
-
}
|
|
3008
|
-
_fileTreeDndCleanups.push(combine(...cleanups));
|
|
3009
|
-
}
|
|
3010
|
-
const rootCleanup = dropTargetForElements({
|
|
3011
|
-
canDrop({ source }) {
|
|
3012
|
-
if (source.data.type !== "file-tree") {
|
|
3013
|
-
return false;
|
|
3014
|
-
}
|
|
3015
|
-
const srcPath = source.data.path;
|
|
3016
|
-
return parentDir2(srcPath) !== ".";
|
|
3017
|
-
},
|
|
3018
|
-
element: tree,
|
|
3019
|
-
getData() {
|
|
3020
|
-
return { targetDir: ".", type: "file-tree-target" };
|
|
3021
|
-
},
|
|
3022
|
-
onDragEnter() {
|
|
3023
|
-
tree.classList.add("drag-over-root");
|
|
3024
|
-
},
|
|
3025
|
-
onDragLeave() {
|
|
3026
|
-
tree.classList.remove("drag-over-root");
|
|
3027
|
-
},
|
|
3028
|
-
onDrop() {
|
|
3029
|
-
tree.classList.remove("drag-over-root");
|
|
3030
|
-
}
|
|
3031
|
-
});
|
|
3032
|
-
_fileTreeDndCleanups.push(rootCleanup, registerFileDropTarget(tree, ".", "drag-over-root", renderLeftPanel));
|
|
3033
|
-
const monitorCleanup = monitorForElements({
|
|
3034
|
-
onDrop({ source, location: location2 }) {
|
|
3035
|
-
const [target] = location2.current.dropTargets;
|
|
3036
|
-
if (!target) {
|
|
3037
|
-
return;
|
|
3038
|
-
}
|
|
3039
|
-
if (source.data.type !== "file-tree") {
|
|
3040
|
-
return;
|
|
3041
|
-
}
|
|
3042
|
-
if (target.data.type !== "file-tree-target") {
|
|
3043
|
-
return;
|
|
3044
|
-
}
|
|
3045
|
-
const srcPath = source.data.path;
|
|
3046
|
-
const targetDirPath = target.data.targetDir;
|
|
3047
|
-
const fileName = srcPath.split("/").pop();
|
|
3048
|
-
const newPath = targetDirPath === "." ? fileName : `${targetDirPath}/${fileName}`;
|
|
3049
|
-
if (newPath === srcPath) {
|
|
3050
|
-
return;
|
|
3051
|
-
}
|
|
3052
|
-
moveFileEntry(srcPath, newPath, renderLeftPanel);
|
|
3053
|
-
}
|
|
3054
|
-
});
|
|
3055
|
-
_fileTreeDndCleanups.push(monitorCleanup);
|
|
3056
|
-
});
|
|
3057
|
-
}
|
|
3058
|
-
async function moveFileEntry(oldPath, newPath, renderLeftPanel) {
|
|
3059
|
-
const platform = getPlatform();
|
|
3060
|
-
markLocalMutation(oldPath, newPath);
|
|
3061
|
-
try {
|
|
3062
|
-
const report = await platform.renameFile(oldPath, newPath);
|
|
3063
|
-
for (const [id] of workspace.tabs.entries()) {
|
|
3064
|
-
if (id === oldPath) {
|
|
3065
|
-
renameTab(oldPath, newPath, newPath);
|
|
3066
|
-
} else if (id.startsWith(`${oldPath}/`)) {
|
|
3067
|
-
const newTabPath = newPath + id.slice(oldPath.length);
|
|
3068
|
-
renameTab(id, newTabPath, newTabPath);
|
|
3069
|
-
}
|
|
3070
|
-
}
|
|
3071
|
-
const oldParent = parentDir2(oldPath);
|
|
3072
|
-
const newParent = parentDir2(newPath);
|
|
3073
|
-
await loadDirectory(oldParent);
|
|
3074
|
-
if (newParent !== oldParent) {
|
|
3075
|
-
await loadDirectory(newParent);
|
|
3076
|
-
}
|
|
3077
|
-
if (newParent !== ".") {
|
|
3078
|
-
requireProjectState().expanded.add(newParent);
|
|
3079
|
-
}
|
|
3080
|
-
reloadRewrittenTabs(report, newPath);
|
|
3081
|
-
renderLeftPanel();
|
|
3082
|
-
statusMessage(`Moved to ${newPath}`);
|
|
3083
|
-
} catch (error) {
|
|
3084
|
-
statusMessage(`Error: ${errorMessage(error)}`);
|
|
3085
|
-
}
|
|
3086
|
-
}
|
|
3087
|
-
function parentDir2(path) {
|
|
3088
|
-
const normalized = path.replaceAll("\\", "/");
|
|
3089
|
-
const lastSlash = normalized.lastIndexOf("/");
|
|
3090
|
-
return lastSlash === -1 ? "." : normalized.slice(0, lastSlash);
|
|
3091
|
-
}
|
|
3092
|
-
var _fileCtxHandle = null;
|
|
3093
|
-
function dismissFileContextMenu() {
|
|
3094
|
-
if (_fileCtxHandle) {
|
|
3095
|
-
_fileCtxHandle.dismiss();
|
|
3096
|
-
_fileCtxHandle = null;
|
|
3097
|
-
}
|
|
3098
|
-
}
|
|
3099
|
-
function showFileContextMenu(e, entry, ctx) {
|
|
3100
|
-
e.preventDefault();
|
|
3101
|
-
dismissFileContextMenu();
|
|
3102
|
-
const isDir = entry.type === "directory";
|
|
3103
|
-
const items = [];
|
|
3104
|
-
if (!isDir) {
|
|
3105
|
-
items.push({ action: () => ctx.openFileFn(entry.path), label: "Open" });
|
|
3106
|
-
}
|
|
3107
|
-
if (isDir) {
|
|
3108
|
-
items.push({
|
|
3109
|
-
action: () => createNewFile(entry.path, ctx.renderLeftPanel),
|
|
3110
|
-
label: "New File…"
|
|
3111
|
-
}, {
|
|
3112
|
-
action: () => pickAndUploadTo(entry.path, ctx.renderLeftPanel),
|
|
3113
|
-
label: "Upload Files…"
|
|
3114
|
-
});
|
|
3115
|
-
const collection = collectionDirs().find(({ dir }) => entry.path === dir || entry.path.endsWith(`/${dir}`));
|
|
3116
|
-
if (collection) {
|
|
3117
|
-
items.push({
|
|
3118
|
-
action: () => {
|
|
3119
|
-
openCollectionGrid(collection.name);
|
|
3120
|
-
},
|
|
3121
|
-
label: "Edit Collection in Grid"
|
|
3122
|
-
});
|
|
3123
|
-
}
|
|
3124
|
-
if (entry.path === "pages" || entry.path.endsWith("/pages")) {
|
|
3125
|
-
items.push({
|
|
3126
|
-
action: () => {
|
|
3127
|
-
openPagesGrid();
|
|
3128
|
-
},
|
|
3129
|
-
label: "Edit Pages in Grid"
|
|
3130
|
-
});
|
|
3131
|
-
}
|
|
3132
|
-
}
|
|
3133
|
-
items.push({ label: "—" }, {
|
|
3134
|
-
action: () => renameFile(entry, ctx.renderLeftPanel),
|
|
3135
|
-
label: "Rename…"
|
|
3136
|
-
}, {
|
|
3137
|
-
action: () => deleteFile(entry, ctx.renderLeftPanel),
|
|
3138
|
-
danger: true,
|
|
3139
|
-
label: "Delete"
|
|
3140
|
-
});
|
|
3141
|
-
let { clientX: x, clientY: y } = e;
|
|
3142
|
-
_fileCtxHandle = renderPopover(html`<sp-popover
|
|
3143
|
-
open
|
|
3144
|
-
style="position:fixed;z-index:10000;left:${x}px;top:${y}px"
|
|
3145
|
-
${ref((el) => {
|
|
3146
|
-
if (!el) {
|
|
3147
|
-
return;
|
|
3148
|
-
}
|
|
3149
|
-
requestAnimationFrame(() => {
|
|
3150
|
-
const popover = el;
|
|
3151
|
-
const menuRect = rectOf(popover);
|
|
3152
|
-
if (x + menuRect.width > window.innerWidth) {
|
|
3153
|
-
x = window.innerWidth - menuRect.width - 4;
|
|
3154
|
-
}
|
|
3155
|
-
if (y + menuRect.height > window.innerHeight) {
|
|
3156
|
-
y = window.innerHeight - menuRect.height - 4;
|
|
3157
|
-
}
|
|
3158
|
-
popover.style.left = `${x}px`;
|
|
3159
|
-
popover.style.top = `${y}px`;
|
|
3160
|
-
});
|
|
3161
|
-
})}
|
|
3162
|
-
>
|
|
3163
|
-
<sp-menu>
|
|
3164
|
-
${items.map((item) => item.label === "—" ? html`<sp-menu-divider></sp-menu-divider>` : html`<sp-menu-item
|
|
3165
|
-
style=${item.danger ? "color: var(--danger)" : ""}
|
|
3166
|
-
@click=${() => {
|
|
3167
|
-
dismissFileContextMenu();
|
|
3168
|
-
item.action?.();
|
|
3169
|
-
}}
|
|
3170
|
-
>${item.label}</sp-menu-item
|
|
3171
|
-
>`)}
|
|
3172
|
-
</sp-menu>
|
|
3173
|
-
</sp-popover>`, {
|
|
3174
|
-
dismissOnOutsideClick: true,
|
|
3175
|
-
onDismiss: () => {
|
|
3176
|
-
_fileCtxHandle = null;
|
|
3177
|
-
}
|
|
3178
|
-
});
|
|
3179
|
-
}
|
|
3180
|
-
async function createNewFile(dirPath, renderLeftPanel) {
|
|
3181
|
-
const name = await showPromptDialog("New File", {
|
|
3182
|
-
confirmLabel: "Create",
|
|
3183
|
-
message: dirPath === "." ? "Creating in the project root." : `Creating in ${dirPath}/`,
|
|
3184
|
-
placeholder: "untitled.json",
|
|
3185
|
-
select: "stem",
|
|
3186
|
-
validate: (v) => v.trim() ? "" : "Enter a file name.",
|
|
3187
|
-
value: "untitled.json"
|
|
3188
|
-
});
|
|
3189
|
-
if (!name) {
|
|
3190
|
-
return;
|
|
3191
|
-
}
|
|
3192
|
-
const path = dirPath === "." ? name : `${dirPath}/${name}`;
|
|
3193
|
-
markLocalMutation(path);
|
|
3194
|
-
await loadFormats();
|
|
3195
|
-
const format = formatForPath(name);
|
|
3196
|
-
const content = format?.studio?.newFileTemplate ?? (format ? "" : JSON.stringify({ children: [{ children: [], tagName: "p" }], tagName: "div" }, null, 2));
|
|
3197
|
-
try {
|
|
3198
|
-
const platform = getPlatform();
|
|
3199
|
-
await platform.writeFile(path, content);
|
|
3200
|
-
await loadDirectory(dirPath);
|
|
3201
|
-
renderLeftPanel();
|
|
3202
|
-
statusMessage(`Created ${path}`);
|
|
3203
|
-
} catch (error) {
|
|
3204
|
-
statusMessage(`Error: ${errorMessage(error)}`);
|
|
3205
|
-
}
|
|
3206
|
-
}
|
|
3207
|
-
function showRenameFileDialog(currentName) {
|
|
3208
|
-
return showPromptDialog("Rename", {
|
|
3209
|
-
confirmLabel: "Rename",
|
|
3210
|
-
select: "stem",
|
|
3211
|
-
validate: (v) => v.trim() ? "" : "Enter a file name.",
|
|
3212
|
-
value: currentName
|
|
3213
|
-
});
|
|
3214
|
-
}
|
|
3215
|
-
function renameStatus(newName, report) {
|
|
3216
|
-
const refs = report.references;
|
|
3217
|
-
const tagNote = report.tag ? `; tag → <${report.tag.to}> (${report.tag.refsUpdated})` : "";
|
|
3218
|
-
if (refs && refs.refsUpdated > 0) {
|
|
3219
|
-
return `Renamed to ${newName}; updated ${refs.refsUpdated} reference(s) in ${refs.filesChanged} file(s)${tagNote}`;
|
|
3220
|
-
}
|
|
3221
|
-
if (tagNote) {
|
|
3222
|
-
return `Renamed to ${newName}${tagNote}`;
|
|
3223
|
-
}
|
|
3224
|
-
return `Renamed to ${newName}`;
|
|
3225
|
-
}
|
|
3226
|
-
function reloadRewrittenTabs(report, skipPath) {
|
|
3227
|
-
for (const f of report.references?.files ?? []) {
|
|
3228
|
-
if (f.path !== skipPath && workspace.tabs.has(f.path)) {
|
|
3229
|
-
reloadFileInTab(f.path);
|
|
3230
|
-
}
|
|
3231
|
-
}
|
|
3232
|
-
}
|
|
3233
|
-
async function renameFile(entry, renderLeftPanel) {
|
|
3234
|
-
const newName = await showRenameFileDialog(entry.name);
|
|
3235
|
-
if (!newName || newName === entry.name) {
|
|
3236
|
-
return;
|
|
3237
|
-
}
|
|
3238
|
-
const entryPath = entry.path.replaceAll("\\", "/");
|
|
3239
|
-
const parentDirPath = entryPath.includes("/") ? entryPath.slice(0, entryPath.lastIndexOf("/")) : ".";
|
|
3240
|
-
const newPath = parentDirPath === "." ? newName : `${parentDirPath}/${newName}`;
|
|
3241
|
-
markLocalMutation(entry.path, newPath);
|
|
3242
|
-
try {
|
|
3243
|
-
const platform = getPlatform();
|
|
3244
|
-
const report = await platform.renameFile(entry.path, newPath);
|
|
3245
|
-
await loadDirectory(parentDirPath);
|
|
3246
|
-
if (requireProjectState().selectedPath === entry.path) {
|
|
3247
|
-
requireProjectState().selectedPath = newPath;
|
|
3248
|
-
}
|
|
3249
|
-
if (workspace.tabs.has(entry.path)) {
|
|
3250
|
-
renameTab(entry.path, newPath, newPath);
|
|
3251
|
-
}
|
|
3252
|
-
reloadRewrittenTabs(report, newPath);
|
|
3253
|
-
renderLeftPanel();
|
|
3254
|
-
statusMessage(renameStatus(newName, report));
|
|
3255
|
-
} catch (error) {
|
|
3256
|
-
statusMessage(`Error: ${errorMessage(error)}`);
|
|
3257
|
-
}
|
|
3258
|
-
}
|
|
3259
|
-
async function deleteFile(entry, renderLeftPanel) {
|
|
3260
|
-
const confirmed = await showConfirmDialog("Delete File", `Delete "${entry.name}"?`, {
|
|
3261
|
-
confirmLabel: "Delete",
|
|
3262
|
-
destructive: true
|
|
3263
|
-
});
|
|
3264
|
-
if (!confirmed) {
|
|
3265
|
-
return;
|
|
3266
|
-
}
|
|
3267
|
-
try {
|
|
3268
|
-
const platform = getPlatform();
|
|
3269
|
-
markLocalMutation(entry.path);
|
|
3270
|
-
await platform.deleteFile(entry.path);
|
|
3271
|
-
const delPath = entry.path.replaceAll("\\", "/");
|
|
3272
|
-
const parentDirPath = delPath.includes("/") ? delPath.slice(0, delPath.lastIndexOf("/")) : ".";
|
|
3273
|
-
await loadDirectory(parentDirPath);
|
|
3274
|
-
if (requireProjectState().selectedPath === entry.path) {
|
|
3275
|
-
requireProjectState().selectedPath = null;
|
|
3276
|
-
}
|
|
3277
|
-
renderLeftPanel();
|
|
3278
|
-
statusMessage(`Deleted ${entry.name}`);
|
|
3279
|
-
} catch (error) {
|
|
3280
|
-
statusMessage(`Error: ${errorMessage(error)}`);
|
|
3281
|
-
}
|
|
3282
|
-
}
|
|
3283
|
-
async function openFileFromTree(ctx, path) {
|
|
3284
|
-
const platform = getPlatform();
|
|
3285
|
-
await loadFormats();
|
|
3286
|
-
if (ctx.S.dirty && ctx.S.documentPath) {
|
|
3287
|
-
try {
|
|
3288
|
-
const tabLike = {
|
|
3289
|
-
doc: {
|
|
3290
|
-
content: ctx.S.content,
|
|
3291
|
-
document: ctx.S.document,
|
|
3292
|
-
mode: ctx.S.mode,
|
|
3293
|
-
sourceFormat: formatForPath(ctx.S.documentPath)?.name ?? null
|
|
3294
|
-
}
|
|
3295
|
-
};
|
|
3296
|
-
const output = await serializeDocument(tabLike);
|
|
3297
|
-
await platform.writeFile(ctx.S.documentPath, output);
|
|
3298
|
-
} catch (error) {
|
|
3299
|
-
statusMessage(`Save error: ${errorMessage(error)}`);
|
|
3300
|
-
}
|
|
3301
|
-
}
|
|
3302
|
-
try {
|
|
3303
|
-
const content = await platform.readFile(path);
|
|
3304
|
-
if (!content) {
|
|
3305
|
-
return;
|
|
3306
|
-
}
|
|
3307
|
-
if (formatForPath(path)) {
|
|
3308
|
-
ctx.loadMarkdown(content, null);
|
|
3309
|
-
ctx.S.documentPath = path;
|
|
3310
|
-
ctx.S.dirty = false;
|
|
3311
|
-
ctx.commit(ctx.S);
|
|
3312
|
-
} else if (path.endsWith(".json")) {
|
|
3313
|
-
const doc = JSON.parse(content);
|
|
3314
|
-
const newS = createState(doc);
|
|
3315
|
-
newS.documentPath = path;
|
|
3316
|
-
newS.dirty = false;
|
|
3317
|
-
ctx.commit(newS);
|
|
3318
|
-
} else {
|
|
3319
|
-
throw noFormatError(path);
|
|
3320
|
-
}
|
|
3321
|
-
requireProjectState().selectedPath = path;
|
|
3322
|
-
ctx.render();
|
|
3323
|
-
statusMessage(`Opened ${path}`);
|
|
3324
|
-
} catch (error) {
|
|
3325
|
-
statusMessage(`Error: ${errorMessage(error)}`);
|
|
3326
|
-
}
|
|
3327
|
-
}
|
|
3328
|
-
async function openFileInTab(path) {
|
|
3329
|
-
for (const [id, tab] of workspace.tabs.entries()) {
|
|
3330
|
-
if (tab.documentPath === path) {
|
|
3331
|
-
activateTab(id);
|
|
3332
|
-
requireProjectState().selectedPath = path;
|
|
3333
|
-
return;
|
|
3334
|
-
}
|
|
3335
|
-
}
|
|
3336
|
-
if (path.toLowerCase().endsWith(".csv")) {
|
|
3337
|
-
try {
|
|
3338
|
-
await openCsvGridTab(path);
|
|
3339
|
-
requireProjectState().selectedPath = path;
|
|
3340
|
-
trackRecentFile({
|
|
3341
|
-
name: path.split("/").pop() || path,
|
|
3342
|
-
path,
|
|
3343
|
-
root: requireProjectState().projectRoot
|
|
3344
|
-
});
|
|
3345
|
-
statusMessage(`Opened ${path.split("/").pop()}`);
|
|
3346
|
-
} catch (error) {
|
|
3347
|
-
statusMessage(`Error: ${errorMessage(error)}`);
|
|
3348
|
-
}
|
|
3349
|
-
return;
|
|
3350
|
-
}
|
|
3351
|
-
const platform = getPlatform();
|
|
3352
|
-
try {
|
|
3353
|
-
const content = await platform.readFile(path);
|
|
3354
|
-
if (!content) {
|
|
3355
|
-
return;
|
|
3356
|
-
}
|
|
3357
|
-
await loadFormats();
|
|
3358
|
-
let document2;
|
|
3359
|
-
let frontmatter;
|
|
3360
|
-
const format = formatForPath(path);
|
|
3361
|
-
if (format) {
|
|
3362
|
-
const result = await parseSourceForPath(path, content);
|
|
3363
|
-
({ document: document2 } = result);
|
|
3364
|
-
({ frontmatter } = result);
|
|
3365
|
-
} else if (path.endsWith(".json")) {
|
|
3366
|
-
document2 = JSON.parse(content);
|
|
3367
|
-
} else {
|
|
3368
|
-
throw noFormatError(path);
|
|
3369
|
-
}
|
|
3370
|
-
const id = path;
|
|
3371
|
-
openTab({
|
|
3372
|
-
id,
|
|
3373
|
-
documentPath: path,
|
|
3374
|
-
document: document2,
|
|
3375
|
-
...frontmatter != null && { frontmatter },
|
|
3376
|
-
sourceFormat: format?.name ?? null
|
|
3377
|
-
});
|
|
3378
|
-
requireProjectState().selectedPath = path;
|
|
3379
|
-
trackRecentFile({
|
|
3380
|
-
name: path.split("/").pop() || path,
|
|
3381
|
-
path,
|
|
3382
|
-
root: requireProjectState().projectRoot
|
|
3383
|
-
});
|
|
3384
|
-
statusMessage(`Opened ${path.split("/").pop()}`);
|
|
3385
|
-
} catch (error) {
|
|
3386
|
-
statusMessage(`Error: ${errorMessage(error)}`);
|
|
3387
|
-
}
|
|
3388
|
-
}
|
|
3389
|
-
function reloadCleanTab(path) {
|
|
3390
|
-
if (isCollabPath(path)) {
|
|
3391
|
-
return;
|
|
3392
|
-
}
|
|
3393
|
-
for (const [, tab] of workspace.tabs.entries()) {
|
|
3394
|
-
if (tab.documentPath === path && !tab.doc.dirty) {
|
|
3395
|
-
reloadFileInTab(path);
|
|
3396
|
-
return;
|
|
3397
|
-
}
|
|
3398
|
-
}
|
|
3399
|
-
}
|
|
3400
|
-
async function reloadFileInTab(path) {
|
|
3401
|
-
for (const [, tab] of workspace.tabs.entries()) {
|
|
3402
|
-
if (tab.documentPath === path) {
|
|
3403
|
-
const platform = getPlatform();
|
|
3404
|
-
try {
|
|
3405
|
-
const content = await platform.readFile(path);
|
|
3406
|
-
if (!content) {
|
|
3407
|
-
return;
|
|
3408
|
-
}
|
|
3409
|
-
await loadFormats();
|
|
3410
|
-
if (formatForPath(path)) {
|
|
3411
|
-
const { document: document2, frontmatter } = await parseSourceForPath(path, content);
|
|
3412
|
-
tab.doc.document = document2;
|
|
3413
|
-
tab.doc.content.frontmatter = frontmatter;
|
|
3414
|
-
} else if (path.endsWith(".json")) {
|
|
3415
|
-
tab.doc.document = JSON.parse(content);
|
|
3416
|
-
}
|
|
3417
|
-
tab.doc.dirty = false;
|
|
3418
|
-
} catch {}
|
|
3419
|
-
return;
|
|
3420
|
-
}
|
|
3421
|
-
}
|
|
3422
|
-
}
|
|
3423
|
-
|
|
3424
|
-
export { hydrateProjectList, getProjectList, installAutomationHook, ensureDependenciesInstalled, startFsSync, dataSurfaceAvailable, testConnection, pushSchema, deriveSecretEnvName, cellValuesEqual, parseGridTabId, gridTabLabel, ROW_KEY_FIELD, getGridController, createGridController, coerceCellInput, cellToText, createCsvFileSource, parseSourceForPath, saveFile, exportFile, serializeDocument, listCollectionEntryIds, openConnectorGrid, openGridSourcePicker, hydrateRecentProjects, getRecentProjects, addRecentProject, removeRecentProject, clearRecentProjects, getRecentFiles, trackRecentFile, loadDirectory, loadProject, openProject, findHomePage, openHomePage, renderFilesTemplate, setupTreeKeyboard, isFileDrag, uploadFilesToDir, pickAndUploadTo, registerFileTreeDnD, openFileFromTree, openFileInTab, reloadCleanTab, reloadFileInTab };
|
|
3425
|
-
|
|
3426
|
-
//# debugId=F6DA95B972A0DA6E64756E2164756E21
|
|
3427
|
-
//# sourceMappingURL=studio-d77qm3pk.js.map
|