Setting the file. One moment.
Media Import · Wix Harmony Editor · wix/skills · Skills Docs
ContentsBack to the top of the page scripts/copy/media-import.mjs
scripts/copy/ media-import.mjs
JavaScript · 76 lines · 5 KB
11 const out = new Array (items. length );
12 let next = 0 ;
13 await Promise . all (Array. from ({ length: Math. min (limit, items. length ) }, async () => {
14 while (next < items. length ) { const i = next ++ ; out[i] = await fn (items[i]); }
15 }));
16 return out;
17 }
18
19 // fetchImage(url) -> { ok, bytes, contentType } | { ok: false, unsafe, reason }; wixFetch is fetch.
20 export async function importPageImages ({ desc , ledger , saveLedger , fetchImage , wixFetch , headers , sleep , concurrency = IMPORT_CONCURRENCY }) {
21 async function runImport ( url ) {
22 const fetched = await fetchImage (url);
23 if ( ! fetched.ok) return { error: `${ fetched . unsafe ? 'unsafe-source-url' : 'source-fetch-failed'}: ${ fetched . reason }` };
24 if ( ! / ^ image \/ / . test (fetched.contentType || '' )) return { error: `source-fetch-failed: not-an-image (${ fetched . contentType || 'no content-type'})` };
25 // Generate File Upload URL rejects a MIME type that does not match the file name's extension
26 // (MISMATCH_MIME_TYPE). The URL's own extension can lie (a .jpg served as WebP) or be missing, so
27 // the name's extension is derived from the type the server actually sent.
28 const extensions = { 'image/jpeg' : '.jpg' , 'image/png' : '.png' , 'image/gif' : '.gif' , 'image/webp' : '.webp' , 'image/avif' : '.avif' , 'image/svg+xml' : '.svg' };
29 const extension = extensions[fetched.contentType];
30 if ( ! extension) return { error: `source-fetch-failed: unsupported-image-type (${ fetched . contentType })` };
31 const stem = url. split ( '/' ). pop (). split ( '?' )[ 0 ]. replace ( / \. [A-Za-z0-9] +$ / , '' ). replace ( / [ ^ \w.~-] + / g , '-' ). slice ( 0 , 80 ) || 'image' ;
32 const name = `${ stem }${ extension }` ;
33 const mimeType = fetched.contentType;
34 // Upload the bytes we fetched; Wix is never given the source URL.
35 const gen = await wixFetch ( 'https://www.wixapis.com/site-media/v1/files/generate-upload-url' , { method: 'POST' , headers, body: JSON . stringify ({ mimeType: fetched.contentType, fileName: name, sizeInBytes: String (fetched.bytes. length ), externalInfo: { origin: 'site-copy' } }) });
36 const g0 = await gen. json (). catch (() => ({}));
37 let file;
38 if (gen.ok && g0.uploadUrl) {
39 const target = new URL (g0.uploadUrl); target.searchParams. set ( 'filename' , name);
40 const put = await wixFetch (target, { method: 'PUT' , headers: { 'Content-Type' : fetched.contentType }, body: fetched.bytes });
41 const pj = await put. json (). catch (() => ({}));
42 if ( ! put.ok || ! pj.file) return { error: `upload-failed: ${ put . status }` , body: JSON . stringify (pj). slice ( 0 , 200 ) };
43 file = pj.file;
44 } else {
45 return { error: `upload-url-refused: ${ gen . status }` , body: JSON . stringify (g0). slice ( 0 , 200 ) };
46 }
47 const e = { id: file.id, uri: file.id, name };
48 let ready = false ;
49 for ( let i = 0 ; i < 20 ; i ++ ) {
50 const g = await wixFetch ( `https://www.wixapis.com/site-media/v1/files/${ encodeURIComponent ( e . id ) }` , { headers }). then (( x ) => x. json ()). catch (() => ({}));
51 if (g.file?.operationStatus === 'READY' ) { ready = true ; e.width = g.file.media?.image?.image?.width; e.height = g.file.media?.image?.image?.height; break ; }
52 await sleep ( 1500 );
53 }
54 if ( ! ready) return { error: 'import not READY after 30 s' , id: e.id };
55 ledger[url] = e; saveLedger (ledger);
56 return e;
57 }
58
59 const inFlight = new Map ();
60 function importOne ( url ) {
61 if (ledger[url]?.uri) return Promise . resolve (ledger[url]);
62 if ( ! inFlight. has (url)) inFlight. set (url, runImport (url));
63 return inFlight. get (url);
64 }
65
66 const jobs = desc.components. filter (( c ) => c.kind === 'image' && ! c.image.uri && c.image.source);
67 return mapBounded (jobs, concurrency, async ( c ) => {
68 let e = await importOne (c.image.source);
69 const want = c.desktop.width / c.desktop.height;
70 if ( ! e.uri || ! e.width || Math. abs (e.width / e.height - want) / want > 0.01 ) { // cropped thumbnail on the source: use what it rendered
71 if (c.image.sourceRendered && c.image.sourceRendered !== c.image.source) e = await importOne (c.image.sourceRendered);
72 }
73 if (e.uri) Object. assign (c.image, { uri: e.uri, width: e.width, height: e.height, name: e.name });
74 return { key: c.key, uri: e.uri || null , size: e.width ? `${ e . width }x${ e . height }` : null , error: e.error || null };
75 });
76 }