@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
@@ -22,19 +22,28 @@
22
22
  import {
23
23
  buildScope,
24
24
  renderNode,
25
+ runScoped,
25
26
  setCanvasAssetResolver,
27
+ setNpmModuleResolver,
28
+ setResolveToken,
26
29
  setSkipServerFunctions,
30
+ setTemplateErrorHandler,
27
31
  } from "@jxsuite/runtime";
28
- import { isFunction } from "@jxsuite/schema/guards";
32
+ import { isFunction, isNonNullObject } from "@jxsuite/schema/guards";
29
33
  import { assetContextFor, hostAssetDeclarations, resolveAssetRef } from "../canvas/asset-refs";
34
+ import { canvasBaseOrigin, documentBase, hostResolveToken } from "../canvas/canvas-origin";
35
+ import { npmModuleUrl } from "../canvas/npm-modules";
36
+ import { projectState } from "../store";
30
37
  import { assetVersionsSnapshot } from "../files/asset-versions";
31
38
  import { componentRegistry } from "../files/components";
32
39
  import { getPlatform } from "../platform";
33
40
  import { loadFormats, formatForPath } from "../format/format-host";
34
41
  import { parseSourceForPath } from "../files/file-ops";
35
- import { renderComponentPreview } from "../panels/component-preview";
42
+ import { defineComponent, renderComponentPreview } from "../panels/component-preview";
43
+ import { componentExampleProps, componentSampleRow } from "./component-examples";
44
+ import { loadProjectPreviewSheets } from "./preview-style";
36
45
  import type { ComponentEntry } from "../files/components";
37
- import type { JxDocument } from "@jxsuite/schema/types";
46
+ import type { JsonValue, JxDocument } from "@jxsuite/schema/types";
38
47
 
39
48
  /**
40
49
  * How many rendered previews stay alive at once.
@@ -113,6 +122,16 @@ export function createPreviewCache(limit: number = PREVIEW_CACHE_LIMIT): Preview
113
122
 
114
123
  // ─── Rendering one preview ───────────────────────────────────────────────────
115
124
 
125
+ /**
126
+ * The URL a previewed document's relative refs resolve against: the file's own URL under the
127
+ * project, as the canvas spells it. A shell with an opaque origin (a custom-scheme shell such as
128
+ * `views://`) has no such URL to build, and keeps the shell's own address.
129
+ */
130
+ function previewBase(filePath: string): string {
131
+ const base = documentBase(projectState?.projectRoot);
132
+ return URL.canParse(base) ? new URL(filePath, base).href : location.href;
133
+ }
134
+
116
135
  /**
117
136
  * Render a page/layout/content document to a detached element, or null when it cannot be rendered.
118
137
  *
@@ -138,26 +157,128 @@ export async function renderDocPreview(filePath: string): Promise<HTMLElement |
138
157
  JSON or a shape renderNode cannot draw throws into the catch below, which is "no preview". */
139
158
  document_ = JSON.parse(content) as JxDocument;
140
159
  }
141
- const scope = await buildScope(document_, {}, location.href);
142
- /* This render lands in the PARENT document, so an `<img src="/hero.jpg">` inside it resolves
143
- against `index.html` — which is the shell, not the site. Same hook the canvas installs, same
144
- context, restored afterwards because the next preview is a different document and a stale
145
- `documentDir` would resolve its relative refs against the wrong entry. The version map is
146
- read as the render starts, after the reads above: the Library is the many-images screen,
147
- and a versioned src is what lets its cards repaint from the browser cache. */
148
- const assets = context && { ...context, versions: assetVersionsSnapshot() };
149
- setCanvasAssetResolver((value) => resolveAssetRef(value, assets));
150
- try {
151
- const element = renderNode(document_, scope);
152
- return element instanceof HTMLElement ? element : null;
153
- } finally {
154
- setCanvasAssetResolver(null);
155
- }
160
+ return await renderDocument(document_, filePath, context);
156
161
  } catch {
157
162
  return null;
158
163
  }
159
164
  }
160
165
 
166
+ /**
167
+ * Render a document as the file `filePath` holds it would be drawn: its refs resolved from that
168
+ * file's URL, its media through the canvas's resolver. Throws what the render throws.
169
+ */
170
+ async function renderDocument(
171
+ document_: JxDocument,
172
+ filePath: string,
173
+ context = assetContextFor(filePath, hostAssetDeclarations()),
174
+ ): Promise<HTMLElement | null> {
175
+ setSkipServerFunctions(true);
176
+ /* The document's own URL, as the canvas spells it, and the canvas's dev-proxy token. With the
177
+ shell's `location.href` as base, a page's `"$src": "../../scripts/x.class.json"` resolved
178
+ against `/__studio__/`, and without the token the proxy refused it before reading it. */
179
+ setResolveToken(hostResolveToken());
180
+ const scope = await buildScope(document_, {}, previewBase(filePath));
181
+ /* This render lands in the PARENT document, so an `<img src="/hero.jpg">` inside it resolves
182
+ against `index.html` — which is the shell, not the site. Same hook the canvas installs, same
183
+ context, restored afterwards because the next preview is a different document and a stale
184
+ `documentDir` would resolve its relative refs against the wrong entry. The version map is
185
+ read as the render starts, after the reads above: the Library is the many-images screen,
186
+ and a versioned src is what lets its cards repaint from the browser cache. */
187
+ const assets = context && { ...context, versions: assetVersionsSnapshot() };
188
+ setCanvasAssetResolver((value) => resolveAssetRef(value, assets));
189
+ try {
190
+ const element = renderNode(document_, scope);
191
+ return element instanceof HTMLElement ? element : null;
192
+ } finally {
193
+ setCanvasAssetResolver(null);
194
+ }
195
+ }
196
+
197
+ /**
198
+ * A project component's preview: an instance given its sample props when it renders with them, else
199
+ * the first row of a list that draws it (`component-examples.ts`), else none.
200
+ */
201
+ async function projectComponentPreview(
202
+ component: ComponentEntry,
203
+ path: string,
204
+ ): Promise<HTMLElement | null> {
205
+ const definition = await readDefinition(path);
206
+ if (!definition) {
207
+ return null;
208
+ }
209
+ const props = await componentExampleProps(definition, path);
210
+ if (props.size > 0 && (await rendersWith(definition, path, props))) {
211
+ return renderComponentPreview(component, props);
212
+ }
213
+ const sample = await componentSampleRow(definition, path);
214
+ if (sample && (await defineComponent(component))) {
215
+ return renderDocument(sample.doc, sample.file);
216
+ }
217
+ return (await rendersWith(definition, path, props))
218
+ ? renderComponentPreview(component, props)
219
+ : null;
220
+ }
221
+
222
+ /** A project component's definition, or null when its file is not one. */
223
+ export async function readDefinition(path: string): Promise<JxDocument | null> {
224
+ try {
225
+ const parsed: unknown = JSON.parse(await getPlatform().readFile(path));
226
+ /* SAFETY: a project component's `.json` is its definition, a Jx element document; a shape
227
+ renderNode cannot draw throws into `rendersWith`'s catch, which is "does not render". */
228
+ return isNonNullObject(parsed) && !Array.isArray(parsed) ? (parsed as JxDocument) : null;
229
+ } catch {
230
+ return null;
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Whether `definition` renders given `props` (its sample props, `component-examples.ts`), tried on
236
+ * a detached copy before a live instance is made.
237
+ *
238
+ * A definition may not render with the values it is given: the museum starter's
239
+ * `<mu-exhibition-card>` formats a `date` that defaults to `""`, and `Intl/formatDate` of an empty
240
+ * date is a `RangeError`, exactly as `new Intl.DateTimeFormat().format(new Date(""))` is (`spec.md`
241
+ * §19.4c). An instance renders in its own async `connectedCallback`, where the throw is reported on
242
+ * the console once per card per paint. The same render, made here synchronously into an element
243
+ * nobody sees, can be caught, and a definition that cannot render gets no instance preview.
244
+ */
245
+ export async function rendersWith(
246
+ definition: JxDocument,
247
+ path: string,
248
+ props: ReadonlyMap<string, JsonValue>,
249
+ ): Promise<boolean> {
250
+ let stopRender: (() => void) | null = null;
251
+ try {
252
+ setSkipServerFunctions(true);
253
+ setResolveToken(hostResolveToken());
254
+ const scope = await buildScope(definition, {}, previewBase(path));
255
+ for (const [name, value] of props) {
256
+ scope[name] = value;
257
+ }
258
+ /* A template binding that throws no longer unwinds the render (spec.md §6.1): it draws nothing
259
+ and waits for its data. The trial still has to see it, so the throw is collected for the one
260
+ synchronous render, as the render critic collects it. */
261
+ let bindingThrew = false;
262
+ setTemplateErrorHandler(() => {
263
+ bindingThrew = true;
264
+ });
265
+ // Its own tag would make an instance, and the instance's render is the one that cannot be caught.
266
+ const { stop } = runScoped(() => renderNode({ ...definition, tagName: "div" }, scope));
267
+ stopRender = stop;
268
+ return !bindingThrew;
269
+ } catch {
270
+ return false;
271
+ } finally {
272
+ setTemplateErrorHandler(null);
273
+ stopRender?.();
274
+ }
275
+ }
276
+
277
+ /** A component whose definition is a JSON file in the project, which is what can be tried first. */
278
+ function isProjectDefinition(component: ComponentEntry): boolean {
279
+ return component.source !== "npm" && (component.path?.endsWith(".json") ?? false);
280
+ }
281
+
161
282
  /**
162
283
  * The preview for one file — a registered component's own preview when there is one, the document
163
284
  * render otherwise. Cached; a cache hit does no work at all.
@@ -172,8 +293,17 @@ export async function previewFor(
172
293
  }
173
294
  let rendered: HTMLElement | null | undefined;
174
295
  try {
296
+ /* A component's `npm:` sidecar, from where the project's packages are served: a preview is a
297
+ live render in this window, and the runtime would otherwise hand `import()` the `npm:` URL. */
298
+ setNpmModuleResolver((specifier) => npmModuleUrl(specifier, `${canvasBaseOrigin()}/`));
299
+ // The project's stylesheet, ready for the box this preview is shown in (`preview-style.ts`).
300
+ await loadProjectPreviewSheets();
175
301
  const component = componentRegistry.find((c: ComponentEntry) => c.path === path);
176
- rendered = component ? await renderComponentPreview(component) : await renderDocPreview(path);
302
+ if (component && isProjectDefinition(component)) {
303
+ rendered = await projectComponentPreview(component, path);
304
+ } else {
305
+ rendered = component ? await renderComponentPreview(component) : await renderDocPreview(path);
306
+ }
177
307
  } catch {
178
308
  return undefined;
179
309
  }
@@ -1,11 +1,11 @@
1
1
  /**
2
- * The Library as a {@link GridSource}.
2
+ * The Content home's scan, as a {@link GridSource}.
3
3
  *
4
- * The Library is one more source over the same contract the CSV, collection, pages and connector
5
- * grids already use, which is what makes Table a real grid rather than a fifth hand-written table:
6
- * columns come from {@link libraryColumns}, rows are the scan, and every layout — Cards, Media,
7
- * Calendar, Board — draws the SAME rows through a different arrangement. A layout switch never
8
- * re-reads the project.
4
+ * The scan is one more source over the same contract the CSV, collection, pages and connector grids
5
+ * already use: columns come from {@link libraryColumns}, rows are the scan, and every view (Table,
6
+ * Cards, Calendar, Board) draws the SAME rows through a different arrangement. A view switch never
7
+ * re-reads the project, and neither does showing another collection: the scan covers the project
8
+ * once, and each collection is a filter over it.
9
9
  *
10
10
  * It is a **read-only** source, and says so rather than pretending. Renaming, duplicating and
11
11
  * deleting a file are real operations with real consequences (a rename rewrites references; a
@@ -16,8 +16,17 @@
16
16
 
17
17
  import { getPlatform } from "../platform";
18
18
  import { projectState } from "../store";
19
- import { makeGridTabId } from "../grid/grid-source";
20
- import { scanLibrary } from "./library-model";
19
+ import { workspaceHomeTabId } from "../workspace/workspace-ids";
20
+ import { PAGES_COLLECTION, capitalise, humanName } from "../content/content-collections";
21
+ import { fileToRoute } from "@jxsuite/site/routes";
22
+ import { libraryScanDirs, scanLibrary } from "./library-model";
23
+ import { routedPageExtensions } from "../format/format-host";
24
+ import { DRAFT_FIELD } from "../content/draft-state";
25
+ import { isString } from "@jxsuite/schema/guards";
26
+ import { collectionForFile } from "../content/collection-match";
27
+ import { fileRecords, recordKey } from "../content/file-records";
28
+ import type { ContentTypeSchema } from "@jxsuite/schema/types";
29
+ import type { FileRecord } from "../content/file-records";
21
30
  import type { LibraryFile, LibraryScan, ScanFailure } from "./library-model";
22
31
  import type {
23
32
  CommitResult,
@@ -30,44 +39,283 @@ import type {
30
39
 
31
40
  /** Why a cell edit is refused — printed by the grid, so the author is told where to go instead. */
32
41
  export const LIBRARY_READ_ONLY =
33
- "The Library lists files; it does not edit them. Rename, duplicate or delete from the file's " +
34
- "context menu, or open the file to edit it.";
35
-
36
- /** The Table layout's columns. `path` is the row key and is never editable. */
37
- export function libraryColumns(): GridColumn[] {
38
- return [
39
- { editable: false, field: "name", kind: "string", pk: true, title: "Name", widthHint: 260 },
40
- { editable: false, field: "category", kind: "string", title: "Category", widthHint: 120 },
41
- { editable: false, field: "locale", kind: "string", title: "Locale", widthHint: 90 },
42
- { editable: false, field: "type", kind: "string", title: "Type", widthHint: 120 },
43
- { editable: false, field: "size", kind: "number", title: "Size", widthHint: 90 },
42
+ "The Content workspace lists files; it does not edit them in the list. Rename, duplicate or " +
43
+ "delete from the file's context menu, or open the file to edit it.";
44
+
45
+ /** Which of an entry's own facts a collection's entries carry, so the Table draws a column for each. */
46
+ export interface EntryColumnFacts {
47
+ /** Some entry says Draft or Published: the collection has a draft workflow. */
48
+ status: boolean;
49
+ /** Some entry is dated by a field of its own. */
50
+ date: boolean;
51
+ /** The files are in more than one language (the Pages table draws a Language column). */
52
+ locale?: boolean;
53
+ /** The collection's own fields, each drawn as a column after Title, Status and Date. */
54
+ fields?: readonly EntryFieldColumn[];
55
+ }
56
+
57
+ /** One schema field the Table draws as a column of its own. */
58
+ export interface EntryFieldColumn {
59
+ /** The field's name in the entry. */
60
+ name: string;
61
+ /** The column heading. */
62
+ title: string;
63
+ /**
64
+ * The field holds a list (`tags: [Patios, Stone]`), which its list text joins with commas. A
65
+ * Board grouped by it puts an entry under each value, and the Filter matches any one of them.
66
+ */
67
+ list?: boolean;
68
+ /**
69
+ * A field whose values are measures, not categories: a number (`works: 90`) or a date (`until`).
70
+ * Translations of one entry repeat them, which is not a grouping, so a Board never groups by
71
+ * one.
72
+ */
73
+ measure?: boolean;
74
+ }
75
+
76
+ /** The grid field a schema field's column is keyed by, kept apart from the file facts' names. */
77
+ export function entryFieldKey(name: string): string {
78
+ return `field:${name}`;
79
+ }
80
+
81
+ /** The schema field a column key names, or null for a column that is no schema field. */
82
+ export function entryFieldOfKey(key: string): string | null {
83
+ return key.startsWith("field:") ? key.slice("field:".length) : null;
84
+ }
85
+
86
+ /** At most this many schema fields become columns; the rest are one Edit in grid away. */
87
+ export const ENTRY_FIELD_COLUMN_LIMIT = 6;
88
+
89
+ /** Formats a list cell cannot show as text: an image is the Cards view's, a body is the editor's. */
90
+ const UNLISTED_FORMATS = new Set(["image", "markdown", "html", "richtext"]);
91
+
92
+ /** "publishedAt" → "Published at", "course" → "Course". */
93
+ export function fieldTitle(name: string): string {
94
+ return capitalise(humanName(name));
95
+ }
96
+
97
+ /**
98
+ * The schema fields a collection's Table draws, in declaration order: every scalar field except the
99
+ * ones the Table already names some other way (the title, the date, the draft flag) and the ones
100
+ * whose value is not text to read in a row (an image, a body, an object).
101
+ *
102
+ * @param schema The collection's schema.
103
+ * @param skip Field names drawn already: the title field and the date field.
104
+ */
105
+ export function entryFieldColumns(
106
+ schema: ContentTypeSchema | null | undefined,
107
+ skip: ReadonlySet<string>,
108
+ ): EntryFieldColumn[] {
109
+ return Object.entries(schema?.properties ?? {})
110
+ .filter(([name, field]) => {
111
+ if (skip.has(name) || name === DRAFT_FIELD) {
112
+ return false;
113
+ }
114
+ if (
115
+ field.type === "object" ||
116
+ (isString(field.format) && UNLISTED_FORMATS.has(field.format))
117
+ ) {
118
+ return false;
119
+ }
120
+ // A list of images is a gallery, which no more reads as row text than one image does.
121
+ const itemFormat = field.items?.format;
122
+ return (
123
+ field.type !== "array" ||
124
+ (field.items?.type === "string" &&
125
+ !(isString(itemFormat) && UNLISTED_FORMATS.has(itemFormat)))
126
+ );
127
+ })
128
+ .slice(0, ENTRY_FIELD_COLUMN_LIMIT)
129
+ .map(([name, field]): EntryFieldColumn => {
130
+ const column: EntryFieldColumn = { name, title: fieldTitle(name) };
131
+ if (field.type === "array") {
132
+ column.list = true;
133
+ }
134
+ if (
135
+ field.type === "number" ||
136
+ field.type === "integer" ||
137
+ field.format === "date" ||
138
+ field.format === "date-time"
139
+ ) {
140
+ column.measure = true;
141
+ }
142
+ return column;
143
+ });
144
+ }
145
+
146
+ /**
147
+ * The Pages table's columns (the Content-Pages board): **Title** (the name the route gives the
148
+ * page, as the Site panel names it), **URL**, **Template**, **Status** when some page is marked a
149
+ * draft, **Language** when the pages are in more than one, and **Edited**. No file name and no
150
+ * path: two pages are both `index.json`, and the URL already says where each one is.
151
+ */
152
+ function pageColumns(facts?: EntryColumnFacts): GridColumn[] {
153
+ const columns: GridColumn[] = [
154
+ { editable: false, field: "title", kind: "string", title: "Title", widthHint: 240 },
155
+ // The URL is what tells two pages apart: no two routed files publish the same one.
156
+ { editable: false, field: "url", kind: "string", pk: true, title: "URL", widthHint: 200 },
157
+ { editable: false, field: "template", kind: "string", title: "Template", widthHint: 140 },
158
+ ];
159
+ if (facts?.status) {
160
+ columns.push({
161
+ editable: false,
162
+ field: "status",
163
+ kind: "string",
164
+ title: "Status",
165
+ widthHint: 100,
166
+ });
167
+ }
168
+ if (facts?.locale) {
169
+ columns.push({
170
+ editable: false,
171
+ field: "locale",
172
+ kind: "string",
173
+ title: "Language",
174
+ widthHint: 110,
175
+ });
176
+ }
177
+ columns.push({
178
+ editable: false,
179
+ field: "modified",
180
+ kind: "date",
181
+ title: "Edited",
182
+ widthHint: 120,
183
+ });
184
+ return columns;
185
+ }
186
+
187
+ /**
188
+ * The Table view's columns for a collection. `path` is the row key and is never editable.
189
+ *
190
+ * Pages are listed by what an author calls them and where they are published ({@link pageColumns}).
191
+ * A declared collection leads with what an entry is called and where it stands (its **Title**, then
192
+ * its **Status** and **Date** when its entries have them), with the file it is stored in after: a
193
+ * post is "Hello world, Draft", not `hello-world.md`.
194
+ */
195
+ export function libraryColumns(
196
+ collection: string = PAGES_COLLECTION,
197
+ facts?: EntryColumnFacts,
198
+ ): GridColumn[] {
199
+ // The Locale column only when the entries are in more than one language: a column that is empty
200
+ // On every row of a one-language project is width taken from the Title for nothing.
201
+ const fileFacts: GridColumn[] = [
202
+ ...(facts?.locale
203
+ ? [
204
+ {
205
+ editable: false,
206
+ field: "locale",
207
+ kind: "string",
208
+ title: "Locale",
209
+ widthHint: 90,
210
+ } as const,
211
+ ]
212
+ : []),
44
213
  { editable: false, field: "modified", kind: "date", title: "Modified", widthHint: 160 },
45
214
  { editable: false, field: "path", kind: "readonly", title: "Path", widthHint: 320 },
46
215
  ];
216
+ if (collection === PAGES_COLLECTION) {
217
+ return pageColumns(facts);
218
+ }
219
+ const entry: GridColumn[] = [
220
+ { editable: false, field: "title", kind: "string", title: "Title", widthHint: 260 },
221
+ ];
222
+ if (facts?.status) {
223
+ entry.push({
224
+ editable: false,
225
+ field: "status",
226
+ kind: "string",
227
+ title: "Status",
228
+ widthHint: 100,
229
+ });
230
+ }
231
+ if (facts?.date) {
232
+ entry.push({ editable: false, field: "date", kind: "date", title: "Date", widthHint: 120 });
233
+ }
234
+ for (const field of facts?.fields ?? []) {
235
+ entry.push({
236
+ editable: false,
237
+ field: entryFieldKey(field.name),
238
+ kind: "string",
239
+ title: field.title,
240
+ widthHint: 140,
241
+ });
242
+ }
243
+ // The Path names the file, so a File column beside it would say the same thing twice.
244
+ return [...entry, ...fileFacts];
47
245
  }
48
246
 
49
247
  /** One scanned file as a grid row. */
50
248
  export function libraryRow(file: LibraryFile): GridRow {
51
249
  return {
52
250
  cells: {
53
- category: file.category,
54
251
  // Null, not "": the platform reported no locale directory for this file, and a grid cell
55
252
  // Distinguishes "no value" from "the empty string" everywhere else in this contract.
56
253
  locale: file.locale ?? null,
57
254
  modified: file.modified ?? null,
58
- name: file.name,
59
- path: file.path,
60
- size: file.size ?? null,
61
- type: file.type,
255
+ // The scan reads no file, so a status, a template and a title are the Content home's to add.
256
+ status: null,
257
+ template: null,
258
+ title: file.name,
259
+ // A page's own URL; an entry's is its collection page's, which the scan does not read.
260
+ url: file.collection === PAGES_COLLECTION ? fileToRoute(file.path).urlPattern : null,
62
261
  },
63
262
  key: file.path,
64
263
  };
65
264
  }
66
265
 
67
266
  /**
68
- * A Library source, plus the two questions a grid source cannot ask but the Library must answer:
69
- * what the scan could not read, and the files behind the rows (the non-table layouts want the typed
70
- * record, not a cell bag).
267
+ * A file-backed collection's file, replaced by its records (`content/file-records.ts`): a CSV of
268
+ * fifteen prices is fifteen entries of its collection, which is what the counts and the Content
269
+ * home list. The file is read once per scan. One that cannot be read, or whose kind holds no
270
+ * records Studio reads, stays listed as the file it is.
271
+ */
272
+ export async function expandFileRecords(
273
+ files: readonly LibraryFile[],
274
+ readFile: (path: string) => Promise<string>,
275
+ ): Promise<LibraryFile[]> {
276
+ const expanded = await Promise.all(
277
+ files.map(async (file): Promise<LibraryFile[]> => {
278
+ const match = file.collection === null ? null : collectionForFile(file.path);
279
+ if (!match?.fileBacked) {
280
+ return [file];
281
+ }
282
+ let text: string;
283
+ try {
284
+ text = await readFile(file.path);
285
+ } catch {
286
+ return [file];
287
+ }
288
+ const records = fileRecords(file.path, text, match.def.schema);
289
+ if (records === null) {
290
+ return [file];
291
+ }
292
+ return records.map((record) => recordRow(file, record));
293
+ }),
294
+ );
295
+ return expanded.flat();
296
+ }
297
+
298
+ /** One record of `file`, listed as a row of its own. */
299
+ function recordRow(file: LibraryFile, record: FileRecord): LibraryFile {
300
+ return {
301
+ ...file,
302
+ name: record.title,
303
+ path: recordKey(file.path, record.index),
304
+ record: { fields: record.fields, row: record.row, source: file.path },
305
+ };
306
+ }
307
+
308
+ /** {@link expandFileRecords} over a whole scan, reading through the platform. */
309
+ async function withRecords(scan: LibraryScan): Promise<LibraryScan> {
310
+ const platform = getPlatform();
311
+ const files = await expandFileRecords(scan.files, (path) => platform.readFile(path));
312
+ return { ...scan, files };
313
+ }
314
+
315
+ /**
316
+ * The scan's source, plus the two questions a grid source cannot ask but the Content home must
317
+ * answer: what the scan could not read, and the files behind the rows (the non-table layouts want
318
+ * the typed record, not a cell bag).
71
319
  */
72
320
  export interface LibrarySource extends GridSource {
73
321
  /** The scanned files, in path order. Empty before the first {@link GridSource.rows} call. */
@@ -76,15 +324,19 @@ export interface LibrarySource extends GridSource {
76
324
  failures: () => readonly ScanFailure[];
77
325
  /** Whether a scan has completed at least once — "not loaded yet" is not "empty". */
78
326
  scanned: () => boolean;
79
- }
80
-
81
- /** The Library's tab id. One Library per window; category and layout are view state, not identity. */
82
- export function libraryTabId(): string {
83
- return makeGridTabId({ kind: "library" });
327
+ /**
328
+ * Whether the completed scan walked a different directory list than the project has now.
329
+ *
330
+ * The list is read when the project's directories load and grows when a collection is declared,
331
+ * and either can happen after a scan already finished: a window restored straight into Content
332
+ * asks for its rows before the project's directories have loaded, and the walk of none is an
333
+ * answer of none. A stale scan is not an empty project, so its owner discards it.
334
+ */
335
+ stale: () => boolean;
84
336
  }
85
337
 
86
338
  /**
87
- * Build the Library source. The scan is lazy: nothing is read until the first `rows()`.
339
+ * Build the scan's source. The scan is lazy: nothing is read until the first `rows()`.
88
340
  *
89
341
  * @param {() => readonly string[]} [dirs] Project directories to scan, injectable for tests and for
90
342
  * the perf measurement; defaults to the open project's own list.
@@ -92,8 +344,12 @@ export function libraryTabId(): string {
92
344
  export function createLibrarySource(dirs?: () => readonly string[]): LibrarySource {
93
345
  let scan: LibraryScan | null = null;
94
346
  let inFlight: Promise<LibraryScan> | null = null;
347
+ /** The directory list the cached scan walked, joined: the key {@link LibrarySource.stale} tests. */
348
+ let scannedKey: string | null = null;
95
349
 
96
- const projectDirs = dirs ?? (() => projectState?.projectDirs ?? []);
350
+ const projectDirs =
351
+ dirs ??
352
+ (() => libraryScanDirs(projectState?.projectDirs ?? [], projectState?.projectConfig ?? null));
97
353
 
98
354
  async function ensureScan(): Promise<LibraryScan> {
99
355
  if (scan) {
@@ -101,11 +357,21 @@ export function createLibrarySource(dirs?: () => readonly string[]): LibrarySour
101
357
  }
102
358
  // Coalesce: five layouts and a command can all ask at once on the first paint, and the answer
103
359
  // Is one walk of the project, not six.
104
- inFlight ??= scanLibrary(projectDirs(), getPlatform()).then((result) => {
105
- scan = result;
106
- inFlight = null;
107
- return result;
108
- });
360
+ const walked = projectDirs();
361
+ /* A collection source the project's own list does not hold may not exist yet (a collection
362
+ declared before its first entry): its absence is an empty collection, never a failure. */
363
+ const conventional = new Set(dirs ? walked : (projectState?.projectDirs ?? []));
364
+ const optional = new Set(walked.filter((dir) => !conventional.has(dir)));
365
+ // The extensions first: a page is the file the build routes, and that is a fact of the formats.
366
+ inFlight ??= routedPageExtensions()
367
+ .then((pageExtensions) => scanLibrary(walked, getPlatform(), optional, pageExtensions))
368
+ .then(withRecords)
369
+ .then((result) => {
370
+ scan = result;
371
+ scannedKey = walked.join("\n");
372
+ inFlight = null;
373
+ return result;
374
+ });
109
375
  return inFlight;
110
376
  }
111
377
 
@@ -134,8 +400,8 @@ export function createLibrarySource(dirs?: () => readonly string[]): LibrarySour
134
400
  },
135
401
  failures: () => scan?.failures ?? [],
136
402
  files: () => scan?.files ?? [],
137
- id: libraryTabId(),
138
- label: "Library",
403
+ id: workspaceHomeTabId("content"),
404
+ label: "Content",
139
405
  async refresh() {
140
406
  scan = null;
141
407
  inFlight = null;
@@ -146,5 +412,6 @@ export function createLibrarySource(dirs?: () => readonly string[]): LibrarySour
146
412
  return { rows: result.files.map((file) => libraryRow(file)), total: result.files.length };
147
413
  },
148
414
  scanned: () => scan !== null,
415
+ stale: () => scan !== null && scannedKey !== projectDirs().join("\n"),
149
416
  };
150
417
  }