nuxt-media-library 0.0.50 → 0.0.52

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 (40) hide show
  1. package/README.md +71 -4
  2. package/dist/module.d.mts +16 -0
  3. package/dist/module.d.ts +16 -0
  4. package/dist/module.json +1 -1
  5. package/dist/module.mjs +50 -5
  6. package/dist/runtime/components/MLMedia/Dropzone.d.vue.ts +1 -1
  7. package/dist/runtime/components/MLMedia/Dropzone.vue.d.ts +1 -1
  8. package/dist/runtime/components/MLMedia/Field.d.vue.ts +17 -4
  9. package/dist/runtime/components/MLMedia/Field.vue +19 -2
  10. package/dist/runtime/components/MLMedia/Field.vue.d.ts +17 -4
  11. package/dist/runtime/components/MLMedia/FilePicker.d.vue.ts +2 -0
  12. package/dist/runtime/components/MLMedia/FilePicker.vue +3 -1
  13. package/dist/runtime/components/MLMedia/FilePicker.vue.d.ts +2 -0
  14. package/dist/runtime/components/MLMedia/FilePickerPanel.d.vue.ts +3 -0
  15. package/dist/runtime/components/MLMedia/FilePickerPanel.vue +6 -1
  16. package/dist/runtime/components/MLMedia/FilePickerPanel.vue.d.ts +3 -0
  17. package/dist/runtime/components/MLMedia/Gallery.d.vue.ts +15 -5
  18. package/dist/runtime/components/MLMedia/Gallery.vue +9 -3
  19. package/dist/runtime/components/MLMedia/Gallery.vue.d.ts +15 -5
  20. package/dist/runtime/components/MLMedia/GallerySection.d.vue.ts +11 -5
  21. package/dist/runtime/components/MLMedia/GallerySection.vue +21 -3
  22. package/dist/runtime/components/MLMedia/GallerySection.vue.d.ts +11 -5
  23. package/dist/runtime/components/MLMedia/MediaTile.d.vue.ts +3 -3
  24. package/dist/runtime/components/MLMedia/MediaTile.vue.d.ts +3 -3
  25. package/dist/runtime/components/MLMedia/PickerCard.d.vue.ts +2 -2
  26. package/dist/runtime/components/MLMedia/PickerCard.vue.d.ts +2 -2
  27. package/dist/runtime/components/MLMedia/PickerRow.d.vue.ts +2 -2
  28. package/dist/runtime/components/MLMedia/PickerRow.vue.d.ts +2 -2
  29. package/dist/runtime/components/MLMedia/ProgressVeil.d.vue.ts +1 -1
  30. package/dist/runtime/components/MLMedia/ProgressVeil.vue.d.ts +1 -1
  31. package/dist/runtime/composables/useMLFilePicker.d.ts +2 -0
  32. package/dist/runtime/composables/useMLMediaField.d.ts +6 -2
  33. package/dist/runtime/composables/useMLMediaField.js +1 -1
  34. package/dist/runtime/composables/useMLMediaGallery.d.ts +5 -1
  35. package/dist/runtime/composables/useMLMediaGallery.js +2 -2
  36. package/dist/runtime/composables/useMLMediaUpload.d.ts +21 -0
  37. package/dist/runtime/composables/useMLMediaUpload.js +41 -12
  38. package/dist/runtime/utils/uploader/serviceBackends.d.ts +26 -0
  39. package/dist/runtime/utils/uploader/serviceBackends.js +18 -0
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -47,8 +47,11 @@ export default defineNuxtConfig({
47
47
  mediaLibrary: {
48
48
  // Auto-register Vue components from src/runtime/components
49
49
  components: true,
50
- // Chunk uploader config (used by MlFileUpload)
51
- baseURL: 'https://api.example.com',
50
+ // Where the Nuxt server reaches the media library (with /v1). Server-side
51
+ // only, never sent to browsers; prefer NUXT_MEDIA_LIBRARY_BASE_URL.
52
+ baseURL: 'http://media-library.internal/v1',
53
+ // Only for direct browser uploads (proxyUploads: false): a public address.
54
+ // publicBaseURL: 'https://media.example.com/v1',
52
55
  // Uploads always go through Nuxt server (/api/media-library/uploads/*)
53
56
  routes: {
54
57
  createSession: '/uploads/sessions',
@@ -60,11 +63,18 @@ export default defineNuxtConfig({
60
63
  partSize: 8 * 1024 * 1024,
61
64
  concurrency: 3,
62
65
  checksum: { algorithm: 'SHA-256', perPart: true, final: true },
63
- headers: {},
66
+ // Sent to browsers too, except credentials: Authorization, Cookie,
67
+ // Proxy-Authorization and X-Api-Key stay server-side (the proxies add them).
68
+ headers: { 'X-Tenant': 'acme' },
64
69
  },
65
70
  })
66
71
  ```
67
72
 
73
+ Nuxt puts the public runtime config in every page, so the module never copies
74
+ `baseURL` or credential headers there. Set the media library's internal
75
+ address and any service token only server-side
76
+ (`NUXT_MEDIA_LIBRARY_BASE_URL`, `NUXT_MEDIA_LIBRARY_SERVICE_TOKEN`).
77
+
68
78
  ### Nuxt usage
69
79
 
70
80
  ```vue
@@ -164,7 +174,7 @@ app's decision, not the service's:
164
174
  // nuxt.config.ts
165
175
  export default defineNuxtConfig({
166
176
  mediaLibrary: {
167
- baseURL: process.env.MEDIA_LIBRARY_URL, // server side, e.g. http://media-library.internal/v1
177
+ // server side only: set NUXT_MEDIA_LIBRARY_BASE_URL (e.g. http://media-library.internal/v1)
168
178
  serviceUploads: { proxy: true }, // parts go through /api/media-library/service-uploads
169
179
  // or, direct to the media library (needs CORS for the site):
170
180
  // serviceUploads: { baseURL: 'https://media.example.com/v1' },
@@ -198,6 +208,63 @@ await $fetch(`/api/videos/lessons/${lessonId}/video`, { method: 'PUT', body: { s
198
208
  grant mode replaces the browser's grant with its own, and the service's
199
209
  `claim_upload` rejects the session.
200
210
 
211
+ #### One layer per service, widgets by backend name
212
+
213
+ Each service can ship a Nuxt layer that registers its upload backend and
214
+ serves its own routes (start, refresh, attach, data) to its service. Nuxt
215
+ merges the layers, so every widget on the site can use any backend by name.
216
+
217
+ ```ts
218
+ // itq-videos-layer/nuxt.config.ts
219
+ export default defineNuxtConfig({
220
+ modules: ['nuxt-media-library'],
221
+ mediaLibrary: {
222
+ serviceUploads: {
223
+ backends: { videos: { start: '/api/videos/uploads' } },
224
+ // refresh defaults to `${start}/:sessionId/grant`
225
+ },
226
+ },
227
+ })
228
+ ```
229
+
230
+ ```ts
231
+ // the site
232
+ export default defineNuxtConfig({
233
+ extends: ['itq-videos-layer', 'itq-courses-layer'],
234
+ modules: ['nuxt-media-library'],
235
+ mediaLibrary: { serviceUploads: { proxy: true } }, // where parts go, for every backend
236
+ })
237
+ ```
238
+
239
+ ```vue
240
+ <MLMediaGallery
241
+ v-model="lesson.mediaIds"
242
+ service="videos"
243
+ profile="lesson-video"
244
+ :service-body="{ context: { lessonId } }"
245
+ @uploaded="(assetId, role, sessionId) => attach(assetId, sessionId)"
246
+ />
247
+ ```
248
+
249
+ - `service` on `MLMediaGallery`, `MLMediaField` and the file picker (and
250
+ `useMLMediaUpload`): a backend name, or `{ start }` for a one-off. Without
251
+ it, widgets upload through Nuxt's own route as before; an unknown name
252
+ fails that upload with a message naming the missing backend.
253
+ - The start request carries the file (`mimeType`, `sizeBytes`, `filename`),
254
+ `profile` (each role's, in a gallery with roles) and `service-body`. The
255
+ service's profile decides kind, visibility and processing, so
256
+ `visibility`, `processing-config` and `collection-name` are not used with
257
+ `service`.
258
+ - `uploaded` also gives the upload's `sessionId` (gallery:
259
+ `(assetId, role, sessionId)`, field: `(assetId, sessionId)`), including
260
+ files uploaded from the picker and chosen. Send both to the service's
261
+ attach endpoint: its `claim_upload` needs them.
262
+ - Processing progress uses `/api/media-library/manage/assets/resolve` and
263
+ the processing stream, so the app's proxy must allow `read` on a file the
264
+ user just uploaded.
265
+ - There is no default backend: each layer's widgets name theirs. The module
266
+ only stores the paths; each layer's own routes reach its service.
267
+
201
268
  ### Vue installation (non-Nuxt)
202
269
 
203
270
  ```bash
package/dist/module.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
+ import { ServiceUploadBackend } from '../dist/runtime/utils/uploader/serviceBackends.js';
2
3
  export * from '../dist/runtime/types/resourcePolicy.js';
3
4
  export * from '../dist/runtime/types/uploader.js';
4
5
  export * from '../dist/runtime/types/preview.js';
@@ -14,7 +15,17 @@ interface ChunkUploaderRoutesConfig {
14
15
  }
15
16
  interface ModuleOptions {
16
17
  components?: boolean;
18
+ /**
19
+ * Where the Nuxt server reaches the media library, with `/v1` (often an
20
+ * internal address). Server-side only: never sent to browsers. Prefer
21
+ * NUXT_MEDIA_LIBRARY_BASE_URL at runtime.
22
+ */
17
23
  baseURL?: string;
24
+ /**
25
+ * The media library address browsers use for direct uploads
26
+ * (`proxyUploads: false`), with `/v1`. Public: only set a public address.
27
+ */
28
+ publicBaseURL?: string;
18
29
  routes?: ChunkUploaderRoutesConfig;
19
30
  partSize?: number;
20
31
  concurrency?: number;
@@ -31,10 +42,15 @@ interface ModuleOptions {
31
42
  * `baseURL` is where the browser sends those uploads (the media library's
32
43
  * public API root with `/v1`, or a proxy route); with `proxy` it defaults
33
44
  * to the pass-through route.
45
+ *
46
+ * `backends` names the services that start uploads, so widgets can use
47
+ * `service="videos"`. Each service's Nuxt layer registers its own; Nuxt
48
+ * merges them across layers. Paths only: each layer serves its routes.
34
49
  */
35
50
  serviceUploads?: {
36
51
  proxy?: boolean;
37
52
  baseURL?: string;
53
+ backends?: Record<string, ServiceUploadBackend>;
38
54
  };
39
55
  }
40
56
  declare const SERVICE_UPLOADS_ROUTE = "/api/media-library/service-uploads";
package/dist/module.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
+ import { ServiceUploadBackend } from '../dist/runtime/utils/uploader/serviceBackends.js';
2
3
  export * from '../dist/runtime/types/resourcePolicy.js';
3
4
  export * from '../dist/runtime/types/uploader.js';
4
5
  export * from '../dist/runtime/types/preview.js';
@@ -14,7 +15,17 @@ interface ChunkUploaderRoutesConfig {
14
15
  }
15
16
  interface ModuleOptions {
16
17
  components?: boolean;
18
+ /**
19
+ * Where the Nuxt server reaches the media library, with `/v1` (often an
20
+ * internal address). Server-side only: never sent to browsers. Prefer
21
+ * NUXT_MEDIA_LIBRARY_BASE_URL at runtime.
22
+ */
17
23
  baseURL?: string;
24
+ /**
25
+ * The media library address browsers use for direct uploads
26
+ * (`proxyUploads: false`), with `/v1`. Public: only set a public address.
27
+ */
28
+ publicBaseURL?: string;
18
29
  routes?: ChunkUploaderRoutesConfig;
19
30
  partSize?: number;
20
31
  concurrency?: number;
@@ -31,10 +42,15 @@ interface ModuleOptions {
31
42
  * `baseURL` is where the browser sends those uploads (the media library's
32
43
  * public API root with `/v1`, or a proxy route); with `proxy` it defaults
33
44
  * to the pass-through route.
45
+ *
46
+ * `backends` names the services that start uploads, so widgets can use
47
+ * `service="videos"`. Each service's Nuxt layer registers its own; Nuxt
48
+ * merges them across layers. Paths only: each layer serves its routes.
34
49
  */
35
50
  serviceUploads?: {
36
51
  proxy?: boolean;
37
52
  baseURL?: string;
53
+ backends?: Record<string, ServiceUploadBackend>;
38
54
  };
39
55
  }
40
56
  declare const SERVICE_UPLOADS_ROUTE = "/api/media-library/service-uploads";
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "media-library",
3
3
  "configKey": "mediaLibrary",
4
- "version": "0.0.50",
4
+ "version": "0.0.52",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,10 +1,42 @@
1
- import { defineNuxtModule, createResolver, addImportsDir, addComponentsDir, addServerHandler, addPlugin } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addImportsDir, addComponentsDir, addServerHandler, useLogger, addPlugin } from '@nuxt/kit';
2
2
  export * from '../dist/runtime/types/resourcePolicy.js';
3
3
  export * from '../dist/runtime/types/uploader.js';
4
4
  export * from '../dist/runtime/types/preview.js';
5
5
  export * from '../dist/runtime/types/asset.js';
6
6
 
7
+ const CREDENTIAL_HEADERS = ["authorization", "proxy-authorization", "cookie", "x-api-key"];
8
+ function browserHeaders(headers) {
9
+ if (!headers || typeof headers !== "object") return { headers: {}, dropped: [] };
10
+ const kept = {};
11
+ const dropped = [];
12
+ for (const [name, value] of Object.entries(headers)) {
13
+ if (CREDENTIAL_HEADERS.includes(name.toLowerCase())) dropped.push(name);
14
+ else kept[name] = value;
15
+ }
16
+ return { headers: kept, dropped };
17
+ }
18
+ function browserBaseURL(existingPublic, publicBaseURL) {
19
+ return existingPublic ?? publicBaseURL ?? "";
20
+ }
21
+
7
22
  const SERVICE_UPLOADS_ROUTE = "/api/media-library/service-uploads";
23
+ function serviceBackends(backends, logger) {
24
+ const out = {};
25
+ for (const [name, backend] of Object.entries(backends ?? {})) {
26
+ if (!backend || typeof backend.start !== "string" || !backend.start) {
27
+ logger.warn(`mediaLibrary.serviceUploads.backends.${name} has no start path; ignored.`);
28
+ continue;
29
+ }
30
+ if (backend.refresh && !backend.refresh.includes(":sessionId")) {
31
+ logger.warn(`mediaLibrary.serviceUploads.backends.${name}.refresh needs a :sessionId placeholder; using the default.`);
32
+ }
33
+ out[name] = {
34
+ start: backend.start,
35
+ ...backend.refresh?.includes(":sessionId") ? { refresh: backend.refresh } : {}
36
+ };
37
+ }
38
+ return out;
39
+ }
8
40
  const module$1 = defineNuxtModule({
9
41
  meta: {
10
42
  name: "media-library",
@@ -38,7 +70,10 @@ const module$1 = defineNuxtModule({
38
70
  const envTenantHeader = process.env.NUXT_MEDIA_LIBRARY_HEADERS_X_TENANT;
39
71
  const envHeaders = envTenantHeader ? { "X-tenant": envTenantHeader } : {};
40
72
  const existingHeaders = typeof existingPrivate.headers === "object" ? existingPrivate.headers : {};
41
- const resolvedHeaders = { ...existingHeaders };
73
+ const optionCredentials = Object.fromEntries(
74
+ browserHeaders(options.headers).dropped.map((name) => [name, options.headers[name]])
75
+ );
76
+ const resolvedHeaders = { ...optionCredentials, ...existingHeaders };
42
77
  for (const [key, value] of Object.entries(envHeaders)) {
43
78
  if (!resolvedHeaders[key]) {
44
79
  resolvedHeaders[key] = value;
@@ -105,9 +140,17 @@ const module$1 = defineNuxtModule({
105
140
  // grants). Declared so NUXT_MEDIA_LIBRARY_SERVICE_TOKEN applies.
106
141
  serviceToken: existingPrivate.serviceToken ?? ""
107
142
  };
143
+ const logger = useLogger("media-library");
144
+ const publicHeaders = browserHeaders(existingPublic.headers ?? options.headers);
145
+ if (publicHeaders.dropped.length) {
146
+ logger.warn(`mediaLibrary.headers: ${publicHeaders.dropped.join(", ")} kept server-side, not sent to browsers.`);
147
+ }
148
+ if (existingPublic.proxyUploads === false && !existingPublic.baseURL && !options.publicBaseURL) {
149
+ logger.warn("proxyUploads is false but no public media library address is set: set mediaLibrary.publicBaseURL. baseURL is server-side only.");
150
+ }
108
151
  nuxt.options.runtimeConfig.public.mediaLibrary = {
109
152
  ...existingPublic,
110
- baseURL: existingPublic.baseURL ?? options.baseURL ?? "",
153
+ baseURL: browserBaseURL(existingPublic.baseURL, options.publicBaseURL),
111
154
  routes: existingPublic.routes ?? routes,
112
155
  proxyUploads: existingPublic.proxyUploads ?? true,
113
156
  partSize: existingPublic.partSize ?? options.partSize ?? 8 * 1024 * 1024,
@@ -117,9 +160,11 @@ const module$1 = defineNuxtModule({
117
160
  perPart: existingPublic.checksum?.perPart ?? options.checksum?.perPart ?? true,
118
161
  final: existingPublic.checksum?.final ?? options.checksum?.final ?? true
119
162
  },
120
- headers: existingPublic.headers ?? options.headers ?? {},
163
+ headers: publicHeaders.headers,
121
164
  // Where useMLServiceUpload sends parts (NUXT_PUBLIC_MEDIA_LIBRARY_SERVICE_UPLOAD_BASE_URL).
122
- serviceUploadBaseURL: existingPublic.serviceUploadBaseURL || options.serviceUploads?.baseURL || (options.serviceUploads?.proxy ? SERVICE_UPLOADS_ROUTE : "")
165
+ serviceUploadBaseURL: existingPublic.serviceUploadBaseURL || options.serviceUploads?.baseURL || (options.serviceUploads?.proxy ? SERVICE_UPLOADS_ROUTE : ""),
166
+ // Names widgets can pass as `service` (NUXT_PUBLIC_MEDIA_LIBRARY_SERVICE_UPLOAD_BACKENDS).
167
+ serviceUploadBackends: { ...serviceBackends(options.serviceUploads?.backends, logger), ...existingPublic.serviceUploadBackends }
123
168
  };
124
169
  addPlugin({
125
170
  src: resolver.resolve("./runtime/plugins/config")
@@ -39,11 +39,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
39
39
  onFiles?: ((files: File[]) => any) | undefined;
40
40
  onRejected?: ((files: RejectedFile[]) => any) | undefined;
41
41
  }>, {
42
+ title: string;
42
43
  variant: "area" | "overlay";
43
44
  compact: boolean;
44
45
  disabled: boolean;
45
46
  multiple: boolean;
46
- title: string;
47
47
  accept: string;
48
48
  maxSize: number | null;
49
49
  maxFiles: number | null;
@@ -39,11 +39,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
39
39
  onFiles?: ((files: File[]) => any) | undefined;
40
40
  onRejected?: ((files: RejectedFile[]) => any) | undefined;
41
41
  }>, {
42
+ title: string;
42
43
  variant: "area" | "overlay";
43
44
  compact: boolean;
44
45
  disabled: boolean;
45
46
  multiple: boolean;
46
- title: string;
47
47
  accept: string;
48
48
  maxSize: number | null;
49
49
  maxFiles: number | null;
@@ -15,6 +15,16 @@ type __VLS_Props = {
15
15
  visibility?: MediaUploadOptions['visibility'];
16
16
  processingConfig?: MediaUploadOptions['processingConfig'];
17
17
  collectionName?: string | null;
18
+ /** Upload profile name for new files. */
19
+ profile?: string | null;
20
+ /**
21
+ * Start uploads at a backend service instead of Nuxt's own route: a name
22
+ * registered under `mediaLibrary.serviceUploads.backends` (e.g. "videos"),
23
+ * or `{ start }`. See useMLMediaUpload.
24
+ */
25
+ service?: MediaUploadOptions['service'];
26
+ /** Extra fields for the service's start request, e.g. `{ context: { lessonId } }`. */
27
+ serviceBody?: Record<string, unknown> | null;
18
28
  /** Show "Select existing", which opens the library picker. */
19
29
  picker?: boolean;
20
30
  /** Replaces the built-in picker, e.g. with the host's own dialog. */
@@ -33,17 +43,17 @@ type __VLS_ModelProps = {
33
43
  };
34
44
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
35
45
  declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
36
- uploaded: (assetId: string) => any;
46
+ uploaded: (assetId: string, sessionId: string | null) => any;
37
47
  "update:modelValue": (value: string | null) => any;
38
48
  "update:alt": (value: string | undefined) => any;
39
49
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
40
- onUploaded?: ((assetId: string) => any) | undefined;
50
+ onUploaded?: ((assetId: string, sessionId: string | null) => any) | undefined;
41
51
  "onUpdate:modelValue"?: ((value: string | null) => any) | undefined;
42
52
  "onUpdate:alt"?: ((value: string | undefined) => any) | undefined;
43
53
  }>, {
44
- disabled: boolean;
45
- visibility: "private" | "public" | "restricted";
54
+ visibility: "private" | "restricted" | "public";
46
55
  label: string;
56
+ disabled: boolean;
47
57
  accept: readonly string[] | string;
48
58
  maxSize: number | null;
49
59
  required: boolean;
@@ -54,6 +64,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
54
64
  enableTranscription?: boolean;
55
65
  };
56
66
  collectionName: string | null;
67
+ profile: string | null;
68
+ service: string | import("../../index.js").MediaServiceUpload | null;
69
+ serviceBody: Record<string, unknown> | null;
57
70
  hint: string;
58
71
  missingAlt: boolean;
59
72
  picker: boolean;
@@ -15,6 +15,9 @@ const props = defineProps({
15
15
  visibility: { type: String, required: false, default: void 0 },
16
16
  processingConfig: { type: Object, required: false, default: void 0 },
17
17
  collectionName: { type: [String, null], required: false, default: null },
18
+ profile: { type: [String, null], required: false, default: null },
19
+ service: { type: null, required: false, default: void 0 },
20
+ serviceBody: { type: [Object, null], required: false, default: null },
18
21
  picker: { type: Boolean, required: false, default: true },
19
22
  onPick: { type: Function, required: false, default: void 0 },
20
23
  editor: { type: Boolean, required: false, default: true },
@@ -30,7 +33,10 @@ const field = useMLMediaField(model, {
30
33
  visibility: props.visibility,
31
34
  processingConfig: props.processingConfig,
32
35
  collectionName: props.collectionName,
33
- onUploaded: (id) => emit("uploaded", id)
36
+ profile: props.profile,
37
+ service: props.service,
38
+ serviceBody: props.serviceBody,
39
+ onUploaded: (id, sessionId) => emit("uploaded", id, sessionId)
34
40
  });
35
41
  const { state, asset, upload, message } = field;
36
42
  const helpText = computed(() => {
@@ -65,19 +71,30 @@ const veilPhase = computed(() => {
65
71
  });
66
72
  const filePicker = useMLFilePicker();
67
73
  const canPick = computed(() => !!props.onPick || props.picker);
74
+ const uploadOptions = computed(() => ({
75
+ visibility: props.visibility,
76
+ processingConfig: props.processingConfig,
77
+ collectionName: props.collectionName,
78
+ profile: props.profile,
79
+ service: props.service,
80
+ serviceBody: props.serviceBody
81
+ }));
68
82
  async function pickExisting() {
69
83
  if (props.onPick) return props.onPick();
84
+ const sessions = /* @__PURE__ */ new Map();
70
85
  const ids = await filePicker.open({
71
86
  accept: props.accept,
72
87
  max: 1,
73
88
  selected: [model.value],
74
89
  title: props.label ? `Select ${props.label.toLowerCase()}` : "Select file",
75
90
  maxSize: props.maxSize,
76
- upload: { visibility: props.visibility, processingConfig: props.processingConfig, collectionName: props.collectionName }
91
+ upload: uploadOptions.value,
92
+ onUploaded: (id, sessionId) => sessions.set(id, sessionId)
77
93
  });
78
94
  if (ids) {
79
95
  field.clearMessage();
80
96
  model.value = ids[0] ?? null;
97
+ if (model.value && sessions.has(model.value)) emit("uploaded", model.value, sessions.get(model.value) ?? null);
81
98
  }
82
99
  }
83
100
  const mediaEditor = useMLMediaEditor();
@@ -15,6 +15,16 @@ type __VLS_Props = {
15
15
  visibility?: MediaUploadOptions['visibility'];
16
16
  processingConfig?: MediaUploadOptions['processingConfig'];
17
17
  collectionName?: string | null;
18
+ /** Upload profile name for new files. */
19
+ profile?: string | null;
20
+ /**
21
+ * Start uploads at a backend service instead of Nuxt's own route: a name
22
+ * registered under `mediaLibrary.serviceUploads.backends` (e.g. "videos"),
23
+ * or `{ start }`. See useMLMediaUpload.
24
+ */
25
+ service?: MediaUploadOptions['service'];
26
+ /** Extra fields for the service's start request, e.g. `{ context: { lessonId } }`. */
27
+ serviceBody?: Record<string, unknown> | null;
18
28
  /** Show "Select existing", which opens the library picker. */
19
29
  picker?: boolean;
20
30
  /** Replaces the built-in picker, e.g. with the host's own dialog. */
@@ -33,17 +43,17 @@ type __VLS_ModelProps = {
33
43
  };
34
44
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
35
45
  declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
36
- uploaded: (assetId: string) => any;
46
+ uploaded: (assetId: string, sessionId: string | null) => any;
37
47
  "update:modelValue": (value: string | null) => any;
38
48
  "update:alt": (value: string | undefined) => any;
39
49
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
40
- onUploaded?: ((assetId: string) => any) | undefined;
50
+ onUploaded?: ((assetId: string, sessionId: string | null) => any) | undefined;
41
51
  "onUpdate:modelValue"?: ((value: string | null) => any) | undefined;
42
52
  "onUpdate:alt"?: ((value: string | undefined) => any) | undefined;
43
53
  }>, {
44
- disabled: boolean;
45
- visibility: "private" | "public" | "restricted";
54
+ visibility: "private" | "restricted" | "public";
46
55
  label: string;
56
+ disabled: boolean;
47
57
  accept: readonly string[] | string;
48
58
  maxSize: number | null;
49
59
  required: boolean;
@@ -54,6 +64,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
54
64
  enableTranscription?: boolean;
55
65
  };
56
66
  collectionName: string | null;
67
+ profile: string | null;
68
+ service: string | import("../../index.js").MediaServiceUpload | null;
69
+ serviceBody: Record<string, unknown> | null;
57
70
  hint: string;
58
71
  missingAlt: boolean;
59
72
  picker: boolean;
@@ -6,6 +6,7 @@ type __VLS_Props = {
6
6
  title?: string;
7
7
  maxSize?: number | null;
8
8
  upload?: MediaUploadOptions;
9
+ onUploaded?: (assetId: string, sessionId: string | null) => void;
9
10
  };
10
11
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
12
  close: (ids: string[] | null) => any;
@@ -18,6 +19,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
18
19
  selected: readonly string[];
19
20
  maxSize: number | null;
20
21
  upload: MediaUploadOptions;
22
+ onUploaded: (assetId: string, sessionId: string | null) => void;
21
23
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
22
24
  declare const _default: typeof __VLS_export;
23
25
  export default _default;
@@ -5,7 +5,8 @@ defineProps({
5
5
  selected: { type: Array, required: false, default: () => [] },
6
6
  title: { type: String, required: false, default: "Select file" },
7
7
  maxSize: { type: [Number, null], required: false, default: null },
8
- upload: { type: Object, required: false, default: () => ({}) }
8
+ upload: { type: Object, required: false, default: () => ({}) },
9
+ onUploaded: { type: Function, required: false, default: void 0 }
9
10
  });
10
11
  const emit = defineEmits(["close"]);
11
12
  </script>
@@ -25,6 +26,7 @@ const emit = defineEmits(["close"]);
25
26
  :selected="selected"
26
27
  :max-size="maxSize"
27
28
  :upload="upload"
29
+ :on-uploaded="onUploaded"
28
30
  @done="(ids) => emit('close', ids)"
29
31
  @cancel="emit('close', null)"
30
32
  />
@@ -6,6 +6,7 @@ type __VLS_Props = {
6
6
  title?: string;
7
7
  maxSize?: number | null;
8
8
  upload?: MediaUploadOptions;
9
+ onUploaded?: (assetId: string, sessionId: string | null) => void;
9
10
  };
10
11
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
12
  close: (ids: string[] | null) => any;
@@ -18,6 +19,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
18
19
  selected: readonly string[];
19
20
  maxSize: number | null;
20
21
  upload: MediaUploadOptions;
22
+ onUploaded: (assetId: string, sessionId: string | null) => void;
21
23
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
22
24
  declare const _default: typeof __VLS_export;
23
25
  export default _default;
@@ -11,6 +11,8 @@ type __VLS_Props = {
11
11
  maxSize?: number | null;
12
12
  /** Upload session settings for files dropped into the picker. */
13
13
  upload?: MediaUploadOptions;
14
+ /** A file uploaded from the picker exists in the library (with its upload session). */
15
+ onUploaded?: (assetId: string, sessionId: string | null) => void;
14
16
  /** Grid rows for a fixed-height body; the list scrolls inside it. */
15
17
  bodyClass?: string;
16
18
  };
@@ -27,6 +29,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
27
29
  selected: readonly string[];
28
30
  maxSize: number | null;
29
31
  upload: MediaUploadOptions;
32
+ onUploaded: (assetId: string, sessionId: string | null) => void;
30
33
  bodyClass: string;
31
34
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
32
35
  declare const _default: typeof __VLS_export;
@@ -15,6 +15,7 @@ const props = defineProps({
15
15
  title: { type: String, required: false, default: void 0 },
16
16
  maxSize: { type: [Number, null], required: false, default: null },
17
17
  upload: { type: Object, required: false, default: () => ({}) },
18
+ onUploaded: { type: Function, required: false, default: void 0 },
18
19
  bodyClass: { type: String, required: false, default: "max-h-[min(60vh,560px)]" }
19
20
  });
20
21
  const emit = defineEmits(["done", "cancel"]);
@@ -69,7 +70,11 @@ function onFiles(files) {
69
70
  rejectNote.value = null;
70
71
  for (const file of files) {
71
72
  const u = uploader.start(file);
72
- void u.created.then((id) => id && !isChosen(id) && choose(id));
73
+ void u.created.then((id) => {
74
+ if (!id) return;
75
+ props.onUploaded?.(id, u.sessionId);
76
+ if (!isChosen(id)) choose(id);
77
+ });
73
78
  }
74
79
  }
75
80
  function onRejected(rejected) {
@@ -11,6 +11,8 @@ type __VLS_Props = {
11
11
  maxSize?: number | null;
12
12
  /** Upload session settings for files dropped into the picker. */
13
13
  upload?: MediaUploadOptions;
14
+ /** A file uploaded from the picker exists in the library (with its upload session). */
15
+ onUploaded?: (assetId: string, sessionId: string | null) => void;
14
16
  /** Grid rows for a fixed-height body; the list scrolls inside it. */
15
17
  bodyClass?: string;
16
18
  };
@@ -27,6 +29,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
27
29
  selected: readonly string[];
28
30
  maxSize: number | null;
29
31
  upload: MediaUploadOptions;
32
+ onUploaded: (assetId: string, sessionId: string | null) => void;
30
33
  bodyClass: string;
31
34
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
32
35
  declare const _default: typeof __VLS_export;
@@ -30,6 +30,14 @@ type __VLS_Props = {
30
30
  collectionName?: string | null;
31
31
  /** Upload profile for the single list (with `profiles`, each role has its own). */
32
32
  profile?: string | null;
33
+ /**
34
+ * Start uploads at a backend service instead of Nuxt's own route: a name
35
+ * registered under `mediaLibrary.serviceUploads.backends` (e.g. "videos"),
36
+ * or `{ start }`. Each role's profile is sent with the start request.
37
+ */
38
+ service?: MediaUploadOptions['service'];
39
+ /** Extra fields for the service's start request, e.g. `{ context: { lessonId } }`. */
40
+ serviceBody?: Record<string, unknown> | null;
33
41
  picker?: boolean;
34
42
  onPick?: () => void;
35
43
  editor?: boolean;
@@ -41,20 +49,19 @@ type __VLS_ModelProps = {
41
49
  };
42
50
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
43
51
  declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
44
- uploaded: (assetId: string, role: string) => any;
52
+ uploaded: (assetId: string, role: string, sessionId: string | null) => any;
45
53
  "update:modelValue": (value: string[]) => any;
46
54
  "update:alt": (value: Record<string, string> | undefined) => any;
47
55
  "update:profiles": (profiles: MediaGalleryProfiles) => any;
48
56
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
49
- onUploaded?: ((assetId: string, role: string) => any) | undefined;
57
+ onUploaded?: ((assetId: string, role: string, sessionId: string | null) => any) | undefined;
50
58
  "onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
51
59
  "onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
52
60
  "onUpdate:profiles"?: ((profiles: MediaGalleryProfiles) => any) | undefined;
53
61
  }>, {
54
- disabled: boolean;
62
+ visibility: "private" | "restricted" | "public";
55
63
  title: string;
56
- visibility: "private" | "public" | "restricted";
57
- profile: string | null;
64
+ disabled: boolean;
58
65
  accept: readonly string[] | string;
59
66
  max: number | null;
60
67
  maxSize: number | null;
@@ -64,6 +71,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
64
71
  enableTranscription?: boolean;
65
72
  };
66
73
  collectionName: string | null;
74
+ profile: string | null;
75
+ service: string | import("../../index.js").MediaServiceUpload | null;
76
+ serviceBody: Record<string, unknown> | null;
67
77
  hint: string;
68
78
  picker: boolean;
69
79
  onPick: () => void;