@jxsuite/studio 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/canvas.html +21 -0
- package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
- package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
- package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
- package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
- package/dist/chunks/{components-799jpvhm.js → components-2bpqp4mq.js} +5 -7
- package/dist/chunks/{components-799jpvhm.js.map → components-2bpqp4mq.js.map} +1 -1
- package/dist/chunks/content-source-34gnkbnn.js +38 -0
- package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
- package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
- package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
- package/dist/chunks/extension-sections-6mqydz35.js +301 -0
- package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
- package/dist/chunks/files-8kn321h1.js +70 -0
- package/dist/chunks/files-8kn321h1.js.map +9 -0
- package/dist/chunks/{format-host-8jxwts2p.js → format-host-mkwsmt8d.js} +3 -3
- package/dist/chunks/git-panel-tjbr191d.js +51 -0
- package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
- package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
- package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
- package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
- package/dist/chunks/layers-panel-87pb25km.js +58 -0
- package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
- package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
- package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
- package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
- package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
- package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
- package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
- package/dist/chunks/right-panel-zpdxd98t.js +37 -0
- package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
- package/dist/chunks/section-registry-zmckaxf4.js +30 -0
- package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
- package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
- package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
- package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
- package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
- package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
- package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
- package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
- package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
- package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
- package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
- package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
- package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
- package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
- package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
- package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
- package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
- package/dist/chunks/studio-41exgzhq.js +6690 -0
- package/dist/chunks/studio-41exgzhq.js.map +38 -0
- package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
- package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
- package/dist/chunks/studio-50k77cca.js +1787 -0
- package/dist/chunks/studio-50k77cca.js.map +18 -0
- package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
- package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
- package/dist/chunks/studio-6f0am8f2.js +53 -0
- package/dist/chunks/studio-6f0am8f2.js.map +10 -0
- package/dist/chunks/studio-6nmjc5qk.js +7002 -0
- package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
- package/dist/chunks/studio-7afd21nw.js +67 -0
- package/dist/chunks/studio-7afd21nw.js.map +10 -0
- package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
- package/dist/chunks/studio-7bmdvzne.js.map +10 -0
- package/dist/chunks/studio-8jpzqk5s.js +128 -0
- package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
- package/dist/chunks/studio-94m77tgk.js +1482 -0
- package/dist/chunks/studio-94m77tgk.js.map +13 -0
- package/dist/chunks/studio-9je6w5sq.js +1000 -0
- package/dist/chunks/studio-9je6w5sq.js.map +16 -0
- package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
- package/dist/chunks/{studio-gs6kqdt1.js → studio-adn0z1k3.js} +14 -8
- package/dist/chunks/{studio-gs6kqdt1.js.map → studio-adn0z1k3.js.map} +3 -3
- package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
- package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
- package/dist/chunks/studio-bw5dz6km.js +608 -0
- package/dist/chunks/studio-bw5dz6km.js.map +21 -0
- package/dist/chunks/studio-c6s5getc.js +4232 -0
- package/dist/chunks/studio-c6s5getc.js.map +39 -0
- package/dist/chunks/studio-cjm7fdjf.js +451 -0
- package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
- package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
- package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
- package/dist/chunks/studio-dg7zyt5x.js +28 -0
- package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
- package/dist/chunks/studio-dzcyvvvz.js +166 -0
- package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
- package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
- package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
- package/dist/chunks/studio-f12h6177.js +411 -0
- package/dist/chunks/studio-f12h6177.js.map +13 -0
- package/dist/chunks/studio-hbp1s8m9.js +1817 -0
- package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
- package/dist/chunks/studio-k2sxjph5.js +15295 -0
- package/dist/chunks/studio-k2sxjph5.js.map +235 -0
- package/dist/chunks/studio-myje51t3.js +385 -0
- package/dist/chunks/studio-myje51t3.js.map +12 -0
- package/dist/chunks/studio-na6rwzhw.js +353 -0
- package/dist/chunks/studio-na6rwzhw.js.map +11 -0
- package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
- package/dist/chunks/studio-p471gg5n.js.map +14 -0
- package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
- package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
- package/dist/chunks/{studio-esrewxap.js → studio-s519zc99.js} +3 -3
- package/dist/chunks/studio-sas7mt3k.js +75 -0
- package/dist/chunks/studio-sas7mt3k.js.map +10 -0
- package/dist/chunks/studio-t5wqwzdh.js +74 -0
- package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
- package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
- package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
- package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
- package/dist/chunks/studio-tr20m8g8.js.map +9 -0
- package/dist/chunks/{studio-v7jkjqng.js → studio-v96qfnww.js} +5514 -4291
- package/dist/chunks/studio-v96qfnww.js.map +54 -0
- package/dist/chunks/studio-vcetc91k.js +659 -0
- package/dist/chunks/studio-vcetc91k.js.map +10 -0
- package/dist/chunks/{studio-jm0z592w.js → studio-vmymjefp.js} +2972 -4937
- package/dist/chunks/studio-vmymjefp.js.map +98 -0
- package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
- package/dist/chunks/studio-x9hmy5kr.js +390 -0
- package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
- package/dist/chunks/studio-xa0ce0n7.js +335 -0
- package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
- package/dist/chunks/studio-xm3dbxvn.js +378 -0
- package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
- package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
- package/dist/chunks/studio-zv37yrfa.js +1054 -0
- package/dist/chunks/studio-zv37yrfa.js.map +13 -0
- package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
- package/dist/chunks/ws-client-654ts66j.js.map +11 -0
- package/dist/iframe-entry.js +668 -230
- package/dist/studio.js +21080 -32585
- package/index.html +35 -4685
- package/package.json +17 -15
- package/src/about/about-modal.ts +42 -11
- package/src/browse/library-commands.ts +240 -0
- package/src/browse/library-layouts.ts +369 -0
- package/src/browse/library-model.ts +396 -0
- package/src/browse/library-pane.ts +1049 -0
- package/src/browse/library-preview.ts +252 -0
- package/src/browse/library-source.ts +150 -0
- package/src/canvas/canvas-helpers.ts +33 -9
- package/src/canvas/canvas-live-render.ts +45 -29
- package/src/canvas/canvas-patcher.ts +151 -31
- package/src/canvas/canvas-perf.ts +46 -7
- package/src/canvas/canvas-render.ts +1092 -220
- package/src/canvas/canvas-surface.ts +240 -0
- package/src/canvas/canvas-utils.ts +1166 -124
- package/src/canvas/iframe-drop.ts +2 -1
- package/src/canvas/iframe-entry.ts +241 -4
- package/src/canvas/iframe-host.ts +1226 -143
- package/src/canvas/iframe-inline-edit.ts +31 -0
- package/src/canvas/iframe-interaction.ts +76 -16
- package/src/canvas/iframe-keys.ts +107 -45
- package/src/canvas/iframe-overlay.ts +38 -1
- package/src/canvas/iframe-protocol.ts +137 -2
- package/src/canvas/iframe-render.ts +70 -2
- package/src/canvas/iframe-slash.ts +6 -0
- package/src/canvas/path-mapping.ts +56 -6
- package/src/canvas/surface-registry.ts +292 -0
- package/src/collab/collab-commands.ts +244 -0
- package/src/collab/collab-session.ts +198 -32
- package/src/collab/collab-state.ts +28 -2
- package/src/collab/presence-chips.ts +100 -9
- package/src/commands/active-registry.ts +42 -0
- package/src/commands/app-commands.ts +176 -0
- package/src/commands/budget.ts +158 -0
- package/src/commands/command-args.ts +253 -0
- package/src/commands/context.ts +387 -0
- package/src/commands/defaults.ts +580 -0
- package/src/commands/keymap.ts +571 -0
- package/src/commands/levels.ts +345 -0
- package/src/commands/live-context.ts +193 -0
- package/src/commands/reference.ts +227 -0
- package/src/commands/registry.ts +293 -0
- package/src/component-props.ts +3 -2
- package/src/content/draft-state.ts +106 -0
- package/src/content/entry-commands.ts +282 -0
- package/src/content/entry-editor.ts +327 -0
- package/src/content/entry-fields.ts +116 -0
- package/src/content/entry-model.ts +175 -0
- package/src/editor/canvas-context-menu.ts +17 -11
- package/src/editor/canvas-slash-bridge.ts +11 -4
- package/src/editor/context-menu.ts +804 -204
- package/src/editor/convert-to-component.ts +31 -10
- package/src/editor/convert-to-repeater.ts +3 -2
- package/src/editor/inline-edit-apply.ts +5 -5
- package/src/editor/inline-edit.ts +86 -50
- package/src/editor/insert-zone-action.ts +1 -1
- package/src/editor/shortcuts.ts +1043 -329
- package/src/editor/slash-menu.ts +1 -0
- package/src/files/elements.ts +166 -0
- package/src/files/file-ops.ts +178 -31
- package/src/files/files.ts +898 -268
- package/src/files/fs-events.ts +21 -0
- package/src/files/media-meta.ts +203 -0
- package/src/files/media-paths.ts +141 -0
- package/src/files/media-upload.ts +7 -3
- package/src/files/media-usage.ts +189 -0
- package/src/github/github-auth.ts +235 -33
- package/src/github/github-publish.ts +80 -21
- package/src/grid/grid-controller.ts +152 -12
- package/src/grid/grid-layout.ts +225 -20
- package/src/grid/grid-open.ts +120 -0
- package/src/grid/grid-panel.ts +554 -39
- package/src/grid/grid-source.ts +27 -1
- package/src/grid/redirects-grid.ts +599 -0
- package/src/grid/redirects.ts +398 -0
- package/src/grid/sources/content-source.ts +108 -2
- package/src/i18n/i18n-commands.ts +322 -0
- package/src/new-project/add-repo-modal.ts +8 -10
- package/src/new-project/import-tab.ts +7 -0
- package/src/new-project/location-fields.ts +9 -1
- package/src/new-project/new-project-modal.ts +221 -245
- package/src/packages/jxsuite-update.ts +97 -48
- package/src/packages/pull-package-sync.ts +18 -9
- package/src/page-params.ts +2 -1
- package/src/panels/activity-bar.ts +152 -91
- package/src/panels/activity-panel.ts +481 -0
- package/src/panels/ai-chat/chat-markdown.ts +8 -3
- package/src/panels/ai-chat/chat-view.ts +245 -18
- package/src/panels/ai-chat/composer.ts +31 -5
- package/src/panels/ai-chat/sessions-view.ts +17 -10
- package/src/panels/ai-panel.ts +279 -53
- package/src/panels/block-action-bar.ts +1115 -224
- package/src/panels/bottom-dock.ts +337 -0
- package/src/panels/canvas-dnd-bridge.ts +37 -8
- package/src/panels/chat-panel.ts +29 -12
- package/src/panels/data-explorer.ts +254 -112
- package/src/panels/data-grid.ts +10 -4
- package/src/panels/dnd.ts +28 -51
- package/src/panels/editors.ts +434 -107
- package/src/panels/elements-panel.ts +66 -2
- package/src/panels/empty-state.ts +112 -0
- package/src/panels/events-panel.ts +596 -82
- package/src/panels/formula-workspace.ts +408 -218
- package/src/panels/frontmatter-fields.ts +51 -10
- package/src/panels/frontmatter-panel.ts +357 -79
- package/src/panels/git-panel.ts +375 -164
- package/src/panels/head-panel.ts +791 -85
- package/src/panels/i18n-panel.ts +592 -0
- package/src/panels/imports-panel.ts +95 -85
- package/src/panels/jump-bar.ts +584 -0
- package/src/panels/layers-panel.ts +1106 -328
- package/src/panels/left-panel.ts +109 -204
- package/src/panels/navigator-panels.ts +131 -0
- package/src/panels/overlays.ts +7 -2
- package/src/panels/pane-context.ts +1477 -0
- package/src/panels/pane-grid.ts +406 -0
- package/src/panels/panel-registry.ts +340 -0
- package/src/panels/panel-scheduler.ts +75 -0
- package/src/panels/problems-panel.ts +234 -0
- package/src/panels/properties-panel.ts +946 -1139
- package/src/panels/provenance.ts +258 -0
- package/src/panels/quick-search.ts +791 -122
- package/src/panels/right-panel.ts +228 -102
- package/src/panels/seo-modal.ts +395 -0
- package/src/panels/settings-pane.ts +203 -0
- package/src/panels/signals-panel.ts +354 -137
- package/src/panels/statement-editor.ts +41 -25
- package/src/panels/statusbar.ts +342 -106
- package/src/panels/style-panel.ts +974 -463
- package/src/panels/style-utils.ts +3 -3
- package/src/panels/stylebook-doc.ts +4 -2
- package/src/panels/stylebook-layers-panel.ts +3 -2
- package/src/panels/stylebook-panel.ts +51 -26
- package/src/panels/tab-strip.ts +1058 -78
- package/src/panels/target-line.ts +274 -0
- package/src/panels/toolbar.ts +631 -484
- package/src/panels/welcome-screen.ts +301 -198
- package/src/platform-errors.ts +26 -3
- package/src/platform.ts +96 -2
- package/src/platforms/cloud.ts +71 -8
- package/src/platforms/devserver.ts +92 -29
- package/src/publish/deploy-checklist.ts +383 -0
- package/src/publish/pages-service.ts +10 -1
- package/src/publish/publish-commands.ts +218 -0
- package/src/publish/publish-panel.ts +110 -16
- package/src/recent-projects.ts +3 -2
- package/src/services/a11y-report.ts +505 -0
- package/src/services/ai-models.ts +30 -1
- package/src/services/ai-project-tools.ts +29 -10
- package/src/services/ai-system-prompt.ts +53 -14
- package/src/services/ai-tools.ts +39 -3
- package/src/services/ai-writes.ts +154 -0
- package/src/services/announce.ts +97 -0
- package/src/services/automation.ts +424 -175
- package/src/services/cem-export.ts +9 -4
- package/src/services/clock.ts +61 -0
- package/src/services/document-assistant.ts +36 -8
- package/src/services/idle.ts +179 -0
- package/src/services/import-client.ts +26 -0
- package/src/services/monaco-buffer.ts +627 -0
- package/src/services/monaco-lazy.ts +36 -0
- package/src/services/notify.ts +271 -0
- package/src/services/profile.ts +155 -0
- package/src/services/references.ts +0 -0
- package/src/services/token-lint.ts +3 -2
- package/src/services/tool-executor.ts +53 -7
- package/src/services/trusted-types.ts +123 -0
- package/src/settings/contexts-section.ts +427 -0
- package/src/settings/contributed-section.ts +149 -11
- package/src/settings/css-vars-editor.ts +306 -349
- package/src/settings/defs-editor.ts +93 -34
- package/src/settings/dependencies-editor.ts +7 -7
- package/src/settings/extension-sections.ts +37 -5
- package/src/settings/general-settings.ts +204 -121
- package/src/settings/locales-section.ts +347 -0
- package/src/settings/preferences-accounts.ts +140 -0
- package/src/settings/preferences-dialog.ts +620 -0
- package/src/settings/preferences-keymap.ts +270 -0
- package/src/settings/project-sections.ts +260 -0
- package/src/settings/section-registry.ts +169 -0
- package/src/settings/settings-document.ts +385 -0
- package/src/shell.ts +1549 -0
- package/src/site-context.ts +45 -20
- package/src/state.ts +14 -275
- package/src/store.ts +88 -43
- package/src/studio.ts +790 -346
- package/src/style/live-preview.ts +36 -0
- package/src/style/project-styles.ts +349 -0
- package/src/style/token-ref.ts +123 -0
- package/src/tabs/patch-ops.ts +16 -4
- package/src/tabs/project-config.ts +416 -0
- package/src/tabs/selection.ts +323 -0
- package/src/tabs/tab.ts +182 -47
- package/src/tabs/transact.ts +209 -28
- package/src/types.ts +68 -13
- package/src/ui/ai-credentials-form.ts +39 -35
- package/src/ui/ai-managed-connect.ts +7 -1
- package/src/ui/dynamic-slot.ts +224 -81
- package/src/ui/expression-editor.ts +71 -63
- package/src/ui/field-input.ts +4 -6
- package/src/ui/field-row.ts +215 -38
- package/src/ui/form-controls.ts +194 -7
- package/src/ui/layers.ts +492 -69
- package/src/ui/media-picker.ts +115 -25
- package/src/ui/panel-resize.ts +109 -91
- package/src/ui/progress-modal.ts +131 -50
- package/src/ui/regions.ts +462 -0
- package/src/ui/schema-form.ts +229 -45
- package/src/ui/spectrum.ts +24 -8
- package/src/ui/timing.ts +49 -0
- package/src/ui/value-selector.ts +20 -0
- package/src/ui/value-source.ts +348 -0
- package/src/ui/virtual-window.ts +400 -0
- package/src/utils/edit-display.ts +3 -2
- package/src/utils/inherited-style.ts +67 -37
- package/src/view.ts +64 -78
- package/src/workspace/pane-derive.ts +1258 -0
- package/src/workspace/session.ts +317 -0
- package/src/workspace/workspace.ts +1345 -23
- package/styles/canvas.css +653 -0
- package/styles/forced-colors.css +106 -0
- package/styles/inspector.css +1454 -0
- package/styles/overlays.css +2704 -0
- package/styles/panels.css +1701 -0
- package/styles/shell.css +1423 -0
- package/styles/tokens.css +151 -0
- package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
- package/dist/chunks/extension-sections-qr3drkkw.js +0 -531
- package/dist/chunks/extension-sections-qr3drkkw.js.map +0 -12
- package/dist/chunks/files-j8a1t06k.js +0 -50
- package/dist/chunks/files-j8a1t06k.js.map +0 -9
- package/dist/chunks/git-panel-xtq5z81x.js +0 -28
- package/dist/chunks/git-panel-xtq5z81x.js.map +0 -9
- package/dist/chunks/layers-panel-ancn2wpq.js +0 -26
- package/dist/chunks/layers-panel-ancn2wpq.js.map +0 -9
- package/dist/chunks/settings-modal-17ttr96g.js +0 -29
- package/dist/chunks/settings-modal-17ttr96g.js.map +0 -9
- package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
- package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
- package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
- package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
- package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
- package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
- package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
- package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
- package/dist/chunks/studio-0xgs7xrs.js +0 -429
- package/dist/chunks/studio-0xgs7xrs.js.map +0 -13
- package/dist/chunks/studio-4d8hzcz5.js +0 -3427
- package/dist/chunks/studio-4d8hzcz5.js.map +0 -26
- package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
- package/dist/chunks/studio-9smfzqhc.js +0 -19
- package/dist/chunks/studio-9smfzqhc.js.map +0 -10
- package/dist/chunks/studio-jm0z592w.js.map +0 -115
- package/dist/chunks/studio-m2t4mdg5.js +0 -1854
- package/dist/chunks/studio-m2t4mdg5.js.map +0 -18
- package/dist/chunks/studio-n4p11dfy.js +0 -853
- package/dist/chunks/studio-n4p11dfy.js.map +0 -12
- package/dist/chunks/studio-qkvt37c0.js.map +0 -9
- package/dist/chunks/studio-sjh1wt1a.js +0 -3744
- package/dist/chunks/studio-sjh1wt1a.js.map +0 -22
- package/dist/chunks/studio-v7jkjqng.js.map +0 -46
- package/dist/chunks/studio-xq0t7n5t.js +0 -140
- package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
- package/dist/chunks/studio-xze4zrs1.js.map +0 -10
- package/dist/chunks/studio-yfcstdzy.js +0 -399
- package/dist/chunks/studio-yfcstdzy.js.map +0 -12
- package/dist/chunks/studio-zp7jncs8.js +0 -469
- package/dist/chunks/studio-zp7jncs8.js.map +0 -14
- package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
- package/src/browse/browse-modal.ts +0 -69
- package/src/browse/browse.ts +0 -904
- package/src/canvas/canvas-diff.ts +0 -216
- package/src/canvas/nested-site-style.ts +0 -58
- package/src/new-project/design-fields.ts +0 -264
- package/src/new-project/templates.ts +0 -61
- package/src/panels/tab-bar.ts +0 -548
- package/src/settings/settings-modal.ts +0 -203
- /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
- /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
- /package/dist/chunks/{format-host-8jxwts2p.js.map → format-host-mkwsmt8d.js.map} +0 -0
- /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
- /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
- /package/dist/chunks/{studio-esrewxap.js.map → studio-s519zc99.js.map} +0 -0
- /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
- /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* Languages — which pages exist in which locale, and which do not.
|
|
4
|
+
*
|
|
5
|
+
* Jx has no message catalogue. A translation is a DIFFERENT FILE in a different directory
|
|
6
|
+
* (`pages/fr/about.json` beside `pages/about.json`, `specs/site-architecture.md` §13.3), and that
|
|
7
|
+
* is why nothing in the shell could answer "is this site translated": the Files panel draws `fr/`
|
|
8
|
+
* the way it draws any other directory, and a page nobody has translated is invisible precisely
|
|
9
|
+
* because the file that would prove it does not exist.
|
|
10
|
+
*
|
|
11
|
+
* So this panel is a grid over ABSENCE. One row per translation key — the path with its locale
|
|
12
|
+
* segment taken out, which is what makes two files the same page — and one column per declared
|
|
13
|
+
* locale. A cell says one of three things:
|
|
14
|
+
*
|
|
15
|
+
* | Cell | Means | Its button |
|
|
16
|
+
* | --------- | ------------------------------------------------------------- | ------------------ |
|
|
17
|
+
* | `present` | the file is there | opens it |
|
|
18
|
+
* | `stale` | it is there, and older than the source it was translated from | opens it, and says |
|
|
19
|
+
* | `missing` | it is not there, and this is where it would go | creates it |
|
|
20
|
+
*
|
|
21
|
+
* **Every button is a command, run by id through the registry** — never a direct call, for the
|
|
22
|
+
* reason `panels/problems-panel.ts` gives about its recovery button: the registry is what makes the
|
|
23
|
+
* action reachable from the palette and from automation, and it is where the refusal sentence comes
|
|
24
|
+
* from. A cell whose command this window has not registered is drawn disabled with that as its
|
|
25
|
+
* tooltip, rather than as a button that does nothing.
|
|
26
|
+
*
|
|
27
|
+
* **Off the rail.** `railDeclarations()` does not apply `when`, so a rail button here would spend
|
|
28
|
+
* the last `rail/project` slot in every monolingual project and shift every document panel's ⌘1–8
|
|
29
|
+
* chord by one. `i18n.showParity` is how this panel is reached.
|
|
30
|
+
*
|
|
31
|
+
* @docs studio/interface/languages
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { html, nothing } from "lit-html";
|
|
35
|
+
import { repeat } from "lit-html/directives/repeat.js";
|
|
36
|
+
import { activeRegistry } from "../commands/active-registry";
|
|
37
|
+
import { errorMessage } from "@jxsuite/schema/parse";
|
|
38
|
+
import { getEffectiveLocales } from "../site-context";
|
|
39
|
+
import { getPlatform } from "../platform";
|
|
40
|
+
import {
|
|
41
|
+
localeLabel,
|
|
42
|
+
localeOfPath,
|
|
43
|
+
translationKeyOfPath,
|
|
44
|
+
translationPathFor,
|
|
45
|
+
} from "@jxsuite/schema/locale";
|
|
46
|
+
import { notify } from "../services/notify";
|
|
47
|
+
import { projectState } from "../store";
|
|
48
|
+
import { registerPanel } from "./panel-registry";
|
|
49
|
+
import { renderEmptyState } from "./empty-state";
|
|
50
|
+
import { scanLibrary } from "../browse/library-model";
|
|
51
|
+
import type { LibraryFile, ScanFailure } from "../browse/library-model";
|
|
52
|
+
import type { NavigatorPanelContext, PanelBody } from "./panel-registry";
|
|
53
|
+
import type { ResolvedI18n } from "@jxsuite/schema/locale";
|
|
54
|
+
import type { TemplateResult } from "lit-html";
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* One cell of the parity grid.
|
|
58
|
+
*
|
|
59
|
+
* **Stale is defined here and nowhere else**: a translation is stale when the DEFAULT locale's file
|
|
60
|
+
* for the same key carries a `modified` strictly newer than the translation's. A file the platform
|
|
61
|
+
* reported no `modified` for is `present`, never `stale` — an absent timestamp is not evidence of
|
|
62
|
+
* being behind, and `browse/library-layouts.ts`'s `formatModified` already set the precedent of
|
|
63
|
+
* refusing to invent one.
|
|
64
|
+
*/
|
|
65
|
+
export type ParityCell =
|
|
66
|
+
/** The file is on disk at `path`. */
|
|
67
|
+
| { state: "present"; path: string }
|
|
68
|
+
/** The file is on disk at `path` and older than `behind`, the source it was translated from. */
|
|
69
|
+
| { state: "stale"; path: string; behind: string }
|
|
70
|
+
/**
|
|
71
|
+
* No file. `path` is where it WOULD go, and `null` when the key cannot carry a locale directory
|
|
72
|
+
* at all — a file directly under `content/`, outside any collection, has no place to put one.
|
|
73
|
+
*/
|
|
74
|
+
| { state: "missing"; path: string | null };
|
|
75
|
+
|
|
76
|
+
/** One row of the parity grid: a translation key and its cell per declared locale. */
|
|
77
|
+
export interface ParityRow {
|
|
78
|
+
/** `translationKeyOfPath` of the files behind the row — the path with the locale taken out. */
|
|
79
|
+
key: string;
|
|
80
|
+
/** Locale → cell, one entry per declared locale, in declaration order. */
|
|
81
|
+
cells: Map<string, ParityCell>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The directories a translation can live in — the two layouts §13 defines, and nothing else.
|
|
86
|
+
*
|
|
87
|
+
* Not `projectState.projectDirs`: `layouts/` and `components/` are shared by every locale and
|
|
88
|
+
* `public/` is not addressable per language, so scanning them would fill the grid with rows whose
|
|
89
|
+
* every cell is permanently `missing` for a reason that is not the author's to fix.
|
|
90
|
+
*/
|
|
91
|
+
const PARITY_DIRS = ["pages", "content"] as const;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* How many keys the grid draws.
|
|
95
|
+
*
|
|
96
|
+
* It states the remainder rather than dropping it silently, which is what `calendarTpl` does with
|
|
97
|
+
* the days it does not show — a truncation nobody is told about is indistinguishable from a project
|
|
98
|
+
* that really is that small.
|
|
99
|
+
*/
|
|
100
|
+
export const PARITY_ROW_LIMIT = 200;
|
|
101
|
+
|
|
102
|
+
/** One completed pass over {@link PARITY_DIRS}, and the project root it was taken in. */
|
|
103
|
+
interface ParityScan {
|
|
104
|
+
root: string;
|
|
105
|
+
files: readonly LibraryFile[];
|
|
106
|
+
failures: readonly ScanFailure[];
|
|
107
|
+
/** Path → the `$translationKey` that document declares, for the documents that declare one. */
|
|
108
|
+
declared: ReadonlyMap<string, string>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/*
|
|
112
|
+
* The scan is module state rather than panel state because the panel's `render` is synchronous and
|
|
113
|
+
* the Navigator repaints it on every keystroke elsewhere in the shell. Keying it on the project
|
|
114
|
+
* root is what makes a project switch re-read rather than draw the previous project's file tree.
|
|
115
|
+
*/
|
|
116
|
+
let scanned: ParityScan | null = null;
|
|
117
|
+
let scanning: string | null = null;
|
|
118
|
+
|
|
119
|
+
/** Forget the scan, so the next render takes a fresh one. The Rescan button, and every test. */
|
|
120
|
+
export function refreshParityScan(): void {
|
|
121
|
+
scanned = null;
|
|
122
|
+
scanning = null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Walk the project once, then repaint.
|
|
127
|
+
*
|
|
128
|
+
* `scanLibrary` never rejects, so the only way into the catch is {@link getPlatform} throwing in a
|
|
129
|
+
* window that has none. That is recorded as a scan failure rather than swallowed, for the reason
|
|
130
|
+
* `browse/library-model.ts` states at length: a listing that half-worked and a project that is
|
|
131
|
+
* empty produced the same sentence, and only one of them is true.
|
|
132
|
+
*/
|
|
133
|
+
async function takeScan(root: string, rerender: () => void): Promise<void> {
|
|
134
|
+
try {
|
|
135
|
+
const scan = await scanLibrary(PARITY_DIRS, getPlatform());
|
|
136
|
+
scanned = {
|
|
137
|
+
declared: await readDeclaredKeys(scan.files),
|
|
138
|
+
failures: scan.failures,
|
|
139
|
+
files: scan.files,
|
|
140
|
+
root,
|
|
141
|
+
};
|
|
142
|
+
} catch (error) {
|
|
143
|
+
scanned = {
|
|
144
|
+
declared: new Map(),
|
|
145
|
+
failures: [{ dir: PARITY_DIRS.join(", "), error: errorMessage(error) }],
|
|
146
|
+
files: [],
|
|
147
|
+
root,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
scanning = null;
|
|
151
|
+
rerender();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The `$translationKey` each page declares, for the pages that declare one.
|
|
156
|
+
*
|
|
157
|
+
* Without this the grid keys on the path alone, and a **localized slug** — the case
|
|
158
|
+
* `site-architecture.md` §13.5 exists to handle — reads as two half-translated pages rather than
|
|
159
|
+
* one whole one: `pages/about.json` present in English and Arabic, `pages/a-propos.json` present in
|
|
160
|
+
* French, and four `missing` cells describing files nobody should write. That is the wrong answer
|
|
161
|
+
* on the one surface whose whole job is to say which pages are translated.
|
|
162
|
+
*
|
|
163
|
+
* A route parameter is normalized to its template spelling — `exhibitions/${slug}` and the English
|
|
164
|
+
* `pages/exhibitions/[slug].json` are the same page — because the grid keys on FILES where the
|
|
165
|
+
* build keys on routes, and a template stands for every route it expands to.
|
|
166
|
+
*
|
|
167
|
+
* Read from the raw text, and only for the files that mention the key: the parse is what costs, and
|
|
168
|
+
* most pages of a multilingual site never declare one — their paths are parallel and the derivation
|
|
169
|
+
* is right. A file that cannot be read is simply absent from the map, which leaves it keyed by
|
|
170
|
+
* path.
|
|
171
|
+
*
|
|
172
|
+
* @param {readonly LibraryFile[]} files
|
|
173
|
+
* @returns {Promise<Map<string, string>>}
|
|
174
|
+
*/
|
|
175
|
+
async function readDeclaredKeys(files: readonly LibraryFile[]): Promise<Map<string, string>> {
|
|
176
|
+
const out = new Map<string, string>();
|
|
177
|
+
const platform = getPlatform();
|
|
178
|
+
for (const file of files) {
|
|
179
|
+
if (file.category === "Media" || !file.path.startsWith("pages/")) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
let text: string;
|
|
183
|
+
try {
|
|
184
|
+
text = await platform.readFile(file.path);
|
|
185
|
+
} catch {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (!text.includes("$translationKey")) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const declared = /"\$translationKey"\s*:\s*"((?:[^"\\]|\\.)*)"/.exec(text)?.[1];
|
|
192
|
+
if (declared !== undefined && declared !== "") {
|
|
193
|
+
out.set(file.path, declared.replaceAll(/\$\{(\w+)\}/g, "[$1]").replaceAll(/^\/+|\/+$/g, ""));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** ISO timestamp as a comparable number, or `null` — never a guessed "now". */
|
|
200
|
+
function timeOf(modified: string | undefined): number | null {
|
|
201
|
+
if (!modified) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
const parsed = Date.parse(modified);
|
|
205
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Fold a scan into the grid. Pure: no I/O, no platform, no DOM.
|
|
210
|
+
*
|
|
211
|
+
* A file under no locale segment lands under the DEFAULT locale, because that is what it is — under
|
|
212
|
+
* `prefix-except-default` the unprefixed path is the default locale's copy, and under
|
|
213
|
+
* `prefix-always` it is the copy an author wrote before declaring the locale they meant. Either way
|
|
214
|
+
* it is the source the other columns are measured against, and hiding it would leave the grid with
|
|
215
|
+
* a column of `missing` cells whose sources are on disk.
|
|
216
|
+
*
|
|
217
|
+
* Assets are skipped. `categoryFor` already decides what is media by extension, so an image beside
|
|
218
|
+
* a page is not a row here for the same reason it is not a page anywhere else.
|
|
219
|
+
*/
|
|
220
|
+
export function parityRows(
|
|
221
|
+
files: readonly LibraryFile[],
|
|
222
|
+
i18n: ResolvedI18n,
|
|
223
|
+
declared: ReadonlyMap<string, string> = new Map(),
|
|
224
|
+
): ParityRow[] {
|
|
225
|
+
const byKey = new Map<string, Map<string, LibraryFile>>();
|
|
226
|
+
/*
|
|
227
|
+
* A document's own `$translationKey` overrides the one its path implies, exactly as it does in
|
|
228
|
+
* the build (§13.5) — which is what makes a localized slug one row instead of two half-empty
|
|
229
|
+
* ones. Prefixed with the directory the derivation would have produced, so the two spellings meet:
|
|
230
|
+
* a declared key is route-shaped (`about`) and this grid is file-shaped (`pages/about.json`).
|
|
231
|
+
*/
|
|
232
|
+
/* Every key the paths alone produce, so a declared one can be matched against a real file rather
|
|
233
|
+
than against a spelling. */
|
|
234
|
+
const derivedKeys = new Set(
|
|
235
|
+
files.filter((f) => f.category !== "Media").map((f) => translationKeyOfPath(f.path, i18n)),
|
|
236
|
+
);
|
|
237
|
+
const keyOf = (file: LibraryFile): string => {
|
|
238
|
+
const own = declared.get(file.path);
|
|
239
|
+
if (own === undefined) {
|
|
240
|
+
return translationKeyOfPath(file.path, i18n);
|
|
241
|
+
}
|
|
242
|
+
/*
|
|
243
|
+
* A declared key is route-shaped (`about`, `exhibitions`) while this grid is file-shaped
|
|
244
|
+
* (`pages/about.json`, `pages/exhibitions/index.json`), and one route can be either — a page or
|
|
245
|
+
* a directory's index. Both spellings are tried against the files actually scanned, so the key
|
|
246
|
+
* lands on the page that exists; the plain form stands when neither does, which keeps the row
|
|
247
|
+
* visible instead of merging it into something arbitrary.
|
|
248
|
+
*/
|
|
249
|
+
const ext = /\.[^./]+$/.exec(file.path)?.[0] ?? "";
|
|
250
|
+
for (const candidate of [`pages/${own}${ext}`, `pages/${own}/index${ext}`]) {
|
|
251
|
+
if (derivedKeys.has(candidate)) {
|
|
252
|
+
return candidate;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return `pages/${own}${ext}`;
|
|
256
|
+
};
|
|
257
|
+
for (const file of files) {
|
|
258
|
+
if (file.category === "Media") {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const key = keyOf(file);
|
|
262
|
+
const locale = localeOfPath(file.path, i18n) ?? i18n.defaultLocale;
|
|
263
|
+
const cell = byKey.get(key) ?? new Map<string, LibraryFile>();
|
|
264
|
+
/*
|
|
265
|
+
* Two files can claim one square — `pages/about.json` and `pages/en/about.json` are both the
|
|
266
|
+
* English copy under `prefix-except-default`. The one at the CANONICAL path wins, so the grid
|
|
267
|
+
* agrees with the location `i18n.createTranslation` would have written to; otherwise the first
|
|
268
|
+
* in path order stands, and the duplicate is the author's to reconcile.
|
|
269
|
+
*/
|
|
270
|
+
if (!cell.has(locale) || file.path === translationPathFor(key, locale, i18n)) {
|
|
271
|
+
cell.set(locale, file);
|
|
272
|
+
}
|
|
273
|
+
byKey.set(key, cell);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const rows: ParityRow[] = [];
|
|
277
|
+
for (const [key, found] of byKey) {
|
|
278
|
+
const source = found.get(i18n.defaultLocale);
|
|
279
|
+
const sourceTime = timeOf(source?.modified);
|
|
280
|
+
const cells = new Map<string, ParityCell>();
|
|
281
|
+
for (const locale of i18n.locales) {
|
|
282
|
+
const file = found.get(locale);
|
|
283
|
+
if (!file) {
|
|
284
|
+
cells.set(locale, { path: translationPathFor(key, locale, i18n), state: "missing" });
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const time = timeOf(file.modified);
|
|
288
|
+
const behind =
|
|
289
|
+
source !== undefined &&
|
|
290
|
+
source.path !== file.path &&
|
|
291
|
+
sourceTime !== null &&
|
|
292
|
+
time !== null &&
|
|
293
|
+
sourceTime > time;
|
|
294
|
+
cells.set(
|
|
295
|
+
locale,
|
|
296
|
+
behind
|
|
297
|
+
? { behind: source.path, path: file.path, state: "stale" }
|
|
298
|
+
: { path: file.path, state: "present" },
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
rows.push({ cells, key });
|
|
302
|
+
}
|
|
303
|
+
return rows.toSorted((a, b) => a.key.localeCompare(b.key));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Where a monolingual project declares its languages — the empty state's one action. */
|
|
307
|
+
const SETTINGS_COMMAND = "settings.open";
|
|
308
|
+
|
|
309
|
+
/** The command each cell state runs. */
|
|
310
|
+
const CELL_COMMAND: Readonly<Record<ParityCell["state"], string>> = {
|
|
311
|
+
missing: "i18n.createTranslation",
|
|
312
|
+
present: "i18n.openTranslation",
|
|
313
|
+
stale: "i18n.openTranslation",
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/** One glyph per state, the way `problems-panel.ts` draws severity — scannable down a column. */
|
|
317
|
+
const CELL_GLYPH: Readonly<Record<ParityCell["state"], string>> = {
|
|
318
|
+
missing: "+",
|
|
319
|
+
present: "✓",
|
|
320
|
+
stale: "!",
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* The path a row's commands are addressed by: whichever file of the row actually exists, preferring
|
|
325
|
+
* the source, and the key itself when the row is empty in every locale.
|
|
326
|
+
*
|
|
327
|
+
* Any sibling answers, because `translationPathFor` takes the locale out before it puts one back.
|
|
328
|
+
*/
|
|
329
|
+
function rowSourcePath(row: ParityRow, i18n: ResolvedI18n): string {
|
|
330
|
+
const source = row.cells.get(i18n.defaultLocale);
|
|
331
|
+
if (source && source.state !== "missing") {
|
|
332
|
+
return source.path;
|
|
333
|
+
}
|
|
334
|
+
for (const cell of row.cells.values()) {
|
|
335
|
+
if (cell.state !== "missing") {
|
|
336
|
+
return cell.path;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return row.key;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** Why a cell's button will not run, or `undefined` when it will. */
|
|
343
|
+
function cellRefusal(cell: ParityCell, id: string): string | undefined {
|
|
344
|
+
if (cell.state === "missing" && cell.path === null) {
|
|
345
|
+
return "a file that can hold a locale directory — this one is under no collection";
|
|
346
|
+
}
|
|
347
|
+
const registry = activeRegistry();
|
|
348
|
+
if (!registry?.get(id)) {
|
|
349
|
+
return "the Languages commands, which this window has not registered";
|
|
350
|
+
}
|
|
351
|
+
return registry.disabledReason(id);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** The cell's whole sentence: what the click does, or what it needs first. */
|
|
355
|
+
function cellTitle(cell: ParityCell, locale: string, refusal: string | undefined): string {
|
|
356
|
+
const label = localeLabel(locale);
|
|
357
|
+
const said =
|
|
358
|
+
cell.state === "missing"
|
|
359
|
+
? cell.path === null
|
|
360
|
+
? `No place for a ${label} copy of this file`
|
|
361
|
+
: `Create ${cell.path}`
|
|
362
|
+
: cell.state === "stale"
|
|
363
|
+
? `Open ${cell.path} — older than ${cell.behind}`
|
|
364
|
+
: `Open ${cell.path}`;
|
|
365
|
+
return refusal === undefined ? said : `${said} — requires ${refusal}`;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Run one of this panel's actions, by id, and say so when it refuses.
|
|
370
|
+
*
|
|
371
|
+
* The `catch` is the point: `i18n.createTranslation` rejects when the file it would write is
|
|
372
|
+
* already there, and a click that produced nothing and printed nothing would leave the author
|
|
373
|
+
* looking at a grid that disagrees with the disk.
|
|
374
|
+
*/
|
|
375
|
+
function runByName(id: string, args: Record<string, unknown> = {}): void {
|
|
376
|
+
const registry = activeRegistry();
|
|
377
|
+
if (!registry?.get(id)) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
void Promise.resolve(registry.run(id, args)).catch((error: unknown) => {
|
|
381
|
+
notify("error", errorMessage(error), { source: "Languages" });
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** One square of the grid. */
|
|
386
|
+
function cellTpl(
|
|
387
|
+
row: ParityRow,
|
|
388
|
+
locale: string,
|
|
389
|
+
cell: ParityCell,
|
|
390
|
+
i18n: ResolvedI18n,
|
|
391
|
+
): TemplateResult {
|
|
392
|
+
const id = CELL_COMMAND[cell.state];
|
|
393
|
+
const refusal = cellRefusal(cell, id);
|
|
394
|
+
const title = cellTitle(cell, locale, refusal);
|
|
395
|
+
return html`<td class="i18n-cell">
|
|
396
|
+
<button
|
|
397
|
+
class="i18n-cell-button i18n-cell-button--${cell.state}"
|
|
398
|
+
?disabled=${refusal !== undefined}
|
|
399
|
+
title=${title}
|
|
400
|
+
aria-label=${title}
|
|
401
|
+
@click=${() => {
|
|
402
|
+
runByName(id, { locale, path: rowSourcePath(row, i18n) });
|
|
403
|
+
}}
|
|
404
|
+
>
|
|
405
|
+
<span aria-hidden="true">${CELL_GLYPH[cell.state]}</span>
|
|
406
|
+
</button>
|
|
407
|
+
</td>`;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** One row: the key it names, then its cell per declared locale. */
|
|
411
|
+
function rowTpl(row: ParityRow, i18n: ResolvedI18n): TemplateResult {
|
|
412
|
+
return html`<tr class="i18n-row">
|
|
413
|
+
<th class="i18n-key" scope="row" title=${row.key}>${row.key}</th>
|
|
414
|
+
${i18n.locales.map((locale) =>
|
|
415
|
+
cellTpl(row, locale, row.cells.get(locale) ?? { path: null, state: "missing" }, i18n),
|
|
416
|
+
)}
|
|
417
|
+
</tr>`;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** The column heads: each locale's autonym, with the tag as the tooltip. */
|
|
421
|
+
function headTpl(i18n: ResolvedI18n): TemplateResult {
|
|
422
|
+
return html`<tr>
|
|
423
|
+
<th class="i18n-key-head" scope="col">Page</th>
|
|
424
|
+
${i18n.locales.map(
|
|
425
|
+
(locale) => html`<th class="i18n-locale-head" scope="col" title=${locale}>
|
|
426
|
+
${localeLabel(locale)}
|
|
427
|
+
${
|
|
428
|
+
locale === i18n.defaultLocale
|
|
429
|
+
? html`<span class="i18n-default-mark">source</span>`
|
|
430
|
+
: nothing
|
|
431
|
+
}
|
|
432
|
+
</th>`,
|
|
433
|
+
)}
|
|
434
|
+
</tr>`;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** How many squares are not yet a file, and how many are behind their source. */
|
|
438
|
+
function countCells(rows: readonly ParityRow[]): { missing: number; stale: number } {
|
|
439
|
+
let missing = 0;
|
|
440
|
+
let stale = 0;
|
|
441
|
+
for (const row of rows) {
|
|
442
|
+
for (const cell of row.cells.values()) {
|
|
443
|
+
if (cell.state === "missing") {
|
|
444
|
+
missing += 1;
|
|
445
|
+
} else if (cell.state === "stale") {
|
|
446
|
+
stale += 1;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return { missing, stale };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** The one sentence above the grid: its size, and what is outstanding in it. */
|
|
454
|
+
function summaryText(rows: readonly ParityRow[]): string {
|
|
455
|
+
const { missing, stale } = countCells(rows);
|
|
456
|
+
const pages = `${rows.length} page${rows.length === 1 ? "" : "s"}`;
|
|
457
|
+
if (missing === 0 && stale === 0) {
|
|
458
|
+
return `${pages}, translated into every declared language.`;
|
|
459
|
+
}
|
|
460
|
+
const parts: string[] = [];
|
|
461
|
+
if (missing > 0) {
|
|
462
|
+
parts.push(`${missing} not written`);
|
|
463
|
+
}
|
|
464
|
+
if (stale > 0) {
|
|
465
|
+
parts.push(`${stale} older than their source`);
|
|
466
|
+
}
|
|
467
|
+
return `${pages} — ${parts.join(", ")}.`;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* The panel body.
|
|
472
|
+
*
|
|
473
|
+
* `getEffectiveLocales()` is read HERE, on every paint, rather than cached at module scope:
|
|
474
|
+
* `projectState` is a plain binding replaced wholesale by `setProjectState`, so a locale added in
|
|
475
|
+
* Settings reaches a cached copy never.
|
|
476
|
+
*/
|
|
477
|
+
export function renderI18nPanel(ctx: NavigatorPanelContext): PanelBody {
|
|
478
|
+
const i18n = getEffectiveLocales();
|
|
479
|
+
if (i18n === null || i18n.locales.length < 2) {
|
|
480
|
+
return renderEmptyState({
|
|
481
|
+
actions: [
|
|
482
|
+
{
|
|
483
|
+
disabled: activeRegistry()?.get(SETTINGS_COMMAND) === undefined,
|
|
484
|
+
label: "Open project settings…",
|
|
485
|
+
run: () => {
|
|
486
|
+
runByName(SETTINGS_COMMAND);
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
],
|
|
490
|
+
detail:
|
|
491
|
+
"Declare them under Locales in the project's settings, then a page's translations are " +
|
|
492
|
+
"sibling files under a directory named for each language.",
|
|
493
|
+
message: "This project is written in one language.",
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const root = projectState?.projectRoot ?? "";
|
|
498
|
+
if (scanned === null || scanned.root !== root) {
|
|
499
|
+
if (scanning !== root) {
|
|
500
|
+
scanning = root;
|
|
501
|
+
void takeScan(root, ctx.rerender);
|
|
502
|
+
}
|
|
503
|
+
return renderEmptyState({
|
|
504
|
+
detail: "Reading pages/ and content/ to find out which languages each page is written in.",
|
|
505
|
+
message: "Looking for translations…",
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const rows = parityRows(scanned.files, i18n, scanned.declared);
|
|
510
|
+
const shown = rows.slice(0, PARITY_ROW_LIMIT);
|
|
511
|
+
const hidden = rows.length - shown.length;
|
|
512
|
+
return html`
|
|
513
|
+
<div class="i18n-panel">
|
|
514
|
+
<div class="i18n-actions">
|
|
515
|
+
<p class="i18n-summary">${summaryText(rows)}</p>
|
|
516
|
+
<button
|
|
517
|
+
class="i18n-rescan"
|
|
518
|
+
title="Read pages/ and content/ again"
|
|
519
|
+
@click=${() => {
|
|
520
|
+
refreshParityScan();
|
|
521
|
+
ctx.rerender();
|
|
522
|
+
}}
|
|
523
|
+
>
|
|
524
|
+
Rescan
|
|
525
|
+
</button>
|
|
526
|
+
</div>
|
|
527
|
+
${
|
|
528
|
+
scanned.failures.length > 0
|
|
529
|
+
? html`<p class="i18n-incomplete">
|
|
530
|
+
This list is incomplete —
|
|
531
|
+
${scanned.failures.map((failure) => `${failure.dir} (${failure.error})`).join("; ")}
|
|
532
|
+
</p>`
|
|
533
|
+
: nothing
|
|
534
|
+
}
|
|
535
|
+
${
|
|
536
|
+
rows.length === 0
|
|
537
|
+
? renderEmptyState({
|
|
538
|
+
compact: true,
|
|
539
|
+
detail: "Add a page under pages/, or an entry to a content collection.",
|
|
540
|
+
message: "There is nothing to translate yet.",
|
|
541
|
+
})
|
|
542
|
+
: html`<table class="i18n-parity">
|
|
543
|
+
<thead>
|
|
544
|
+
${headTpl(i18n)}
|
|
545
|
+
</thead>
|
|
546
|
+
<tbody>
|
|
547
|
+
${repeat(
|
|
548
|
+
shown,
|
|
549
|
+
(row) => row.key,
|
|
550
|
+
(row) => rowTpl(row, i18n),
|
|
551
|
+
)}
|
|
552
|
+
</tbody>
|
|
553
|
+
</table>`
|
|
554
|
+
}
|
|
555
|
+
${
|
|
556
|
+
hidden > 0
|
|
557
|
+
? html`<p class="i18n-truncated">
|
|
558
|
+
${hidden} more page${hidden === 1 ? " is" : "s are"} not shown — the grid stops at
|
|
559
|
+
${PARITY_ROW_LIMIT}.
|
|
560
|
+
</p>`
|
|
561
|
+
: nothing
|
|
562
|
+
}
|
|
563
|
+
</div>
|
|
564
|
+
`;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Define the Languages panel.
|
|
569
|
+
*
|
|
570
|
+
* `level: "project"` because it writes project FILES, and its body therefore never reads `ctx.doc`
|
|
571
|
+
* — `tests/panel-registry.test.ts` renders every project-level panel with `doc: null` and a deps
|
|
572
|
+
* object whose document-level members throw.
|
|
573
|
+
*
|
|
574
|
+
* One caller: `panels/navigator-panels.ts`, which is the one place panel records are composed.
|
|
575
|
+
*/
|
|
576
|
+
export function registerI18nPanel(): void {
|
|
577
|
+
registerPanel({
|
|
578
|
+
id: "i18n",
|
|
579
|
+
title: "Languages",
|
|
580
|
+
level: "project",
|
|
581
|
+
dock: "navigator",
|
|
582
|
+
// Inert, like every other `rail: false` panel's: `tabIcon()` is only ever called by a rail
|
|
583
|
+
// Button, and `check-icons.ts` fails on a resolver row no rail panel declares.
|
|
584
|
+
icon: "sp-icon-globe",
|
|
585
|
+
// OFF THE RAIL. `railDeclarations()` does not apply `when`, so a rail button would spend the
|
|
586
|
+
// Last rail/project slot in every monolingual project — and would shift every document panel's
|
|
587
|
+
// ⌘1-8 chord by one. `i18n.showParity` is how it is reached.
|
|
588
|
+
rail: false,
|
|
589
|
+
when: (ctx) => ctx.project.isMultilingual,
|
|
590
|
+
render: (ctx) => renderI18nPanel(ctx),
|
|
591
|
+
});
|
|
592
|
+
}
|