@jxsuite/studio 10.4.0 → 10.6.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/data/locales/en/elements.json +434 -0
- package/data/webdata.json +116 -139
- package/dist/chunks/2020-rqm7qrje.js +4 -0
- package/dist/chunks/adapter-cyy8vbcx.js +4 -0
- package/dist/chunks/add-new-flow-8cgtejv2.js +4 -0
- package/dist/chunks/{cf-account-picker-xb8vvt2h.js → cf-account-picker-v3qd2dhw.js} +3 -3
- package/dist/chunks/cf-settings-1fnw874z.js +4 -0
- package/dist/chunks/{color-area-h8dbasp1.js → color-area-frd9y9q6.js} +2 -2
- package/dist/chunks/color-field-rebpbe33.js +4 -0
- package/dist/chunks/{color-slider-nz2mrzky.js → color-slider-6bzmk3s7.js} +2 -2
- package/dist/chunks/color-t2bnnj8y.js +4 -0
- package/dist/chunks/{combobox-6hs00996.js → combobox-c49x7gf1.js} +3 -3
- package/dist/chunks/components-y8yhdd7d.js +4 -0
- package/dist/chunks/content-source-9e7qvmsf.js +4 -0
- package/dist/chunks/convert-to-repeater-pd2w4z21.js +4 -0
- package/dist/chunks/editor-mb9jzdrw.js +4 -0
- package/dist/chunks/entry-commands-at0wnx3f.js +4 -0
- package/dist/chunks/extension-sections-4z9qq0gh.js +4 -0
- package/dist/chunks/file-ops-dtdvjmza.js +4 -0
- package/dist/chunks/files-8pf70854.js +4 -0
- package/dist/chunks/format-host-fd3m2y2a.js +4 -0
- package/dist/chunks/fs-events-hrk29vcv.js +4 -0
- package/dist/chunks/grid-open-bh74v82z.js +4 -0
- package/dist/chunks/{grid-view-2f03wcgw.js → grid-view-a51mgzmq.js} +17 -17
- package/dist/chunks/{icons-gb967qc8.js → icons-qp8fn1k8.js} +2 -2
- package/dist/chunks/{iframe-entry-btck0e9s.js → iframe-entry-mpwe5pwk.js} +4 -4
- package/dist/chunks/{iframe-entry-2x9bn5ng.js → iframe-entry-msd43x9h.js} +4 -4
- package/dist/chunks/iframe-entry-rt8jz5t8.js +5 -0
- package/dist/chunks/{iframe-entry-b5qgggyp.js → iframe-entry-v40cr813.js} +4 -4
- package/dist/chunks/iframe-host-q0s0wwy3.js +4 -0
- package/dist/chunks/{image-bake-4afr4y6a.js → image-bake-q9ajdjsj.js} +3 -3
- package/dist/chunks/{image-editor-x7t8jbjn.js → image-editor-ke9std9b.js} +3 -3
- package/dist/chunks/index-57ang7ma.js +4 -0
- package/dist/chunks/index-eytr7av4.js +17 -0
- package/dist/chunks/{index-yjrfeks8.js → index-x495reff.js} +3 -3
- package/dist/chunks/{javascript-hy2mws8v.js → javascript-qe34bvaz.js} +3 -3
- package/dist/chunks/jsonMode-m8rseh31.js +13 -0
- package/dist/chunks/jx-validate-bbd1mary.js +4 -0
- package/dist/chunks/layers-panel-wcjtdqsa.js +4 -0
- package/dist/chunks/layers-yscpys6q.js +4 -0
- package/dist/chunks/{listbox-7gka1h86.js → listbox-tktzq5xj.js} +2 -2
- package/dist/chunks/lit-html-qm5wbhqz.js +4 -0
- package/dist/chunks/media-library-2hfff88g.js +4 -0
- package/dist/chunks/media-open-4pb05cne.js +4 -0
- package/dist/chunks/media-picker-bzktmmwr.js +4 -0
- package/dist/chunks/menu-xc3cbgsd.js +4 -0
- package/dist/chunks/{monaco-binding-a51py33c.js → monaco-binding-7t38arh9.js} +3 -3
- package/dist/chunks/monaco-buffer-3vz94c9f.js +4 -0
- package/dist/chunks/monaco-setup-fstdew02.js +4 -0
- package/dist/chunks/notify-gbdcckrh.js +4 -0
- package/dist/chunks/parse-fpdzgk4c.js +4 -0
- package/dist/chunks/platform-9g72gdqr.js +4 -0
- package/dist/chunks/popover-cnmsav6b.js +4 -0
- package/dist/chunks/publish-panel-91x1cb5d.js +4 -0
- package/dist/chunks/publish-quick-e2dmf7eg.js +4 -0
- package/dist/chunks/publish-workspace-1x8mnzrj.js +4 -0
- package/dist/chunks/quick-search-2m4y6p1x.js +4 -0
- package/dist/chunks/references-yg1g9rwx.js +4 -0
- package/dist/chunks/right-panel-7mfyhzrk.js +4 -0
- package/dist/chunks/source-control-rrc4jgp5.js +4 -0
- package/dist/chunks/{split-m5rwn31z.js → split-d15yencd.js} +2 -2
- package/dist/chunks/studio-0jhhznxy.js +9 -0
- package/dist/chunks/studio-0nb57pjg.js +7 -0
- package/dist/chunks/{studio-s24mhq0z.js → studio-0wa4wv4s.js} +4 -4
- package/dist/chunks/studio-0z8kp4xk.js +5 -0
- package/dist/chunks/{studio-g0hxtcga.js → studio-18gc5t3v.js} +4 -4
- package/dist/chunks/{studio-4fnb5648.js → studio-1b79rpcb.js} +4 -4
- package/dist/chunks/studio-211q5j7m.js +4 -0
- package/dist/chunks/studio-213pyfxh.js +16 -0
- package/dist/chunks/studio-2b1smjvp.js +5 -0
- package/dist/chunks/{studio-nwf0c32p.js → studio-2v6tc9jp.js} +4 -4
- package/dist/chunks/studio-30j0wz99.js +5 -0
- package/dist/chunks/studio-3kmqhwtt.js +5 -0
- package/dist/chunks/studio-44drsmhw.js +5 -0
- package/dist/chunks/{studio-2pk7kc7z.js → studio-495b6f2r.js} +4 -4
- package/dist/chunks/{studio-jma2q8r9.js → studio-4gthb2fd.js} +4 -4
- package/dist/chunks/{studio-0gvvqj3h.js → studio-4xadhxj1.js} +4 -4
- package/dist/chunks/{studio-nvcn2ykc.js → studio-52mzbxqm.js} +4 -4
- package/dist/chunks/{studio-v7a7yb5w.js → studio-56mmnsw8.js} +7 -7
- package/dist/chunks/studio-59hz8f4r.js +5 -0
- package/dist/chunks/{studio-sce30r7b.js → studio-5k9mfafh.js} +3 -3
- package/dist/chunks/{studio-rr00ex23.js → studio-5qez462c.js} +6 -6
- package/dist/chunks/studio-6tnpg0vx.js +5 -0
- package/dist/chunks/studio-6w8mt734.js +6 -0
- package/dist/chunks/studio-7hm04x5z.js +5 -0
- package/dist/chunks/studio-8atmfjjm.js +7 -0
- package/dist/chunks/{studio-wmjmsw0p.js → studio-8n99z71s.js} +4 -4
- package/dist/chunks/studio-8vmad6x2.js +5 -0
- package/dist/chunks/studio-93p5k4r4.js +5 -0
- package/dist/chunks/{studio-2x972v58.js → studio-9gb0m81w.js} +4 -4
- package/dist/chunks/studio-a4ymg4vd.js +5 -0
- package/dist/chunks/studio-anc8067x.js +5 -0
- package/dist/chunks/{studio-2e4hry72.js → studio-dbdt67kr.js} +5 -5
- package/dist/chunks/studio-dbex091t.js +5 -0
- package/dist/chunks/{studio-amaz545p.js → studio-dj8ctxey.js} +4 -4
- package/dist/chunks/{studio-k2evngrj.js → studio-ed9bz68j.js} +5 -5
- package/dist/chunks/{studio-4bgvvzhv.js → studio-fe2d1fp5.js} +4 -4
- package/dist/chunks/{studio-45mj4jqe.js → studio-fgp4cyy3.js} +4 -4
- package/dist/chunks/studio-ft706q02.js +5 -0
- package/dist/chunks/studio-g79s1dyg.js +6 -0
- package/dist/chunks/{studio-pj216fmn.js → studio-gahswxp4.js} +4 -4
- package/dist/chunks/studio-gbqc12bk.js +5 -0
- package/dist/chunks/studio-gxphbetb.js +6 -0
- package/dist/chunks/studio-hdf01j7q.js +7 -0
- package/dist/chunks/studio-hf259xzd.js +48 -0
- package/dist/chunks/studio-jbva4wxw.js +5 -0
- package/dist/chunks/studio-jj9gf1qc.js +5 -0
- package/dist/chunks/studio-jsjg3mjv.js +5 -0
- package/dist/chunks/studio-jtvb8hd7.js +5 -0
- package/dist/chunks/{studio-g9brbewz.js → studio-kp4vzrct.js} +4 -4
- package/dist/chunks/studio-kpx02gaa.js +7 -0
- package/dist/chunks/{studio-3na1r48j.js → studio-kv1728ex.js} +8 -8
- package/dist/chunks/studio-m25ry0mf.js +5 -0
- package/dist/chunks/studio-mcbwmkyh.js +5 -0
- package/dist/chunks/studio-meajkr51.js +5 -0
- package/dist/chunks/studio-mkjrjpr2.js +6 -0
- package/dist/chunks/{studio-eqcddvc2.js → studio-mr084886.js} +201 -201
- package/dist/chunks/studio-mrb10hv3.js +5 -0
- package/dist/chunks/{studio-0939t1q1.js → studio-nhpg405j.js} +4 -4
- package/dist/chunks/studio-nsec0hxm.js +64 -0
- package/dist/chunks/{studio-xx3r3sb0.js → studio-nv0mh0p5.js} +4 -4
- package/dist/chunks/studio-pafmb06w.js +5 -0
- package/dist/chunks/{studio-pm17wdc4.js → studio-pgftd79b.js} +4 -4
- package/dist/chunks/{studio-4zd6an2b.js → studio-pk961xgz.js} +4 -4
- package/dist/chunks/studio-pzbr7g05.js +5 -0
- package/dist/chunks/studio-qcc5hsw2.js +10 -0
- package/dist/chunks/studio-qfq58c8n.js +5 -0
- package/dist/chunks/studio-qvb2jr83.js +5 -0
- package/dist/chunks/studio-r85zw6ay.js +5 -0
- package/dist/chunks/studio-s0kwce5w.js +9 -0
- package/dist/chunks/studio-s6ymkf3q.js +14 -0
- package/dist/chunks/{studio-0x6f3nk9.js → studio-s7p5ccxa.js} +4 -4
- package/dist/chunks/{studio-1410q5dw.js → studio-s9jjap3x.js} +4 -4
- package/dist/chunks/{studio-evq47j2z.js → studio-t5hde1ws.js} +4 -4
- package/dist/chunks/studio-vj9yv897.js +5 -0
- package/dist/chunks/studio-wmak8xjw.js +5 -0
- package/dist/chunks/{studio-bnc50f9h.js → studio-wqsmvtqj.js} +4 -4
- package/dist/chunks/studio-x3z3rhej.js +5 -0
- package/dist/chunks/{studio-bg8pzprg.js → studio-xb55kpmy.js} +4 -4
- package/dist/chunks/studio-xxtdajmv.js +12 -0
- package/dist/chunks/studio-xxttcym1.js +5 -0
- package/dist/chunks/{studio-90zkgne5.js → studio-y2zsnzye.js} +7 -7
- package/dist/chunks/{studio-3qr08sxq.js → studio-y3tjh2xb.js} +3 -3
- package/dist/chunks/studio-y4f1rdsj.js +5 -0
- package/dist/chunks/studio-ybkd5w8k.js +5 -0
- package/dist/chunks/{studio-tpxcryf2.js → studio-yetr4gg1.js} +4 -4
- package/dist/chunks/studio-z295anhs.js +6 -0
- package/dist/chunks/studio-z89e6wpg.js +9 -0
- package/dist/chunks/{studio-5tet77de.js → studio-zbwr6prc.js} +3 -3
- package/dist/chunks/{studio-daksw8kh.js → studio-zdbsv5q2.js} +4 -4
- package/dist/chunks/{studio-abh9kfdh.js → studio-zvthj8hg.js} +4 -4
- package/dist/chunks/toast-2kn2vrn4.js +4 -0
- package/dist/chunks/{toast-host-g3r2s0kn.js → toast-host-3rxeyj3d.js} +3 -3
- package/dist/chunks/{tooltip-dj1pvgz7.js → tooltip-ghkxkasp.js} +3 -3
- package/dist/chunks/{tree-j0ebwr5s.js → tree-vw4nc63s.js} +2 -2
- package/dist/chunks/tsMode-7gngmamr.js +14 -0
- package/dist/chunks/workspace-gv0hxgsm.js +4 -0
- package/dist/chunks/workspace-home-j7e0mjy5.js +4 -0
- package/dist/chunks/{ws-client-arz2qsy9.js → ws-client-vs11sxc5.js} +3 -3
- package/dist/iframe-entry.js +14 -14
- package/dist/manifest.json +84 -83
- package/dist/studio.js +99 -98
- package/dist/workers/ts.worker.js +3 -3
- package/package.json +12 -12
- package/src/browse/library-layouts.ts +9 -12
- package/src/browse/library-pane.ts +22 -16
- package/src/browse/library-source.ts +3 -4
- package/src/browse/preview-style.ts +2 -1
- package/src/canvas/canvas-live-render.ts +2 -5
- package/src/canvas/canvas-page-context.ts +5 -0
- package/src/canvas/canvas-render.ts +6 -1
- package/src/canvas/iframe-drop.ts +84 -1
- package/src/canvas/iframe-entry.ts +9 -6
- package/src/canvas/iframe-render.ts +13 -0
- package/src/collab/collab-session.ts +2 -2
- package/src/commands/levels.ts +1 -1
- package/src/content/entry-commands.ts +32 -27
- package/src/content/entry-editor.ts +46 -44
- package/src/content/entry-inspector.ts +15 -12
- package/src/content/entry-model.ts +3 -3
- package/src/content/entry-summaries.ts +5 -5
- package/src/content/hold-state.ts +172 -0
- package/src/content/page-summaries.ts +10 -14
- package/src/create/add-new-flow.ts +2 -1
- package/src/editor/convert-targets.ts +2 -23
- package/src/editor/inline-edit.ts +6 -1
- package/src/editor/repeater-scope.ts +333 -74
- package/src/editor/slash-menu.ts +35 -17
- package/src/files/files.ts +18 -0
- package/src/format/format-choices.ts +5 -5
- package/src/format/format-host.ts +35 -3
- package/src/grid/redirects-grid.ts +4 -4
- package/src/grid/sources/content-source.ts +42 -66
- package/src/locale/element-names.ts +131 -0
- package/src/panels/block-action-bar.ts +3 -4
- package/src/panels/dnd.ts +3 -1
- package/src/panels/elements-panel.ts +93 -36
- package/src/panels/events-panel.ts +6 -2
- package/src/panels/formula-workspace.ts +9 -0
- package/src/panels/imports-panel.ts +3 -2
- package/src/panels/navigator-panels.ts +10 -10
- package/src/panels/panel-registry.ts +3 -3
- package/src/panels/properties-panel.ts +127 -35
- package/src/panels/site-panel.ts +2 -1
- package/src/panels/statement-editor.ts +4 -0
- package/src/panels/style-panel.ts +2 -1
- package/src/panels/styles-panel.ts +2 -2
- package/src/panels/tab-strip.ts +29 -26
- package/src/platforms/cloud.ts +5 -2
- package/src/services/ai-command-tools.ts +2 -1
- package/src/services/ai-project-tools.ts +21 -8
- package/src/services/ai-tools.ts +5 -5
- package/src/services/ai-write-report.ts +22 -4
- package/src/services/context-manager.ts +110 -2
- package/src/services/document-assistant.ts +2 -2
- package/src/services/harness/chat-reducer.ts +9 -4
- package/src/services/jxn-language.ts +110 -0
- package/src/services/model-uri.ts +3 -0
- package/src/services/monaco-setup.ts +6 -2
- package/src/services/render-critic.ts +234 -12
- package/src/services/tool-executor.ts +16 -8
- package/src/settings/contributed-section.ts +2 -1
- package/src/settings/extension-commands.ts +2 -1
- package/src/settings/general-settings.ts +2 -2
- package/src/settings/settings-document.ts +5 -5
- package/src/shell.ts +1 -1
- package/src/studio.ts +28 -11
- package/src/style/variables-view.ts +2 -2
- package/src/surfaces/collections-panel.json +1 -1
- package/src/surfaces/collections-panel.ts +1 -1
- package/src/surfaces/entry-editor.json +10 -10
- package/src/surfaces/entry-editor.ts +22 -19
- package/src/surfaces/entry-inspector.json +11 -11
- package/src/surfaces/entry-inspector.ts +17 -10
- package/src/surfaces/panel-elements.json +466 -206
- package/src/surfaces/panel-elements.ts +61 -25
- package/src/surfaces/tab-strip.json +5 -5
- package/src/surfaces/tab-strip.ts +2 -2
- package/src/tabs/project-config.ts +13 -13
- package/src/tabs/tab.ts +7 -2
- package/src/types.ts +13 -2
- package/src/ui/expression-editor.ts +10 -5
- package/src/ui/schema-form.ts +18 -1
- package/src/utils/edit-display.ts +27 -1
- package/src/view.ts +9 -2
- package/src/workspace/tab-workspace.ts +2 -2
- package/src/workspace/workspace-back.ts +2 -2
- package/dist/chunks/2020-906vx2dq.js +0 -4
- package/dist/chunks/adapter-hnvad61x.js +0 -4
- package/dist/chunks/add-new-flow-v0pm96mr.js +0 -4
- package/dist/chunks/cf-settings-mkbcz4z2.js +0 -4
- package/dist/chunks/color-04361ttk.js +0 -4
- package/dist/chunks/color-field-qv5r2b16.js +0 -4
- package/dist/chunks/components-w5zqwva1.js +0 -4
- package/dist/chunks/content-source-g38w0d3f.js +0 -4
- package/dist/chunks/convert-to-repeater-r8sfxjjm.js +0 -4
- package/dist/chunks/editor-wvf5e6j9.js +0 -4
- package/dist/chunks/entry-commands-t13dyw3c.js +0 -4
- package/dist/chunks/extension-sections-vjzw46xb.js +0 -4
- package/dist/chunks/file-ops-1sghzgcr.js +0 -4
- package/dist/chunks/files-zrqtgt41.js +0 -4
- package/dist/chunks/format-host-9gy76erh.js +0 -4
- package/dist/chunks/fs-events-45rhg3me.js +0 -4
- package/dist/chunks/grid-open-t4fq6bbm.js +0 -4
- package/dist/chunks/iframe-entry-42hpmwz5.js +0 -5
- package/dist/chunks/iframe-host-9aq8ze3q.js +0 -4
- package/dist/chunks/index-f9qr7kzh.js +0 -4
- package/dist/chunks/jsonMode-vj30te2h.js +0 -13
- package/dist/chunks/jx-validate-r4ax11e3.js +0 -4
- package/dist/chunks/layers-m49nmwvx.js +0 -4
- package/dist/chunks/layers-panel-zr92pwdp.js +0 -4
- package/dist/chunks/lit-html-k2mb9k0r.js +0 -4
- package/dist/chunks/media-library-j74rt0w4.js +0 -4
- package/dist/chunks/media-open-mypr0k6b.js +0 -4
- package/dist/chunks/media-picker-pemtbsjv.js +0 -4
- package/dist/chunks/menu-d2jxpgma.js +0 -4
- package/dist/chunks/monaco-buffer-pa0g6ax7.js +0 -4
- package/dist/chunks/monaco-setup-0hw9f835.js +0 -4
- package/dist/chunks/notify-bwehrp7q.js +0 -4
- package/dist/chunks/parse-hee7ne9b.js +0 -4
- package/dist/chunks/platform-kv0k65cy.js +0 -4
- package/dist/chunks/popover-9tf4cjrk.js +0 -4
- package/dist/chunks/publish-panel-vg1n880b.js +0 -4
- package/dist/chunks/publish-quick-4fwngwrw.js +0 -4
- package/dist/chunks/publish-workspace-jv4w2bx2.js +0 -4
- package/dist/chunks/quick-search-nh29rt8a.js +0 -4
- package/dist/chunks/references-vqyr5s9x.js +0 -4
- package/dist/chunks/right-panel-3ws9f2w0.js +0 -4
- package/dist/chunks/source-control-h3hcs5gy.js +0 -4
- package/dist/chunks/studio-0khd8xtx.js +0 -4
- package/dist/chunks/studio-1ctnw3xv.js +0 -5
- package/dist/chunks/studio-1ks5d9y4.js +0 -5
- package/dist/chunks/studio-1y9mab9m.js +0 -5
- package/dist/chunks/studio-2kpxm0eh.js +0 -7
- package/dist/chunks/studio-2zz4qbv9.js +0 -5
- package/dist/chunks/studio-3pz4t5a2.js +0 -5
- package/dist/chunks/studio-4f7thq0p.js +0 -5
- package/dist/chunks/studio-61rg2cz5.js +0 -5
- package/dist/chunks/studio-7rrdbvex.js +0 -5
- package/dist/chunks/studio-9c6cbq2c.js +0 -4
- package/dist/chunks/studio-9dq7rwc7.js +0 -5
- package/dist/chunks/studio-9gr7xs9r.js +0 -6
- package/dist/chunks/studio-a88ej93p.js +0 -7
- package/dist/chunks/studio-acer0g8w.js +0 -5
- package/dist/chunks/studio-az3wvyh2.js +0 -5
- package/dist/chunks/studio-brh8d2pr.js +0 -5
- package/dist/chunks/studio-c2fa783p.js +0 -9
- package/dist/chunks/studio-cfnn70h1.js +0 -5
- package/dist/chunks/studio-cqxj4fcy.js +0 -5
- package/dist/chunks/studio-cw11h078.js +0 -5
- package/dist/chunks/studio-dn56z7sn.js +0 -5
- package/dist/chunks/studio-egnjpyde.js +0 -9
- package/dist/chunks/studio-ejrypvmq.js +0 -5
- package/dist/chunks/studio-frc16j8v.js +0 -10
- package/dist/chunks/studio-gnazwtv3.js +0 -5
- package/dist/chunks/studio-hfqpfmpk.js +0 -5
- package/dist/chunks/studio-hh394phe.js +0 -5
- package/dist/chunks/studio-j0zf5x2x.js +0 -5
- package/dist/chunks/studio-j5fcp6gj.js +0 -5
- package/dist/chunks/studio-jd1qb1st.js +0 -7
- package/dist/chunks/studio-k0xzrse2.js +0 -64
- package/dist/chunks/studio-k1xqrgm5.js +0 -5
- package/dist/chunks/studio-k3we3kqb.js +0 -5
- package/dist/chunks/studio-knsjyx7n.js +0 -5
- package/dist/chunks/studio-mrjtnm62.js +0 -5
- package/dist/chunks/studio-n9qba0gx.js +0 -5
- package/dist/chunks/studio-nzemfbkk.js +0 -14
- package/dist/chunks/studio-pwctzka3.js +0 -16
- package/dist/chunks/studio-q80fg1d6.js +0 -12
- package/dist/chunks/studio-q9hx2qp0.js +0 -5
- package/dist/chunks/studio-qrzmfaf6.js +0 -5
- package/dist/chunks/studio-r6mx7vm5.js +0 -7
- package/dist/chunks/studio-swedpd82.js +0 -5
- package/dist/chunks/studio-tmfcn347.js +0 -6
- package/dist/chunks/studio-v5wktj6f.js +0 -5
- package/dist/chunks/studio-vpevkfxw.js +0 -5
- package/dist/chunks/studio-wqfaqw4m.js +0 -5
- package/dist/chunks/studio-xpx91fby.js +0 -6
- package/dist/chunks/studio-xva3hzkv.js +0 -5
- package/dist/chunks/studio-z3fztxrp.js +0 -9
- package/dist/chunks/studio-z9453ypn.js +0 -6
- package/dist/chunks/toast-w11qpkyz.js +0 -4
- package/dist/chunks/tsMode-jg1dtphj.js +0 -14
- package/dist/chunks/workspace-6ztqcrsh.js +0 -4
- package/dist/chunks/workspace-home-rse9h8a0.js +0 -4
- package/src/content/draft-state.ts +0 -108
package/src/editor/slash-menu.ts
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
import { openSlashMenuSurface } from "../surfaces/slash-menu";
|
|
30
30
|
import { componentSlashCommands } from "./insert-component";
|
|
31
|
+
import { elementName } from "../locale/element-names";
|
|
31
32
|
import { rectOf } from "../utils/geometry";
|
|
32
33
|
import type {
|
|
33
34
|
SlashMenuGroup,
|
|
@@ -39,24 +40,41 @@ import type { SlashCommand } from "./inline-edit";
|
|
|
39
40
|
|
|
40
41
|
// ─── Commands ─────────────────────────────────────────────────────────────────
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
43
|
+
/**
|
|
44
|
+
* The HTML elements the slash menu offers, in this order. Each row's label is the element's name in
|
|
45
|
+
* the locale catalog (`locale/element-names.ts`), so the menu, the Insert palette and the convert
|
|
46
|
+
* menu call an element the same thing in every language. A row carries no description: the
|
|
47
|
+
* catalog's are sentences, written for the palette's tooltips, and a menu row draws its description
|
|
48
|
+
* on the label's own line and cuts it off there, while a name like "Bulleted list" already says
|
|
49
|
+
* what the row inserts.
|
|
50
|
+
*/
|
|
51
|
+
const SLASH_TAGS = [
|
|
52
|
+
"h1",
|
|
53
|
+
"h2",
|
|
54
|
+
"h3",
|
|
55
|
+
"p",
|
|
56
|
+
"ul",
|
|
57
|
+
"ol",
|
|
58
|
+
"blockquote",
|
|
59
|
+
"img",
|
|
60
|
+
"hr",
|
|
61
|
+
"button",
|
|
62
|
+
"a",
|
|
63
|
+
"pre",
|
|
64
|
+
"table",
|
|
65
|
+
"div",
|
|
66
|
+
"section",
|
|
58
67
|
];
|
|
59
68
|
|
|
69
|
+
/** The element rows, named in the reader's language at the moment the menu opens. */
|
|
70
|
+
function elementCommands(): SlashCommand[] {
|
|
71
|
+
return SLASH_TAGS.map((tag) => ({
|
|
72
|
+
description: "",
|
|
73
|
+
label: elementName(tag),
|
|
74
|
+
tag,
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
|
|
60
78
|
// ─── State ────────────────────────────────────────────────────────────────────
|
|
61
79
|
|
|
62
80
|
/** Callbacks a caller passes to {@link showSlashMenu}/{@link showSlashMenuAtRect}. */
|
|
@@ -127,7 +145,7 @@ export function showSlashMenuAtRect(
|
|
|
127
145
|
* on offer.
|
|
128
146
|
*/
|
|
129
147
|
function standardCommands(): SlashCommand[] {
|
|
130
|
-
return [...
|
|
148
|
+
return [...elementCommands(), ...componentSlashCommands()];
|
|
131
149
|
}
|
|
132
150
|
|
|
133
151
|
/** Everything on offer right now: what the caller named, or the standard set. */
|
package/src/files/files.ts
CHANGED
|
@@ -286,6 +286,24 @@ export async function loadProject() {
|
|
|
286
286
|
}
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Show the documents the operating system asks of this window, now and from here on.
|
|
291
|
+
*
|
|
292
|
+
* Called once the window has finished loading whatever it holds, so a document opens on top of the
|
|
293
|
+
* restored session rather than being replaced by it. Called whether or not there is a project yet,
|
|
294
|
+
* because a welcome window can be given one later and then be asked for a document in it.
|
|
295
|
+
*/
|
|
296
|
+
export function followOpenDocuments(): void {
|
|
297
|
+
getPlatform().subscribeOpenDocument?.((path) => {
|
|
298
|
+
// A window whose project did not load has been told why, and has nowhere to put a tab.
|
|
299
|
+
if (!projectState) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
// It reports its own failure ("Could not open …"), as it does for every other caller.
|
|
303
|
+
void openFileInTab(path);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
289
307
|
// ─── Open Project (PAL-based) ─────────────────────────────────────────────
|
|
290
308
|
|
|
291
309
|
/**
|
|
@@ -69,11 +69,11 @@ function declaresDocumentKind(kinds: readonly string[]): boolean {
|
|
|
69
69
|
/**
|
|
70
70
|
* Whether this path is (or is used as) a layout.
|
|
71
71
|
*
|
|
72
|
-
*
|
|
73
|
-
* and throws, `packages/studio/src/site-context.ts` `JSON.parse`s and silently
|
|
74
|
-
* there is no `"layout"` document kind for a format to declare, so this clause
|
|
75
|
-
* cannot be derived. `layouts/` is excluded by {@link DOCUMENT_ROOTS} already;
|
|
76
|
-
* project whose `layout` points somewhere inside a document root.
|
|
72
|
+
* A layout is native at both readers — `packages/compiler/src/site/layout-resolver.ts` reads
|
|
73
|
+
* `.json` and `.jxn` and throws, `packages/studio/src/site-context.ts` `JSON.parse`s and silently
|
|
74
|
+
* returns null — and there is no `"layout"` document kind for a format to declare, so this clause
|
|
75
|
+
* is hand-written and cannot be derived. `layouts/` is excluded by {@link DOCUMENT_ROOTS} already;
|
|
76
|
+
* what remains is a project whose `layout` points somewhere inside a document root.
|
|
77
77
|
*/
|
|
78
78
|
function isLayoutPath(path: string): boolean {
|
|
79
79
|
const declared = projectState?.projectConfig?.layout;
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* Format classes are auto-discovered server-side from the project imports map
|
|
5
5
|
* (specs/extensions.md); the studio introspects them via the PAL (`listFormats`) and invokes
|
|
6
6
|
* parse/serialize capabilities via `formatAction` (POST /__studio/format in the dev server, RPC on
|
|
7
|
-
* desktop). The studio
|
|
7
|
+
* desktop). The studio holds no other format knowledge: `.json` is native, and `.jxn` is read and
|
|
8
|
+
* written here in the same process with the same code as everywhere else (`spec.md` §22), so a host
|
|
9
|
+
* with no extension runtime has it too.
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
import { setProjectSchemasForMonaco } from "../services/monaco-lazy";
|
|
@@ -17,7 +19,8 @@ import type {
|
|
|
17
19
|
StudioPlatform,
|
|
18
20
|
} from "../types";
|
|
19
21
|
import { isString } from "@jxsuite/schema/guards";
|
|
20
|
-
import
|
|
22
|
+
import { parseJxn, serializeJxn } from "@jxsuite/schema/jxn";
|
|
23
|
+
import type { JxDocument, JxDocumentValue, JxMutableNode } from "@jxsuite/schema/types";
|
|
21
24
|
|
|
22
25
|
export interface StudioFormatHints {
|
|
23
26
|
icon?: string;
|
|
@@ -59,6 +62,24 @@ export interface StudioFormat {
|
|
|
59
62
|
capabilities: Record<string, { identifier: string; timing: string[] }>;
|
|
60
63
|
}
|
|
61
64
|
|
|
65
|
+
/**
|
|
66
|
+
* What opening a `.jxn` file is told about its format. It is not in {@link getFormats}, which is
|
|
67
|
+
* what a project chose and what a picker offers: a new document is still `.json`.
|
|
68
|
+
*/
|
|
69
|
+
const JXN_FORMAT: StudioFormat = {
|
|
70
|
+
capabilities: {
|
|
71
|
+
parse: { identifier: "parse", timing: ["compiler", "server"] },
|
|
72
|
+
serialize: { identifier: "serialize", timing: ["compiler", "server"] },
|
|
73
|
+
},
|
|
74
|
+
documentKinds: ["page", "component"],
|
|
75
|
+
exportTarget: false,
|
|
76
|
+
extensions: [".jxn"],
|
|
77
|
+
mediaType: null,
|
|
78
|
+
name: "jxn",
|
|
79
|
+
remote: false,
|
|
80
|
+
studio: { documentMode: { default: "component" } },
|
|
81
|
+
};
|
|
82
|
+
|
|
62
83
|
let _formats: StudioFormat[] = [];
|
|
63
84
|
let _loaded: Promise<StudioFormat[]> | null = null;
|
|
64
85
|
|
|
@@ -235,6 +256,9 @@ export function getFormats(): StudioFormat[] {
|
|
|
235
256
|
/** Look up the format claiming a file extension (".md" or "md"), optionally by capability. */
|
|
236
257
|
export function formatByExtension(ext: string, capability?: string): StudioFormat | undefined {
|
|
237
258
|
const norm = ext.startsWith(".") ? ext.toLowerCase() : `.${ext.toLowerCase()}`;
|
|
259
|
+
if (norm === ".jxn") {
|
|
260
|
+
return JXN_FORMAT;
|
|
261
|
+
}
|
|
238
262
|
return _formats.find(
|
|
239
263
|
(f) => f.extensions.includes(norm) && (!capability || f.capabilities[capability]),
|
|
240
264
|
);
|
|
@@ -245,7 +269,7 @@ export function formatByName(name: string | null | undefined): StudioFormat | un
|
|
|
245
269
|
if (!name) {
|
|
246
270
|
return undefined;
|
|
247
271
|
}
|
|
248
|
-
return _formats.find((f) => f.name === name);
|
|
272
|
+
return name === JXN_FORMAT.name ? JXN_FORMAT : _formats.find((f) => f.name === name);
|
|
249
273
|
}
|
|
250
274
|
|
|
251
275
|
/** Error for opening a non-JSON file when no imported format class claims its extension. */
|
|
@@ -330,6 +354,9 @@ export async function formatParse(
|
|
|
330
354
|
source: string,
|
|
331
355
|
options?: FormatActionOptions,
|
|
332
356
|
): Promise<JxMutableNode> {
|
|
357
|
+
if (name === JXN_FORMAT.name) {
|
|
358
|
+
return parseJxn(source);
|
|
359
|
+
}
|
|
333
360
|
/* SAFETY: a format class's `parse` capability returns the Jx document it built from the source —
|
|
334
361
|
The contract every format class implements (specs/extensions.md) — and nothing here inspects
|
|
335
362
|
It before `splitFormatDocument` reads its keys. */
|
|
@@ -352,6 +379,11 @@ export async function formatSerialize(
|
|
|
352
379
|
doc: JxMutableNode,
|
|
353
380
|
options?: FormatActionOptions,
|
|
354
381
|
): Promise<string> {
|
|
382
|
+
if (name === JXN_FORMAT.name) {
|
|
383
|
+
// `source` is what keeps the comments: everything that did not change keeps its exact text.
|
|
384
|
+
// SAFETY: a document in its editing form, and every member of a JxDocument is optional.
|
|
385
|
+
return serializeJxn(doc as JxDocument, options?.source);
|
|
386
|
+
}
|
|
355
387
|
/* SAFETY: a format class's `serialize` capability returns the source text it wrote — the contract
|
|
356
388
|
Every format class implements (specs/extensions.md) — so the answer is a string. */
|
|
357
389
|
return (await formatAction({
|
|
@@ -29,7 +29,7 @@ import { projectState } from "../store";
|
|
|
29
29
|
import { clearProblems, notify, problemFindings } from "../services/notify";
|
|
30
30
|
import { showPromptDialog } from "../ui/layers";
|
|
31
31
|
import { activateTab, openTab, workspace } from "../workspace/workspace";
|
|
32
|
-
import {
|
|
32
|
+
import { projectConfigPath, commitProjectConfig } from "../tabs/project-config";
|
|
33
33
|
import { pageRoute } from "../panels/tab-strip";
|
|
34
34
|
import { createGridController, getGridController } from "./grid-controller";
|
|
35
35
|
import {
|
|
@@ -230,7 +230,7 @@ export function reportRedirectProblems(
|
|
|
230
230
|
action: "redirects.open",
|
|
231
231
|
detail: problem.detail,
|
|
232
232
|
key: `redirects.${problem.rule}:${problem.source}`,
|
|
233
|
-
path:
|
|
233
|
+
path: projectConfigPath(),
|
|
234
234
|
source: REDIRECTS_PROBLEM_SOURCE,
|
|
235
235
|
tier: "problem",
|
|
236
236
|
});
|
|
@@ -263,7 +263,7 @@ export function createRedirectsSource(): GridSource {
|
|
|
263
263
|
const load = () => rulesFromConfig(currentConfig());
|
|
264
264
|
|
|
265
265
|
return {
|
|
266
|
-
backingPaths: () => new Map([[
|
|
266
|
+
backingPaths: () => new Map([[projectConfigPath(), "*"]]),
|
|
267
267
|
capabilities: { delete: true, insert: true, remotePaging: false, remoteSort: false },
|
|
268
268
|
columns: () => Promise.resolve(redirectColumns()),
|
|
269
269
|
|
|
@@ -476,7 +476,7 @@ export function stageRedirectImport(
|
|
|
476
476
|
action: "redirects.import",
|
|
477
477
|
detail: notes.join("\n"),
|
|
478
478
|
key: "redirects.import",
|
|
479
|
-
path:
|
|
479
|
+
path: projectConfigPath(),
|
|
480
480
|
source: REDIRECTS_PROBLEM_SOURCE,
|
|
481
481
|
tier: "problem",
|
|
482
482
|
},
|
|
@@ -12,22 +12,18 @@
|
|
|
12
12
|
* Collections type their columns from the content-type schema (plus inferred extras found in the
|
|
13
13
|
* data); the pages grid is fully inference-driven.
|
|
14
14
|
*
|
|
15
|
-
* **A collection listing obeys the
|
|
16
|
-
* `content/
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
15
|
+
* **A collection listing obeys the hold perspective (site-architecture.md §7.6).**
|
|
16
|
+
* `content/hold-state.ts` owns both the one flag and the reading of a project's rules, and this
|
|
17
|
+
* source calls it rather than reading fields itself, which is what makes "including held entries"
|
|
18
|
+
* mean the same thing here as it does on the tab chip. It can, because that module does not import
|
|
19
|
+
* the richer `content/entry-model.ts`, which asks THIS file where a collection's entries live, so
|
|
20
|
+
* an edge back to it would be the cycle `import/no-cycle` rejects.
|
|
21
21
|
*/
|
|
22
22
|
import { getPlatform } from "../../platform";
|
|
23
23
|
import { projectState } from "../../store";
|
|
24
24
|
import { workspace } from "../../workspace/workspace";
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
applyDraftFilter,
|
|
28
|
-
hasDraftAxis,
|
|
29
|
-
includingDrafts,
|
|
30
|
-
} from "../../content/draft-state";
|
|
25
|
+
import { applyHoldFilter, holdSwitch, includingHeld } from "../../content/hold-state";
|
|
26
|
+
import type { HoldBearer } from "../../content/hold-state";
|
|
31
27
|
import { getGridController } from "../grid-controller";
|
|
32
28
|
import { isRecentLocal, markLocalMutation } from "../../files/fs-events";
|
|
33
29
|
import {
|
|
@@ -275,39 +271,38 @@ interface EntryFileSourceOptions {
|
|
|
275
271
|
/** Inferred columns to front-load, in order (e.g. title/description for pages). */
|
|
276
272
|
priorityFields?: string[];
|
|
277
273
|
/**
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
* to explain why.
|
|
274
|
+
* The collection's definition, when this listing is subject to the hold perspective
|
|
275
|
+
* (site-architecture.md §7.6). Collections are and the pages tree is not: `hold` rules are
|
|
276
|
+
* written on a content type, and a page belongs to none.
|
|
282
277
|
*/
|
|
283
|
-
|
|
278
|
+
holdDef?: () => HoldBearer | null | undefined;
|
|
284
279
|
}
|
|
285
280
|
|
|
286
281
|
/**
|
|
287
|
-
* The live sources that obey the
|
|
282
|
+
* The live sources that obey the hold perspective.
|
|
288
283
|
*
|
|
289
284
|
* A `WeakSet` of the source objects rather than a list of tab-id prefixes: whether a listing is
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
285
|
+
* filtered is decided by one `holdDef` in this file, and re-deriving it from `"grid://collection/"`
|
|
286
|
+
* in {@link reloadHoldAwareGrids} would be a second answer free to drift. Membership is added by the
|
|
287
|
+
* engine off that same option, so the two cannot disagree. Weak, so a closed tab's source is
|
|
288
|
+
* collectable.
|
|
294
289
|
*/
|
|
295
|
-
const
|
|
290
|
+
const holdAwareSources = new WeakSet<GridSource>();
|
|
296
291
|
|
|
297
292
|
/**
|
|
298
|
-
* Reload every open grid whose rows depend on the
|
|
293
|
+
* Reload every open grid whose rows depend on the hold perspective.
|
|
299
294
|
*
|
|
300
295
|
* The perspective is a flag, and a grid holds its rows in controller state that was loaded once —
|
|
301
|
-
* so `content.
|
|
296
|
+
* so `content.setIncludeHeld` changing the flag has to say so. `load()` (not `refresh()`) is the
|
|
302
297
|
* right verb: it re-pulls columns and rows and leaves the edit buffer alone, whereas `refresh()`
|
|
303
298
|
* would stop to ask an author with pending edits whether to discard them, which is not a question a
|
|
304
299
|
* change of perspective should provoke.
|
|
305
300
|
*/
|
|
306
|
-
export async function
|
|
301
|
+
export async function reloadHoldAwareGrids(): Promise<void> {
|
|
307
302
|
const reloads: Promise<void>[] = [];
|
|
308
303
|
for (const tab of workspace.tabs.values()) {
|
|
309
304
|
const controller = getGridController(tab);
|
|
310
|
-
if (controller &&
|
|
305
|
+
if (controller && holdAwareSources.has(controller.source)) {
|
|
311
306
|
reloads.push(controller.load());
|
|
312
307
|
}
|
|
313
308
|
}
|
|
@@ -317,8 +312,8 @@ export async function reloadDraftAwareGrids(): Promise<void> {
|
|
|
317
312
|
/** Shared engine for frontmatter-file sources (collections, pages). */
|
|
318
313
|
function createEntryFileSource(opts: EntryFileSourceOptions): GridSource {
|
|
319
314
|
const source = buildEntryFileSource(opts);
|
|
320
|
-
if (opts.
|
|
321
|
-
|
|
315
|
+
if (opts.holdDef) {
|
|
316
|
+
holdAwareSources.add(source);
|
|
322
317
|
}
|
|
323
318
|
return source;
|
|
324
319
|
}
|
|
@@ -420,43 +415,26 @@ function buildEntryFileSource(opts: EntryFileSourceOptions): GridSource {
|
|
|
420
415
|
extraColumns.sort((a, b) => rank(a) - rank(b));
|
|
421
416
|
}
|
|
422
417
|
|
|
423
|
-
columnsCache =
|
|
418
|
+
columnsCache = withHoldColumn([pathColumn, ...schemaColumns, ...extraColumns]);
|
|
424
419
|
return columnsCache;
|
|
425
420
|
};
|
|
426
421
|
|
|
427
|
-
/** Whether this listing has a draft axis at all — the schema declares one, or an entry claims one. */
|
|
428
|
-
const draftAxis = (): boolean => {
|
|
429
|
-
if (!opts.drafts) {
|
|
430
|
-
return false;
|
|
431
|
-
}
|
|
432
|
-
const schema = opts.schema();
|
|
433
|
-
return (
|
|
434
|
-
hasDraftAxis(schema) ||
|
|
435
|
-
[...entries.values()].some((entry) => hasDraftAxis(schema, entry.frontmatter))
|
|
436
|
-
);
|
|
437
|
-
};
|
|
438
|
-
|
|
439
422
|
/**
|
|
440
|
-
* Put the
|
|
441
|
-
*
|
|
442
|
-
* A MOVE, and deliberately not a synthesis: wherever there is an axis there is already a column.
|
|
443
|
-
* A schema that declares `draft` gave `columnsFromSchema` one, and an entry that carries the
|
|
444
|
-
* field without the schema declaring it gave `inferColumnsFromRows` one — the two conditions of
|
|
445
|
-
* {@link hasDraftAxis} are exactly the two conditions that produce a column. Building a third here
|
|
446
|
-
* would be an unreachable branch pretending to be a safety net.
|
|
423
|
+
* Put the hold switch's column immediately after Path, when the collection's rules offer one.
|
|
447
424
|
*
|
|
425
|
+
* A MOVE, and deliberately not a synthesis: a field the rules name but neither the schema nor any
|
|
426
|
+
* entry states has no column to move, and inventing one would show a state nobody wrote down.
|
|
448
427
|
* What the move buys is that the one field deciding whether an entry appears in its own list is
|
|
449
428
|
* not sitting out at column nine, where declaration order or first-sight order happened to put
|
|
450
|
-
* it.
|
|
451
|
-
* column is ordinary data there, and promoting it would invent a workflow the project never
|
|
452
|
-
* declared.
|
|
429
|
+
* it.
|
|
453
430
|
*/
|
|
454
|
-
const
|
|
455
|
-
const
|
|
456
|
-
|
|
431
|
+
const withHoldColumn = (columns: GridColumn[]): GridColumn[] => {
|
|
432
|
+
const field = holdSwitch(opts.holdDef?.())?.field;
|
|
433
|
+
const index = columns.findIndex((column) => column.field === field);
|
|
434
|
+
if (index < 1) {
|
|
457
435
|
return columns;
|
|
458
436
|
}
|
|
459
|
-
const rest = columns.filter((column) => column.field !==
|
|
437
|
+
const rest = columns.filter((column) => column.field !== field);
|
|
460
438
|
return [rest[0]!, columns[index]!, ...rest.slice(1)];
|
|
461
439
|
};
|
|
462
440
|
|
|
@@ -661,15 +639,13 @@ function buildEntryFileSource(opts: EntryFileSourceOptions): GridSource {
|
|
|
661
639
|
async rows(): Promise<GridRowsResult> {
|
|
662
640
|
await load();
|
|
663
641
|
// The perspective is applied to the LISTING, not the load: `entries` keeps every parsed file,
|
|
664
|
-
// So the
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
)
|
|
672
|
-
: [...entries.entries()];
|
|
642
|
+
// So turning the perspective back on is a re-list rather than a re-read of the directory.
|
|
643
|
+
const listed = applyHoldFilter(
|
|
644
|
+
[...entries.entries()],
|
|
645
|
+
opts.holdDef?.(),
|
|
646
|
+
([, entry]) => entry.frontmatter,
|
|
647
|
+
includingHeld(),
|
|
648
|
+
);
|
|
673
649
|
const rows = listed.map(([path, entry]) => rowFor(path, entry));
|
|
674
650
|
return { rows, total: rows.length };
|
|
675
651
|
},
|
|
@@ -687,7 +663,7 @@ export function createCollectionSource(typeName: string): GridSource {
|
|
|
687
663
|
};
|
|
688
664
|
|
|
689
665
|
return createEntryFileSource({
|
|
690
|
-
|
|
666
|
+
holdDef: () => collectionInfo(typeName)?.def,
|
|
691
667
|
id: makeGridTabId({ kind: "collection", name: typeName }),
|
|
692
668
|
insertFormatName: (path) => {
|
|
693
669
|
const info = requireInfo();
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What Studio calls an HTML element, in the reader's language: "Heading 1" for `h1`, "Bulleted
|
|
3
|
+
* list" for `ul`, and the names of the Insert palette's categories (§5.3).
|
|
4
|
+
*
|
|
5
|
+
* **No web-platform database carries these names.** `@webref/elements` lists each element's tag and
|
|
6
|
+
* interface and nothing a person reads, and MDN's translated pages are prose, not data. So the
|
|
7
|
+
* names are Studio's own, kept as one catalog per language under `data/locales/<language>/`.
|
|
8
|
+
* English (`data/locales/en/elements.json`) is the source: it names every tag the palette offers,
|
|
9
|
+
* which `tests/locale-element-names.test.ts` holds it to, and a translation is a copy of that file
|
|
10
|
+
* with its values translated and its keys left alone, registered in {@link CATALOGS}.
|
|
11
|
+
*
|
|
12
|
+
* **The language is the reader's, and Studio has no setting for it yet.** {@link studioLocale}
|
|
13
|
+
* takes the first of the browser's preferred languages that has a catalog, by exact tag and then by
|
|
14
|
+
* base language (`fr-CA` reads `fr`), and falls back to English. A name the chosen catalog lacks
|
|
15
|
+
* falls back to the English one, and a tag no catalog knows is shown as itself, so a translation
|
|
16
|
+
* can land incomplete and an element a future `@webref` release adds is never nameless.
|
|
17
|
+
*
|
|
18
|
+
* Three surfaces read this module, so they cannot disagree about a name: the Insert palette's
|
|
19
|
+
* cards, the slash menu, and the block action bar's convert menu.
|
|
20
|
+
*
|
|
21
|
+
* @docs studio/design/elements
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import en from "../../data/locales/en/elements.json";
|
|
25
|
+
|
|
26
|
+
/** One element's entry: the name a card shows, and the line that says what it is for. */
|
|
27
|
+
export interface ElementEntry {
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** One language's catalog: category names by key, and element entries by tag. */
|
|
33
|
+
export interface ElementCatalog {
|
|
34
|
+
categories: Readonly<Record<string, string>>;
|
|
35
|
+
elements: Readonly<Record<string, ElementEntry>>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The language every lookup falls back to, and the catalog that is complete by contract. */
|
|
39
|
+
export const SOURCE_LOCALE = "en";
|
|
40
|
+
|
|
41
|
+
/** The source catalog, read through the open shape every other catalog has. */
|
|
42
|
+
const SOURCE: ElementCatalog = en;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Every catalog Studio ships, keyed by BCP 47 language tag. Adding a language is a new
|
|
46
|
+
* `data/locales/<tag>/elements.json` and one entry here.
|
|
47
|
+
*/
|
|
48
|
+
const CATALOGS = new Map([[SOURCE_LOCALE, SOURCE]]);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The best catalog for a list of preferred languages, most preferred first.
|
|
52
|
+
*
|
|
53
|
+
* Each preference is tried whole and then by its base language before the next one is, because a
|
|
54
|
+
* reader who asked for Canadian French first wants French before their second choice. The match is
|
|
55
|
+
* case-insensitive, as BCP 47 tags are.
|
|
56
|
+
*
|
|
57
|
+
* @param {readonly string[]} preferred - Language tags, most preferred first
|
|
58
|
+
* @param {readonly string[]} available - The tags a catalog exists for
|
|
59
|
+
* @returns {string} One of `available`, or {@link SOURCE_LOCALE}
|
|
60
|
+
*/
|
|
61
|
+
export function resolveLocale(
|
|
62
|
+
preferred: readonly string[],
|
|
63
|
+
available: readonly string[] = [...CATALOGS.keys()],
|
|
64
|
+
): string {
|
|
65
|
+
const byLower = new Map(available.map((tag) => [tag.toLowerCase(), tag]));
|
|
66
|
+
for (const tag of preferred) {
|
|
67
|
+
const lower = tag.toLowerCase();
|
|
68
|
+
const match = byLower.get(lower) ?? byLower.get(lower.split("-")[0] ?? "");
|
|
69
|
+
if (match) {
|
|
70
|
+
return match;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return SOURCE_LOCALE;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The browser's preferred languages, or none where there is no `navigator` to ask. Anything that is
|
|
78
|
+
* not a non-empty string is dropped: an embedder or a privacy setting can blank the answer, and a
|
|
79
|
+
* reader with no stated language gets English rather than an exception.
|
|
80
|
+
*/
|
|
81
|
+
function preferredLanguages(): readonly string[] {
|
|
82
|
+
if (typeof navigator === "undefined") {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
/* SAFETY: the DOM typings promise both members, but a navigator outside a browser window (Bun's
|
|
86
|
+
own, a worker's in some engines) lacks `languages`, so each is read as possibly absent and
|
|
87
|
+
checked before use. */
|
|
88
|
+
const { language, languages } = navigator as Partial<Pick<Navigator, "language" | "languages">>;
|
|
89
|
+
const listed: readonly unknown[] = languages && languages.length > 0 ? languages : [language];
|
|
90
|
+
return listed.filter((tag): tag is string => typeof tag === "string" && tag !== "");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The language Studio names elements in for this reader. */
|
|
94
|
+
export function studioLocale(): string {
|
|
95
|
+
return resolveLocale(preferredLanguages());
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The catalog for `locale`, or the source catalog when there is none. */
|
|
99
|
+
function catalog(locale: string): ElementCatalog {
|
|
100
|
+
return CATALOGS.get(locale) ?? SOURCE;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The name of the element `tag`: the catalog's, else the English one, else the tag itself.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} tag
|
|
107
|
+
* @param {string} [locale]
|
|
108
|
+
*/
|
|
109
|
+
export function elementName(tag: string, locale: string = studioLocale()): string {
|
|
110
|
+
return catalog(locale).elements[tag]?.name ?? SOURCE.elements[tag]?.name ?? tag;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* What the element `tag` is for, in one line, or `""` when no catalog says.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} tag
|
|
117
|
+
* @param {string} [locale]
|
|
118
|
+
*/
|
|
119
|
+
export function elementDescription(tag: string, locale: string = studioLocale()): string {
|
|
120
|
+
return catalog(locale).elements[tag]?.description ?? SOURCE.elements[tag]?.description ?? "";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The name of the palette category `key` (§5.3), else the English one, else the key.
|
|
125
|
+
*
|
|
126
|
+
* @param {string} key
|
|
127
|
+
* @param {string} [locale]
|
|
128
|
+
*/
|
|
129
|
+
export function categoryName(key: string, locale: string = studioLocale()): string {
|
|
130
|
+
return catalog(locale).categories[key] ?? SOURCE.categories[key] ?? key;
|
|
131
|
+
}
|
|
@@ -1193,11 +1193,10 @@ function openMergeTagMenu(anchor: HTMLElement): void {
|
|
|
1193
1193
|
// No live `$map` in edit mode — the perimeter renders one glyph template — so fields resolve
|
|
1194
1194
|
// Parent-side from schema, keyed off the selected element's doc path (which carries a `map` segment).
|
|
1195
1195
|
const selPath = getEditSnapshot().snapshot?.path ?? primarySelection(tab?.session.selection);
|
|
1196
|
-
|
|
1197
|
-
if (arrayNode && tab) {
|
|
1196
|
+
if (tab && selPath && findEnclosingRepeater(tab.doc.document, selPath)) {
|
|
1198
1197
|
const tokens = resolveRepeaterItemFields(
|
|
1199
|
-
|
|
1200
|
-
|
|
1198
|
+
tab.doc.document,
|
|
1199
|
+
selPath,
|
|
1201
1200
|
projectState?.projectConfig,
|
|
1202
1201
|
);
|
|
1203
1202
|
tags.push(...buildRepeaterTagsFromFields(tokens));
|
package/src/panels/dnd.ts
CHANGED
|
@@ -41,6 +41,7 @@ import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
|
41
41
|
import type { ComponentEntry } from "../files/components.js";
|
|
42
42
|
import type { ElementDragPayload } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
43
43
|
import type { DropTargetRecord } from "@atlaskit/pragmatic-drag-and-drop/types";
|
|
44
|
+
import { elementName } from "../locale/element-names";
|
|
44
45
|
|
|
45
46
|
interface DragCanDragArgs {
|
|
46
47
|
element: HTMLElement;
|
|
@@ -314,7 +315,8 @@ export function registerElementsDnD() {
|
|
|
314
315
|
const preview = row.querySelector(".element-card-preview");
|
|
315
316
|
if (preview && !preview.firstChild) {
|
|
316
317
|
const el = document.createElement(unsafeTags.has(tag) ? "span" : tag);
|
|
317
|
-
|
|
318
|
+
// The specimen reads the card's own name, so a heading previews as "Heading 1", not "h1".
|
|
319
|
+
el.textContent = elementName(tag);
|
|
318
320
|
preview.append(el);
|
|
319
321
|
}
|
|
320
322
|
const def = defaultDef(tag);
|