@jxsuite/studio 10.5.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-7sx2vexa.js → 2020-rqm7qrje.js} +3 -3
- package/dist/chunks/adapter-cyy8vbcx.js +4 -0
- package/dist/chunks/add-new-flow-8cgtejv2.js +4 -0
- package/dist/chunks/{cf-account-picker-ajh60k54.js → cf-account-picker-v3qd2dhw.js} +3 -3
- package/dist/chunks/cf-settings-1fnw874z.js +4 -0
- package/dist/chunks/{color-area-gbcftzh3.js → color-area-frd9y9q6.js} +2 -2
- package/dist/chunks/{color-field-85136598.js → color-field-rebpbe33.js} +3 -3
- package/dist/chunks/{color-slider-w1s3ndd8.js → color-slider-6bzmk3s7.js} +2 -2
- package/dist/chunks/color-t2bnnj8y.js +4 -0
- package/dist/chunks/{combobox-dk98wc5x.js → combobox-c49x7gf1.js} +2 -2
- 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-0v4hjspb.js → grid-view-a51mgzmq.js} +10 -10
- package/dist/chunks/{icons-j9eksgew.js → icons-qp8fn1k8.js} +2 -2
- package/dist/chunks/{iframe-entry-4zqtn3ry.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-54kwwhmg.js → iframe-entry-v40cr813.js} +2 -2
- package/dist/chunks/iframe-host-q0s0wwy3.js +4 -0
- package/dist/chunks/{image-bake-m8gzaan9.js → image-bake-q9ajdjsj.js} +3 -3
- package/dist/chunks/{image-editor-0afyws7c.js → image-editor-ke9std9b.js} +2 -2
- package/dist/chunks/index-57ang7ma.js +4 -0
- package/dist/chunks/index-eytr7av4.js +17 -0
- package/dist/chunks/{index-2q88g57p.js → index-x495reff.js} +3 -3
- package/dist/chunks/{javascript-5sxtr1g7.js → javascript-qe34bvaz.js} +3 -3
- package/dist/chunks/{jsonMode-m6sf47hq.js → jsonMode-m8rseh31.js} +8 -8
- 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-k5p41049.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-26nez152.js → menu-xc3cbgsd.js} +2 -2
- package/dist/chunks/{monaco-binding-1s2b8snq.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-fhdnt4be.js → popover-cnmsav6b.js} +2 -2
- 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-qh21j2b0.js → split-d15yencd.js} +2 -2
- package/dist/chunks/{studio-r5g7fx7q.js → studio-0jhhznxy.js} +6 -6
- package/dist/chunks/studio-0nb57pjg.js +7 -0
- package/dist/chunks/{studio-bpzwhn88.js → studio-0wa4wv4s.js} +4 -4
- package/dist/chunks/studio-0z8kp4xk.js +5 -0
- package/dist/chunks/{studio-jqvke4ac.js → studio-18gc5t3v.js} +4 -4
- package/dist/chunks/{studio-xh6rf05g.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-dst8y8vh.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-d5f7sskn.js → studio-495b6f2r.js} +4 -4
- package/dist/chunks/{studio-g65t6svn.js → studio-4gthb2fd.js} +4 -4
- package/dist/chunks/{studio-bmwgj8ez.js → studio-4xadhxj1.js} +4 -4
- package/dist/chunks/{studio-zav9hgb3.js → studio-52mzbxqm.js} +4 -4
- package/dist/chunks/{studio-thgexj3w.js → studio-56mmnsw8.js} +7 -7
- package/dist/chunks/studio-59hz8f4r.js +5 -0
- package/dist/chunks/{studio-bk0anq3b.js → studio-5k9mfafh.js} +3 -3
- package/dist/chunks/{studio-qarg1xf9.js → studio-5qez462c.js} +5 -5
- 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-69g6a0jn.js → studio-8n99z71s.js} +4 -4
- package/dist/chunks/{studio-ep1384t9.js → studio-8vmad6x2.js} +4 -4
- package/dist/chunks/{studio-6sxnqqw8.js → studio-93p5k4r4.js} +4 -4
- package/dist/chunks/{studio-hy936516.js → studio-9gb0m81w.js} +4 -4
- package/dist/chunks/{studio-ek4fb4eb.js → studio-a4ymg4vd.js} +4 -4
- package/dist/chunks/studio-anc8067x.js +5 -0
- package/dist/chunks/{studio-ketrgtph.js → studio-dbdt67kr.js} +5 -5
- package/dist/chunks/studio-dbex091t.js +5 -0
- package/dist/chunks/{studio-nngc45ct.js → studio-dj8ctxey.js} +4 -4
- package/dist/chunks/{studio-pgze9p34.js → studio-ed9bz68j.js} +5 -5
- package/dist/chunks/{studio-4ncxzw5m.js → studio-fe2d1fp5.js} +4 -4
- package/dist/chunks/{studio-m8hesdrw.js → studio-fgp4cyy3.js} +4 -4
- package/dist/chunks/{studio-151kgn69.js → studio-ft706q02.js} +4 -4
- package/dist/chunks/{studio-pv1ts08p.js → studio-g79s1dyg.js} +5 -5
- package/dist/chunks/{studio-zkkt13qd.js → studio-gahswxp4.js} +4 -4
- package/dist/chunks/{studio-x5ffyssw.js → studio-gbqc12bk.js} +4 -4
- package/dist/chunks/{studio-4jy967rj.js → studio-gxphbetb.js} +5 -5
- 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-td3h1644.js → studio-jj9gf1qc.js} +4 -4
- package/dist/chunks/studio-jsjg3mjv.js +5 -0
- package/dist/chunks/studio-jtvb8hd7.js +5 -0
- package/dist/chunks/{studio-c72v1kwc.js → studio-kp4vzrct.js} +4 -4
- package/dist/chunks/{studio-s4k1v7dt.js → studio-kpx02gaa.js} +6 -6
- package/dist/chunks/{studio-3yr1zdp9.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-nv5eq0f7.js → studio-mr084886.js} +181 -181
- package/dist/chunks/{studio-43pynnhz.js → studio-mrb10hv3.js} +4 -4
- package/dist/chunks/{studio-7qqb7hb8.js → studio-nhpg405j.js} +4 -4
- package/dist/chunks/{studio-n271vdsg.js → studio-nsec0hxm.js} +41 -41
- package/dist/chunks/{studio-ztzf8mce.js → studio-nv0mh0p5.js} +4 -4
- package/dist/chunks/studio-pafmb06w.js +5 -0
- package/dist/chunks/{studio-4s5bka8t.js → studio-pgftd79b.js} +4 -4
- package/dist/chunks/{studio-wn2cmf9q.js → studio-pk961xgz.js} +4 -4
- package/dist/chunks/studio-pzbr7g05.js +5 -0
- package/dist/chunks/{studio-15mfah02.js → studio-qcc5hsw2.js} +6 -6
- package/dist/chunks/studio-qfq58c8n.js +5 -0
- package/dist/chunks/{studio-j3bq30yz.js → studio-qvb2jr83.js} +4 -4
- package/dist/chunks/studio-r85zw6ay.js +5 -0
- package/dist/chunks/{studio-f69wcajk.js → studio-s0kwce5w.js} +5 -5
- package/dist/chunks/studio-s6ymkf3q.js +14 -0
- package/dist/chunks/{studio-8ncm9zwx.js → studio-s7p5ccxa.js} +4 -4
- package/dist/chunks/{studio-zzgp5yka.js → studio-s9jjap3x.js} +4 -4
- package/dist/chunks/{studio-ber1dgte.js → studio-t5hde1ws.js} +4 -4
- package/dist/chunks/studio-vj9yv897.js +5 -0
- package/dist/chunks/{studio-ajfq4fmr.js → studio-wmak8xjw.js} +4 -4
- package/dist/chunks/{studio-k05g7efh.js → studio-wqsmvtqj.js} +4 -4
- package/dist/chunks/studio-x3z3rhej.js +5 -0
- package/dist/chunks/{studio-7qr8k0v2.js → studio-xb55kpmy.js} +4 -4
- package/dist/chunks/{studio-f3awr5nc.js → studio-xxtdajmv.js} +7 -7
- package/dist/chunks/{studio-1a7zshnv.js → studio-xxttcym1.js} +4 -4
- package/dist/chunks/{studio-czpmzs9r.js → studio-y2zsnzye.js} +7 -7
- package/dist/chunks/{studio-9f5vcb4w.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-cv6gnjqd.js → studio-yetr4gg1.js} +4 -4
- package/dist/chunks/{studio-ehj3csbm.js → studio-z295anhs.js} +5 -5
- package/dist/chunks/{studio-pte5w7rf.js → studio-z89e6wpg.js} +8 -8
- package/dist/chunks/{studio-4z0cdh16.js → studio-zbwr6prc.js} +3 -3
- package/dist/chunks/{studio-w3aafbpk.js → studio-zdbsv5q2.js} +3 -3
- package/dist/chunks/{studio-eq5vqn1q.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-gtx40bjg.js → tooltip-ghkxkasp.js} +2 -2
- package/dist/chunks/{tree-a19s1c05.js → tree-vw4nc63s.js} +2 -2
- package/dist/chunks/{tsMode-3f116hth.js → tsMode-7gngmamr.js} +7 -7
- package/dist/chunks/workspace-gv0hxgsm.js +4 -0
- package/dist/chunks/workspace-home-j7e0mjy5.js +4 -0
- package/dist/chunks/{ws-client-cb49m87s.js → ws-client-vs11sxc5.js} +3 -3
- package/dist/iframe-entry.js +14 -14
- package/dist/manifest.json +84 -83
- package/dist/studio.js +80 -80
- package/package.json +12 -12
- package/src/browse/preview-style.ts +2 -1
- 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/create/add-new-flow.ts +2 -1
- package/src/editor/convert-targets.ts +2 -23
- 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/locale/element-names.ts +131 -0
- package/src/panels/dnd.ts +3 -1
- package/src/panels/elements-panel.ts +93 -36
- 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/site-panel.ts +2 -1
- package/src/panels/style-panel.ts +2 -1
- package/src/panels/styles-panel.ts +2 -2
- package/src/platforms/cloud.ts +5 -2
- package/src/services/ai-command-tools.ts +2 -1
- package/src/services/ai-project-tools.ts +18 -5
- 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/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/panel-elements.json +466 -206
- package/src/surfaces/panel-elements.ts +61 -25
- package/src/tabs/project-config.ts +13 -13
- package/src/tabs/tab.ts +7 -2
- package/src/types.ts +11 -2
- 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/adapter-12dxr3z7.js +0 -4
- package/dist/chunks/add-new-flow-any4pw2q.js +0 -4
- package/dist/chunks/cf-settings-hbj16kh3.js +0 -4
- package/dist/chunks/color-thzpppyc.js +0 -4
- package/dist/chunks/components-a98hzqw9.js +0 -4
- package/dist/chunks/content-source-jq3fjs3w.js +0 -4
- package/dist/chunks/convert-to-repeater-g66r6qp1.js +0 -4
- package/dist/chunks/editor-rth1rzj8.js +0 -4
- package/dist/chunks/entry-commands-02j0s19w.js +0 -4
- package/dist/chunks/extension-sections-rqrs7dr0.js +0 -4
- package/dist/chunks/file-ops-6a49er4c.js +0 -4
- package/dist/chunks/files-b2p9y0yz.js +0 -4
- package/dist/chunks/format-host-7xe57k1s.js +0 -4
- package/dist/chunks/fs-events-0xsy168g.js +0 -4
- package/dist/chunks/grid-open-h3q4fjy5.js +0 -4
- package/dist/chunks/iframe-entry-m24fza30.js +0 -5
- package/dist/chunks/iframe-host-8jcmaktg.js +0 -4
- package/dist/chunks/index-7nj2375a.js +0 -4
- package/dist/chunks/jx-validate-808zgbgy.js +0 -4
- package/dist/chunks/layers-0ffdtqjn.js +0 -4
- package/dist/chunks/layers-panel-rw62wtvf.js +0 -4
- package/dist/chunks/lit-html-9bz9rcfv.js +0 -4
- package/dist/chunks/media-library-2a5hdbek.js +0 -4
- package/dist/chunks/media-open-069kzhj4.js +0 -4
- package/dist/chunks/media-picker-4p4pd28y.js +0 -4
- package/dist/chunks/monaco-buffer-m9fd2dka.js +0 -4
- package/dist/chunks/monaco-setup-e0crh3v6.js +0 -4
- package/dist/chunks/notify-re3js157.js +0 -4
- package/dist/chunks/parse-hee7ne9b.js +0 -4
- package/dist/chunks/platform-rywy3tkg.js +0 -4
- package/dist/chunks/publish-panel-czkpg8m4.js +0 -4
- package/dist/chunks/publish-quick-b9wbhqj6.js +0 -4
- package/dist/chunks/publish-workspace-yy6d23b7.js +0 -4
- package/dist/chunks/quick-search-tp7pjnmv.js +0 -4
- package/dist/chunks/references-wd2yj8s1.js +0 -4
- package/dist/chunks/right-panel-x971k0zc.js +0 -4
- package/dist/chunks/source-control-k4p00hpe.js +0 -4
- package/dist/chunks/studio-1d8m5s38.js +0 -16
- package/dist/chunks/studio-35pmfjtx.js +0 -5
- package/dist/chunks/studio-44xqe0b7.js +0 -4
- package/dist/chunks/studio-48yzqzqr.js +0 -5
- package/dist/chunks/studio-5rq3e4q5.js +0 -5
- package/dist/chunks/studio-6hcfm9q3.js +0 -5
- package/dist/chunks/studio-6mf31e16.js +0 -5
- package/dist/chunks/studio-8w5pa88p.js +0 -7
- package/dist/chunks/studio-9c6cbq2c.js +0 -4
- package/dist/chunks/studio-aaw7wxv8.js +0 -5
- package/dist/chunks/studio-afx9brsc.js +0 -5
- package/dist/chunks/studio-dmgh2rrj.js +0 -5
- package/dist/chunks/studio-erjccagy.js +0 -5
- package/dist/chunks/studio-ex1644g2.js +0 -5
- package/dist/chunks/studio-gw83z798.js +0 -7
- package/dist/chunks/studio-j52ks8q7.js +0 -5
- package/dist/chunks/studio-kefs4sz4.js +0 -5
- package/dist/chunks/studio-m3jrjfhy.js +0 -14
- package/dist/chunks/studio-n4pnf0sw.js +0 -5
- package/dist/chunks/studio-ncwx602y.js +0 -5
- package/dist/chunks/studio-nzp0sqjg.js +0 -5
- package/dist/chunks/studio-ph17an19.js +0 -5
- package/dist/chunks/studio-q509sqt8.js +0 -5
- package/dist/chunks/studio-s49y6m5b.js +0 -5
- package/dist/chunks/studio-t46rt6gx.js +0 -5
- package/dist/chunks/studio-veh91hjv.js +0 -5
- package/dist/chunks/studio-vswmz40d.js +0 -6
- package/dist/chunks/studio-w4mfaaam.js +0 -5
- package/dist/chunks/studio-x5py815g.js +0 -5
- package/dist/chunks/studio-z6s56dzz.js +0 -5
- package/dist/chunks/studio-ze1sm147.js +0 -7
- package/dist/chunks/studio-zn7ysyzb.js +0 -5
- package/dist/chunks/toast-w11qpkyz.js +0 -4
- package/dist/chunks/workspace-home-61n1jz3q.js +0 -4
- package/dist/chunks/workspace-sheek6zw.js +0 -4
|
@@ -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
|
+
}
|
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);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
|
-
* Elements panel — the Insert palette: the blocks and components a page is built out of
|
|
3
|
+
* Elements panel — the Insert palette: the blocks and components a page is built out of, on two
|
|
4
|
+
* tabs, Elements and Components (§5.3). Each element card is named in the reader's language
|
|
5
|
+
* (`locale/element-names.ts`), with its tag beside the name, and the categories lead with what a
|
|
6
|
+
* first page is made of: text, lists, media and layout before forms, tables and inline formatting.
|
|
4
7
|
*
|
|
5
8
|
* **The body is a Jx document** (`surfaces/panel-elements.json`, mounted by
|
|
6
9
|
* `surfaces/panel-elements.ts`), so `render` draws nothing and `afterRender` mounts. What stays
|
|
@@ -30,6 +33,7 @@ import { mountElementsSurface } from "../surfaces/panel-elements";
|
|
|
30
33
|
import { mutateInsertNode, transactDoc } from "../tabs/transact";
|
|
31
34
|
import { primarySelection } from "../tabs/selection";
|
|
32
35
|
import { registerPanel } from "./panel-registry";
|
|
36
|
+
import { categoryName, elementDescription, elementName } from "../locale/element-names";
|
|
33
37
|
import { activeTab } from "../workspace/workspace";
|
|
34
38
|
import { view } from "../view";
|
|
35
39
|
|
|
@@ -38,8 +42,10 @@ import type {
|
|
|
38
42
|
ComponentCardView,
|
|
39
43
|
ElementCategoryView,
|
|
40
44
|
ElementsActions,
|
|
45
|
+
ElementsEmptyState,
|
|
41
46
|
ElementsSurfaceHandle,
|
|
42
47
|
ElementsValues,
|
|
48
|
+
InsertTab,
|
|
43
49
|
} from "../surfaces/panel-elements";
|
|
44
50
|
import type { JxElement, JxMutableNode } from "@jxsuite/schema/types";
|
|
45
51
|
import type { NavigatorPanelContext, NavigatorPanelDeps, PanelBody } from "./panel-registry";
|
|
@@ -49,8 +55,13 @@ interface ElementsWebdata {
|
|
|
49
55
|
elements?: Record<string, { tag: string }[]>;
|
|
50
56
|
}
|
|
51
57
|
|
|
52
|
-
/**
|
|
53
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The categories that start closed: inline formatting and the advanced remainder (§5.3). Both are
|
|
60
|
+
* long, and neither is where somebody building their first page looks, so a newcomer sees the
|
|
61
|
+
* everyday blocks first and opens these when they want them. A filter opens them too, unless the
|
|
62
|
+
* reader has closed one by hand.
|
|
63
|
+
*/
|
|
64
|
+
const CLOSED_AT_FIRST: ReadonlySet<string> = new Set(["inline", "advanced"]);
|
|
54
65
|
|
|
55
66
|
/**
|
|
56
67
|
* What the palette is built from, held at module scope rather than closed over at mount.
|
|
@@ -76,18 +87,37 @@ function needle(): string {
|
|
|
76
87
|
return view.elementsFilter.toLowerCase();
|
|
77
88
|
}
|
|
78
89
|
|
|
79
|
-
/**
|
|
90
|
+
/** Whether a section is drawn open: the reader's own choice, else its default. */
|
|
91
|
+
function sectionOpen(key: string, filter: string): boolean {
|
|
92
|
+
return view.elementsOpen.get(key) ?? (filter !== "" || !CLOSED_AT_FIRST.has(key));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The categories that still have something in them, with the filter applied, in the order
|
|
97
|
+
* `webdata.elements` declares them (§5.3). A card matches on its name in the reader's language or
|
|
98
|
+
* on its tag, so "image" and "img" both find the same card.
|
|
99
|
+
*/
|
|
80
100
|
function categoryViews(webdata: ElementsWebdata, filter: string): ElementCategoryView[] {
|
|
81
101
|
const sections: ElementCategoryView[] = [];
|
|
82
|
-
for (const [
|
|
83
|
-
const
|
|
102
|
+
for (const [key, elements] of Object.entries(webdata.elements ?? {})) {
|
|
103
|
+
const cards = elements.map(({ tag }) => ({
|
|
104
|
+
description: elementDescription(tag),
|
|
105
|
+
name: elementName(tag),
|
|
106
|
+
tag,
|
|
107
|
+
}));
|
|
108
|
+
const matched = filter
|
|
109
|
+
? cards.filter(
|
|
110
|
+
(card) => card.tag.includes(filter) || card.name.toLowerCase().includes(filter),
|
|
111
|
+
)
|
|
112
|
+
: cards;
|
|
84
113
|
if (matched.length === 0) {
|
|
85
114
|
continue;
|
|
86
115
|
}
|
|
87
116
|
sections.push({
|
|
88
|
-
elements: matched
|
|
89
|
-
|
|
90
|
-
|
|
117
|
+
elements: matched,
|
|
118
|
+
key,
|
|
119
|
+
name: categoryName(key),
|
|
120
|
+
open: sectionOpen(key, filter),
|
|
91
121
|
});
|
|
92
122
|
}
|
|
93
123
|
return sections;
|
|
@@ -145,23 +175,51 @@ function componentViews(filter: string): ComponentCardView[] {
|
|
|
145
175
|
}));
|
|
146
176
|
}
|
|
147
177
|
|
|
178
|
+
/** Which body a tab shows: its cards, or why it has none. */
|
|
179
|
+
function emptyState(count: number, filter: string): ElementsEmptyState {
|
|
180
|
+
if (count > 0) {
|
|
181
|
+
return "none";
|
|
182
|
+
}
|
|
183
|
+
return filter ? "filtered" : "empty";
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** The other tab, which a filtered-out tab offers to switch to. */
|
|
187
|
+
function otherTab(tab: InsertTab): InsertTab {
|
|
188
|
+
return tab === "elements" ? "components" : "elements";
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** "Show 1 matching component", "Show 3 matching elements": the other tab's offer. */
|
|
192
|
+
function otherMatchesLabel(tab: InsertTab, count: number): string {
|
|
193
|
+
const noun = tab === "elements" ? "element" : "component";
|
|
194
|
+
return `Show ${count} matching ${noun}${count === 1 ? "" : "s"}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
148
197
|
/** Everything the document draws, as one value. */
|
|
149
198
|
function panelValues(source: NavigatorPanelDeps): ElementsValues {
|
|
150
199
|
const filter = needle();
|
|
200
|
+
const tab = view.insertTab;
|
|
151
201
|
/* SAFETY: `webdata` is wired in studio.ts from the bundled data/webdata.json, whose `elements`
|
|
152
|
-
maps each category
|
|
202
|
+
maps each category key to an array of `{ tag }` entries. */
|
|
153
203
|
const categories = categoryViews(source.webdata as ElementsWebdata, filter);
|
|
154
204
|
const components = componentViews(filter);
|
|
155
|
-
const
|
|
205
|
+
const elementCount = categories.reduce((sum, section) => sum + section.elements.length, 0);
|
|
206
|
+
const counts: Record<InsertTab, number> = {
|
|
207
|
+
components: components.length,
|
|
208
|
+
elements: elementCount,
|
|
209
|
+
};
|
|
210
|
+
const other = otherTab(tab);
|
|
211
|
+
const showOffer = filter !== "" && counts[tab] === 0 && counts[other] > 0;
|
|
156
212
|
return {
|
|
157
213
|
categories,
|
|
158
214
|
components,
|
|
159
|
-
|
|
160
|
-
|
|
215
|
+
componentsEmpty: emptyState(components.length, filter),
|
|
216
|
+
elementsEmpty: emptyState(elementCount, filter),
|
|
161
217
|
/* The reader's own text, not `filter`: the field is bound to this, and a scope holding the
|
|
162
218
|
lower-cased form would rewrite what was typed on every keystroke. */
|
|
163
219
|
filter: view.elementsFilter,
|
|
164
|
-
|
|
220
|
+
filterLabel: tab === "elements" ? "Filter elements" : "Filter components",
|
|
221
|
+
otherMatches: showOffer ? otherMatchesLabel(other, counts[other]) : "",
|
|
222
|
+
tab,
|
|
165
223
|
};
|
|
166
224
|
}
|
|
167
225
|
|
|
@@ -207,6 +265,17 @@ function refresh(): void {
|
|
|
207
265
|
|
|
208
266
|
/** What the document's controls do. Every one of them is a decision this module owns. */
|
|
209
267
|
const ACTIONS: ElementsActions = {
|
|
268
|
+
/* The strip announces any value its tabs carry; only the two this panel draws are taken. */
|
|
269
|
+
selectTab: (tab) => {
|
|
270
|
+
if (tab === "elements" || tab === "components") {
|
|
271
|
+
view.insertTab = tab;
|
|
272
|
+
refresh();
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
showOtherTab: () => {
|
|
276
|
+
view.insertTab = otherTab(view.insertTab);
|
|
277
|
+
refresh();
|
|
278
|
+
},
|
|
210
279
|
clearFilter: () => {
|
|
211
280
|
view.elementsFilter = "";
|
|
212
281
|
refresh();
|
|
@@ -223,31 +292,20 @@ const ACTIONS: ElementsActions = {
|
|
|
223
292
|
insertAtSelection(panelDeps.defaultDef(tag) as JxMutableNode);
|
|
224
293
|
}
|
|
225
294
|
},
|
|
226
|
-
setComponentsOpen: (open) => {
|
|
227
|
-
setCollapsed(COMPONENTS, !open);
|
|
228
|
-
refresh();
|
|
229
|
-
},
|
|
230
295
|
/* The echo: what the field now holds goes into the state the palette is filtered by, before
|
|
231
296
|
anything is decided about it. */
|
|
232
297
|
setFilter: (value) => {
|
|
233
298
|
view.elementsFilter = value;
|
|
234
299
|
refresh();
|
|
235
300
|
},
|
|
236
|
-
|
|
237
|
-
|
|
301
|
+
/* Remembered across repaints and panel switches, and from then on it outranks the section's
|
|
302
|
+
default, the filter's included. */
|
|
303
|
+
setSectionOpen: (key, open) => {
|
|
304
|
+
view.elementsOpen.set(key, open);
|
|
238
305
|
refresh();
|
|
239
306
|
},
|
|
240
307
|
};
|
|
241
308
|
|
|
242
|
-
/** Remember a section's disclosure across repaints and across panel switches. */
|
|
243
|
-
function setCollapsed(name: string, collapsed: boolean): void {
|
|
244
|
-
if (collapsed) {
|
|
245
|
-
view.elementsCollapsed.add(name);
|
|
246
|
-
} else {
|
|
247
|
-
view.elementsCollapsed.delete(name);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
309
|
/**
|
|
252
310
|
* Draw the panel body — mounting the document the first time, updating it every time after.
|
|
253
311
|
*
|
|
@@ -277,13 +335,13 @@ export function mountElementsPanel(ctx: NavigatorPanelContext, host: HTMLElement
|
|
|
277
335
|
}
|
|
278
336
|
|
|
279
337
|
/**
|
|
280
|
-
* Contribute the Insert panel —
|
|
338
|
+
* Contribute the Insert panel — the first button of the rail's DOCUMENT group (§5.1).
|
|
281
339
|
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
340
|
+
* It is the place an author picks an element or component from and drags it onto the canvas, so it
|
|
341
|
+
* stays on screen while they work: a card is a choice to look at, compare and drag, and a palette
|
|
342
|
+
* that has to be summoned by name before it can be seen is one only a reader of the command list
|
|
343
|
+
* finds. The slash menu and the canvas `+` stay the inserts at the caret; this is the one an author
|
|
344
|
+
* browses.
|
|
287
345
|
*
|
|
288
346
|
* `level: "document"`, per §13.2's own worked example: it READS the project's component registry
|
|
289
347
|
* and WRITES the document tree.
|
|
@@ -295,7 +353,6 @@ export function registerInsertPanel(): void {
|
|
|
295
353
|
level: "document",
|
|
296
354
|
dock: "navigator",
|
|
297
355
|
icon: "grid-four",
|
|
298
|
-
rail: false,
|
|
299
356
|
// The body is a document, so lit draws nothing and the mount happens against the painted DOM.
|
|
300
357
|
// `afterRender` runs on every repaint; {@link mountElementsPanel} is idempotent.
|
|
301
358
|
render: (): PanelBody => nothing,
|
|
@@ -50,6 +50,7 @@ import type {
|
|
|
50
50
|
} from "../surfaces/panel-imports";
|
|
51
51
|
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
52
52
|
import type { SurfaceOption } from "../ui/surface";
|
|
53
|
+
import { isProjectConfigPath } from "@jxsuite/schema/project-file";
|
|
53
54
|
|
|
54
55
|
/** What the panel is drawn against. `left-panel.ts`'s context, reduced to what this reads. */
|
|
55
56
|
export interface ImportsContext {
|
|
@@ -204,7 +205,7 @@ function documentView(ctx: ImportsContext): ImportsView {
|
|
|
204
205
|
|
|
205
206
|
/** What the panel shows right now. `project.json` is the one document with the other mood. */
|
|
206
207
|
function view(ctx: ImportsContext): ImportsView {
|
|
207
|
-
return ctx.documentPath
|
|
208
|
+
return isProjectConfigPath(ctx.documentPath) ? siteView() : documentView(ctx);
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
/** Push the current projection into the standing surface, in this turn. */
|
|
@@ -295,7 +296,7 @@ async function onToggleComponent(id: string, checked: boolean): Promise<void> {
|
|
|
295
296
|
if (!ctx || !comp) {
|
|
296
297
|
return;
|
|
297
298
|
}
|
|
298
|
-
if (ctx.documentPath
|
|
299
|
+
if (isProjectConfigPath(ctx.documentPath)) {
|
|
299
300
|
const before = siteElements();
|
|
300
301
|
const updated = checked
|
|
301
302
|
? enableElement(before, comp, null)
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* the second definition site the registry exists to prevent.
|
|
9
9
|
*
|
|
10
10
|
* **Registration order is rail order** within a level group: PROJECT (Site, then Files with the
|
|
11
|
-
* developer panels on) above the divider, DOCUMENT (Outline · Data, then Packages with the
|
|
12
|
-
* developer panels on) below it (§5.1). Search
|
|
13
|
-
*
|
|
11
|
+
* developer panels on) above the divider, DOCUMENT (Insert · Outline · Data, then Packages with the
|
|
12
|
+
* developer panels on) below it (§5.1). Search and Languages are registered rail-less: reachable by
|
|
13
|
+
* `view.setActivity` and the palette, with no button.
|
|
14
14
|
*
|
|
15
15
|
* **Every rail button opens the Navigator.** Problems used to hold the fourth PROJECT slot while
|
|
16
16
|
* its body was drawn in the Bottom dock, and paying for that took a per-dock branch in three
|
|
@@ -84,13 +84,13 @@ export function registerNavigatorPanels(): void {
|
|
|
84
84
|
registerI18nPanel();
|
|
85
85
|
|
|
86
86
|
// ── DOCUMENT group ──────────────────────────────────────────────────────────
|
|
87
|
+
/* Insert leads the group: it is where a page's elements and components are picked from and
|
|
88
|
+
dragged onto the canvas, the verb a Design session is mostly made of (§5.1, §5.3). */
|
|
89
|
+
registerInsertPanel();
|
|
87
90
|
registerLayersPanel();
|
|
88
91
|
registerDataPanel();
|
|
89
92
|
registerPackagesPanel();
|
|
90
93
|
|
|
91
|
-
// ── Off-rail: reachable by command and palette, no rail button ──────────────
|
|
92
|
-
registerInsertPanel();
|
|
93
|
-
|
|
94
94
|
// ── The Bottom dock's tabs (§16.3): Problems · Logic · Activity ──────────────
|
|
95
95
|
// Composed here, in the one place panel records are gathered, so the shell has ONE composition
|
|
96
96
|
// Site rather than one per dock. Importing the module is also what attaches the dock to the
|
|
@@ -122,10 +122,10 @@ export function navigatorPanelSet(): readonly PanelRecord[] {
|
|
|
122
122
|
* The roster `panel.focus.*` is generated from: rail order first, then the rail-less Navigator
|
|
123
123
|
* panels.
|
|
124
124
|
*
|
|
125
|
-
* Not `railPanelSet()`:
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
125
|
+
* Not `railPanelSet()`: Languages has no rail button and no chord, and it still needs a `Show …`
|
|
126
|
+
* command and a palette row, which is the whole point of `rail: false` (a surface reachable by
|
|
127
|
+
* name, not by number). State and Insert were rail-less beside it once: State's editor is the Data
|
|
128
|
+
* panel's now, and Insert is back on the rail (§5.1).
|
|
129
129
|
*
|
|
130
130
|
* The Bottom dock's three tabs are deliberately absent — Problems as of this change, and Logic and
|
|
131
131
|
* Activity all along. Each is `rail: false` AND hosted there, so each is addressed by
|
|
@@ -153,9 +153,9 @@ export interface PanelRecord {
|
|
|
153
153
|
*
|
|
154
154
|
* Not a hiding mechanism — a rail-less panel is still reachable by `view.setActivity`, by the
|
|
155
155
|
* palette and by another surface linking to it. It is how a record survives the interval between
|
|
156
|
-
* losing its rail slot and gaining its real home (
|
|
157
|
-
*
|
|
158
|
-
*
|
|
156
|
+
* losing its rail slot and gaining its real home (State, folded into Data — §5.1). Declaring it
|
|
157
|
+
* `false` also removes the panel's `rail/<level>` placement, which is what keeps the rail inside
|
|
158
|
+
* its four-per-group budget.
|
|
159
159
|
*
|
|
160
160
|
* `"developer"` is a rail button only while **Show developer panels** is on (§5.1,
|
|
161
161
|
* `settings/developer-panels.ts`): Files and Packages, which a site author does not need on the
|
package/src/panels/site-panel.ts
CHANGED
|
@@ -71,6 +71,7 @@ import type {
|
|
|
71
71
|
SiteSectionState,
|
|
72
72
|
} from "../surfaces/site-panel";
|
|
73
73
|
import type { SiteComponentRow, SiteMediaRow, SitePageFact } from "./site-model";
|
|
74
|
+
import { isProjectConfigPath } from "@jxsuite/schema/project-file";
|
|
74
75
|
|
|
75
76
|
// ─── The project index ───────────────────────────────────────────────────────
|
|
76
77
|
|
|
@@ -213,7 +214,7 @@ export function mayDefineComponent(path: string): boolean {
|
|
|
213
214
|
}
|
|
214
215
|
return (
|
|
215
216
|
path.endsWith(".json") &&
|
|
216
|
-
path
|
|
217
|
+
!isProjectConfigPath(path) &&
|
|
217
218
|
!NON_COMPONENT_DIRS.some((dir) => path.startsWith(dir))
|
|
218
219
|
);
|
|
219
220
|
}
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
isNumber,
|
|
55
55
|
isString,
|
|
56
56
|
} from "@jxsuite/schema/guards";
|
|
57
|
+
import { PROJECT_FILE_NAME } from "@jxsuite/schema/project-file";
|
|
57
58
|
import { effect, effectScope, reactive } from "../reactivity";
|
|
58
59
|
import {
|
|
59
60
|
cancelStyleDebounce,
|
|
@@ -374,7 +375,7 @@ function stylebookTagOf(selector: string | null): string | null {
|
|
|
374
375
|
* elements of its own for "in this document" to be about.
|
|
375
376
|
*/
|
|
376
377
|
function isProjectStylesheet(path: string): boolean {
|
|
377
|
-
return /
|
|
378
|
+
return path.split("/").pop() === PROJECT_FILE_NAME;
|
|
378
379
|
}
|
|
379
380
|
|
|
380
381
|
/**
|
|
@@ -34,7 +34,7 @@ import { effect, effectScope } from "../reactivity";
|
|
|
34
34
|
import { projectState } from "../store";
|
|
35
35
|
import { setDockCollapsed, shell } from "../shell";
|
|
36
36
|
import { workspace } from "../workspace/workspace";
|
|
37
|
-
import {
|
|
37
|
+
import { projectConfigPath } from "../tabs/tab";
|
|
38
38
|
import { componentRegistry } from "../files/components";
|
|
39
39
|
import { runActiveReported } from "../commands/run-reported";
|
|
40
40
|
import { showSettingsDocument } from "../settings/settings-document";
|
|
@@ -88,7 +88,7 @@ export interface StylesPanelInput {
|
|
|
88
88
|
* opened with.
|
|
89
89
|
*/
|
|
90
90
|
export function stylesConfig(): StylesPanelInput["config"] {
|
|
91
|
-
const tab = workspace.tabs.get(
|
|
91
|
+
const tab = workspace.tabs.get(projectConfigPath());
|
|
92
92
|
return (tab ? tab.doc.document : projectState?.projectConfig) ?? null;
|
|
93
93
|
}
|
|
94
94
|
|
package/src/platforms/cloud.ts
CHANGED
|
@@ -20,6 +20,7 @@ import type { WsCollabConnection } from "@jxsuite/collab/client";
|
|
|
20
20
|
import { isBoolean, isJsonObject, isNonNullObject, isString } from "@jxsuite/schema/guards";
|
|
21
21
|
import type { JsonObject, JsonValue, JxMutableNode, ProjectConfig } from "@jxsuite/schema/types";
|
|
22
22
|
import { componentMetaFrom } from "@jxsuite/schema/component-meta";
|
|
23
|
+
import { isJxnPath, isNativeDocumentPath, parseJxn } from "@jxsuite/schema/jxn";
|
|
23
24
|
import { streamImport } from "../services/import-client";
|
|
24
25
|
import type {
|
|
25
26
|
AccountEntry,
|
|
@@ -1403,7 +1404,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
|
|
|
1403
1404
|
if (!SKIP.has(entry.name) && !entry.name.startsWith(".")) {
|
|
1404
1405
|
dirs.push(entry.path);
|
|
1405
1406
|
}
|
|
1406
|
-
} else if (entry.name
|
|
1407
|
+
} else if (isNativeDocumentPath(entry.name) && jsonPaths.length < MAX_FILES) {
|
|
1407
1408
|
jsonPaths.push(entry.path);
|
|
1408
1409
|
}
|
|
1409
1410
|
}
|
|
@@ -1414,7 +1415,9 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
|
|
|
1414
1415
|
const metas = await Promise.all(
|
|
1415
1416
|
jsonPaths.map(async (path) => {
|
|
1416
1417
|
try {
|
|
1417
|
-
|
|
1418
|
+
const text = await platform.readFile(path);
|
|
1419
|
+
const document = isJxnPath(path) ? parseJxn(text) : parseJson(text);
|
|
1420
|
+
return componentMetaFrom(isJsonObject(document) ? document : null, path);
|
|
1418
1421
|
} catch {
|
|
1419
1422
|
return null; // Not JSON, not readable, or not a component — all the same answer here.
|
|
1420
1423
|
}
|
|
@@ -84,6 +84,7 @@ import type { CommandContext } from "../commands/context";
|
|
|
84
84
|
import type { AiToolReport, AnyCommand, CommandRegistry } from "../commands/registry";
|
|
85
85
|
import type { Tab } from "../tabs/tab";
|
|
86
86
|
import { runAsActor } from "../tabs/transact";
|
|
87
|
+
import { projectConfigPath } from "../tabs/tab";
|
|
87
88
|
|
|
88
89
|
/**
|
|
89
90
|
* The record a selection-level projection composes through.
|
|
@@ -328,7 +329,7 @@ function defaultPaths(command: AnyCommand, tab: Tab | null): string[] {
|
|
|
328
329
|
return [tab?.documentPath ?? "(untitled)"];
|
|
329
330
|
}
|
|
330
331
|
if (command.undo === "project") {
|
|
331
|
-
return [
|
|
332
|
+
return [projectConfigPath()];
|
|
332
333
|
}
|
|
333
334
|
return ["(unknown file)"];
|
|
334
335
|
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { JX_TOOL_FACTS } from "@jxsuite/ai/jx-tools";
|
|
18
18
|
import { createToolDefinition } from "@jxsuite/ai/tools";
|
|
19
19
|
import { isJsonObject, isNumber } from "@jxsuite/schema/guards";
|
|
20
|
+
import { classifyJxn, isJxnPath, isNativeDocumentPath, parseJxn } from "@jxsuite/schema/jxn";
|
|
20
21
|
import type { CreateProjectDestination, StudioPlatform } from "../types";
|
|
21
22
|
import type { ToolRegistry } from "@jxsuite/ai/tools";
|
|
22
23
|
import type { JsonValue, JxMutableNode, ProjectConfig } from "@jxsuite/schema/types";
|
|
@@ -30,6 +31,7 @@ import { validateDoc, validateProjectConfig } from "./jx-validate";
|
|
|
30
31
|
import { flagHardcodedTokens, formatTokenHints } from "./token-lint";
|
|
31
32
|
import type { CandidateDocument, RenderProbe } from "./ai-write-report";
|
|
32
33
|
import { invalidPathResult, normalizeRelPath } from "./project-path";
|
|
34
|
+
import { isProjectConfigPath } from "@jxsuite/schema/project-file";
|
|
33
35
|
|
|
34
36
|
/** Directories the file tools never descend into or report. */
|
|
35
37
|
const EXCLUDED_DIRS = new Set(["node_modules", "dist", ".git", ".jx-cache"]);
|
|
@@ -49,7 +51,7 @@ const NOT_UNDOABLE = "(saved to disk; not undoable with Cmd+Z)";
|
|
|
49
51
|
* {@link looksLikeJxDoc} sniff.
|
|
50
52
|
*/
|
|
51
53
|
function isJxDocPath(path: string): boolean {
|
|
52
|
-
return /^(pages|layouts|components|elements)\//.test(path) && path
|
|
54
|
+
return /^(pages|layouts|components|elements)\//.test(path) && isNativeDocumentPath(path);
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
/** Structural sniff for Jx-document-shaped JSON values. */
|
|
@@ -299,8 +301,19 @@ export function registerProjectTools(
|
|
|
299
301
|
|
|
300
302
|
// Pre-validate Jx documents — disk writes have no undo to lean on.
|
|
301
303
|
let parsed: JsonValue | undefined;
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
+
const isJxn = isJxnPath(relPath) && classifyJxn(content) !== "project";
|
|
305
|
+
const isJson = relPath.endsWith(".json") && !isProjectConfigPath(relPath);
|
|
306
|
+
if (isJxn) {
|
|
307
|
+
try {
|
|
308
|
+
const document = parseJxn(content);
|
|
309
|
+
parsed = isJsonObject(document) ? document : null;
|
|
310
|
+
} catch (error) {
|
|
311
|
+
return {
|
|
312
|
+
success: false,
|
|
313
|
+
error: `Content is not valid Jx Notation: ${error instanceof Error ? error.message : String(error)}`,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
} else if (isJson) {
|
|
304
317
|
try {
|
|
305
318
|
// SAFETY: `JSON.parse` without a reviver yields nothing but JSON values.
|
|
306
319
|
parsed = JSON.parse(content) as JsonValue;
|
|
@@ -312,7 +325,7 @@ export function registerProjectTools(
|
|
|
312
325
|
}
|
|
313
326
|
}
|
|
314
327
|
let tokenHints = "";
|
|
315
|
-
// The parse above sets `parsed` exactly when
|
|
328
|
+
// The parse above sets `parsed` exactly when one of those holds: it never yields `undefined`.
|
|
316
329
|
if (parsed !== undefined && (isJxDocPath(relPath) || looksLikeJxDoc(parsed))) {
|
|
317
330
|
const errors = await validate(parsed);
|
|
318
331
|
if (errors.length > 0) {
|
|
@@ -345,7 +358,7 @@ export function registerProjectTools(
|
|
|
345
358
|
/* Parsed as `unknown` because that is what it is until the schema says otherwise — this is
|
|
346
359
|
model-authored text, and the gate below is the whole reason we do not trust its shape. */
|
|
347
360
|
let projectConfig: ProjectConfig | undefined;
|
|
348
|
-
if (relPath
|
|
361
|
+
if (isProjectConfigPath(relPath)) {
|
|
349
362
|
let parsedConfig: JsonValue;
|
|
350
363
|
try {
|
|
351
364
|
// SAFETY: `JSON.parse` without a reviver yields nothing but JSON values.
|