@orthacms/media-admin 0.0.0-reserve.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 (214) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +1 -0
  6. package/dist/lib/components/AssetActionsMenu/index.d.ts +37 -0
  7. package/dist/lib/components/AssetActionsMenu/index.d.ts.map +1 -0
  8. package/dist/lib/components/AssetActionsMenu/index.js +57 -0
  9. package/dist/lib/components/AssetDetailDrawer/MetaRow/index.d.ts +11 -0
  10. package/dist/lib/components/AssetDetailDrawer/MetaRow/index.d.ts.map +1 -0
  11. package/dist/lib/components/AssetDetailDrawer/MetaRow/index.js +9 -0
  12. package/dist/lib/components/AssetDetailDrawer/index.d.ts +28 -0
  13. package/dist/lib/components/AssetDetailDrawer/index.d.ts.map +1 -0
  14. package/dist/lib/components/AssetDetailDrawer/index.js +93 -0
  15. package/dist/lib/components/EntryMediaTab/MediaFieldSection/index.d.ts +28 -0
  16. package/dist/lib/components/EntryMediaTab/MediaFieldSection/index.d.ts.map +1 -0
  17. package/dist/lib/components/EntryMediaTab/MediaFieldSection/index.js +52 -0
  18. package/dist/lib/components/EntryMediaTab/index.d.ts +11 -0
  19. package/dist/lib/components/EntryMediaTab/index.d.ts.map +1 -0
  20. package/dist/lib/components/EntryMediaTab/index.js +31 -0
  21. package/dist/lib/components/FolderActionsMenu/index.d.ts +21 -0
  22. package/dist/lib/components/FolderActionsMenu/index.d.ts.map +1 -0
  23. package/dist/lib/components/FolderActionsMenu/index.js +25 -0
  24. package/dist/lib/components/MediaAltTextWidget/index.d.ts +9 -0
  25. package/dist/lib/components/MediaAltTextWidget/index.d.ts.map +1 -0
  26. package/dist/lib/components/MediaAltTextWidget/index.js +66 -0
  27. package/dist/lib/components/MediaBreadcrumbs/index.d.ts +11 -0
  28. package/dist/lib/components/MediaBreadcrumbs/index.d.ts.map +1 -0
  29. package/dist/lib/components/MediaEmptyState/index.d.ts +13 -0
  30. package/dist/lib/components/MediaEmptyState/index.d.ts.map +1 -0
  31. package/dist/lib/components/MediaEmptyState/index.js +38 -0
  32. package/dist/lib/components/MediaFieldControl/MediaFieldItem/index.d.ts +34 -0
  33. package/dist/lib/components/MediaFieldControl/MediaFieldItem/index.d.ts.map +1 -0
  34. package/dist/lib/components/MediaFieldControl/MediaFieldItem/index.js +117 -0
  35. package/dist/lib/components/MediaFieldControl/index.d.ts +61 -0
  36. package/dist/lib/components/MediaFieldControl/index.d.ts.map +1 -0
  37. package/dist/lib/components/MediaFieldControl/index.js +422 -0
  38. package/dist/lib/components/MediaFoldersNav/MediaFolderTreeItem/index.d.ts +17 -0
  39. package/dist/lib/components/MediaFoldersNav/MediaFolderTreeItem/index.d.ts.map +1 -0
  40. package/dist/lib/components/MediaFoldersNav/MediaFolderTreeItem/index.js +37 -0
  41. package/dist/lib/components/MediaFoldersNav/index.d.ts +22 -0
  42. package/dist/lib/components/MediaFoldersNav/index.d.ts.map +1 -0
  43. package/dist/lib/components/MediaFoldersNav/index.js +40 -0
  44. package/dist/lib/components/MediaGrid/MediaAssetCard/index.d.ts +18 -0
  45. package/dist/lib/components/MediaGrid/MediaAssetCard/index.d.ts.map +1 -0
  46. package/dist/lib/components/MediaGrid/MediaAssetCard/index.js +29 -0
  47. package/dist/lib/components/MediaGrid/MediaFolderCard/index.d.ts +16 -0
  48. package/dist/lib/components/MediaGrid/MediaFolderCard/index.d.ts.map +1 -0
  49. package/dist/lib/components/MediaGrid/MediaFolderCard/index.js +25 -0
  50. package/dist/lib/components/MediaGrid/index.d.ts +24 -0
  51. package/dist/lib/components/MediaGrid/index.d.ts.map +1 -0
  52. package/dist/lib/components/MediaGrid/index.js +20 -0
  53. package/dist/lib/components/MediaKindBadge/index.d.ts +10 -0
  54. package/dist/lib/components/MediaKindBadge/index.d.ts.map +1 -0
  55. package/dist/lib/components/MediaKindBadge/index.js +30 -0
  56. package/dist/lib/components/MediaKindIcon/index.d.ts +11 -0
  57. package/dist/lib/components/MediaKindIcon/index.d.ts.map +1 -0
  58. package/dist/lib/components/MediaKindIcon/index.js +20 -0
  59. package/dist/lib/components/MediaLibrarySkeleton/index.d.ts +18 -0
  60. package/dist/lib/components/MediaLibrarySkeleton/index.d.ts.map +1 -0
  61. package/dist/lib/components/MediaLibrarySkeleton/index.js +43 -0
  62. package/dist/lib/components/MediaPagination/index.d.ts +26 -0
  63. package/dist/lib/components/MediaPagination/index.d.ts.map +1 -0
  64. package/dist/lib/components/MediaPagination/index.js +52 -0
  65. package/dist/lib/components/MediaPickerDialog/MediaPickerTile/index.d.ts +17 -0
  66. package/dist/lib/components/MediaPickerDialog/MediaPickerTile/index.d.ts.map +1 -0
  67. package/dist/lib/components/MediaPickerDialog/MediaPickerTile/index.js +43 -0
  68. package/dist/lib/components/MediaPickerDialog/index.d.ts +21 -0
  69. package/dist/lib/components/MediaPickerDialog/index.d.ts.map +1 -0
  70. package/dist/lib/components/MediaPickerDialog/index.js +208 -0
  71. package/dist/lib/components/MediaSelectionBar/index.d.ts +18 -0
  72. package/dist/lib/components/MediaSelectionBar/index.d.ts.map +1 -0
  73. package/dist/lib/components/MediaSelectionBar/index.js +26 -0
  74. package/dist/lib/components/MediaStorageStat/index.d.ts +9 -0
  75. package/dist/lib/components/MediaStorageStat/index.d.ts.map +1 -0
  76. package/dist/lib/components/MediaStorageStat/index.js +31 -0
  77. package/dist/lib/components/MediaStorageWidget/index.d.ts +10 -0
  78. package/dist/lib/components/MediaStorageWidget/index.d.ts.map +1 -0
  79. package/dist/lib/components/MediaStorageWidget/index.js +93 -0
  80. package/dist/lib/components/MediaTable/MediaAssetRow/index.d.ts +17 -0
  81. package/dist/lib/components/MediaTable/MediaAssetRow/index.d.ts.map +1 -0
  82. package/dist/lib/components/MediaTable/MediaFolderRow/index.d.ts +16 -0
  83. package/dist/lib/components/MediaTable/MediaFolderRow/index.d.ts.map +1 -0
  84. package/dist/lib/components/MediaTable/index.d.ts +26 -0
  85. package/dist/lib/components/MediaTable/index.d.ts.map +1 -0
  86. package/dist/lib/components/MediaThumbnail/index.d.ts +19 -0
  87. package/dist/lib/components/MediaThumbnail/index.d.ts.map +1 -0
  88. package/dist/lib/components/MediaThumbnail/index.js +26 -0
  89. package/dist/lib/components/MediaToolbar/index.d.ts +19 -0
  90. package/dist/lib/components/MediaToolbar/index.d.ts.map +1 -0
  91. package/dist/lib/components/MediaToolbar/index.js +62 -0
  92. package/dist/lib/components/MediaTopBar/index.d.ts +20 -0
  93. package/dist/lib/components/MediaTopBar/index.d.ts.map +1 -0
  94. package/dist/lib/components/MediaTopBar/index.js +35 -0
  95. package/dist/lib/components/MediaUploadBanner/MediaUploadRow/index.d.ts +14 -0
  96. package/dist/lib/components/MediaUploadBanner/MediaUploadRow/index.d.ts.map +1 -0
  97. package/dist/lib/components/MediaUploadBanner/MediaUploadRow/index.js +51 -0
  98. package/dist/lib/components/MediaUploadBanner/index.d.ts +24 -0
  99. package/dist/lib/components/MediaUploadBanner/index.d.ts.map +1 -0
  100. package/dist/lib/components/MediaUploadBanner/index.js +99 -0
  101. package/dist/lib/components/MediaUploadsWidget/index.d.ts +7 -0
  102. package/dist/lib/components/MediaUploadsWidget/index.d.ts.map +1 -0
  103. package/dist/lib/components/MediaUploadsWidget/index.js +70 -0
  104. package/dist/lib/components/MoveAssetsDialog/DestinationOption/index.d.ts +19 -0
  105. package/dist/lib/components/MoveAssetsDialog/DestinationOption/index.d.ts.map +1 -0
  106. package/dist/lib/components/MoveAssetsDialog/DestinationOption/index.js +10 -0
  107. package/dist/lib/components/MoveAssetsDialog/index.d.ts +18 -0
  108. package/dist/lib/components/MoveAssetsDialog/index.d.ts.map +1 -0
  109. package/dist/lib/components/MoveAssetsDialog/index.js +41 -0
  110. package/dist/lib/components/NewFolderDialog/index.d.ts +13 -0
  111. package/dist/lib/components/NewFolderDialog/index.d.ts.map +1 -0
  112. package/dist/lib/components/NewFolderDialog/index.js +45 -0
  113. package/dist/lib/components/RenameDialog/index.d.ts +13 -0
  114. package/dist/lib/components/RenameDialog/index.d.ts.map +1 -0
  115. package/dist/lib/components/RenameDialog/index.js +37 -0
  116. package/dist/lib/components/UploadDialog/StagedFileRow/index.d.ts +25 -0
  117. package/dist/lib/components/UploadDialog/StagedFileRow/index.d.ts.map +1 -0
  118. package/dist/lib/components/UploadDialog/StagedFileRow/index.js +82 -0
  119. package/dist/lib/components/UploadDialog/index.d.ts +50 -0
  120. package/dist/lib/components/UploadDialog/index.d.ts.map +1 -0
  121. package/dist/lib/components/UploadDialog/index.js +120 -0
  122. package/dist/lib/components/WysiwygLibrarySource/index.d.ts +14 -0
  123. package/dist/lib/components/WysiwygLibrarySource/index.d.ts.map +1 -0
  124. package/dist/lib/components/WysiwygLibrarySource/index.js +28 -0
  125. package/dist/lib/components/WysiwygUploadSource/index.d.ts +25 -0
  126. package/dist/lib/components/WysiwygUploadSource/index.d.ts.map +1 -0
  127. package/dist/lib/components/WysiwygUploadSource/index.js +102 -0
  128. package/dist/lib/constants/index.d.ts +82 -0
  129. package/dist/lib/constants/index.d.ts.map +1 -0
  130. package/dist/lib/constants/index.js +75 -0
  131. package/dist/lib/hooks/useMediaInsights/index.d.ts +32 -0
  132. package/dist/lib/hooks/useMediaInsights/index.d.ts.map +1 -0
  133. package/dist/lib/hooks/useMediaInsights/index.js +85 -0
  134. package/dist/lib/hooks/useMediaLibrary/index.d.ts +83 -0
  135. package/dist/lib/hooks/useMediaLibrary/index.d.ts.map +1 -0
  136. package/dist/lib/hooks/useMediaLibrary/index.js +341 -0
  137. package/dist/lib/hooks/usePendingMediaUploads/index.d.ts +26 -0
  138. package/dist/lib/hooks/usePendingMediaUploads/index.d.ts.map +1 -0
  139. package/dist/lib/hooks/usePendingMediaUploads/index.js +217 -0
  140. package/dist/lib/hooks/useUploadQueue/index.d.ts +33 -0
  141. package/dist/lib/hooks/useUploadQueue/index.d.ts.map +1 -0
  142. package/dist/lib/hooks/useUploadQueue/index.js +218 -0
  143. package/dist/lib/hooks/useWysiwygAssetPicker/index.d.ts +23 -0
  144. package/dist/lib/hooks/useWysiwygAssetPicker/index.d.ts.map +1 -0
  145. package/dist/lib/infrastructure/apiMessage/index.d.ts +17 -0
  146. package/dist/lib/infrastructure/apiMessage/index.d.ts.map +1 -0
  147. package/dist/lib/infrastructure/apiMessage/index.js +30 -0
  148. package/dist/lib/infrastructure/httpMediaGateway/index.d.ts +9 -0
  149. package/dist/lib/infrastructure/httpMediaGateway/index.d.ts.map +1 -0
  150. package/dist/lib/infrastructure/httpMediaGateway/index.js +157 -0
  151. package/dist/lib/infrastructure/httpMediaInsightsGateway/index.d.ts +8 -0
  152. package/dist/lib/infrastructure/httpMediaInsightsGateway/index.d.ts.map +1 -0
  153. package/dist/lib/infrastructure/httpMediaInsightsGateway/index.js +35 -0
  154. package/dist/lib/infrastructure/mediaGateway/index.d.ts +120 -0
  155. package/dist/lib/infrastructure/mediaGateway/index.d.ts.map +1 -0
  156. package/dist/lib/infrastructure/mediaGateway/index.js +1 -0
  157. package/dist/lib/infrastructure/mediaInsightsGateway/index.d.ts +46 -0
  158. package/dist/lib/infrastructure/mediaInsightsGateway/index.d.ts.map +1 -0
  159. package/dist/lib/infrastructure/mediaInsightsGateway/index.js +6 -0
  160. package/dist/lib/infrastructure/mediaKeys/index.d.ts +23 -0
  161. package/dist/lib/infrastructure/mediaKeys/index.d.ts.map +1 -0
  162. package/dist/lib/infrastructure/mediaKeys/index.js +11 -0
  163. package/dist/lib/infrastructure/mediaMapper/index.d.ts +50 -0
  164. package/dist/lib/infrastructure/mediaMapper/index.d.ts.map +1 -0
  165. package/dist/lib/infrastructure/mediaMapper/index.js +39 -0
  166. package/dist/lib/pages/MediaLibraryPage/index.d.ts +23 -0
  167. package/dist/lib/pages/MediaLibraryPage/index.d.ts.map +1 -0
  168. package/dist/lib/pages/MediaLibraryPage/index.js +425 -0
  169. package/dist/lib/types/mediaAsset/index.d.ts +44 -0
  170. package/dist/lib/types/mediaAsset/index.d.ts.map +1 -0
  171. package/dist/lib/types/mediaAsset/index.js +1 -0
  172. package/dist/lib/types/mediaFieldDisplay/index.d.ts +48 -0
  173. package/dist/lib/types/mediaFieldDisplay/index.d.ts.map +1 -0
  174. package/dist/lib/types/mediaFieldDisplay/index.js +1 -0
  175. package/dist/lib/types/mediaFolder/index.d.ts +16 -0
  176. package/dist/lib/types/mediaFolder/index.d.ts.map +1 -0
  177. package/dist/lib/types/mediaFolder/index.js +1 -0
  178. package/dist/lib/types/pendingUpload/index.d.ts +38 -0
  179. package/dist/lib/types/pendingUpload/index.d.ts.map +1 -0
  180. package/dist/lib/types/pendingUpload/index.js +1 -0
  181. package/dist/lib/types/upload/index.d.ts +37 -0
  182. package/dist/lib/types/upload/index.d.ts.map +1 -0
  183. package/dist/lib/types/upload/index.js +1 -0
  184. package/dist/lib/utils/assetGradient/index.d.ts +8 -0
  185. package/dist/lib/utils/assetGradient/index.d.ts.map +1 -0
  186. package/dist/lib/utils/assetGradient/index.js +16 -0
  187. package/dist/lib/utils/assetHandlers/index.d.ts +11 -0
  188. package/dist/lib/utils/assetHandlers/index.d.ts.map +1 -0
  189. package/dist/lib/utils/assetHandlers/index.js +17 -0
  190. package/dist/lib/utils/folderContents/index.d.ts +17 -0
  191. package/dist/lib/utils/folderContents/index.d.ts.map +1 -0
  192. package/dist/lib/utils/folderContents/index.js +35 -0
  193. package/dist/lib/utils/folderTree/index.d.ts +13 -0
  194. package/dist/lib/utils/folderTree/index.d.ts.map +1 -0
  195. package/dist/lib/utils/folderTree/index.js +15 -0
  196. package/dist/lib/utils/formatBytes/index.d.ts +6 -0
  197. package/dist/lib/utils/formatBytes/index.d.ts.map +1 -0
  198. package/dist/lib/utils/formatBytes/index.js +17 -0
  199. package/dist/lib/utils/formatDuration/index.d.ts +6 -0
  200. package/dist/lib/utils/formatDuration/index.d.ts.map +1 -0
  201. package/dist/lib/utils/formatDuration/index.js +13 -0
  202. package/dist/lib/utils/kindFromMime/index.d.ts +10 -0
  203. package/dist/lib/utils/kindFromMime/index.d.ts.map +1 -0
  204. package/dist/lib/utils/kindFromMime/index.js +27 -0
  205. package/dist/lib/utils/mediaAccept/index.d.ts +30 -0
  206. package/dist/lib/utils/mediaAccept/index.d.ts.map +1 -0
  207. package/dist/lib/utils/mediaAccept/index.js +63 -0
  208. package/dist/lib/utils/mediaPlugin/index.d.ts +15 -0
  209. package/dist/lib/utils/mediaPlugin/index.d.ts.map +1 -0
  210. package/dist/lib/utils/mediaPlugin/index.js +177 -0
  211. package/dist/lib/utils/toWysiwygEmbed/index.d.ts +23 -0
  212. package/dist/lib/utils/toWysiwygEmbed/index.d.ts.map +1 -0
  213. package/dist/lib/utils/toWysiwygEmbed/index.js +44 -0
  214. package/package.json +50 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ortha CMS contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # @orthacms/media-admin
2
+
3
+ Part of [Ortha CMS](https://github.com/ortha-source/ortha-cms).
4
+
5
+ ```sh
6
+ npm install @orthacms/media-admin
7
+ ```
@@ -0,0 +1,3 @@
1
+ export { MediaPlugin } from './lib/utils/mediaPlugin';
2
+ export type { MediaAdminPlugin } from './lib/utils/mediaPlugin';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { MediaPlugin } from './lib/utils/mediaPlugin';
@@ -0,0 +1,37 @@
1
+ import type { ReactNode } from 'react';
2
+ /** The set of actions offered for a single asset (each permission-gated). */
3
+ export type AssetActionHandlers = {
4
+ onDownload: () => void;
5
+ onCopyLink: () => void;
6
+ onDuplicate: () => void;
7
+ onRename: () => void;
8
+ onMove: () => void;
9
+ onDelete: () => void;
10
+ };
11
+ /** One asset action, used as the discriminant of a single dispatch callback. */
12
+ export type AssetActionKind = 'open' | 'download' | 'copyLink' | 'duplicate' | 'rename' | 'move' | 'delete';
13
+ /**
14
+ * The per-asset action menu — Download, Copy link, Duplicate, Rename, Move, and
15
+ * a destructive Delete. Shared by the grid tile and the detail drawer so the
16
+ * action set stays identical everywhere. Write actions are hidden unless the
17
+ * caller passes the matching permission (`canCreate` for Duplicate, `canUpdate`
18
+ * for Rename/Move, `canDelete` for Delete). Pass `trigger` to override the
19
+ * default ⋯ button.
20
+ *
21
+ * There is deliberately **no "Open" item**. In the drawer it re-opened the
22
+ * drawer you were already reading — a visible no-op — and on the tile it
23
+ * duplicated the two controls that already open the detail (the thumbnail and
24
+ * the filename are both buttons). The `'open'` action kind still exists; those
25
+ * two controls dispatch it.
26
+ */
27
+ export declare function AssetActionsMenu({ handlers, assetName, canCreate, canUpdate, canDelete, trigger, align }: {
28
+ handlers: AssetActionHandlers;
29
+ /** Names the default trigger ("Actions for hero.png"); omit in a drawer. */
30
+ assetName?: string;
31
+ canCreate: boolean;
32
+ canUpdate: boolean;
33
+ canDelete: boolean;
34
+ trigger?: ReactNode;
35
+ align?: 'start' | 'center' | 'end';
36
+ }): import("react").JSX.Element;
37
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/AssetActionsMenu/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAoBvC,6EAA6E;AAC7E,MAAM,MAAM,mBAAmB,GAAG;IAC9B,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC;AAEF,gFAAgF;AAChF,MAAM,MAAM,eAAe,GACrB,MAAM,GACN,UAAU,GACV,UAAU,GACV,WAAW,GACX,QAAQ,GACR,MAAM,GACN,QAAQ,CAAC;AAkCf;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,EAC7B,QAAQ,EACR,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,OAAO,EACP,KAAa,EAChB,EAAE;IACC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;CACtC,+BAiEA"}
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { defineMessages, useIntl } from 'react-intl';
3
+ import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@orthacms/design-system';
4
+ import { Copy, Download, FolderInput, Link2, MoreVertical, Pencil, Trash2 } from 'lucide-react';
5
+ /** Intl descriptors for {@link AssetActionsMenu}, co-located. */
6
+ const messages = defineMessages({
7
+ trigger: {
8
+ id: 'media.asset.actions.trigger',
9
+ defaultMessage: 'Asset actions'
10
+ },
11
+ /**
12
+ * The trigger's name when the caller knows which asset it belongs to. A
13
+ * grid of twenty tiles otherwise offers twenty buttons all called "Asset
14
+ * actions", which is the same defect the folder cards already fixed.
15
+ */
16
+ triggerFor: {
17
+ id: 'media.asset.actions.triggerFor',
18
+ defaultMessage: 'Actions for {name}'
19
+ },
20
+ download: {
21
+ id: 'media.asset.actions.download',
22
+ defaultMessage: 'Download'
23
+ },
24
+ copyLink: {
25
+ id: 'media.asset.actions.copyLink',
26
+ defaultMessage: 'Copy link'
27
+ },
28
+ duplicate: {
29
+ id: 'media.asset.actions.duplicate',
30
+ defaultMessage: 'Duplicate'
31
+ },
32
+ rename: { id: 'media.asset.actions.rename', defaultMessage: 'Rename' },
33
+ move: { id: 'media.asset.actions.move', defaultMessage: 'Move to…' },
34
+ delete: { id: 'media.asset.actions.delete', defaultMessage: 'Delete' }
35
+ });
36
+ /**
37
+ * The per-asset action menu — Download, Copy link, Duplicate, Rename, Move, and
38
+ * a destructive Delete. Shared by the grid tile and the detail drawer so the
39
+ * action set stays identical everywhere. Write actions are hidden unless the
40
+ * caller passes the matching permission (`canCreate` for Duplicate, `canUpdate`
41
+ * for Rename/Move, `canDelete` for Delete). Pass `trigger` to override the
42
+ * default ⋯ button.
43
+ *
44
+ * There is deliberately **no "Open" item**. In the drawer it re-opened the
45
+ * drawer you were already reading — a visible no-op — and on the tile it
46
+ * duplicated the two controls that already open the detail (the thumbnail and
47
+ * the filename are both buttons). The `'open'` action kind still exists; those
48
+ * two controls dispatch it.
49
+ */
50
+ export function AssetActionsMenu({ handlers, assetName, canCreate, canUpdate, canDelete, trigger, align = 'end' }) {
51
+ const intl = useIntl();
52
+ return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: trigger ?? (_jsx(Button, { variant: "ghost", size: "icon", className: "size-8 shadow-none", "aria-label": assetName
53
+ ? intl.formatMessage(messages.triggerFor, {
54
+ name: assetName
55
+ })
56
+ : intl.formatMessage(messages.trigger), children: _jsx(MoreVertical, { "aria-hidden": true }) })) }), _jsxs(DropdownMenuContent, { align: align, className: "w-48", children: [_jsxs(DropdownMenuItem, { onSelect: handlers.onDownload, children: [_jsx(Download, { "aria-hidden": true }), intl.formatMessage(messages.download)] }), _jsxs(DropdownMenuItem, { onSelect: handlers.onCopyLink, children: [_jsx(Link2, { "aria-hidden": true }), intl.formatMessage(messages.copyLink)] }), canCreate ? (_jsxs(DropdownMenuItem, { onSelect: handlers.onDuplicate, children: [_jsx(Copy, { "aria-hidden": true }), intl.formatMessage(messages.duplicate)] })) : null, canUpdate ? (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onSelect: handlers.onRename, children: [_jsx(Pencil, { "aria-hidden": true }), intl.formatMessage(messages.rename)] }), _jsxs(DropdownMenuItem, { onSelect: handlers.onMove, children: [_jsx(FolderInput, { "aria-hidden": true }), intl.formatMessage(messages.move)] })] })) : null, canDelete ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { className: "text-destructive focus:text-destructive", onSelect: handlers.onDelete, children: [_jsx(Trash2, { "aria-hidden": true }), intl.formatMessage(messages.delete)] })] })) : null] })] }));
57
+ }
@@ -0,0 +1,11 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * One label/value line in the detail drawer's metadata list — a muted term on
4
+ * the left and its value on the right, aligned as a `dt`/`dd` pair. Purely
5
+ * presentational; the drawer supplies localized labels and formatted values.
6
+ */
7
+ export declare function MetaRow({ label, children }: {
8
+ label: ReactNode;
9
+ children: ReactNode;
10
+ }): import("react").JSX.Element;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/AssetDetailDrawer/MetaRow/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,EACpB,KAAK,EACL,QAAQ,EACX,EAAE;IACC,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,SAAS,CAAC;CACvB,+BASA"}
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * One label/value line in the detail drawer's metadata list — a muted term on
4
+ * the left and its value on the right, aligned as a `dt`/`dd` pair. Purely
5
+ * presentational; the drawer supplies localized labels and formatted values.
6
+ */
7
+ export function MetaRow({ label, children }) {
8
+ return (_jsxs("div", { className: "flex items-start justify-between gap-4 py-2", children: [_jsx("dt", { className: "shrink-0 text-sm text-muted-foreground", children: label }), _jsx("dd", { className: "min-w-0 truncate text-right text-sm font-medium", children: children })] }));
9
+ }
@@ -0,0 +1,28 @@
1
+ import type { MediaAsset } from '../../types/mediaAsset';
2
+ import { type AssetActionKind } from '../AssetActionsMenu';
3
+ /**
4
+ * The asset detail drawer — a right-hand panel with a large preview, primary
5
+ * Download / Copy link actions and the shared ⋯ menu, then a metadata list
6
+ * (type, size, dimensions, duration, format, location, uploader, timestamps),
7
+ * alt text, and tags. Open state is derived from `asset` (null → closed); every
8
+ * action dispatches up to the page.
9
+ *
10
+ * **Alt text is editable here** (with `media:update`) — it is the only route to
11
+ * an asset's description after upload. The section used to render `asset.alt`
12
+ * as read-only prose *and only when it was already set*, which meant an image
13
+ * that landed without a description could never acquire one from the admin,
14
+ * while the Insights alt-coverage card went on reporting the gap.
15
+ */
16
+ export declare function AssetDetailDrawer({ asset, locationLabel, onOpenChange, onAction, onSaveAlt, canCreate, canUpdate, canDelete }: {
17
+ asset: MediaAsset | null;
18
+ /** Human name of the folder the asset lives in. */
19
+ locationLabel: string;
20
+ onOpenChange: (open: boolean) => void;
21
+ onAction: (kind: AssetActionKind, asset: MediaAsset) => void;
22
+ /** Persists the alt text; resolves whether the server accepted it. */
23
+ onSaveAlt?: (id: string, alt: string) => Promise<boolean>;
24
+ canCreate: boolean;
25
+ canUpdate: boolean;
26
+ canDelete: boolean;
27
+ }): import("react").JSX.Element;
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/AssetDetailDrawer/index.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAIzD,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AA0C7E;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,KAAK,EACL,aAAa,EACb,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACZ,EAAE;IACC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,mDAAmD;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IAC7D,sEAAsE;IACtE,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACtB,+BAqQA"}
@@ -0,0 +1,93 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useId, useState } from 'react';
3
+ import { defineMessages, useIntl } from 'react-intl';
4
+ import { Badge, Button, Drawer, DrawerClose, DrawerContent, DrawerHeader, DrawerTitle, InputField, Separator } from '@orthacms/design-system';
5
+ import { Download, Link2, MoreVertical, X } from 'lucide-react';
6
+ import { MEDIA_KIND } from '../../constants';
7
+ import { formatBytes } from '../../utils/formatBytes';
8
+ import { formatDuration } from '../../utils/formatDuration';
9
+ import { buildAssetHandlers } from '../../utils/assetHandlers';
10
+ import { AssetActionsMenu } from '../AssetActionsMenu';
11
+ import { MediaKindBadge } from '../MediaKindBadge';
12
+ import { MediaThumbnail } from '../MediaThumbnail';
13
+ import { MetaRow } from './MetaRow';
14
+ /** Intl descriptors for {@link AssetDetailDrawer}, co-located. */
15
+ const messages = defineMessages({
16
+ close: { id: 'media.detail.close', defaultMessage: 'Close' },
17
+ download: { id: 'media.detail.download', defaultMessage: 'Download' },
18
+ copyLink: { id: 'media.detail.copyLink', defaultMessage: 'Copy link' },
19
+ more: { id: 'media.detail.more', defaultMessage: 'More actions' },
20
+ details: { id: 'media.detail.details', defaultMessage: 'Details' },
21
+ type: { id: 'media.detail.type', defaultMessage: 'Type' },
22
+ size: { id: 'media.detail.size', defaultMessage: 'Size' },
23
+ dimensions: { id: 'media.detail.dimensions', defaultMessage: 'Dimensions' },
24
+ duration: { id: 'media.detail.duration', defaultMessage: 'Duration' },
25
+ format: { id: 'media.detail.format', defaultMessage: 'Format' },
26
+ location: { id: 'media.detail.location', defaultMessage: 'Location' },
27
+ uploadedBy: {
28
+ id: 'media.detail.uploadedBy',
29
+ defaultMessage: 'Uploaded by'
30
+ },
31
+ created: { id: 'media.detail.created', defaultMessage: 'Created' },
32
+ modified: { id: 'media.detail.modified', defaultMessage: 'Modified' },
33
+ altText: { id: 'media.detail.altText', defaultMessage: 'Alt text' },
34
+ altPlaceholder: {
35
+ id: 'media.detail.altPlaceholder',
36
+ defaultMessage: 'Describe this image'
37
+ },
38
+ altHint: {
39
+ id: 'media.detail.altHint',
40
+ defaultMessage: 'Read aloud in place of the image. Leave blank only if it is purely decorative.'
41
+ },
42
+ altSave: { id: 'media.detail.altSave', defaultMessage: 'Save alt text' },
43
+ altMissing: {
44
+ id: 'media.detail.altMissing',
45
+ defaultMessage: 'No alt text yet'
46
+ },
47
+ tags: { id: 'media.detail.tags', defaultMessage: 'Tags' }
48
+ });
49
+ /**
50
+ * The asset detail drawer — a right-hand panel with a large preview, primary
51
+ * Download / Copy link actions and the shared ⋯ menu, then a metadata list
52
+ * (type, size, dimensions, duration, format, location, uploader, timestamps),
53
+ * alt text, and tags. Open state is derived from `asset` (null → closed); every
54
+ * action dispatches up to the page.
55
+ *
56
+ * **Alt text is editable here** (with `media:update`) — it is the only route to
57
+ * an asset's description after upload. The section used to render `asset.alt`
58
+ * as read-only prose *and only when it was already set*, which meant an image
59
+ * that landed without a description could never acquire one from the admin,
60
+ * while the Insights alt-coverage card went on reporting the gap.
61
+ */
62
+ export function AssetDetailDrawer({ asset, locationLabel, onOpenChange, onAction, onSaveAlt, canCreate, canUpdate, canDelete }) {
63
+ const intl = useIntl();
64
+ const altId = useId();
65
+ const [alt, setAlt] = useState('');
66
+ const [saving, setSaving] = useState(false);
67
+ // Re-seed whenever a different asset opens, so the field never shows the
68
+ // previous asset's description over this one's.
69
+ const assetId = asset?.id ?? null;
70
+ const assetAlt = asset?.alt ?? '';
71
+ useEffect(() => {
72
+ setAlt(assetAlt);
73
+ }, [assetId, assetAlt]);
74
+ const canEditAlt = canUpdate && !!onSaveAlt && asset?.kind === MEDIA_KIND.Image;
75
+ const altDirty = alt !== (asset?.alt ?? '');
76
+ const saveAlt = () => {
77
+ if (!asset || !onSaveAlt)
78
+ return;
79
+ setSaving(true);
80
+ void onSaveAlt(asset.id, alt).finally(() => setSaving(false));
81
+ };
82
+ const dateOpts = {
83
+ year: 'numeric',
84
+ month: 'short',
85
+ day: 'numeric',
86
+ hour: '2-digit',
87
+ minute: '2-digit'
88
+ };
89
+ return (_jsx(Drawer, { direction: "right", open: asset !== null, onOpenChange: onOpenChange, children: _jsx(DrawerContent, { className: "flex flex-col", children: asset ? (_jsxs(_Fragment, { children: [_jsxs(DrawerHeader, { className: "flex flex-row items-center justify-between gap-3 border-b", children: [_jsx(DrawerTitle, { className: "min-w-0 truncate", children: asset.name }), _jsx(DrawerClose, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", className: "size-8 shrink-0 shadow-none", "aria-label": intl.formatMessage(messages.close), children: _jsx(X, { "aria-hidden": true }) }) })] }), _jsxs("div", { className: "flex-1 space-y-5 overflow-auto p-4", children: [_jsx(MediaThumbnail, { asset: asset, size: "preview", className: "aspect-video w-full rounded-xl", iconClassName: "size-12" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { className: "flex-1", onClick: () => onAction('download', asset), children: [_jsx(Download, { "aria-hidden": true }), intl.formatMessage(messages.download)] }), _jsxs(Button, { variant: "outline", className: "flex-1 shadow-none", onClick: () => onAction('copyLink', asset), children: [_jsx(Link2, { "aria-hidden": true }), intl.formatMessage(messages.copyLink)] }), _jsx(AssetActionsMenu, { handlers: buildAssetHandlers(asset, onAction), canCreate: canCreate, canUpdate: canUpdate, canDelete: canDelete, trigger: _jsx(Button, { variant: "outline", size: "icon", className: "shadow-none", "aria-label": intl.formatMessage(messages.more), children: _jsx(MoreVertical, { "aria-hidden": true }) }) })] }), _jsxs("div", { children: [_jsx("h3", { className: "mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: intl.formatMessage(messages.details) }), _jsxs("dl", { className: "divide-y", children: [_jsx(MetaRow, { label: intl.formatMessage(messages.type), children: _jsx(MediaKindBadge, { kind: asset.kind }) }), _jsx(MetaRow, { label: intl.formatMessage(messages.size), children: formatBytes(asset.size) }), asset.dimensions ? (_jsxs(MetaRow, { label: intl.formatMessage(messages.dimensions), children: [asset.dimensions.width, "\u00D7", asset.dimensions.height] })) : null, asset.duration ? (_jsx(MetaRow, { label: intl.formatMessage(messages.duration), children: formatDuration(asset.duration) })) : null, _jsx(MetaRow, { label: intl.formatMessage(messages.format), children: asset.mimeType }), _jsx(MetaRow, { label: intl.formatMessage(messages.location), children: locationLabel }), _jsx(MetaRow, { label: intl.formatMessage(messages.uploadedBy), children: asset.uploadedBy }), _jsx(MetaRow, { label: intl.formatMessage(messages.created), children: intl.formatDate(asset.createdAt, dateOpts) }), _jsx(MetaRow, { label: intl.formatMessage(messages.modified), children: intl.formatDate(asset.updatedAt, dateOpts) })] })] }), canEditAlt ? (_jsxs("div", { className: "space-y-2", children: [_jsx(InputField, { id: `asset-alt-${altId}`, label: intl.formatMessage(messages.altText), description: intl.formatMessage(messages.altHint), placeholder: intl.formatMessage(messages.altPlaceholder), value: alt, maxLength: 1000, onChange: (event) => setAlt(event.target.value) }), _jsx(Button, { size: "sm", variant: "outline", className: "shadow-none", disabled: !altDirty || saving, onClick: saveAlt, children: intl.formatMessage(messages.altSave) })] })) : (_jsxs("div", { children: [_jsx("h3", { className: "mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: intl.formatMessage(messages.altText) }), _jsx("p", { className: asset.alt
90
+ ? 'text-sm'
91
+ : 'text-sm text-muted-foreground', children: asset.alt ??
92
+ intl.formatMessage(messages.altMissing) })] })), asset.tags.length > 0 ? (_jsxs("div", { children: [_jsx(Separator, { className: "mb-3" }), _jsx("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: intl.formatMessage(messages.tags) }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: asset.tags.map((tag) => (_jsx(Badge, { variant: "outline", className: "font-normal", children: tag }, tag))) })] })) : null] })] })) : null }) }));
93
+ }
@@ -0,0 +1,28 @@
1
+ import { type ContentField, type MediaRef } from '@orthacms/content-admin';
2
+ import type { MediaPendingUploads } from '../../../types/pendingUpload';
3
+ /**
4
+ * One media field as a **titled card** in the entry editor's Media tab —
5
+ * deliberately the same shape as the Relations tab's `RelationFieldSection`, so
6
+ * the two contributed tabs read as one editor: a header (label, required mark,
7
+ * the localized globe, a "Changed" pill, an error alert) over a one-line
8
+ * description of what the field holds, then the {@link MediaFieldControl}. An
9
+ * error tints the card's border, the same signal a relation card gives.
10
+ */
11
+ export declare function MediaFieldSection({ field, error, changed, value, initialRefs, refsPending, uploads, readOnly, onChange, onBlur }: {
12
+ field: ContentField;
13
+ error?: string;
14
+ /** Whether the field has unsaved changes (drives the "Changed" badge). */
15
+ changed: boolean;
16
+ value: unknown;
17
+ /** Server-resolved refs for the already-saved ids. */
18
+ initialRefs?: MediaRef[];
19
+ /** Whether those refs are still loading (a tile waits rather than guesses). */
20
+ refsPending?: boolean;
21
+ /** Editor-level staging for files that upload with the record. */
22
+ uploads?: MediaPendingUploads;
23
+ /** Whether the entry editor is a read-only preview (no `content:update`). */
24
+ readOnly?: boolean;
25
+ onChange: (value: unknown) => void;
26
+ onBlur: () => void;
27
+ }): import("react").JSX.Element;
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/EntryMediaTab/MediaFieldSection/index.tsx"],"names":[],"mappings":"AAWA,OAAO,EAEH,KAAK,YAAY,EACjB,KAAK,QAAQ,EAChB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAkCxE;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,KAAK,EACL,KAAK,EACL,OAAO,EACP,KAAK,EACL,WAAW,EACX,WAAW,EACX,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,MAAM,EACT,EAAE;IACC,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,sDAAsD;IACtD,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC;IACzB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,kEAAkE;IAClE,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,MAAM,EAAE,MAAM,IAAI,CAAC;CACtB,+BA2GA"}
@@ -0,0 +1,52 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { defineMessages, useIntl } from 'react-intl';
3
+ import { AlertCircle, Globe } from 'lucide-react';
4
+ import { cn, Field, FieldDescription, FieldError, Tooltip, TooltipContent, TooltipTrigger } from '@orthacms/design-system';
5
+ import { ChangedBadge } from '@orthacms/content-admin';
6
+ import { MediaFieldControl } from '../../MediaFieldControl';
7
+ /** Intl descriptors for {@link MediaFieldSection}, co-located. */
8
+ const messages = defineMessages({
9
+ invalid: {
10
+ id: 'media.section.invalid',
11
+ defaultMessage: 'This field has an error'
12
+ },
13
+ localized: {
14
+ id: 'media.section.localized',
15
+ defaultMessage: 'This asset can differ per locale.'
16
+ },
17
+ localizedLabel: {
18
+ id: 'media.section.localizedLabel',
19
+ defaultMessage: 'Localized field'
20
+ },
21
+ descSingle: {
22
+ id: 'media.section.descSingle',
23
+ defaultMessage: 'One asset from the Media Library.'
24
+ },
25
+ descMultiple: {
26
+ id: 'media.section.descMultiple',
27
+ defaultMessage: 'Ordered list — assets are delivered in the order shown here.'
28
+ }
29
+ });
30
+ /** The display label for a field — its admin label, else its machine name. */
31
+ function labelOf(field) {
32
+ const label = field.admin?.['label'];
33
+ return typeof label === 'string' && label ? label : field.name;
34
+ }
35
+ /**
36
+ * One media field as a **titled card** in the entry editor's Media tab —
37
+ * deliberately the same shape as the Relations tab's `RelationFieldSection`, so
38
+ * the two contributed tabs read as one editor: a header (label, required mark,
39
+ * the localized globe, a "Changed" pill, an error alert) over a one-line
40
+ * description of what the field holds, then the {@link MediaFieldControl}. An
41
+ * error tints the card's border, the same signal a relation card gives.
42
+ */
43
+ export function MediaFieldSection({ field, error, changed, value, initialRefs, refsPending, uploads, readOnly, onChange, onBlur }) {
44
+ const intl = useIntl();
45
+ const controlId = `media-field-${field.name}`;
46
+ const errorId = `${controlId}-error`;
47
+ const titleId = `${controlId}-title`;
48
+ const description = field.admin?.['description'];
49
+ return (_jsxs(Field, { "data-invalid": !!error, "aria-labelledby": titleId, className: cn('gap-3 rounded-2xl border bg-card p-4', error && 'border-destructive/50'), children: [_jsx("div", { className: "flex items-start gap-2", children: _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs("h2", { id: titleId, className: "text-sm font-medium", children: [labelOf(field), field.required ? (_jsx("span", { "aria-hidden": true, className: "text-destructive", children: "*" })) : null] }), field.localized ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "inline-flex shrink-0 rounded text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [_jsx(Globe, { "aria-hidden": true, className: "size-3.5" }), _jsx("span", { className: "sr-only", children: intl.formatMessage(messages.localizedLabel) })] }) }), _jsx(TooltipContent, { children: intl.formatMessage(messages.localized) })] })) : null, changed ? _jsx(ChangedBadge, {}) : null, error ? (_jsx(AlertCircle, { className: "size-4 shrink-0 text-destructive", "aria-label": intl.formatMessage(messages.invalid) })) : null] }), _jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: intl.formatMessage(field.multiple
50
+ ? messages.descMultiple
51
+ : messages.descSingle) })] }) }), _jsx(MediaFieldControl, { id: controlId, multiple: !!field.multiple, accept: field.accept, required: field.required, value: value, initialRefs: initialRefs, refsPending: refsPending, uploads: uploads, readOnly: readOnly, invalid: !!error, describedBy: error ? errorId : undefined, onChange: onChange, onBlur: onBlur }), !error && typeof description === 'string' ? (_jsx(FieldDescription, { children: description })) : null, error ? _jsx(FieldError, { id: errorId, children: error }) : null] }));
52
+ }
@@ -0,0 +1,11 @@
1
+ import { type EntryTabContext } from '@orthacms/content-admin';
2
+ /**
3
+ * The entry editor's **Media tab**, contributed by the media plugin through
4
+ * content-admin's `ENTRY_TAB_SLOT`. Renders one {@link MediaFieldSection} per
5
+ * media field on the type — the same titled-card shape the Relations tab uses —
6
+ * bound to the editor's shared form, so a change here rides Save, the publish
7
+ * gate, and the 422→field mapping exactly like a General-tab field. Media values
8
+ * live in the entry `values` bag; this tab is only their rendering surface.
9
+ */
10
+ export declare function EntryMediaTab(ctx: EntryTabContext): import("react").JSX.Element | null;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/EntryMediaTab/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAEH,KAAK,eAAe,EACvB,MAAM,yBAAyB,CAAC;AAajC;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,eAAe,sCA0CjD"}
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { defineMessages, useIntl } from 'react-intl';
3
+ import { CONTENT_FIELD_TYPE } from '@orthacms/content-admin';
4
+ import { MEDIA_PRESAVE_ID } from '../../hooks/usePendingMediaUploads';
5
+ import { MediaFieldSection } from './MediaFieldSection';
6
+ const messages = defineMessages({
7
+ subtitle: {
8
+ id: 'media.tab.subtitle',
9
+ defaultMessage: 'Attach images and files from the Media Library, or upload new ones — uploads are added to the library too.'
10
+ }
11
+ });
12
+ /**
13
+ * The entry editor's **Media tab**, contributed by the media plugin through
14
+ * content-admin's `ENTRY_TAB_SLOT`. Renders one {@link MediaFieldSection} per
15
+ * media field on the type — the same titled-card shape the Relations tab uses —
16
+ * bound to the editor's shared form, so a change here rides Save, the publish
17
+ * gate, and the 422→field mapping exactly like a General-tab field. Media values
18
+ * live in the entry `values` bag; this tab is only their rendering surface.
19
+ */
20
+ export function EntryMediaTab(ctx) {
21
+ const intl = useIntl();
22
+ const { schema, form, mediaRefs, mediaRefsPending, readOnly } = ctx;
23
+ // The plugin's own presave handle — the staging for files chosen here, held
24
+ // above this panel (which unmounts on every tab switch) and uploaded by the
25
+ // save. A tab reads only its own key from `presave`.
26
+ const uploads = ctx.presave[MEDIA_PRESAVE_ID];
27
+ const fields = schema.fields.filter((field) => field.type === CONTENT_FIELD_TYPE.Media);
28
+ if (fields.length === 0)
29
+ return null;
30
+ return (_jsxs("div", { className: "flex flex-col gap-4", children: [readOnly ? null : (_jsx("p", { className: "text-sm text-muted-foreground", children: intl.formatMessage(messages.subtitle) })), fields.map((field) => (_jsx(MediaFieldSection, { field: field, error: form.errorFor(field.name), changed: form.isFieldDirty(field.name), value: form.values[field.name], initialRefs: mediaRefs[field.name], refsPending: mediaRefsPending, uploads: uploads, readOnly: readOnly, onChange: (value) => form.setValue(field.name, value), onBlur: () => form.touch(field.name) }, field.name)))] }));
31
+ }
@@ -0,0 +1,21 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * The per-folder action menu — Open, Rename, and a destructive Delete (which
4
+ * removes the folder and everything inside). Rename/Delete are hidden unless the
5
+ * caller passes the matching permission. Pass `trigger` to override the default ⋯.
6
+ */
7
+ export declare function FolderActionsMenu({ name, onOpen, onRename, onDelete, canUpdate, canDelete, trigger }: {
8
+ /**
9
+ * The folder this menu acts on — its name goes in the trigger's accessible
10
+ * name, so a grid of folders doesn't present a row of buttons all called
11
+ * "Folder actions" with nothing to tell them apart.
12
+ */
13
+ name: string;
14
+ onOpen: () => void;
15
+ onRename: () => void;
16
+ onDelete: () => void;
17
+ canUpdate: boolean;
18
+ canDelete: boolean;
19
+ trigger?: ReactNode;
20
+ }): import("react").JSX.Element;
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/FolderActionsMenu/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAuBvC;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAS,EACT,OAAO,EACV,EAAE;IACC;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,SAAS,CAAC;CACvB,+BA6CA"}
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { defineMessages, useIntl } from 'react-intl';
3
+ import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@orthacms/design-system';
4
+ import { FolderOpen, MoreVertical, Pencil, Trash2 } from 'lucide-react';
5
+ /** Intl descriptors for {@link FolderActionsMenu}, co-located. */
6
+ const messages = defineMessages({
7
+ trigger: {
8
+ id: 'media.folder.actions.trigger',
9
+ defaultMessage: 'Actions for {name}'
10
+ },
11
+ open: { id: 'media.folder.actions.open', defaultMessage: 'Open' },
12
+ rename: { id: 'media.folder.actions.rename', defaultMessage: 'Rename' },
13
+ delete: { id: 'media.folder.actions.delete', defaultMessage: 'Delete' }
14
+ });
15
+ /**
16
+ * The per-folder action menu — Open, Rename, and a destructive Delete (which
17
+ * removes the folder and everything inside). Rename/Delete are hidden unless the
18
+ * caller passes the matching permission. Pass `trigger` to override the default ⋯.
19
+ */
20
+ export function FolderActionsMenu({ name, onOpen, onRename, onDelete, canUpdate, canDelete, trigger }) {
21
+ const intl = useIntl();
22
+ return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: trigger ?? (_jsx(Button, { variant: "ghost", size: "icon", className: "size-8 shadow-none", "aria-label": intl.formatMessage(messages.trigger, {
23
+ name
24
+ }), children: _jsx(MoreVertical, { "aria-hidden": true }) })) }), _jsxs(DropdownMenuContent, { align: "end", className: "w-44", children: [_jsxs(DropdownMenuItem, { onSelect: onOpen, children: [_jsx(FolderOpen, { "aria-hidden": true }), intl.formatMessage(messages.open)] }), canUpdate ? (_jsxs(DropdownMenuItem, { onSelect: onRename, children: [_jsx(Pencil, { "aria-hidden": true }), intl.formatMessage(messages.rename)] })) : null, canDelete ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { className: "text-destructive focus:text-destructive", onSelect: onDelete, children: [_jsx(Trash2, { "aria-hidden": true }), intl.formatMessage(messages.delete)] })] })) : null] })] }));
25
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * How many of the workspace's images carry alt text.
3
+ *
4
+ * The missing count is the headline rather than the coverage percentage: "794
5
+ * images need alt text" is a job someone can pick up, where "68% covered" is a
6
+ * score. The meter carries the proportion for context.
7
+ */
8
+ export declare function MediaAltTextWidget(): import("react").JSX.Element;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/MediaAltTextWidget/index.tsx"],"names":[],"mappings":"AA8CA;;;;;;GAMG;AACH,wBAAgB,kBAAkB,gCAgFjC"}
@@ -0,0 +1,66 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { defineMessages, useIntl } from 'react-intl';
3
+ import { AlertCircle, Check } from 'lucide-react';
4
+ import { WidgetCard, WidgetChip, toneBackground } from '@orthacms/insights-admin';
5
+ import { useMediaAltCoverage } from '../../hooks/useMediaInsights';
6
+ /** Intl descriptors for the alt-text widget, co-located here. */
7
+ const messages = defineMessages({
8
+ title: {
9
+ id: 'media.insights.alt.title',
10
+ defaultMessage: 'Images missing alt text'
11
+ },
12
+ description: {
13
+ id: 'media.insights.alt.description',
14
+ defaultMessage: 'Accessibility debt in the library'
15
+ },
16
+ covered: {
17
+ id: 'media.insights.alt.covered',
18
+ defaultMessage: 'All covered'
19
+ },
20
+ share: {
21
+ id: 'media.insights.alt.share',
22
+ defaultMessage: '{percent}% uncovered'
23
+ },
24
+ summary: {
25
+ id: 'media.insights.alt.summary',
26
+ defaultMessage: 'of {images, number} images'
27
+ },
28
+ hasAlt: {
29
+ id: 'media.insights.alt.hasAlt',
30
+ defaultMessage: 'Has alt · {count, number}'
31
+ },
32
+ missing: {
33
+ id: 'media.insights.alt.missing',
34
+ defaultMessage: 'Missing · {count, number}'
35
+ },
36
+ meter: {
37
+ id: 'media.insights.alt.meter',
38
+ defaultMessage: '{percent}% of images have alt text; {missing, number} do not.'
39
+ }
40
+ });
41
+ /**
42
+ * How many of the workspace's images carry alt text.
43
+ *
44
+ * The missing count is the headline rather than the coverage percentage: "794
45
+ * images need alt text" is a job someone can pick up, where "68% covered" is a
46
+ * score. The meter carries the proportion for context.
47
+ */
48
+ export function MediaAltTextWidget() {
49
+ const intl = useIntl();
50
+ const { data, isPending, isError } = useMediaAltCoverage();
51
+ const images = data?.images ?? 0;
52
+ const missing = data?.missing ?? 0;
53
+ const withAlt = data?.withAlt ?? 0;
54
+ const missingPercent = images > 0 ? Math.round((missing / images) * 100) : 0;
55
+ const coveredPercent = 100 - missingPercent;
56
+ return (_jsx(WidgetCard, { title: intl.formatMessage(messages.title), description: intl.formatMessage(messages.description), action: missing === 0 ? (_jsx(WidgetChip, { tone: "ok", icon: Check, children: intl.formatMessage(messages.covered) })) : (_jsx(WidgetChip, { tone: "warn", icon: AlertCircle, children: intl.formatMessage(messages.share, {
57
+ percent: missingPercent
58
+ }) })), isPending: isPending, isError: isError, isEmpty: images === 0, skeletonRows: 3, children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { children: [_jsx("div", { className: "text-3xl font-semibold tracking-[-0.025em] tabular-nums", children: intl.formatNumber(missing) }), _jsx("div", { className: "text-xs text-muted-foreground", children: intl.formatMessage(messages.summary, { images }) })] }), _jsxs("div", { className: "flex h-2 gap-[2px] overflow-hidden rounded-full bg-muted", role: "img", "aria-label": intl.formatMessage(messages.meter, {
59
+ percent: coveredPercent,
60
+ missing
61
+ }), children: [_jsx("span", { className: `block rounded-full ${toneBackground('series-1')}`, style: { width: `${coveredPercent}%` } }), _jsx("span", { className: "block rounded-full bg-warning", style: { width: `${missingPercent}%` } })] }), _jsxs("div", { className: "flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground", children: [_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx("span", { className: `block size-2 rounded-[2px] ${toneBackground('series-1')}` }), intl.formatMessage(messages.hasAlt, {
62
+ count: withAlt
63
+ })] }), _jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx("span", { className: "block size-2 rounded-[2px] bg-warning" }), intl.formatMessage(messages.missing, {
64
+ count: missing
65
+ })] })] })] }) }));
66
+ }
@@ -0,0 +1,11 @@
1
+ import type { MediaFolder } from '../../types/mediaFolder';
2
+ /**
3
+ * The folder path above the browser — "All media" followed by each ancestor up
4
+ * to the open folder, chevron-separated. Every crumb but the last is a button
5
+ * that navigates to that folder; the last is the current location (not a link).
6
+ */
7
+ export declare function MediaBreadcrumbs({ breadcrumbs, onNavigate }: {
8
+ breadcrumbs: MediaFolder[];
9
+ onNavigate: (folderId: string) => void;
10
+ }): import("react").JSX.Element;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/MediaBreadcrumbs/index.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAQ3D;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAC7B,WAAW,EACX,UAAU,EACb,EAAE;IACC,WAAW,EAAE,WAAW,EAAE,CAAC;IAC3B,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C,+BA8CA"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The browser's empty state. When a search/filter is active it offers to clear
3
+ * them; otherwise (a genuinely empty folder) it invites an upload — the upload
4
+ * CTA shows only when the user may create. The page decides which variant via
5
+ * `hasFilters`.
6
+ */
7
+ export declare function MediaEmptyState({ hasFilters, canCreate, onUpload, onClearFilters }: {
8
+ hasFilters: boolean;
9
+ canCreate: boolean;
10
+ onUpload: () => void;
11
+ onClearFilters: () => void;
12
+ }): import("react").JSX.Element;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/MediaEmptyState/index.tsx"],"names":[],"mappings":"AAmCA;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAC5B,UAAU,EACV,SAAS,EACT,QAAQ,EACR,cAAc,EACjB,EAAE;IACC,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,cAAc,EAAE,MAAM,IAAI,CAAC;CAC9B,+BAqDA"}