@jxsuite/studio 2.0.0 → 2.1.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 (419) hide show
  1. package/canvas.html +21 -0
  2. package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
  3. package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
  4. package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
  5. package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
  6. package/dist/chunks/{components-0hzentwq.js → components-2bpqp4mq.js} +5 -7
  7. package/dist/chunks/{components-0hzentwq.js.map → components-2bpqp4mq.js.map} +1 -1
  8. package/dist/chunks/content-source-34gnkbnn.js +38 -0
  9. package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
  10. package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
  11. package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
  12. package/dist/chunks/extension-sections-6mqydz35.js +301 -0
  13. package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
  14. package/dist/chunks/files-8kn321h1.js +70 -0
  15. package/dist/chunks/files-8kn321h1.js.map +9 -0
  16. package/dist/chunks/{format-host-2c188n41.js → format-host-mkwsmt8d.js} +3 -3
  17. package/dist/chunks/git-panel-tjbr191d.js +51 -0
  18. package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
  19. package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
  20. package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
  21. package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
  22. package/dist/chunks/layers-panel-87pb25km.js +58 -0
  23. package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
  24. package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
  25. package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
  26. package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
  27. package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
  28. package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
  29. package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
  30. package/dist/chunks/right-panel-zpdxd98t.js +37 -0
  31. package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
  32. package/dist/chunks/section-registry-zmckaxf4.js +30 -0
  33. package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
  34. package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
  35. package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
  36. package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
  37. package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
  38. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
  39. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
  40. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
  41. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
  42. package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
  43. package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
  44. package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
  45. package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
  46. package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
  47. package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
  48. package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
  49. package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
  50. package/dist/chunks/studio-41exgzhq.js +6690 -0
  51. package/dist/chunks/studio-41exgzhq.js.map +38 -0
  52. package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
  53. package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
  54. package/dist/chunks/studio-50k77cca.js +1787 -0
  55. package/dist/chunks/studio-50k77cca.js.map +18 -0
  56. package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
  57. package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
  58. package/dist/chunks/studio-6f0am8f2.js +53 -0
  59. package/dist/chunks/studio-6f0am8f2.js.map +10 -0
  60. package/dist/chunks/studio-6nmjc5qk.js +7002 -0
  61. package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
  62. package/dist/chunks/studio-7afd21nw.js +67 -0
  63. package/dist/chunks/studio-7afd21nw.js.map +10 -0
  64. package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
  65. package/dist/chunks/studio-7bmdvzne.js.map +10 -0
  66. package/dist/chunks/studio-8jpzqk5s.js +128 -0
  67. package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
  68. package/dist/chunks/studio-94m77tgk.js +1482 -0
  69. package/dist/chunks/studio-94m77tgk.js.map +13 -0
  70. package/dist/chunks/studio-9je6w5sq.js +1000 -0
  71. package/dist/chunks/studio-9je6w5sq.js.map +16 -0
  72. package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
  73. package/dist/chunks/{studio-97qcgvdn.js → studio-adn0z1k3.js} +14 -8
  74. package/dist/chunks/{studio-97qcgvdn.js.map → studio-adn0z1k3.js.map} +3 -3
  75. package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
  76. package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
  77. package/dist/chunks/studio-bw5dz6km.js +608 -0
  78. package/dist/chunks/studio-bw5dz6km.js.map +21 -0
  79. package/dist/chunks/studio-c6s5getc.js +4232 -0
  80. package/dist/chunks/studio-c6s5getc.js.map +39 -0
  81. package/dist/chunks/studio-cjm7fdjf.js +451 -0
  82. package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
  83. package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
  84. package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
  85. package/dist/chunks/studio-dg7zyt5x.js +28 -0
  86. package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
  87. package/dist/chunks/studio-dzcyvvvz.js +166 -0
  88. package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
  89. package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
  90. package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
  91. package/dist/chunks/studio-f12h6177.js +411 -0
  92. package/dist/chunks/studio-f12h6177.js.map +13 -0
  93. package/dist/chunks/studio-hbp1s8m9.js +1817 -0
  94. package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
  95. package/dist/chunks/studio-k2sxjph5.js +15295 -0
  96. package/dist/chunks/studio-k2sxjph5.js.map +235 -0
  97. package/dist/chunks/studio-myje51t3.js +385 -0
  98. package/dist/chunks/studio-myje51t3.js.map +12 -0
  99. package/dist/chunks/studio-na6rwzhw.js +353 -0
  100. package/dist/chunks/studio-na6rwzhw.js.map +11 -0
  101. package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
  102. package/dist/chunks/studio-p471gg5n.js.map +14 -0
  103. package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
  104. package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
  105. package/dist/chunks/{studio-hhj92gcr.js → studio-s519zc99.js} +3 -3
  106. package/dist/chunks/studio-sas7mt3k.js +75 -0
  107. package/dist/chunks/studio-sas7mt3k.js.map +10 -0
  108. package/dist/chunks/studio-t5wqwzdh.js +74 -0
  109. package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
  110. package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
  111. package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
  112. package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
  113. package/dist/chunks/studio-tr20m8g8.js.map +9 -0
  114. package/dist/chunks/{studio-ygzav7z5.js → studio-v96qfnww.js} +5514 -4291
  115. package/dist/chunks/studio-v96qfnww.js.map +54 -0
  116. package/dist/chunks/studio-vcetc91k.js +659 -0
  117. package/dist/chunks/studio-vcetc91k.js.map +10 -0
  118. package/dist/chunks/{studio-n9zmpxjg.js → studio-vmymjefp.js} +2972 -4937
  119. package/dist/chunks/studio-vmymjefp.js.map +98 -0
  120. package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
  121. package/dist/chunks/studio-x9hmy5kr.js +390 -0
  122. package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
  123. package/dist/chunks/studio-xa0ce0n7.js +335 -0
  124. package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
  125. package/dist/chunks/studio-xm3dbxvn.js +378 -0
  126. package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
  127. package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
  128. package/dist/chunks/studio-zv37yrfa.js +1054 -0
  129. package/dist/chunks/studio-zv37yrfa.js.map +13 -0
  130. package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
  131. package/dist/chunks/ws-client-654ts66j.js.map +11 -0
  132. package/dist/iframe-entry.js +668 -230
  133. package/dist/studio.js +23711 -35185
  134. package/index.html +35 -4685
  135. package/package.json +17 -15
  136. package/src/about/about-modal.ts +42 -11
  137. package/src/browse/library-commands.ts +240 -0
  138. package/src/browse/library-layouts.ts +369 -0
  139. package/src/browse/library-model.ts +396 -0
  140. package/src/browse/library-pane.ts +1049 -0
  141. package/src/browse/library-preview.ts +252 -0
  142. package/src/browse/library-source.ts +150 -0
  143. package/src/canvas/canvas-helpers.ts +33 -9
  144. package/src/canvas/canvas-live-render.ts +45 -29
  145. package/src/canvas/canvas-patcher.ts +151 -31
  146. package/src/canvas/canvas-perf.ts +46 -7
  147. package/src/canvas/canvas-render.ts +1092 -220
  148. package/src/canvas/canvas-surface.ts +240 -0
  149. package/src/canvas/canvas-utils.ts +1166 -124
  150. package/src/canvas/iframe-drop.ts +2 -1
  151. package/src/canvas/iframe-entry.ts +241 -4
  152. package/src/canvas/iframe-host.ts +1226 -143
  153. package/src/canvas/iframe-inline-edit.ts +31 -0
  154. package/src/canvas/iframe-interaction.ts +76 -16
  155. package/src/canvas/iframe-keys.ts +107 -45
  156. package/src/canvas/iframe-overlay.ts +38 -1
  157. package/src/canvas/iframe-protocol.ts +137 -2
  158. package/src/canvas/iframe-render.ts +70 -2
  159. package/src/canvas/iframe-slash.ts +6 -0
  160. package/src/canvas/path-mapping.ts +56 -6
  161. package/src/canvas/surface-registry.ts +292 -0
  162. package/src/collab/collab-commands.ts +244 -0
  163. package/src/collab/collab-session.ts +198 -32
  164. package/src/collab/collab-state.ts +28 -2
  165. package/src/collab/presence-chips.ts +100 -9
  166. package/src/commands/active-registry.ts +42 -0
  167. package/src/commands/app-commands.ts +176 -0
  168. package/src/commands/budget.ts +158 -0
  169. package/src/commands/command-args.ts +253 -0
  170. package/src/commands/context.ts +387 -0
  171. package/src/commands/defaults.ts +580 -0
  172. package/src/commands/keymap.ts +571 -0
  173. package/src/commands/levels.ts +345 -0
  174. package/src/commands/live-context.ts +193 -0
  175. package/src/commands/reference.ts +227 -0
  176. package/src/commands/registry.ts +293 -0
  177. package/src/component-props.ts +3 -2
  178. package/src/content/draft-state.ts +106 -0
  179. package/src/content/entry-commands.ts +282 -0
  180. package/src/content/entry-editor.ts +327 -0
  181. package/src/content/entry-fields.ts +116 -0
  182. package/src/content/entry-model.ts +175 -0
  183. package/src/editor/canvas-context-menu.ts +17 -11
  184. package/src/editor/canvas-slash-bridge.ts +11 -4
  185. package/src/editor/context-menu.ts +804 -204
  186. package/src/editor/convert-to-component.ts +31 -10
  187. package/src/editor/convert-to-repeater.ts +3 -2
  188. package/src/editor/inline-edit-apply.ts +5 -5
  189. package/src/editor/inline-edit.ts +86 -50
  190. package/src/editor/insert-zone-action.ts +1 -1
  191. package/src/editor/shortcuts.ts +1043 -329
  192. package/src/editor/slash-menu.ts +1 -0
  193. package/src/files/elements.ts +166 -0
  194. package/src/files/file-ops.ts +178 -31
  195. package/src/files/files.ts +898 -268
  196. package/src/files/fs-events.ts +21 -0
  197. package/src/files/media-meta.ts +203 -0
  198. package/src/files/media-paths.ts +141 -0
  199. package/src/files/media-upload.ts +7 -3
  200. package/src/files/media-usage.ts +189 -0
  201. package/src/github/github-auth.ts +235 -33
  202. package/src/github/github-publish.ts +80 -21
  203. package/src/grid/grid-controller.ts +152 -12
  204. package/src/grid/grid-layout.ts +225 -20
  205. package/src/grid/grid-open.ts +120 -0
  206. package/src/grid/grid-panel.ts +554 -39
  207. package/src/grid/grid-source.ts +27 -1
  208. package/src/grid/redirects-grid.ts +599 -0
  209. package/src/grid/redirects.ts +398 -0
  210. package/src/grid/sources/content-source.ts +108 -2
  211. package/src/i18n/i18n-commands.ts +322 -0
  212. package/src/new-project/add-repo-modal.ts +8 -10
  213. package/src/new-project/import-tab.ts +34 -9
  214. package/src/new-project/location-fields.ts +9 -1
  215. package/src/new-project/new-project-modal.ts +270 -262
  216. package/src/packages/jxsuite-update.ts +97 -48
  217. package/src/packages/pull-package-sync.ts +18 -9
  218. package/src/page-params.ts +2 -1
  219. package/src/panels/activity-bar.ts +152 -91
  220. package/src/panels/activity-panel.ts +481 -0
  221. package/src/panels/ai-chat/chat-markdown.ts +8 -3
  222. package/src/panels/ai-chat/chat-view.ts +245 -18
  223. package/src/panels/ai-chat/composer.ts +31 -5
  224. package/src/panels/ai-chat/sessions-view.ts +17 -10
  225. package/src/panels/ai-panel.ts +279 -120
  226. package/src/panels/block-action-bar.ts +1115 -224
  227. package/src/panels/bottom-dock.ts +337 -0
  228. package/src/panels/canvas-dnd-bridge.ts +37 -8
  229. package/src/panels/chat-panel.ts +29 -12
  230. package/src/panels/data-explorer.ts +254 -112
  231. package/src/panels/data-grid.ts +10 -4
  232. package/src/panels/dnd.ts +28 -51
  233. package/src/panels/editors.ts +434 -107
  234. package/src/panels/elements-panel.ts +66 -2
  235. package/src/panels/empty-state.ts +112 -0
  236. package/src/panels/events-panel.ts +596 -82
  237. package/src/panels/formula-workspace.ts +408 -218
  238. package/src/panels/frontmatter-fields.ts +51 -10
  239. package/src/panels/frontmatter-panel.ts +357 -79
  240. package/src/panels/git-panel.ts +375 -164
  241. package/src/panels/head-panel.ts +791 -85
  242. package/src/panels/i18n-panel.ts +592 -0
  243. package/src/panels/imports-panel.ts +95 -85
  244. package/src/panels/jump-bar.ts +584 -0
  245. package/src/panels/layers-panel.ts +1106 -328
  246. package/src/panels/left-panel.ts +109 -204
  247. package/src/panels/navigator-panels.ts +131 -0
  248. package/src/panels/overlays.ts +7 -2
  249. package/src/panels/pane-context.ts +1477 -0
  250. package/src/panels/pane-grid.ts +406 -0
  251. package/src/panels/panel-registry.ts +340 -0
  252. package/src/panels/panel-scheduler.ts +75 -0
  253. package/src/panels/problems-panel.ts +234 -0
  254. package/src/panels/properties-panel.ts +946 -1139
  255. package/src/panels/provenance.ts +258 -0
  256. package/src/panels/quick-search.ts +791 -122
  257. package/src/panels/right-panel.ts +228 -102
  258. package/src/panels/seo-modal.ts +395 -0
  259. package/src/panels/settings-pane.ts +203 -0
  260. package/src/panels/signals-panel.ts +354 -137
  261. package/src/panels/statement-editor.ts +41 -25
  262. package/src/panels/statusbar.ts +342 -106
  263. package/src/panels/style-panel.ts +974 -463
  264. package/src/panels/style-utils.ts +3 -3
  265. package/src/panels/stylebook-doc.ts +4 -2
  266. package/src/panels/stylebook-layers-panel.ts +3 -2
  267. package/src/panels/stylebook-panel.ts +51 -26
  268. package/src/panels/tab-strip.ts +1058 -78
  269. package/src/panels/target-line.ts +274 -0
  270. package/src/panels/toolbar.ts +631 -484
  271. package/src/panels/welcome-screen.ts +301 -198
  272. package/src/platform-errors.ts +26 -3
  273. package/src/platform.ts +96 -2
  274. package/src/platforms/cloud.ts +71 -8
  275. package/src/platforms/devserver.ts +92 -29
  276. package/src/publish/deploy-checklist.ts +383 -0
  277. package/src/publish/pages-service.ts +10 -1
  278. package/src/publish/publish-commands.ts +218 -0
  279. package/src/publish/publish-panel.ts +110 -16
  280. package/src/recent-projects.ts +3 -2
  281. package/src/services/a11y-report.ts +505 -0
  282. package/src/services/ai-models.ts +75 -2
  283. package/src/services/ai-project-tools.ts +29 -10
  284. package/src/services/ai-system-prompt.ts +53 -14
  285. package/src/services/ai-tools.ts +39 -3
  286. package/src/services/ai-writes.ts +154 -0
  287. package/src/services/announce.ts +97 -0
  288. package/src/services/automation.ts +424 -175
  289. package/src/services/cem-export.ts +9 -4
  290. package/src/services/clock.ts +61 -0
  291. package/src/services/document-assistant.ts +36 -8
  292. package/src/services/idle.ts +179 -0
  293. package/src/services/import-client.ts +26 -0
  294. package/src/services/monaco-buffer.ts +627 -0
  295. package/src/services/monaco-lazy.ts +36 -0
  296. package/src/services/notify.ts +271 -0
  297. package/src/services/profile.ts +155 -0
  298. package/src/services/references.ts +0 -0
  299. package/src/services/token-lint.ts +3 -2
  300. package/src/services/tool-executor.ts +53 -7
  301. package/src/services/trusted-types.ts +123 -0
  302. package/src/settings/contexts-section.ts +427 -0
  303. package/src/settings/contributed-section.ts +149 -11
  304. package/src/settings/css-vars-editor.ts +306 -349
  305. package/src/settings/defs-editor.ts +93 -34
  306. package/src/settings/dependencies-editor.ts +7 -7
  307. package/src/settings/extension-sections.ts +37 -5
  308. package/src/settings/general-settings.ts +204 -121
  309. package/src/settings/locales-section.ts +347 -0
  310. package/src/settings/preferences-accounts.ts +140 -0
  311. package/src/settings/preferences-dialog.ts +620 -0
  312. package/src/settings/preferences-keymap.ts +270 -0
  313. package/src/settings/project-sections.ts +260 -0
  314. package/src/settings/section-registry.ts +169 -0
  315. package/src/settings/settings-document.ts +385 -0
  316. package/src/shell.ts +1549 -0
  317. package/src/site-context.ts +45 -20
  318. package/src/state.ts +14 -275
  319. package/src/store.ts +88 -43
  320. package/src/studio.ts +790 -346
  321. package/src/style/live-preview.ts +36 -0
  322. package/src/style/project-styles.ts +349 -0
  323. package/src/style/token-ref.ts +123 -0
  324. package/src/tabs/patch-ops.ts +16 -4
  325. package/src/tabs/project-config.ts +416 -0
  326. package/src/tabs/selection.ts +323 -0
  327. package/src/tabs/tab.ts +182 -47
  328. package/src/tabs/transact.ts +209 -28
  329. package/src/types.ts +68 -13
  330. package/src/ui/ai-credentials-form.ts +39 -35
  331. package/src/ui/ai-managed-connect.ts +113 -0
  332. package/src/ui/dynamic-slot.ts +224 -81
  333. package/src/ui/expression-editor.ts +71 -63
  334. package/src/ui/field-input.ts +4 -6
  335. package/src/ui/field-row.ts +215 -38
  336. package/src/ui/form-controls.ts +194 -7
  337. package/src/ui/layers.ts +492 -69
  338. package/src/ui/media-picker.ts +115 -25
  339. package/src/ui/panel-resize.ts +109 -91
  340. package/src/ui/progress-modal.ts +131 -50
  341. package/src/ui/regions.ts +462 -0
  342. package/src/ui/schema-form.ts +229 -45
  343. package/src/ui/spectrum.ts +24 -8
  344. package/src/ui/timing.ts +49 -0
  345. package/src/ui/value-selector.ts +20 -0
  346. package/src/ui/value-source.ts +348 -0
  347. package/src/ui/virtual-window.ts +400 -0
  348. package/src/utils/edit-display.ts +3 -2
  349. package/src/utils/inherited-style.ts +67 -37
  350. package/src/view.ts +64 -78
  351. package/src/workspace/pane-derive.ts +1258 -0
  352. package/src/workspace/session.ts +317 -0
  353. package/src/workspace/workspace.ts +1345 -23
  354. package/styles/canvas.css +653 -0
  355. package/styles/forced-colors.css +106 -0
  356. package/styles/inspector.css +1454 -0
  357. package/styles/overlays.css +2704 -0
  358. package/styles/panels.css +1701 -0
  359. package/styles/shell.css +1423 -0
  360. package/styles/tokens.css +151 -0
  361. package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
  362. package/dist/chunks/extension-sections-qct0gt8f.js +0 -531
  363. package/dist/chunks/extension-sections-qct0gt8f.js.map +0 -12
  364. package/dist/chunks/files-02zn5y24.js +0 -50
  365. package/dist/chunks/files-02zn5y24.js.map +0 -9
  366. package/dist/chunks/git-panel-mbb0y0hz.js +0 -28
  367. package/dist/chunks/git-panel-mbb0y0hz.js.map +0 -9
  368. package/dist/chunks/layers-panel-9qekmqk1.js +0 -26
  369. package/dist/chunks/layers-panel-9qekmqk1.js.map +0 -9
  370. package/dist/chunks/settings-modal-gj0hrah4.js +0 -29
  371. package/dist/chunks/settings-modal-gj0hrah4.js.map +0 -9
  372. package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
  373. package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
  374. package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
  375. package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
  376. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
  377. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
  378. package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
  379. package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
  380. package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
  381. package/dist/chunks/studio-568nx4t5.js +0 -469
  382. package/dist/chunks/studio-568nx4t5.js.map +0 -14
  383. package/dist/chunks/studio-9smfzqhc.js +0 -19
  384. package/dist/chunks/studio-9smfzqhc.js.map +0 -10
  385. package/dist/chunks/studio-asvf0j9e.js +0 -429
  386. package/dist/chunks/studio-asvf0j9e.js.map +0 -13
  387. package/dist/chunks/studio-d77qm3pk.js +0 -3427
  388. package/dist/chunks/studio-d77qm3pk.js.map +0 -26
  389. package/dist/chunks/studio-j6gqyjf4.js +0 -1854
  390. package/dist/chunks/studio-j6gqyjf4.js.map +0 -18
  391. package/dist/chunks/studio-n9zmpxjg.js.map +0 -115
  392. package/dist/chunks/studio-qkvt37c0.js.map +0 -9
  393. package/dist/chunks/studio-rq0vxb19.js +0 -3744
  394. package/dist/chunks/studio-rq0vxb19.js.map +0 -22
  395. package/dist/chunks/studio-wd6ewdvd.js +0 -853
  396. package/dist/chunks/studio-wd6ewdvd.js.map +0 -12
  397. package/dist/chunks/studio-xq0t7n5t.js +0 -140
  398. package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
  399. package/dist/chunks/studio-xze4zrs1.js.map +0 -10
  400. package/dist/chunks/studio-ygzav7z5.js.map +0 -46
  401. package/dist/chunks/studio-zmy7241e.js +0 -399
  402. package/dist/chunks/studio-zmy7241e.js.map +0 -12
  403. package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
  404. package/src/browse/browse-modal.ts +0 -69
  405. package/src/browse/browse.ts +0 -904
  406. package/src/canvas/canvas-diff.ts +0 -216
  407. package/src/canvas/nested-site-style.ts +0 -58
  408. package/src/new-project/design-fields.ts +0 -264
  409. package/src/new-project/templates.ts +0 -61
  410. package/src/panels/tab-bar.ts +0 -548
  411. package/src/settings/settings-modal.ts +0 -203
  412. /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
  413. /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
  414. /package/dist/chunks/{format-host-2c188n41.js.map → format-host-mkwsmt8d.js.map} +0 -0
  415. /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
  416. /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
  417. /package/dist/chunks/{studio-hhj92gcr.js.map → studio-s519zc99.js.map} +0 -0
  418. /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
  419. /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
@@ -0,0 +1,1049 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * The Library — an editor kind, drawn into the pane, over a {@link LibrarySource}.
4
+ *
5
+ * It replaces the Manage view, which was a full-screen MODAL over the whole shell. Everything that
6
+ * was wrong with that follows from the modal: it could not be a tab, so it could not be one of two
7
+ * panes, so it could not be open beside the page you were browsing FOR; it had no editor kind, so
8
+ * the status bar and the pane context bar had nothing to say about it; and its own filter bar was
9
+ * the only way to reach any of its state, so nothing it did was in the palette, on a chord, or
10
+ * available to the assistant. `manage` was already in `commands/context.ts`'s `EDITOR_KIND_BY_MODE`
11
+ * — the map just had nothing behind it.
12
+ *
13
+ * Four things this module is careful about:
14
+ *
15
+ * 1. **It never says "No files found" for two different reasons.** A scan that could not read a
16
+ * directory is INCOMPLETE, not empty; it raises a Problem carrying the directory and a Retry
17
+ * (`library.refresh`), and the surface says so. A filter that matched nothing says which
18
+ * filter.
19
+ * 2. **The rendered item count is proportional to the viewport, not to the project.** See
20
+ * `../ui/virtual-window.ts` and `library-preview.ts`; the acceptance case is 300 pages in
21
+ * "All".
22
+ * 3. **An upload has a named destination**, shown before the drop, asked for when the active category
23
+ * does not name one.
24
+ * 4. **Creation is somebody else's flow.** A page, layout or component is `files.ts`'s creation, so a
25
+ * new page from the Library and a new file from the tree refuse a name that is already taken in
26
+ * exactly the same way; a collection entry is `content/entry-commands.ts`'s `createEntry`, so it
27
+ * arrives with the collection's extension and a body seeded from that collection's schema.
28
+ *
29
+ * @docs studio/projects/browse
30
+ */
31
+
32
+ import { html, render as litRender, nothing } from "lit-html";
33
+ import { ref } from "lit-html/directives/ref.js";
34
+ import { effect, effectScope, reactive } from "../reactivity";
35
+ import { errorMessage } from "@jxsuite/schema/parse";
36
+ import { notify } from "../services/notify";
37
+ import { beginActivity } from "../panels/activity-panel";
38
+ import { renderPopover, showPromptDialog } from "../ui/layers";
39
+ import { rectOf } from "../utils/geometry";
40
+ import { getPlatform } from "../platform";
41
+ import { invalidateUsages } from "../services/references";
42
+ import { confirmFileDelete, renamePromptMessage } from "../files/file-ops";
43
+ import { createFileIn, openFileInTab } from "../files/files";
44
+ import { createEntry } from "../content/entry-commands";
45
+ import { entryCollections } from "../content/entry-model";
46
+ import { UPLOAD_ACCEPT, uploadAssets } from "../files/media-upload";
47
+ import { localeLabel } from "@jxsuite/schema/locale";
48
+ import {
49
+ LIBRARY_CATEGORIES,
50
+ LIBRARY_LAYOUTS,
51
+ LIBRARY_LAYOUT_LABELS,
52
+ PREVIEW_LAYOUTS,
53
+ filterLibrary,
54
+ libraryCategory,
55
+ libraryLocales,
56
+ uploadDirForCategory,
57
+ } from "./library-model";
58
+ import { createLibrarySource, libraryColumns } from "./library-source";
59
+ import { createPreviewCache, createPreviewObserver, previewFor } from "./library-preview";
60
+ import { computeWindow } from "../ui/virtual-window";
61
+ import { paneRegion } from "../ui/regions";
62
+ import {
63
+ LAYOUT_METRICS,
64
+ boardTpl,
65
+ calendarTpl,
66
+ cardsTpl,
67
+ columnsAt,
68
+ mediaTpl,
69
+ tableHeadTpl,
70
+ tableRowsTpl,
71
+ } from "./library-layouts";
72
+ import type { ActivityHandle } from "../panels/activity-panel";
73
+ import type { EffectScope } from "@vue/reactivity";
74
+ import type { LibraryFile, LibraryLayout } from "./library-model";
75
+ import type { LibrarySource } from "./library-source";
76
+ import type { LayoutContext } from "./library-layouts";
77
+ import type { PreviewCache, PreviewObserver } from "./library-preview";
78
+ import type { WindowRange } from "../ui/virtual-window";
79
+ import type { Tab } from "../tabs/tab";
80
+ import type { CanvasSurface } from "../canvas/canvas-surface";
81
+
82
+ // ─── View state ──────────────────────────────────────────────────────────────
83
+
84
+ export interface LibraryViewState {
85
+ category: string;
86
+ layout: LibraryLayout;
87
+ query: string;
88
+ /**
89
+ * The language facet: a canonical tag, or "" for every language.
90
+ *
91
+ * A tag rather than a label, because two locales can display the same autonym and only the tag
92
+ * matches what {@link LibraryFile.locale} carries.
93
+ */
94
+ locale: string;
95
+ /** Bumped to force a repaint from a non-reactive source (a scroll, a finished scan). */
96
+ revision: number;
97
+ /** A scan is running. Distinct from "scanned and empty", which is what the old view conflated. */
98
+ loading: boolean;
99
+ }
100
+
101
+ /**
102
+ * The Library's view state, reactive and module-scoped.
103
+ *
104
+ * Module-scoped rather than per-tab because there is one Library per window (its tab id carries no
105
+ * category — see `grid-source.ts`), and because the commands that write it must be able to do so
106
+ * whether or not the pane is currently mounted: `library.setCategory` run from the palette while
107
+ * another tab is focused sets the state the Library opens into.
108
+ */
109
+ export const libraryView: LibraryViewState = reactive({
110
+ category: "all",
111
+ layout: "cards",
112
+ loading: false,
113
+ locale: "",
114
+ query: "",
115
+ revision: 0,
116
+ });
117
+
118
+ /** The window's Library source. Built lazily; discarded by {@link invalidateLibrary}. */
119
+ let source: LibrarySource | null = null;
120
+
121
+ /**
122
+ * Whether a scan has been STARTED for the current source.
123
+ *
124
+ * Distinct from `source.scanned()`, which only answers whether one SUCCEEDED. The pane's render
125
+ * effect kicks the first scan off, so without this a scan that failed would be retried on the very
126
+ * repaint its own failure caused — an infinite loop, and the reason a Retry has to be a command the
127
+ * reader presses rather than something the surface does on its own behalf.
128
+ */
129
+ let scanAttempted = false;
130
+
131
+ /** The source, creating it on first use. */
132
+ export function librarySource(): LibrarySource {
133
+ source ??= createLibrarySource();
134
+ return source;
135
+ }
136
+
137
+ /** Repaint whatever is mounted. */
138
+ function bump() {
139
+ libraryView.revision += 1;
140
+ }
141
+
142
+ export function setLibraryCategory(key: string): void {
143
+ libraryView.category = key;
144
+ bump();
145
+ }
146
+
147
+ export function setLibraryLayout(layout: LibraryLayout): void {
148
+ libraryView.layout = layout;
149
+ bump();
150
+ }
151
+
152
+ export function setLibrarySearch(query: string): void {
153
+ libraryView.query = query;
154
+ bump();
155
+ }
156
+
157
+ /** Show one language, or `""` for all of them. A setter, so "" is a value and not an absence. */
158
+ export function setLibraryLocale(locale: string): void {
159
+ libraryView.locale = locale;
160
+ bump();
161
+ }
162
+
163
+ /**
164
+ * Drop the scan and the rendered previews.
165
+ *
166
+ * Called by every surface that changes the project's file set — an upload, a creation, a rename, a
167
+ * delete — so the Library is never showing a file that is gone.
168
+ */
169
+ export function invalidateLibrary(): void {
170
+ source = null;
171
+ scanAttempted = false;
172
+ for (const panel of _active.values()) {
173
+ panel.cache.clear();
174
+ panel.slots.clear();
175
+ }
176
+ bump();
177
+ }
178
+
179
+ // ─── Loading ─────────────────────────────────────────────────────────────────
180
+
181
+ /** A scan slower than this earns an Activity row; a fast one must not litter the dock. */
182
+ export const SCAN_ACTIVITY_DELAY_MS = 600;
183
+
184
+ /** Deduplication key for the scan Problem, so a failing watcher is one row and not sixty. */
185
+ const SCAN_PROBLEM_KEY = "library.scan";
186
+
187
+ /**
188
+ * Run the scan and report it honestly.
189
+ *
190
+ * No Cancel: `platform.listDirectory` has no abort, so a Cancel button here could only stop the
191
+ * WAITING, not the work — and a button that does not do what it says is the failure the Activity
192
+ * contract exists to end. The row is informational, and it only appears when the scan is slow
193
+ * enough that its absence would look like a hang.
194
+ */
195
+ async function loadLibrary(): Promise<void> {
196
+ const current = librarySource();
197
+ scanAttempted = true;
198
+ libraryView.loading = true;
199
+ bump();
200
+
201
+ const slow: { handle: ActivityHandle | null } = { handle: null };
202
+ const timer = setTimeout(() => {
203
+ slow.handle = beginActivity({
204
+ source: "Library",
205
+ status: "Reading the project's directories…",
206
+ title: "Scan project files",
207
+ });
208
+ }, SCAN_ACTIVITY_DELAY_MS);
209
+
210
+ try {
211
+ await current.rows();
212
+ const failures = current.failures();
213
+ if (failures.length > 0) {
214
+ const detail = failures.map((f) => `${f.dir} — ${f.error}`).join("\n");
215
+ const summary = `Could not read ${failures.length} project ${
216
+ failures.length === 1 ? "directory" : "directories"
217
+ } — this list is incomplete.`;
218
+ // A Problem, not a toast: the list on screen is wrong until this is fixed, and the fix is a
219
+ // Command the row can run.
220
+ notify.error(summary, {
221
+ action: "library.refresh",
222
+ detail,
223
+ key: SCAN_PROBLEM_KEY,
224
+ path: failures[0]!.dir,
225
+ source: "Library",
226
+ tier: "problem",
227
+ });
228
+ slow.handle?.setStatus(summary);
229
+ }
230
+ slow.handle?.done(`${current.files().length} file(s)`);
231
+ } catch (error) {
232
+ // `scanLibrary` does not reject, so reaching here means the platform itself is gone.
233
+ const message = "Could not scan the project's files.";
234
+ if (slow.handle) {
235
+ slow.handle.fail(message);
236
+ } else {
237
+ notify.error(message, {
238
+ action: "library.refresh",
239
+ detail: errorMessage(error),
240
+ key: SCAN_PROBLEM_KEY,
241
+ source: "Library",
242
+ tier: "problem",
243
+ });
244
+ }
245
+ } finally {
246
+ clearTimeout(timer);
247
+ libraryView.loading = false;
248
+ bump();
249
+ }
250
+ }
251
+
252
+ /** Re-scan from scratch — the Retry behind the Problem, and the toolbar's Refresh. */
253
+ export async function refreshLibrary(): Promise<void> {
254
+ source = null;
255
+ scanAttempted = false;
256
+ for (const panel of _active.values()) {
257
+ panel.cache.clear();
258
+ }
259
+ await loadLibrary();
260
+ }
261
+
262
+ // ─── The mounted pane ────────────────────────────────────────────────────────
263
+
264
+ interface ActiveLibraryPane {
265
+ /** The pane whose stage this Library is drawn on. The map key, held on the record too. */
266
+ paneId: string;
267
+ tabId: string;
268
+ scope: EffectScope;
269
+ wrap: HTMLElement;
270
+ cache: PreviewCache;
271
+ observer: PreviewObserver;
272
+ /** The scroller, once lit has created it. */
273
+ scroller: HTMLElement | null;
274
+ /** Slot elements awaiting a preview, keyed by the element itself. */
275
+ pending: WeakMap<Element, LibraryFile>;
276
+ /** The live preview slot for each path in the CURRENT window, so a resolved render finds it. */
277
+ slots: Map<string, Element>;
278
+ /**
279
+ * Paths whose preview is being built right now.
280
+ *
281
+ * Without this a repaint mid-load asks for the same document again: lit hands a fresh `ref`
282
+ * closure on every render, so the slot is re-registered before the first read has resolved, and a
283
+ * scroll through a long list would read each document several times over.
284
+ */
285
+ pendingPaths: Set<string>;
286
+ }
287
+
288
+ /**
289
+ * The Library mounted in each pane, keyed by pane id.
290
+ *
291
+ * ONE instance per pane, not per window. It was a module-level `let active`, which is a fact about
292
+ * the shell having had one stage — and the failure it produced the moment two are drawn is not
293
+ * cosmetic: pane B mounting a Library called `detachLibraryPane()`, destroying pane A's
294
+ * `IntersectionObserver`, preview cache and effect scope while pane A's DOM was still on screen.
295
+ * `canvas-render.ts`'s `resetCanvasView` calls the detach UNCONDITIONALLY on every empty pane, so
296
+ * it did not even take a second Library to do it.
297
+ */
298
+ const _active = new Map<string, ActiveLibraryPane>();
299
+
300
+ /** The Library mounted in a pane, or null. */
301
+ function activeIn(paneId: string): ActiveLibraryPane | null {
302
+ return _active.get(paneId) ?? null;
303
+ }
304
+
305
+ /** Whether the Library is live in this pane for this tab — the canvas-render fast-path guard. */
306
+ export function libraryPaneMounted(paneId: string, tab: Tab): boolean {
307
+ const panel = activeIn(paneId);
308
+ return panel !== null && panel.tabId === tab.id && panel.wrap.isConnected;
309
+ }
310
+
311
+ /** Tear one pane's Library down (mode change, tab switch, project close). Idempotent. */
312
+ export function detachLibraryPane(paneId: string): void {
313
+ const panel = _active.get(paneId);
314
+ if (!panel) {
315
+ return;
316
+ }
317
+ panel.observer.destroy();
318
+ panel.cache.clear();
319
+ panel.slots.clear();
320
+ panel.pendingPaths.clear();
321
+ panel.scope.stop();
322
+ _active.delete(paneId);
323
+ }
324
+
325
+ // ─── Geometry ────────────────────────────────────────────────────────────────
326
+
327
+ /** The window for the current layout, or the whole list for a layout that does not window. */
328
+ function windowFor(panel: ActiveLibraryPane, layout: LibraryLayout, count: number): WindowRange {
329
+ const metric = LAYOUT_METRICS[layout];
330
+ const { scroller } = panel;
331
+ if (!metric.windowed || !scroller) {
332
+ return { end: count, padBottom: 0, padTop: 0, start: 0, totalRows: count };
333
+ }
334
+ return computeWindow({
335
+ columns: columnsAt(layout, scroller.clientWidth),
336
+ count,
337
+ rowHeight: metric.rowHeight,
338
+ scrollTop: scroller.scrollTop,
339
+ viewportHeight: scroller.clientHeight,
340
+ });
341
+ }
342
+
343
+ // ─── Previews ────────────────────────────────────────────────────────────────
344
+
345
+ /** Build a preview and attach it to whichever slot is currently showing that path. */
346
+ async function fillPreview(panel: ActiveLibraryPane, file: LibraryFile) {
347
+ // Only ever entered once per path at a time: `mountPreview` is the sole caller and refuses to
348
+ // Observe a slot whose path is already in flight.
349
+ panel.pendingPaths.add(file.path);
350
+ try {
351
+ const rendered = await previewFor(file.path, panel.cache);
352
+ if (!rendered || activeIn(panel.paneId) !== panel) {
353
+ return;
354
+ }
355
+ const slot = panel.slots.get(file.path);
356
+ if (slot?.isConnected && !slot.firstElementChild) {
357
+ slot.append(rendered);
358
+ }
359
+ } finally {
360
+ panel.pendingPaths.delete(file.path);
361
+ }
362
+ }
363
+
364
+ /** The `ref` callback every previewable card carries. */
365
+ function mountPreview(panel: ActiveLibraryPane, element: Element | undefined, file: LibraryFile) {
366
+ if (!element) {
367
+ return;
368
+ }
369
+ panel.slots.set(file.path, element);
370
+ // Read the cache BEFORE the "already filled" shortcut, because the read is what marks the entry
371
+ // As recently used. Skipping it let a preview that was on screen — and therefore never touched —
372
+ // Drift to the tail of the LRU, get evicted, get detached, and be re-rendered on the next
373
+ // Repaint: the 300-page measurement showed 560 reads for 300 documents until this line moved.
374
+ const cached = panel.cache.get(file.path);
375
+ if (element.firstElementChild) {
376
+ return;
377
+ }
378
+ if (cached) {
379
+ element.append(cached);
380
+ return;
381
+ }
382
+ if (panel.pendingPaths.has(file.path)) {
383
+ return;
384
+ }
385
+ panel.pending.set(element, file);
386
+ panel.observer.observe(element);
387
+ }
388
+
389
+ // ─── Context menu ────────────────────────────────────────────────────────────
390
+
391
+ let contextHandle: ReturnType<typeof renderPopover> | null = null;
392
+
393
+ function dismissLibraryContextMenu() {
394
+ contextHandle?.dismiss();
395
+ contextHandle = null;
396
+ }
397
+
398
+ interface MenuItem {
399
+ label: string;
400
+ action?: () => void | Promise<void>;
401
+ danger?: boolean;
402
+ }
403
+
404
+ function showLibraryContextMenu(event: MouseEvent, file: LibraryFile) {
405
+ event.preventDefault();
406
+ event.stopPropagation();
407
+ dismissLibraryContextMenu();
408
+
409
+ const items: MenuItem[] = [
410
+ {
411
+ action: () => {
412
+ void openFileInTab(file.path);
413
+ },
414
+ label: "Open",
415
+ },
416
+ { label: "—" },
417
+ { action: () => renameLibraryFile(file), label: "Rename…" },
418
+ { action: () => duplicateLibraryFile(file), label: "Duplicate" },
419
+ { label: "—" },
420
+ { action: () => deleteLibraryFile(file), danger: true, label: "Delete" },
421
+ ];
422
+
423
+ let x = event.clientX;
424
+ let y = event.clientY;
425
+
426
+ contextHandle = renderPopover(
427
+ html`<sp-popover
428
+ open
429
+ style="position:fixed;left:${x}px;top:${y}px"
430
+ ${ref((el) => {
431
+ if (!el) {
432
+ return;
433
+ }
434
+ requestAnimationFrame(() => {
435
+ const popover = el as HTMLElement;
436
+ const menuRect = rectOf(popover);
437
+ if (x + menuRect.width > window.innerWidth) {
438
+ x = window.innerWidth - menuRect.width - 4;
439
+ }
440
+ if (y + menuRect.height > window.innerHeight) {
441
+ y = window.innerHeight - menuRect.height - 4;
442
+ }
443
+ popover.style.left = `${x}px`;
444
+ popover.style.top = `${y}px`;
445
+ });
446
+ })}
447
+ >
448
+ <sp-menu>
449
+ ${items.map((item) =>
450
+ item.label === "—"
451
+ ? html`<sp-menu-divider></sp-menu-divider>`
452
+ : html`<sp-menu-item
453
+ style=${item.danger ? "color: var(--danger)" : ""}
454
+ @click=${() => {
455
+ dismissLibraryContextMenu();
456
+ void item.action?.();
457
+ }}
458
+ >${item.label}</sp-menu-item
459
+ >`,
460
+ )}
461
+ </sp-menu>
462
+ </sp-popover>`,
463
+ {
464
+ dismissOnOutsideClick: true,
465
+ layer: "dialog",
466
+ onDismiss: () => {
467
+ contextHandle = null;
468
+ },
469
+ },
470
+ );
471
+ }
472
+
473
+ /**
474
+ * The Library's rename dialog — the same copy, from the same helper, as the Files panel's.
475
+ *
476
+ * A rename here rewrites references exactly as one in the sidebar does, and two dialogs that
477
+ * disagreed about that is the divergence the shared helper exists to prevent.
478
+ */
479
+ async function renameLibraryFile(file: LibraryFile) {
480
+ const message = await renamePromptMessage(file.path);
481
+ const newName = await showPromptDialog("Rename", {
482
+ confirmLabel: "Rename",
483
+ ...(message === undefined ? {} : { message }),
484
+ select: "stem",
485
+ validate: (value) => (value.trim() ? "" : "Enter a file name."),
486
+ value: file.name,
487
+ });
488
+ if (!newName || newName === file.name) {
489
+ return;
490
+ }
491
+ const normalized = file.path.replaceAll("\\", "/");
492
+ const parent = normalized.includes("/") ? normalized.slice(0, normalized.lastIndexOf("/")) : ".";
493
+ const newPath = parent === "." ? newName : `${parent}/${newName}`;
494
+ try {
495
+ await getPlatform().renameFile(file.path, newPath);
496
+ invalidateUsages();
497
+ invalidateLibrary();
498
+ await loadLibrary();
499
+ notify.success(`Renamed to ${newName}`);
500
+ } catch (error) {
501
+ notify.error(`Could not rename ${file.name}.`, {
502
+ detail: errorMessage(error),
503
+ path: file.path,
504
+ source: "Library",
505
+ });
506
+ }
507
+ }
508
+
509
+ async function duplicateLibraryFile(file: LibraryFile) {
510
+ const normalized = file.path.replaceAll("\\", "/");
511
+ const parent = normalized.includes("/") ? normalized.slice(0, normalized.lastIndexOf("/")) : ".";
512
+ const stem = file.name.replace(/(\.[^.]+)$/, "");
513
+ const copyName = `${stem}-copy${file.ext || ""}`;
514
+ const copyPath = parent === "." ? copyName : `${parent}/${copyName}`;
515
+ try {
516
+ const platform = getPlatform();
517
+ const content = await platform.readFile(file.path);
518
+ await platform.writeFile(copyPath, content);
519
+ invalidateLibrary();
520
+ await loadLibrary();
521
+ notify.success(`Duplicated as ${copyName}`);
522
+ } catch (error) {
523
+ notify.error(`Could not duplicate ${file.name}.`, {
524
+ detail: errorMessage(error),
525
+ path: file.path,
526
+ source: "Library",
527
+ });
528
+ }
529
+ }
530
+
531
+ async function deleteLibraryFile(file: LibraryFile) {
532
+ const confirmed = await confirmFileDelete(file);
533
+ if (!confirmed) {
534
+ return;
535
+ }
536
+ try {
537
+ await getPlatform().deleteFile(file.path);
538
+ invalidateUsages();
539
+ invalidateLibrary();
540
+ await loadLibrary();
541
+ notify.success(`Deleted ${file.name}`);
542
+ } catch (error) {
543
+ notify.error(`Could not delete ${file.name}.`, {
544
+ detail: errorMessage(error),
545
+ path: file.path,
546
+ source: "Library",
547
+ });
548
+ }
549
+ }
550
+
551
+ // ─── Creation ────────────────────────────────────────────────────────────────
552
+
553
+ /** What the New menu offers: the three document kinds, plus one row per creatable collection. */
554
+ export interface LibraryNewEntry {
555
+ key: string;
556
+ label: string;
557
+ dir: string;
558
+ /**
559
+ * The content collection this row creates an entry IN, or undefined for a document kind.
560
+ *
561
+ * Present is the whole difference between the two creation paths below — a collection row is
562
+ * `content/`'s job, because only that module knows the collection's extension and its schema.
563
+ */
564
+ collection?: string;
565
+ }
566
+
567
+ /**
568
+ * The new-entity rows.
569
+ *
570
+ * Collections come from `content/entry-model.ts` rather than from `project.json` directly, so the
571
+ * menu offers exactly the collections an entry can be created in. The predecessor read the raw
572
+ * `content` map and derived a directory from the type NAME when `source` was absent, which produced
573
+ * a row for a collection that has no entry directory at all and a row for a single-file (CSV)
574
+ * catalogue, whose entries are rows and not files. Both created a file nothing would ever load.
575
+ */
576
+ export function libraryNewEntries(): LibraryNewEntry[] {
577
+ const rows: LibraryNewEntry[] = [
578
+ { dir: "pages", key: "page", label: "Page" },
579
+ { dir: "layouts", key: "layout", label: "Layout" },
580
+ { dir: "components", key: "component", label: "Component" },
581
+ ];
582
+ for (const collection of entryCollections()) {
583
+ rows.push({
584
+ collection: collection.name,
585
+ dir: collection.dir,
586
+ key: `collection:${collection.name}`,
587
+ label: collection.name.charAt(0).toUpperCase() + collection.name.slice(1),
588
+ });
589
+ }
590
+ return rows;
591
+ }
592
+
593
+ /**
594
+ * Create one entity and open it.
595
+ *
596
+ * Two paths, because a collection entry is not a blank document. A **collection** row goes through
597
+ * `content/entry-commands.ts`'s `createEntry`, which supplies the collection's extension (so the
598
+ * field asks for a display name and the file is actually matched by the collection it was created
599
+ * in) and a body seeded from the collection's **schema defaults** — the entry is valid the moment
600
+ * it exists, rather than a pile of absent required fields — and then opens it in the entry FORM,
601
+ * which is the editor that collection's fields belong to. Both of those are the reason
602
+ * `createEntry` is called rather than reimplemented; a second seeder here would drift from the one
603
+ * the palette's `content.newEntry` uses within a release.
604
+ *
605
+ * A **document kind** keeps `files.ts`'s verbatim name field, deliberately: a page may be a `.md`
606
+ * as easily as a `.json`, so the extension the author types is a real choice and forcing one here
607
+ * would remove it. A collection's extension is not a choice — it is the collection's.
608
+ */
609
+ export async function createLibraryEntry(key: string): Promise<string | null> {
610
+ const entry = libraryNewEntries().find((row) => row.key === key);
611
+ if (!entry) {
612
+ return null;
613
+ }
614
+ const created =
615
+ entry.collection === undefined
616
+ ? await createFileIn({
617
+ dir: entry.dir,
618
+ source: "Library",
619
+ suggestedName: "untitled",
620
+ title: `New ${entry.label}`,
621
+ })
622
+ : await createEntry(entry.collection);
623
+ if (created === null) {
624
+ return null;
625
+ }
626
+ invalidateLibrary();
627
+ await loadLibrary();
628
+ // `createEntry` has already opened the entry form; opening the file again here would replace that
629
+ // Editor with the generic one for the same tab.
630
+ if (entry.collection === undefined) {
631
+ void openFileInTab(created);
632
+ }
633
+ return created;
634
+ }
635
+
636
+ // ─── Upload ──────────────────────────────────────────────────────────────────
637
+
638
+ /**
639
+ * Where an upload lands, and how the author was told.
640
+ *
641
+ * The active category's directory when it has one — printed on the drop zone, so the destination is
642
+ * visible BEFORE the drop. "All" has none, and rather than falling back to a default nobody chose,
643
+ * it asks. That fallback is exactly the surprise §7 names: the file arrives, the toast says it
644
+ * worked, and it is in a directory the author never picked.
645
+ */
646
+ export async function resolveUploadDir(): Promise<string | null> {
647
+ const named = uploadDirForCategory(libraryView.category);
648
+ if (named !== undefined) {
649
+ return named;
650
+ }
651
+ const chosen = await showPromptDialog("Upload files", {
652
+ confirmLabel: "Upload",
653
+ message: "The All view has no folder of its own. Choose where these files land.",
654
+ placeholder: "public",
655
+ validate: (value) => (value.trim() ? "" : "Enter a folder."),
656
+ value: "public",
657
+ });
658
+ return chosen === null ? null : chosen.trim().replace(/\/$/, "");
659
+ }
660
+
661
+ async function uploadIntoLibrary(files: FileList | File[]) {
662
+ const dir = await resolveUploadDir();
663
+ if (dir === null) {
664
+ return;
665
+ }
666
+ await uploadAssets([...files], { dir });
667
+ invalidateLibrary();
668
+ await loadLibrary();
669
+ }
670
+
671
+ // ─── Templates ───────────────────────────────────────────────────────────────
672
+
673
+ /**
674
+ * The language facet, drawn only where it can change the answer.
675
+ *
676
+ * Its options come from ALL scanned files, never from the filtered ones: a picker whose choices
677
+ * collapse to the choice just made cannot be used to make another. One locale — or none — means
678
+ * every file already agrees, and a permanently-selected chip is chrome that says nothing.
679
+ */
680
+ function localeFilterTpl(files: readonly LibraryFile[]) {
681
+ const locales = libraryLocales(files);
682
+ if (locales.length < 2) {
683
+ return nothing;
684
+ }
685
+ const value = libraryView.locale === "" ? "all" : libraryView.locale;
686
+ return html`
687
+ <sp-picker
688
+ size="s"
689
+ quiet
690
+ class="library-locale-filter"
691
+ label="All languages"
692
+ .value=${value}
693
+ @change=${(e: Event) => {
694
+ const chosen = (e.target as HTMLElement & { value: string }).value;
695
+ setLibraryLocale(chosen === "all" ? "" : chosen);
696
+ }}
697
+ >
698
+ <sp-menu-item value="all">All languages</sp-menu-item>
699
+ ${locales.map((tag) => html`<sp-menu-item value=${tag}>${localeLabel(tag)}</sp-menu-item>`)}
700
+ </sp-picker>
701
+ `;
702
+ }
703
+
704
+ function toolbarTpl(panel: ActiveLibraryPane, files: readonly LibraryFile[]) {
705
+ const destination = uploadDirForCategory(libraryView.category);
706
+ return html`
707
+ <div class="library-toolbar">
708
+ <sp-action-group selects="single" size="s" compact>
709
+ ${LIBRARY_CATEGORIES.map(
710
+ (category) => html`
711
+ <sp-action-button
712
+ size="s"
713
+ ?selected=${libraryView.category === category.key}
714
+ @click=${() => setLibraryCategory(category.key)}
715
+ >
716
+ ${category.label}
717
+ </sp-action-button>
718
+ `,
719
+ )}
720
+ </sp-action-group>
721
+ ${localeFilterTpl(files)}
722
+ <sp-search
723
+ size="s"
724
+ placeholder="Filter files…"
725
+ .value=${libraryView.query}
726
+ @input=${(e: Event) => setLibrarySearch((e.target as HTMLInputElement).value)}
727
+ @submit=${(e: Event) => e.preventDefault()}
728
+ ></sp-search>
729
+ <overlay-trigger placement="bottom-start" triggered-by="click">
730
+ <sp-action-button size="s" slot="trigger">
731
+ <sp-icon-add slot="icon"></sp-icon-add> New
732
+ </sp-action-button>
733
+ <sp-popover slot="click-content" tip>
734
+ <sp-menu
735
+ @change=${(e: Event) => {
736
+ void createLibraryEntry((e.target as HTMLSelectElement).value);
737
+ }}
738
+ >
739
+ ${libraryNewEntries().map(
740
+ (entry) =>
741
+ html`<sp-menu-item value=${entry.key}
742
+ >${entry.label} — ${entry.dir}/</sp-menu-item
743
+ >`,
744
+ )}
745
+ </sp-menu>
746
+ </sp-popover>
747
+ </overlay-trigger>
748
+ <sp-action-button
749
+ size="s"
750
+ title=${destination === undefined ? "Upload — asks for a folder" : `Upload into ${destination}/`}
751
+ @click=${() => {
752
+ const input = panel.wrap.querySelector(
753
+ ".library-upload-input",
754
+ ) as HTMLInputElement | null;
755
+ input?.click();
756
+ }}
757
+ >
758
+ <sp-icon-upload slot="icon"></sp-icon-upload> Upload
759
+ </sp-action-button>
760
+ <input
761
+ type="file"
762
+ multiple
763
+ accept=${UPLOAD_ACCEPT}
764
+ class="library-upload-input"
765
+ @change=${(e: Event) => {
766
+ const input = e.target as HTMLInputElement;
767
+ if (input.files?.length) {
768
+ void uploadIntoLibrary(input.files);
769
+ }
770
+ input.value = "";
771
+ }}
772
+ />
773
+ <span class="library-spacer"></span>
774
+ <sp-action-group selects="single" size="s" compact class="library-layout-switch">
775
+ ${LIBRARY_LAYOUTS.map(
776
+ (layout) => html`
777
+ <sp-action-button
778
+ size="s"
779
+ ?selected=${libraryView.layout === layout}
780
+ title=${LIBRARY_LAYOUT_LABELS[layout]}
781
+ @click=${() => setLibraryLayout(layout)}
782
+ >
783
+ ${LIBRARY_LAYOUT_LABELS[layout]}
784
+ </sp-action-button>
785
+ `,
786
+ )}
787
+ </sp-action-group>
788
+ </div>
789
+ `;
790
+ }
791
+
792
+ /**
793
+ * The banner an INCOMPLETE listing carries.
794
+ *
795
+ * The Problem is the durable record; this is the same fact on the surface it is about, because a
796
+ * reader looking at a short list needs to know it is short for a reason without going to the dock.
797
+ */
798
+ function failureBannerTpl(current: LibrarySource) {
799
+ const failures = current.failures();
800
+ if (failures.length === 0) {
801
+ return nothing;
802
+ }
803
+ return html`<div class="library-banner" role="status">
804
+ <span>
805
+ This list is incomplete — ${failures.length}
806
+ ${failures.length === 1 ? "directory" : "directories"} could not be read
807
+ (${failures[0]!.dir}${failures.length > 1 ? ", …" : ""}).
808
+ </span>
809
+ <sp-button
810
+ size="s"
811
+ variant="secondary"
812
+ @click=${() => {
813
+ void refreshLibrary();
814
+ }}
815
+ >Retry</sp-button
816
+ >
817
+ </div>`;
818
+ }
819
+
820
+ /**
821
+ * The empty state, which is FOUR states and not one.
822
+ *
823
+ * The view this replaces printed "No files found" for every one of them, including the one where
824
+ * the request had failed.
825
+ */
826
+ function emptyTpl(current: LibrarySource, total: number) {
827
+ if (libraryView.loading || !current.scanned()) {
828
+ return html`<div class="library-empty">Scanning the project…</div>`;
829
+ }
830
+ if (current.failures().length > 0) {
831
+ return html`<div class="library-empty">
832
+ <p>Nothing to show, and the scan did not finish.</p>
833
+ <p class="library-empty-detail">
834
+ ${current.failures().length}
835
+ ${current.failures().length === 1 ? "directory" : "directories"} could not be read, so this
836
+ is not the same as an empty project.
837
+ </p>
838
+ <sp-button
839
+ size="s"
840
+ variant="accent"
841
+ @click=${() => {
842
+ void refreshLibrary();
843
+ }}
844
+ >Retry</sp-button
845
+ >
846
+ </div>`;
847
+ }
848
+ if (total === 0) {
849
+ return html`<div class="library-empty">
850
+ <p>This project has no files yet.</p>
851
+ <p class="library-empty-detail">Use New to create a page, layout, component or entry.</p>
852
+ </div>`;
853
+ }
854
+ const category = libraryCategory(libraryView.category);
855
+ // Both facets in one clause, so a reader who filtered twice is told about both rather than being
856
+ // Sent to clear one and find the list still empty.
857
+ const scopes = [
858
+ category && category.key !== "all" ? category.label : "",
859
+ libraryView.locale === "" ? "" : localeLabel(libraryView.locale),
860
+ ].filter((scope) => scope !== "");
861
+ const where = scopes.length === 0 ? "" : ` in ${scopes.join(" and ")}`;
862
+ const term = libraryView.query.trim();
863
+ return html`<div class="library-empty">
864
+ <p>
865
+ No files match${term ? html` “${term}”` : nothing}${where}.
866
+ <span class="library-empty-detail">${total} file(s) in the project.</span>
867
+ </p>
868
+ <sp-button
869
+ size="s"
870
+ variant="secondary"
871
+ @click=${() => {
872
+ setLibrarySearch("");
873
+ setLibraryCategory("all");
874
+ setLibraryLocale("");
875
+ }}
876
+ >
877
+ Clear filters
878
+ </sp-button>
879
+ </div>`;
880
+ }
881
+
882
+ /** The body for one layout, windowed where the layout windows. */
883
+ function bodyTpl(panel: ActiveLibraryPane, files: readonly LibraryFile[], ctx: LayoutContext) {
884
+ const { layout } = libraryView;
885
+ const range = windowFor(panel, layout, files.length);
886
+ const slice = files.slice(range.start, range.end);
887
+
888
+ switch (layout) {
889
+ case "board": {
890
+ return boardTpl(files, ctx);
891
+ }
892
+ case "calendar": {
893
+ return calendarTpl(files, ctx);
894
+ }
895
+ case "media": {
896
+ return html`<div
897
+ class="library-grid library-grid-media"
898
+ style="padding-top:${range.padTop}px;padding-bottom:${range.padBottom}px"
899
+ >
900
+ ${mediaTpl(slice, ctx)}
901
+ </div>`;
902
+ }
903
+ case "table": {
904
+ return html`<div class="library-table" role="table">
905
+ ${tableHeadTpl(ctx.columns)}
906
+ <div style="height:${range.padTop}px"></div>
907
+ ${tableRowsTpl(slice, ctx)}
908
+ <div style="height:${range.padBottom}px"></div>
909
+ </div>`;
910
+ }
911
+ default: {
912
+ return html`<div
913
+ class="library-grid library-grid-cards"
914
+ style="padding-top:${range.padTop}px;padding-bottom:${range.padBottom}px"
915
+ >
916
+ ${cardsTpl(slice, ctx)}
917
+ </div>`;
918
+ }
919
+ }
920
+ }
921
+
922
+ // ─── Mount ───────────────────────────────────────────────────────────────────
923
+
924
+ /**
925
+ * Draw the Library into the pane. Re-entrant: a same-tab call while it is live is a no-op, because
926
+ * the pane owns its own reactivity from here (the grid/settings/stylebook pattern).
927
+ */
928
+ export function renderLibraryMode(surface: CanvasSurface, tab: Tab): void {
929
+ const { paneId, wrap: canvasWrap } = surface;
930
+ if (libraryPaneMounted(paneId, tab)) {
931
+ return;
932
+ }
933
+ detachLibraryPane(paneId);
934
+
935
+ const scope = effectScope();
936
+ const panel: ActiveLibraryPane = {
937
+ cache: createPreviewCache(),
938
+ observer: createPreviewObserver(() => {
939
+ /* Replaced immediately below — the observer needs the panel to exist first. */
940
+ }),
941
+ paneId,
942
+ pending: new WeakMap(),
943
+ pendingPaths: new Set(),
944
+ scope,
945
+ scroller: null,
946
+ slots: new Map(),
947
+ tabId: tab.id,
948
+ wrap: canvasWrap,
949
+ };
950
+ panel.observer.destroy();
951
+ panel.observer = createPreviewObserver((element) => {
952
+ const file = panel.pending.get(element);
953
+ if (file) {
954
+ panel.pending.delete(element);
955
+ void fillPreview(panel, file);
956
+ }
957
+ });
958
+ _active.set(paneId, panel);
959
+
960
+ const ctx: LayoutContext = {
961
+ columns: libraryColumns(),
962
+ contextMenu: (event, file) => showLibraryContextMenu(event, file),
963
+ mountPreview: (element, file) => {
964
+ if (PREVIEW_LAYOUTS.has(libraryView.layout)) {
965
+ mountPreview(panel, element, file);
966
+ }
967
+ },
968
+ openFile: (path) => {
969
+ void openFileInTab(path);
970
+ },
971
+ };
972
+
973
+ const onScroller = (element: Element | undefined) => {
974
+ const next = (element as HTMLElement | undefined) ?? null;
975
+ if (next === panel.scroller) {
976
+ return;
977
+ }
978
+ panel.scroller = next;
979
+ next?.addEventListener("scroll", () => bump(), { passive: true });
980
+ };
981
+
982
+ scope.run(() => {
983
+ effect(() => {
984
+ if (activeIn(paneId) !== panel) {
985
+ return;
986
+ }
987
+ // Everything the pane draws from.
988
+ void libraryView.revision;
989
+ void libraryView.category;
990
+ void libraryView.layout;
991
+ void libraryView.query;
992
+ void libraryView.locale;
993
+ void libraryView.loading;
994
+
995
+ const current = librarySource();
996
+ const files = filterLibrary(current.files(), {
997
+ category: libraryView.category,
998
+ locale: libraryView.locale,
999
+ query: libraryView.query,
1000
+ });
1001
+ const total = current.files().length;
1002
+
1003
+ // The slot table describes the CURRENT window only; the `ref` callbacks below refill it as
1004
+ // Lit commits, and a stale entry would hand a resolved preview to a detached node.
1005
+ panel.slots.clear();
1006
+
1007
+ litRender(
1008
+ html`
1009
+ <div class="library" data-jx-region=${paneRegion(paneId, "library")}>
1010
+ ${toolbarTpl(panel, current.files())} ${failureBannerTpl(current)}
1011
+ <div
1012
+ class="library-body"
1013
+ data-jx-region=${paneRegion(paneId, "library/dropZone")}
1014
+ ${ref(onScroller)}
1015
+ @dragover=${(e: DragEvent) => {
1016
+ e.preventDefault();
1017
+ (e.currentTarget as HTMLElement).classList.add("library-drop-active");
1018
+ }}
1019
+ @dragleave=${(e: DragEvent) => {
1020
+ (e.currentTarget as HTMLElement).classList.remove("library-drop-active");
1021
+ }}
1022
+ @drop=${(e: DragEvent) => {
1023
+ e.preventDefault();
1024
+ (e.currentTarget as HTMLElement).classList.remove("library-drop-active");
1025
+ const dropped = e.dataTransfer?.files;
1026
+ if (dropped?.length) {
1027
+ void uploadIntoLibrary(dropped);
1028
+ }
1029
+ }}
1030
+ >
1031
+ ${files.length === 0 ? emptyTpl(current, total) : bodyTpl(panel, files, ctx)}
1032
+ </div>
1033
+ </div>
1034
+ `,
1035
+ canvasWrap,
1036
+ );
1037
+
1038
+ // Lit has committed, so every card outside the new window is now detached. Hand those
1039
+ // Observations back HERE and nowhere else: this is the only moment the pane knows the window
1040
+ // Has moved, and the intersect callback cannot be the only release path — a card flicked past
1041
+ // Before it ever intersected is never reported, so it would be watched until the tab closed.
1042
+ panel.observer.releaseDetached();
1043
+
1044
+ if (!scanAttempted && !libraryView.loading) {
1045
+ void loadLibrary();
1046
+ }
1047
+ });
1048
+ });
1049
+ }