PluginProbe
Extendify / 3.1.6
Extendify v3.1.6
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / AutoLaunch / functions / get-imported-images.js

get-imported-images.js in Extendify 3.1.6, at src/AutoLaunch/functions/get-imported-images.js

93 lines 2.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { uploadMediaFromUrl } from '@auto-launch/functions/upload-from-url';
2
3 const CONCURRENCY = 3;
4
5 // Not the markup: it also carries avatar placeholders the build never chose.
6 const placedImages = (pattern) => pattern.images ?? [];
7
8 // blogImages can name urls the markup never places, and buildBlogPosts prefers them.
9 const importableImages = (pattern) => [
10 ...placedImages(pattern),
11 ...(pattern.blogImages ?? []),
12 ];
13
14 const sideload = async (urls) => {
15 const map = new Map();
16 for (let i = 0; i < urls.length; i += CONCURRENCY) {
17 const chunk = urls.slice(i, i + CONCURRENCY);
18 const uploaded = await Promise.all(
19 chunk.map(async (url) => {
20 const fileObj = await uploadMediaFromUrl(url, {
21 prefix: 'ext-imported',
22 caller: 'importBuiltPagesImages',
23 });
24 return [url, fileObj?.url ?? null];
25 }),
26 );
27 for (const [url, newUrl] of uploaded) {
28 if (newUrl) map.set(url, newUrl);
29 }
30 }
31 return map;
32 };
33
34 // The build's URLs are signed and expire minutes after launch.
35 export const importBuiltPagesImages = async (builtPages) => {
36 if (!builtPages?.length) return builtPages;
37
38 const urls = [
39 ...new Set(
40 builtPages.flatMap((page) =>
41 (page.patterns ?? []).flatMap(importableImages),
42 ),
43 ),
44 ];
45 if (!urls.length) return builtPages;
46
47 const map = await sideload(urls);
48 if (!map.size) return builtPages;
49
50 // A shorter URL that prefixes another would eat it mid-replace.
51 const swaps = [...map].sort(([a], [b]) => b.length - a.length);
52 const swap = (code) => {
53 if (!code) return code;
54 let out = code;
55 for (const [oldUrl, newUrl] of swaps) {
56 out = out.replaceAll(oldUrl, newUrl);
57 }
58 return out;
59 };
60
61 return builtPages.map((page) => ({
62 ...page,
63 patterns: (page.patterns ?? []).map((pattern) => ({
64 ...pattern,
65 code: swap(pattern.code),
66 // Plugin sections ship a second copy that process-placeholders promotes over code.
67 patternReplacementCode: swap(pattern.patternReplacementCode),
68 images: pattern.images?.map((url) => map.get(url) ?? url),
69 blogImages: pattern.blogImages?.map((url) => map.get(url) ?? url),
70 })),
71 }));
72 };
73
74 // A hero has to stay unique, and a named person's face must not decorate another page.
75 const POOL_EXCLUDED_TYPES = new Set(['hero-header', 'team']);
76
77 // Durable image URLs for the siteImages pool: blog-section first, so blog posts
78 // reuse them card-for-card.
79 export const collectBuiltPageImageUrls = (builtPages) => {
80 if (!builtPages?.length) return [];
81 const reusable = builtPages
82 .flatMap((page) => page.patterns ?? [])
83 .filter(
84 (pattern) =>
85 !pattern.patternTypes?.some((type) => POOL_EXCLUDED_TYPES.has(type)),
86 );
87 const ordered = [
88 ...reusable.filter((p) => p.patternTypes?.includes('blog-section')),
89 ...reusable.filter((p) => !p.patternTypes?.includes('blog-section')),
90 ];
91 return [...new Set(ordered.flatMap(placedImages))];
92 };
93