@jxsuite/studio 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/canvas.html +21 -0
- package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
- package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
- package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
- package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
- package/dist/chunks/{components-799jpvhm.js → components-2bpqp4mq.js} +5 -7
- package/dist/chunks/{components-799jpvhm.js.map → components-2bpqp4mq.js.map} +1 -1
- package/dist/chunks/content-source-34gnkbnn.js +38 -0
- package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
- package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
- package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
- package/dist/chunks/extension-sections-6mqydz35.js +301 -0
- package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
- package/dist/chunks/files-8kn321h1.js +70 -0
- package/dist/chunks/files-8kn321h1.js.map +9 -0
- package/dist/chunks/{format-host-8jxwts2p.js → format-host-mkwsmt8d.js} +3 -3
- package/dist/chunks/git-panel-tjbr191d.js +51 -0
- package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
- package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
- package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
- package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
- package/dist/chunks/layers-panel-87pb25km.js +58 -0
- package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
- package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
- package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
- package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
- package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
- package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
- package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
- package/dist/chunks/right-panel-zpdxd98t.js +37 -0
- package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
- package/dist/chunks/section-registry-zmckaxf4.js +30 -0
- package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
- package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
- package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
- package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
- package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
- package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
- package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
- package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
- package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
- package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
- package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
- package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
- package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
- package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
- package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
- package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
- package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
- package/dist/chunks/studio-41exgzhq.js +6690 -0
- package/dist/chunks/studio-41exgzhq.js.map +38 -0
- package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
- package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
- package/dist/chunks/studio-50k77cca.js +1787 -0
- package/dist/chunks/studio-50k77cca.js.map +18 -0
- package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
- package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
- package/dist/chunks/studio-6f0am8f2.js +53 -0
- package/dist/chunks/studio-6f0am8f2.js.map +10 -0
- package/dist/chunks/studio-6nmjc5qk.js +7002 -0
- package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
- package/dist/chunks/studio-7afd21nw.js +67 -0
- package/dist/chunks/studio-7afd21nw.js.map +10 -0
- package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
- package/dist/chunks/studio-7bmdvzne.js.map +10 -0
- package/dist/chunks/studio-8jpzqk5s.js +128 -0
- package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
- package/dist/chunks/studio-94m77tgk.js +1482 -0
- package/dist/chunks/studio-94m77tgk.js.map +13 -0
- package/dist/chunks/studio-9je6w5sq.js +1000 -0
- package/dist/chunks/studio-9je6w5sq.js.map +16 -0
- package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
- package/dist/chunks/{studio-gs6kqdt1.js → studio-adn0z1k3.js} +14 -8
- package/dist/chunks/{studio-gs6kqdt1.js.map → studio-adn0z1k3.js.map} +3 -3
- package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
- package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
- package/dist/chunks/studio-bw5dz6km.js +608 -0
- package/dist/chunks/studio-bw5dz6km.js.map +21 -0
- package/dist/chunks/studio-c6s5getc.js +4232 -0
- package/dist/chunks/studio-c6s5getc.js.map +39 -0
- package/dist/chunks/studio-cjm7fdjf.js +451 -0
- package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
- package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
- package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
- package/dist/chunks/studio-dg7zyt5x.js +28 -0
- package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
- package/dist/chunks/studio-dzcyvvvz.js +166 -0
- package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
- package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
- package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
- package/dist/chunks/studio-f12h6177.js +411 -0
- package/dist/chunks/studio-f12h6177.js.map +13 -0
- package/dist/chunks/studio-hbp1s8m9.js +1817 -0
- package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
- package/dist/chunks/studio-k2sxjph5.js +15295 -0
- package/dist/chunks/studio-k2sxjph5.js.map +235 -0
- package/dist/chunks/studio-myje51t3.js +385 -0
- package/dist/chunks/studio-myje51t3.js.map +12 -0
- package/dist/chunks/studio-na6rwzhw.js +353 -0
- package/dist/chunks/studio-na6rwzhw.js.map +11 -0
- package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
- package/dist/chunks/studio-p471gg5n.js.map +14 -0
- package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
- package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
- package/dist/chunks/{studio-esrewxap.js → studio-s519zc99.js} +3 -3
- package/dist/chunks/studio-sas7mt3k.js +75 -0
- package/dist/chunks/studio-sas7mt3k.js.map +10 -0
- package/dist/chunks/studio-t5wqwzdh.js +74 -0
- package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
- package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
- package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
- package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
- package/dist/chunks/studio-tr20m8g8.js.map +9 -0
- package/dist/chunks/{studio-v7jkjqng.js → studio-v96qfnww.js} +5514 -4291
- package/dist/chunks/studio-v96qfnww.js.map +54 -0
- package/dist/chunks/studio-vcetc91k.js +659 -0
- package/dist/chunks/studio-vcetc91k.js.map +10 -0
- package/dist/chunks/{studio-jm0z592w.js → studio-vmymjefp.js} +2972 -4937
- package/dist/chunks/studio-vmymjefp.js.map +98 -0
- package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
- package/dist/chunks/studio-x9hmy5kr.js +390 -0
- package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
- package/dist/chunks/studio-xa0ce0n7.js +335 -0
- package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
- package/dist/chunks/studio-xm3dbxvn.js +378 -0
- package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
- package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
- package/dist/chunks/studio-zv37yrfa.js +1054 -0
- package/dist/chunks/studio-zv37yrfa.js.map +13 -0
- package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
- package/dist/chunks/ws-client-654ts66j.js.map +11 -0
- package/dist/iframe-entry.js +668 -230
- package/dist/studio.js +21080 -32585
- package/index.html +35 -4685
- package/package.json +17 -15
- package/src/about/about-modal.ts +42 -11
- package/src/browse/library-commands.ts +240 -0
- package/src/browse/library-layouts.ts +369 -0
- package/src/browse/library-model.ts +396 -0
- package/src/browse/library-pane.ts +1049 -0
- package/src/browse/library-preview.ts +252 -0
- package/src/browse/library-source.ts +150 -0
- package/src/canvas/canvas-helpers.ts +33 -9
- package/src/canvas/canvas-live-render.ts +45 -29
- package/src/canvas/canvas-patcher.ts +151 -31
- package/src/canvas/canvas-perf.ts +46 -7
- package/src/canvas/canvas-render.ts +1092 -220
- package/src/canvas/canvas-surface.ts +240 -0
- package/src/canvas/canvas-utils.ts +1166 -124
- package/src/canvas/iframe-drop.ts +2 -1
- package/src/canvas/iframe-entry.ts +241 -4
- package/src/canvas/iframe-host.ts +1226 -143
- package/src/canvas/iframe-inline-edit.ts +31 -0
- package/src/canvas/iframe-interaction.ts +76 -16
- package/src/canvas/iframe-keys.ts +107 -45
- package/src/canvas/iframe-overlay.ts +38 -1
- package/src/canvas/iframe-protocol.ts +137 -2
- package/src/canvas/iframe-render.ts +70 -2
- package/src/canvas/iframe-slash.ts +6 -0
- package/src/canvas/path-mapping.ts +56 -6
- package/src/canvas/surface-registry.ts +292 -0
- package/src/collab/collab-commands.ts +244 -0
- package/src/collab/collab-session.ts +198 -32
- package/src/collab/collab-state.ts +28 -2
- package/src/collab/presence-chips.ts +100 -9
- package/src/commands/active-registry.ts +42 -0
- package/src/commands/app-commands.ts +176 -0
- package/src/commands/budget.ts +158 -0
- package/src/commands/command-args.ts +253 -0
- package/src/commands/context.ts +387 -0
- package/src/commands/defaults.ts +580 -0
- package/src/commands/keymap.ts +571 -0
- package/src/commands/levels.ts +345 -0
- package/src/commands/live-context.ts +193 -0
- package/src/commands/reference.ts +227 -0
- package/src/commands/registry.ts +293 -0
- package/src/component-props.ts +3 -2
- package/src/content/draft-state.ts +106 -0
- package/src/content/entry-commands.ts +282 -0
- package/src/content/entry-editor.ts +327 -0
- package/src/content/entry-fields.ts +116 -0
- package/src/content/entry-model.ts +175 -0
- package/src/editor/canvas-context-menu.ts +17 -11
- package/src/editor/canvas-slash-bridge.ts +11 -4
- package/src/editor/context-menu.ts +804 -204
- package/src/editor/convert-to-component.ts +31 -10
- package/src/editor/convert-to-repeater.ts +3 -2
- package/src/editor/inline-edit-apply.ts +5 -5
- package/src/editor/inline-edit.ts +86 -50
- package/src/editor/insert-zone-action.ts +1 -1
- package/src/editor/shortcuts.ts +1043 -329
- package/src/editor/slash-menu.ts +1 -0
- package/src/files/elements.ts +166 -0
- package/src/files/file-ops.ts +178 -31
- package/src/files/files.ts +898 -268
- package/src/files/fs-events.ts +21 -0
- package/src/files/media-meta.ts +203 -0
- package/src/files/media-paths.ts +141 -0
- package/src/files/media-upload.ts +7 -3
- package/src/files/media-usage.ts +189 -0
- package/src/github/github-auth.ts +235 -33
- package/src/github/github-publish.ts +80 -21
- package/src/grid/grid-controller.ts +152 -12
- package/src/grid/grid-layout.ts +225 -20
- package/src/grid/grid-open.ts +120 -0
- package/src/grid/grid-panel.ts +554 -39
- package/src/grid/grid-source.ts +27 -1
- package/src/grid/redirects-grid.ts +599 -0
- package/src/grid/redirects.ts +398 -0
- package/src/grid/sources/content-source.ts +108 -2
- package/src/i18n/i18n-commands.ts +322 -0
- package/src/new-project/add-repo-modal.ts +8 -10
- package/src/new-project/import-tab.ts +7 -0
- package/src/new-project/location-fields.ts +9 -1
- package/src/new-project/new-project-modal.ts +221 -245
- package/src/packages/jxsuite-update.ts +97 -48
- package/src/packages/pull-package-sync.ts +18 -9
- package/src/page-params.ts +2 -1
- package/src/panels/activity-bar.ts +152 -91
- package/src/panels/activity-panel.ts +481 -0
- package/src/panels/ai-chat/chat-markdown.ts +8 -3
- package/src/panels/ai-chat/chat-view.ts +245 -18
- package/src/panels/ai-chat/composer.ts +31 -5
- package/src/panels/ai-chat/sessions-view.ts +17 -10
- package/src/panels/ai-panel.ts +279 -53
- package/src/panels/block-action-bar.ts +1115 -224
- package/src/panels/bottom-dock.ts +337 -0
- package/src/panels/canvas-dnd-bridge.ts +37 -8
- package/src/panels/chat-panel.ts +29 -12
- package/src/panels/data-explorer.ts +254 -112
- package/src/panels/data-grid.ts +10 -4
- package/src/panels/dnd.ts +28 -51
- package/src/panels/editors.ts +434 -107
- package/src/panels/elements-panel.ts +66 -2
- package/src/panels/empty-state.ts +112 -0
- package/src/panels/events-panel.ts +596 -82
- package/src/panels/formula-workspace.ts +408 -218
- package/src/panels/frontmatter-fields.ts +51 -10
- package/src/panels/frontmatter-panel.ts +357 -79
- package/src/panels/git-panel.ts +375 -164
- package/src/panels/head-panel.ts +791 -85
- package/src/panels/i18n-panel.ts +592 -0
- package/src/panels/imports-panel.ts +95 -85
- package/src/panels/jump-bar.ts +584 -0
- package/src/panels/layers-panel.ts +1106 -328
- package/src/panels/left-panel.ts +109 -204
- package/src/panels/navigator-panels.ts +131 -0
- package/src/panels/overlays.ts +7 -2
- package/src/panels/pane-context.ts +1477 -0
- package/src/panels/pane-grid.ts +406 -0
- package/src/panels/panel-registry.ts +340 -0
- package/src/panels/panel-scheduler.ts +75 -0
- package/src/panels/problems-panel.ts +234 -0
- package/src/panels/properties-panel.ts +946 -1139
- package/src/panels/provenance.ts +258 -0
- package/src/panels/quick-search.ts +791 -122
- package/src/panels/right-panel.ts +228 -102
- package/src/panels/seo-modal.ts +395 -0
- package/src/panels/settings-pane.ts +203 -0
- package/src/panels/signals-panel.ts +354 -137
- package/src/panels/statement-editor.ts +41 -25
- package/src/panels/statusbar.ts +342 -106
- package/src/panels/style-panel.ts +974 -463
- package/src/panels/style-utils.ts +3 -3
- package/src/panels/stylebook-doc.ts +4 -2
- package/src/panels/stylebook-layers-panel.ts +3 -2
- package/src/panels/stylebook-panel.ts +51 -26
- package/src/panels/tab-strip.ts +1058 -78
- package/src/panels/target-line.ts +274 -0
- package/src/panels/toolbar.ts +631 -484
- package/src/panels/welcome-screen.ts +301 -198
- package/src/platform-errors.ts +26 -3
- package/src/platform.ts +96 -2
- package/src/platforms/cloud.ts +71 -8
- package/src/platforms/devserver.ts +92 -29
- package/src/publish/deploy-checklist.ts +383 -0
- package/src/publish/pages-service.ts +10 -1
- package/src/publish/publish-commands.ts +218 -0
- package/src/publish/publish-panel.ts +110 -16
- package/src/recent-projects.ts +3 -2
- package/src/services/a11y-report.ts +505 -0
- package/src/services/ai-models.ts +30 -1
- package/src/services/ai-project-tools.ts +29 -10
- package/src/services/ai-system-prompt.ts +53 -14
- package/src/services/ai-tools.ts +39 -3
- package/src/services/ai-writes.ts +154 -0
- package/src/services/announce.ts +97 -0
- package/src/services/automation.ts +424 -175
- package/src/services/cem-export.ts +9 -4
- package/src/services/clock.ts +61 -0
- package/src/services/document-assistant.ts +36 -8
- package/src/services/idle.ts +179 -0
- package/src/services/import-client.ts +26 -0
- package/src/services/monaco-buffer.ts +627 -0
- package/src/services/monaco-lazy.ts +36 -0
- package/src/services/notify.ts +271 -0
- package/src/services/profile.ts +155 -0
- package/src/services/references.ts +0 -0
- package/src/services/token-lint.ts +3 -2
- package/src/services/tool-executor.ts +53 -7
- package/src/services/trusted-types.ts +123 -0
- package/src/settings/contexts-section.ts +427 -0
- package/src/settings/contributed-section.ts +149 -11
- package/src/settings/css-vars-editor.ts +306 -349
- package/src/settings/defs-editor.ts +93 -34
- package/src/settings/dependencies-editor.ts +7 -7
- package/src/settings/extension-sections.ts +37 -5
- package/src/settings/general-settings.ts +204 -121
- package/src/settings/locales-section.ts +347 -0
- package/src/settings/preferences-accounts.ts +140 -0
- package/src/settings/preferences-dialog.ts +620 -0
- package/src/settings/preferences-keymap.ts +270 -0
- package/src/settings/project-sections.ts +260 -0
- package/src/settings/section-registry.ts +169 -0
- package/src/settings/settings-document.ts +385 -0
- package/src/shell.ts +1549 -0
- package/src/site-context.ts +45 -20
- package/src/state.ts +14 -275
- package/src/store.ts +88 -43
- package/src/studio.ts +790 -346
- package/src/style/live-preview.ts +36 -0
- package/src/style/project-styles.ts +349 -0
- package/src/style/token-ref.ts +123 -0
- package/src/tabs/patch-ops.ts +16 -4
- package/src/tabs/project-config.ts +416 -0
- package/src/tabs/selection.ts +323 -0
- package/src/tabs/tab.ts +182 -47
- package/src/tabs/transact.ts +209 -28
- package/src/types.ts +68 -13
- package/src/ui/ai-credentials-form.ts +39 -35
- package/src/ui/ai-managed-connect.ts +7 -1
- package/src/ui/dynamic-slot.ts +224 -81
- package/src/ui/expression-editor.ts +71 -63
- package/src/ui/field-input.ts +4 -6
- package/src/ui/field-row.ts +215 -38
- package/src/ui/form-controls.ts +194 -7
- package/src/ui/layers.ts +492 -69
- package/src/ui/media-picker.ts +115 -25
- package/src/ui/panel-resize.ts +109 -91
- package/src/ui/progress-modal.ts +131 -50
- package/src/ui/regions.ts +462 -0
- package/src/ui/schema-form.ts +229 -45
- package/src/ui/spectrum.ts +24 -8
- package/src/ui/timing.ts +49 -0
- package/src/ui/value-selector.ts +20 -0
- package/src/ui/value-source.ts +348 -0
- package/src/ui/virtual-window.ts +400 -0
- package/src/utils/edit-display.ts +3 -2
- package/src/utils/inherited-style.ts +67 -37
- package/src/view.ts +64 -78
- package/src/workspace/pane-derive.ts +1258 -0
- package/src/workspace/session.ts +317 -0
- package/src/workspace/workspace.ts +1345 -23
- package/styles/canvas.css +653 -0
- package/styles/forced-colors.css +106 -0
- package/styles/inspector.css +1454 -0
- package/styles/overlays.css +2704 -0
- package/styles/panels.css +1701 -0
- package/styles/shell.css +1423 -0
- package/styles/tokens.css +151 -0
- package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
- package/dist/chunks/extension-sections-qr3drkkw.js +0 -531
- package/dist/chunks/extension-sections-qr3drkkw.js.map +0 -12
- package/dist/chunks/files-j8a1t06k.js +0 -50
- package/dist/chunks/files-j8a1t06k.js.map +0 -9
- package/dist/chunks/git-panel-xtq5z81x.js +0 -28
- package/dist/chunks/git-panel-xtq5z81x.js.map +0 -9
- package/dist/chunks/layers-panel-ancn2wpq.js +0 -26
- package/dist/chunks/layers-panel-ancn2wpq.js.map +0 -9
- package/dist/chunks/settings-modal-17ttr96g.js +0 -29
- package/dist/chunks/settings-modal-17ttr96g.js.map +0 -9
- package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
- package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
- package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
- package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
- package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
- package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
- package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
- package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
- package/dist/chunks/studio-0xgs7xrs.js +0 -429
- package/dist/chunks/studio-0xgs7xrs.js.map +0 -13
- package/dist/chunks/studio-4d8hzcz5.js +0 -3427
- package/dist/chunks/studio-4d8hzcz5.js.map +0 -26
- package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
- package/dist/chunks/studio-9smfzqhc.js +0 -19
- package/dist/chunks/studio-9smfzqhc.js.map +0 -10
- package/dist/chunks/studio-jm0z592w.js.map +0 -115
- package/dist/chunks/studio-m2t4mdg5.js +0 -1854
- package/dist/chunks/studio-m2t4mdg5.js.map +0 -18
- package/dist/chunks/studio-n4p11dfy.js +0 -853
- package/dist/chunks/studio-n4p11dfy.js.map +0 -12
- package/dist/chunks/studio-qkvt37c0.js.map +0 -9
- package/dist/chunks/studio-sjh1wt1a.js +0 -3744
- package/dist/chunks/studio-sjh1wt1a.js.map +0 -22
- package/dist/chunks/studio-v7jkjqng.js.map +0 -46
- package/dist/chunks/studio-xq0t7n5t.js +0 -140
- package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
- package/dist/chunks/studio-xze4zrs1.js.map +0 -10
- package/dist/chunks/studio-yfcstdzy.js +0 -399
- package/dist/chunks/studio-yfcstdzy.js.map +0 -12
- package/dist/chunks/studio-zp7jncs8.js +0 -469
- package/dist/chunks/studio-zp7jncs8.js.map +0 -14
- package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
- package/src/browse/browse-modal.ts +0 -69
- package/src/browse/browse.ts +0 -904
- package/src/canvas/canvas-diff.ts +0 -216
- package/src/canvas/nested-site-style.ts +0 -58
- package/src/new-project/design-fields.ts +0 -264
- package/src/new-project/templates.ts +0 -61
- package/src/panels/tab-bar.ts +0 -548
- package/src/settings/settings-modal.ts +0 -203
- /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
- /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
- /package/dist/chunks/{format-host-8jxwts2p.js.map → format-host-mkwsmt8d.js.map} +0 -0
- /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
- /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
- /package/dist/chunks/{studio-esrewxap.js.map → studio-s519zc99.js.map} +0 -0
- /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
- /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
package/src/editor/slash-menu.ts
CHANGED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Elements.ts — what belongs in a document's (or a project's) `$elements`, decided once.
|
|
3
|
+
*
|
|
4
|
+
* `$elements` is the list of component modules a document may use. Three surfaces wrote it, each
|
|
5
|
+
* with its own idea of what "already imported" means, and plan §11.2 names all three: the Packages
|
|
6
|
+
* panel's cherry-pick checkboxes, its "Add component…" picker, and the automatic push when a
|
|
7
|
+
* component is dropped on the canvas. "One `enableElement()` / `disableElement()` service behind
|
|
8
|
+
* all three. Today the same array is written three ways with no shared UI and no shared
|
|
9
|
+
* validation."
|
|
10
|
+
*
|
|
11
|
+
* They really did disagree. The canvas drop matched a local component by `ref.endsWith(basename)`,
|
|
12
|
+
* so `./components/card.json` counted `./vendor/card.json` as already imported and the drop
|
|
13
|
+
* silently produced an element the page could not resolve. The picker checked nothing at all and
|
|
14
|
+
* pushed a duplicate `$ref` every time you chose the same component twice. Only the checkbox knew
|
|
15
|
+
* that a legacy full-package entry (`@acme/ui`) satisfies a cherry-picked one (`@acme/ui/card`), so
|
|
16
|
+
* enabling one component through the picker left the package-wide import beside it.
|
|
17
|
+
*
|
|
18
|
+
* **Pure functions over the array.** They return the new list rather than writing it, because the
|
|
19
|
+
* two levels persist differently and genuinely should: a document's `$elements` goes through
|
|
20
|
+
* `transact` and is undoable, and the project's goes through `updateSiteConfig` and is not. What
|
|
21
|
+
* they must not differ on is WHICH entries the list should hold.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { computeRelativePath } from "./components";
|
|
25
|
+
import type { ComponentEntry } from "./components";
|
|
26
|
+
import type { JxElement } from "@jxsuite/schema/types";
|
|
27
|
+
|
|
28
|
+
/** One `$elements` entry: an npm specifier, or a `$ref` to a file in this project. */
|
|
29
|
+
export type ElementsEntry = string | JxElement | { $ref: string };
|
|
30
|
+
|
|
31
|
+
/** The `$ref` of an entry, when it has one. */
|
|
32
|
+
function refOf(entry: ElementsEntry): string | null {
|
|
33
|
+
return typeof entry === "object" && entry !== null && typeof entry.$ref === "string"
|
|
34
|
+
? entry.$ref
|
|
35
|
+
: null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The npm specifier a component is imported by — its cherry-picked subpath, or the bare package.
|
|
40
|
+
*
|
|
41
|
+
* `null` for a project-local component, which is imported by path instead.
|
|
42
|
+
*/
|
|
43
|
+
export function npmSpecifier(comp: ComponentEntry): string | null {
|
|
44
|
+
if (comp.source !== "npm" || !comp.package) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return comp.modulePath ? `${comp.package}/${comp.modulePath}` : comp.package;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The entry that imports `comp` into a document at `fromPath`.
|
|
52
|
+
*
|
|
53
|
+
* `null` when the component can be named neither way — an npm entry with no package, or a local one
|
|
54
|
+
* with no path — which is a registry the caller should not be writing from.
|
|
55
|
+
*/
|
|
56
|
+
export function elementsEntryFor(
|
|
57
|
+
comp: ComponentEntry,
|
|
58
|
+
fromPath: string | null,
|
|
59
|
+
): ElementsEntry | null {
|
|
60
|
+
const specifier = npmSpecifier(comp);
|
|
61
|
+
if (specifier) {
|
|
62
|
+
return specifier;
|
|
63
|
+
}
|
|
64
|
+
return comp.path ? { $ref: computeRelativePath(fromPath, comp.path) } : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Is `comp` already imported by this list?
|
|
69
|
+
*
|
|
70
|
+
* ONE rule, and it is the union of what the three call sites each knew separately:
|
|
71
|
+
*
|
|
72
|
+
* - An npm component matches its cherry-picked specifier **or** a legacy whole-package entry;
|
|
73
|
+
* - A local component matches a `$ref` that resolves to the same file, compared on the resolved path
|
|
74
|
+
* rather than on its last segment — `endsWith("card.json")` called two different cards the same
|
|
75
|
+
* component, and the drop that trusted it produced an unresolvable element.
|
|
76
|
+
*/
|
|
77
|
+
export function hasElement(
|
|
78
|
+
elements: readonly ElementsEntry[],
|
|
79
|
+
comp: ComponentEntry,
|
|
80
|
+
fromPath: string | null,
|
|
81
|
+
): boolean {
|
|
82
|
+
const specifier = npmSpecifier(comp);
|
|
83
|
+
if (specifier) {
|
|
84
|
+
return elements.some(
|
|
85
|
+
(entry) => typeof entry === "string" && (entry === specifier || entry === comp.package),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (!comp.path) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const wanted = normalizeRef(computeRelativePath(fromPath, comp.path));
|
|
92
|
+
return elements.some((entry) => {
|
|
93
|
+
const ref = refOf(entry);
|
|
94
|
+
return ref !== null && normalizeRef(ref) === wanted;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** A `$ref` reduced to a comparable form: `./a/b.json`, `a/b.json` and `././a/b.json` are one file. */
|
|
99
|
+
function normalizeRef(ref: string): string {
|
|
100
|
+
return ref.replace(/^(?:\.\/)+/, "");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Import `comp`, and return the new list. Idempotent, and it drops what the new entry supersedes.
|
|
105
|
+
*
|
|
106
|
+
* Enabling a cherry-picked npm component removes the whole-package entry it replaces — otherwise
|
|
107
|
+
* the page keeps pulling in every component of that package while the panel shows one ticked.
|
|
108
|
+
*/
|
|
109
|
+
export function enableElement(
|
|
110
|
+
elements: readonly ElementsEntry[],
|
|
111
|
+
comp: ComponentEntry,
|
|
112
|
+
fromPath: string | null,
|
|
113
|
+
): ElementsEntry[] {
|
|
114
|
+
const entry = elementsEntryFor(comp, fromPath);
|
|
115
|
+
if (!entry) {
|
|
116
|
+
return [...elements];
|
|
117
|
+
}
|
|
118
|
+
const specifier = npmSpecifier(comp);
|
|
119
|
+
// The legacy whole-package import goes when a cherry-picked one arrives — but never when the
|
|
120
|
+
// Component IS the package (`modulePath` absent), where the two are the same entry.
|
|
121
|
+
const kept =
|
|
122
|
+
specifier && comp.modulePath ? elements.filter((e) => e !== comp.package) : [...elements];
|
|
123
|
+
return hasElement(kept, comp, fromPath) ? kept : [...kept, entry];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Remove `comp`'s import, and return the new list. Idempotent. */
|
|
127
|
+
export function disableElement(
|
|
128
|
+
elements: readonly ElementsEntry[],
|
|
129
|
+
comp: ComponentEntry,
|
|
130
|
+
fromPath: string | null,
|
|
131
|
+
): ElementsEntry[] {
|
|
132
|
+
const specifier = npmSpecifier(comp);
|
|
133
|
+
if (specifier) {
|
|
134
|
+
// Both spellings: a component ticked off must not stay imported through the package entry.
|
|
135
|
+
return elements.filter(
|
|
136
|
+
(entry) => !(typeof entry === "string" && (entry === specifier || entry === comp.package)),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
if (!comp.path) {
|
|
140
|
+
return [...elements];
|
|
141
|
+
}
|
|
142
|
+
const wanted = normalizeRef(computeRelativePath(fromPath, comp.path));
|
|
143
|
+
return elements.filter((entry) => {
|
|
144
|
+
const ref = refOf(entry);
|
|
145
|
+
return ref === null || normalizeRef(ref) !== wanted;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Remove every entry belonging to `pkg` — what uninstalling a package leaves behind. */
|
|
150
|
+
export function removePackageElements(
|
|
151
|
+
elements: readonly ElementsEntry[],
|
|
152
|
+
pkg: string,
|
|
153
|
+
): ElementsEntry[] {
|
|
154
|
+
return elements.filter(
|
|
155
|
+
(entry) => typeof entry !== "string" || !(entry === pkg || entry.startsWith(`${pkg}/`)),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Remove the entry naming `ref` exactly — the Packages panel's per-row × on a local import. */
|
|
160
|
+
export function removeElementRef(elements: readonly ElementsEntry[], ref: string): ElementsEntry[] {
|
|
161
|
+
const wanted = normalizeRef(ref);
|
|
162
|
+
return elements.filter((entry) => {
|
|
163
|
+
const found = refOf(entry);
|
|
164
|
+
return found === null || normalizeRef(found) !== wanted;
|
|
165
|
+
});
|
|
166
|
+
}
|
package/src/files/file-ops.ts
CHANGED
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
|
-
* File Operations — open, save, export documents.
|
|
3
|
+
* File Operations — open, save, export documents, and confirm the destructive ones.
|
|
4
4
|
*
|
|
5
5
|
* All functions read/write directly from/to `activeTab.value` (the reactive tab). `.json` is the
|
|
6
6
|
* native document format; every other extension dispatches through the project's format registry
|
|
7
7
|
* (parse to open, serialize to save).
|
|
8
|
+
*
|
|
9
|
+
* {@link confirmFileDelete} and {@link renamePromptMessage} live here because a delete and a rename
|
|
10
|
+
* are the same question asked twice — "what does this break?" — and the answer was missing from
|
|
11
|
+
* both. Every surface that removes or moves a file routes its confirmation through them, so no
|
|
12
|
+
* caller can ship a destructive dialog that grades only by reversibility again.
|
|
13
|
+
*
|
|
14
|
+
* @docs studio/projects/pages-layouts-components
|
|
8
15
|
*/
|
|
9
16
|
|
|
17
|
+
import { html, nothing } from "lit-html";
|
|
18
|
+
import { loadUsages, usageWarning } from "../services/references";
|
|
19
|
+
import { isMediaFile } from "./media-upload";
|
|
20
|
+
import { loadMediaUsages } from "./media-usage";
|
|
21
|
+
import { showConfirmDialog } from "../ui/layers";
|
|
10
22
|
import { locateDocument } from "../services/code-services";
|
|
11
23
|
import { errorMessage } from "@jxsuite/schema/parse";
|
|
12
|
-
import {
|
|
24
|
+
import { noteDocumentSaved } from "../panels/statusbar";
|
|
25
|
+
import { notify } from "../services/notify";
|
|
13
26
|
import { validateComponentSlots } from "../services/cem-export";
|
|
14
27
|
import { getPlatform } from "../platform";
|
|
15
28
|
import { getGridController } from "../grid/grid-controller";
|
|
16
29
|
import { activeTab, openTab } from "../workspace/workspace";
|
|
17
|
-
import { collabSave } from "../collab/collab-session";
|
|
30
|
+
import { collabReadOnly, collabSave } from "../collab/collab-session";
|
|
18
31
|
import { flushCanvasEdits } from "../canvas/iframe-host";
|
|
19
32
|
import {
|
|
20
33
|
defaultContentFormat,
|
|
@@ -29,6 +42,7 @@ import {
|
|
|
29
42
|
} from "../format/format-host";
|
|
30
43
|
import type { StudioFormat } from "../format/format-host";
|
|
31
44
|
import type { Tab } from "../tabs/tab.js";
|
|
45
|
+
import { mediaTypeEssence } from "@jxsuite/schema/media-type";
|
|
32
46
|
|
|
33
47
|
/**
|
|
34
48
|
* Parse a format-class source string into document + frontmatter + mode per the format's
|
|
@@ -73,7 +87,9 @@ export async function openFile() {
|
|
|
73
87
|
...formats
|
|
74
88
|
.filter((f) => f.capabilities.parse)
|
|
75
89
|
.map((f) => ({
|
|
76
|
-
|
|
90
|
+
// The essence, not the declared type: a File System Access `accept` key is a bare
|
|
91
|
+
// MIME type, and `text/markdown; variant=GFM` is not one.
|
|
92
|
+
accept: { [mediaTypeEssence(f.mediaType) ?? "text/plain"]: f.extensions },
|
|
77
93
|
description: f.name,
|
|
78
94
|
})),
|
|
79
95
|
];
|
|
@@ -102,7 +118,8 @@ export async function openFile() {
|
|
|
102
118
|
} else {
|
|
103
119
|
throw noFormatError(name);
|
|
104
120
|
}
|
|
105
|
-
|
|
121
|
+
// Opening a file is stated permanently by the tab strip and the status bar's DOCUMENT
|
|
122
|
+
// Field; it does not need a message that erases itself.
|
|
106
123
|
};
|
|
107
124
|
|
|
108
125
|
if ("showOpenFilePicker" in window) {
|
|
@@ -128,37 +145,67 @@ export async function openFile() {
|
|
|
128
145
|
input.click();
|
|
129
146
|
}
|
|
130
147
|
} catch (error) {
|
|
148
|
+
// A cancelled file picker is not a failure — the user withdrew the request.
|
|
131
149
|
if (!(error instanceof Error && error.name === "AbortError")) {
|
|
132
|
-
|
|
150
|
+
notify.error("Could not open the file.", {
|
|
151
|
+
detail: errorMessage(error),
|
|
152
|
+
source: "Open File",
|
|
153
|
+
});
|
|
133
154
|
}
|
|
134
155
|
}
|
|
135
156
|
}
|
|
136
157
|
|
|
137
158
|
/**
|
|
138
|
-
*
|
|
139
|
-
* still proceeds — the warning replaces the plain success message.
|
|
159
|
+
* Record a successful write, and raise the slot-validation warning component documents can carry.
|
|
140
160
|
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
161
|
+
* The success half no longer notifies at ALL. "Saved" is ambient state: the status bar's DOCUMENT
|
|
162
|
+
* field says "Saved 2m ago" for as long as it is true, which is strictly more information than a
|
|
163
|
+
* message that said it once and erased itself — and it is the field a reader looks at to ask the
|
|
164
|
+
* question in the first place.
|
|
165
|
+
*
|
|
166
|
+
* The warning half became a PROBLEM. A component whose slots do not line up is a thing to fix, and
|
|
167
|
+
* it was previously shown for six seconds in the same grey as the word "Saved".
|
|
143
168
|
*/
|
|
144
|
-
function
|
|
169
|
+
function reportSaved(tab: Tab) {
|
|
170
|
+
noteDocumentSaved(tab.documentPath);
|
|
145
171
|
const doc = tab.doc.document;
|
|
146
172
|
const warning =
|
|
147
173
|
typeof doc.tagName === "string" && doc.tagName.includes("-")
|
|
148
174
|
? validateComponentSlots(doc)
|
|
149
175
|
: null;
|
|
150
176
|
if (warning) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
177
|
+
notify.warn(warning, {
|
|
178
|
+
key: `slots:${tab.documentPath ?? tab.id}`,
|
|
179
|
+
...(tab.documentPath === null ? {} : { path: tab.documentPath }),
|
|
180
|
+
source: "Components",
|
|
181
|
+
tier: "problem",
|
|
182
|
+
});
|
|
154
183
|
}
|
|
155
184
|
}
|
|
156
185
|
|
|
157
|
-
/**
|
|
158
|
-
|
|
159
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Save a document back to its source location. Defaults to the focused tab.
|
|
188
|
+
*
|
|
189
|
+
* Two things here are for the tab strip's Save-on-close, and both are what a caller that must
|
|
190
|
+
* decide something afterwards needs:
|
|
191
|
+
*
|
|
192
|
+
* - `tab` is a PARAMETER. The × on a tab closes that tab, focused or not, and reading
|
|
193
|
+
* `activeTab.value` would have saved a different document than the one being closed.
|
|
194
|
+
* - The return value says whether the work reached disk. Every failure in here is reported to the
|
|
195
|
+
* user and swallowed, which is right for ⌘S and useless to a caller that would otherwise close
|
|
196
|
+
* the tab on top of the failure.
|
|
197
|
+
*
|
|
198
|
+
* **A read-only collaborator has no save target, and that is a refusal rather than a fallback.**
|
|
199
|
+
* Their edits never reached the Y-doc, so there is nothing for the provider to flush; and the local
|
|
200
|
+
* file is the ROOM's file, so writing it here would fork the shared document behind the owner's
|
|
201
|
+
* back. Both "saved" and "written anyway" are wrong answers, so the answer is `false` with the
|
|
202
|
+
* reason said out loud.
|
|
203
|
+
*
|
|
204
|
+
* @returns Whether the document was persisted.
|
|
205
|
+
*/
|
|
206
|
+
export async function saveFile(tab: Tab | null = activeTab.value): Promise<boolean> {
|
|
160
207
|
if (!tab) {
|
|
161
|
-
return;
|
|
208
|
+
return false;
|
|
162
209
|
}
|
|
163
210
|
// Text reaches the document on an idle tick, so the words still sitting in the caret's block have
|
|
164
211
|
// To be committed before anything serializes it. This used to be `if (isEditing()) stopEditing()`,
|
|
@@ -166,17 +213,40 @@ export async function saveFile() {
|
|
|
166
213
|
// Realm's copy of that module, where a session never starts. Saving mid-sentence silently wrote
|
|
167
214
|
// The file without the sentence.
|
|
168
215
|
await flushCanvasEdits(tab.id);
|
|
216
|
+
/* THE REFUSAL COMES FIRST, and it used to come second.
|
|
217
|
+
`ensureCollab` attaches a session to every tab with a `documentPath` except `project.json` —
|
|
218
|
+
`.csv` included, and a `.csv` tab is exactly the kind `grid-panel.ts` provisions a controller
|
|
219
|
+
for. So a read-only collaborator on a co-edited sheet took the grid branch above this line,
|
|
220
|
+
`grid.save()` wrote the ROOM's file to disk behind the owner's back, and `!tab.doc.dirty`
|
|
221
|
+
reported it as a save — the precise outcome the paragraph above says this function prevents.
|
|
222
|
+
A refusal that any earlier branch can step in front of is not a refusal, so nothing may
|
|
223
|
+
precede it except `flushCanvasEdits`, which commits the caret's block and writes nothing. */
|
|
224
|
+
if (collabReadOnly(tab)) {
|
|
225
|
+
notify.warn(
|
|
226
|
+
`You have read access to this session — changes to ${tab.documentPath ?? "this document"} ` +
|
|
227
|
+
`stay in your browser and cannot be saved.`,
|
|
228
|
+
{
|
|
229
|
+
action: "file.save",
|
|
230
|
+
key: `save.readOnly:${tab.documentPath ?? tab.id}`,
|
|
231
|
+
...(tab.documentPath === null ? {} : { path: tab.documentPath }),
|
|
232
|
+
source: "Collaboration",
|
|
233
|
+
},
|
|
234
|
+
);
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
169
237
|
// Grid tabs batch-save through their controller (per-source commit semantics, not a doc write).
|
|
170
238
|
const grid = getGridController(tab);
|
|
171
239
|
if (grid) {
|
|
172
240
|
await grid.save();
|
|
173
|
-
|
|
241
|
+
// A batch commit keeps failed rows dirty and mirrors that onto the tab, so the buffer's own
|
|
242
|
+
// Verdict is the answer — there is nothing more honest to report here.
|
|
243
|
+
return !tab.doc.dirty;
|
|
174
244
|
}
|
|
175
245
|
try {
|
|
176
246
|
// A co-edited tab persists through its provider (a direct file write would reset the room).
|
|
177
247
|
if (await collabSave(tab)) {
|
|
178
|
-
|
|
179
|
-
return;
|
|
248
|
+
reportSaved(tab);
|
|
249
|
+
return true;
|
|
180
250
|
}
|
|
181
251
|
const output = await serializeDocument(tab);
|
|
182
252
|
|
|
@@ -184,8 +254,10 @@ export async function saveFile() {
|
|
|
184
254
|
const platform = getPlatform();
|
|
185
255
|
await platform.writeFile(tab.documentPath, output);
|
|
186
256
|
tab.doc.dirty = false;
|
|
187
|
-
|
|
188
|
-
|
|
257
|
+
reportSaved(tab);
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
if (tab.fileHandle && "createWritable" in tab.fileHandle) {
|
|
189
261
|
const writable =
|
|
190
262
|
await /**
|
|
191
263
|
* @type {{
|
|
@@ -198,15 +270,22 @@ export async function saveFile() {
|
|
|
198
270
|
await writable.write(output);
|
|
199
271
|
await writable.close();
|
|
200
272
|
tab.doc.dirty = false;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
statusMessage("No save target — use Export");
|
|
273
|
+
reportSaved(tab);
|
|
274
|
+
return true;
|
|
204
275
|
}
|
|
276
|
+
notify.warn("This document has no save target — use Export.", { key: "save.noTarget" });
|
|
205
277
|
} catch (error) {
|
|
206
278
|
if (!(error instanceof Error && error.name === "AbortError")) {
|
|
207
|
-
|
|
279
|
+
notify.error(`Could not save ${tab.documentPath ?? tab.id}.`, {
|
|
280
|
+
action: "file.save",
|
|
281
|
+
detail: errorMessage(error),
|
|
282
|
+
key: `save:${tab.documentPath ?? tab.id}`,
|
|
283
|
+
...(tab.documentPath === null ? {} : { path: tab.documentPath }),
|
|
284
|
+
source: "Save",
|
|
285
|
+
});
|
|
208
286
|
}
|
|
209
287
|
}
|
|
288
|
+
return false;
|
|
210
289
|
}
|
|
211
290
|
|
|
212
291
|
/** The output format for a tab: its source format, or the default content format. */
|
|
@@ -227,7 +306,9 @@ export async function exportFile() {
|
|
|
227
306
|
await loadFormats();
|
|
228
307
|
const format = tabFormat(tab);
|
|
229
308
|
const output = await serializeDocument(tab);
|
|
230
|
-
const mimeType = format
|
|
309
|
+
const mimeType = format
|
|
310
|
+
? (mediaTypeEssence(format.mediaType) ?? "text/plain")
|
|
311
|
+
: "application/json";
|
|
231
312
|
const ext = format ? format.extensions[0] : ".json";
|
|
232
313
|
const description = format ? format.name : "Jx Component";
|
|
233
314
|
const fallbackName = format ? `content${ext}` : "component.json";
|
|
@@ -246,7 +327,8 @@ export async function exportFile() {
|
|
|
246
327
|
await writable.write(output);
|
|
247
328
|
await writable.close();
|
|
248
329
|
tab.doc.dirty = false;
|
|
249
|
-
|
|
330
|
+
reportSaved(tab);
|
|
331
|
+
notify.success(`Exported as ${handle.name}.`);
|
|
250
332
|
} else {
|
|
251
333
|
// Fallback: download
|
|
252
334
|
const blob = new Blob([output], { type: mimeType });
|
|
@@ -257,11 +339,15 @@ export async function exportFile() {
|
|
|
257
339
|
a.click();
|
|
258
340
|
URL.revokeObjectURL(url);
|
|
259
341
|
tab.doc.dirty = false;
|
|
260
|
-
|
|
342
|
+
reportSaved(tab);
|
|
343
|
+
notify.success(`Downloaded ${fallbackName}.`);
|
|
261
344
|
}
|
|
262
345
|
} catch (error) {
|
|
263
346
|
if (!(error instanceof Error && error.name === "AbortError")) {
|
|
264
|
-
|
|
347
|
+
notify.error("Could not export the document.", {
|
|
348
|
+
detail: errorMessage(error),
|
|
349
|
+
source: "Export",
|
|
350
|
+
});
|
|
265
351
|
}
|
|
266
352
|
}
|
|
267
353
|
}
|
|
@@ -303,3 +389,64 @@ export async function serializeDocument(tab: Tab): Promise<string> {
|
|
|
303
389
|
}
|
|
304
390
|
return JSON.stringify(tab.doc.document, null, 2);
|
|
305
391
|
}
|
|
392
|
+
|
|
393
|
+
// ─── Destructive confirmations ───────────────────────────────────────────────
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* The reference sentence a destructive dialog carries, or `nothing` when the host cannot count.
|
|
397
|
+
*
|
|
398
|
+
* The query is awaited BEFORE the dialog opens rather than rendered into it and filled in later: a
|
|
399
|
+
* confirm button that becomes truthful two frames after the user has already pressed it is the same
|
|
400
|
+
* defect as not counting at all.
|
|
401
|
+
*
|
|
402
|
+
* **A media file is asked about through the media index, and that is not a refinement.** The
|
|
403
|
+
* generic sweep compares each authored reference's RESOLVED path to the path it was asked about,
|
|
404
|
+
* and a media file's authored ref usually resolves somewhere else: `public/hero.jpg` is written
|
|
405
|
+
* `/hero.jpg` and resolves to `hero.jpg`; an asset inside a collection whose `source` is not
|
|
406
|
+
* already `content/<type>/` is addressed at its asset mount and resolves under that prefix. Asking
|
|
407
|
+
* the generic index about the file on disk returns a confident zero for both, which is the one
|
|
408
|
+
* answer a delete dialog must never invent. `media-usage.ts` asks every authored form and unions
|
|
409
|
+
* the answers — and reports **unknown** when a lane cannot be counted, rather than totalling the
|
|
410
|
+
* lanes that could.
|
|
411
|
+
*
|
|
412
|
+
* @param path — the file about to be deleted or renamed.
|
|
413
|
+
* @param verb — which way the references go. A rename repairs them; a delete breaks them.
|
|
414
|
+
*/
|
|
415
|
+
async function usageLine(path: string, verb: "delete" | "rename") {
|
|
416
|
+
const state = isMediaFile(path) ? await loadMediaUsages(path) : await loadUsages({ path });
|
|
417
|
+
const sentence = usageWarning(state, verb);
|
|
418
|
+
return sentence === null ? nothing : html`<p class="dialog-consequence">${sentence}</p>`;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Confirm deleting a file, stating what it breaks and what survives.
|
|
423
|
+
*
|
|
424
|
+
* @param file — its display name and project-relative path.
|
|
425
|
+
* @returns Whether the user confirmed.
|
|
426
|
+
*/
|
|
427
|
+
export async function confirmFileDelete(file: { name: string; path: string }): Promise<boolean> {
|
|
428
|
+
const consequence = await usageLine(file.path, "delete");
|
|
429
|
+
// `showDialog`'s generic widens to unknown through the confirm wrapper; the dialog only ever
|
|
430
|
+
// Resolves true/false, and Boolean() is the narrowing that says so without a cast.
|
|
431
|
+
return Boolean(
|
|
432
|
+
await showConfirmDialog(
|
|
433
|
+
"Delete File",
|
|
434
|
+
html`<span>Delete <strong>${file.name}</strong>? This cannot be undone.</span>${consequence}`,
|
|
435
|
+
{ confirmLabel: "Delete", destructive: true },
|
|
436
|
+
),
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* The explanatory copy above a rename field — where the file's references go when it moves.
|
|
442
|
+
*
|
|
443
|
+
* A rename is not a delete and must not read like one: the refactor pass rewrites every reference
|
|
444
|
+
* it finds, so the honest sentence is "N references will be updated automatically", and the count
|
|
445
|
+
* is there to say how much work is silently being done on the user's behalf.
|
|
446
|
+
*
|
|
447
|
+
* @param path — the file about to be renamed.
|
|
448
|
+
*/
|
|
449
|
+
export async function renamePromptMessage(path: string) {
|
|
450
|
+
const consequence = await usageLine(path, "rename");
|
|
451
|
+
return consequence === nothing ? undefined : html`${consequence}`;
|
|
452
|
+
}
|