@aphexcms/cms-core 0.2.3 → 2.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 (617) hide show
  1. package/dist/api/api-keys.d.ts +37 -0
  2. package/dist/api/api-keys.d.ts.map +1 -0
  3. package/dist/api/api-keys.js +20 -0
  4. package/dist/api/assets.d.ts +27 -0
  5. package/dist/api/assets.d.ts.map +1 -1
  6. package/dist/api/assets.js +22 -1
  7. package/dist/api/index.d.ts +8 -0
  8. package/dist/api/index.d.ts.map +1 -1
  9. package/dist/api/index.js +4 -0
  10. package/dist/api/instance.d.ts +17 -0
  11. package/dist/api/instance.d.ts.map +1 -0
  12. package/dist/api/instance.js +21 -0
  13. package/dist/api/invitations.d.ts +37 -0
  14. package/dist/api/invitations.d.ts.map +1 -0
  15. package/dist/api/invitations.js +27 -0
  16. package/dist/api/organizations.d.ts +7 -0
  17. package/dist/api/organizations.d.ts.map +1 -1
  18. package/dist/api/organizations.js +7 -0
  19. package/dist/api/types.d.ts +1 -1
  20. package/dist/api/types.d.ts.map +1 -1
  21. package/dist/api/user.d.ts +23 -0
  22. package/dist/api/user.d.ts.map +1 -0
  23. package/dist/api/user.js +20 -0
  24. package/dist/auth/auth-errors.d.ts +1 -1
  25. package/dist/auth/auth-errors.d.ts.map +1 -1
  26. package/dist/auth/auth-hooks.d.ts.map +1 -1
  27. package/dist/auth/auth-hooks.js +39 -23
  28. package/dist/auth/provider.d.ts +2 -2
  29. package/dist/auth/provider.d.ts.map +1 -1
  30. package/dist/cli/generate-types.d.ts +14 -0
  31. package/dist/cli/generate-types.d.ts.map +1 -0
  32. package/dist/cli/generate-types.js +15 -7
  33. package/dist/cli/generate-types.js.map +1 -0
  34. package/dist/cli/index.d.ts +7 -0
  35. package/dist/cli/index.d.ts.map +1 -0
  36. package/dist/cli/index.js +1 -0
  37. package/dist/cli/index.js.map +1 -0
  38. package/dist/client/index.d.ts +1 -0
  39. package/dist/client/index.d.ts.map +1 -1
  40. package/dist/client/index.js +2 -0
  41. package/dist/components/AdminApp.svelte +160 -63
  42. package/dist/components/AdminApp.svelte.d.ts +1 -1
  43. package/dist/components/AdminApp.svelte.d.ts.map +1 -1
  44. package/dist/components/admin/AdminLayout.svelte.d.ts +3 -3
  45. package/dist/components/admin/AssetBrowserModal.svelte +66 -0
  46. package/dist/components/admin/AssetBrowserModal.svelte.d.ts +15 -0
  47. package/dist/components/admin/AssetBrowserModal.svelte.d.ts.map +1 -0
  48. package/dist/components/admin/DocumentEditor.svelte +137 -69
  49. package/dist/components/admin/DocumentEditor.svelte.d.ts +1 -1
  50. package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -1
  51. package/dist/components/admin/DocumentsSkeleton.svelte +40 -0
  52. package/dist/components/admin/DocumentsSkeleton.svelte.d.ts +7 -0
  53. package/dist/components/admin/DocumentsSkeleton.svelte.d.ts.map +1 -0
  54. package/dist/components/admin/MediaBrowser.svelte +1398 -0
  55. package/dist/components/admin/MediaBrowser.svelte.d.ts +23 -0
  56. package/dist/components/admin/MediaBrowser.svelte.d.ts.map +1 -0
  57. package/dist/components/admin/ObjectModal.svelte +3 -4
  58. package/dist/components/admin/ObjectModal.svelte.d.ts +1 -1
  59. package/dist/components/admin/ObjectModal.svelte.d.ts.map +1 -1
  60. package/dist/components/admin/SchemaField.svelte +109 -81
  61. package/dist/components/admin/SchemaField.svelte.d.ts +1 -1
  62. package/dist/components/admin/SchemaField.svelte.d.ts.map +1 -1
  63. package/dist/components/admin/fields/ArrayField.svelte +611 -277
  64. package/dist/components/admin/fields/ArrayField.svelte.d.ts.map +1 -1
  65. package/dist/components/admin/fields/DateField.svelte +3 -2
  66. package/dist/components/admin/fields/DateField.svelte.d.ts.map +1 -1
  67. package/dist/components/admin/fields/DateTimeField.svelte +3 -2
  68. package/dist/components/admin/fields/DateTimeField.svelte.d.ts.map +1 -1
  69. package/dist/components/admin/fields/ImageField.svelte +217 -120
  70. package/dist/components/admin/fields/ImageField.svelte.d.ts +1 -0
  71. package/dist/components/admin/fields/ImageField.svelte.d.ts.map +1 -1
  72. package/dist/components/admin/fields/ReferenceField.svelte +11 -6
  73. package/dist/components/admin/fields/ReferenceField.svelte.d.ts.map +1 -1
  74. package/dist/components/admin/fields/StringField.svelte +2 -1
  75. package/dist/components/admin/fields/StringField.svelte.d.ts.map +1 -1
  76. package/dist/components/index.d.ts +2 -0
  77. package/dist/components/index.d.ts.map +1 -1
  78. package/dist/components/index.js +2 -0
  79. package/dist/components/layout/OrganizationSwitcher.svelte +109 -45
  80. package/dist/components/layout/OrganizationSwitcher.svelte.d.ts.map +1 -1
  81. package/dist/components/layout/Sidebar.svelte +36 -14
  82. package/dist/components/layout/Sidebar.svelte.d.ts +2 -1
  83. package/dist/components/layout/Sidebar.svelte.d.ts.map +1 -1
  84. package/dist/components/layout/sidebar/AppSidebar.svelte +1 -1
  85. package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts +8 -1
  86. package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts.map +1 -1
  87. package/dist/components/layout/sidebar/NavMain.svelte +1 -1
  88. package/dist/components/layout/sidebar/NavMain.svelte.d.ts +1 -1
  89. package/dist/components/layout/sidebar/NavMain.svelte.d.ts.map +1 -1
  90. package/dist/components/layout/sidebar/NavSecondary.svelte +3 -3
  91. package/dist/components/layout/sidebar/NavUser.svelte +22 -10
  92. package/dist/components/layout/sidebar/NavUser.svelte.d.ts +2 -2
  93. package/dist/components/layout/sidebar/NavUser.svelte.d.ts.map +1 -1
  94. package/dist/db/interfaces/document.d.ts +20 -0
  95. package/dist/db/interfaces/document.d.ts.map +1 -1
  96. package/dist/db/interfaces/index.d.ts +3 -1
  97. package/dist/db/interfaces/index.d.ts.map +1 -1
  98. package/dist/db/interfaces/instance.d.ts +7 -0
  99. package/dist/db/interfaces/instance.d.ts.map +1 -0
  100. package/dist/db/interfaces/instance.js +1 -0
  101. package/dist/db/interfaces/organization.d.ts +1 -0
  102. package/dist/db/interfaces/organization.d.ts.map +1 -1
  103. package/dist/engine.d.ts.map +1 -1
  104. package/dist/engine.js +4 -3
  105. package/dist/field-validation/date-utils.d.ts.map +1 -1
  106. package/dist/field-validation/date-utils.js +12 -11
  107. package/dist/field-validation/rule.d.ts.map +1 -1
  108. package/dist/field-validation/rule.js +11 -10
  109. package/dist/field-validation/utils.d.ts.map +1 -1
  110. package/dist/field-validation/utils.js +16 -15
  111. package/dist/graphql/index.d.ts +23 -0
  112. package/dist/graphql/index.d.ts.map +1 -0
  113. package/dist/graphql/index.js +85 -0
  114. package/dist/graphql/resolvers.d.ts +4 -0
  115. package/dist/graphql/resolvers.d.ts.map +1 -0
  116. package/dist/graphql/resolvers.js +542 -0
  117. package/dist/graphql/schema.d.ts +3 -0
  118. package/dist/graphql/schema.d.ts.map +1 -0
  119. package/dist/graphql/schema.js +356 -0
  120. package/dist/hooks.d.ts +2 -0
  121. package/dist/hooks.d.ts.map +1 -1
  122. package/dist/hooks.js +62 -9
  123. package/dist/lib/api/api-keys.d.ts +37 -0
  124. package/dist/lib/api/api-keys.d.ts.map +1 -0
  125. package/dist/lib/api/api-keys.js +21 -0
  126. package/dist/lib/api/api-keys.js.map +1 -0
  127. package/dist/lib/api/assets.d.ts +75 -0
  128. package/dist/lib/api/assets.d.ts.map +1 -0
  129. package/dist/lib/api/assets.js +74 -0
  130. package/dist/lib/api/assets.js.map +1 -0
  131. package/dist/lib/api/client.d.ts +37 -0
  132. package/dist/lib/api/client.d.ts.map +1 -0
  133. package/dist/lib/api/client.js +132 -0
  134. package/dist/lib/api/client.js.map +1 -0
  135. package/dist/lib/api/documents.d.ts +57 -0
  136. package/dist/lib/api/documents.d.ts.map +1 -0
  137. package/dist/lib/api/documents.js +86 -0
  138. package/dist/lib/api/documents.js.map +1 -0
  139. package/dist/lib/api/index.d.ts +15 -0
  140. package/dist/lib/api/index.d.ts.map +1 -0
  141. package/dist/lib/api/index.js +10 -0
  142. package/dist/lib/api/index.js.map +1 -0
  143. package/dist/lib/api/instance.d.ts +17 -0
  144. package/dist/lib/api/instance.d.ts.map +1 -0
  145. package/dist/lib/api/instance.js +22 -0
  146. package/dist/lib/api/instance.js.map +1 -0
  147. package/dist/lib/api/invitations.d.ts +37 -0
  148. package/dist/lib/api/invitations.d.ts.map +1 -0
  149. package/dist/lib/api/invitations.js +28 -0
  150. package/dist/lib/api/invitations.js.map +1 -0
  151. package/dist/lib/api/organizations.d.ts +108 -0
  152. package/dist/lib/api/organizations.d.ts.map +1 -0
  153. package/dist/lib/api/organizations.js +100 -0
  154. package/dist/lib/api/organizations.js.map +1 -0
  155. package/dist/lib/api/types.d.ts +47 -0
  156. package/dist/lib/api/types.d.ts.map +1 -0
  157. package/dist/lib/api/types.js +2 -0
  158. package/dist/lib/api/types.js.map +1 -0
  159. package/dist/lib/api/user.d.ts +23 -0
  160. package/dist/lib/api/user.d.ts.map +1 -0
  161. package/dist/lib/api/user.js +21 -0
  162. package/dist/lib/api/user.js.map +1 -0
  163. package/dist/lib/auth/auth-errors.d.ts +7 -0
  164. package/dist/lib/auth/auth-errors.d.ts.map +1 -0
  165. package/dist/lib/auth/auth-errors.js +14 -0
  166. package/dist/lib/auth/auth-errors.js.map +1 -0
  167. package/dist/lib/auth/auth-hooks.d.ts +6 -0
  168. package/dist/lib/auth/auth-hooks.d.ts.map +1 -0
  169. package/dist/lib/auth/auth-hooks.js +139 -0
  170. package/dist/lib/auth/auth-hooks.js.map +1 -0
  171. package/dist/lib/auth/provider.d.ts +17 -0
  172. package/dist/lib/auth/provider.d.ts.map +1 -0
  173. package/dist/lib/auth/provider.js +1 -0
  174. package/dist/lib/auth/provider.js.map +1 -0
  175. package/dist/lib/client/index.d.ts +24 -0
  176. package/dist/lib/client/index.d.ts.map +1 -0
  177. package/dist/lib/client/index.js +33 -0
  178. package/dist/lib/client/index.js.map +1 -0
  179. package/dist/lib/components/fields/index.d.ts +9 -0
  180. package/dist/lib/components/fields/index.d.ts.map +1 -0
  181. package/dist/lib/components/fields/index.js +10 -0
  182. package/dist/lib/components/fields/index.js.map +1 -0
  183. package/dist/lib/components/index.d.ts +8 -0
  184. package/dist/lib/components/index.d.ts.map +1 -0
  185. package/dist/lib/components/index.js +14 -0
  186. package/dist/lib/components/index.js.map +1 -0
  187. package/dist/lib/config.d.ts +3 -0
  188. package/dist/lib/config.d.ts.map +1 -0
  189. package/dist/lib/config.js +16 -0
  190. package/dist/lib/config.js.map +1 -0
  191. package/dist/lib/db/adapters/index.d.ts +1 -0
  192. package/dist/lib/db/adapters/index.d.ts.map +1 -0
  193. package/dist/lib/db/adapters/index.js +5 -0
  194. package/dist/lib/db/adapters/index.js.map +1 -0
  195. package/dist/lib/db/index.d.ts +2 -0
  196. package/dist/lib/db/index.d.ts.map +1 -0
  197. package/dist/lib/db/index.js +1 -0
  198. package/dist/lib/db/index.js.map +1 -0
  199. package/dist/lib/db/interfaces/asset.d.ts +73 -0
  200. package/dist/lib/db/interfaces/asset.d.ts.map +1 -0
  201. package/dist/lib/db/interfaces/asset.js +1 -0
  202. package/dist/lib/db/interfaces/asset.js.map +1 -0
  203. package/dist/lib/db/interfaces/document.d.ts +79 -0
  204. package/dist/lib/db/interfaces/document.d.ts.map +1 -0
  205. package/dist/lib/db/interfaces/document.js +1 -0
  206. package/dist/lib/db/interfaces/document.js.map +1 -0
  207. package/dist/lib/db/interfaces/index.d.ts +76 -0
  208. package/dist/lib/db/interfaces/index.d.ts.map +1 -0
  209. package/dist/lib/db/interfaces/index.js +1 -0
  210. package/dist/lib/db/interfaces/index.js.map +1 -0
  211. package/dist/lib/db/interfaces/instance.d.ts +7 -0
  212. package/dist/lib/db/interfaces/instance.d.ts.map +1 -0
  213. package/dist/lib/db/interfaces/instance.js +2 -0
  214. package/dist/lib/db/interfaces/instance.js.map +1 -0
  215. package/dist/lib/db/interfaces/organization.d.ts +28 -0
  216. package/dist/lib/db/interfaces/organization.d.ts.map +1 -0
  217. package/dist/lib/db/interfaces/organization.js +1 -0
  218. package/dist/lib/db/interfaces/organization.js.map +1 -0
  219. package/dist/lib/db/interfaces/schema.d.ts +21 -0
  220. package/dist/lib/db/interfaces/schema.d.ts.map +1 -0
  221. package/dist/lib/db/interfaces/schema.js +1 -0
  222. package/dist/lib/db/interfaces/schema.js.map +1 -0
  223. package/dist/lib/db/interfaces/user.d.ts +17 -0
  224. package/dist/lib/db/interfaces/user.d.ts.map +1 -0
  225. package/dist/lib/db/interfaces/user.js +1 -0
  226. package/dist/lib/db/interfaces/user.js.map +1 -0
  227. package/dist/lib/db/utils/reference-resolver.d.ts +18 -0
  228. package/dist/lib/db/utils/reference-resolver.d.ts.map +1 -0
  229. package/dist/lib/db/utils/reference-resolver.js +81 -0
  230. package/dist/lib/db/utils/reference-resolver.js.map +1 -0
  231. package/dist/lib/define.d.ts +3 -0
  232. package/dist/lib/define.d.ts.map +1 -0
  233. package/dist/lib/define.js +5 -0
  234. package/dist/lib/define.js.map +1 -0
  235. package/dist/lib/email/index.d.ts +2 -0
  236. package/dist/lib/email/index.d.ts.map +1 -0
  237. package/dist/lib/email/index.js +1 -0
  238. package/dist/lib/email/index.js.map +1 -0
  239. package/dist/lib/email/interfaces/email.d.ts +42 -0
  240. package/dist/lib/email/interfaces/email.d.ts.map +1 -0
  241. package/dist/lib/email/interfaces/email.js +1 -0
  242. package/dist/lib/email/interfaces/email.js.map +1 -0
  243. package/dist/lib/engine.d.ts +26 -0
  244. package/dist/lib/engine.d.ts.map +1 -0
  245. package/dist/lib/engine.js +71 -0
  246. package/dist/lib/engine.js.map +1 -0
  247. package/dist/lib/field-validation/date-utils.d.ts +30 -0
  248. package/dist/lib/field-validation/date-utils.d.ts.map +1 -0
  249. package/dist/lib/field-validation/date-utils.js +13 -11
  250. package/dist/lib/field-validation/date-utils.js.map +1 -0
  251. package/dist/lib/field-validation/rule.d.ts +55 -0
  252. package/dist/lib/field-validation/rule.d.ts.map +1 -0
  253. package/dist/lib/field-validation/rule.js +12 -10
  254. package/dist/lib/field-validation/rule.js.map +1 -0
  255. package/dist/lib/field-validation/utils.d.ts +43 -0
  256. package/dist/lib/field-validation/utils.d.ts.map +1 -0
  257. package/dist/lib/field-validation/utils.js +17 -15
  258. package/dist/lib/field-validation/utils.js.map +1 -0
  259. package/dist/lib/graphql/index.d.ts +23 -0
  260. package/dist/lib/graphql/index.d.ts.map +1 -0
  261. package/dist/lib/graphql/index.js +86 -0
  262. package/dist/lib/graphql/index.js.map +1 -0
  263. package/dist/lib/graphql/resolvers.d.ts +4 -0
  264. package/dist/lib/graphql/resolvers.d.ts.map +1 -0
  265. package/dist/lib/graphql/resolvers.js +543 -0
  266. package/dist/lib/graphql/resolvers.js.map +1 -0
  267. package/dist/lib/graphql/schema.d.ts +3 -0
  268. package/dist/lib/graphql/schema.d.ts.map +1 -0
  269. package/dist/lib/graphql/schema.js +357 -0
  270. package/dist/lib/graphql/schema.js.map +1 -0
  271. package/dist/lib/hooks.d.ts +27 -0
  272. package/dist/lib/hooks.d.ts.map +1 -0
  273. package/dist/lib/hooks.js +235 -0
  274. package/dist/lib/hooks.js.map +1 -0
  275. package/dist/lib/index.d.ts +2 -0
  276. package/dist/lib/index.d.ts.map +1 -0
  277. package/dist/lib/index.js +5 -0
  278. package/dist/lib/index.js.map +1 -0
  279. package/dist/lib/is-mobile.svelte.d.ts +5 -0
  280. package/dist/lib/is-mobile.svelte.d.ts.map +1 -0
  281. package/dist/lib/is-mobile.svelte.js +8 -0
  282. package/dist/lib/is-mobile.svelte.js.map +1 -0
  283. package/dist/lib/local-api/auth-helpers.d.ts +65 -0
  284. package/dist/lib/local-api/auth-helpers.d.ts.map +1 -0
  285. package/dist/lib/local-api/auth-helpers.js +103 -0
  286. package/dist/lib/local-api/auth-helpers.js.map +1 -0
  287. package/dist/lib/local-api/collection-api.d.ts +150 -0
  288. package/dist/lib/local-api/collection-api.d.ts.map +1 -0
  289. package/dist/lib/local-api/collection-api.js +311 -0
  290. package/dist/lib/local-api/collection-api.js.map +1 -0
  291. package/dist/lib/local-api/index.d.ts +108 -0
  292. package/dist/lib/local-api/index.d.ts.map +1 -0
  293. package/dist/lib/local-api/index.js +158 -0
  294. package/dist/lib/local-api/index.js.map +1 -0
  295. package/dist/lib/local-api/permissions.d.ts +45 -0
  296. package/dist/lib/local-api/permissions.d.ts.map +1 -0
  297. package/dist/lib/local-api/permissions.js +117 -0
  298. package/dist/lib/local-api/permissions.js.map +1 -0
  299. package/dist/lib/local-api/types.d.ts +65 -0
  300. package/dist/lib/local-api/types.d.ts.map +1 -0
  301. package/dist/lib/local-api/types.js +5 -0
  302. package/dist/lib/local-api/types.js.map +1 -0
  303. package/dist/lib/routes/assets-bulk.d.ts +3 -0
  304. package/dist/lib/routes/assets-bulk.d.ts.map +1 -0
  305. package/dist/lib/routes/assets-bulk.js +49 -0
  306. package/dist/lib/routes/assets-bulk.js.map +1 -0
  307. package/dist/lib/routes/assets-by-id.d.ts +5 -0
  308. package/dist/lib/routes/assets-by-id.d.ts.map +1 -0
  309. package/dist/lib/routes/assets-by-id.js +106 -0
  310. package/dist/lib/routes/assets-by-id.js.map +1 -0
  311. package/dist/lib/routes/assets-cdn.d.ts +3 -0
  312. package/dist/lib/routes/assets-cdn.d.ts.map +1 -0
  313. package/dist/lib/routes/assets-cdn.js +125 -0
  314. package/dist/lib/routes/assets-cdn.js.map +1 -0
  315. package/dist/lib/routes/assets-references-counts.d.ts +7 -0
  316. package/dist/lib/routes/assets-references-counts.d.ts.map +1 -0
  317. package/dist/lib/routes/assets-references-counts.js +32 -0
  318. package/dist/lib/routes/assets-references-counts.js.map +1 -0
  319. package/dist/lib/routes/assets-references.d.ts +7 -0
  320. package/dist/lib/routes/assets-references.d.ts.map +1 -0
  321. package/dist/lib/routes/assets-references.js +35 -0
  322. package/dist/lib/routes/assets-references.js.map +1 -0
  323. package/dist/lib/routes/assets.d.ts +4 -0
  324. package/dist/lib/routes/assets.d.ts.map +1 -0
  325. package/dist/lib/routes/assets.js +121 -0
  326. package/dist/lib/routes/assets.js.map +1 -0
  327. package/dist/lib/routes/documents-by-id.d.ts +5 -0
  328. package/dist/lib/routes/documents-by-id.d.ts.map +1 -0
  329. package/dist/lib/routes/documents-by-id.js +176 -0
  330. package/dist/lib/routes/documents-by-id.js.map +1 -0
  331. package/dist/lib/routes/documents-publish.d.ts +4 -0
  332. package/dist/lib/routes/documents-publish.d.ts.map +1 -0
  333. package/dist/lib/routes/documents-publish.js +138 -0
  334. package/dist/lib/routes/documents-publish.js.map +1 -0
  335. package/dist/lib/routes/documents-query.d.ts +26 -0
  336. package/dist/lib/routes/documents-query.d.ts.map +1 -0
  337. package/dist/lib/routes/documents-query.js +101 -0
  338. package/dist/lib/routes/documents-query.js.map +1 -0
  339. package/dist/lib/routes/documents.d.ts +4 -0
  340. package/dist/lib/routes/documents.d.ts.map +1 -0
  341. package/dist/lib/routes/documents.js +154 -0
  342. package/dist/lib/routes/documents.js.map +1 -0
  343. package/dist/lib/routes/index.d.ts +9 -0
  344. package/dist/lib/routes/index.d.ts.map +1 -0
  345. package/dist/lib/routes/index.js +15 -0
  346. package/dist/lib/routes/index.js.map +1 -0
  347. package/dist/lib/routes/organizations-by-id.d.ts +5 -0
  348. package/dist/lib/routes/organizations-by-id.d.ts.map +1 -0
  349. package/dist/lib/routes/organizations-by-id.js +189 -0
  350. package/dist/lib/routes/organizations-by-id.js.map +1 -0
  351. package/dist/lib/routes/organizations-invitations.d.ts +4 -0
  352. package/dist/lib/routes/organizations-invitations.d.ts.map +1 -0
  353. package/dist/lib/routes/organizations-invitations.js +127 -0
  354. package/dist/lib/routes/organizations-invitations.js.map +1 -0
  355. package/dist/lib/routes/organizations-members.d.ts +5 -0
  356. package/dist/lib/routes/organizations-members.d.ts.map +1 -0
  357. package/dist/lib/routes/organizations-members.js +208 -0
  358. package/dist/lib/routes/organizations-members.js.map +1 -0
  359. package/dist/lib/routes/organizations-switch.d.ts +3 -0
  360. package/dist/lib/routes/organizations-switch.d.ts.map +1 -0
  361. package/dist/lib/routes/organizations-switch.js +55 -0
  362. package/dist/lib/routes/organizations-switch.js.map +1 -0
  363. package/dist/lib/routes/organizations.d.ts +4 -0
  364. package/dist/lib/routes/organizations.d.ts.map +1 -0
  365. package/dist/lib/routes/organizations.js +111 -0
  366. package/dist/lib/routes/organizations.js.map +1 -0
  367. package/dist/lib/routes/schemas-by-type.d.ts +3 -0
  368. package/dist/lib/routes/schemas-by-type.d.ts.map +1 -0
  369. package/dist/lib/routes/schemas-by-type.js +27 -0
  370. package/dist/lib/routes/schemas-by-type.js.map +1 -0
  371. package/dist/lib/routes/schemas.d.ts +3 -0
  372. package/dist/lib/routes/schemas.d.ts.map +1 -0
  373. package/dist/lib/routes/schemas.js +12 -0
  374. package/dist/lib/routes/schemas.js.map +1 -0
  375. package/dist/lib/routes/user-preferences.d.ts +4 -0
  376. package/dist/lib/routes/user-preferences.d.ts.map +1 -0
  377. package/dist/lib/routes/user-preferences.js +79 -0
  378. package/dist/lib/routes/user-preferences.js.map +1 -0
  379. package/dist/lib/routes-exports.d.ts +17 -0
  380. package/dist/lib/routes-exports.d.ts.map +1 -0
  381. package/dist/lib/routes-exports.js +23 -0
  382. package/dist/lib/routes-exports.js.map +1 -0
  383. package/dist/lib/schema/index.d.ts +6 -0
  384. package/dist/lib/schema/index.d.ts.map +1 -0
  385. package/dist/lib/schema/index.js +12 -0
  386. package/dist/lib/schema/index.js.map +1 -0
  387. package/dist/lib/schema-context.svelte.d.ts +10 -0
  388. package/dist/lib/schema-context.svelte.d.ts.map +1 -0
  389. package/dist/lib/schema-context.svelte.js +19 -0
  390. package/dist/lib/schema-context.svelte.js.map +1 -0
  391. package/dist/lib/schema-utils/cleanup.d.ts +21 -0
  392. package/dist/lib/schema-utils/cleanup.d.ts.map +1 -0
  393. package/dist/lib/schema-utils/cleanup.js +81 -0
  394. package/dist/lib/schema-utils/cleanup.js.map +1 -0
  395. package/dist/lib/schema-utils/index.d.ts +4 -0
  396. package/dist/lib/schema-utils/index.d.ts.map +1 -0
  397. package/dist/lib/schema-utils/index.js +5 -0
  398. package/dist/lib/schema-utils/index.js.map +1 -0
  399. package/dist/lib/schema-utils/utils.d.ts +34 -0
  400. package/dist/lib/schema-utils/utils.d.ts.map +1 -0
  401. package/dist/lib/schema-utils/utils.js +59 -0
  402. package/dist/lib/schema-utils/utils.js.map +1 -0
  403. package/dist/lib/schema-utils/validator.d.ts +10 -0
  404. package/dist/lib/schema-utils/validator.d.ts.map +1 -0
  405. package/dist/lib/schema-utils/validator.js +167 -0
  406. package/dist/lib/schema-utils/validator.js.map +1 -0
  407. package/dist/lib/server/index.d.ts +19 -0
  408. package/dist/lib/server/index.d.ts.map +1 -0
  409. package/dist/lib/server/index.js +35 -0
  410. package/dist/lib/server/index.js.map +1 -0
  411. package/dist/lib/services/asset-service.d.ts +86 -0
  412. package/dist/lib/services/asset-service.d.ts.map +1 -0
  413. package/dist/lib/services/asset-service.js +189 -0
  414. package/dist/lib/services/asset-service.js.map +1 -0
  415. package/dist/lib/services/index.d.ts +3 -0
  416. package/dist/lib/services/index.d.ts.map +1 -0
  417. package/dist/lib/services/index.js +5 -0
  418. package/dist/lib/services/index.js.map +1 -0
  419. package/dist/lib/storage/adapters/index.d.ts +2 -0
  420. package/dist/lib/storage/adapters/index.d.ts.map +1 -0
  421. package/dist/lib/storage/adapters/index.js +3 -0
  422. package/dist/lib/storage/adapters/index.js.map +1 -0
  423. package/dist/lib/storage/adapters/local-storage-adapter.d.ts +54 -0
  424. package/dist/lib/storage/adapters/local-storage-adapter.d.ts.map +1 -0
  425. package/dist/lib/storage/adapters/local-storage-adapter.js +189 -0
  426. package/dist/lib/storage/adapters/local-storage-adapter.js.map +1 -0
  427. package/dist/lib/storage/index.d.ts +3 -0
  428. package/dist/lib/storage/index.d.ts.map +1 -0
  429. package/dist/lib/storage/index.js +7 -0
  430. package/dist/lib/storage/index.js.map +1 -0
  431. package/dist/lib/storage/interfaces/index.d.ts +2 -0
  432. package/dist/lib/storage/interfaces/index.d.ts.map +1 -0
  433. package/dist/lib/storage/interfaces/index.js +1 -0
  434. package/dist/lib/storage/interfaces/index.js.map +1 -0
  435. package/dist/lib/storage/interfaces/storage.d.ts +91 -0
  436. package/dist/lib/storage/interfaces/storage.d.ts.map +1 -0
  437. package/dist/lib/storage/interfaces/storage.js +1 -0
  438. package/dist/lib/storage/interfaces/storage.js.map +1 -0
  439. package/dist/lib/storage/providers/storage.d.ts +43 -0
  440. package/dist/lib/storage/providers/storage.d.ts.map +1 -0
  441. package/dist/lib/storage/providers/storage.js +65 -0
  442. package/dist/lib/storage/providers/storage.js.map +1 -0
  443. package/dist/lib/types/asset.d.ts +73 -0
  444. package/dist/lib/types/asset.d.ts.map +1 -0
  445. package/dist/lib/types/asset.js +1 -0
  446. package/dist/lib/types/asset.js.map +1 -0
  447. package/dist/lib/types/auth.d.ts +62 -0
  448. package/dist/lib/types/auth.d.ts.map +1 -0
  449. package/dist/lib/types/auth.js +7 -5
  450. package/dist/lib/types/auth.js.map +1 -0
  451. package/dist/lib/types/config.d.ts +77 -0
  452. package/dist/lib/types/config.d.ts.map +1 -0
  453. package/dist/lib/types/config.js +1 -0
  454. package/dist/lib/types/config.js.map +1 -0
  455. package/dist/lib/types/document.d.ts +35 -0
  456. package/dist/lib/types/document.d.ts.map +1 -0
  457. package/dist/lib/types/document.js +1 -0
  458. package/dist/lib/types/document.js.map +1 -0
  459. package/dist/lib/types/filters.d.ts +186 -0
  460. package/dist/lib/types/filters.d.ts.map +1 -0
  461. package/dist/lib/types/filters.js +1 -0
  462. package/dist/lib/types/filters.js.map +1 -0
  463. package/dist/lib/types/index.d.ts +11 -0
  464. package/dist/lib/types/index.d.ts.map +1 -0
  465. package/dist/lib/types/index.js +2 -0
  466. package/dist/lib/types/index.js.map +1 -0
  467. package/dist/lib/types/instance.d.ts +5 -0
  468. package/dist/lib/types/instance.d.ts.map +1 -0
  469. package/dist/lib/types/instance.js +3 -0
  470. package/dist/lib/types/instance.js.map +1 -0
  471. package/dist/lib/types/organization.d.ts +108 -0
  472. package/dist/lib/types/organization.d.ts.map +1 -0
  473. package/dist/lib/types/organization.js +1 -0
  474. package/dist/lib/types/organization.js.map +1 -0
  475. package/dist/lib/types/schemas.d.ts +179 -0
  476. package/dist/lib/types/schemas.d.ts.map +1 -0
  477. package/dist/lib/types/schemas.js +1 -0
  478. package/dist/lib/types/schemas.js.map +1 -0
  479. package/dist/lib/types/sidebar.d.ts +34 -0
  480. package/dist/lib/types/sidebar.d.ts.map +1 -0
  481. package/dist/lib/types/sidebar.js +1 -0
  482. package/dist/lib/types/sidebar.js.map +1 -0
  483. package/dist/lib/types/user.d.ts +14 -0
  484. package/dist/lib/types/user.d.ts.map +1 -0
  485. package/dist/lib/types/user.js +1 -0
  486. package/dist/lib/types/user.js.map +1 -0
  487. package/dist/lib/utils/asset-actions.d.ts +9 -0
  488. package/dist/lib/utils/asset-actions.d.ts.map +1 -0
  489. package/dist/lib/utils/asset-actions.js +28 -0
  490. package/dist/lib/utils/asset-actions.js.map +1 -0
  491. package/dist/lib/utils/content-hash.d.ts +22 -0
  492. package/dist/lib/utils/content-hash.d.ts.map +1 -0
  493. package/dist/lib/utils/content-hash.js +68 -0
  494. package/dist/lib/utils/content-hash.js.map +1 -0
  495. package/dist/lib/utils/default-orderings.d.ts +10 -0
  496. package/dist/lib/utils/default-orderings.d.ts.map +1 -0
  497. package/dist/lib/utils/default-orderings.js +64 -0
  498. package/dist/lib/utils/default-orderings.js.map +1 -0
  499. package/dist/lib/utils/element-events.d.ts +15 -0
  500. package/dist/lib/utils/element-events.d.ts.map +1 -0
  501. package/dist/lib/utils/element-events.js +17 -0
  502. package/dist/lib/utils/element-events.js.map +1 -0
  503. package/dist/lib/utils/field-defaults.d.ts +8 -0
  504. package/dist/lib/utils/field-defaults.d.ts.map +1 -0
  505. package/dist/lib/utils/field-defaults.js +21 -0
  506. package/dist/lib/utils/field-defaults.js.map +1 -0
  507. package/dist/lib/utils/image-url.d.ts +88 -0
  508. package/dist/lib/utils/image-url.d.ts.map +1 -0
  509. package/dist/lib/utils/image-url.js +167 -0
  510. package/dist/lib/utils/image-url.js.map +1 -0
  511. package/dist/lib/utils/index.d.ts +8 -0
  512. package/dist/lib/utils/index.d.ts.map +1 -0
  513. package/dist/lib/utils/index.js +13 -0
  514. package/dist/lib/utils/index.js.map +1 -0
  515. package/dist/lib/utils/initial-value-helpers.d.ts +50 -0
  516. package/dist/lib/utils/initial-value-helpers.d.ts.map +1 -0
  517. package/dist/lib/utils/initial-value-helpers.js +71 -0
  518. package/dist/lib/utils/initial-value-helpers.js.map +1 -0
  519. package/dist/lib/utils/logger.d.ts +9 -0
  520. package/dist/lib/utils/logger.d.ts.map +1 -0
  521. package/dist/lib/utils/logger.js +30 -0
  522. package/dist/lib/utils/logger.js.map +1 -0
  523. package/dist/lib/utils/slug.d.ts +13 -0
  524. package/dist/lib/utils/slug.d.ts.map +1 -0
  525. package/dist/lib/utils/slug.js +31 -0
  526. package/dist/lib/utils/slug.js.map +1 -0
  527. package/dist/lib/utils.d.ts +13 -0
  528. package/dist/lib/utils.d.ts.map +1 -0
  529. package/dist/lib/utils.js +6 -0
  530. package/dist/lib/utils.js.map +1 -0
  531. package/dist/local-api/index.d.ts.map +1 -1
  532. package/dist/local-api/permissions.d.ts.map +1 -1
  533. package/dist/local-api/permissions.js +3 -4
  534. package/dist/routes/assets-bulk.d.ts +3 -0
  535. package/dist/routes/assets-bulk.d.ts.map +1 -0
  536. package/dist/routes/assets-bulk.js +48 -0
  537. package/dist/routes/assets-by-id.d.ts.map +1 -1
  538. package/dist/routes/assets-by-id.js +22 -55
  539. package/dist/routes/assets-cdn.d.ts.map +1 -1
  540. package/dist/routes/assets-cdn.js +12 -50
  541. package/dist/routes/assets-references-counts.d.ts +7 -0
  542. package/dist/routes/assets-references-counts.d.ts.map +1 -0
  543. package/dist/routes/assets-references-counts.js +31 -0
  544. package/dist/routes/assets-references.d.ts +7 -0
  545. package/dist/routes/assets-references.d.ts.map +1 -0
  546. package/dist/routes/assets-references.js +34 -0
  547. package/dist/routes/assets.d.ts.map +1 -1
  548. package/dist/routes/assets.js +27 -6
  549. package/dist/routes/documents-by-id.d.ts.map +1 -1
  550. package/dist/routes/documents-by-id.js +4 -3
  551. package/dist/routes/documents-publish.d.ts.map +1 -1
  552. package/dist/routes/documents-publish.js +3 -2
  553. package/dist/routes/documents-query.d.ts.map +1 -1
  554. package/dist/routes/documents-query.js +2 -1
  555. package/dist/routes/documents.d.ts.map +1 -1
  556. package/dist/routes/documents.js +3 -2
  557. package/dist/routes/organizations-by-id.d.ts.map +1 -1
  558. package/dist/routes/organizations-by-id.js +4 -3
  559. package/dist/routes/organizations-invitations.d.ts.map +1 -1
  560. package/dist/routes/organizations-invitations.js +5 -4
  561. package/dist/routes/organizations-members.d.ts.map +1 -1
  562. package/dist/routes/organizations-members.js +7 -6
  563. package/dist/routes/organizations-switch.d.ts.map +1 -1
  564. package/dist/routes/organizations-switch.js +2 -1
  565. package/dist/routes/organizations.d.ts.map +1 -1
  566. package/dist/routes/organizations.js +3 -2
  567. package/dist/routes/schemas-by-type.d.ts.map +1 -1
  568. package/dist/routes/schemas-by-type.js +3 -2
  569. package/dist/routes/user-preferences.d.ts.map +1 -1
  570. package/dist/routes/user-preferences.js +3 -2
  571. package/dist/routes-exports.d.ts +3 -0
  572. package/dist/routes-exports.d.ts.map +1 -1
  573. package/dist/routes-exports.js +3 -0
  574. package/dist/schema/index.d.ts +6 -0
  575. package/dist/schema/index.d.ts.map +1 -0
  576. package/dist/schema/index.js +11 -0
  577. package/dist/schema-utils/validator.d.ts.map +1 -1
  578. package/dist/schema-utils/validator.js +4 -3
  579. package/dist/server/index.d.ts +2 -0
  580. package/dist/server/index.d.ts.map +1 -1
  581. package/dist/server/index.js +4 -0
  582. package/dist/services/asset-service.d.ts.map +1 -1
  583. package/dist/services/asset-service.js +8 -7
  584. package/dist/storage/adapters/local-storage-adapter.d.ts.map +1 -1
  585. package/dist/storage/adapters/local-storage-adapter.js +5 -4
  586. package/dist/types/auth.d.ts +13 -1
  587. package/dist/types/auth.d.ts.map +1 -1
  588. package/dist/types/auth.js +6 -5
  589. package/dist/types/config.d.ts +14 -1
  590. package/dist/types/config.d.ts.map +1 -1
  591. package/dist/types/document.d.ts +1 -1
  592. package/dist/types/document.d.ts.map +1 -1
  593. package/dist/types/index.d.ts +1 -0
  594. package/dist/types/index.d.ts.map +1 -1
  595. package/dist/types/index.js +1 -0
  596. package/dist/types/instance.d.ts +5 -0
  597. package/dist/types/instance.d.ts.map +1 -0
  598. package/dist/types/instance.js +2 -0
  599. package/dist/types/schemas.d.ts +1 -1
  600. package/dist/types/schemas.d.ts.map +1 -1
  601. package/dist/types/sidebar.d.ts +1 -0
  602. package/dist/types/sidebar.d.ts.map +1 -1
  603. package/dist/utils/asset-actions.d.ts +9 -0
  604. package/dist/utils/asset-actions.d.ts.map +1 -0
  605. package/dist/utils/asset-actions.js +27 -0
  606. package/dist/utils/element-events.d.ts +15 -0
  607. package/dist/utils/element-events.d.ts.map +1 -0
  608. package/dist/utils/element-events.js +16 -0
  609. package/dist/utils/image-url.d.ts.map +1 -1
  610. package/dist/utils/image-url.js +10 -9
  611. package/dist/utils/index.d.ts +1 -0
  612. package/dist/utils/index.d.ts.map +1 -1
  613. package/dist/utils/index.js +2 -0
  614. package/dist/utils/logger.d.ts +9 -0
  615. package/dist/utils/logger.d.ts.map +1 -0
  616. package/dist/utils/logger.js +29 -0
  617. package/package.json +69 -36
@@ -0,0 +1,1398 @@
1
+ <script lang="ts">
2
+ import { Button } from '@aphexcms/ui/shadcn/button';
3
+ import { Input } from '@aphexcms/ui/shadcn/input';
4
+ import { Label } from '@aphexcms/ui/shadcn/label';
5
+ import { Separator } from '@aphexcms/ui/shadcn/separator';
6
+ import { Checkbox } from '@aphexcms/ui/shadcn/checkbox';
7
+ import * as Dialog from '@aphexcms/ui/shadcn/dialog';
8
+ import {
9
+ Upload,
10
+ Search,
11
+ Grid3x3,
12
+ List,
13
+ ArrowDownUp,
14
+ X,
15
+ Trash2,
16
+ Image as ImageIcon,
17
+ FileText,
18
+ FileImage,
19
+ ChevronLeft,
20
+ ChevronRight,
21
+ Download,
22
+ Link,
23
+ CheckCircle2,
24
+ AlertCircle,
25
+ SquareCheckBig
26
+ } from '@lucide/svelte';
27
+ import { page } from '$app/state';
28
+ import { goto } from '$app/navigation';
29
+ import { assets } from '../../api/assets';
30
+ import type { AssetReference } from '../../api/assets';
31
+ import type { Asset } from '../../types/asset';
32
+ import { toast } from 'svelte-sonner';
33
+ import { copyUrlToClipboard, downloadFile } from '../../utils/asset-actions';
34
+ import { cmsLogger } from '../../utils/logger';
35
+ import { SvelteSet } from 'svelte/reactivity';
36
+
37
+ interface Props {
38
+ /** When true, shows a "Select" button for picking an asset */
39
+ selectable?: boolean;
40
+ /** When true, allows selecting multiple assets (used with selectable) */
41
+ multiSelect?: boolean;
42
+ /** Callback when an asset is selected (single select mode) */
43
+ onSelect?: (asset: Asset) => void;
44
+ /** Callback when multiple assets are selected (multi select mode) */
45
+ onSelectMultiple?: (assets: Asset[]) => void;
46
+ /** Filter to specific asset type */
47
+ assetTypeFilter?: 'image' | 'file';
48
+ /** Number of assets per page */
49
+ pageSize?: number;
50
+ /** Whether this tab is currently active (triggers refetch when becoming active) */
51
+ active?: boolean;
52
+ /** Asset IDs already in use (shown with a tick indicator) */
53
+ existingAssetIds?: Set<string>;
54
+ }
55
+
56
+ let {
57
+ selectable = false,
58
+ multiSelect = false,
59
+ onSelect,
60
+ onSelectMultiple,
61
+ assetTypeFilter,
62
+ pageSize = 30,
63
+ active = true,
64
+ existingAssetIds
65
+ }: Props = $props();
66
+
67
+ // State
68
+ let assetList = $state<Asset[]>([]);
69
+ let loading = $state(false);
70
+ let searchQuery = $state('');
71
+ let viewMode = $state<'grid' | 'list'>('grid');
72
+ let sortOrder = $state<'newest' | 'oldest' | 'name-asc' | 'name-desc'>('newest');
73
+
74
+ let selectedAsset = $state<Asset | null>(null);
75
+ let lightboxOpen = $state(false);
76
+ let currentPage = $state(1);
77
+ let totalPages = $state(1);
78
+ let totalAssets = $state(0);
79
+
80
+ // Upload state
81
+ let isUploading = $state(false);
82
+ let isDragging = $state(false);
83
+ let showUploadModal = $state(false);
84
+ let modalFileInputRef: HTMLInputElement;
85
+ let modalIsDragging = $state(false);
86
+
87
+ interface UploadQueueItem {
88
+ file: File;
89
+ status: 'pending' | 'uploading' | 'done' | 'failed';
90
+ }
91
+ let uploadQueue = $state<UploadQueueItem[]>([]);
92
+
93
+ // Detail editing state
94
+ let editTitle = $state('');
95
+ let editDescription = $state('');
96
+ let editAlt = $state('');
97
+ let editCreditLine = $state('');
98
+ let isSaving = $state(false);
99
+
100
+ // Bulk selection state
101
+ let selectMode = $state(false);
102
+ let selectedIds = $state<Set<string>>(
103
+ selectable && multiSelect && existingAssetIds ? new Set(existingAssetIds) : new Set()
104
+ );
105
+ let isBulkDeleting = $state(false);
106
+
107
+ // In selectable+multiSelect mode, always be in select mode
108
+ const isSelectMode = $derived(selectMode || (selectable && multiSelect));
109
+
110
+ function toggleSelectMode() {
111
+ selectMode = !selectMode;
112
+ if (!selectMode) {
113
+ selectedIds = new Set();
114
+ }
115
+ }
116
+
117
+ // Reference tracking state
118
+ let referenceCounts = $state<Record<string, number>>({});
119
+ let detailTab = $state<'details' | 'references'>('details');
120
+ let selectedAssetRefs = $state<AssetReference[]>([]);
121
+ let loadingRefs = $state(false);
122
+ let selectedRefCount = $state(0);
123
+
124
+ // Debounced search
125
+ let searchTimeout: ReturnType<typeof setTimeout>;
126
+
127
+ function handleSearchInput(value: string) {
128
+ searchQuery = value;
129
+ clearTimeout(searchTimeout);
130
+ searchTimeout = setTimeout(() => {
131
+ currentPage = 1;
132
+ fetchAssets();
133
+ }, 300);
134
+ }
135
+
136
+ // Fetch assets
137
+ async function fetchAssets(page = currentPage) {
138
+ loading = true;
139
+ try {
140
+ const offset = (page - 1) * pageSize;
141
+ const result = await assets.list({
142
+ assetType: assetTypeFilter,
143
+ search: searchQuery || undefined,
144
+ limit: pageSize,
145
+ offset
146
+ });
147
+
148
+ if (result.success && result.data) {
149
+ assetList = result.data;
150
+ currentPage = page;
151
+ if (result.pagination) {
152
+ totalPages = result.pagination.totalPages;
153
+ totalAssets = result.pagination.total;
154
+ }
155
+ // Clear bulk selection on page change (but never in multi-select picker mode —
156
+ // selection is initialised once at mount and only changed by user interaction)
157
+ if (!(selectable && multiSelect)) {
158
+ selectedIds = new Set();
159
+ }
160
+ // Fetch reference counts for this page
161
+ fetchReferenceCounts(result.data.map((a) => a.id));
162
+ }
163
+ } catch (err) {
164
+ toast.error('Failed to fetch assets');
165
+ } finally {
166
+ loading = false;
167
+ }
168
+ }
169
+
170
+ function goToPage(page: number) {
171
+ if (page < 1 || page > totalPages || page === currentPage) return;
172
+ fetchAssets(page);
173
+ }
174
+
175
+ // Fetch reference counts for current page of assets
176
+ async function fetchReferenceCounts(assetIds: string[]) {
177
+ if (assetIds.length === 0) return;
178
+ try {
179
+ const result = await assets.getReferenceCounts(assetIds);
180
+ if (result.success && result.data) {
181
+ referenceCounts = { ...referenceCounts, ...result.data };
182
+ }
183
+ } catch (err) {
184
+ toast.error('Failed to fetch reference counts');
185
+ }
186
+ }
187
+
188
+ // Fetch full references for a specific asset (sidebar)
189
+ async function fetchAssetReferences(assetId: string) {
190
+ loadingRefs = true;
191
+ try {
192
+ const result = await assets.getReferences(assetId);
193
+ if (result.success && result.data) {
194
+ selectedAssetRefs = result.data.references;
195
+ selectedRefCount = result.data.total;
196
+ }
197
+ } catch (err) {
198
+ toast.error('Failed to fetch asset references');
199
+ selectedAssetRefs = [];
200
+ selectedRefCount = 0;
201
+ } finally {
202
+ loadingRefs = false;
203
+ }
204
+ }
205
+
206
+ // Sort assets client-side
207
+ function sortAssets(list: Asset[]): Asset[] {
208
+ const sorted = [...list];
209
+ switch (sortOrder) {
210
+ case 'newest':
211
+ return sorted.sort(
212
+ (a, b) => new Date(b.createdAt || 0).getTime() - new Date(a.createdAt || 0).getTime()
213
+ );
214
+ case 'oldest':
215
+ return sorted.sort(
216
+ (a, b) => new Date(a.createdAt || 0).getTime() - new Date(b.createdAt || 0).getTime()
217
+ );
218
+ case 'name-asc':
219
+ return sorted.sort((a, b) => a.originalFilename.localeCompare(b.originalFilename));
220
+ case 'name-desc':
221
+ return sorted.sort((a, b) => b.originalFilename.localeCompare(a.originalFilename));
222
+ default:
223
+ return sorted;
224
+ }
225
+ }
226
+
227
+ // Pinned assets (already in array) — separate from the main sorted list
228
+ const pinnedAssets = $derived.by(() => {
229
+ if (!(selectable && multiSelect && existingAssetIds && existingAssetIds.size > 0)) return [];
230
+ return assetList.filter((a) => existingAssetIds!.has(a.id));
231
+ });
232
+
233
+ const sortedAssets = $derived.by(() => {
234
+ if (selectable && multiSelect && existingAssetIds && existingAssetIds.size > 0) {
235
+ return sortAssets(assetList.filter((a) => !existingAssetIds!.has(a.id)));
236
+ }
237
+ return sortAssets(assetList);
238
+ });
239
+
240
+ // Bulk selection derived (must be after sortedAssets)
241
+ const allSelected = $derived(
242
+ sortedAssets.length > 0 && sortedAssets.every((a) => selectedIds.has(a.id))
243
+ );
244
+
245
+ function toggleSelectAll() {
246
+ if (allSelected) {
247
+ selectedIds = new Set();
248
+ } else {
249
+ selectedIds = new Set(sortedAssets.map((a) => a.id));
250
+ }
251
+ }
252
+
253
+ function toggleSelect(id: string) {
254
+ const next = new SvelteSet(selectedIds);
255
+ if (next.has(id)) {
256
+ next.delete(id);
257
+ } else {
258
+ next.add(id);
259
+ }
260
+ selectedIds = next;
261
+ }
262
+
263
+ function confirmMultiSelect() {
264
+ if (onSelectMultiple) {
265
+ const selected = assetList.filter((a) => selectedIds.has(a.id));
266
+ onSelectMultiple(selected);
267
+ selectedIds = new Set();
268
+ }
269
+ }
270
+
271
+ async function bulkDelete() {
272
+ if (selectedIds.size === 0) return;
273
+
274
+ // Fetch fresh reference counts before checking
275
+ const idsToCheck = [...selectedIds];
276
+ try {
277
+ const result = await assets.getReferenceCounts(idsToCheck);
278
+ if (result.success && result.data) {
279
+ referenceCounts = { ...referenceCounts, ...result.data };
280
+ }
281
+ } catch (err) {
282
+ toast.error('Failed to check references');
283
+ }
284
+
285
+ // Check for referenced assets
286
+ const referencedAssets = idsToCheck.filter((id) => (referenceCounts[id] || 0) > 0);
287
+ if (referencedAssets.length > 0) {
288
+ toast.error(
289
+ `Cannot delete ${referencedAssets.length} asset${referencedAssets.length > 1 ? 's' : ''} — still referenced by documents. Remove the references first.`
290
+ );
291
+ return;
292
+ }
293
+
294
+ const count = selectedIds.size;
295
+ if (!confirm(`Delete ${count} asset${count > 1 ? 's' : ''}? This cannot be undone.`)) return;
296
+
297
+ isBulkDeleting = true;
298
+ try {
299
+ const result = await assets.deleteBulk([...selectedIds]);
300
+ if (result.success) {
301
+ if (selectedAsset && selectedIds.has(selectedAsset.id)) {
302
+ selectedAsset = null;
303
+ }
304
+ selectedIds = new Set();
305
+ await fetchAssets();
306
+ }
307
+ } catch (err) {
308
+ toast.error('Failed to delete assets');
309
+ } finally {
310
+ isBulkDeleting = false;
311
+ }
312
+ }
313
+
314
+ // Upload files via modal queue
315
+ function addFilesToQueue(files: FileList | null) {
316
+ if (!files || files.length === 0) return;
317
+ const newItems: UploadQueueItem[] = Array.from(files).map((file) => ({
318
+ file,
319
+ status: 'pending' as const
320
+ }));
321
+ uploadQueue = [...uploadQueue, ...newItems];
322
+ processUploadQueue();
323
+ }
324
+
325
+ async function processUploadQueue() {
326
+ if (isUploading) return;
327
+ isUploading = true;
328
+
329
+ for (let i = 0; i < uploadQueue.length; i++) {
330
+ if (uploadQueue[i]!.status !== 'pending') continue;
331
+ uploadQueue[i]!.status = 'uploading';
332
+ uploadQueue = [...uploadQueue]; // trigger reactivity
333
+
334
+ try {
335
+ const formData = new FormData();
336
+ formData.append('file', uploadQueue[i]!.file);
337
+ const result = await assets.upload(formData);
338
+ uploadQueue[i]!.status = result.success ? 'done' : 'failed';
339
+ } catch {
340
+ uploadQueue[i]!.status = 'failed';
341
+ }
342
+ uploadQueue = [...uploadQueue];
343
+ }
344
+
345
+ isUploading = false;
346
+
347
+ // If all done, refetch and close after a brief pause
348
+ if (uploadQueue.every((item) => item.status === 'done' || item.status === 'failed')) {
349
+ currentPage = 1;
350
+ await fetchAssets(1);
351
+ setTimeout(() => {
352
+ showUploadModal = false;
353
+ uploadQueue = [];
354
+ }, 800);
355
+ }
356
+ }
357
+
358
+ // Drag and drop
359
+ function handleDragOver(e: DragEvent) {
360
+ e.preventDefault();
361
+ isDragging = true;
362
+ }
363
+
364
+ function handleDragLeave(e: DragEvent) {
365
+ e.preventDefault();
366
+ isDragging = false;
367
+ }
368
+
369
+ function handleDrop(e: DragEvent) {
370
+ e.preventDefault();
371
+ isDragging = false;
372
+ showUploadModal = true;
373
+ addFilesToQueue(e.dataTransfer?.files || null);
374
+ }
375
+
376
+ // Select an asset for detail view — re-fetch to get fresh data
377
+ // Select an asset for detail view
378
+ function openAssetDetail(asset: Asset) {
379
+ const isSameAsset = selectedAsset?.id === asset.id;
380
+
381
+ selectedAsset = asset;
382
+ editTitle = asset.title || '';
383
+ editDescription = asset.description || '';
384
+ editAlt = asset.alt || '';
385
+ editCreditLine = asset.creditLine || '';
386
+
387
+ // Only reset references/tab when switching to a different asset
388
+ if (!isSameAsset) {
389
+ detailTab = 'details';
390
+ selectedAssetRefs = [];
391
+ selectedRefCount = referenceCounts[asset.id] || 0;
392
+ }
393
+ }
394
+
395
+ function closeAssetDetail() {
396
+ selectedAsset = null;
397
+ }
398
+
399
+ // Save metadata
400
+ async function saveMetadata() {
401
+ if (!selectedAsset) return;
402
+ isSaving = true;
403
+ try {
404
+ const result = await assets.update(selectedAsset.id, {
405
+ title: editTitle || undefined,
406
+ description: editDescription || undefined,
407
+ alt: editAlt || undefined,
408
+ creditLine: editCreditLine || undefined
409
+ });
410
+ if (result.success && result.data) {
411
+ // Update in list
412
+ assetList = assetList.map((a) => (a.id === selectedAsset!.id ? result.data! : a));
413
+ selectedAsset = result.data;
414
+ }
415
+ } catch (err) {
416
+ toast.error('Failed to save metadata');
417
+ } finally {
418
+ isSaving = false;
419
+ }
420
+ }
421
+
422
+ // Delete asset
423
+ async function deleteAsset(asset: Asset) {
424
+ const refCount = referenceCounts[asset.id] || 0;
425
+ if (refCount > 0) {
426
+ toast.error(
427
+ `Cannot delete "${asset.originalFilename}" — referenced by ${refCount} document${refCount > 1 ? 's' : ''}. Remove the references first.`
428
+ );
429
+ return;
430
+ }
431
+ if (!confirm(`Delete "${asset.originalFilename}"? This cannot be undone.`)) return;
432
+ try {
433
+ const result = await assets.delete(asset.id);
434
+ if (result.success) {
435
+ if (selectedAsset?.id === asset.id) {
436
+ selectedAsset = null;
437
+ }
438
+ await fetchAssets();
439
+ }
440
+ } catch (err) {
441
+ toast.error('Failed to delete asset');
442
+ }
443
+ }
444
+
445
+ // Copy URL state
446
+ let copiedUrl = $state(false);
447
+
448
+ async function copyAssetUrl(asset: Asset) {
449
+ const url = getThumbnailUrl(asset);
450
+ const success = await copyUrlToClipboard(url);
451
+ if (success) {
452
+ copiedUrl = true;
453
+ setTimeout(() => (copiedUrl = false), 2000);
454
+ }
455
+ }
456
+
457
+ function downloadAsset(asset: Asset) {
458
+ downloadFile(getThumbnailUrl(asset), asset.originalFilename);
459
+ }
460
+
461
+ // Format file size
462
+ function formatSize(bytes: number): string {
463
+ if (bytes < 1024) return `${bytes} B`;
464
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} kB`;
465
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
466
+ }
467
+
468
+ // Format date
469
+ function formatDate(date: Date | string | null): string {
470
+ if (!date) return '';
471
+ const d = new Date(date);
472
+ return d.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' });
473
+ }
474
+
475
+ // Get thumbnail URL
476
+ function getThumbnailUrl(asset: Asset): string {
477
+ return asset.url || `/media/${asset.id}/${asset.filename}`;
478
+ }
479
+
480
+ // Is image type
481
+ function isImage(asset: Asset): boolean {
482
+ return asset.assetType === 'image' || asset.mimeType.startsWith('image/');
483
+ }
484
+
485
+ // Compute visible page numbers (show up to 5 pages with ellipsis)
486
+ const visiblePages = $derived.by(() => {
487
+ const pages: (number | '...')[] = [];
488
+ if (totalPages <= 7) {
489
+ for (let i = 1; i <= totalPages; i++) pages.push(i);
490
+ } else {
491
+ pages.push(1);
492
+ if (currentPage > 3) pages.push('...');
493
+ const start = Math.max(2, currentPage - 1);
494
+ const end = Math.min(totalPages - 1, currentPage + 1);
495
+ for (let i = start; i <= end; i++) pages.push(i);
496
+ if (currentPage < totalPages - 2) pages.push('...');
497
+ pages.push(totalPages);
498
+ }
499
+ return pages;
500
+ });
501
+
502
+ // Sort label
503
+ const sortLabel = $derived(
504
+ sortOrder === 'newest'
505
+ ? 'Last created: Newest first'
506
+ : sortOrder === 'oldest'
507
+ ? 'Last created: Oldest first'
508
+ : sortOrder === 'name-asc'
509
+ ? 'Name: A-Z'
510
+ : 'Name: Z-A'
511
+ );
512
+
513
+ // Cycle sort
514
+ function cycleSort() {
515
+ const orders: (typeof sortOrder)[] = ['newest', 'oldest', 'name-asc', 'name-desc'];
516
+ const idx = orders.indexOf(sortOrder);
517
+ sortOrder = orders[(idx + 1) % orders.length]!;
518
+ }
519
+
520
+ // Track org changes to refetch assets
521
+ let currentOrgId = $state<string | null>(null);
522
+
523
+ // Track whether we've been active before to detect tab switches
524
+ let wasActive = $state(false);
525
+
526
+ // Load on mount and refetch when org changes
527
+ $effect(() => {
528
+ const orgId = page.url.searchParams.get('orgId');
529
+ if (orgId !== currentOrgId) {
530
+ currentOrgId = orgId;
531
+ selectedAsset = null;
532
+ currentPage = 1;
533
+ fetchAssets(1);
534
+ }
535
+ });
536
+
537
+ // Refetch when tab becomes active (switching from another tab)
538
+ $effect(() => {
539
+ if (active && wasActive) {
540
+ selectedAsset = null;
541
+ fetchAssets();
542
+ }
543
+ wasActive = active;
544
+ });
545
+ </script>
546
+
547
+ <div
548
+ class="flex h-full flex-col"
549
+ role="region"
550
+ ondragover={handleDragOver}
551
+ ondragleave={handleDragLeave}
552
+ ondrop={handleDrop}
553
+ >
554
+ <!-- Drag overlay -->
555
+ {#if isDragging}
556
+ <div
557
+ class="bg-primary/5 border-primary absolute inset-0 z-50 flex items-center justify-center border-2 border-dashed"
558
+ >
559
+ <div class="text-center">
560
+ <Upload class="text-primary mx-auto mb-2 h-12 w-12" />
561
+ <p class="text-primary text-lg font-medium">Drop files to upload</p>
562
+ </div>
563
+ </div>
564
+ {/if}
565
+
566
+ <!-- Header -->
567
+ <div class="border-border flex items-center justify-between border-b px-4 py-3 sm:px-6 sm:py-4">
568
+ <h2 class="text-base font-semibold sm:text-lg">Browse Assets</h2>
569
+ <Button
570
+ size="sm"
571
+ onclick={() => {
572
+ showUploadModal = true;
573
+ uploadQueue = [];
574
+ }}
575
+ >
576
+ <Upload size={16} class="sm:mr-2" />
577
+ <span class="hidden sm:inline">Upload assets</span>
578
+ </Button>
579
+ </div>
580
+
581
+ <!-- Toolbar -->
582
+ <div
583
+ class="border-border flex flex-wrap items-center gap-2 border-b px-4 py-2 sm:gap-3 sm:px-6 sm:py-3"
584
+ >
585
+ <div class="relative min-w-0 flex-1 sm:w-48 sm:flex-none">
586
+ <Search size={14} class="text-muted-foreground absolute top-1/2 left-2.5 -translate-y-1/2" />
587
+ <Input
588
+ placeholder="Search"
589
+ class="h-8 pl-8 text-sm"
590
+ value={searchQuery}
591
+ oninput={(e) => handleSearchInput((e.target as HTMLInputElement).value)}
592
+ />
593
+ </div>
594
+
595
+ {#if totalAssets > 0}
596
+ <span class="text-muted-foreground hidden text-xs sm:inline">
597
+ {(currentPage - 1) * pageSize + 1}–{Math.min(currentPage * pageSize, totalAssets)} of {totalAssets}
598
+ </span>
599
+ {/if}
600
+ <div class="hidden flex-1 sm:block"></div>
601
+
602
+ <!-- Page size -->
603
+ <div class="hidden items-center gap-1.5 sm:flex">
604
+ <span class="text-muted-foreground text-xs">Show</span>
605
+ <select
606
+ value={pageSize}
607
+ onchange={(e) => {
608
+ pageSize = parseInt((e.target as HTMLSelectElement).value);
609
+ currentPage = 1;
610
+ fetchAssets(1);
611
+ }}
612
+ class="border-input bg-background text-foreground h-7 rounded-md border px-1.5 text-xs"
613
+ >
614
+ <option value={10}>10</option>
615
+ <option value={20}>20</option>
616
+ <option value={30}>30</option>
617
+ <option value={50}>50</option>
618
+ <option value={100}>100</option>
619
+ </select>
620
+ </div>
621
+
622
+ <!-- View toggle -->
623
+ <div class="bg-muted flex items-center rounded-md p-0.5">
624
+ <button
625
+ onclick={() => (viewMode = 'grid')}
626
+ class="rounded p-1.5 {viewMode === 'grid'
627
+ ? 'bg-background shadow'
628
+ : 'text-muted-foreground'}"
629
+ title="Grid view"
630
+ >
631
+ <Grid3x3 size={14} />
632
+ </button>
633
+ <button
634
+ onclick={() => (viewMode = 'list')}
635
+ class="rounded p-1.5 {viewMode === 'list'
636
+ ? 'bg-background shadow'
637
+ : 'text-muted-foreground'}"
638
+ title="List view"
639
+ >
640
+ <List size={14} />
641
+ </button>
642
+ </div>
643
+
644
+ <!-- Select mode toggle -->
645
+ {#if !selectable}
646
+ <button
647
+ onclick={toggleSelectMode}
648
+ class="rounded p-1.5 transition-colors {isSelectMode
649
+ ? 'bg-primary text-primary-foreground'
650
+ : 'text-muted-foreground hover:text-foreground'}"
651
+ title={isSelectMode ? 'Exit select mode' : 'Select multiple'}
652
+ >
653
+ <SquareCheckBig size={14} />
654
+ </button>
655
+ {/if}
656
+
657
+ <!-- Sort -->
658
+ <button
659
+ onclick={cycleSort}
660
+ class="text-muted-foreground hover:text-foreground flex items-center gap-1 text-xs transition-colors sm:gap-1.5"
661
+ >
662
+ <ArrowDownUp size={14} />
663
+ <span class="hidden sm:inline">{sortLabel}</span>
664
+ </button>
665
+ </div>
666
+
667
+ <!-- Content area -->
668
+ <div class="flex flex-1 flex-col overflow-y-auto md:flex-row md:overflow-hidden">
669
+ <!-- Main content (hidden on mobile when asset detail is open) -->
670
+ <div class="min-h-0 flex-1 md:overflow-y-auto {selectedAsset ? 'hidden md:block' : ''}">
671
+ <svelte:boundary
672
+ onerror={(error) => cmsLogger.error('[MediaBrowser]', 'Render error:', error)}
673
+ >
674
+ {#if loading && assetList.length === 0}
675
+ <div class="flex h-full items-center justify-center">
676
+ <p class="text-muted-foreground">Loading assets...</p>
677
+ </div>
678
+ {:else if sortedAssets.length === 0}
679
+ <div class="flex h-full flex-col items-center justify-center gap-4">
680
+ <div class="bg-muted/50 flex h-16 w-16 items-center justify-center rounded-full">
681
+ <ImageIcon class="text-muted-foreground h-8 w-8" />
682
+ </div>
683
+ <div class="text-center">
684
+ <h3 class="mb-1 font-medium">No assets found</h3>
685
+ <p class="text-muted-foreground text-sm">
686
+ {searchQuery
687
+ ? 'Try a different search term'
688
+ : 'Upload your first asset to get started'}
689
+ </p>
690
+ </div>
691
+ </div>
692
+ {:else}
693
+ <!-- Bulk action bar (shared for grid and list) -->
694
+ {#if selectable && multiSelect}
695
+ <div class="bg-muted border-border flex items-center gap-3 border-b px-4 py-2">
696
+ <span class="text-sm font-medium">
697
+ {selectedIds.size} selected
698
+ </span>
699
+ <Button variant="default" size="sm" onclick={confirmMultiSelect}>Done</Button>
700
+ </div>
701
+ {:else if selectedIds.size > 0}
702
+ <div class="bg-muted border-border flex items-center gap-3 border-b px-4 py-2">
703
+ <span class="text-sm font-medium">
704
+ {selectedIds.size} selected
705
+ </span>
706
+ <Button
707
+ variant="destructive"
708
+ size="sm"
709
+ onclick={bulkDelete}
710
+ disabled={isBulkDeleting}
711
+ >
712
+ <Trash2 size={14} class="mr-1.5" />
713
+ {isBulkDeleting ? 'Deleting...' : 'Delete'}
714
+ </Button>
715
+ <button
716
+ onclick={() => (selectedIds = new Set())}
717
+ class="text-muted-foreground hover:text-foreground text-sm transition-colors"
718
+ >
719
+ Clear selection
720
+ </button>
721
+ </div>
722
+ {/if}
723
+ {#if viewMode === 'grid'}
724
+ <!-- Grid View -->
725
+ <div class="grid grid-cols-2 gap-0.5 p-1 sm:grid-cols-5 xl:grid-cols-10">
726
+ {#each pinnedAssets as asset (asset.id)}
727
+ <button
728
+ onclick={() => openAssetDetail(asset)}
729
+ class="group relative flex flex-col overflow-hidden rounded-sm transition-colors {selectedIds.has(
730
+ asset.id
731
+ )
732
+ ? 'ring-primary ring-2'
733
+ : selectedAsset?.id === asset.id
734
+ ? 'ring-primary ring-2'
735
+ : 'hover:bg-muted/50'}"
736
+ >
737
+ <div class="bg-muted/30 relative aspect-square overflow-hidden">
738
+ {#if isImage(asset)}
739
+ <img
740
+ src={getThumbnailUrl(asset)}
741
+ alt={asset.alt || asset.originalFilename}
742
+ class="h-full w-full object-contain"
743
+ loading="lazy"
744
+ />
745
+ {:else}
746
+ <div class="flex h-full items-center justify-center">
747
+ <FileText class="text-muted-foreground h-10 w-10" />
748
+ </div>
749
+ {/if}
750
+ <div class="absolute top-1.5 left-1.5">
751
+ <Checkbox
752
+ checked={selectedIds.has(asset.id)}
753
+ onCheckedChange={() => toggleSelect(asset.id)}
754
+ onclick={(e) => e.stopPropagation()}
755
+ />
756
+ </div>
757
+ </div>
758
+ <div class="p-1.5">
759
+ <p class="text-muted-foreground truncate text-xs">
760
+ {asset.originalFilename}
761
+ </p>
762
+ </div>
763
+ </button>
764
+ {/each}
765
+ {#each sortedAssets as asset (asset.id)}
766
+ <button
767
+ onclick={() => {
768
+ if (selectable && multiSelect) {
769
+ openAssetDetail(asset);
770
+ } else if (isSelectMode) {
771
+ toggleSelect(asset.id);
772
+ } else if (selectable && onSelect) {
773
+ onSelect(asset);
774
+ } else {
775
+ openAssetDetail(asset);
776
+ }
777
+ }}
778
+ class="group relative flex flex-col overflow-hidden rounded-sm transition-colors {selectedIds.has(
779
+ asset.id
780
+ )
781
+ ? 'ring-primary ring-2'
782
+ : selectedAsset?.id === asset.id
783
+ ? 'ring-primary ring-2'
784
+ : 'hover:bg-muted/50'}"
785
+ >
786
+ <div class="bg-muted/30 relative aspect-square overflow-hidden">
787
+ {#if isImage(asset)}
788
+ <img
789
+ src={getThumbnailUrl(asset)}
790
+ alt={asset.alt || asset.originalFilename}
791
+ class="h-full w-full object-contain"
792
+ loading="lazy"
793
+ />
794
+ {:else}
795
+ <div class="flex h-full items-center justify-center">
796
+ <FileText class="text-muted-foreground h-10 w-10" />
797
+ </div>
798
+ {/if}
799
+ <!-- Checkbox overlay (only in select mode) -->
800
+ {#if isSelectMode}
801
+ <div class="absolute top-1.5 left-1.5">
802
+ <Checkbox
803
+ checked={selectedIds.has(asset.id)}
804
+ onCheckedChange={() => toggleSelect(asset.id)}
805
+ onclick={(e) => e.stopPropagation()}
806
+ />
807
+ </div>
808
+ {/if}
809
+ </div>
810
+ <div class="p-1.5">
811
+ <p class="text-muted-foreground truncate text-xs">
812
+ {asset.originalFilename}
813
+ </p>
814
+ </div>
815
+ </button>
816
+ {/each}
817
+ </div>
818
+
819
+ <!-- Pagination -->
820
+ {#if totalPages > 1}
821
+ <div class="border-border flex items-center justify-center gap-1 border-t px-4 py-3">
822
+ <button
823
+ onclick={() => goToPage(currentPage - 1)}
824
+ disabled={currentPage <= 1 || loading}
825
+ class="hover:bg-muted rounded p-1.5 transition-colors disabled:pointer-events-none disabled:opacity-30"
826
+ >
827
+ <ChevronLeft size={16} />
828
+ </button>
829
+ {#each visiblePages as pg}
830
+ {#if pg === '...'}
831
+ <span class="text-muted-foreground px-1.5 text-sm">...</span>
832
+ {:else}
833
+ <button
834
+ onclick={() => goToPage(pg)}
835
+ disabled={loading}
836
+ class="min-w-[32px] rounded px-2 py-1 text-sm font-medium transition-colors {pg ===
837
+ currentPage
838
+ ? 'bg-foreground text-background'
839
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground'}"
840
+ >
841
+ {pg}
842
+ </button>
843
+ {/if}
844
+ {/each}
845
+ <button
846
+ onclick={() => goToPage(currentPage + 1)}
847
+ disabled={currentPage >= totalPages || loading}
848
+ class="hover:bg-muted rounded p-1.5 transition-colors disabled:pointer-events-none disabled:opacity-30"
849
+ >
850
+ <ChevronRight size={16} />
851
+ </button>
852
+ </div>
853
+ {/if}
854
+ {:else}
855
+ <!-- List View -->
856
+ <div class="w-full">
857
+ <!-- Table header -->
858
+ <div
859
+ class="bg-muted/30 border-border text-muted-foreground hidden items-center gap-4 border-b px-4 py-2 text-xs font-medium tracking-wider uppercase md:grid md:grid-cols-[auto_40px_1fr_100px_100px_80px_50px_100px]"
860
+ >
861
+ <div class="w-4">
862
+ <Checkbox checked={allSelected} onCheckedChange={toggleSelectAll} />
863
+ </div>
864
+ <div></div>
865
+ <div>Filename</div>
866
+ <div>Resolution</div>
867
+ <div>Mime type</div>
868
+ <div>Size</div>
869
+ <div>Refs</div>
870
+ <div>Last updated</div>
871
+ </div>
872
+ <!-- Mobile header -->
873
+ <div
874
+ class="bg-muted/30 border-border text-muted-foreground flex items-center gap-3 border-b px-4 py-2 text-xs font-medium tracking-wider uppercase md:hidden"
875
+ >
876
+ <div class="w-4">
877
+ <Checkbox checked={allSelected} onCheckedChange={toggleSelectAll} />
878
+ </div>
879
+ <div>Assets</div>
880
+ </div>
881
+ {#each sortedAssets as asset (asset.id)}
882
+ <!-- Desktop row -->
883
+ <button
884
+ onclick={() => {
885
+ if (selectable && multiSelect) {
886
+ openAssetDetail(asset);
887
+ } else if (isSelectMode) {
888
+ toggleSelect(asset.id);
889
+ } else if (selectable && onSelect) {
890
+ onSelect(asset);
891
+ } else {
892
+ openAssetDetail(asset);
893
+ }
894
+ }}
895
+ class="border-border hidden w-full items-center gap-4 border-b px-4 py-2 text-left transition-colors md:grid md:grid-cols-[auto_40px_1fr_100px_100px_80px_50px_100px] {selectedAsset?.id ===
896
+ asset.id
897
+ ? 'bg-muted'
898
+ : selectedIds.has(asset.id)
899
+ ? 'bg-muted/70'
900
+ : 'hover:bg-muted/50'}"
901
+ >
902
+ <div class="w-4">
903
+ <Checkbox
904
+ checked={selectedIds.has(asset.id)}
905
+ onCheckedChange={() => toggleSelect(asset.id)}
906
+ onclick={(e) => e.stopPropagation()}
907
+ />
908
+ </div>
909
+ <div class="bg-muted/30 h-10 w-10 overflow-hidden rounded">
910
+ {#if isImage(asset)}
911
+ <img
912
+ src={getThumbnailUrl(asset)}
913
+ alt={asset.alt || asset.originalFilename}
914
+ class="h-full w-full object-cover"
915
+ loading="lazy"
916
+ />
917
+ {:else}
918
+ <div class="flex h-full items-center justify-center">
919
+ <FileText class="text-muted-foreground h-4 w-4" />
920
+ </div>
921
+ {/if}
922
+ </div>
923
+ <div class="min-w-0">
924
+ <p class="truncate text-sm">{asset.originalFilename}</p>
925
+ </div>
926
+ <div class="text-muted-foreground text-xs">
927
+ {asset.width && asset.height ? `${asset.width}x${asset.height}` : '-'}
928
+ </div>
929
+ <div class="text-muted-foreground text-xs">{asset.mimeType}</div>
930
+ <div class="text-muted-foreground text-xs">{formatSize(asset.size)}</div>
931
+ <div class="text-muted-foreground text-xs">{referenceCounts[asset.id] || 0}</div>
932
+ <div class="text-muted-foreground text-xs">
933
+ {formatDate(asset.updatedAt || asset.createdAt)}
934
+ </div>
935
+ </button>
936
+ <!-- Mobile row -->
937
+ <button
938
+ onclick={() => {
939
+ if (selectable && multiSelect) {
940
+ openAssetDetail(asset);
941
+ } else if (isSelectMode) {
942
+ toggleSelect(asset.id);
943
+ } else if (selectable && onSelect) {
944
+ onSelect(asset);
945
+ } else {
946
+ openAssetDetail(asset);
947
+ }
948
+ }}
949
+ class="border-border flex w-full items-center gap-3 border-b px-4 py-2 text-left transition-colors md:hidden {selectedAsset?.id ===
950
+ asset.id
951
+ ? 'bg-muted'
952
+ : selectedIds.has(asset.id)
953
+ ? 'bg-muted/70'
954
+ : 'hover:bg-muted/50'}"
955
+ >
956
+ <div class="w-4">
957
+ <Checkbox
958
+ checked={selectedIds.has(asset.id)}
959
+ onCheckedChange={() => toggleSelect(asset.id)}
960
+ onclick={(e) => e.stopPropagation()}
961
+ />
962
+ </div>
963
+ <div class="bg-muted/30 h-10 w-10 shrink-0 overflow-hidden rounded">
964
+ {#if isImage(asset)}
965
+ <img
966
+ src={getThumbnailUrl(asset)}
967
+ alt={asset.alt || asset.originalFilename}
968
+ class="h-full w-full object-cover"
969
+ loading="lazy"
970
+ />
971
+ {:else}
972
+ <div class="flex h-full items-center justify-center">
973
+ <FileText class="text-muted-foreground h-4 w-4" />
974
+ </div>
975
+ {/if}
976
+ </div>
977
+ <div class="min-w-0 flex-1">
978
+ <p class="truncate text-sm">{asset.originalFilename}</p>
979
+ <p class="text-muted-foreground text-xs">{formatSize(asset.size)}</p>
980
+ </div>
981
+ </button>
982
+ {/each}
983
+
984
+ <!-- Pagination -->
985
+ {#if totalPages > 1}
986
+ <div
987
+ class="border-border flex items-center justify-center gap-1 border-t px-4 py-3"
988
+ >
989
+ <button
990
+ onclick={() => goToPage(currentPage - 1)}
991
+ disabled={currentPage <= 1 || loading}
992
+ class="hover:bg-muted rounded p-1.5 transition-colors disabled:pointer-events-none disabled:opacity-30"
993
+ >
994
+ <ChevronLeft size={16} />
995
+ </button>
996
+ {#each visiblePages as pg}
997
+ {#if pg === '...'}
998
+ <span class="text-muted-foreground px-1.5 text-sm">...</span>
999
+ {:else}
1000
+ <button
1001
+ onclick={() => goToPage(pg)}
1002
+ disabled={loading}
1003
+ class="min-w-[32px] rounded px-2 py-1 text-sm font-medium transition-colors {pg ===
1004
+ currentPage
1005
+ ? 'bg-foreground text-background'
1006
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground'}"
1007
+ >
1008
+ {pg}
1009
+ </button>
1010
+ {/if}
1011
+ {/each}
1012
+ <button
1013
+ onclick={() => goToPage(currentPage + 1)}
1014
+ disabled={currentPage >= totalPages || loading}
1015
+ class="hover:bg-muted rounded p-1.5 transition-colors disabled:pointer-events-none disabled:opacity-30"
1016
+ >
1017
+ <ChevronRight size={16} />
1018
+ </button>
1019
+ </div>
1020
+ {/if}
1021
+ </div>
1022
+ {/if}
1023
+ {/if}
1024
+
1025
+ {#snippet failed(error, reset)}
1026
+ <div class="border-destructive/30 bg-destructive/5 rounded-md border p-4 text-center">
1027
+ <p class="text-destructive font-medium">Media browser encountered an error</p>
1028
+ <p class="text-muted-foreground mt-1 text-sm">{error instanceof Error ? error.message : 'Unknown error'}</p>
1029
+ <button
1030
+ class="bg-primary text-primary-foreground mt-3 rounded px-4 py-2 text-sm"
1031
+ onclick={reset}
1032
+ >
1033
+ Retry
1034
+ </button>
1035
+ </div>
1036
+ {/snippet}
1037
+ </svelte:boundary>
1038
+ </div>
1039
+
1040
+ <!-- Asset Detail Sidebar (extends page on mobile, side panel on desktop) -->
1041
+ {#if selectedAsset}
1042
+ <div
1043
+ class="bg-background border-border flex flex-col border-t md:w-[350px] md:shrink-0 md:overflow-y-auto md:border-t-0 md:border-l"
1044
+ >
1045
+ <!-- Header -->
1046
+ <div class="border-border flex items-center justify-between border-b px-4 py-3">
1047
+ <!-- Back button (mobile only) -->
1048
+ <button
1049
+ onclick={closeAssetDetail}
1050
+ class="text-muted-foreground hover:text-foreground flex items-center gap-1 text-sm transition-colors md:hidden"
1051
+ >
1052
+ <ChevronLeft size={16} />
1053
+ Back
1054
+ </button>
1055
+ <!-- Filename -->
1056
+ <p
1057
+ class="min-w-0 flex-1 truncate pl-2 text-sm font-medium md:pl-0"
1058
+ title={selectedAsset.originalFilename}
1059
+ >
1060
+ {selectedAsset.originalFilename}
1061
+ </p>
1062
+ <div class="flex items-center gap-1">
1063
+ {#if !selectable}
1064
+ <Button
1065
+ variant="ghost"
1066
+ size="sm"
1067
+ class="h-7 w-7 p-0"
1068
+ onclick={() => deleteAsset(selectedAsset!)}
1069
+ title="Delete asset"
1070
+ >
1071
+ <Trash2 size={14} class="text-destructive" />
1072
+ </Button>
1073
+ {/if}
1074
+ <Button
1075
+ variant="ghost"
1076
+ size="sm"
1077
+ class="hidden h-7 w-7 p-0 md:flex"
1078
+ onclick={closeAssetDetail}
1079
+ title="Close"
1080
+ >
1081
+ <X size={14} />
1082
+ </Button>
1083
+ </div>
1084
+ </div>
1085
+
1086
+ <!-- Preview (click to enlarge) -->
1087
+ <div class="p-4 pb-0">
1088
+ {#if isImage(selectedAsset)}
1089
+ <button
1090
+ onclick={() => (lightboxOpen = true)}
1091
+ class="bg-muted/30 mb-3 w-full cursor-zoom-in overflow-hidden rounded-lg"
1092
+ title="Click to enlarge"
1093
+ >
1094
+ <img
1095
+ src={getThumbnailUrl(selectedAsset)}
1096
+ alt={selectedAsset.alt || selectedAsset.originalFilename}
1097
+ class="w-full object-contain"
1098
+ style="max-height: 200px;"
1099
+ />
1100
+ </button>
1101
+ {:else}
1102
+ <div
1103
+ class="bg-muted/30 mb-3 flex h-28 items-center justify-center overflow-hidden rounded-lg"
1104
+ >
1105
+ <FileText class="text-muted-foreground h-12 w-12" />
1106
+ </div>
1107
+ {/if}
1108
+ </div>
1109
+
1110
+ <!-- Tabs -->
1111
+ <div class="border-border flex border-b">
1112
+ <button
1113
+ onclick={() => (detailTab = 'details')}
1114
+ class="flex-1 px-4 py-2.5 text-sm font-medium transition-colors {detailTab === 'details'
1115
+ ? 'border-foreground text-foreground border-b-2'
1116
+ : 'text-muted-foreground hover:text-foreground'}"
1117
+ >
1118
+ Details
1119
+ </button>
1120
+ <button
1121
+ onclick={() => {
1122
+ detailTab = 'references';
1123
+ if (selectedAssetRefs.length === 0 && selectedAsset) {
1124
+ fetchAssetReferences(selectedAsset.id);
1125
+ }
1126
+ }}
1127
+ class="flex-1 px-4 py-2.5 text-sm font-medium transition-colors {detailTab ===
1128
+ 'references'
1129
+ ? 'border-foreground text-foreground border-b-2'
1130
+ : 'text-muted-foreground hover:text-foreground'}"
1131
+ >
1132
+ References ({selectedRefCount})
1133
+ </button>
1134
+ </div>
1135
+
1136
+ <!-- Tab content -->
1137
+ <div class="flex-1 overflow-y-auto p-4">
1138
+ {#if detailTab === 'details'}
1139
+ <!-- Info -->
1140
+ <div class="mb-4 space-y-2 text-sm">
1141
+ <div class="flex justify-between">
1142
+ <span class="text-muted-foreground">Filename</span>
1143
+ <span
1144
+ class="max-w-[180px] truncate font-medium"
1145
+ title={selectedAsset.originalFilename}
1146
+ >
1147
+ {selectedAsset.originalFilename}
1148
+ </span>
1149
+ </div>
1150
+ <div class="flex justify-between">
1151
+ <span class="text-muted-foreground">Type</span>
1152
+ <span>{selectedAsset.mimeType}</span>
1153
+ </div>
1154
+ <div class="flex justify-between">
1155
+ <span class="text-muted-foreground">Size</span>
1156
+ <span>{formatSize(selectedAsset.size)}</span>
1157
+ </div>
1158
+ {#if selectedAsset.width && selectedAsset.height}
1159
+ <div class="flex justify-between">
1160
+ <span class="text-muted-foreground">Dimensions</span>
1161
+ <span>{selectedAsset.width} x {selectedAsset.height}</span>
1162
+ </div>
1163
+ {/if}
1164
+ <div class="flex justify-between">
1165
+ <span class="text-muted-foreground">Uploaded</span>
1166
+ <span>{formatDate(selectedAsset.createdAt)}</span>
1167
+ </div>
1168
+ </div>
1169
+
1170
+ <!-- Actions -->
1171
+ <div class="mb-4 flex gap-2">
1172
+ <Button
1173
+ variant="outline"
1174
+ size="sm"
1175
+ class="flex-1"
1176
+ onclick={() => downloadAsset(selectedAsset!)}
1177
+ >
1178
+ <Download size={14} class="mr-1.5" />
1179
+ Download
1180
+ </Button>
1181
+ <Button
1182
+ variant="outline"
1183
+ size="sm"
1184
+ class="flex-1"
1185
+ onclick={() => copyAssetUrl(selectedAsset!)}
1186
+ >
1187
+ <Link size={14} class="mr-1.5" />
1188
+ {copiedUrl ? 'Copied!' : 'Copy URL'}
1189
+ </Button>
1190
+ </div>
1191
+
1192
+ <Separator class="my-4" />
1193
+
1194
+ <!-- Metadata editing -->
1195
+ <div class="space-y-3">
1196
+ <div>
1197
+ <Label for="asset-title" class="text-xs">Title</Label>
1198
+ <Input
1199
+ id="asset-title"
1200
+ bind:value={editTitle}
1201
+ class="mt-1 h-8 text-sm"
1202
+ placeholder="Asset title"
1203
+ />
1204
+ </div>
1205
+ <div>
1206
+ <Label for="asset-description" class="text-xs">Description</Label>
1207
+ <textarea
1208
+ id="asset-description"
1209
+ bind:value={editDescription}
1210
+ class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring mt-1 flex w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
1211
+ rows="2"
1212
+ placeholder="Description"
1213
+ ></textarea>
1214
+ </div>
1215
+ <div>
1216
+ <Label for="asset-alt" class="text-xs">Alt text</Label>
1217
+ <Input
1218
+ id="asset-alt"
1219
+ bind:value={editAlt}
1220
+ class="mt-1 h-8 text-sm"
1221
+ placeholder="Alternative text"
1222
+ />
1223
+ </div>
1224
+ <div>
1225
+ <Label for="asset-credit" class="text-xs">Credit line</Label>
1226
+ <Input
1227
+ id="asset-credit"
1228
+ bind:value={editCreditLine}
1229
+ class="mt-1 h-8 text-sm"
1230
+ placeholder="Credit / attribution"
1231
+ />
1232
+ </div>
1233
+
1234
+ <Button onclick={saveMetadata} disabled={isSaving} size="sm" class="w-full">
1235
+ {isSaving ? 'Saving...' : 'Save changes'}
1236
+ </Button>
1237
+ </div>
1238
+ {:else}
1239
+ <!-- References tab -->
1240
+ {#if loadingRefs}
1241
+ <p class="text-muted-foreground text-sm">Loading references...</p>
1242
+ {:else if selectedAssetRefs.length === 0}
1243
+ <p class="text-muted-foreground text-sm">Not used in any documents</p>
1244
+ {:else}
1245
+ <div class="space-y-1">
1246
+ {#each selectedAssetRefs as ref (ref.documentId)}
1247
+ <button
1248
+ onclick={() => {
1249
+ const params = new URLSearchParams(page.url.searchParams);
1250
+ params.set('docType', ref.type);
1251
+ params.set('docId', ref.documentId);
1252
+ params.set('view', 'structure');
1253
+ params.delete('action');
1254
+ goto(`/admin?${params.toString()}`);
1255
+ }}
1256
+ class="hover:bg-muted flex w-full items-center gap-3 rounded-md p-2.5 text-left transition-colors"
1257
+ >
1258
+ <div class="bg-muted flex h-9 w-9 shrink-0 items-center justify-center rounded">
1259
+ <FileText size={16} class="text-muted-foreground" />
1260
+ </div>
1261
+ <div class="min-w-0">
1262
+ <p class="truncate text-sm font-medium">{ref.title}</p>
1263
+ <p class="text-muted-foreground truncate text-xs">
1264
+ {ref.type}{ref.status ? ` · ${ref.status}` : ''}
1265
+ </p>
1266
+ </div>
1267
+ </button>
1268
+ {/each}
1269
+ </div>
1270
+ {/if}
1271
+ {/if}
1272
+ </div>
1273
+ </div>
1274
+ {/if}
1275
+ </div>
1276
+ </div>
1277
+
1278
+ <!-- Lightbox Modal -->
1279
+ {#if selectedAsset && isImage(selectedAsset)}
1280
+ <Dialog.Root bind:open={lightboxOpen}>
1281
+ <Dialog.Content
1282
+ showCloseButton={false}
1283
+ class="flex max-h-[90vh] max-w-[90vw] flex-col overflow-hidden p-0 sm:max-w-[90vw]"
1284
+ >
1285
+ <Dialog.Header class="border-border border-b px-4 py-3">
1286
+ <Dialog.Title class="truncate text-sm font-medium"
1287
+ >{selectedAsset.originalFilename}</Dialog.Title
1288
+ >
1289
+ </Dialog.Header>
1290
+ <div class="flex flex-1 items-center justify-center overflow-hidden p-4">
1291
+ <img
1292
+ src={getThumbnailUrl(selectedAsset)}
1293
+ alt={selectedAsset.alt || selectedAsset.originalFilename}
1294
+ class="max-h-[70vh] max-w-full object-contain"
1295
+ />
1296
+ </div>
1297
+ <div class="border-border flex items-center justify-between border-t px-4 py-3">
1298
+ <div class="flex items-center gap-2">
1299
+ <Button variant="outline" size="sm" onclick={() => downloadAsset(selectedAsset!)}>
1300
+ <Download size={14} class="mr-1.5" />
1301
+ Download
1302
+ </Button>
1303
+ <Button variant="outline" size="sm" onclick={() => copyAssetUrl(selectedAsset!)}>
1304
+ <Link size={14} class="mr-1.5" />
1305
+ {copiedUrl ? 'Copied!' : 'Copy URL'}
1306
+ </Button>
1307
+ </div>
1308
+ <Button variant="outline" size="sm" onclick={() => (lightboxOpen = false)}>Close</Button>
1309
+ </div>
1310
+ </Dialog.Content>
1311
+ </Dialog.Root>
1312
+ {/if}
1313
+
1314
+ <!-- Upload Modal -->
1315
+ <Dialog.Root
1316
+ bind:open={showUploadModal}
1317
+ onOpenChange={(v) => {
1318
+ if (!v && !isUploading) {
1319
+ showUploadModal = false;
1320
+ }
1321
+ }}
1322
+ >
1323
+ <Dialog.Content class="max-w-lg">
1324
+ <Dialog.Header>
1325
+ <Dialog.Title>Upload Assets</Dialog.Title>
1326
+ </Dialog.Header>
1327
+
1328
+ <!-- Drop zone -->
1329
+ <div
1330
+ class="border-border mt-2 flex flex-col items-center justify-center rounded-lg border-2 border-dashed px-6 py-10 transition-colors {modalIsDragging
1331
+ ? 'border-primary bg-primary/5'
1332
+ : 'hover:bg-muted/50'}"
1333
+ ondragover={(e) => {
1334
+ e.preventDefault();
1335
+ modalIsDragging = true;
1336
+ }}
1337
+ ondragleave={(e) => {
1338
+ e.preventDefault();
1339
+ modalIsDragging = false;
1340
+ }}
1341
+ ondrop={(e) => {
1342
+ e.preventDefault();
1343
+ modalIsDragging = false;
1344
+ addFilesToQueue(e.dataTransfer?.files || null);
1345
+ }}
1346
+ role="button"
1347
+ tabindex="0"
1348
+ onclick={() => modalFileInputRef?.click()}
1349
+ onkeydown={(e) => {
1350
+ if (e.key === 'Enter' || e.key === ' ') modalFileInputRef?.click();
1351
+ }}
1352
+ >
1353
+ <FileImage size={32} class="text-muted-foreground mb-3" />
1354
+ <p class="text-sm font-medium">
1355
+ {modalIsDragging ? 'Drop files here' : 'Drag and drop files here'}
1356
+ </p>
1357
+ <p class="text-muted-foreground mt-1 text-xs">or click to browse</p>
1358
+ </div>
1359
+
1360
+ <input
1361
+ bind:this={modalFileInputRef}
1362
+ type="file"
1363
+ multiple
1364
+ accept="image/*,.pdf,.txt"
1365
+ class="hidden"
1366
+ onchange={(e) => {
1367
+ const target = e.target as HTMLInputElement;
1368
+ addFilesToQueue(target.files);
1369
+ target.value = '';
1370
+ }}
1371
+ />
1372
+
1373
+ <!-- Upload queue -->
1374
+ {#if uploadQueue.length > 0}
1375
+ <div class="mt-4 max-h-48 space-y-2 overflow-y-auto">
1376
+ {#each uploadQueue as item}
1377
+ <div class="border-border flex items-center gap-3 rounded-md border px-3 py-2">
1378
+ <div class="min-w-0 flex-1">
1379
+ <p class="truncate text-sm">{item.file.name}</p>
1380
+ <p class="text-muted-foreground text-xs">{formatSize(item.file.size)}</p>
1381
+ </div>
1382
+ {#if item.status === 'uploading'}
1383
+ <div
1384
+ class="border-primary h-4 w-4 shrink-0 animate-spin rounded-full border-2 border-t-transparent"
1385
+ ></div>
1386
+ {:else if item.status === 'done'}
1387
+ <CheckCircle2 size={16} class="shrink-0 text-green-500" />
1388
+ {:else if item.status === 'failed'}
1389
+ <AlertCircle size={16} class="text-destructive shrink-0" />
1390
+ {:else}
1391
+ <div class="bg-muted h-4 w-4 shrink-0 rounded-full"></div>
1392
+ {/if}
1393
+ </div>
1394
+ {/each}
1395
+ </div>
1396
+ {/if}
1397
+ </Dialog.Content>
1398
+ </Dialog.Root>