@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.
Files changed (346) hide show
  1. package/data/locales/en/elements.json +434 -0
  2. package/data/webdata.json +116 -139
  3. package/dist/chunks/2020-rqm7qrje.js +4 -0
  4. package/dist/chunks/adapter-cyy8vbcx.js +4 -0
  5. package/dist/chunks/add-new-flow-8cgtejv2.js +4 -0
  6. package/dist/chunks/{cf-account-picker-xb8vvt2h.js → cf-account-picker-v3qd2dhw.js} +3 -3
  7. package/dist/chunks/cf-settings-1fnw874z.js +4 -0
  8. package/dist/chunks/{color-area-h8dbasp1.js → color-area-frd9y9q6.js} +2 -2
  9. package/dist/chunks/color-field-rebpbe33.js +4 -0
  10. package/dist/chunks/{color-slider-nz2mrzky.js → color-slider-6bzmk3s7.js} +2 -2
  11. package/dist/chunks/color-t2bnnj8y.js +4 -0
  12. package/dist/chunks/{combobox-6hs00996.js → combobox-c49x7gf1.js} +3 -3
  13. package/dist/chunks/components-y8yhdd7d.js +4 -0
  14. package/dist/chunks/content-source-9e7qvmsf.js +4 -0
  15. package/dist/chunks/convert-to-repeater-pd2w4z21.js +4 -0
  16. package/dist/chunks/editor-mb9jzdrw.js +4 -0
  17. package/dist/chunks/entry-commands-at0wnx3f.js +4 -0
  18. package/dist/chunks/extension-sections-4z9qq0gh.js +4 -0
  19. package/dist/chunks/file-ops-dtdvjmza.js +4 -0
  20. package/dist/chunks/files-8pf70854.js +4 -0
  21. package/dist/chunks/format-host-fd3m2y2a.js +4 -0
  22. package/dist/chunks/fs-events-hrk29vcv.js +4 -0
  23. package/dist/chunks/grid-open-bh74v82z.js +4 -0
  24. package/dist/chunks/{grid-view-2f03wcgw.js → grid-view-a51mgzmq.js} +17 -17
  25. package/dist/chunks/{icons-gb967qc8.js → icons-qp8fn1k8.js} +2 -2
  26. package/dist/chunks/{iframe-entry-btck0e9s.js → iframe-entry-mpwe5pwk.js} +4 -4
  27. package/dist/chunks/{iframe-entry-2x9bn5ng.js → iframe-entry-msd43x9h.js} +4 -4
  28. package/dist/chunks/iframe-entry-rt8jz5t8.js +5 -0
  29. package/dist/chunks/{iframe-entry-b5qgggyp.js → iframe-entry-v40cr813.js} +4 -4
  30. package/dist/chunks/iframe-host-q0s0wwy3.js +4 -0
  31. package/dist/chunks/{image-bake-4afr4y6a.js → image-bake-q9ajdjsj.js} +3 -3
  32. package/dist/chunks/{image-editor-x7t8jbjn.js → image-editor-ke9std9b.js} +3 -3
  33. package/dist/chunks/index-57ang7ma.js +4 -0
  34. package/dist/chunks/index-eytr7av4.js +17 -0
  35. package/dist/chunks/{index-yjrfeks8.js → index-x495reff.js} +3 -3
  36. package/dist/chunks/{javascript-hy2mws8v.js → javascript-qe34bvaz.js} +3 -3
  37. package/dist/chunks/jsonMode-m8rseh31.js +13 -0
  38. package/dist/chunks/jx-validate-bbd1mary.js +4 -0
  39. package/dist/chunks/layers-panel-wcjtdqsa.js +4 -0
  40. package/dist/chunks/layers-yscpys6q.js +4 -0
  41. package/dist/chunks/{listbox-7gka1h86.js → listbox-tktzq5xj.js} +2 -2
  42. package/dist/chunks/lit-html-qm5wbhqz.js +4 -0
  43. package/dist/chunks/media-library-2hfff88g.js +4 -0
  44. package/dist/chunks/media-open-4pb05cne.js +4 -0
  45. package/dist/chunks/media-picker-bzktmmwr.js +4 -0
  46. package/dist/chunks/menu-xc3cbgsd.js +4 -0
  47. package/dist/chunks/{monaco-binding-a51py33c.js → monaco-binding-7t38arh9.js} +3 -3
  48. package/dist/chunks/monaco-buffer-3vz94c9f.js +4 -0
  49. package/dist/chunks/monaco-setup-fstdew02.js +4 -0
  50. package/dist/chunks/notify-gbdcckrh.js +4 -0
  51. package/dist/chunks/parse-fpdzgk4c.js +4 -0
  52. package/dist/chunks/platform-9g72gdqr.js +4 -0
  53. package/dist/chunks/popover-cnmsav6b.js +4 -0
  54. package/dist/chunks/publish-panel-91x1cb5d.js +4 -0
  55. package/dist/chunks/publish-quick-e2dmf7eg.js +4 -0
  56. package/dist/chunks/publish-workspace-1x8mnzrj.js +4 -0
  57. package/dist/chunks/quick-search-2m4y6p1x.js +4 -0
  58. package/dist/chunks/references-yg1g9rwx.js +4 -0
  59. package/dist/chunks/right-panel-7mfyhzrk.js +4 -0
  60. package/dist/chunks/source-control-rrc4jgp5.js +4 -0
  61. package/dist/chunks/{split-m5rwn31z.js → split-d15yencd.js} +2 -2
  62. package/dist/chunks/studio-0jhhznxy.js +9 -0
  63. package/dist/chunks/studio-0nb57pjg.js +7 -0
  64. package/dist/chunks/{studio-s24mhq0z.js → studio-0wa4wv4s.js} +4 -4
  65. package/dist/chunks/studio-0z8kp4xk.js +5 -0
  66. package/dist/chunks/{studio-g0hxtcga.js → studio-18gc5t3v.js} +4 -4
  67. package/dist/chunks/{studio-4fnb5648.js → studio-1b79rpcb.js} +4 -4
  68. package/dist/chunks/studio-211q5j7m.js +4 -0
  69. package/dist/chunks/studio-213pyfxh.js +16 -0
  70. package/dist/chunks/studio-2b1smjvp.js +5 -0
  71. package/dist/chunks/{studio-nwf0c32p.js → studio-2v6tc9jp.js} +4 -4
  72. package/dist/chunks/studio-30j0wz99.js +5 -0
  73. package/dist/chunks/studio-3kmqhwtt.js +5 -0
  74. package/dist/chunks/studio-44drsmhw.js +5 -0
  75. package/dist/chunks/{studio-2pk7kc7z.js → studio-495b6f2r.js} +4 -4
  76. package/dist/chunks/{studio-jma2q8r9.js → studio-4gthb2fd.js} +4 -4
  77. package/dist/chunks/{studio-0gvvqj3h.js → studio-4xadhxj1.js} +4 -4
  78. package/dist/chunks/{studio-nvcn2ykc.js → studio-52mzbxqm.js} +4 -4
  79. package/dist/chunks/{studio-v7a7yb5w.js → studio-56mmnsw8.js} +7 -7
  80. package/dist/chunks/studio-59hz8f4r.js +5 -0
  81. package/dist/chunks/{studio-sce30r7b.js → studio-5k9mfafh.js} +3 -3
  82. package/dist/chunks/{studio-rr00ex23.js → studio-5qez462c.js} +6 -6
  83. package/dist/chunks/studio-6tnpg0vx.js +5 -0
  84. package/dist/chunks/studio-6w8mt734.js +6 -0
  85. package/dist/chunks/studio-7hm04x5z.js +5 -0
  86. package/dist/chunks/studio-8atmfjjm.js +7 -0
  87. package/dist/chunks/{studio-wmjmsw0p.js → studio-8n99z71s.js} +4 -4
  88. package/dist/chunks/studio-8vmad6x2.js +5 -0
  89. package/dist/chunks/studio-93p5k4r4.js +5 -0
  90. package/dist/chunks/{studio-2x972v58.js → studio-9gb0m81w.js} +4 -4
  91. package/dist/chunks/studio-a4ymg4vd.js +5 -0
  92. package/dist/chunks/studio-anc8067x.js +5 -0
  93. package/dist/chunks/{studio-2e4hry72.js → studio-dbdt67kr.js} +5 -5
  94. package/dist/chunks/studio-dbex091t.js +5 -0
  95. package/dist/chunks/{studio-amaz545p.js → studio-dj8ctxey.js} +4 -4
  96. package/dist/chunks/{studio-k2evngrj.js → studio-ed9bz68j.js} +5 -5
  97. package/dist/chunks/{studio-4bgvvzhv.js → studio-fe2d1fp5.js} +4 -4
  98. package/dist/chunks/{studio-45mj4jqe.js → studio-fgp4cyy3.js} +4 -4
  99. package/dist/chunks/studio-ft706q02.js +5 -0
  100. package/dist/chunks/studio-g79s1dyg.js +6 -0
  101. package/dist/chunks/{studio-pj216fmn.js → studio-gahswxp4.js} +4 -4
  102. package/dist/chunks/studio-gbqc12bk.js +5 -0
  103. package/dist/chunks/studio-gxphbetb.js +6 -0
  104. package/dist/chunks/studio-hdf01j7q.js +7 -0
  105. package/dist/chunks/studio-hf259xzd.js +48 -0
  106. package/dist/chunks/studio-jbva4wxw.js +5 -0
  107. package/dist/chunks/studio-jj9gf1qc.js +5 -0
  108. package/dist/chunks/studio-jsjg3mjv.js +5 -0
  109. package/dist/chunks/studio-jtvb8hd7.js +5 -0
  110. package/dist/chunks/{studio-g9brbewz.js → studio-kp4vzrct.js} +4 -4
  111. package/dist/chunks/studio-kpx02gaa.js +7 -0
  112. package/dist/chunks/{studio-3na1r48j.js → studio-kv1728ex.js} +8 -8
  113. package/dist/chunks/studio-m25ry0mf.js +5 -0
  114. package/dist/chunks/studio-mcbwmkyh.js +5 -0
  115. package/dist/chunks/studio-meajkr51.js +5 -0
  116. package/dist/chunks/studio-mkjrjpr2.js +6 -0
  117. package/dist/chunks/{studio-eqcddvc2.js → studio-mr084886.js} +201 -201
  118. package/dist/chunks/studio-mrb10hv3.js +5 -0
  119. package/dist/chunks/{studio-0939t1q1.js → studio-nhpg405j.js} +4 -4
  120. package/dist/chunks/studio-nsec0hxm.js +64 -0
  121. package/dist/chunks/{studio-xx3r3sb0.js → studio-nv0mh0p5.js} +4 -4
  122. package/dist/chunks/studio-pafmb06w.js +5 -0
  123. package/dist/chunks/{studio-pm17wdc4.js → studio-pgftd79b.js} +4 -4
  124. package/dist/chunks/{studio-4zd6an2b.js → studio-pk961xgz.js} +4 -4
  125. package/dist/chunks/studio-pzbr7g05.js +5 -0
  126. package/dist/chunks/studio-qcc5hsw2.js +10 -0
  127. package/dist/chunks/studio-qfq58c8n.js +5 -0
  128. package/dist/chunks/studio-qvb2jr83.js +5 -0
  129. package/dist/chunks/studio-r85zw6ay.js +5 -0
  130. package/dist/chunks/studio-s0kwce5w.js +9 -0
  131. package/dist/chunks/studio-s6ymkf3q.js +14 -0
  132. package/dist/chunks/{studio-0x6f3nk9.js → studio-s7p5ccxa.js} +4 -4
  133. package/dist/chunks/{studio-1410q5dw.js → studio-s9jjap3x.js} +4 -4
  134. package/dist/chunks/{studio-evq47j2z.js → studio-t5hde1ws.js} +4 -4
  135. package/dist/chunks/studio-vj9yv897.js +5 -0
  136. package/dist/chunks/studio-wmak8xjw.js +5 -0
  137. package/dist/chunks/{studio-bnc50f9h.js → studio-wqsmvtqj.js} +4 -4
  138. package/dist/chunks/studio-x3z3rhej.js +5 -0
  139. package/dist/chunks/{studio-bg8pzprg.js → studio-xb55kpmy.js} +4 -4
  140. package/dist/chunks/studio-xxtdajmv.js +12 -0
  141. package/dist/chunks/studio-xxttcym1.js +5 -0
  142. package/dist/chunks/{studio-90zkgne5.js → studio-y2zsnzye.js} +7 -7
  143. package/dist/chunks/{studio-3qr08sxq.js → studio-y3tjh2xb.js} +3 -3
  144. package/dist/chunks/studio-y4f1rdsj.js +5 -0
  145. package/dist/chunks/studio-ybkd5w8k.js +5 -0
  146. package/dist/chunks/{studio-tpxcryf2.js → studio-yetr4gg1.js} +4 -4
  147. package/dist/chunks/studio-z295anhs.js +6 -0
  148. package/dist/chunks/studio-z89e6wpg.js +9 -0
  149. package/dist/chunks/{studio-5tet77de.js → studio-zbwr6prc.js} +3 -3
  150. package/dist/chunks/{studio-daksw8kh.js → studio-zdbsv5q2.js} +4 -4
  151. package/dist/chunks/{studio-abh9kfdh.js → studio-zvthj8hg.js} +4 -4
  152. package/dist/chunks/toast-2kn2vrn4.js +4 -0
  153. package/dist/chunks/{toast-host-g3r2s0kn.js → toast-host-3rxeyj3d.js} +3 -3
  154. package/dist/chunks/{tooltip-dj1pvgz7.js → tooltip-ghkxkasp.js} +3 -3
  155. package/dist/chunks/{tree-j0ebwr5s.js → tree-vw4nc63s.js} +2 -2
  156. package/dist/chunks/tsMode-7gngmamr.js +14 -0
  157. package/dist/chunks/workspace-gv0hxgsm.js +4 -0
  158. package/dist/chunks/workspace-home-j7e0mjy5.js +4 -0
  159. package/dist/chunks/{ws-client-arz2qsy9.js → ws-client-vs11sxc5.js} +3 -3
  160. package/dist/iframe-entry.js +14 -14
  161. package/dist/manifest.json +84 -83
  162. package/dist/studio.js +99 -98
  163. package/dist/workers/ts.worker.js +3 -3
  164. package/package.json +12 -12
  165. package/src/browse/library-layouts.ts +9 -12
  166. package/src/browse/library-pane.ts +22 -16
  167. package/src/browse/library-source.ts +3 -4
  168. package/src/browse/preview-style.ts +2 -1
  169. package/src/canvas/canvas-live-render.ts +2 -5
  170. package/src/canvas/canvas-page-context.ts +5 -0
  171. package/src/canvas/canvas-render.ts +6 -1
  172. package/src/canvas/iframe-drop.ts +84 -1
  173. package/src/canvas/iframe-entry.ts +9 -6
  174. package/src/canvas/iframe-render.ts +13 -0
  175. package/src/collab/collab-session.ts +2 -2
  176. package/src/commands/levels.ts +1 -1
  177. package/src/content/entry-commands.ts +32 -27
  178. package/src/content/entry-editor.ts +46 -44
  179. package/src/content/entry-inspector.ts +15 -12
  180. package/src/content/entry-model.ts +3 -3
  181. package/src/content/entry-summaries.ts +5 -5
  182. package/src/content/hold-state.ts +172 -0
  183. package/src/content/page-summaries.ts +10 -14
  184. package/src/create/add-new-flow.ts +2 -1
  185. package/src/editor/convert-targets.ts +2 -23
  186. package/src/editor/inline-edit.ts +6 -1
  187. package/src/editor/repeater-scope.ts +333 -74
  188. package/src/editor/slash-menu.ts +35 -17
  189. package/src/files/files.ts +18 -0
  190. package/src/format/format-choices.ts +5 -5
  191. package/src/format/format-host.ts +35 -3
  192. package/src/grid/redirects-grid.ts +4 -4
  193. package/src/grid/sources/content-source.ts +42 -66
  194. package/src/locale/element-names.ts +131 -0
  195. package/src/panels/block-action-bar.ts +3 -4
  196. package/src/panels/dnd.ts +3 -1
  197. package/src/panels/elements-panel.ts +93 -36
  198. package/src/panels/events-panel.ts +6 -2
  199. package/src/panels/formula-workspace.ts +9 -0
  200. package/src/panels/imports-panel.ts +3 -2
  201. package/src/panels/navigator-panels.ts +10 -10
  202. package/src/panels/panel-registry.ts +3 -3
  203. package/src/panels/properties-panel.ts +127 -35
  204. package/src/panels/site-panel.ts +2 -1
  205. package/src/panels/statement-editor.ts +4 -0
  206. package/src/panels/style-panel.ts +2 -1
  207. package/src/panels/styles-panel.ts +2 -2
  208. package/src/panels/tab-strip.ts +29 -26
  209. package/src/platforms/cloud.ts +5 -2
  210. package/src/services/ai-command-tools.ts +2 -1
  211. package/src/services/ai-project-tools.ts +21 -8
  212. package/src/services/ai-tools.ts +5 -5
  213. package/src/services/ai-write-report.ts +22 -4
  214. package/src/services/context-manager.ts +110 -2
  215. package/src/services/document-assistant.ts +2 -2
  216. package/src/services/harness/chat-reducer.ts +9 -4
  217. package/src/services/jxn-language.ts +110 -0
  218. package/src/services/model-uri.ts +3 -0
  219. package/src/services/monaco-setup.ts +6 -2
  220. package/src/services/render-critic.ts +234 -12
  221. package/src/services/tool-executor.ts +16 -8
  222. package/src/settings/contributed-section.ts +2 -1
  223. package/src/settings/extension-commands.ts +2 -1
  224. package/src/settings/general-settings.ts +2 -2
  225. package/src/settings/settings-document.ts +5 -5
  226. package/src/shell.ts +1 -1
  227. package/src/studio.ts +28 -11
  228. package/src/style/variables-view.ts +2 -2
  229. package/src/surfaces/collections-panel.json +1 -1
  230. package/src/surfaces/collections-panel.ts +1 -1
  231. package/src/surfaces/entry-editor.json +10 -10
  232. package/src/surfaces/entry-editor.ts +22 -19
  233. package/src/surfaces/entry-inspector.json +11 -11
  234. package/src/surfaces/entry-inspector.ts +17 -10
  235. package/src/surfaces/panel-elements.json +466 -206
  236. package/src/surfaces/panel-elements.ts +61 -25
  237. package/src/surfaces/tab-strip.json +5 -5
  238. package/src/surfaces/tab-strip.ts +2 -2
  239. package/src/tabs/project-config.ts +13 -13
  240. package/src/tabs/tab.ts +7 -2
  241. package/src/types.ts +13 -2
  242. package/src/ui/expression-editor.ts +10 -5
  243. package/src/ui/schema-form.ts +18 -1
  244. package/src/utils/edit-display.ts +27 -1
  245. package/src/view.ts +9 -2
  246. package/src/workspace/tab-workspace.ts +2 -2
  247. package/src/workspace/workspace-back.ts +2 -2
  248. package/dist/chunks/2020-906vx2dq.js +0 -4
  249. package/dist/chunks/adapter-hnvad61x.js +0 -4
  250. package/dist/chunks/add-new-flow-v0pm96mr.js +0 -4
  251. package/dist/chunks/cf-settings-mkbcz4z2.js +0 -4
  252. package/dist/chunks/color-04361ttk.js +0 -4
  253. package/dist/chunks/color-field-qv5r2b16.js +0 -4
  254. package/dist/chunks/components-w5zqwva1.js +0 -4
  255. package/dist/chunks/content-source-g38w0d3f.js +0 -4
  256. package/dist/chunks/convert-to-repeater-r8sfxjjm.js +0 -4
  257. package/dist/chunks/editor-wvf5e6j9.js +0 -4
  258. package/dist/chunks/entry-commands-t13dyw3c.js +0 -4
  259. package/dist/chunks/extension-sections-vjzw46xb.js +0 -4
  260. package/dist/chunks/file-ops-1sghzgcr.js +0 -4
  261. package/dist/chunks/files-zrqtgt41.js +0 -4
  262. package/dist/chunks/format-host-9gy76erh.js +0 -4
  263. package/dist/chunks/fs-events-45rhg3me.js +0 -4
  264. package/dist/chunks/grid-open-t4fq6bbm.js +0 -4
  265. package/dist/chunks/iframe-entry-42hpmwz5.js +0 -5
  266. package/dist/chunks/iframe-host-9aq8ze3q.js +0 -4
  267. package/dist/chunks/index-f9qr7kzh.js +0 -4
  268. package/dist/chunks/jsonMode-vj30te2h.js +0 -13
  269. package/dist/chunks/jx-validate-r4ax11e3.js +0 -4
  270. package/dist/chunks/layers-m49nmwvx.js +0 -4
  271. package/dist/chunks/layers-panel-zr92pwdp.js +0 -4
  272. package/dist/chunks/lit-html-k2mb9k0r.js +0 -4
  273. package/dist/chunks/media-library-j74rt0w4.js +0 -4
  274. package/dist/chunks/media-open-mypr0k6b.js +0 -4
  275. package/dist/chunks/media-picker-pemtbsjv.js +0 -4
  276. package/dist/chunks/menu-d2jxpgma.js +0 -4
  277. package/dist/chunks/monaco-buffer-pa0g6ax7.js +0 -4
  278. package/dist/chunks/monaco-setup-0hw9f835.js +0 -4
  279. package/dist/chunks/notify-bwehrp7q.js +0 -4
  280. package/dist/chunks/parse-hee7ne9b.js +0 -4
  281. package/dist/chunks/platform-kv0k65cy.js +0 -4
  282. package/dist/chunks/popover-9tf4cjrk.js +0 -4
  283. package/dist/chunks/publish-panel-vg1n880b.js +0 -4
  284. package/dist/chunks/publish-quick-4fwngwrw.js +0 -4
  285. package/dist/chunks/publish-workspace-jv4w2bx2.js +0 -4
  286. package/dist/chunks/quick-search-nh29rt8a.js +0 -4
  287. package/dist/chunks/references-vqyr5s9x.js +0 -4
  288. package/dist/chunks/right-panel-3ws9f2w0.js +0 -4
  289. package/dist/chunks/source-control-h3hcs5gy.js +0 -4
  290. package/dist/chunks/studio-0khd8xtx.js +0 -4
  291. package/dist/chunks/studio-1ctnw3xv.js +0 -5
  292. package/dist/chunks/studio-1ks5d9y4.js +0 -5
  293. package/dist/chunks/studio-1y9mab9m.js +0 -5
  294. package/dist/chunks/studio-2kpxm0eh.js +0 -7
  295. package/dist/chunks/studio-2zz4qbv9.js +0 -5
  296. package/dist/chunks/studio-3pz4t5a2.js +0 -5
  297. package/dist/chunks/studio-4f7thq0p.js +0 -5
  298. package/dist/chunks/studio-61rg2cz5.js +0 -5
  299. package/dist/chunks/studio-7rrdbvex.js +0 -5
  300. package/dist/chunks/studio-9c6cbq2c.js +0 -4
  301. package/dist/chunks/studio-9dq7rwc7.js +0 -5
  302. package/dist/chunks/studio-9gr7xs9r.js +0 -6
  303. package/dist/chunks/studio-a88ej93p.js +0 -7
  304. package/dist/chunks/studio-acer0g8w.js +0 -5
  305. package/dist/chunks/studio-az3wvyh2.js +0 -5
  306. package/dist/chunks/studio-brh8d2pr.js +0 -5
  307. package/dist/chunks/studio-c2fa783p.js +0 -9
  308. package/dist/chunks/studio-cfnn70h1.js +0 -5
  309. package/dist/chunks/studio-cqxj4fcy.js +0 -5
  310. package/dist/chunks/studio-cw11h078.js +0 -5
  311. package/dist/chunks/studio-dn56z7sn.js +0 -5
  312. package/dist/chunks/studio-egnjpyde.js +0 -9
  313. package/dist/chunks/studio-ejrypvmq.js +0 -5
  314. package/dist/chunks/studio-frc16j8v.js +0 -10
  315. package/dist/chunks/studio-gnazwtv3.js +0 -5
  316. package/dist/chunks/studio-hfqpfmpk.js +0 -5
  317. package/dist/chunks/studio-hh394phe.js +0 -5
  318. package/dist/chunks/studio-j0zf5x2x.js +0 -5
  319. package/dist/chunks/studio-j5fcp6gj.js +0 -5
  320. package/dist/chunks/studio-jd1qb1st.js +0 -7
  321. package/dist/chunks/studio-k0xzrse2.js +0 -64
  322. package/dist/chunks/studio-k1xqrgm5.js +0 -5
  323. package/dist/chunks/studio-k3we3kqb.js +0 -5
  324. package/dist/chunks/studio-knsjyx7n.js +0 -5
  325. package/dist/chunks/studio-mrjtnm62.js +0 -5
  326. package/dist/chunks/studio-n9qba0gx.js +0 -5
  327. package/dist/chunks/studio-nzemfbkk.js +0 -14
  328. package/dist/chunks/studio-pwctzka3.js +0 -16
  329. package/dist/chunks/studio-q80fg1d6.js +0 -12
  330. package/dist/chunks/studio-q9hx2qp0.js +0 -5
  331. package/dist/chunks/studio-qrzmfaf6.js +0 -5
  332. package/dist/chunks/studio-r6mx7vm5.js +0 -7
  333. package/dist/chunks/studio-swedpd82.js +0 -5
  334. package/dist/chunks/studio-tmfcn347.js +0 -6
  335. package/dist/chunks/studio-v5wktj6f.js +0 -5
  336. package/dist/chunks/studio-vpevkfxw.js +0 -5
  337. package/dist/chunks/studio-wqfaqw4m.js +0 -5
  338. package/dist/chunks/studio-xpx91fby.js +0 -6
  339. package/dist/chunks/studio-xva3hzkv.js +0 -5
  340. package/dist/chunks/studio-z3fztxrp.js +0 -9
  341. package/dist/chunks/studio-z9453ypn.js +0 -6
  342. package/dist/chunks/toast-w11qpkyz.js +0 -4
  343. package/dist/chunks/tsMode-jg1dtphj.js +0 -14
  344. package/dist/chunks/workspace-6ztqcrsh.js +0 -4
  345. package/dist/chunks/workspace-home-rse9h8a0.js +0 -4
  346. package/src/content/draft-state.ts +0 -108
@@ -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
- const SLASH_COMMANDS = [
43
- { description: "Large heading", label: "Heading 1", tag: "h1" },
44
- { description: "Medium heading", label: "Heading 2", tag: "h2" },
45
- { description: "Small heading", label: "Heading 3", tag: "h3" },
46
- { description: "Plain text", label: "Paragraph", tag: "p" },
47
- { description: "Unordered list", label: "Bulleted List", tag: "ul" },
48
- { description: "Numbered list", label: "Numbered List", tag: "ol" },
49
- { description: "Quote block", label: "Blockquote", tag: "blockquote" },
50
- { description: "Insert image", label: "Image", tag: "img" },
51
- { description: "Divider line", label: "Horizontal Rule", tag: "hr" },
52
- { description: "Button element", label: "Button", tag: "button" },
53
- { description: "Anchor link", label: "Link", tag: "a" },
54
- { description: "Preformatted code", label: "Code Block", tag: "pre" },
55
- { description: "Insert table", label: "Table", tag: "table" },
56
- { description: "Container", label: "Div", tag: "div" },
57
- { description: "Section container", label: "Section", tag: "section" },
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 [...SLASH_COMMANDS, ...componentSlashCommands()];
148
+ return [...elementCommands(), ...componentSlashCommands()];
131
149
  }
132
150
 
133
151
  /** Everything on offer right now: what the caller named, or the standard set. */
@@ -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
- * Layouts are JSON at BOTH readers — `packages/compiler/src/site/layout-resolver.ts` `JSON.parse`s
73
- * and throws, `packages/studio/src/site-context.ts` `JSON.parse`s and silently returns null — and
74
- * there is no `"layout"` document kind for a format to declare, so this clause is hand-written and
75
- * cannot be derived. `layouts/` is excluded by {@link DOCUMENT_ROOTS} already; what remains is a
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 itself holds zero format knowledge — `.json` is the single native built-in.
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 type { JxDocumentValue, JxMutableNode } from "@jxsuite/schema/types";
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 { PROJECT_CONFIG_PATH, commitProjectConfig } from "../tabs/project-config";
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: PROJECT_CONFIG_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([[PROJECT_CONFIG_PATH, "*"]]),
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: PROJECT_CONFIG_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 draft perspective (site-architecture.md §7.6).**
16
- * `content/draft-state.ts` owns both the one flag and the one definition of what a draft is, and
17
- * this source calls it rather than testing `draft === true` itself — which is what makes "including
18
- * drafts" mean the same thing here as it does on the tab chip. It can, because that module imports
19
- * nothing but `reactivity`: the richer `content/entry-model.ts` asks THIS file where a collection's
20
- * entries live, so an edge back to it would be the cycle `import/no-cycle` rejects.
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
- DRAFT_FIELD,
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
- * Whether this listing is subject to the draft perspective (site-architecture.md §7.6) —
279
- * collections are, the pages tree is not. A draft is a property of a content ENTRY: a page
280
- * carrying `draft: true` would vanish from the only tree that lists it, with no collection schema
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
- drafts?: boolean;
278
+ holdDef?: () => HoldBearer | null | undefined;
284
279
  }
285
280
 
286
281
  /**
287
- * The live sources that obey the draft perspective.
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
- * draft filtered is decided by one `drafts: true` in this file, and re-deriving it from
291
- * `"grid://collection/"` in {@link reloadDraftAwareGrids} would be a second answer free to drift.
292
- * Membership is added by the engine off that same option, so the two cannot disagree. Weak, so a
293
- * closed tab's source is collectable.
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 draftAwareSources = new WeakSet<GridSource>();
290
+ const holdAwareSources = new WeakSet<GridSource>();
296
291
 
297
292
  /**
298
- * Reload every open grid whose rows depend on the draft perspective.
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.setIncludeDrafts` changing the flag has to say so. `load()` (not `refresh()`) is the
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 reloadDraftAwareGrids(): Promise<void> {
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 && draftAwareSources.has(controller.source)) {
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.drafts) {
321
- draftAwareSources.add(source);
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 = withDraftColumn([pathColumn, ...schemaColumns, ...extraColumns]);
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 Draft column immediately after Path, when this collection has a draft axis.
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. A collection whose entries only ever say `draft: false` has NO axis and is left alone: the
451
- * column is ordinary data there, and promoting it would invent a workflow the project never
452
- * declared.
429
+ * it.
453
430
  */
454
- const withDraftColumn = (columns: GridColumn[]): GridColumn[] => {
455
- const index = columns.findIndex((column) => column.field === DRAFT_FIELD);
456
- if (index < 1 || !draftAxis()) {
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 !== DRAFT_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 Draft column still knows the collection has an axis while its drafts are hidden, and
665
- // Turning the perspective back on is a re-list rather than a re-read of the directory.
666
- const listed = opts.drafts
667
- ? applyDraftFilter(
668
- [...entries.entries()],
669
- ([, entry]) => entry.frontmatter,
670
- includingDrafts(),
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
- drafts: true,
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
- const arrayNode = tab && selPath ? findEnclosingRepeater(tab.doc.document, selPath) : null;
1197
- if (arrayNode && tab) {
1196
+ if (tab && selPath && findEnclosingRepeater(tab.doc.document, selPath)) {
1198
1197
  const tokens = resolveRepeaterItemFields(
1199
- arrayNode,
1200
- tab.doc.document.state,
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
- el.textContent = tag;
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);