@murumets-ee/media 0.1.4 → 0.1.5

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 (88) hide show
  1. package/dist/{admin.d.ts → admin.d.mts} +4 -25
  2. package/dist/admin.d.mts.map +1 -0
  3. package/dist/admin.mjs +2 -0
  4. package/dist/admin.mjs.map +1 -0
  5. package/dist/client-sF8mf4Fg.mjs +2 -0
  6. package/dist/client-sF8mf4Fg.mjs.map +1 -0
  7. package/dist/client.d.mts +95 -0
  8. package/dist/client.d.mts.map +1 -0
  9. package/dist/client.mjs +2 -0
  10. package/dist/client.mjs.map +1 -0
  11. package/dist/entity-D5P2l05s.mjs +2 -0
  12. package/dist/entity-D5P2l05s.mjs.map +1 -0
  13. package/dist/entity-DZFku8b7.mjs +2 -0
  14. package/dist/entity-DZFku8b7.mjs.map +1 -0
  15. package/dist/image-styles-settings-7K_jPNIA.mjs +2 -0
  16. package/dist/image-styles-settings-7K_jPNIA.mjs.map +1 -0
  17. package/dist/image-styles-settings.d.mts +10 -0
  18. package/dist/image-styles-settings.d.mts.map +1 -0
  19. package/dist/image-styles-settings.mjs +2 -0
  20. package/dist/image-styles-settings.mjs.map +1 -0
  21. package/dist/image-styles.d.mts +71 -0
  22. package/dist/image-styles.d.mts.map +1 -0
  23. package/dist/image-styles.mjs +3 -0
  24. package/dist/image-styles.mjs.map +1 -0
  25. package/dist/index.d.mts +112 -0
  26. package/dist/index.d.mts.map +1 -0
  27. package/dist/index.mjs +2 -0
  28. package/dist/index.mjs.map +1 -0
  29. package/dist/picker.d.mts +228 -0
  30. package/dist/picker.d.mts.map +1 -0
  31. package/dist/picker.mjs +3 -0
  32. package/dist/picker.mjs.map +1 -0
  33. package/dist/plugin-DV7lvImm.mjs +2 -0
  34. package/dist/plugin-DV7lvImm.mjs.map +1 -0
  35. package/dist/plugin.d.mts +20 -0
  36. package/dist/plugin.d.mts.map +1 -0
  37. package/dist/plugin.mjs +2 -0
  38. package/dist/plugin.mjs.map +1 -0
  39. package/dist/query-client.d.mts +28 -0
  40. package/dist/query-client.d.mts.map +1 -0
  41. package/dist/query-client.mjs +2 -0
  42. package/dist/query-client.mjs.map +1 -0
  43. package/dist/{ref.d.ts → ref.d.mts} +26 -24
  44. package/dist/ref.d.mts.map +1 -0
  45. package/dist/ref.mjs +2 -0
  46. package/dist/ref.mjs.map +1 -0
  47. package/dist/regenerate-variants-DY7D4Ky3.mjs +2 -0
  48. package/dist/regenerate-variants-DY7D4Ky3.mjs.map +1 -0
  49. package/dist/types-BV_pOm23.d.mts +103 -0
  50. package/dist/types-BV_pOm23.d.mts.map +1 -0
  51. package/dist/usage-D7Bn7Vvv.mjs +2 -0
  52. package/dist/usage-D7Bn7Vvv.mjs.map +1 -0
  53. package/dist/usage.d.mts +21 -0
  54. package/dist/usage.d.mts.map +1 -0
  55. package/dist/usage.mjs +2 -0
  56. package/dist/usage.mjs.map +1 -0
  57. package/dist/variant-key-DZUYURS5.mjs +2 -0
  58. package/dist/variant-key-DZUYURS5.mjs.map +1 -0
  59. package/package.json +29 -29
  60. package/dist/admin.js +0 -1
  61. package/dist/chunk-JGE5BDIT.js +0 -1
  62. package/dist/chunk-L6BDKI76.js +0 -1
  63. package/dist/chunk-QTUXM53A.js +0 -1
  64. package/dist/chunk-YAHM4C5J.js +0 -1
  65. package/dist/client-37WG2Y6P.js +0 -1
  66. package/dist/client.d.ts +0 -102
  67. package/dist/client.js +0 -1
  68. package/dist/entity-QOBW3TFU.js +0 -1
  69. package/dist/image-styles-settings-AB5WFEFF.js +0 -1
  70. package/dist/image-styles-settings.d.ts +0 -9
  71. package/dist/image-styles-settings.js +0 -1
  72. package/dist/image-styles.d.ts +0 -62
  73. package/dist/image-styles.js +0 -2
  74. package/dist/index.d.ts +0 -131
  75. package/dist/index.js +0 -1
  76. package/dist/picker.d.ts +0 -183
  77. package/dist/picker.js +0 -2
  78. package/dist/plugin-CAV5BZMF.js +0 -1
  79. package/dist/plugin.d.ts +0 -35
  80. package/dist/plugin.js +0 -1
  81. package/dist/query-client.d.ts +0 -36
  82. package/dist/query-client.js +0 -1
  83. package/dist/ref.js +0 -1
  84. package/dist/regenerate-variants-IUDIIWXU.js +0 -1
  85. package/dist/types-ChlTxvlq.d.ts +0 -101
  86. package/dist/usage-E5RZMWE4.js +0 -1
  87. package/dist/usage.d.ts +0 -28
  88. package/dist/usage.js +0 -1
@@ -1 +0,0 @@
1
- import{a}from"./chunk-YAHM4C5J.js";export{a as Media};
@@ -1 +0,0 @@
1
- import{defineSettings as e,setting as t}from"@murumets-ee/settings";var a=e({namespace:"media.imageStyles",scope:"global",label:"Image Styles",schema:{imageStyles:t.json({default:{thumbnail:{width:200,height:200,fit:"cover",format:"webp",quality:80}},label:"Image processing presets"})}});export{a as imageStylesSettings};
@@ -1,9 +0,0 @@
1
- import * as _murumets_ee_settings from '@murumets-ee/settings';
2
- import { I as ImageStyle } from './types-ChlTxvlq.js';
3
- import '@murumets-ee/storage';
4
-
5
- declare const imageStylesSettings: _murumets_ee_settings.SettingsDefinition<{
6
- readonly imageStyles: _murumets_ee_settings.JsonSettingConfig<Record<string, ImageStyle>>;
7
- }>;
8
-
9
- export { imageStylesSettings };
@@ -1 +0,0 @@
1
- import{a}from"./chunk-JGE5BDIT.js";export{a as imageStylesSettings};
@@ -1,62 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
-
3
- /** Named image processing preset — defines how to resize/convert an image variant */
4
- interface ImageStyle {
5
- /** Max width in pixels (omit for height-only constraint) */
6
- width?: number;
7
- /** Max height in pixels (omit for height-only constraint) */
8
- height?: number;
9
- /** Resize strategy (default: 'cover') */
10
- fit?: 'cover' | 'contain' | 'inside' | 'outside' | 'fill';
11
- /** Output format (default: 'webp') */
12
- format?: 'webp' | 'jpeg' | 'png' | 'avif';
13
- /** Output quality 1–100 (default: 80) */
14
- quality?: number;
15
- }
16
-
17
- interface ImageStylesManagerLabels {
18
- title?: string;
19
- description?: string;
20
- addStyle?: string;
21
- editStyle?: string;
22
- styleName?: string;
23
- width?: string;
24
- height?: string;
25
- fit?: string;
26
- format?: string;
27
- quality?: string;
28
- save?: string;
29
- cancel?: string;
30
- delete?: string;
31
- deleteConfirmTitle?: string;
32
- deleteConfirmDescription?: string;
33
- systemBadge?: string;
34
- noStyles?: string;
35
- regenerate?: string;
36
- regenerateDescription?: string;
37
- regenerating?: string;
38
- saving?: string;
39
- px?: string;
40
- }
41
- interface ImageStylesManagerClassNames {
42
- root?: string;
43
- header?: string;
44
- table?: string;
45
- dialog?: string;
46
- actions?: string;
47
- regenerateSection?: string;
48
- }
49
- interface ImageStylesManagerProps {
50
- /** Current image styles from server (required, server-first pattern) */
51
- initialStyles: Record<string, ImageStyle>;
52
- /** API base path for media admin routes (default: '/api/admin/media') */
53
- apiBasePath?: string;
54
- /** i18n labels with English defaults */
55
- labels?: ImageStylesManagerLabels;
56
- /** Per-element class overrides */
57
- classNames?: ImageStylesManagerClassNames;
58
- }
59
-
60
- declare function ImageStylesManager({ initialStyles, apiBasePath, labels: userLabels, classNames, }: ImageStylesManagerProps): react_jsx_runtime.JSX.Element;
61
-
62
- export { ImageStylesManager, type ImageStylesManagerClassNames, type ImageStylesManagerLabels, type ImageStylesManagerProps };
@@ -1,2 +0,0 @@
1
- "use client";
2
- import{Badge as fe,Button as d,cn as b,Dialog as X,DialogContent as Y,DialogDescription as Z,DialogFooter as ee,DialogHeader as te,DialogTitle as ae,Input as I,Label as h,Select as se,Table as ve,TableBody as be,TableCell as g,TableHead as u,TableHeader as Ne,TableRow as re}from"@murumets-ee/ui";import{Loader2 as z,Pencil as xe,Plus as Se,RefreshCw as we,Trash2 as Ce}from"lucide-react";import{useCallback as p,useState as i}from"react";import{Fragment as P,jsx as t,jsxs as a}from"react/jsx-runtime";var Te=["cover","contain","inside","outside","fill"],De=["webp","jpeg","png","avif"],Ie={title:"Image Styles",description:'Configure image processing presets. Variants are generated on upload for each style. Use "Regenerate" to update existing images.',addStyle:"Add Style",editStyle:"Edit Style",styleName:"Style Name",width:"Width",height:"Height",fit:"Fit",format:"Format",quality:"Quality",save:"Save",cancel:"Cancel",delete:"Delete",deleteConfirmTitle:"Delete Image Style",deleteConfirmDescription:"Are you sure? This will not remove existing variant files.",systemBadge:"system",noStyles:"No image styles configured.",regenerate:"Regenerate All Variants",regenerateDescription:"Reprocess all images with the current styles. This may take a while for large libraries.",regenerating:"Regenerating...",saving:"Saving...",px:"px"};function Fe({initialStyles:ie,apiBasePath:N="/api/admin/media",labels:le,classNames:y}){let s={...Ie,...le},[m,ne]=i(ie),[x,_]=i(!1),[K,F]=i(null),[oe,f]=i(!1),[n,Q]=i(null),[k,R]=i(""),[S,H]=i(""),[w,O]=i(""),[E,B]=i("cover"),[L,$]=i("webp"),[j,q]=i("80"),[W,o]=i(null),[me,C]=i(!1),[v,J]=i(null),[U,V]=i(!1),[c,G]=i(null),T=p(async e=>{_(!0),F(null);try{let r=await fetch(`${N}/settings`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({imageStyles:e})});if(!r.ok){let l=await r.json();throw new Error(l.error??`Save failed (${r.status})`)}ne(e)}catch(r){let l=r instanceof Error?r.message:"Save failed";throw F(l),r}finally{_(!1)}},[N]),de=p(()=>{Q(null),R(""),H(""),O(""),B("cover"),$("webp"),q("80"),o(null),f(!0)},[]),ce=p(e=>{let r=m[e];r&&(Q(e),R(e),H(r.width?.toString()??""),O(r.height?.toString()??""),B(r.fit??"cover"),$(r.format??"webp"),q((r.quality??80).toString()),o(null),f(!0))},[m]),ge=p(async()=>{o(null);let e=k.trim().toLowerCase();if(!e||!/^[a-z][a-z0-9_-]*$/.test(e)){o("Name must be lowercase alphanumeric (a-z, 0-9, -, _)");return}if(e!==n&&m[e]){o(`A style named "${e}" already exists`);return}let r=S?Number.parseInt(S,10):void 0,l=w?Number.parseInt(w,10):void 0;if(!r&&!l){o("At least width or height is required");return}if(r!==void 0&&(Number.isNaN(r)||r<=0)){o("Width must be a positive number");return}if(l!==void 0&&(Number.isNaN(l)||l<=0)){o("Height must be a positive number");return}let D=Number.parseInt(j,10);if(Number.isNaN(D)||D<1||D>100){o("Quality must be 1-100");return}let he={...r?{width:r}:{},...l?{height:l}:{},fit:E,format:L,quality:D},M={...m};n&&n!==e&&delete M[n],M[e]=he;try{await T(M),f(!1)}catch{}},[m,n,k,S,w,E,L,j,T]),ue=p(e=>{J(e),C(!0)},[]),pe=p(async()=>{if(!v)return;let e={...m};delete e[v];try{await T(e),C(!1),J(null)}catch{}},[v,m,T]),ye=p(async()=>{V(!0),G(null);try{let e=await fetch(`${N}/regenerate-variants`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({})});if(!e.ok){let l=await e.json();throw new Error(l.error??`Regeneration failed (${e.status})`)}let r=await e.json();G(r)}catch(e){let r=e instanceof Error?e.message:"Regeneration failed";F(r)}finally{V(!1)}},[N]),A=Object.entries(m).sort(([e],[r])=>e.localeCompare(r));return a("div",{className:b("space-y-6",y?.root),children:[a("div",{className:b("flex items-start justify-between",y?.header),children:[a("div",{children:[t("h2",{className:"text-2xl font-semibold tracking-tight",children:s.title}),t("p",{className:"text-sm text-muted-foreground mt-1",children:s.description})]}),a(d,{onClick:de,size:"sm",children:[t(Se,{className:"h-4 w-4 mr-1"}),s.addStyle]})]}),K&&t("div",{className:"rounded-md border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive",children:K}),A.length===0?t("p",{className:"text-sm text-muted-foreground py-8 text-center",children:s.noStyles}):t("div",{className:b("rounded-md border",y?.table),children:a(ve,{children:[t(Ne,{children:a(re,{children:[t(u,{children:s.styleName}),t(u,{className:"text-center",children:s.width}),t(u,{className:"text-center",children:s.height}),t(u,{className:"text-center",children:s.fit}),t(u,{className:"text-center",children:s.format}),t(u,{className:"text-center",children:s.quality}),t(u,{className:"w-24"})]})}),t(be,{children:A.map(([e,r])=>a(re,{children:[a(g,{className:"font-mono text-sm",children:[e,e==="thumbnail"&&t(fe,{variant:"secondary",className:"ml-2 text-xs",children:s.systemBadge})]}),t(g,{className:"text-center tabular-nums",children:r.width?`${r.width}${s.px}`:"\u2014"}),t(g,{className:"text-center tabular-nums",children:r.height?`${r.height}${s.px}`:"\u2014"}),t(g,{className:"text-center text-sm text-muted-foreground",children:r.fit??"cover"}),t(g,{className:"text-center text-sm text-muted-foreground",children:r.format??"webp"}),t(g,{className:"text-center tabular-nums",children:r.quality??80}),t(g,{children:a("div",{className:b("flex items-center gap-1 justify-end",y?.actions),children:[t(d,{variant:"ghost",size:"sm",onClick:()=>ce(e),title:s.editStyle,children:t(xe,{className:"h-3.5 w-3.5"})}),t(d,{variant:"ghost",size:"sm",onClick:()=>ue(e),title:s.delete,className:"text-destructive hover:text-destructive",children:t(Ce,{className:"h-3.5 w-3.5"})})]})})]},e))})]})}),a("div",{className:b("rounded-md border p-4 space-y-3",y?.regenerateSection),children:[a("div",{className:"flex items-center justify-between",children:[a("div",{children:[t("h3",{className:"text-sm font-medium",children:s.regenerate}),t("p",{className:"text-xs text-muted-foreground mt-0.5",children:s.regenerateDescription})]}),t(d,{variant:"outline",size:"sm",onClick:ye,disabled:U||A.length===0,children:U?a(P,{children:[t(z,{className:"h-4 w-4 mr-1 animate-spin"}),s.regenerating]}):a(P,{children:[t(we,{className:"h-4 w-4 mr-1"}),s.regenerate]})})]}),c&&a("div",{className:"text-sm text-muted-foreground bg-muted/50 rounded px-3 py-2",children:["Processed ",c.processed," of ",c.total," images",c.skipped>0&&`, ${c.skipped} skipped`,c.errors>0&&a("span",{className:"text-destructive",children:[", ",c.errors," errors"]})]})]}),t(X,{open:oe,onOpenChange:f,children:a(Y,{className:y?.dialog,children:[a(te,{children:[t(ae,{children:n?s.editStyle:s.addStyle}),t(Z,{children:n?`Editing "${n}" image style.`:"Create a new image processing preset."})]}),a("div",{className:"space-y-4 py-2",children:[W&&t("div",{className:"text-sm text-destructive",children:W}),a("div",{className:"space-y-2",children:[t(h,{htmlFor:"style-name",children:s.styleName}),t(I,{id:"style-name",value:k,onChange:e=>R(e.target.value),placeholder:"e.g. thumbnail, medium, large",disabled:!!n})]}),a("div",{className:"grid grid-cols-2 gap-4",children:[a("div",{className:"space-y-2",children:[a(h,{htmlFor:"style-width",children:[s.width," (",s.px,")"]}),t(I,{id:"style-width",type:"number",value:S,onChange:e=>H(e.target.value),placeholder:"e.g. 200",min:1})]}),a("div",{className:"space-y-2",children:[a(h,{htmlFor:"style-height",children:[s.height," (",s.px,")"]}),t(I,{id:"style-height",type:"number",value:w,onChange:e=>O(e.target.value),placeholder:"e.g. 200",min:1})]})]}),a("div",{className:"grid grid-cols-2 gap-4",children:[a("div",{className:"space-y-2",children:[t(h,{htmlFor:"style-fit",children:s.fit}),t(se,{id:"style-fit",value:E,onChange:e=>B(e.target.value),children:Te.map(e=>t("option",{value:e,children:e},e))})]}),a("div",{className:"space-y-2",children:[t(h,{htmlFor:"style-format",children:s.format}),t(se,{id:"style-format",value:L,onChange:e=>$(e.target.value),children:De.map(e=>t("option",{value:e,children:e},e))})]})]}),a("div",{className:"space-y-2",children:[a(h,{htmlFor:"style-quality",children:[s.quality," (1-100)"]}),t(I,{id:"style-quality",type:"number",value:j,onChange:e=>q(e.target.value),min:1,max:100})]})]}),a(ee,{children:[t(d,{variant:"outline",onClick:()=>f(!1),children:s.cancel}),t(d,{onClick:ge,disabled:x,children:x?a(P,{children:[t(z,{className:"h-4 w-4 mr-1 animate-spin"}),s.saving]}):s.save})]})]})}),t(X,{open:me,onOpenChange:C,children:a(Y,{children:[a(te,{children:[t(ae,{children:s.deleteConfirmTitle}),a(Z,{children:[s.deleteConfirmDescription,v&&t("span",{className:"block mt-2 font-mono text-foreground",children:v})]})]}),a(ee,{children:[t(d,{variant:"outline",onClick:()=>C(!1),children:s.cancel}),a(d,{variant:"destructive",onClick:pe,disabled:x,children:[x?t(z,{className:"h-4 w-4 mr-1 animate-spin"}):null,s.delete]})]})]})})]})}export{Fe as ImageStylesManager};
package/dist/index.d.ts DELETED
@@ -1,131 +0,0 @@
1
- import * as _murumets_ee_entity from '@murumets-ee/entity';
2
- export { imageStylesSettings } from './image-styles-settings.js';
3
- export { I as ImageStyle, c as MediaListOptions, d as MediaListResult, e as MediaPluginConfig, b as MediaRecord, f as MediaType, M as MediaUploadOptions, a as MediaUploadResult } from './types-ChlTxvlq.js';
4
- import '@murumets-ee/settings';
5
- import '@murumets-ee/storage';
6
-
7
- /**
8
- * Server-side utility to enrich entity list items with resolved media URLs.
9
- *
10
- * For entities with `field.media()` columns, this scans items for media UUIDs,
11
- * batch-resolves them via MediaClient, and injects `${fieldName}Url` into each item.
12
- *
13
- * Convention: a media field named `coverImage` (UUID) gets enriched with
14
- * `coverImageUrl` (resolved URL string).
15
- *
16
- * @example
17
- * ```typescript
18
- * import { enrichWithMediaUrls } from '@murumets-ee/media'
19
- *
20
- * const items = await adminClient.findMany({ limit: 20 })
21
- * await enrichWithMediaUrls(Article, items)
22
- * // items[0].coverImageUrl → 'https://cdn.example.com/uploads/.../thumbnail_photo.webp'
23
- * ```
24
- */
25
-
26
- /**
27
- * Enrich entity list items by resolving media field UUIDs to variant URLs.
28
- *
29
- * Mutates items in place — injects `${fieldName}Url` for each media field.
30
- *
31
- * @param entity - Entity definition (or any object with allFields containing type info)
32
- * @param items - Array of entity records to enrich
33
- * @param styleName - Image style to resolve (default: 'thumbnail')
34
- */
35
- declare function enrichWithMediaUrls(entity: {
36
- allFields: Record<string, {
37
- type: string;
38
- }>;
39
- }, items: Record<string, unknown>[], styleName?: string): Promise<void>;
40
-
41
- /**
42
- * Pre-defined Media entity.
43
- *
44
- * Auto-registered by the media() plugin — users do NOT add this to their entities array.
45
- * Connected to toolkit_files via the fileKey field (stores the StorageClient file key).
46
- *
47
- * @example
48
- * ```typescript
49
- * import { media } from '@murumets-ee/media/plugin'
50
- *
51
- * export default defineConfig({
52
- * plugins: [storage(), media()],
53
- * // Media entity is auto-added — no need to list it here
54
- * })
55
- * ```
56
- */
57
- declare const Media: _murumets_ee_entity.Entity<{
58
- id: _murumets_ee_entity.IdField;
59
- } & _murumets_ee_entity.AuditableFields & {
60
- title: _murumets_ee_entity.TextField & {
61
- readonly translatable: true;
62
- };
63
- alt: _murumets_ee_entity.TextField & {
64
- readonly translatable: true;
65
- };
66
- description: _murumets_ee_entity.TextField & {
67
- readonly translatable: true;
68
- };
69
- /** Key into toolkit_files table (e.g., 'uploads/2026/02/uuid/photo.jpg') */
70
- fileKey: _murumets_ee_entity.TextField & {
71
- readonly required: true;
72
- readonly indexed: true;
73
- };
74
- filename: _murumets_ee_entity.TextField & {
75
- readonly required: true;
76
- };
77
- mimeType: _murumets_ee_entity.TextField & {
78
- readonly required: true;
79
- readonly indexed: true;
80
- };
81
- size: _murumets_ee_entity.NumberField & {
82
- readonly required: true;
83
- readonly integer: true;
84
- };
85
- width: _murumets_ee_entity.NumberField & {
86
- readonly integer: true;
87
- };
88
- height: _murumets_ee_entity.NumberField & {
89
- readonly integer: true;
90
- };
91
- mediaType: _murumets_ee_entity.SelectField & {
92
- options: readonly ["image", "video", "audio", "document", "other"];
93
- } & {
94
- readonly options: readonly ["image", "video", "audio", "document", "other"];
95
- readonly required: true;
96
- readonly indexed: true;
97
- };
98
- }>;
99
-
100
- /** Lightweight media item for the picker (no server-only deps) */
101
- interface MediaPickerItem {
102
- id: string;
103
- title: string | null;
104
- alt: string | null;
105
- filename: string;
106
- mimeType: string;
107
- size: number;
108
- width: number | null;
109
- height: number | null;
110
- mediaType: 'image' | 'video' | 'audio' | 'document' | 'other';
111
- url: string;
112
- }
113
- /** Paginated result from fetchMedia callback */
114
- interface MediaPickerListResult {
115
- items: MediaPickerItem[];
116
- total: number;
117
- }
118
- /** Callbacks the consumer provides — framework-agnostic */
119
- interface MediaPickerCallbacks {
120
- /** Fetch media items with filtering and pagination */
121
- fetchMedia: (options: {
122
- search?: string;
123
- mediaType?: string;
124
- limit: number;
125
- offset: number;
126
- }) => Promise<MediaPickerListResult>;
127
- /** Upload a file and return the created media item */
128
- uploadMedia: (file: File) => Promise<MediaPickerItem>;
129
- }
130
-
131
- export { Media, type MediaPickerCallbacks, type MediaPickerItem, type MediaPickerListResult, enrichWithMediaUrls };
package/dist/index.js DELETED
@@ -1 +0,0 @@
1
- import{a as f}from"./chunk-JGE5BDIT.js";import{a as c}from"./chunk-L6BDKI76.js";import"server-only";async function p(n,s,l="thumbnail"){let o=Object.entries(n.allFields).filter(([,e])=>e.type==="media").map(([e])=>e);if(o.length===0)return;let r=new Set;for(let e of s)for(let i of o){let t=e[i];typeof t=="string"&&t.length>0&&r.add(t)}if(r.size===0)return;let{getMediaClient:d}=await import("./client.js"),a=await(await d()).getVariantUrls([...r],l);for(let e of s)for(let i of o){let t=e[i];typeof t=="string"&&a.has(t)&&(e[`${i}Url`]=a.get(t))}}export{c as Media,p as enrichWithMediaUrls,f as imageStylesSettings};
package/dist/picker.d.ts DELETED
@@ -1,183 +0,0 @@
1
- import { ReactNode } from 'react';
2
- import * as react_jsx_runtime from 'react/jsx-runtime';
3
-
4
- /** Lightweight media item for the picker (no server-only deps) */
5
- interface MediaPickerItem {
6
- id: string;
7
- title: string | null;
8
- alt: string | null;
9
- filename: string;
10
- mimeType: string;
11
- size: number;
12
- width: number | null;
13
- height: number | null;
14
- mediaType: 'image' | 'video' | 'audio' | 'document' | 'other';
15
- url: string;
16
- }
17
- /** Paginated result from fetchMedia callback */
18
- interface MediaPickerListResult {
19
- items: MediaPickerItem[];
20
- total: number;
21
- }
22
- /** Callbacks the consumer provides — framework-agnostic */
23
- interface MediaPickerCallbacks {
24
- /** Fetch media items with filtering and pagination */
25
- fetchMedia: (options: {
26
- search?: string;
27
- mediaType?: string;
28
- limit: number;
29
- offset: number;
30
- }) => Promise<MediaPickerListResult>;
31
- /** Upload a file and return the created media item */
32
- uploadMedia: (file: File) => Promise<MediaPickerItem>;
33
- }
34
- /** Selection mode */
35
- type MediaPickerMode = 'single' | 'multiple';
36
- /** Per-element class overrides for MediaPicker */
37
- interface MediaPickerClassNames {
38
- overlay?: string;
39
- content?: string;
40
- header?: string;
41
- title?: string;
42
- toolbar?: string;
43
- searchInput?: string;
44
- filterTabs?: string;
45
- grid?: string;
46
- card?: string;
47
- cardSelected?: string;
48
- cardImage?: string;
49
- cardLabel?: string;
50
- uploadZone?: string;
51
- uploadZoneActive?: string;
52
- footer?: string;
53
- confirmButton?: string;
54
- cancelButton?: string;
55
- loading?: string;
56
- empty?: string;
57
- }
58
- /** Props for the MediaPicker dialog */
59
- interface MediaPickerProps {
60
- /** Whether the dialog is open */
61
- open: boolean;
62
- /** Called when open state changes */
63
- onOpenChange: (open: boolean) => void;
64
- /** Called when user confirms selection */
65
- onSelect: (items: MediaPickerItem[]) => void;
66
- /** Selection mode (default: 'single') */
67
- mode?: MediaPickerMode;
68
- /** MIME patterns for upload restriction (e.g., ['image/*', 'video/*']) — passed to file input */
69
- accept?: string[];
70
- /** Filter browse results to a specific media classification (e.g., 'image', 'video') */
71
- mediaType?: string;
72
- /** Maximum items selectable in multi mode */
73
- maxSelect?: number;
74
- /** Currently selected item IDs (for pre-selection) */
75
- selectedIds?: string[];
76
- /** Dialog title (default: 'Select Media') */
77
- title?: string;
78
- /** Dialog description for screen readers (optional) */
79
- description?: string;
80
- /** Per-element class overrides */
81
- classNames?: MediaPickerClassNames;
82
- /** Additional className for the dialog content */
83
- className?: string;
84
- /** Children rendered in dialog footer (extra actions) */
85
- children?: ReactNode;
86
- }
87
-
88
- /**
89
- * Pre-built media callbacks that talk to the admin API.
90
- *
91
- * Eliminates boilerplate in every project — just call:
92
- * const media = createAdminMediaCallbacks('/api/admin')
93
- *
94
- * Returns callbacks compatible with both MediaPickerProvider and
95
- * BlockEditor's `media` prop (with getMediaUrl for thumbnail previews).
96
- */
97
-
98
- /** Extended callbacks with getMediaUrl (for editor thumbnail previews) */
99
- type AdminMediaCallbacks = MediaPickerCallbacks & {
100
- getMediaUrl: (id: string) => Promise<string | null>;
101
- };
102
- /**
103
- * Create media callbacks that talk to the admin API.
104
- *
105
- * @param apiBasePath - Base path for admin API (default: '/api/admin')
106
- * @returns Callbacks for MediaPickerProvider + BlockEditor media prop
107
- *
108
- * @example
109
- * ```tsx
110
- * import { createAdminMediaCallbacks } from '@murumets-ee/media/picker'
111
- *
112
- * const media = createAdminMediaCallbacks()
113
- * // Use with BlockEditor:
114
- * <BlockEditor media={media} ... />
115
- * // Use with MediaPickerProvider:
116
- * <MediaPickerProvider fetchMedia={media.fetchMedia} uploadMedia={media.uploadMedia}>
117
- * ```
118
- */
119
- declare function createAdminMediaCallbacks(apiBasePath?: string): AdminMediaCallbacks;
120
-
121
- interface MediaCardProps {
122
- item: MediaPickerItem;
123
- isSelected: boolean;
124
- onToggle: () => void;
125
- classNames?: MediaPickerClassNames;
126
- }
127
- declare function MediaCard({ item, isSelected, onToggle, classNames }: MediaCardProps): react_jsx_runtime.JSX.Element;
128
-
129
- interface MediaGridProps {
130
- items: MediaPickerItem[];
131
- selected: Set<string>;
132
- onToggle: (item: MediaPickerItem) => void;
133
- isLoading: boolean;
134
- total: number;
135
- offset: number;
136
- limit: number;
137
- onPageChange: (offset: number) => void;
138
- classNames?: MediaPickerClassNames;
139
- }
140
- declare function MediaGrid({ items, selected, onToggle, isLoading, total, offset, limit, onPageChange, classNames, }: MediaGridProps): react_jsx_runtime.JSX.Element;
141
-
142
- declare function MediaPicker({ open, onOpenChange, onSelect, mode, accept, mediaType, maxSelect, selectedIds, title, description, classNames, className, children, }: MediaPickerProps): react_jsx_runtime.JSX.Element;
143
-
144
- interface MediaPickerProviderProps extends MediaPickerCallbacks {
145
- children: ReactNode;
146
- }
147
- /**
148
- * Provides media picker callbacks to all picker instances below.
149
- * Wrap your admin layout with this provider.
150
- *
151
- * @example
152
- * ```tsx
153
- * <MediaPickerProvider
154
- * fetchMedia={fetchMediaAction}
155
- * uploadMedia={uploadMediaAction}
156
- * >
157
- * <AdminShell>...</AdminShell>
158
- * </MediaPickerProvider>
159
- * ```
160
- */
161
- declare function MediaPickerProvider({ children, fetchMedia, uploadMedia, }: MediaPickerProviderProps): react_jsx_runtime.JSX.Element;
162
- declare function useMediaPicker(): MediaPickerCallbacks;
163
-
164
- interface SearchBarProps {
165
- value: string;
166
- onChange: (value: string) => void;
167
- mediaType: string | undefined;
168
- onMediaTypeChange: (type: string | undefined) => void;
169
- /** When true, the media type filter is locked (no tabs shown) */
170
- locked?: boolean;
171
- classNames?: MediaPickerClassNames;
172
- }
173
- declare function SearchBar({ value, onChange, mediaType, onMediaTypeChange, locked, classNames, }: SearchBarProps): react_jsx_runtime.JSX.Element;
174
-
175
- interface UploadZoneProps {
176
- onUpload: (file: File) => Promise<void>;
177
- isUploading: boolean;
178
- accept?: string[];
179
- classNames?: MediaPickerClassNames;
180
- }
181
- declare function UploadZone({ onUpload, isUploading, accept, classNames }: UploadZoneProps): react_jsx_runtime.JSX.Element;
182
-
183
- export { type AdminMediaCallbacks, MediaCard, MediaGrid, MediaPicker, type MediaPickerCallbacks, type MediaPickerClassNames, type MediaPickerItem, type MediaPickerListResult, type MediaPickerMode, type MediaPickerProps, MediaPickerProvider, SearchBar, UploadZone, createAdminMediaCallbacks, useMediaPicker };
package/dist/picker.js DELETED
@@ -1,2 +0,0 @@
1
- "use client";
2
- function ce(e="/api/admin"){let r=`${e}/media`;return{fetchMedia:async i=>{let t=new URLSearchParams;i.search&&t.set("search",i.search),i.mediaType&&t.set("mediaType",i.mediaType),t.set("limit",String(i.limit)),t.set("offset",String(i.offset));let s=`${r}?${t.toString()}`,n=await fetch(s);if(!n.ok)throw new Error(`Failed to fetch media: ${n.status}`);return n.json()},uploadMedia:async i=>{let t=new FormData;t.append("file",i);let s=await fetch(r,{method:"POST",body:t});if(!s.ok)throw new Error(`Upload failed: ${s.status}`);return s.json()},getMediaUrl:async i=>{try{let t=await fetch(`${r}/${i}`);return t.ok?(await t.json()).url??null:null}catch{return null}}}}import{Check as pe,File as K,FileText as fe,Film as be,Music as ge}from"lucide-react";import{clsx as me}from"clsx";import{twMerge as ue}from"tailwind-merge";function a(...e){return ue(me(e))}import{jsx as P,jsxs as ye}from"react/jsx-runtime";var ke={video:be,audio:ge,document:fe,other:K};function R({item:e,isSelected:r,onToggle:i,classNames:t}){let s=e.mediaType==="image",n=s?null:ke[e.mediaType]??K;return ye("button",{type:"button",onClick:i,className:a("group relative aspect-square overflow-hidden rounded-lg border-2 transition-all","focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-zinc-950",r?"border-blue-500 ring-2 ring-blue-500/20":"border-zinc-200 hover:border-zinc-300 dark:border-zinc-800 dark:hover:border-zinc-700",t?.card,r&&t?.cardSelected),children:[s?P("img",{src:e.url,alt:e.alt??e.filename,className:a("h-full w-full object-cover",t?.cardImage),loading:"lazy"}):P("div",{className:"flex h-full w-full items-center justify-center bg-zinc-100 dark:bg-zinc-800",children:n&&P(n,{className:"h-8 w-8 text-zinc-400 dark:text-zinc-500"})}),r&&P("div",{className:"absolute right-1.5 top-1.5 flex h-5 w-5 items-center justify-center rounded-full bg-blue-500 text-white",children:P(pe,{className:"h-3 w-3"})}),P("div",{className:a("absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/60 to-transparent px-2 py-1.5","opacity-0 transition-opacity group-hover:opacity-100",t?.cardLabel),children:P("p",{className:"truncate text-xs text-white",children:e.title??e.filename})})]})}import{Fragment as ve,jsx as x,jsxs as U}from"react/jsx-runtime";function L({items:e,selected:r,onToggle:i,isLoading:t,total:s,offset:n,limit:u,onPageChange:d,classNames:b}){if(t)return x("div",{className:a("grid grid-cols-4 gap-3 sm:grid-cols-6",b?.grid),children:Array.from({length:12}).map((y,w)=>x("div",{className:a("aspect-square animate-pulse rounded-lg bg-zinc-100 dark:bg-zinc-800",b?.loading)},`skeleton-${w.toString()}`))});if(e.length===0)return x("div",{className:a("py-12 text-center text-sm text-zinc-500 dark:text-zinc-400",b?.empty),children:"No media found. Upload a file to get started."});let l=Math.ceil(s/u),o=Math.floor(n/u)+1;return U(ve,{children:[x("div",{className:a("grid grid-cols-4 gap-3 sm:grid-cols-6",b?.grid),children:e.map(y=>x(R,{item:y,isSelected:r.has(y.id),onToggle:()=>i(y),classNames:b},y.id))}),l>1&&U("div",{className:"mt-4 flex items-center justify-center gap-2",children:[x("button",{type:"button",disabled:o<=1,onClick:()=>d(n-u),className:"rounded-md border border-zinc-300 px-3 py-1 text-sm disabled:opacity-50 dark:border-zinc-700",children:"Prev"}),U("span",{className:"text-sm text-zinc-500 dark:text-zinc-400",children:[o," / ",l]}),x("button",{type:"button",disabled:o>=l,onClick:()=>d(n+u),className:"rounded-md border border-zinc-300 px-3 py-1 text-sm disabled:opacity-50 dark:border-zinc-700",children:"Next"})]})]})}import*as m from"@radix-ui/react-dialog";import*as V from"@radix-ui/react-visually-hidden";import{X as Se}from"lucide-react";import{useCallback as C,useEffect as te,useRef as ie,useState as k}from"react";import{createContext as Pe,useContext as xe,useMemo as he}from"react";import{jsx as ze}from"react/jsx-runtime";var Q=Pe(null);function Me({children:e,fetchMedia:r,uploadMedia:i}){let t=he(()=>({fetchMedia:r,uploadMedia:i}),[r,i]);return ze(Q,{value:t,children:e})}function $(){let e=xe(Q);if(!e)throw new Error("useMediaPicker must be used within <MediaPickerProvider>. Wrap your admin layout with <MediaPickerProvider fetchMedia={...} uploadMedia={...}>.");return e}import{Search as Ce}from"lucide-react";import{jsx as z,jsxs as Y}from"react/jsx-runtime";var we=[{value:void 0,label:"All"},{value:"image",label:"Images"},{value:"video",label:"Videos"},{value:"audio",label:"Audio"},{value:"document",label:"Docs"}];function E({value:e,onChange:r,mediaType:i,onMediaTypeChange:t,locked:s,classNames:n}){let u=!s;return Y("div",{className:"flex items-center gap-3",children:[Y("div",{className:"relative flex-1",children:[z(Ce,{className:"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-zinc-400"}),z("input",{type:"text",value:e,onChange:d=>r(d.target.value),placeholder:"Search media...",className:a("w-full rounded-md border border-zinc-300 bg-transparent py-2 pl-10 pr-3 text-sm","placeholder:text-zinc-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500","dark:border-zinc-700 dark:placeholder:text-zinc-500",n?.searchInput)})]}),u&&z("div",{className:a("flex gap-1",n?.filterTabs),children:we.map(d=>z("button",{type:"button",onClick:()=>t(d.value),className:a("rounded-md px-3 py-1.5 text-xs font-medium transition-colors",i===d.value?"bg-zinc-900 text-white dark:bg-zinc-100 dark:text-zinc-900":"text-zinc-600 hover:bg-zinc-100 dark:text-zinc-400 dark:hover:bg-zinc-800"),children:d.label},d.label))})]})}import{Upload as Ne}from"lucide-react";import{useCallback as ee,useRef as De,useState as Ie}from"react";import{jsx as F,jsxs as Te}from"react/jsx-runtime";function A({onUpload:e,isUploading:r,accept:i,classNames:t}){let s=De(null),[n,u]=Ie(!1),d=ee(async l=>{l.preventDefault(),u(!1);let o=l.dataTransfer.files[0];o&&await e(o)},[e]),b=ee(async l=>{let o=l.target.files?.[0];o&&(await e(o),l.target.value="")},[e]);return Te("button",{type:"button",onDragOver:l=>{l.preventDefault(),u(!0)},onDragLeave:()=>u(!1),onDrop:d,onClick:()=>s.current?.click(),className:a("mb-4 flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed px-6 py-6 transition-colors",n?"border-blue-500 bg-blue-50 dark:bg-blue-950/20":"border-zinc-300 hover:border-zinc-400 dark:border-zinc-700 dark:hover:border-zinc-600",t?.uploadZone,n&&t?.uploadZoneActive),children:[F(Ne,{className:"mb-2 h-6 w-6 text-zinc-400 dark:text-zinc-500"}),F("p",{className:"text-sm text-zinc-600 dark:text-zinc-400",children:r?"Uploading...":"Drop a file here or click to upload"}),F("input",{ref:s,type:"file",accept:i?.join(","),onChange:b,className:"hidden"})]})}import{jsx as c,jsxs as h}from"react/jsx-runtime";var re=24;function Re({open:e,onOpenChange:r,onSelect:i,mode:t="single",accept:s,mediaType:n,maxSelect:u,selectedIds:d=[],title:b="Select Media",description:l,classNames:o,className:y,children:w}){let{fetchMedia:B,uploadMedia:H}=$(),[N,Z]=k([]),[M,G]=k(0),[v,D]=k(()=>new Set(d)),[I,j]=k(""),[T,ae]=k(n),[oe,_]=k(!1),[ne,q]=k(!1),[S,O]=k(0),W=C(async()=>{_(!0);try{let p=await B({search:I||void 0,mediaType:T,limit:re,offset:S});Z(p.items),G(p.total)}catch{}finally{_(!1)}},[B,I,T,S]);te(()=>{e&&W()},[e,W]);let X=ie(e),J=ie(d);J.current=d,te(()=>{X.current&&!e&&(j(""),O(0),D(new Set(J.current))),X.current=e},[e]);let se=C(p=>{if(t==="single"){i([p]),r(!1);return}D(g=>{let f=new Set(g);if(f.has(p.id))f.delete(p.id);else{if(u&&f.size>=u)return g;f.add(p.id)}return f})},[t,u,i,r]),de=C(()=>{let p=N.filter(g=>v.has(g.id));i(p),r(!1)},[N,v,i,r]),le=C(async p=>{q(!0);try{let g=await H(p);if(t==="single"){i([g]),r(!1);return}Z(f=>[g,...f]),G(f=>f+1),D(f=>new Set([...f,g.id]))}finally{q(!1)}},[H,t,i,r]);return c(m.Root,{open:e,onOpenChange:r,children:h(m.Portal,{children:[c(m.Overlay,{className:a("fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",o?.overlay)}),h(m.Content,{...!l&&{"aria-describedby":void 0},className:a("fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2","flex max-h-[85vh] w-[90vw] max-w-4xl flex-col","rounded-xl border border-zinc-200 bg-white shadow-2xl dark:border-zinc-800 dark:bg-zinc-950",o?.content,y),children:[h("div",{className:a("flex items-center justify-between border-b border-zinc-200 px-6 py-4 dark:border-zinc-800",o?.header),children:[c(m.Title,{className:a("text-lg font-semibold text-zinc-900 dark:text-zinc-50",o?.title),children:b}),l&&c(V.Root,{asChild:!0,children:c(m.Description,{children:l})}),h(m.Close,{className:"rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-zinc-400 dark:focus:ring-zinc-600",children:[c(Se,{className:"h-4 w-4"}),c(V.Root,{children:"Close"})]})]}),c("div",{className:a("border-b border-zinc-200 px-6 py-3 dark:border-zinc-800",o?.toolbar),children:c(E,{value:I,onChange:j,mediaType:T,onMediaTypeChange:ae,locked:!!n,classNames:o})}),h("div",{className:"flex-1 overflow-y-auto px-6 py-4",children:[c(A,{onUpload:le,isUploading:ne,accept:s,classNames:o}),c(L,{items:N,selected:v,onToggle:se,isLoading:oe,total:M,offset:S,limit:re,onPageChange:O,classNames:o})]}),h("div",{className:a("flex items-center justify-between border-t border-zinc-200 px-6 py-4 dark:border-zinc-800",o?.footer),children:[c("span",{className:"text-sm text-zinc-500 dark:text-zinc-400",children:t==="single"?`${M.toString()} item${M!==1?"s":""} \u2014 click to select`:v.size>0?`${v.size.toString()} selected`:`${M.toString()} item${M!==1?"s":""}`}),h("div",{className:"flex gap-2",children:[w,c(m.Close,{asChild:!0,children:c("button",{type:"button",className:a("rounded-md border border-zinc-300 px-4 py-2 text-sm font-medium text-zinc-700 hover:bg-zinc-50","dark:border-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-900",o?.cancelButton),children:"Cancel"})}),t!=="single"&&c("button",{type:"button",onClick:de,disabled:v.size===0,className:a("rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-500","disabled:cursor-not-allowed disabled:opacity-50",o?.confirmButton),children:`Select (${v.size.toString()})`})]})]})]})]})})}export{R as MediaCard,L as MediaGrid,Re as MediaPicker,Me as MediaPickerProvider,E as SearchBar,A as UploadZone,ce as createAdminMediaCallbacks,$ as useMediaPicker};
@@ -1 +0,0 @@
1
- import{a as n}from"./chunk-YAHM4C5J.js";var r=null;function d(){if(!r)throw new Error("@murumets-ee/media plugin not initialized. Add media() to your plugins array.");return r}function m(t){let e={acceptedTypes:t?.acceptedTypes??["image/*","video/*","audio/*","application/pdf"],maxUploadSize:t?.maxUploadSize??52428800,defaultVisibility:t?.defaultVisibility??"public",imageStyles:t?.imageStyles??{thumbnail:{width:200,height:200,fit:"cover",format:"webp",quality:80}}};return{name:"@murumets-ee/media",entities:[n],init:async i=>{if(!i.plugins.has("@murumets-ee/storage"))throw new Error("@murumets-ee/media requires @murumets-ee/storage plugin. Add storage() before media() in your plugins array.");if(!i.plugins.has("@murumets-ee/settings"))throw new Error("@murumets-ee/media requires @murumets-ee/settings plugin. Add settings() before media() in your plugins array.");r=e;try{let{createSettingsClient:a}=await import("@murumets-ee/settings"),{imageStylesSettings:o}=await import("./image-styles-settings-AB5WFEFF.js"),s=a(o,{app:i});await s.has("imageStyles")||(await s.set("imageStyles",e.imageStyles),i.logger.info({styles:Object.keys(e.imageStyles)},"Seeded default image styles to settings DB"))}catch(a){i.logger.warn({error:a},"Failed to seed image styles to settings DB (non-fatal)")}i.logger.info({acceptedTypes:e.acceptedTypes,maxUploadSize:e.maxUploadSize,defaultVisibility:e.defaultVisibility},"Media plugin initialized")}}}export{d as getMediaConfig,m as media};
package/dist/plugin.d.ts DELETED
@@ -1,35 +0,0 @@
1
- import { Plugin } from '@murumets-ee/core';
2
- import { e as MediaPluginConfig } from './types-ChlTxvlq.js';
3
- import '@murumets-ee/storage';
4
-
5
- /**
6
- * Media plugin — auto-registers the Media entity and validates storage dependency.
7
- *
8
- * @example
9
- * ```typescript
10
- * import { media } from '@murumets-ee/media/plugin'
11
- *
12
- * export default defineConfig({
13
- * plugins: [
14
- * storage(),
15
- * media({ maxUploadSize: 10 * 1024 * 1024 }),
16
- * ],
17
- * })
18
- * ```
19
- */
20
-
21
- /**
22
- * Get the resolved media plugin configuration.
23
- * Throws if plugin not initialized.
24
- */
25
- declare function getMediaConfig(): Required<MediaPluginConfig>;
26
- /**
27
- * Media plugin factory.
28
- *
29
- * - Auto-registers the Media entity (no need to add to entities array)
30
- * - Validates that @murumets-ee/storage plugin is registered
31
- * - Stores resolved configuration
32
- */
33
- declare function media(config?: MediaPluginConfig): Plugin;
34
-
35
- export { getMediaConfig, media };
package/dist/plugin.js DELETED
@@ -1 +0,0 @@
1
- import{a as n}from"./chunk-L6BDKI76.js";var r=null;function d(){if(!r)throw new Error("@murumets-ee/media plugin not initialized. Add media() to your plugins array.");return r}function m(t){let e={acceptedTypes:t?.acceptedTypes??["image/*","video/*","audio/*","application/pdf"],maxUploadSize:t?.maxUploadSize??52428800,defaultVisibility:t?.defaultVisibility??"public",imageStyles:t?.imageStyles??{thumbnail:{width:200,height:200,fit:"cover",format:"webp",quality:80}}};return{name:"@murumets-ee/media",entities:[n],init:async i=>{if(!i.plugins.has("@murumets-ee/storage"))throw new Error("@murumets-ee/media requires @murumets-ee/storage plugin. Add storage() before media() in your plugins array.");if(!i.plugins.has("@murumets-ee/settings"))throw new Error("@murumets-ee/media requires @murumets-ee/settings plugin. Add settings() before media() in your plugins array.");r=e;try{let{createSettingsClient:a}=await import("@murumets-ee/settings"),{imageStylesSettings:o}=await import("./image-styles-settings.js"),s=a(o,{app:i});await s.has("imageStyles")||(await s.set("imageStyles",e.imageStyles),i.logger.info({styles:Object.keys(e.imageStyles)},"Seeded default image styles to settings DB"))}catch(a){i.logger.warn({error:a},"Failed to seed image styles to settings DB (non-fatal)")}i.logger.info({acceptedTypes:e.acceptedTypes,maxUploadSize:e.maxUploadSize,defaultVisibility:e.defaultVisibility},"Media plugin initialized")}}}export{d as getMediaConfig,m as media};
@@ -1,36 +0,0 @@
1
- import { Logger } from '@murumets-ee/core';
2
- import { FindByIdOptions, FindManyOptions, CountOptions } from '@murumets-ee/entity/query';
3
- import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
4
- import { b as MediaRecord } from './types-ChlTxvlq.js';
5
- import '@murumets-ee/storage';
6
-
7
- /**
8
- * MediaQueryClient — read-only media client for frontends.
9
- *
10
- * Usage:
11
- * import { createMediaQueryClient } from '@murumets-ee/media/query'
12
- * const media = await createMediaQueryClient()
13
- * const image = await media.findById(id)
14
- */
15
-
16
- interface MediaQueryClientConfig {
17
- db: PostgresJsDatabase;
18
- logger?: Logger;
19
- }
20
- declare class MediaQueryClient {
21
- private db;
22
- private logger?;
23
- private query;
24
- constructor(config: MediaQueryClientConfig);
25
- private getQuery;
26
- findById(id: string, options?: FindByIdOptions): Promise<MediaRecord | null>;
27
- findMany(options?: FindManyOptions): Promise<MediaRecord[]>;
28
- count(options?: CountOptions): Promise<number>;
29
- }
30
- /**
31
- * Factory — creates a MediaQueryClient.
32
- * Must be called after createApp().
33
- */
34
- declare function createMediaQueryClient(): Promise<MediaQueryClient>;
35
-
36
- export { MediaQueryClient, type MediaQueryClientConfig, createMediaQueryClient };
@@ -1 +0,0 @@
1
- import{a as n}from"./chunk-L6BDKI76.js";var r=class{db;logger;query=null;constructor(e){this.db=e.db,this.logger=e.logger}async getQuery(){if(!this.query){let{QueryClient:e}=await import("@murumets-ee/entity/query");this.query=new e({entity:n,db:this.db,logger:this.logger})}return this.query}async findById(e,t){return await(await this.getQuery()).findById(e,t)}async findMany(e){return await(await this.getQuery()).findMany(e)}async count(e){return(await this.getQuery()).count(e)}};async function u(){let{getApp:i}=await import("@murumets-ee/core"),e=i();return new r({db:e.db.readOnly,logger:e.logger.child({mediaQuery:!0})})}export{r as MediaQueryClient,u as createMediaQueryClient};
package/dist/ref.js DELETED
@@ -1 +0,0 @@
1
- var s=/\[media:(image|video|audio|file):([a-f0-9-]{36})(?::([a-z0-9-]+))?\]/g;function f(e){let t=[];s.lastIndex=0;let i=s.exec(e);for(;i!==null;)t.push({raw:i[0],type:i[1],id:i[2],variant:i[3]??null}),i=s.exec(e);return t}function R(e,t){let i=f(e),r=t?.type?i.filter(a=>a.type===t.type):i;return[...new Set(r.map(a=>a.id))]}function p(e,t){let i=e.flatMap(r=>R(r,t));return[...new Set(i)]}function u(e,t,i){return i?`[media:${e}:${t}:${i}]`:`[media:${e}:${t}]`}var c={image:e=>`<img ${[`src="${e.url}"`,`alt="${e.alt??e.filename??""}"`,e.width?`width="${e.width}"`:"",e.height?`height="${e.height}"`:""].filter(Boolean).join(" ")} />`,video:e=>`<video src="${e.url}" controls></video>`,audio:e=>`<audio src="${e.url}" controls></audio>`,file:e=>`<a href="${e.url}" download="${e.filename??""}">${e.filename??e.url}</a>`};async function g(e,t,i){let r=f(e);if(r.length===0)return e;let a=await t(r),d=e;for(let n of r){let o=a.get(n.id);if(!o)continue;let l=i?.[n.type]??c[n.type];l&&(d=d.replace(n.raw,l(o)))}return d}export{u as createMediaRef,p as extractAllMediaIds,R as extractMediaIds,f as parseMediaRefs,g as resolveMediaRefs};
@@ -1 +0,0 @@
1
- import{a as w,b as h,c as v}from"./chunk-QTUXM53A.js";import"server-only";var g=100;async function C(R){let{db:k,storage:o,logger:r,styles:y}=R,{AdminClient:K}=await import("@murumets-ee/entity/admin"),{Media:M}=await import("./entity-QOBW3TFU.js"),{schemaRegistry:O}=await import("@murumets-ee/db"),{eq:j}=await import("drizzle-orm"),S=new K({entity:M,db:k,logger:r}),p=O.get("media");if(!p)throw new Error("Media schema not registered");let a={total:0,processed:0,skipped:0,errors:0},u=0;for(r?.info({styles:Object.keys(y)},"Starting variant regeneration");;){let d=await S.findMany({where:j(p.mediaType,"image"),limit:g,offset:u});if(d.length===0)break;a.total+=d.length;for(let t of d)try{if(!w(t.mimeType)){a.skipped++;continue}let n=await o.download(t.fileKey),f;if(Buffer.isBuffer(n.body))f=n.body;else{let e=[],i=n.body.getReader();for(;;){let{done:s,value:c}=await i.read();if(s)break;c&&e.push(c)}f=Buffer.concat(e)}let B=await h(f,y),m=await o.getMetadata(t.fileKey),b=m?.metadata?.variants;if(b)for(let e of Object.values(b))await o.delete(e).catch(()=>{});let l={},I=m?.visibility??"public";for(let[e,i]of B.variants.entries()){let s=v(t.fileKey,e,i.format);try{await o.upload(i.buffer,{key:s,filename:`${e}_${t.filename}`,mimeType:i.mimeType,size:i.buffer.byteLength,visibility:I,metadata:{variantOf:t.fileKey,style:e}}),l[e]=s}catch(c){r?.warn({style:e,key:s,error:c},"Failed to upload regenerated variant (non-fatal)")}}Object.keys(l).length>0&&await o.updateMetadata(t.fileKey,{metadata:{...m?.metadata??{},variants:l}}).catch(e=>{r?.warn({key:t.fileKey,error:e},"Failed to update variant metadata (non-fatal)")}),a.processed++,r?.debug({id:t.id,variants:Object.keys(l)},"Regenerated variants")}catch(n){a.errors++,r?.error({id:t.id,fileKey:t.fileKey,error:n},"Failed to regenerate variants for media record")}if(u+=g,d.length<g)break}return r?.info(a,"Variant regeneration complete"),a}export{C as regenerateAllVariants};
@@ -1,101 +0,0 @@
1
- import { FileVisibility } from '@murumets-ee/storage';
2
-
3
- /** Media type derived from MIME type */
4
- type MediaType = 'image' | 'video' | 'audio' | 'document' | 'other';
5
- /** Options for uploading media through MediaClient */
6
- interface MediaUploadOptions {
7
- /** Original filename */
8
- filename: string;
9
- /** MIME type */
10
- mimeType: string;
11
- /** File size in bytes */
12
- size: number;
13
- /** File visibility (default: from plugin config) */
14
- visibility?: FileVisibility;
15
- /** Display title (defaults to filename without extension) */
16
- title?: string;
17
- /** Alt text for images */
18
- alt?: string;
19
- /** Description */
20
- description?: string;
21
- /** Image width in pixels (client-measured) */
22
- width?: number;
23
- /** Image height in pixels (client-measured) */
24
- height?: number;
25
- /** UUID of the uploading user */
26
- uploadedBy?: string;
27
- }
28
- /** Result of a media upload: entity record + storage URL */
29
- interface MediaUploadResult {
30
- /** The created media entity record */
31
- media: MediaRecord;
32
- /** The file's public/signed URL */
33
- url: string;
34
- }
35
- /** Lightweight view of a media entity for API consumers */
36
- interface MediaRecord {
37
- id: string;
38
- title: string | null;
39
- alt: string | null;
40
- description: string | null;
41
- fileKey: string;
42
- filename: string;
43
- mimeType: string;
44
- size: number;
45
- width: number | null;
46
- height: number | null;
47
- mediaType: MediaType;
48
- createdBy: string | null;
49
- createdAt: Date | string;
50
- updatedAt: Date | string;
51
- }
52
- /** Options for listing media */
53
- interface MediaListOptions {
54
- /** Filter by media type */
55
- mediaType?: MediaType;
56
- /** Filter by MIME type prefix (e.g., 'image/') */
57
- mimeTypePrefix?: string;
58
- /** Search in title and filename */
59
- search?: string;
60
- /** Pagination limit (default: 50) */
61
- limit?: number;
62
- /** Pagination offset (default: 0) */
63
- offset?: number;
64
- /** Order by field (default: 'createdAt') */
65
- orderBy?: 'createdAt' | 'filename' | 'size';
66
- /** Order direction (default: 'desc') */
67
- orderDirection?: 'asc' | 'desc';
68
- }
69
- /** Paginated media list result */
70
- interface MediaListResult {
71
- items: MediaRecord[];
72
- total: number;
73
- limit: number;
74
- offset: number;
75
- }
76
- /** Named image processing preset — defines how to resize/convert an image variant */
77
- interface ImageStyle {
78
- /** Max width in pixels (omit for height-only constraint) */
79
- width?: number;
80
- /** Max height in pixels (omit for height-only constraint) */
81
- height?: number;
82
- /** Resize strategy (default: 'cover') */
83
- fit?: 'cover' | 'contain' | 'inside' | 'outside' | 'fill';
84
- /** Output format (default: 'webp') */
85
- format?: 'webp' | 'jpeg' | 'png' | 'avif';
86
- /** Output quality 1–100 (default: 80) */
87
- quality?: number;
88
- }
89
- /** Plugin configuration for @murumets-ee/media */
90
- interface MediaPluginConfig {
91
- /** Accepted MIME type patterns (default: ['image/*', 'video/*', 'audio/*', 'application/pdf']) */
92
- acceptedTypes?: string[];
93
- /** Max upload size in bytes (default: 50MB) */
94
- maxUploadSize?: number;
95
- /** Default file visibility (default: 'public') */
96
- defaultVisibility?: FileVisibility;
97
- /** Named image processing presets. Variants generated on upload for each style. */
98
- imageStyles?: Record<string, ImageStyle>;
99
- }
100
-
101
- export type { ImageStyle as I, MediaUploadOptions as M, MediaUploadResult as a, MediaRecord as b, MediaListOptions as c, MediaListResult as d, MediaPluginConfig as e, MediaType as f };