@jxsuite/studio 6.1.2 → 8.0.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 (522) hide show
  1. package/README.md +5 -5
  2. package/dist/chunks/2020-5wtzce5g.js +4 -0
  3. package/dist/chunks/adapter-5tphg7xe.js +4 -0
  4. package/dist/chunks/add-new-flow-psym14sn.js +4 -0
  5. package/dist/chunks/cf-account-picker-haqazq5r.js +4 -0
  6. package/dist/chunks/cf-settings-h2wrh6hq.js +4 -0
  7. package/dist/chunks/{color-area-0fj8a4ga.js → color-area-6rbra5dy.js} +2 -2
  8. package/dist/chunks/{color-field-xch8kx2g.js → color-field-gy56c94g.js} +3 -3
  9. package/dist/chunks/color-m7r1vk54.js +4 -0
  10. package/dist/chunks/{color-slider-jxd8v350.js → color-slider-sqqdyf6c.js} +2 -2
  11. package/dist/chunks/{combobox-p0efwjke.js → combobox-72h32ejk.js} +3 -3
  12. package/dist/chunks/components-r22xca09.js +4 -0
  13. package/dist/chunks/content-source-mmppmcyg.js +4 -0
  14. package/dist/chunks/convert-to-repeater-he70fc2b.js +4 -0
  15. package/dist/chunks/editor-ccd9jwsa.js +4 -0
  16. package/dist/chunks/entry-commands-6qxz9e8p.js +4 -0
  17. package/dist/chunks/extension-sections-0egb88dm.js +4 -0
  18. package/dist/chunks/file-ops-qnab36ek.js +4 -0
  19. package/dist/chunks/files-rcn56qvm.js +4 -0
  20. package/dist/chunks/format-host-yxz0kjjs.js +4 -0
  21. package/dist/chunks/fs-events-d1qxq4z4.js +4 -0
  22. package/dist/chunks/grid-open-4z1g062t.js +4 -0
  23. package/dist/chunks/{grid-view-d2wq9ezg.js → grid-view-qg1b5eqx.js} +25 -25
  24. package/dist/chunks/{icons-pnfz478q.js → icons-63bjtbs7.js} +2 -2
  25. package/dist/chunks/{iframe-entry-jdcktez8.js → iframe-entry-2zggzajv.js} +4 -4
  26. package/dist/chunks/{iframe-entry-zap785cw.js → iframe-entry-76j8vwwx.js} +4 -4
  27. package/dist/chunks/iframe-entry-99nr5zfb.js +5 -0
  28. package/dist/chunks/{iframe-entry-x0ygf2yr.js → iframe-entry-pkgmcgp0.js} +4 -4
  29. package/dist/chunks/iframe-host-ssv7kyj0.js +4 -0
  30. package/dist/chunks/{index-zpjmzqfk.js → index-32eqzv1k.js} +3 -3
  31. package/dist/chunks/index-f0jn7nnp.js +4 -0
  32. package/dist/chunks/{javascript-m54mnwp6.js → javascript-pjp734q4.js} +3 -3
  33. package/dist/chunks/jsonMode-njkvsa02.js +13 -0
  34. package/dist/chunks/jx-validate-ttasrbh7.js +4 -0
  35. package/dist/chunks/layers-bgyr11tr.js +4 -0
  36. package/dist/chunks/layers-panel-7ez66zeq.js +4 -0
  37. package/dist/chunks/{listbox-e74qjyq6.js → listbox-c421cf8c.js} +2 -2
  38. package/dist/chunks/lit-html-v9gh687x.js +4 -0
  39. package/dist/chunks/media-library-sq468wts.js +4 -0
  40. package/dist/chunks/media-picker-shfe59pt.js +4 -0
  41. package/dist/chunks/{menu-n6yzswmy.js → menu-bdpzbfvs.js} +2 -2
  42. package/dist/chunks/{monaco-binding-7gx83x3r.js → monaco-binding-vq1hgnn0.js} +3 -3
  43. package/dist/chunks/monaco-buffer-3afgwvbp.js +4 -0
  44. package/dist/chunks/monaco-setup-xrd2cbkp.js +4 -0
  45. package/dist/chunks/notify-kzrjr3fv.js +4 -0
  46. package/dist/chunks/parse-v77xtwv7.js +4 -0
  47. package/dist/chunks/platform-7v4rtd7m.js +4 -0
  48. package/dist/chunks/popover-b89sfej7.js +4 -0
  49. package/dist/chunks/publish-panel-2z9gw8ee.js +4 -0
  50. package/dist/chunks/publish-quick-qx2455cj.js +4 -0
  51. package/dist/chunks/publish-workspace-wrc1dgwx.js +4 -0
  52. package/dist/chunks/quick-search-n0t0eqxb.js +4 -0
  53. package/dist/chunks/references-9547rg5s.js +4 -0
  54. package/dist/chunks/right-panel-wsgnr9pn.js +4 -0
  55. package/dist/chunks/source-control-kwktr9y6.js +4 -0
  56. package/dist/chunks/{split-n2gfpm5m.js → split-c9zfdgxf.js} +2 -2
  57. package/dist/chunks/studio-03ct6yfv.js +9 -0
  58. package/dist/chunks/studio-07rcfje3.js +5 -0
  59. package/dist/chunks/studio-3e54qh39.js +7 -0
  60. package/dist/chunks/studio-3exxhz5g.js +5 -0
  61. package/dist/chunks/studio-3q40e3rj.js +5 -0
  62. package/dist/chunks/studio-3qeh9t0h.js +6 -0
  63. package/dist/chunks/studio-3qqjeep5.js +6 -0
  64. package/dist/chunks/studio-43sd2mam.js +4 -0
  65. package/dist/chunks/studio-4hp8pkyt.js +5 -0
  66. package/dist/chunks/studio-58xg6j2q.js +9 -0
  67. package/dist/chunks/studio-5g0rprdt.js +7 -0
  68. package/dist/chunks/{studio-2j1cb95x.js → studio-6m380wp7.js} +4 -4
  69. package/dist/chunks/studio-80jyh1xh.js +4 -0
  70. package/dist/chunks/studio-83mk4bgg.js +9 -0
  71. package/dist/chunks/{studio-v0jzqdjm.js → studio-881z1xrx.js} +4 -4
  72. package/dist/chunks/studio-8m0y2qe7.js +5 -0
  73. package/dist/chunks/{studio-d81xfh1g.js → studio-8tr6cfrc.js} +4 -4
  74. package/dist/chunks/studio-8vcvfs7r.js +6 -0
  75. package/dist/chunks/studio-9tmebf0s.js +5 -0
  76. package/dist/chunks/studio-a2bvaptz.js +5 -0
  77. package/dist/chunks/studio-b7y5mr30.js +5 -0
  78. package/dist/chunks/studio-bavb31r0.js +5 -0
  79. package/dist/chunks/{studio-dgz8d0c1.js → studio-bbe4c90a.js} +4 -4
  80. package/dist/chunks/studio-c05wdnt3.js +5 -0
  81. package/dist/chunks/studio-ccv2svc6.js +5 -0
  82. package/dist/chunks/studio-d5grtb9e.js +16 -0
  83. package/dist/chunks/studio-df0baea9.js +5 -0
  84. package/dist/chunks/studio-dgk4dqtx.js +7 -0
  85. package/dist/chunks/studio-e1g64q43.js +5 -0
  86. package/dist/chunks/studio-ed1hp6zf.js +6 -0
  87. package/dist/chunks/studio-ed3xx40h.js +7 -0
  88. package/dist/chunks/studio-f14e1zdj.js +5 -0
  89. package/dist/chunks/studio-fbhxxc5f.js +5 -0
  90. package/dist/chunks/studio-fyh0jc96.js +5 -0
  91. package/dist/chunks/studio-gcxk20n7.js +5 -0
  92. package/dist/chunks/studio-jaaz8ce9.js +5 -0
  93. package/dist/chunks/{studio-90pq9km1.js → studio-jcnsdaep.js} +4 -4
  94. package/dist/chunks/studio-jsgf2q5k.js +7 -0
  95. package/dist/chunks/{studio-0q988f9w.js → studio-k3xxbagr.js} +3 -3
  96. package/dist/chunks/{studio-5ve6bbep.js → studio-k7evxqh4.js} +4 -4
  97. package/dist/chunks/studio-kt3pjkwd.js +5 -0
  98. package/dist/chunks/studio-m8g16bgz.js +5 -0
  99. package/dist/chunks/studio-mn0476ag.js +5 -0
  100. package/dist/chunks/studio-mx1smyq0.js +5 -0
  101. package/dist/chunks/studio-n0c5h6mf.js +5 -0
  102. package/dist/chunks/{studio-485046te.js → studio-nh6d342n.js} +7 -7
  103. package/dist/chunks/studio-nqxqf8p2.js +4 -0
  104. package/dist/chunks/studio-p4nrkvqr.js +5 -0
  105. package/dist/chunks/{studio-1fxkaaea.js → studio-pbxa5fpc.js} +4 -4
  106. package/dist/chunks/studio-pgmvdv7z.js +7 -0
  107. package/dist/chunks/studio-ph84avkf.js +5 -0
  108. package/dist/chunks/{studio-tq0rtfwj.js → studio-q0mvbaq6.js} +4 -4
  109. package/dist/chunks/studio-q0tccd43.js +5 -0
  110. package/dist/chunks/studio-q7rtx6zq.js +14 -0
  111. package/dist/chunks/{studio-a3g98wjr.js → studio-qgh13jtz.js} +4 -4
  112. package/dist/chunks/studio-qmn1xc24.js +5 -0
  113. package/dist/chunks/studio-qwdqwkj0.js +5 -0
  114. package/dist/chunks/studio-r5qy30vx.js +5 -0
  115. package/dist/chunks/studio-rj9eamf8.js +5 -0
  116. package/dist/chunks/studio-rptgsk96.js +5 -0
  117. package/dist/chunks/studio-rthyg42z.js +928 -0
  118. package/dist/chunks/studio-s19tp1xr.js +5 -0
  119. package/dist/chunks/studio-s32578pn.js +5 -0
  120. package/dist/chunks/studio-svb7yyve.js +5 -0
  121. package/dist/chunks/studio-syreb644.js +5 -0
  122. package/dist/chunks/studio-tf9jryb1.js +10 -0
  123. package/dist/chunks/studio-tfkznv39.js +5 -0
  124. package/dist/chunks/studio-tft6r105.js +5 -0
  125. package/dist/chunks/studio-txf2kysx.js +5 -0
  126. package/dist/chunks/studio-v8867ddv.js +12 -0
  127. package/dist/chunks/studio-vxq7fcvv.js +5 -0
  128. package/dist/chunks/studio-w4rz9hdk.js +5 -0
  129. package/dist/chunks/studio-wbcjbhy1.js +9 -0
  130. package/dist/chunks/studio-wc649wss.js +5 -0
  131. package/dist/chunks/studio-wys23mxz.js +5 -0
  132. package/dist/chunks/studio-x1x167jh.js +5 -0
  133. package/dist/chunks/studio-x9hsze61.js +5 -0
  134. package/dist/chunks/studio-y0n7yxea.js +9 -0
  135. package/dist/chunks/studio-y3ahhdpx.js +5 -0
  136. package/dist/chunks/studio-z97c48fk.js +5 -0
  137. package/dist/chunks/studio-zat62m0c.js +5 -0
  138. package/dist/chunks/studio-zcd2e7yz.js +5 -0
  139. package/dist/chunks/studio-zz9qe3rw.js +64 -0
  140. package/dist/chunks/toast-1gv0h53h.js +4 -0
  141. package/dist/chunks/{toast-host-ke5z4ng7.js → toast-host-1yg5ar8d.js} +3 -3
  142. package/dist/chunks/{tooltip-5v8ngwj2.js → tooltip-yypz28gq.js} +3 -3
  143. package/dist/chunks/{tree-mdq3v5cw.js → tree-9xa4hm7n.js} +2 -2
  144. package/dist/chunks/tsMode-epyfz1hv.js +14 -0
  145. package/dist/chunks/workspace-dz7nxfhv.js +4 -0
  146. package/dist/chunks/workspace-home-xsd7hkjw.js +4 -0
  147. package/dist/chunks/{ws-client-nze0k6mv.js → ws-client-bv552sfx.js} +3 -3
  148. package/dist/iframe-entry.js +20 -16
  149. package/dist/manifest.json +77 -63
  150. package/dist/studio.js +103 -104
  151. package/dist/workers/ts.worker.js +3 -3
  152. package/package.json +12 -11
  153. package/src/browse/component-examples.ts +272 -0
  154. package/src/browse/library-commands.ts +64 -104
  155. package/src/browse/library-layouts.ts +263 -49
  156. package/src/browse/library-model.ts +181 -133
  157. package/src/browse/library-pane.ts +738 -446
  158. package/src/browse/library-preview.ts +149 -19
  159. package/src/browse/library-source.ts +308 -41
  160. package/src/browse/preview-root.ts +78 -0
  161. package/src/browse/preview-style.ts +155 -0
  162. package/src/canvas/asset-refs.ts +16 -1
  163. package/src/canvas/canvas-live-render.ts +222 -30
  164. package/src/canvas/canvas-origin.ts +37 -12
  165. package/src/canvas/canvas-page-context.ts +112 -0
  166. package/src/canvas/canvas-render.ts +113 -107
  167. package/src/canvas/canvas-utils.ts +57 -12
  168. package/src/canvas/iframe-drop.ts +107 -0
  169. package/src/canvas/iframe-entry.ts +24 -12
  170. package/src/canvas/iframe-host.ts +134 -62
  171. package/src/canvas/iframe-keys.ts +11 -0
  172. package/src/canvas/iframe-patch.ts +8 -7
  173. package/src/canvas/iframe-protocol.ts +24 -5
  174. package/src/canvas/iframe-render.ts +48 -1
  175. package/src/canvas/iframe-subtree.ts +5 -2
  176. package/src/canvas/npm-modules.ts +22 -0
  177. package/src/canvas/preview-navigate.ts +16 -0
  178. package/src/canvas/repeater-sources.ts +200 -0
  179. package/src/canvas/stage-scrollbar.ts +5 -1
  180. package/src/canvas/surface-registry.ts +6 -0
  181. package/src/commands/app-commands.ts +16 -6
  182. package/src/commands/budget.ts +10 -8
  183. package/src/commands/context.ts +35 -10
  184. package/src/commands/defaults.ts +69 -45
  185. package/src/commands/keymap.ts +9 -0
  186. package/src/commands/levels.ts +11 -5
  187. package/src/commands/live-context.ts +2 -0
  188. package/src/commands/registry.ts +10 -1
  189. package/src/content/collection-match.ts +6 -5
  190. package/src/content/collection-pages.ts +412 -0
  191. package/src/content/content-collections.ts +270 -0
  192. package/src/content/entry-commands.ts +51 -60
  193. package/src/content/entry-editor.ts +111 -18
  194. package/src/content/entry-fields.ts +13 -1
  195. package/src/content/entry-inspector.ts +225 -0
  196. package/src/content/entry-model.ts +17 -3
  197. package/src/content/entry-open.ts +80 -0
  198. package/src/content/entry-summaries.ts +440 -0
  199. package/src/content/file-record-ids.ts +80 -0
  200. package/src/content/file-records.ts +125 -0
  201. package/src/content/page-summaries.ts +129 -0
  202. package/src/content/record-entry.ts +201 -0
  203. package/src/content/writing-banner.ts +177 -0
  204. package/src/create/add-new-commands.ts +185 -0
  205. package/src/create/add-new-flow.ts +1116 -0
  206. package/src/create/add-new.ts +1014 -0
  207. package/src/create/element-name.ts +121 -0
  208. package/src/editor/convert-to-component.ts +67 -43
  209. package/src/editor/file-drop-action.ts +9 -1
  210. package/src/editor/inline-edit-apply.ts +44 -0
  211. package/src/editor/inline-edit.ts +10 -0
  212. package/src/editor/insert-component.ts +91 -0
  213. package/src/editor/insert-zone-action.ts +13 -4
  214. package/src/editor/shortcuts.ts +44 -22
  215. package/src/editor/slash-menu.ts +51 -8
  216. package/src/files/components.ts +74 -10
  217. package/src/files/file-ops.ts +15 -5
  218. package/src/files/files.ts +164 -150
  219. package/src/files/fs-events.ts +18 -7
  220. package/src/files/media-meta.ts +95 -1
  221. package/src/files/media-types.ts +55 -0
  222. package/src/files/media-upload.ts +209 -52
  223. package/src/files/new-file-name.ts +156 -0
  224. package/src/files/serialize-document.ts +13 -3
  225. package/src/format/format-host.ts +15 -0
  226. package/src/github/github-auth.ts +2 -2
  227. package/src/github/github-publish.ts +4 -3
  228. package/src/grid/grid-controller.ts +5 -1
  229. package/src/grid/grid-open.ts +0 -27
  230. package/src/grid/grid-panel.ts +54 -2
  231. package/src/grid/grid-reveal.ts +34 -0
  232. package/src/grid/grid-source.ts +3 -25
  233. package/src/grid/grid-view.ts +31 -6
  234. package/src/grid/sources/content-source.ts +75 -7
  235. package/src/grid/tabulator-tables.d.ts +6 -0
  236. package/src/hosting/document.ts +2 -2
  237. package/src/i18n/i18n-commands.ts +2 -1
  238. package/src/media/media-commands.ts +181 -0
  239. package/src/media/media-library-model.ts +232 -0
  240. package/src/media/media-library.ts +1283 -0
  241. package/src/media/media-pane.ts +47 -19
  242. package/src/packages/pull-package-sync.ts +1 -1
  243. package/src/panels/activity-panel.ts +21 -5
  244. package/src/panels/ai-chat/chat-markdown.ts +8 -8
  245. package/src/panels/block-action-bar.ts +11 -11
  246. package/src/panels/canvas-dnd-bridge.ts +5 -2
  247. package/src/panels/chat-panel.ts +6 -6
  248. package/src/panels/collections-panel.ts +306 -0
  249. package/src/panels/component-preview.ts +35 -13
  250. package/src/panels/data-explorer.ts +1 -1
  251. package/src/panels/data-grid.ts +4 -0
  252. package/src/panels/dnd.ts +17 -30
  253. package/src/panels/elements-panel.ts +9 -2
  254. package/src/panels/empty-state.ts +2 -2
  255. package/src/panels/formula-workspace.ts +2 -3
  256. package/src/panels/frontmatter-fields.ts +9 -9
  257. package/src/panels/git-diff-open.ts +9 -5
  258. package/src/panels/head-panel.ts +369 -103
  259. package/src/panels/i18n-panel.ts +11 -10
  260. package/src/panels/imports-panel.ts +2 -0
  261. package/src/panels/jump-bar.ts +27 -2
  262. package/src/panels/layers-panel.ts +36 -30
  263. package/src/panels/left-panel.ts +22 -8
  264. package/src/panels/navigator-panels.ts +23 -11
  265. package/src/panels/pane-context.ts +111 -29
  266. package/src/panels/pane-grid.ts +6 -6
  267. package/src/panels/panel-registry.ts +14 -9
  268. package/src/panels/properties-panel.ts +7 -3
  269. package/src/panels/right-panel.ts +199 -43
  270. package/src/panels/seo-modal.ts +41 -108
  271. package/src/panels/settings-menu.ts +52 -50
  272. package/src/panels/settings-pane.ts +268 -162
  273. package/src/panels/signals-panel.ts +3 -4
  274. package/src/panels/site-model.ts +367 -0
  275. package/src/panels/site-panel.ts +638 -0
  276. package/src/panels/style-panel.ts +37 -3
  277. package/src/panels/stylebook-doc.ts +142 -10
  278. package/src/panels/stylebook-layers-panel.ts +39 -80
  279. package/src/panels/stylebook-panel.ts +47 -10
  280. package/src/panels/styles-panel.ts +327 -0
  281. package/src/panels/tab-strip.ts +109 -25
  282. package/src/platform.ts +1 -1
  283. package/src/platforms/cloud.ts +28 -1
  284. package/src/platforms/default-platform.ts +4 -4
  285. package/src/platforms/devserver.ts +51 -25
  286. package/src/publish/deploy-checklist.ts +12 -7
  287. package/src/publish/pages-service.ts +38 -14
  288. package/src/publish/publish-changes.ts +491 -0
  289. package/src/publish/publish-commands.ts +83 -18
  290. package/src/publish/publish-ids.ts +49 -0
  291. package/src/publish/publish-quick.ts +148 -0
  292. package/src/publish/publish-workspace.ts +926 -0
  293. package/src/publish/source-control.ts +907 -0
  294. package/src/services/automation.ts +16 -20
  295. package/src/services/cf-settings.ts +4 -4
  296. package/src/services/connection.ts +2 -2
  297. package/src/services/directory-picker.ts +2 -2
  298. package/src/services/references.ts +0 -0
  299. package/src/services/render-critic.ts +21 -2
  300. package/src/services/settings/definitions.ts +25 -14
  301. package/src/settings/contributed-section.ts +49 -10
  302. package/src/settings/css-vars-editor.ts +93 -27
  303. package/src/settings/developer-panels.ts +43 -0
  304. package/src/settings/extension-sections.ts +20 -2
  305. package/src/settings/general-settings.ts +91 -21
  306. package/src/settings/preferences-dialog.ts +8 -1
  307. package/src/settings/project-sections.ts +27 -10
  308. package/src/settings/schema-field-ui.ts +3 -1
  309. package/src/settings/section-registry.ts +116 -11
  310. package/src/settings/settings-dialog.ts +192 -0
  311. package/src/settings/settings-document.ts +182 -162
  312. package/src/shell.ts +467 -431
  313. package/src/site-context.ts +64 -10
  314. package/src/state.ts +28 -9
  315. package/src/studio.ts +194 -41
  316. package/src/style/project-styles.ts +34 -0
  317. package/src/style/stylebook-specimens.ts +89 -0
  318. package/src/style/stylebook-target.ts +26 -0
  319. package/src/style/tag-inheritance.ts +105 -0
  320. package/src/style/variables-focus.ts +61 -0
  321. package/src/style/variables-view.ts +235 -0
  322. package/src/surfaces/add-new.json +1482 -0
  323. package/src/surfaces/add-new.ts +325 -0
  324. package/src/surfaces/ai-chat.ts +2 -2
  325. package/src/surfaces/boot-failure.json +4 -3
  326. package/src/surfaces/bottom-dock.json +1 -1
  327. package/src/surfaces/canvas-stage.json +30 -19
  328. package/src/surfaces/canvas-stage.ts +5 -5
  329. package/src/surfaces/collections-panel.json +615 -0
  330. package/src/surfaces/collections-panel.ts +168 -0
  331. package/src/surfaces/commandbar.json +241 -96
  332. package/src/surfaces/commandbar.ts +134 -99
  333. package/src/surfaces/empty-state.json +1 -1
  334. package/src/surfaces/entry-editor.json +136 -0
  335. package/src/surfaces/entry-editor.ts +21 -0
  336. package/src/surfaces/entry-inspector.json +371 -0
  337. package/src/surfaces/entry-inspector.ts +143 -0
  338. package/src/surfaces/inspector-dock.json +162 -14
  339. package/src/surfaces/inspector-dock.ts +46 -19
  340. package/src/surfaces/library-pane.json +670 -437
  341. package/src/surfaces/library-pane.ts +94 -59
  342. package/src/surfaces/media-browser.json +62 -6
  343. package/src/surfaces/media-browser.ts +15 -1
  344. package/src/surfaces/media-field.json +1 -1
  345. package/src/surfaces/media-library.json +1586 -0
  346. package/src/surfaces/media-library.ts +282 -0
  347. package/src/surfaces/media-pick-dialog.json +71 -0
  348. package/src/surfaces/media-pick-dialog.ts +132 -0
  349. package/src/surfaces/menu.ts +18 -0
  350. package/src/surfaces/pane-context.json +144 -77
  351. package/src/surfaces/pane-context.ts +24 -0
  352. package/src/surfaces/panel-deploy-checklist.json +12 -6
  353. package/src/surfaces/panel-deploy-checklist.ts +3 -1
  354. package/src/surfaces/panel-page.json +72 -2
  355. package/src/surfaces/panel-page.ts +38 -7
  356. package/src/surfaces/panel-signals.json +1 -1
  357. package/src/surfaces/panel-signals.ts +5 -5
  358. package/src/surfaces/panel-stylebook-layers.json +71 -204
  359. package/src/surfaces/panel-stylebook-layers.ts +0 -21
  360. package/src/surfaces/preferences.json +120 -48
  361. package/src/surfaces/preferences.ts +8 -0
  362. package/src/surfaces/publish-quick.json +428 -0
  363. package/src/surfaces/publish-quick.ts +165 -0
  364. package/src/surfaces/publish-workspace.json +2892 -0
  365. package/src/surfaces/publish-workspace.ts +391 -0
  366. package/src/surfaces/rail.json +1 -159
  367. package/src/surfaces/rail.ts +18 -75
  368. package/src/surfaces/schema-form.json +10 -2
  369. package/src/surfaces/schema-form.ts +2 -0
  370. package/src/surfaces/seo.json +1 -1
  371. package/src/surfaces/settings-contributed.json +5 -5
  372. package/src/surfaces/settings-contributed.ts +18 -2
  373. package/src/surfaces/settings-css-vars.json +44 -36
  374. package/src/surfaces/settings-css-vars.ts +77 -4
  375. package/src/surfaces/settings-deploy.json +2 -15
  376. package/src/surfaces/settings-deploy.ts +1 -1
  377. package/src/surfaces/settings-dialog.json +51 -0
  378. package/src/surfaces/settings-dialog.ts +169 -0
  379. package/src/surfaces/settings-overview.json +3 -3
  380. package/src/surfaces/settings-overview.ts +9 -2
  381. package/src/surfaces/settings-pane.json +269 -46
  382. package/src/surfaces/settings-pane.ts +85 -24
  383. package/src/surfaces/shell.json +2 -1
  384. package/src/surfaces/site-panel.json +1029 -0
  385. package/src/surfaces/site-panel.ts +233 -0
  386. package/src/surfaces/slash-menu.json +63 -21
  387. package/src/surfaces/slash-menu.ts +23 -0
  388. package/src/surfaces/statusbar.ts +39 -15
  389. package/src/surfaces/styles-panel.json +735 -0
  390. package/src/surfaces/styles-panel.ts +195 -0
  391. package/src/surfaces/styles-variables.json +149 -0
  392. package/src/surfaces/styles-variables.ts +145 -0
  393. package/src/surfaces/tab-strip.json +4 -4
  394. package/src/surfaces/welcome.ts +1 -1
  395. package/src/surfaces/writing-banner.json +94 -0
  396. package/src/surfaces/writing-banner.ts +80 -0
  397. package/src/tabs/bare-repeaters.ts +101 -0
  398. package/src/tabs/tab.ts +47 -7
  399. package/src/types.ts +21 -26
  400. package/src/ui/form-controls.ts +47 -8
  401. package/src/ui/layers.ts +13 -0
  402. package/src/ui/media-picker.ts +284 -40
  403. package/src/ui/regions.ts +7 -6
  404. package/src/ui/schema-form.ts +259 -27
  405. package/src/ui/virtual-window.ts +26 -1
  406. package/src/utils/base64.ts +3 -3
  407. package/src/utils/edit-display.ts +322 -67
  408. package/src/utils/local-day.ts +18 -0
  409. package/src/workspace/pane-derive.ts +4 -4
  410. package/src/workspace/session.ts +28 -7
  411. package/src/workspace/tab-workspace.ts +137 -0
  412. package/src/workspace/workspace-back.ts +189 -0
  413. package/src/workspace/workspace-home.ts +133 -0
  414. package/src/workspace/workspace-ids.ts +129 -0
  415. package/src/workspace/workspace-switch.ts +372 -0
  416. package/src/workspace/workspace.ts +125 -11
  417. package/styles/shell-frame.css +30 -7
  418. package/styles/shell-frame.json +15 -3
  419. package/dist/chunks/2020-f2gkpy8d.js +0 -4
  420. package/dist/chunks/adapter-4912g339.js +0 -4
  421. package/dist/chunks/cf-account-picker-wccaenh6.js +0 -4
  422. package/dist/chunks/cf-settings-br6105tr.js +0 -4
  423. package/dist/chunks/color-28ksk2qm.js +0 -4
  424. package/dist/chunks/components-db68vsn7.js +0 -4
  425. package/dist/chunks/content-source-1449qesq.js +0 -4
  426. package/dist/chunks/convert-to-repeater-5h457egj.js +0 -4
  427. package/dist/chunks/editor-x9jn86a7.js +0 -4
  428. package/dist/chunks/entry-commands-may24cpt.js +0 -4
  429. package/dist/chunks/extension-sections-pvr14vxr.js +0 -4
  430. package/dist/chunks/file-ops-x9v54pcj.js +0 -4
  431. package/dist/chunks/files-j3knqf0s.js +0 -4
  432. package/dist/chunks/format-host-tyre2trb.js +0 -4
  433. package/dist/chunks/fs-events-64ma9htc.js +0 -4
  434. package/dist/chunks/git-panel-84ps5dex.js +0 -4
  435. package/dist/chunks/iframe-entry-fvbk89gm.js +0 -5
  436. package/dist/chunks/iframe-host-ck6vejt5.js +0 -4
  437. package/dist/chunks/index-b8xw3vy9.js +0 -4
  438. package/dist/chunks/jsonMode-w15cmrcm.js +0 -13
  439. package/dist/chunks/jx-validate-at3zn8wa.js +0 -4
  440. package/dist/chunks/layers-h4xp1pss.js +0 -4
  441. package/dist/chunks/layers-panel-m5hw1d7g.js +0 -4
  442. package/dist/chunks/lit-html-8t8t3dnr.js +0 -4
  443. package/dist/chunks/media-picker-2cweexc5.js +0 -4
  444. package/dist/chunks/menu-ynw13y0s.js +0 -4
  445. package/dist/chunks/monaco-buffer-qzxn567r.js +0 -4
  446. package/dist/chunks/monaco-setup-gxf6656g.js +0 -4
  447. package/dist/chunks/notify-cj5zbrs2.js +0 -4
  448. package/dist/chunks/parse-b7ddhr8m.js +0 -4
  449. package/dist/chunks/platform-3dfws9js.js +0 -4
  450. package/dist/chunks/popover-jt2gxmzs.js +0 -4
  451. package/dist/chunks/publish-panel-1gvxbj21.js +0 -4
  452. package/dist/chunks/quick-search-0dajrwqg.js +0 -4
  453. package/dist/chunks/references-49qgt404.js +0 -4
  454. package/dist/chunks/right-panel-jm1bwe53.js +0 -4
  455. package/dist/chunks/section-registry-6kwc4fky.js +0 -4
  456. package/dist/chunks/studio-0rt52vpq.js +0 -5
  457. package/dist/chunks/studio-18vgbhs5.js +0 -5
  458. package/dist/chunks/studio-24mccdrj.js +0 -5
  459. package/dist/chunks/studio-2eh507zk.js +0 -9
  460. package/dist/chunks/studio-2hcbccfv.js +0 -5
  461. package/dist/chunks/studio-2jbs3rxv.js +0 -5
  462. package/dist/chunks/studio-4h8k4dw9.js +0 -5
  463. package/dist/chunks/studio-5f6n18kx.js +0 -5
  464. package/dist/chunks/studio-5gthpky6.js +0 -5
  465. package/dist/chunks/studio-6ep3js6b.js +0 -9
  466. package/dist/chunks/studio-6wkmf3wz.js +0 -9
  467. package/dist/chunks/studio-841rf3cg.js +0 -5
  468. package/dist/chunks/studio-8gmwgheg.js +0 -5
  469. package/dist/chunks/studio-8qm0ep50.js +0 -5
  470. package/dist/chunks/studio-924p1tm1.js +0 -6
  471. package/dist/chunks/studio-a3shpz9y.js +0 -5
  472. package/dist/chunks/studio-amsbaaar.js +0 -6
  473. package/dist/chunks/studio-at5spjbc.js +0 -7
  474. package/dist/chunks/studio-b2xv945e.js +0 -5
  475. package/dist/chunks/studio-bn5nkafw.js +0 -5
  476. package/dist/chunks/studio-bw6q5xjf.js +0 -9
  477. package/dist/chunks/studio-c7q0t9x1.js +0 -5
  478. package/dist/chunks/studio-camnhc96.js +0 -6
  479. package/dist/chunks/studio-cffqvnzd.js +0 -5
  480. package/dist/chunks/studio-efvfrsf8.js +0 -5
  481. package/dist/chunks/studio-er30bpcx.js +0 -5
  482. package/dist/chunks/studio-ezscgr64.js +0 -5
  483. package/dist/chunks/studio-ffrrncvz.js +0 -5
  484. package/dist/chunks/studio-fpphgn8y.js +0 -10
  485. package/dist/chunks/studio-j1kvhdjk.js +0 -4
  486. package/dist/chunks/studio-j430n16g.js +0 -7
  487. package/dist/chunks/studio-jjkg84nj.js +0 -5
  488. package/dist/chunks/studio-m0q32ars.js +0 -4
  489. package/dist/chunks/studio-m6bdst11.js +0 -5
  490. package/dist/chunks/studio-nv2cptd4.js +0 -64
  491. package/dist/chunks/studio-nwfrc98n.js +0 -5
  492. package/dist/chunks/studio-p1wgvp7r.js +0 -5
  493. package/dist/chunks/studio-pag2wncm.js +0 -5
  494. package/dist/chunks/studio-qvanvzfd.js +0 -5
  495. package/dist/chunks/studio-qwa64kt7.js +0 -5
  496. package/dist/chunks/studio-rk1zc4x3.js +0 -928
  497. package/dist/chunks/studio-se2r3ptc.js +0 -5
  498. package/dist/chunks/studio-sg2a0dh0.js +0 -5
  499. package/dist/chunks/studio-tjzjhe68.js +0 -5
  500. package/dist/chunks/studio-v2w7q8r1.js +0 -7
  501. package/dist/chunks/studio-v5vjq1h0.js +0 -4
  502. package/dist/chunks/studio-v8aga3vp.js +0 -14
  503. package/dist/chunks/studio-vfcdrfmv.js +0 -5
  504. package/dist/chunks/studio-vmfkgdjf.js +0 -5
  505. package/dist/chunks/studio-wt1816jj.js +0 -5
  506. package/dist/chunks/studio-wz73q5kq.js +0 -4
  507. package/dist/chunks/studio-y04qtxvp.js +0 -7
  508. package/dist/chunks/studio-y21g8zzg.js +0 -5
  509. package/dist/chunks/studio-y64hrt8p.js +0 -12
  510. package/dist/chunks/studio-ynn90f58.js +0 -16
  511. package/dist/chunks/studio-yxz682sx.js +0 -5
  512. package/dist/chunks/studio-zp7snvkq.js +0 -5
  513. package/dist/chunks/studio-zq4z6tm6.js +0 -5
  514. package/dist/chunks/toast-c9fn075p.js +0 -4
  515. package/dist/chunks/tsMode-sf49ee9x.js +0 -14
  516. package/dist/chunks/workspace-jtfahpee.js +0 -4
  517. package/src/panels/frontmatter-panel.ts +0 -711
  518. package/src/panels/git-panel.ts +0 -1108
  519. package/src/surfaces/doc-header.json +0 -776
  520. package/src/surfaces/doc-header.ts +0 -222
  521. package/src/surfaces/git-panel.json +0 -1880
  522. package/src/surfaces/git-panel.ts +0 -264
@@ -0,0 +1,129 @@
1
+ /**
2
+ * What the Content workspace's Pages list says about each page beyond its file: the template it is
3
+ * drawn in, the collection a dynamic page expands over, and whether it is marked a draft
4
+ * (`specs/studio.md` §22.5, the Content-Pages board: Title, URL, Template, Status, Edited).
5
+ *
6
+ * The scan behind the Content home knows each page's FILE; its `$layout`, `$paths` and `draft` are
7
+ * inside it. So this reads each page once, through the same parse the editor uses, and keeps the
8
+ * answer until a file event drops it ({@link invalidatePageSummaries}, wired beside the
9
+ * template-page index in `studio.ts`). The read never rejects: a page that cannot be read states
10
+ * nothing, and is listed by its route alone.
11
+ *
12
+ * @docs studio/projects/browse
13
+ */
14
+
15
+ import { isNonNullObject } from "@jxsuite/schema/guards";
16
+ import { getPlatform, hasPlatform } from "../platform";
17
+ import { pageFactsOf } from "./collection-pages";
18
+ import { DRAFT_FIELD } from "./draft-state";
19
+ import type { FormatFrontmatter } from "../format/format-host";
20
+ import type { JxMutableNode } from "@jxsuite/schema/types";
21
+
22
+ /** One page, as the Pages list states it. */
23
+ export interface PageSummary {
24
+ path: string;
25
+ /** Its `$layout`: a path, `false` (no layout), or `undefined` (the project's default). */
26
+ layout: string | false | undefined;
27
+ /** The collection its `$paths` expands over, or `null` for a page of its own. */
28
+ contentType: string | null;
29
+ /** Whether the page carries `draft: true`, the one draft marker a page can have. */
30
+ draft: boolean;
31
+ }
32
+
33
+ /** What a page that could not be read states: nothing. */
34
+ function unread(path: string): PageSummary {
35
+ return { contentType: null, draft: false, layout: undefined, path };
36
+ }
37
+
38
+ /** The summary a parsed page states. Exported so the rule is assertable without a platform. */
39
+ export function pageSummaryOf(
40
+ path: string,
41
+ document: JxMutableNode | null,
42
+ frontmatter: FormatFrontmatter | null,
43
+ ): PageSummary {
44
+ const { contentType, layout } = pageFactsOf(document, frontmatter);
45
+ const draft = frontmatter?.[DRAFT_FIELD] === true || document?.[DRAFT_FIELD] === true;
46
+ return { contentType, draft, layout, path };
47
+ }
48
+
49
+ /** Read one page. */
50
+ async function readSummary(path: string): Promise<PageSummary> {
51
+ try {
52
+ const source = await getPlatform().readFile(path);
53
+ if (path.endsWith(".json")) {
54
+ const parsed: unknown = JSON.parse(source);
55
+ /* SAFETY: a `.json` file under `pages/` is a page document by the project layout; one that is
56
+ not an object states nothing, which is the branch below. */
57
+ return isNonNullObject(parsed) && !Array.isArray(parsed)
58
+ ? pageSummaryOf(path, parsed as JxMutableNode, null)
59
+ : unread(path);
60
+ }
61
+ const { parseSourceForPath } = await import("../files/file-ops");
62
+ const parsed = await parseSourceForPath(path, source);
63
+ /* SAFETY: a format class's parser returns its file as a Jx element tree (the format contract);
64
+ only `$layout`, `$paths` and `draft` are read off it. */
65
+ return pageSummaryOf(path, parsed.document as JxMutableNode, parsed.frontmatter);
66
+ } catch {
67
+ return unread(path);
68
+ }
69
+ }
70
+
71
+ // ─── The cache ───────────────────────────────────────────────────────────────
72
+
73
+ /** At most this many pages are read for one list; the rest are listed by route alone. */
74
+ export const PAGE_READ_LIMIT = 300;
75
+
76
+ let _cache: { key: string; pages: Map<string, PageSummary> } | null = null;
77
+ let _loading: { key: string; read: Promise<void> } | null = null;
78
+ let _epoch = 0;
79
+ const _listeners = new Set<() => void>();
80
+
81
+ /**
82
+ * The pages' summaries as they stand, keyed by path: the map, or `null` while they are being read
83
+ * (which starts the read; listeners are told when it lands).
84
+ *
85
+ * @param paths The page files, in the order the list draws them.
86
+ */
87
+ export function peekPageSummaries(
88
+ paths: readonly string[],
89
+ ): ReadonlyMap<string, PageSummary> | null {
90
+ const key = paths.join("\n");
91
+ if (_cache?.key === key) {
92
+ return _cache.pages;
93
+ }
94
+ if (!hasPlatform()) {
95
+ return new Map(paths.map((path) => [path, unread(path)]));
96
+ }
97
+ if (_loading?.key !== key) {
98
+ const epoch = _epoch;
99
+ const read = Promise.all(paths.slice(0, PAGE_READ_LIMIT).map((path) => readSummary(path))).then(
100
+ (summaries) => {
101
+ if (epoch !== _epoch || _loading?.read !== read) {
102
+ return;
103
+ }
104
+ _loading = null;
105
+ _cache = { key, pages: new Map(summaries.map((summary) => [summary.path, summary])) };
106
+ for (const listener of _listeners) {
107
+ listener();
108
+ }
109
+ },
110
+ );
111
+ _loading = { key, read };
112
+ }
113
+ return null;
114
+ }
115
+
116
+ /** Forget every summary. The next list re-reads its pages. */
117
+ export function invalidatePageSummaries(): void {
118
+ _epoch += 1;
119
+ _cache = null;
120
+ _loading = null;
121
+ }
122
+
123
+ /** Be told when a read lands. Returns the function that stops it. */
124
+ export function onPageSummariesRead(listener: () => void): () => void {
125
+ _listeners.add(listener);
126
+ return () => {
127
+ _listeners.delete(listener);
128
+ };
129
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * One record of a file-backed collection, edited as an entry: a form over one CSV row.
3
+ *
4
+ * A collection whose `source` names one file (`./content/projects.csv`) has one entry per record
5
+ * (`content/file-records.ts`). The file's own grid edits all of them at once; this is the form-only
6
+ * entry editor (`content/entry-editor.ts`, `specs/studio.md` §22.5) over ONE of them, so opening a
7
+ * project from the Content workspace opens that project's fields, typed by the collection's schema,
8
+ * rather than a table the reader has to find the row in.
9
+ *
10
+ * **It is an editor of the file's grid tab, not a second document.** The record's fields are read
11
+ * from, and written into, the grid controller's edit buffer (`grid/edit-buffer.ts`), the same
12
+ * buffer the grid's cells write. So ⌘S saves the file through the grid's one commit path (with its
13
+ * staleness check and its untouched-cell rule), ⌘Z undoes a field edit exactly as it undoes a cell,
14
+ * the tab's dirty dot is the buffer's, and switching the tab's editor to Grid shows the edit as a
15
+ * pending cell.
16
+ *
17
+ * Which record the form shows is per tab, here, keyed by the tab's id. An opener names the record
18
+ * by its place among the file's data rows, because that is all a scan knows and the rows may not
19
+ * have loaded yet; the place resolves to the row's key the moment they have, reactively, so the
20
+ * form draws the record as soon as it exists. Previous / Next then name rows by key, which a save
21
+ * that re-reads the file does not move.
22
+ *
23
+ * @docs studio/projects/content-types
24
+ */
25
+
26
+ import { onScopeDispose, reactive } from "../reactivity";
27
+ import { activateTab, workspace } from "../workspace/workspace";
28
+ import { getGridController } from "../grid/grid-controller";
29
+ import { isBoolean, isNonNullObject, isNumber, isString } from "@jxsuite/schema/guards";
30
+ import { entryDay } from "./entry-summaries";
31
+ import type { GridCellValue } from "../grid/grid-source";
32
+ import type { EntryFields } from "./entry-fields";
33
+ import type { JsonValue } from "../types";
34
+ import type { Tab } from "../tabs/tab";
35
+
36
+ /** A value that already starts with a calendar day, which a date input reads as it is. */
37
+ const ISO_DAY = /^\d{4}-\d{2}-\d{2}/;
38
+
39
+ /**
40
+ * The `canvasMode` the entry form draws under. Defined here, with the record it may be over, and
41
+ * re-exported by `entry-editor.ts`, which imports this module.
42
+ */
43
+ export const ENTRY_MODE = "entry";
44
+
45
+ /** A tab's record: by its row key, or (until the rows load) by its place among the data rows. */
46
+ type ShownRecord = { key: string } | { row: number };
47
+
48
+ /** Tab id → the record its entry form shows. */
49
+ const shown = reactive(new Map<string, ShownRecord>());
50
+
51
+ /** The key of the record a tab's entry form shows, or null when it shows none (yet). Tracks. */
52
+ export function recordOf(tab: Tab): string | null {
53
+ const record = shown.get(tab.id);
54
+ if (record === undefined) {
55
+ return null;
56
+ }
57
+ if ("key" in record) {
58
+ return record.key;
59
+ }
60
+ return getGridController(tab)?.state.rows[record.row]?.key ?? null;
61
+ }
62
+
63
+ /** Whether the tab's entry form is over a record of its file, chosen or still loading. Tracks. */
64
+ export function hasRecord(tab: Tab): boolean {
65
+ return shown.has(tab.id);
66
+ }
67
+
68
+ /** Show the record keyed `rowKey` in this tab's entry form. */
69
+ export function showRecord(tab: Tab, rowKey: string): void {
70
+ shown.set(tab.id, { key: rowKey });
71
+ }
72
+
73
+ /** Forget the tab's record (its tab closed, or the record is gone). */
74
+ export function forgetRecord(tab: Tab): void {
75
+ shown.delete(tab.id);
76
+ }
77
+
78
+ /** Whether this tab's entry form is over a record of its grid rather than over its own document. */
79
+ export function isRecordTab(tab: Tab): boolean {
80
+ return hasRecord(tab) && getGridController(tab) !== null;
81
+ }
82
+
83
+ /**
84
+ * The record's fields as the form reads them: every column's current value, pending edits included,
85
+ * typed as the grid types it (a number column's cell is a number, a list column's a list of
86
+ * strings). A cell with nothing in it is absent, as an empty frontmatter key would be.
87
+ */
88
+ export function recordFields(tab: Tab): EntryFields {
89
+ const controller = getGridController(tab);
90
+ const rowKey = recordOf(tab);
91
+ if (!controller || rowKey === null) {
92
+ return {};
93
+ }
94
+ const fields: EntryFields = {};
95
+ for (const column of controller.state.columns) {
96
+ const value = controller.buffer.effectiveValue(rowKey, column.field);
97
+ if (value === null || value === "") {
98
+ continue;
99
+ }
100
+ if (column.kind === "date" && isString(value) && !ISO_DAY.test(value)) {
101
+ /* A CSV spells a date the way its spreadsheet did (`6/3/2026`), which a date input cannot
102
+ show. The form is shown the day it means; the cell keeps its spelling until it is edited. */
103
+ fields[column.field] = entryDay(value) || value;
104
+ continue;
105
+ }
106
+ fields[column.field] = isNonNullObject(value) && !Array.isArray(value) ? value.$ref : value;
107
+ }
108
+ return fields;
109
+ }
110
+
111
+ /** A form value as the cell it is written into. */
112
+ function cellValue(value: JsonValue | undefined): GridCellValue {
113
+ if (value === undefined || value === null) {
114
+ return null;
115
+ }
116
+ if (Array.isArray(value)) {
117
+ return value.map((item) => (isString(item) ? item : JSON.stringify(item)));
118
+ }
119
+ if (isString(value) || isNumber(value) || isBoolean(value)) {
120
+ return value;
121
+ }
122
+ // A cell holds text: an object a field produced is written as its JSON.
123
+ return JSON.stringify(value);
124
+ }
125
+
126
+ /**
127
+ * Write a form patch into the record's row as one undo step. A field with no column is ignored: the
128
+ * file has nowhere to keep it, and adding a column is the grid's decision, not a form's.
129
+ */
130
+ export function commitRecordFields(tab: Tab, patch: Record<string, JsonValue | undefined>): void {
131
+ const controller = getGridController(tab);
132
+ const rowKey = recordOf(tab);
133
+ if (!controller || rowKey === null) {
134
+ return;
135
+ }
136
+ const columns = new Set(controller.state.columns.map((column) => column.field));
137
+ controller.buffer.group(() => {
138
+ for (const [field, value] of Object.entries(patch)) {
139
+ if (columns.has(field)) {
140
+ controller.buffer.setCell(rowKey, field, cellValue(value));
141
+ }
142
+ }
143
+ });
144
+ }
145
+
146
+ /** Where the shown record sits among the file's rows, for the form's Previous / Next. */
147
+ export interface RecordPlace {
148
+ /** Its place among the rows, from 0; -1 when the record is not (or no longer) among them. */
149
+ index: number;
150
+ total: number;
151
+ previous: string | null;
152
+ next: string | null;
153
+ }
154
+
155
+ /** The shown record's place among the grid's rows, in file order. Tracks the rows. */
156
+ export function recordPlace(tab: Tab): RecordPlace {
157
+ const controller = getGridController(tab);
158
+ const rowKey = recordOf(tab);
159
+ const rows = controller?.state.rows ?? [];
160
+ const index = rowKey === null ? -1 : rows.findIndex((row) => row.key === rowKey);
161
+ return {
162
+ index,
163
+ next: index === -1 ? null : (rows[index + 1]?.key ?? null),
164
+ previous: index <= 0 ? null : (rows[index - 1]?.key ?? null),
165
+ total: rows.length,
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Open one record of a file-backed collection in its entry form.
171
+ *
172
+ * Opens (or reveals) the file's grid tab, names the record at `row` (its place among the file's
173
+ * data rows, as `content/file-records.ts` numbers it) and puts the tab in the entry editor. The
174
+ * form draws the record once the grid's rows have loaded ({@link recordOf}).
175
+ *
176
+ * @returns The tab, or null when the file could not be opened at all.
177
+ */
178
+ export async function openRecordEntry(path: string, row: number): Promise<Tab | null> {
179
+ /* Dynamic for the reason `entry-open.ts` gives: `files/files.ts` reaches the platform, the format
180
+ registry and the packages layer. */
181
+ const { openFileInTab } = await import("../files/files");
182
+ await openFileInTab(path);
183
+ const tab = workspace.tabs.get(path) ?? null;
184
+ if (!tab || !getGridController(tab)) {
185
+ return tab;
186
+ }
187
+ if (!shown.has(tab.id)) {
188
+ // The choice lives as long as the tab: a file reopened later starts on no record.
189
+ tab.scope.run(() => {
190
+ onScopeDispose(() => forgetRecord(tab));
191
+ });
192
+ }
193
+ shown.set(tab.id, { row });
194
+ if (!tab.capabilities.modes.includes(ENTRY_MODE)) {
195
+ tab.capabilities.modes.unshift(ENTRY_MODE);
196
+ }
197
+ tab.session.ui.canvasMode = ENTRY_MODE;
198
+ tab.session.ui.preview = false;
199
+ activateTab(tab.id);
200
+ return tab;
201
+ }
@@ -0,0 +1,177 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * The writing-mode banner: what the canvas says while a collection entry is written inside its
4
+ * template's layout (`specs/studio.md` §22.5).
5
+ *
6
+ * "Writing mode: text and media are editable, the layout is locked", with **Edit the <item>
7
+ * template in Design**. The template it opens is the collection's template PAGE when the project
8
+ * has one (the page whose `$paths.contentType` names the collection,
9
+ * `content/collection-pages.ts`), because that is the page that decides what an entry looks like on
10
+ * the site; with no such page it is the layout the entry is drawn in. Opening either activates it,
11
+ * and activating a page or a layout enters Design (§22.1).
12
+ *
13
+ * One banner per pane, mounted into the host the canvas stage draws above Edit's column
14
+ * (`surfaces/canvas-stage.json`'s `writing-banner`); `canvas/canvas-render.ts` hands the host over
15
+ * and repaints it. It is the one band Edit draws over a document: an ordinary page opens at the top
16
+ * with nothing above it (studio.md §4.2).
17
+ *
18
+ * @docs studio/projects/content-types
19
+ */
20
+
21
+ import { isString } from "@jxsuite/schema/guards";
22
+ import { paneRegion } from "../ui/regions";
23
+ import { workspace } from "../workspace/workspace";
24
+ import { collectionForFile } from "./collection-match";
25
+ import { contentCollection, templateDisplayName } from "./content-collections";
26
+ import {
27
+ entryLayoutPath,
28
+ entryLocale,
29
+ onCollectionPagesRead,
30
+ peekCollectionPage,
31
+ } from "./collection-pages";
32
+ import { mountWritingBannerSurface } from "../surfaces/writing-banner";
33
+ import type { Tab } from "../tabs/tab";
34
+ import type { WritingBannerHandle, WritingBannerView } from "../surfaces/writing-banner";
35
+
36
+ /** The sentence the banner always says. */
37
+ export const WRITING_MODE_TEXT = "Writing mode: text and media are editable, the layout is locked";
38
+
39
+ /** Where the banner's button goes, and what it is called. `null` when there is nothing to open. */
40
+ export interface BannerTarget {
41
+ path: string;
42
+ /** The button's text, as `WritingBannerView.actionLabel` draws it. */
43
+ actionLabel: string;
44
+ }
45
+
46
+ /**
47
+ * What an entry's banner opens: its collection's page, else the layout it is drawn in.
48
+ *
49
+ * Each label names the file it opens. The collection's page is called what the Content home's
50
+ * header line calls it ("Design the post page"), so the two surfaces that reach it agree; a layout
51
+ * is a template everywhere in Studio, and is called one by its own name.
52
+ */
53
+ export function bannerTarget(tab: Tab): BannerTarget | null {
54
+ const match = collectionForFile(tab.documentPath);
55
+ if (!match) {
56
+ return null;
57
+ }
58
+ const item = contentCollection(match.name)?.item ?? "entry";
59
+ const page = peekCollectionPage(match.name, entryLocale(tab.documentPath));
60
+ if (page) {
61
+ return { actionLabel: `Design the ${item} page`, path: page.path };
62
+ }
63
+ const own = tab.doc.document.$layout ?? tab.doc.content.frontmatter.$layout;
64
+ const layout = entryLayoutPath(isString(own) || own === false ? own : undefined, page ?? null);
65
+ if (!layout) {
66
+ return null;
67
+ }
68
+ return {
69
+ actionLabel: `Edit the ${templateDisplayName(layout)} template in Design`,
70
+ path: layout.replace(/^\.\//, ""),
71
+ };
72
+ }
73
+
74
+ /** What the banner over `tab` in `paneId` draws. */
75
+ export function writingBannerView(paneId: string, tab: Tab): WritingBannerView {
76
+ const target = bannerTarget(tab);
77
+ return {
78
+ actionLabel: target?.actionLabel ?? "",
79
+ actionState: target ? "shown" : "hidden",
80
+ region: paneRegion(paneId, "writing-banner"),
81
+ text: WRITING_MODE_TEXT,
82
+ };
83
+ }
84
+
85
+ // ─── Mounting ────────────────────────────────────────────────────────────────
86
+
87
+ interface StandingBanner {
88
+ host: HTMLElement;
89
+ handle: WritingBannerHandle;
90
+ /** The tab the banner was last drawn for: what its button opens the template of. */
91
+ tabId: string | null;
92
+ }
93
+
94
+ const _banners = new Map<string, StandingBanner>();
95
+ let _unlisten: (() => void) | null = null;
96
+
97
+ /** Open the template of the entry a pane's banner is over. */
98
+ async function openTemplate(paneId: string): Promise<void> {
99
+ const tabId = _banners.get(paneId)?.tabId;
100
+ const tab = tabId ? workspace.tabs.get(tabId) : undefined;
101
+ const target = tab ? bannerTarget(tab) : null;
102
+ if (!target) {
103
+ return;
104
+ }
105
+ const { openFileInTab } = await import("../files/files");
106
+ await openFileInTab(target.path);
107
+ }
108
+
109
+ /** Repaint every banner: the collection pages it names its button from have just been read. */
110
+ function repaintAll(): void {
111
+ for (const [paneId, banner] of _banners) {
112
+ const tab = banner.tabId ? workspace.tabs.get(banner.tabId) : undefined;
113
+ if (tab) {
114
+ banner.handle.update(writingBannerView(paneId, tab));
115
+ }
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Take a pane's banner host, or let it go (`null`).
121
+ *
122
+ * The stage announces the host when it draws the `writing` or `banner` lead; the same host comes
123
+ * back on every repaint that keeps that lead, so a second announcement of it changes nothing.
124
+ */
125
+ export function attachWritingBanner(paneId: string, host: HTMLElement | null): void {
126
+ const held = _banners.get(paneId);
127
+ if (held?.host === host) {
128
+ return;
129
+ }
130
+ if (held) {
131
+ held.handle.dispose();
132
+ _banners.delete(paneId);
133
+ }
134
+ if (!host) {
135
+ return;
136
+ }
137
+ _unlisten ??= onCollectionPagesRead(repaintAll);
138
+ const handle = mountWritingBannerSurface(
139
+ host,
140
+ {
141
+ actionLabel: "",
142
+ actionState: "hidden",
143
+ region: paneRegion(paneId, "writing-banner"),
144
+ text: WRITING_MODE_TEXT,
145
+ },
146
+ {
147
+ openTemplate: () => {
148
+ void openTemplate(paneId);
149
+ },
150
+ },
151
+ );
152
+ _banners.set(paneId, { handle, host, tabId: null });
153
+ }
154
+
155
+ /** Draw a pane's banner for the entry it is over. A pane with no banner host is left alone. */
156
+ export function renderWritingBanner(paneId: string, tab: Tab): void {
157
+ const banner = _banners.get(paneId);
158
+ if (!banner) {
159
+ return;
160
+ }
161
+ banner.tabId = tab.id;
162
+ banner.handle.update(writingBannerView(paneId, tab));
163
+ }
164
+
165
+ /** Take a pane's banner down (its lead went away, or the pane did). Idempotent. */
166
+ export function detachWritingBanner(paneId: string): void {
167
+ attachWritingBanner(paneId, null);
168
+ }
169
+
170
+ /** Take every banner down — tests. */
171
+ export function resetWritingBanners(): void {
172
+ for (const paneId of _banners.keys()) {
173
+ detachWritingBanner(paneId);
174
+ }
175
+ _unlisten?.();
176
+ _unlisten = null;
177
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * `file.new`: Add new, as a command record (`specs/studio.md` §9.1.1).
3
+ *
4
+ * One record, four doors: the palette, the Command Bar's overflow, the chords, and the assistant.
5
+ * The Site panel's **+** and the Content home's **New** run it too, so there is one way in and
6
+ * every surface is a rendering of it.
7
+ *
8
+ * **Two behaviours, chosen by the arguments.** With a `name`, it creates — kind, name and options
9
+ * through `deriveCreation`, refused by throwing the sentence the dialog would print, written by the
10
+ * flow's one write path — and opens nothing modal. Without one, it OPENS the dialog on the kind
11
+ * asked for and returns at once, never awaiting it: a `run` that waits on a person is the second
12
+ * deletion rule of `studio-ui-guidelines.md` §12.4, and returning is what keeps this record a tool.
13
+ * The model is told as much in the description, and the report says which of the two happened.
14
+ *
15
+ * **The chords.** `⌘N` where the host owns it — the desktop app — and `⌘⌥N` everywhere, because a
16
+ * browser keeps `⌘N` for a new window and never delivers it to the page.
17
+ *
18
+ * DOM-free at import: the flow is loaded on the first run, so `commands/app-commands.ts` can list
19
+ * this record in a bare Bun process.
20
+ *
21
+ * @docs studio/projects/pages-layouts-components
22
+ */
23
+
24
+ import {
25
+ argsSchema,
26
+ derivedEnumProperty,
27
+ enumArg,
28
+ enumProperty,
29
+ optionalStringArg,
30
+ stringProperty,
31
+ } from "../commands/command-args";
32
+ import { entryCollections } from "../content/entry-model";
33
+ import { CREATION_KINDS } from "./add-new";
34
+ import type { CreationKind } from "./add-new";
35
+ import type { AddNewRequest, AddNewResult } from "./add-new-flow";
36
+ import type { CommandArgValues } from "../commands/command-args";
37
+ import type { AiToolReport, AnyCommand, CommandRegistry } from "../commands/registry";
38
+
39
+ /** The id, which the Site panel and the Content home run. */
40
+ export const FILE_NEW = "file.new";
41
+
42
+ /** What one run did: a creation, or the dialog it opened instead. */
43
+ interface LastRun {
44
+ created: AddNewResult | null;
45
+ opened: CreationKind | null;
46
+ }
47
+
48
+ /** The last run, for `report`, which reads module state rather than an injected dep. */
49
+ let _lastRun: LastRun = { created: null, opened: null };
50
+
51
+ /** The arguments as a request, each one read with the reader its schema names. */
52
+ function requestOf(args: CommandArgValues): AddNewRequest {
53
+ const request: AddNewRequest = {};
54
+ if (args.kind !== undefined) {
55
+ request.kind = enumArg(FILE_NEW, args, "kind", CREATION_KINDS);
56
+ }
57
+ const name = optionalStringArg(FILE_NEW, args, "name");
58
+ if (name !== undefined) {
59
+ request.name = name;
60
+ }
61
+ const collection = optionalStringArg(FILE_NEW, args, "collection");
62
+ if (collection !== undefined) {
63
+ request.collection = collection;
64
+ }
65
+ const locale = optionalStringArg(FILE_NEW, args, "locale");
66
+ if (locale !== undefined) {
67
+ request.locale = locale;
68
+ }
69
+ const template = optionalStringArg(FILE_NEW, args, "template");
70
+ if (template !== undefined) {
71
+ request.template = template;
72
+ }
73
+ return request;
74
+ }
75
+
76
+ /** The sentence the model reads after a run. */
77
+ function reportFor(): AiToolReport {
78
+ const { created, opened } = _lastRun;
79
+ if (created) {
80
+ return { summary: describe(created), wrote: created.wrote };
81
+ }
82
+ return {
83
+ summary:
84
+ `Opened Add new on ${opened ?? "page"} for the person; nothing is created until they ` +
85
+ "confirm. Pass a name to create without the dialog.",
86
+ wrote: [],
87
+ };
88
+ }
89
+
90
+ /** One sentence naming what a creation made. */
91
+ export function describe(result: AddNewResult): string {
92
+ switch (result.kind) {
93
+ case "page": {
94
+ return `Created the page ${result.path}${result.route ? `, which opens at ${result.route}` : ""}, and opened it in Design.`;
95
+ }
96
+ case "component": {
97
+ return `Created <${result.tag ?? ""}> in ${result.path}; it is listed under Components in the Site panel and open in Design.`;
98
+ }
99
+ case "template": {
100
+ return `Created the template ${result.path} and opened it in Design.`;
101
+ }
102
+ case "entry": {
103
+ return `Created the entry ${result.path} and opened it.`;
104
+ }
105
+ case "collection": {
106
+ return `Created the ${result.path.replace(/^content\//, "")} collection, writing ${result.wrote.join(", ")}.`;
107
+ }
108
+ default: {
109
+ return `Uploaded ${result.wrote.length === 1 ? "1 file" : `${result.wrote.length} files`} to public/media/.`;
110
+ }
111
+ }
112
+ }
113
+
114
+ /** The `file.new` record. */
115
+ export function addNewCommands(): AnyCommand[] {
116
+ return [
117
+ {
118
+ aiTool: {
119
+ description:
120
+ "Create a page, collection, template, component or collection entry, the way Add new does: " +
121
+ "pass kind and name and it is written and opened, with the path derived from the name " +
122
+ "(pages/<slug>.json, layouts/<slug>.json, components/<tag>.json, a content/<name>/ " +
123
+ "collection). Without a name it only opens the Add new dialog for the person.",
124
+ name: "add_new",
125
+ report: () => reportFor(),
126
+ },
127
+ args: argsSchema(
128
+ {
129
+ collection: derivedEnumProperty(
130
+ () => entryCollections().map((collection) => collection.name),
131
+ "For an entry: the content collection it is created in.",
132
+ ),
133
+ kind: enumProperty(
134
+ CREATION_KINDS,
135
+ "What to create: page, collection, template, component, media (an upload) or entry.",
136
+ ),
137
+ locale: stringProperty(
138
+ "For a page or an entry of a localized collection: the language it is written in, one " +
139
+ "of the project's i18n locales such as fr-CA. A page in another language is saved in " +
140
+ "that language's folder and opens under its URL prefix.",
141
+ ),
142
+ name: stringProperty(
143
+ "The name. A page's URL, a template's file and a component's tag are derived from it. " +
144
+ "Omit it to open the Add new dialog instead of creating.",
145
+ ),
146
+ template: stringProperty(
147
+ "The template to start from: a layout such as base or layouts/base.json, article for a " +
148
+ "Markdown page, or blank for none. A collection uses it for its own page; a template " +
149
+ "is wrapped in it.",
150
+ ),
151
+ },
152
+ [],
153
+ ),
154
+ category: "File",
155
+ group: "1_file",
156
+ icon: "plus",
157
+ id: FILE_NEW,
158
+ keybinding: ["mod+n", "mod+alt+n"],
159
+ level: "project",
160
+ menus: ["commandbar/overflow", "palette"],
161
+ requires: "an open project",
162
+ run: async (_ctx, args) => {
163
+ const request = requestOf(args);
164
+ const flow = await import("./add-new-flow");
165
+ if (request.name !== undefined) {
166
+ _lastRun = { created: null, opened: null };
167
+ const created = await flow.createDirect({ ...request, name: request.name });
168
+ _lastRun = { created, opened: null };
169
+ return;
170
+ }
171
+ _lastRun = { created: null, opened: request.kind ?? "page" };
172
+ // Opened, never awaited: the dialog belongs to the person (studio-ui-guidelines.md §12.4).
173
+ void flow.openAddNew(request);
174
+ },
175
+ title: "Add New…",
176
+ undo: "none",
177
+ when: (ctx) => ctx.project.open,
178
+ },
179
+ ];
180
+ }
181
+
182
+ /** Register `file.new`. */
183
+ export function registerAddNewCommands(registry: CommandRegistry): void {
184
+ registry.registerAll(addNewCommands());
185
+ }