@frybynite/image-cloud 0.6.3 → 0.6.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.
@@ -1,38 +1,5 @@
1
- const h = class h {
2
- /**
3
- * Register a loader implementation with the registry
4
- * @param name - Loader identifier (e.g., 'static', 'google-drive', 'composite')
5
- * @param Loader - Loader class constructor to register
6
- */
7
- static registerLoader(e, s) {
8
- h.registry.set(e, s);
9
- }
10
- /**
11
- * Get a registered loader implementation
12
- * @param name - Loader identifier
13
- * @returns Loader class constructor
14
- * @throws Error if loader is not registered
15
- */
16
- static getLoader(e) {
17
- const s = h.registry.get(e);
18
- if (!s)
19
- throw new Error(
20
- `Loader "${e}" is not registered. Import "@frybynite/image-cloud/loaders/${e}" or "@frybynite/image-cloud/loaders/all".`
21
- );
22
- return s;
23
- }
24
- /**
25
- * Check if a loader is registered
26
- * @param name - Loader identifier
27
- * @returns True if the loader is registered, false otherwise
28
- */
29
- static isRegistered(e) {
30
- return h.registry.has(e);
31
- }
32
- };
33
- h.registry = /* @__PURE__ */ new Map();
34
- let g = h;
35
- class f {
1
+ import { LoaderRegistry as g } from "@frybynite/image-cloud";
2
+ class m {
36
3
  constructor(e) {
37
4
  if (this._prepared = !1, this._discoveredUrls = [], this.apiKey = e.apiKey ?? "", this.apiEndpoint = e.apiEndpoint ?? "https://www.googleapis.com/drive/v3/files", this.debugLogging = e.debugLogging ?? !1, this.sources = e.sources ?? [], !this.sources || this.sources.length === 0)
38
5
  throw new Error("GoogleDriveLoader requires at least one source to be configured");
@@ -45,13 +12,13 @@ class f {
45
12
  this._discoveredUrls = [];
46
13
  for (const s of this.sources)
47
14
  if ("folders" in s)
48
- for (const r of s.folders) {
49
- const t = s.recursive !== void 0 ? s.recursive : !0, o = await this.loadFromFolder(r, e, t);
50
- this._discoveredUrls.push(...o);
15
+ for (const o of s.folders) {
16
+ const t = s.recursive !== void 0 ? s.recursive : !0, r = await this.loadFromFolder(o, e, t);
17
+ this._discoveredUrls.push(...r);
51
18
  }
52
19
  else if ("files" in s) {
53
- const r = await this.loadFiles(s.files, e);
54
- this._discoveredUrls.push(...r);
20
+ const o = await this.loadFiles(s.files, e);
21
+ this._discoveredUrls.push(...o);
55
22
  }
56
23
  this._prepared = !0;
57
24
  }
@@ -91,8 +58,8 @@ class f {
91
58
  /id=([a-zA-Z0-9_-]+)/
92
59
  // Alternative format
93
60
  ];
94
- for (const r of s) {
95
- const t = e.match(r);
61
+ for (const o of s) {
62
+ const t = e.match(o);
96
63
  if (t && t[1])
97
64
  return t[1];
98
65
  }
@@ -105,16 +72,16 @@ class f {
105
72
  * @param recursive - Whether to include images from subfolders
106
73
  * @returns Promise resolving to array of image URLs
107
74
  */
108
- async loadFromFolder(e, s, r = !0) {
75
+ async loadFromFolder(e, s, o = !0) {
109
76
  const t = this.extractFolderId(e);
110
77
  if (!t)
111
78
  throw new Error("Invalid Google Drive folder URL. Please check the URL format.");
112
79
  if (!this.apiKey || this.apiKey === "YOUR_API_KEY_HERE")
113
80
  return this.loadImagesDirectly(t, s);
114
81
  try {
115
- return r ? await this.loadImagesRecursively(t, s) : await this.loadImagesFromSingleFolder(t, s);
116
- } catch (o) {
117
- return console.error("Error loading from Google Drive API:", o), this.loadImagesDirectly(t, s);
82
+ return o ? await this.loadImagesRecursively(t, s) : await this.loadImagesFromSingleFolder(t, s);
83
+ } catch (r) {
84
+ return console.error("Error loading from Google Drive API:", r), this.loadImagesDirectly(t, s);
118
85
  }
119
86
  }
120
87
  /**
@@ -124,15 +91,15 @@ class f {
124
91
  * @returns Promise resolving to array of image URLs
125
92
  */
126
93
  async loadImagesFromSingleFolder(e, s) {
127
- const r = [], t = `'${e}' in parents and trashed=false`, d = `${this.apiEndpoint}?q=${encodeURIComponent(t)}&fields=files(id,name,mimeType,thumbnailLink)&key=${this.apiKey}`, i = await fetch(d);
94
+ const o = [], t = `'${e}' in parents and trashed=false`, d = `${this.apiEndpoint}?q=${encodeURIComponent(t)}&fields=files(id,name,mimeType,thumbnailLink)&key=${this.apiKey}`, i = await fetch(d);
128
95
  if (!i.ok)
129
96
  throw new Error(`API request failed: ${i.status} ${i.statusText}`);
130
97
  const c = (await i.json()).files.filter(
131
98
  (l) => l.mimeType.startsWith("image/") && s.isAllowed(l.name)
132
99
  );
133
100
  return this.log(`Found ${c.length} images in folder ${e} (non-recursive)`), c.forEach((l) => {
134
- r.push(`https://lh3.googleusercontent.com/d/${l.id}=s1600`), this.log(`Added file: ${l.name}`);
135
- }), r;
101
+ o.push(`https://lh3.googleusercontent.com/d/${l.id}=s1600`), this.log(`Added file: ${l.name}`);
102
+ }), o;
136
103
  }
137
104
  /**
138
105
  * Load specific files by their URLs or IDs
@@ -141,28 +108,28 @@ class f {
141
108
  * @returns Promise resolving to array of image URLs
142
109
  */
143
110
  async loadFiles(e, s) {
144
- const r = [];
111
+ const o = [];
145
112
  for (const t of e) {
146
- const o = this.extractFileId(t);
147
- if (!o) {
113
+ const r = this.extractFileId(t);
114
+ if (!r) {
148
115
  this.log(`Skipping invalid file URL: ${t}`);
149
116
  continue;
150
117
  }
151
118
  if (this.apiKey && this.apiKey !== "YOUR_API_KEY_HERE")
152
119
  try {
153
- const d = `${this.apiEndpoint}/${o}?fields=name,mimeType&key=${this.apiKey}`, i = await fetch(d);
120
+ const d = `${this.apiEndpoint}/${r}?fields=name,mimeType&key=${this.apiKey}`, i = await fetch(d);
154
121
  if (i.ok) {
155
122
  const n = await i.json();
156
- n.mimeType.startsWith("image/") && s.isAllowed(n.name) ? (r.push(`https://lh3.googleusercontent.com/d/${o}=s1600`), this.log(`Added file: ${n.name}`)) : this.log(`Skipping non-image file: ${n.name} (${n.mimeType})`);
123
+ n.mimeType.startsWith("image/") && s.isAllowed(n.name) ? (o.push(`https://lh3.googleusercontent.com/d/${r}=s1600`), this.log(`Added file: ${n.name}`)) : this.log(`Skipping non-image file: ${n.name} (${n.mimeType})`);
157
124
  } else
158
- this.log(`Failed to fetch metadata for file ${o}: ${i.status}`);
125
+ this.log(`Failed to fetch metadata for file ${r}: ${i.status}`);
159
126
  } catch (d) {
160
- this.log(`Error fetching metadata for file ${o}:`, d);
127
+ this.log(`Error fetching metadata for file ${r}:`, d);
161
128
  }
162
129
  else
163
- r.push(`https://lh3.googleusercontent.com/d/${o}=s1600`);
130
+ o.push(`https://lh3.googleusercontent.com/d/${r}=s1600`);
164
131
  }
165
- return r;
132
+ return o;
166
133
  }
167
134
  /**
168
135
  * Extract file ID from Google Drive file URL
@@ -180,8 +147,8 @@ class f {
180
147
  /id=([a-zA-Z0-9_-]+)/
181
148
  // Generic id parameter
182
149
  ];
183
- for (const r of s) {
184
- const t = e.match(r);
150
+ for (const o of s) {
151
+ const t = e.match(o);
185
152
  if (t && t[1])
186
153
  return t[1];
187
154
  }
@@ -194,7 +161,7 @@ class f {
194
161
  * @returns Promise resolving to array of image URLs
195
162
  */
196
163
  async loadImagesRecursively(e, s) {
197
- const r = [], t = `'${e}' in parents and trashed=false`, d = `${this.apiEndpoint}?q=${encodeURIComponent(t)}&fields=files(id,name,mimeType,thumbnailLink)&key=${this.apiKey}`, i = await fetch(d);
164
+ const o = [], t = `'${e}' in parents and trashed=false`, d = `${this.apiEndpoint}?q=${encodeURIComponent(t)}&fields=files(id,name,mimeType,thumbnailLink)&key=${this.apiKey}`, i = await fetch(d);
198
165
  if (!i.ok)
199
166
  throw new Error(`API request failed: ${i.status} ${i.statusText}`);
200
167
  const n = await i.json(), c = n.files.filter(
@@ -203,14 +170,14 @@ class f {
203
170
  (a) => a.mimeType === "application/vnd.google-apps.folder"
204
171
  );
205
172
  this.log(`Found ${n.files.length} total items in folder ${e}`), n.files.forEach((a) => this.log(` - File: ${a.name} (${a.mimeType})`)), this.log(`- ${c.length} valid files (images only)`), this.log(`- ${l.length} subfolders`), c.forEach((a) => {
206
- r.push(`https://lh3.googleusercontent.com/d/${a.id}=s1600`), this.log(`Added file: ${a.name}`);
173
+ o.push(`https://lh3.googleusercontent.com/d/${a.id}=s1600`), this.log(`Added file: ${a.name}`);
207
174
  });
208
175
  for (const a of l) {
209
176
  this.log(`Loading images from subfolder: ${a.name}`);
210
- const m = await this.loadImagesRecursively(a.id, s);
211
- r.push(...m);
177
+ const h = await this.loadImagesRecursively(a.id, s);
178
+ o.push(...h);
212
179
  }
213
- return r;
180
+ return o;
214
181
  }
215
182
  /**
216
183
  * Direct loading method (no API key required, but less reliable)
@@ -221,15 +188,15 @@ class f {
221
188
  */
222
189
  async loadImagesDirectly(e, s) {
223
190
  try {
224
- const r = `https://drive.google.com/embeddedfolderview?id=${e}`, t = await fetch(r, { mode: "cors" });
191
+ const o = `https://drive.google.com/embeddedfolderview?id=${e}`, t = await fetch(o, { mode: "cors" });
225
192
  if (!t.ok)
226
193
  throw new Error("Cannot access folder directly (CORS or permissions issue)");
227
- const o = await t.text(), d = /\/file\/d\/([a-zA-Z0-9_-]+)/g, i = [...o.matchAll(d)];
194
+ const r = await t.text(), d = /\/file\/d\/([a-zA-Z0-9_-]+)/g, i = [...r.matchAll(d)];
228
195
  return [...new Set(i.map((l) => l[1]))].map(
229
196
  (l) => `https://drive.google.com/uc?export=view&id=${l}`
230
197
  );
231
- } catch (r) {
232
- throw console.error("Direct loading failed:", r), new Error(
198
+ } catch (o) {
199
+ throw console.error("Direct loading failed:", o), new Error(
233
200
  `Unable to load images. Please ensure:
234
201
  1. The folder is shared publicly (Anyone with the link can view)
235
202
  2. The folder contains image files
@@ -253,8 +220,8 @@ class f {
253
220
  this.debugLogging && typeof console < "u" && console.log(...e);
254
221
  }
255
222
  }
256
- g.registerLoader("google-drive", f);
223
+ g.registerLoader("google-drive", m);
257
224
  export {
258
- f as GoogleDriveLoader
225
+ m as GoogleDriveLoader
259
226
  };
260
227
  //# sourceMappingURL=google-drive.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"google-drive.js","sources":["../../src/engines/LoaderRegistry.ts","../../src/loaders/GoogleDriveLoader.ts","../../src/loaders/index-google-drive.ts"],"sourcesContent":["/**\n * Loader Registry - Manages registration and lookup of image loader implementations\n *\n * This registry enables dynamic loading of loaders through separate bundles\n * while maintaining a central registry of available loaders. It mirrors the\n * LayoutEngine registry pattern for consistency.\n *\n * Public API:\n * - registerLoader(name, LoaderClass)\n * - getLoader(name)\n * - isRegistered(name)\n */\n\nimport type { ImageLoader, StaticLoaderInnerConfig, GoogleDriveLoaderInnerConfig } from '../config/types';\n\n/**\n * Constructor signature for loader classes\n * Supports both simple loaders and composite loaders with their respective config types\n */\nexport type LoaderConstructor =\n | (new (config: StaticLoaderInnerConfig) => ImageLoader)\n | (new (config: GoogleDriveLoaderInnerConfig) => ImageLoader)\n | (new (config: any) => ImageLoader);\n\nexport class LoaderRegistry {\n private static readonly registry = new Map<string, LoaderConstructor>();\n\n /**\n * Register a loader implementation with the registry\n * @param name - Loader identifier (e.g., 'static', 'google-drive', 'composite')\n * @param Loader - Loader class constructor to register\n */\n static registerLoader(name: string, Loader: LoaderConstructor): void {\n LoaderRegistry.registry.set(name, Loader);\n }\n\n /**\n * Get a registered loader implementation\n * @param name - Loader identifier\n * @returns Loader class constructor\n * @throws Error if loader is not registered\n */\n static getLoader(name: string): LoaderConstructor {\n const Loader = LoaderRegistry.registry.get(name);\n\n if (!Loader) {\n throw new Error(\n `Loader \"${name}\" is not registered. ` +\n `Import \"@frybynite/image-cloud/loaders/${name}\" or \"@frybynite/image-cloud/loaders/all\".`\n );\n }\n\n return Loader;\n }\n\n /**\n * Check if a loader is registered\n * @param name - Loader identifier\n * @returns True if the loader is registered, false otherwise\n */\n static isRegistered(name: string): boolean {\n return LoaderRegistry.registry.has(name);\n }\n}\n","/**\n * GoogleDriveLoader.ts\n * Loads images from a public Google Drive folder\n *\n * Public API:\n * - prepare(filter) - Async discovery of images\n * - imagesLength() - Get count of discovered images\n * - imageURLs() - Get ordered list of image URLs\n * - isPrepared() - Check if loader has been prepared\n *\n * Helper methods (for advanced usage):\n * - extractFolderId(folderUrl)\n * - manualImageUrls(imageIds)\n */\n\nimport type { ImageLoader, IImageFilter, GoogleDriveResponse, GoogleDriveLoaderInnerConfig, GoogleDriveSource } from '../config/types';\n\nexport class GoogleDriveLoader implements ImageLoader {\n private apiKey: string;\n private apiEndpoint: string;\n private debugLogging: boolean;\n private sources: GoogleDriveSource[];\n\n // State for new interface\n private _prepared: boolean = false;\n private _discoveredUrls: string[] = [];\n\n constructor(config: GoogleDriveLoaderInnerConfig) {\n this.apiKey = config.apiKey ?? '';\n this.apiEndpoint = config.apiEndpoint ?? 'https://www.googleapis.com/drive/v3/files';\n this.debugLogging = config.debugLogging ?? false;\n this.sources = config.sources ?? [];\n\n // Validate that we have sources configured\n if (!this.sources || this.sources.length === 0) {\n throw new Error('GoogleDriveLoader requires at least one source to be configured');\n }\n }\n\n /**\n * Prepare the loader by discovering all images from configured sources\n * @param filter - Filter to apply to discovered images\n */\n async prepare(filter: IImageFilter): Promise<void> {\n this._discoveredUrls = [];\n\n for (const source of this.sources) {\n if ('folders' in source) {\n for (const folderUrl of source.folders) {\n const recursive = source.recursive !== undefined ? source.recursive : true;\n const urls = await this.loadFromFolder(folderUrl, filter, recursive);\n this._discoveredUrls.push(...urls);\n }\n } else if ('files' in source) {\n const urls = await this.loadFiles(source.files, filter);\n this._discoveredUrls.push(...urls);\n }\n }\n\n this._prepared = true;\n }\n\n /**\n * Get the number of discovered images\n * @throws Error if called before prepare()\n */\n imagesLength(): number {\n if (!this._prepared) {\n throw new Error('GoogleDriveLoader.imagesLength() called before prepare()');\n }\n return this._discoveredUrls.length;\n }\n\n /**\n * Get the ordered list of image URLs\n * @throws Error if called before prepare()\n */\n imageURLs(): string[] {\n if (!this._prepared) {\n throw new Error('GoogleDriveLoader.imageURLs() called before prepare()');\n }\n return [...this._discoveredUrls];\n }\n\n /**\n * Check if the loader has been prepared\n */\n isPrepared(): boolean {\n return this._prepared;\n }\n\n /**\n * Extract folder ID from various Google Drive URL formats\n * @param folderUrl - Google Drive folder URL\n * @returns Folder ID or null if invalid\n */\n extractFolderId(folderUrl: string): string | null {\n // Handle various URL formats:\n // https://drive.google.com/drive/folders/FOLDER_ID\n // https://drive.google.com/drive/folders/FOLDER_ID?usp=sharing\n\n const patterns = [\n /\\/folders\\/([a-zA-Z0-9_-]+)/, // Standard format\n /id=([a-zA-Z0-9_-]+)/ // Alternative format\n ];\n\n for (const pattern of patterns) {\n const match = folderUrl.match(pattern);\n if (match && match[1]) {\n return match[1];\n }\n }\n\n return null;\n }\n\n /**\n * Load images from a Google Drive folder\n * @param folderUrl - Google Drive folder URL\n * @param filter - Filter to apply to discovered images\n * @param recursive - Whether to include images from subfolders\n * @returns Promise resolving to array of image URLs\n */\n private async loadFromFolder(folderUrl: string, filter: IImageFilter, recursive: boolean = true): Promise<string[]> {\n const folderId = this.extractFolderId(folderUrl);\n\n if (!folderId) {\n throw new Error('Invalid Google Drive folder URL. Please check the URL format.');\n }\n\n // If no API key is configured, use direct link method\n if (!this.apiKey || this.apiKey === 'YOUR_API_KEY_HERE') {\n return this.loadImagesDirectly(folderId, filter);\n }\n\n try {\n if (recursive) {\n return await this.loadImagesRecursively(folderId, filter);\n } else {\n return await this.loadImagesFromSingleFolder(folderId, filter);\n }\n } catch (error) {\n console.error('Error loading from Google Drive API:', error);\n // Fallback to direct link method\n return this.loadImagesDirectly(folderId, filter);\n }\n }\n\n /**\n * Load images from a single folder (non-recursive)\n * @param folderId - Google Drive folder ID\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of image URLs\n */\n private async loadImagesFromSingleFolder(folderId: string, filter: IImageFilter): Promise<string[]> {\n const imageUrls: string[] = [];\n\n // Query for all files in this folder\n const query = `'${folderId}' in parents and trashed=false`;\n const fields = 'files(id,name,mimeType,thumbnailLink)';\n const url = `${this.apiEndpoint}?q=${encodeURIComponent(query)}&fields=${fields}&key=${this.apiKey}`;\n\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`API request failed: ${response.status} ${response.statusText}`);\n }\n\n const data: GoogleDriveResponse = await response.json();\n\n // Filter for valid image files only using the provided filter\n const validFiles = data.files.filter(file =>\n file.mimeType.startsWith('image/') && filter.isAllowed(file.name)\n );\n\n this.log(`Found ${validFiles.length} images in folder ${folderId} (non-recursive)`);\n\n // Add image URLs\n validFiles.forEach(file => {\n imageUrls.push(`https://lh3.googleusercontent.com/d/${file.id}=s1600`);\n this.log(`Added file: ${file.name}`);\n });\n\n return imageUrls;\n }\n\n /**\n * Load specific files by their URLs or IDs\n * @param fileUrls - Array of Google Drive file URLs or IDs\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of image URLs\n */\n private async loadFiles(fileUrls: string[], filter: IImageFilter): Promise<string[]> {\n const imageUrls: string[] = [];\n\n for (const fileUrl of fileUrls) {\n const fileId = this.extractFileId(fileUrl);\n\n if (!fileId) {\n this.log(`Skipping invalid file URL: ${fileUrl}`);\n continue;\n }\n\n // Validate it's an image file\n if (this.apiKey && this.apiKey !== 'YOUR_API_KEY_HERE') {\n try {\n // Get file metadata to verify it's an image\n const metadataUrl = `${this.apiEndpoint}/${fileId}?fields=name,mimeType&key=${this.apiKey}`;\n const response = await fetch(metadataUrl);\n\n if (response.ok) {\n const metadata = await response.json();\n if (metadata.mimeType.startsWith('image/') && filter.isAllowed(metadata.name)) {\n imageUrls.push(`https://lh3.googleusercontent.com/d/${fileId}=s1600`);\n this.log(`Added file: ${metadata.name}`);\n } else {\n this.log(`Skipping non-image file: ${metadata.name} (${metadata.mimeType})`);\n }\n } else {\n this.log(`Failed to fetch metadata for file ${fileId}: ${response.status}`);\n }\n } catch (error) {\n this.log(`Error fetching metadata for file ${fileId}:`, error);\n }\n } else {\n // Without API key, assume it's valid and add it\n imageUrls.push(`https://lh3.googleusercontent.com/d/${fileId}=s1600`);\n }\n }\n\n return imageUrls;\n }\n\n /**\n * Extract file ID from Google Drive file URL\n * @param fileUrl - Google Drive file URL or file ID\n * @returns File ID or null if invalid\n */\n private extractFileId(fileUrl: string): string | null {\n // Handle various URL formats:\n // https://drive.google.com/file/d/FILE_ID/view\n // https://drive.google.com/open?id=FILE_ID\n // FILE_ID (raw ID)\n\n // If it looks like a raw ID (no slashes or protocol), return it\n if (!/[/:.]/.test(fileUrl)) {\n return fileUrl;\n }\n\n const patterns = [\n /\\/file\\/d\\/([a-zA-Z0-9_-]+)/, // Standard file format\n /\\/open\\?id=([a-zA-Z0-9_-]+)/, // Alternative format\n /id=([a-zA-Z0-9_-]+)/ // Generic id parameter\n ];\n\n for (const pattern of patterns) {\n const match = fileUrl.match(pattern);\n if (match && match[1]) {\n return match[1];\n }\n }\n\n return null;\n }\n\n /**\n * Recursively load images from a folder and all its subfolders\n * @param folderId - Google Drive folder ID\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of image URLs\n */\n private async loadImagesRecursively(folderId: string, filter: IImageFilter): Promise<string[]> {\n const imageUrls: string[] = [];\n\n // Query for all files in this folder\n const query = `'${folderId}' in parents and trashed=false`;\n // Request thumbnailLink for PDFs\n const fields = 'files(id,name,mimeType,thumbnailLink)';\n const url = `${this.apiEndpoint}?q=${encodeURIComponent(query)}&fields=${fields}&key=${this.apiKey}`;\n\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`API request failed: ${response.status} ${response.statusText}`);\n }\n\n const data: GoogleDriveResponse = await response.json();\n\n // Separate images and folders using the provided filter\n const validFiles = data.files.filter(file =>\n file.mimeType.startsWith('image/') && filter.isAllowed(file.name)\n );\n\n const subfolders = data.files.filter(file =>\n file.mimeType === 'application/vnd.google-apps.folder'\n );\n\n this.log(`Found ${data.files.length} total items in folder ${folderId}`);\n // Log details of all files to see what we are missing\n data.files.forEach(f => this.log(` - File: ${f.name} (${f.mimeType})`));\n\n this.log(`- ${validFiles.length} valid files (images only)`);\n this.log(`- ${subfolders.length} subfolders`);\n\n // Add image URLs from this folder\n validFiles.forEach(file => {\n // Use the reliable thumbnail/preview endpoint for both Images and PDFs\n // This works for public folders and handles file format conversion automatically\n // 'sz=w1000' requests a high-quality preview (1000px width)\n // detailed explanation:\n // 1. \"drive.google.com\" is blocked by ad-blockers (net::ERR_BLOCKED_BY_CLIENT)\n // 2. The API's \"thumbnailLink\" is a signed URL that can expire or fail 403.\n // 3. \"lh3.googleusercontent.com/d/{ID}\" is the permanent CDN link structure.\n // It bypasses the domain block AND the signing issues.\n imageUrls.push(`https://lh3.googleusercontent.com/d/${file.id}=s1600`);\n\n this.log(`Added file: ${file.name}`);\n });\n\n // Recursively process subfolders\n for (const folder of subfolders) {\n this.log(`Loading images from subfolder: ${folder.name}`);\n const subfolderImages = await this.loadImagesRecursively(folder.id, filter);\n imageUrls.push(...subfolderImages);\n }\n\n return imageUrls;\n }\n\n /**\n * Direct loading method (no API key required, but less reliable)\n * Uses embedded folder view to scrape image IDs\n * @param folderId - Google Drive folder ID\n * @param filter - Filter to apply (not used in fallback mode)\n * @returns Promise resolving to array of image URLs\n */\n private async loadImagesDirectly(folderId: string, _filter: IImageFilter): Promise<string[]> {\n // For now, we'll return a method that requires the user to manually provide image IDs\n // or we construct URLs based on a known pattern\n\n // This is a fallback - in production, you'd want to use the API\n // For demo purposes, we can try to fetch the folder page and extract image IDs\n\n try {\n // Attempt to fetch folder page (CORS may block this)\n const folderUrl = `https://drive.google.com/embeddedfolderview?id=${folderId}`;\n const response = await fetch(folderUrl, { mode: 'cors' });\n\n if (!response.ok) {\n throw new Error('Cannot access folder directly (CORS or permissions issue)');\n }\n\n const html = await response.text();\n\n // Try to extract image IDs from HTML (this is fragile and may break)\n const imageIdPattern = /\\/file\\/d\\/([a-zA-Z0-9_-]+)/g;\n const matches = [...html.matchAll(imageIdPattern)];\n const imageIds = [...new Set(matches.map(m => m[1]))];\n\n const imageUrls = imageIds.map(id =>\n `https://drive.google.com/uc?export=view&id=${id}`\n );\n\n return imageUrls;\n\n } catch (error) {\n console.error('Direct loading failed:', error);\n throw new Error(\n 'Unable to load images. Please ensure:\\n' +\n '1. The folder is shared publicly (Anyone with the link can view)\\n' +\n '2. The folder contains image files\\n' +\n '3. Consider adding a Google Drive API key in config.js for better reliability'\n );\n }\n }\n\n /**\n * Manually add image URLs (for testing or when auto-loading fails)\n * @param imageIds - Array of Google Drive file IDs\n * @returns Array of direct image URLs\n */\n manualImageUrls(imageIds: string[]): string[] {\n return imageIds.map(id => `https://drive.google.com/uc?export=view&id=${id}`);\n }\n\n /**\n * Debug logging helper\n * @param args - Arguments to log\n */\n private log(...args: unknown[]): void {\n if (this.debugLogging && typeof console !== 'undefined') {\n console.log(...args);\n }\n }\n}\n","import { LoaderRegistry } from '../engines/LoaderRegistry';\nimport { GoogleDriveLoader } from './GoogleDriveLoader';\n\nLoaderRegistry.registerLoader('google-drive', GoogleDriveLoader);\n\nexport { GoogleDriveLoader };\n"],"names":["_LoaderRegistry","name","Loader","LoaderRegistry","GoogleDriveLoader","config","filter","source","folderUrl","recursive","urls","patterns","pattern","match","folderId","error","imageUrls","query","url","response","validFiles","file","fileUrls","fileUrl","fileId","metadataUrl","metadata","data","subfolders","f","folder","subfolderImages","_filter","html","imageIdPattern","matches","m","id","imageIds","args"],"mappings":"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"}
1
+ {"version":3,"file":"google-drive.js","sources":["../../src/loaders/GoogleDriveLoader.ts","../../src/loaders/index-google-drive.ts"],"sourcesContent":["/**\n * GoogleDriveLoader.ts\n * Loads images from a public Google Drive folder\n *\n * Public API:\n * - prepare(filter) - Async discovery of images\n * - imagesLength() - Get count of discovered images\n * - imageURLs() - Get ordered list of image URLs\n * - isPrepared() - Check if loader has been prepared\n *\n * Helper methods (for advanced usage):\n * - extractFolderId(folderUrl)\n * - manualImageUrls(imageIds)\n */\n\nimport type { ImageLoader, IImageFilter, GoogleDriveResponse, GoogleDriveLoaderInnerConfig, GoogleDriveSource } from '../config/types';\n\nexport class GoogleDriveLoader implements ImageLoader {\n private apiKey: string;\n private apiEndpoint: string;\n private debugLogging: boolean;\n private sources: GoogleDriveSource[];\n\n // State for new interface\n private _prepared: boolean = false;\n private _discoveredUrls: string[] = [];\n\n constructor(config: GoogleDriveLoaderInnerConfig) {\n this.apiKey = config.apiKey ?? '';\n this.apiEndpoint = config.apiEndpoint ?? 'https://www.googleapis.com/drive/v3/files';\n this.debugLogging = config.debugLogging ?? false;\n this.sources = config.sources ?? [];\n\n // Validate that we have sources configured\n if (!this.sources || this.sources.length === 0) {\n throw new Error('GoogleDriveLoader requires at least one source to be configured');\n }\n }\n\n /**\n * Prepare the loader by discovering all images from configured sources\n * @param filter - Filter to apply to discovered images\n */\n async prepare(filter: IImageFilter): Promise<void> {\n this._discoveredUrls = [];\n\n for (const source of this.sources) {\n if ('folders' in source) {\n for (const folderUrl of source.folders) {\n const recursive = source.recursive !== undefined ? source.recursive : true;\n const urls = await this.loadFromFolder(folderUrl, filter, recursive);\n this._discoveredUrls.push(...urls);\n }\n } else if ('files' in source) {\n const urls = await this.loadFiles(source.files, filter);\n this._discoveredUrls.push(...urls);\n }\n }\n\n this._prepared = true;\n }\n\n /**\n * Get the number of discovered images\n * @throws Error if called before prepare()\n */\n imagesLength(): number {\n if (!this._prepared) {\n throw new Error('GoogleDriveLoader.imagesLength() called before prepare()');\n }\n return this._discoveredUrls.length;\n }\n\n /**\n * Get the ordered list of image URLs\n * @throws Error if called before prepare()\n */\n imageURLs(): string[] {\n if (!this._prepared) {\n throw new Error('GoogleDriveLoader.imageURLs() called before prepare()');\n }\n return [...this._discoveredUrls];\n }\n\n /**\n * Check if the loader has been prepared\n */\n isPrepared(): boolean {\n return this._prepared;\n }\n\n /**\n * Extract folder ID from various Google Drive URL formats\n * @param folderUrl - Google Drive folder URL\n * @returns Folder ID or null if invalid\n */\n extractFolderId(folderUrl: string): string | null {\n // Handle various URL formats:\n // https://drive.google.com/drive/folders/FOLDER_ID\n // https://drive.google.com/drive/folders/FOLDER_ID?usp=sharing\n\n const patterns = [\n /\\/folders\\/([a-zA-Z0-9_-]+)/, // Standard format\n /id=([a-zA-Z0-9_-]+)/ // Alternative format\n ];\n\n for (const pattern of patterns) {\n const match = folderUrl.match(pattern);\n if (match && match[1]) {\n return match[1];\n }\n }\n\n return null;\n }\n\n /**\n * Load images from a Google Drive folder\n * @param folderUrl - Google Drive folder URL\n * @param filter - Filter to apply to discovered images\n * @param recursive - Whether to include images from subfolders\n * @returns Promise resolving to array of image URLs\n */\n private async loadFromFolder(folderUrl: string, filter: IImageFilter, recursive: boolean = true): Promise<string[]> {\n const folderId = this.extractFolderId(folderUrl);\n\n if (!folderId) {\n throw new Error('Invalid Google Drive folder URL. Please check the URL format.');\n }\n\n // If no API key is configured, use direct link method\n if (!this.apiKey || this.apiKey === 'YOUR_API_KEY_HERE') {\n return this.loadImagesDirectly(folderId, filter);\n }\n\n try {\n if (recursive) {\n return await this.loadImagesRecursively(folderId, filter);\n } else {\n return await this.loadImagesFromSingleFolder(folderId, filter);\n }\n } catch (error) {\n console.error('Error loading from Google Drive API:', error);\n // Fallback to direct link method\n return this.loadImagesDirectly(folderId, filter);\n }\n }\n\n /**\n * Load images from a single folder (non-recursive)\n * @param folderId - Google Drive folder ID\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of image URLs\n */\n private async loadImagesFromSingleFolder(folderId: string, filter: IImageFilter): Promise<string[]> {\n const imageUrls: string[] = [];\n\n // Query for all files in this folder\n const query = `'${folderId}' in parents and trashed=false`;\n const fields = 'files(id,name,mimeType,thumbnailLink)';\n const url = `${this.apiEndpoint}?q=${encodeURIComponent(query)}&fields=${fields}&key=${this.apiKey}`;\n\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`API request failed: ${response.status} ${response.statusText}`);\n }\n\n const data: GoogleDriveResponse = await response.json();\n\n // Filter for valid image files only using the provided filter\n const validFiles = data.files.filter(file =>\n file.mimeType.startsWith('image/') && filter.isAllowed(file.name)\n );\n\n this.log(`Found ${validFiles.length} images in folder ${folderId} (non-recursive)`);\n\n // Add image URLs\n validFiles.forEach(file => {\n imageUrls.push(`https://lh3.googleusercontent.com/d/${file.id}=s1600`);\n this.log(`Added file: ${file.name}`);\n });\n\n return imageUrls;\n }\n\n /**\n * Load specific files by their URLs or IDs\n * @param fileUrls - Array of Google Drive file URLs or IDs\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of image URLs\n */\n private async loadFiles(fileUrls: string[], filter: IImageFilter): Promise<string[]> {\n const imageUrls: string[] = [];\n\n for (const fileUrl of fileUrls) {\n const fileId = this.extractFileId(fileUrl);\n\n if (!fileId) {\n this.log(`Skipping invalid file URL: ${fileUrl}`);\n continue;\n }\n\n // Validate it's an image file\n if (this.apiKey && this.apiKey !== 'YOUR_API_KEY_HERE') {\n try {\n // Get file metadata to verify it's an image\n const metadataUrl = `${this.apiEndpoint}/${fileId}?fields=name,mimeType&key=${this.apiKey}`;\n const response = await fetch(metadataUrl);\n\n if (response.ok) {\n const metadata = await response.json();\n if (metadata.mimeType.startsWith('image/') && filter.isAllowed(metadata.name)) {\n imageUrls.push(`https://lh3.googleusercontent.com/d/${fileId}=s1600`);\n this.log(`Added file: ${metadata.name}`);\n } else {\n this.log(`Skipping non-image file: ${metadata.name} (${metadata.mimeType})`);\n }\n } else {\n this.log(`Failed to fetch metadata for file ${fileId}: ${response.status}`);\n }\n } catch (error) {\n this.log(`Error fetching metadata for file ${fileId}:`, error);\n }\n } else {\n // Without API key, assume it's valid and add it\n imageUrls.push(`https://lh3.googleusercontent.com/d/${fileId}=s1600`);\n }\n }\n\n return imageUrls;\n }\n\n /**\n * Extract file ID from Google Drive file URL\n * @param fileUrl - Google Drive file URL or file ID\n * @returns File ID or null if invalid\n */\n private extractFileId(fileUrl: string): string | null {\n // Handle various URL formats:\n // https://drive.google.com/file/d/FILE_ID/view\n // https://drive.google.com/open?id=FILE_ID\n // FILE_ID (raw ID)\n\n // If it looks like a raw ID (no slashes or protocol), return it\n if (!/[/:.]/.test(fileUrl)) {\n return fileUrl;\n }\n\n const patterns = [\n /\\/file\\/d\\/([a-zA-Z0-9_-]+)/, // Standard file format\n /\\/open\\?id=([a-zA-Z0-9_-]+)/, // Alternative format\n /id=([a-zA-Z0-9_-]+)/ // Generic id parameter\n ];\n\n for (const pattern of patterns) {\n const match = fileUrl.match(pattern);\n if (match && match[1]) {\n return match[1];\n }\n }\n\n return null;\n }\n\n /**\n * Recursively load images from a folder and all its subfolders\n * @param folderId - Google Drive folder ID\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of image URLs\n */\n private async loadImagesRecursively(folderId: string, filter: IImageFilter): Promise<string[]> {\n const imageUrls: string[] = [];\n\n // Query for all files in this folder\n const query = `'${folderId}' in parents and trashed=false`;\n // Request thumbnailLink for PDFs\n const fields = 'files(id,name,mimeType,thumbnailLink)';\n const url = `${this.apiEndpoint}?q=${encodeURIComponent(query)}&fields=${fields}&key=${this.apiKey}`;\n\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`API request failed: ${response.status} ${response.statusText}`);\n }\n\n const data: GoogleDriveResponse = await response.json();\n\n // Separate images and folders using the provided filter\n const validFiles = data.files.filter(file =>\n file.mimeType.startsWith('image/') && filter.isAllowed(file.name)\n );\n\n const subfolders = data.files.filter(file =>\n file.mimeType === 'application/vnd.google-apps.folder'\n );\n\n this.log(`Found ${data.files.length} total items in folder ${folderId}`);\n // Log details of all files to see what we are missing\n data.files.forEach(f => this.log(` - File: ${f.name} (${f.mimeType})`));\n\n this.log(`- ${validFiles.length} valid files (images only)`);\n this.log(`- ${subfolders.length} subfolders`);\n\n // Add image URLs from this folder\n validFiles.forEach(file => {\n // Use the reliable thumbnail/preview endpoint for both Images and PDFs\n // This works for public folders and handles file format conversion automatically\n // 'sz=w1000' requests a high-quality preview (1000px width)\n // detailed explanation:\n // 1. \"drive.google.com\" is blocked by ad-blockers (net::ERR_BLOCKED_BY_CLIENT)\n // 2. The API's \"thumbnailLink\" is a signed URL that can expire or fail 403.\n // 3. \"lh3.googleusercontent.com/d/{ID}\" is the permanent CDN link structure.\n // It bypasses the domain block AND the signing issues.\n imageUrls.push(`https://lh3.googleusercontent.com/d/${file.id}=s1600`);\n\n this.log(`Added file: ${file.name}`);\n });\n\n // Recursively process subfolders\n for (const folder of subfolders) {\n this.log(`Loading images from subfolder: ${folder.name}`);\n const subfolderImages = await this.loadImagesRecursively(folder.id, filter);\n imageUrls.push(...subfolderImages);\n }\n\n return imageUrls;\n }\n\n /**\n * Direct loading method (no API key required, but less reliable)\n * Uses embedded folder view to scrape image IDs\n * @param folderId - Google Drive folder ID\n * @param filter - Filter to apply (not used in fallback mode)\n * @returns Promise resolving to array of image URLs\n */\n private async loadImagesDirectly(folderId: string, _filter: IImageFilter): Promise<string[]> {\n // For now, we'll return a method that requires the user to manually provide image IDs\n // or we construct URLs based on a known pattern\n\n // This is a fallback - in production, you'd want to use the API\n // For demo purposes, we can try to fetch the folder page and extract image IDs\n\n try {\n // Attempt to fetch folder page (CORS may block this)\n const folderUrl = `https://drive.google.com/embeddedfolderview?id=${folderId}`;\n const response = await fetch(folderUrl, { mode: 'cors' });\n\n if (!response.ok) {\n throw new Error('Cannot access folder directly (CORS or permissions issue)');\n }\n\n const html = await response.text();\n\n // Try to extract image IDs from HTML (this is fragile and may break)\n const imageIdPattern = /\\/file\\/d\\/([a-zA-Z0-9_-]+)/g;\n const matches = [...html.matchAll(imageIdPattern)];\n const imageIds = [...new Set(matches.map(m => m[1]))];\n\n const imageUrls = imageIds.map(id =>\n `https://drive.google.com/uc?export=view&id=${id}`\n );\n\n return imageUrls;\n\n } catch (error) {\n console.error('Direct loading failed:', error);\n throw new Error(\n 'Unable to load images. Please ensure:\\n' +\n '1. The folder is shared publicly (Anyone with the link can view)\\n' +\n '2. The folder contains image files\\n' +\n '3. Consider adding a Google Drive API key in config.js for better reliability'\n );\n }\n }\n\n /**\n * Manually add image URLs (for testing or when auto-loading fails)\n * @param imageIds - Array of Google Drive file IDs\n * @returns Array of direct image URLs\n */\n manualImageUrls(imageIds: string[]): string[] {\n return imageIds.map(id => `https://drive.google.com/uc?export=view&id=${id}`);\n }\n\n /**\n * Debug logging helper\n * @param args - Arguments to log\n */\n private log(...args: unknown[]): void {\n if (this.debugLogging && typeof console !== 'undefined') {\n console.log(...args);\n }\n }\n}\n","import { LoaderRegistry } from '@frybynite/image-cloud';\nimport { GoogleDriveLoader } from './GoogleDriveLoader';\n\nLoaderRegistry.registerLoader('google-drive', GoogleDriveLoader);\n\nexport { GoogleDriveLoader };\n"],"names":["GoogleDriveLoader","config","filter","source","folderUrl","recursive","urls","patterns","pattern","match","folderId","error","imageUrls","query","url","response","validFiles","file","fileUrls","fileUrl","fileId","metadataUrl","metadata","data","subfolders","f","folder","subfolderImages","_filter","html","imageIdPattern","matches","m","id","imageIds","args","LoaderRegistry"],"mappings":"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"}
@@ -1089,8 +1089,37 @@ declare interface LayoutSpacingConfig {
1089
1089
  minGap: number;
1090
1090
  }
1091
1091
 
1092
+ /**
1093
+ * Constructor signature for loader classes
1094
+ * Supports both simple loaders and composite loaders with their respective config types
1095
+ */
1096
+ declare type LoaderConstructor = (new (config: StaticLoaderInnerConfig) => ImageLoader) | (new (config: GoogleDriveLoaderInnerConfig) => ImageLoader) | (new (config: any) => ImageLoader);
1097
+
1092
1098
  export declare type LoaderEntry = StaticLoaderEntry | GoogleDriveLoaderEntry;
1093
1099
 
1100
+ export declare class LoaderRegistry {
1101
+ private static readonly registry;
1102
+ /**
1103
+ * Register a loader implementation with the registry
1104
+ * @param name - Loader identifier (e.g., 'static', 'google-drive', 'composite')
1105
+ * @param Loader - Loader class constructor to register
1106
+ */
1107
+ static registerLoader(name: string, Loader: LoaderConstructor): void;
1108
+ /**
1109
+ * Get a registered loader implementation
1110
+ * @param name - Loader identifier
1111
+ * @returns Loader class constructor
1112
+ * @throws Error if loader is not registered
1113
+ */
1114
+ static getLoader(name: string): LoaderConstructor;
1115
+ /**
1116
+ * Check if a loader is registered
1117
+ * @param name - Loader identifier
1118
+ * @returns True if the loader is registered, false otherwise
1119
+ */
1120
+ static isRegistered(name: string): boolean;
1121
+ }
1122
+
1094
1123
  declare interface NavigationInteractionConfig {
1095
1124
  keyboard?: boolean;
1096
1125
  swipe?: boolean;
@@ -1,38 +1,5 @@
1
- const a = class a {
2
- /**
3
- * Register a loader implementation with the registry
4
- * @param name - Loader identifier (e.g., 'static', 'google-drive', 'composite')
5
- * @param Loader - Loader class constructor to register
6
- */
7
- static registerLoader(e, t) {
8
- a.registry.set(e, t);
9
- }
10
- /**
11
- * Get a registered loader implementation
12
- * @param name - Loader identifier
13
- * @returns Loader class constructor
14
- * @throws Error if loader is not registered
15
- */
16
- static getLoader(e) {
17
- const t = a.registry.get(e);
18
- if (!t)
19
- throw new Error(
20
- `Loader "${e}" is not registered. Import "@frybynite/image-cloud/loaders/${e}" or "@frybynite/image-cloud/loaders/all".`
21
- );
22
- return t;
23
- }
24
- /**
25
- * Check if a loader is registered
26
- * @param name - Loader identifier
27
- * @returns True if the loader is registered, false otherwise
28
- */
29
- static isRegistered(e) {
30
- return a.registry.has(e);
31
- }
32
- };
33
- a.registry = /* @__PURE__ */ new Map();
34
- let n = a;
35
- class l {
1
+ import { LoaderRegistry as n } from "@frybynite/image-cloud";
2
+ class a {
36
3
  constructor(e) {
37
4
  if (this._prepared = !1, this._discoveredUrls = [], this.validateUrls = e.validateUrls !== !1, this.validationTimeout = e.validationTimeout ?? 5e3, this.validationMethod = e.validationMethod ?? "head", this.debugLogging = e.debugLogging ?? !1, this.sources = e.sources ?? [], !this.sources || this.sources.length === 0)
38
5
  throw new Error("StaticImageLoader requires at least one source to be configured");
@@ -44,12 +11,12 @@ class l {
44
11
  */
45
12
  async prepare(e) {
46
13
  this._discoveredUrls = [], this.log(`Processing ${this.sources.length} source(s)`);
47
- for (const t of this.sources)
14
+ for (const r of this.sources)
48
15
  try {
49
- const r = await this.processSource(t, e);
50
- this._discoveredUrls.push(...r);
51
- } catch (r) {
52
- console.warn("Failed to process source:", t, r);
16
+ const t = await this.processSource(r, e);
17
+ this._discoveredUrls.push(...t);
18
+ } catch (t) {
19
+ console.warn("Failed to process source:", r, t);
53
20
  }
54
21
  this._prepared = !0, this.log(`Successfully loaded ${this._discoveredUrls.length} image(s)`);
55
22
  }
@@ -83,8 +50,8 @@ class l {
83
50
  * @param filter - Filter to apply to discovered images
84
51
  * @returns Promise resolving to array of valid URLs from this source
85
52
  */
86
- async processSource(e, t) {
87
- return e ? "urls" in e ? await this.processUrls(e.urls, t) : "path" in e ? await this.processPath(e.path, e.files, t) : "json" in e ? await this.processJson(e.json, t) : (console.warn("Unknown source shape:", e), []) : (console.warn("Invalid source object:", e), []);
53
+ async processSource(e, r) {
54
+ return e ? "urls" in e ? await this.processUrls(e.urls, r) : "path" in e ? await this.processPath(e.path, e.files, r) : "json" in e ? await this.processJson(e.json, r) : (console.warn("Unknown source shape:", e), []) : (console.warn("Invalid source object:", e), []);
88
55
  }
89
56
  /**
90
57
  * Process a list of direct URLs
@@ -92,19 +59,19 @@ class l {
92
59
  * @param filter - Filter to apply to discovered images
93
60
  * @returns Promise resolving to array of validated URLs
94
61
  */
95
- async processUrls(e, t) {
62
+ async processUrls(e, r) {
96
63
  if (!Array.isArray(e))
97
64
  return console.warn("URLs must be an array:", e), [];
98
- const r = [];
65
+ const t = [];
99
66
  for (const i of e) {
100
67
  const s = i.split("/").pop() || i;
101
- if (!t.isAllowed(s)) {
68
+ if (!r.isAllowed(s)) {
102
69
  this.log(`Skipping filtered URL: ${i}`);
103
70
  continue;
104
71
  }
105
- this.validateUrls ? await this.validateUrl(i) ? r.push(i) : console.warn(`Skipping invalid/missing URL: ${i}`) : r.push(i);
72
+ this.validateUrls ? await this.validateUrl(i) ? t.push(i) : console.warn(`Skipping invalid/missing URL: ${i}`) : t.push(i);
106
73
  }
107
- return r;
74
+ return t;
108
75
  }
109
76
  /**
110
77
  * Process a path-based source
@@ -113,12 +80,12 @@ class l {
113
80
  * @param filter - Filter to apply to discovered images
114
81
  * @returns Promise resolving to array of validated URLs
115
82
  */
116
- async processPath(e, t, r) {
117
- if (!Array.isArray(t))
118
- return console.warn("files must be an array:", t), [];
83
+ async processPath(e, r, t) {
84
+ if (!Array.isArray(r))
85
+ return console.warn("files must be an array:", r), [];
119
86
  const i = [];
120
- for (const s of t) {
121
- if (!r.isAllowed(s)) {
87
+ for (const s of r) {
88
+ if (!t.isAllowed(s)) {
122
89
  this.log(`Skipping filtered file: ${s}`);
123
90
  continue;
124
91
  }
@@ -134,17 +101,17 @@ class l {
134
101
  * @param filter - Filter to apply to discovered images
135
102
  * @returns Promise resolving to array of validated URLs
136
103
  */
137
- async processJson(e, t) {
104
+ async processJson(e, r) {
138
105
  this.log(`Fetching JSON endpoint: ${e}`);
139
- const r = new AbortController(), i = setTimeout(() => r.abort(), 1e4);
106
+ const t = new AbortController(), i = setTimeout(() => t.abort(), 1e4);
140
107
  try {
141
- const s = await fetch(e, { signal: r.signal });
108
+ const s = await fetch(e, { signal: t.signal });
142
109
  if (clearTimeout(i), !s.ok)
143
110
  throw new Error(`HTTP ${s.status} fetching ${e}`);
144
111
  const o = await s.json();
145
112
  if (!o || !Array.isArray(o.images))
146
113
  throw new Error('JSON source must return JSON with shape { "images": ["url1", "url2", ...] }');
147
- return this.log(`JSON endpoint returned ${o.images.length} image(s)`), await this.processUrls(o.images, t);
114
+ return this.log(`JSON endpoint returned ${o.images.length} image(s)`), await this.processUrls(o.images, r);
148
115
  } catch (s) {
149
116
  throw clearTimeout(i), s instanceof Error && s.name === "AbortError" ? new Error(`Timeout fetching JSON endpoint: ${e}`) : s;
150
117
  }
@@ -168,13 +135,13 @@ class l {
168
135
  if (!(e.startsWith(window.location.origin) || e.startsWith("/")))
169
136
  return this.log(`Skipping validation for cross-origin URL: ${e}`), !0;
170
137
  try {
171
- const r = new AbortController(), i = setTimeout(() => r.abort(), this.validationTimeout), s = await fetch(e, {
138
+ const t = new AbortController(), i = setTimeout(() => t.abort(), this.validationTimeout), s = await fetch(e, {
172
139
  method: "HEAD",
173
- signal: r.signal
140
+ signal: t.signal
174
141
  });
175
142
  return clearTimeout(i), s.ok ? !0 : (this.log(`Validation failed for ${e}: HTTP ${s.status}`), !1);
176
- } catch (r) {
177
- return r instanceof Error && (r.name === "AbortError" ? this.log(`Validation timeout for ${e}`) : this.log(`Validation failed for ${e}:`, r.message)), !1;
143
+ } catch (t) {
144
+ return t instanceof Error && (t.name === "AbortError" ? this.log(`Validation timeout for ${e}`) : this.log(`Validation failed for ${e}:`, t.message)), !1;
178
145
  }
179
146
  }
180
147
  /**
@@ -183,14 +150,14 @@ class l {
183
150
  * @param filename - Filename to append
184
151
  * @returns Complete URL
185
152
  */
186
- constructUrl(e, t) {
187
- const r = e.replace(/\/$/, "");
153
+ constructUrl(e, r) {
154
+ const t = e.replace(/\/$/, "");
188
155
  if (this.isAbsoluteUrl(e))
189
- return `${r}/${t}`;
156
+ return `${t}/${r}`;
190
157
  if (typeof window > "u")
191
- return `${r}/${t}`;
158
+ return `${t}/${r}`;
192
159
  const i = window.location.origin, o = (e.startsWith("/") ? e : "/" + e).replace(/\/$/, "");
193
- return `${i}${o}/${t}`;
160
+ return `${i}${o}/${r}`;
194
161
  }
195
162
  /**
196
163
  * Check if URL is absolute (contains protocol)
@@ -212,8 +179,8 @@ class l {
212
179
  this.debugLogging && typeof console < "u" && console.log(...e);
213
180
  }
214
181
  }
215
- n.registerLoader("static", l);
182
+ n.registerLoader("static", a);
216
183
  export {
217
- l as StaticImageLoader
184
+ a as StaticImageLoader
218
185
  };
219
186
  //# sourceMappingURL=static.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"static.js","sources":["../../src/engines/LoaderRegistry.ts","../../src/loaders/StaticImageLoader.ts","../../src/loaders/index-static.ts"],"sourcesContent":["/**\n * Loader Registry - Manages registration and lookup of image loader implementations\n *\n * This registry enables dynamic loading of loaders through separate bundles\n * while maintaining a central registry of available loaders. It mirrors the\n * LayoutEngine registry pattern for consistency.\n *\n * Public API:\n * - registerLoader(name, LoaderClass)\n * - getLoader(name)\n * - isRegistered(name)\n */\n\nimport type { ImageLoader, StaticLoaderInnerConfig, GoogleDriveLoaderInnerConfig } from '../config/types';\n\n/**\n * Constructor signature for loader classes\n * Supports both simple loaders and composite loaders with their respective config types\n */\nexport type LoaderConstructor =\n | (new (config: StaticLoaderInnerConfig) => ImageLoader)\n | (new (config: GoogleDriveLoaderInnerConfig) => ImageLoader)\n | (new (config: any) => ImageLoader);\n\nexport class LoaderRegistry {\n private static readonly registry = new Map<string, LoaderConstructor>();\n\n /**\n * Register a loader implementation with the registry\n * @param name - Loader identifier (e.g., 'static', 'google-drive', 'composite')\n * @param Loader - Loader class constructor to register\n */\n static registerLoader(name: string, Loader: LoaderConstructor): void {\n LoaderRegistry.registry.set(name, Loader);\n }\n\n /**\n * Get a registered loader implementation\n * @param name - Loader identifier\n * @returns Loader class constructor\n * @throws Error if loader is not registered\n */\n static getLoader(name: string): LoaderConstructor {\n const Loader = LoaderRegistry.registry.get(name);\n\n if (!Loader) {\n throw new Error(\n `Loader \"${name}\" is not registered. ` +\n `Import \"@frybynite/image-cloud/loaders/${name}\" or \"@frybynite/image-cloud/loaders/all\".`\n );\n }\n\n return Loader;\n }\n\n /**\n * Check if a loader is registered\n * @param name - Loader identifier\n * @returns True if the loader is registered, false otherwise\n */\n static isRegistered(name: string): boolean {\n return LoaderRegistry.registry.has(name);\n }\n}\n","/**\n * StaticImageLoader.ts\n * Loads images from predefined URL sources and local paths\n * Compatible with ImageCloud's loader interface\n *\n * Public API:\n * - prepare(filter) - Async discovery of images\n * - imagesLength() - Get count of discovered images\n * - imageURLs() - Get ordered list of image URLs\n * - isPrepared() - Check if loader has been prepared\n */\n\nimport type { ImageLoader, IImageFilter, StaticSource, StaticLoaderInnerConfig } from '../config/types';\n\nexport class StaticImageLoader implements ImageLoader {\n private validateUrls: boolean;\n private validationTimeout: number;\n private validationMethod: 'head' | 'simple' | 'none';\n private sources: StaticSource[];\n private debugLogging: boolean;\n\n // State for new interface\n private _prepared: boolean = false;\n private _discoveredUrls: string[] = [];\n\n constructor(config: StaticLoaderInnerConfig) {\n this.validateUrls = config.validateUrls !== false;\n this.validationTimeout = config.validationTimeout ?? 5000;\n this.validationMethod = config.validationMethod ?? 'head';\n this.debugLogging = config.debugLogging ?? false;\n this.sources = config.sources ?? [];\n\n // Validate that we have sources configured\n if (!this.sources || this.sources.length === 0) {\n throw new Error('StaticImageLoader requires at least one source to be configured');\n }\n\n this.log('StaticImageLoader initialized with config:', config);\n }\n\n /**\n * Prepare the loader by discovering all images from configured sources\n * @param filter - Filter to apply to discovered images\n */\n async prepare(filter: IImageFilter): Promise<void> {\n this._discoveredUrls = [];\n\n this.log(`Processing ${this.sources.length} source(s)`);\n\n // Process sources sequentially to preserve order\n for (const source of this.sources) {\n try {\n const urls = await this.processSource(source, filter);\n this._discoveredUrls.push(...urls);\n } catch (error) {\n console.warn('Failed to process source:', source, error);\n // Continue processing other sources\n }\n }\n\n this._prepared = true;\n this.log(`Successfully loaded ${this._discoveredUrls.length} image(s)`);\n }\n\n /**\n * Get the number of discovered images\n * @throws Error if called before prepare()\n */\n imagesLength(): number {\n if (!this._prepared) {\n throw new Error('StaticImageLoader.imagesLength() called before prepare()');\n }\n return this._discoveredUrls.length;\n }\n\n /**\n * Get the ordered list of image URLs\n * @throws Error if called before prepare()\n */\n imageURLs(): string[] {\n if (!this._prepared) {\n throw new Error('StaticImageLoader.imageURLs() called before prepare()');\n }\n return [...this._discoveredUrls];\n }\n\n /**\n * Check if the loader has been prepared\n */\n isPrepared(): boolean {\n return this._prepared;\n }\n\n /**\n * Process a single source object using shape-based detection\n * @param source - Source configuration detected by key presence\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of valid URLs from this source\n */\n private async processSource(source: StaticSource, filter: IImageFilter): Promise<string[]> {\n if (!source) {\n console.warn('Invalid source object:', source);\n return [];\n }\n\n if ('urls' in source) {\n return await this.processUrls(source.urls, filter);\n } else if ('path' in source) {\n return await this.processPath(source.path, source.files, filter);\n } else if ('json' in source) {\n return await this.processJson(source.json, filter);\n } else {\n console.warn('Unknown source shape:', source);\n return [];\n }\n }\n\n /**\n * Process a list of direct URLs\n * @param urls - Array of image URLs\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of validated URLs\n */\n private async processUrls(urls: string[], filter: IImageFilter): Promise<string[]> {\n if (!Array.isArray(urls)) {\n console.warn('URLs must be an array:', urls);\n return [];\n }\n\n const validUrls: string[] = [];\n\n for (const url of urls) {\n // Apply filter based on URL filename\n const filename = url.split('/').pop() || url;\n if (!filter.isAllowed(filename)) {\n this.log(`Skipping filtered URL: ${url}`);\n continue;\n }\n\n if (this.validateUrls) {\n const isValid = await this.validateUrl(url);\n if (isValid) {\n validUrls.push(url);\n } else {\n console.warn(`Skipping invalid/missing URL: ${url}`);\n }\n } else {\n // No validation - add all URLs\n validUrls.push(url);\n }\n }\n\n return validUrls;\n }\n\n /**\n * Process a path-based source\n * @param basePath - Base path (relative or absolute)\n * @param files - Array of filenames\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of validated URLs\n */\n private async processPath(basePath: string, files: string[], filter: IImageFilter): Promise<string[]> {\n\n if (!Array.isArray(files)) {\n console.warn('files must be an array:', files);\n return [];\n }\n\n const validUrls: string[] = [];\n\n for (const file of files) {\n // Apply filter based on filename\n if (!filter.isAllowed(file)) {\n this.log(`Skipping filtered file: ${file}`);\n continue;\n }\n\n const url = this.constructUrl(basePath, file);\n\n if (this.validateUrls) {\n const isValid = await this.validateUrl(url);\n if (isValid) {\n validUrls.push(url);\n } else {\n console.warn(`Skipping invalid/missing file: ${url}`);\n }\n } else {\n // No validation - add all URLs\n validUrls.push(url);\n }\n }\n\n return validUrls;\n }\n\n /**\n * Process a JSON endpoint source\n * Fetches a JSON endpoint that returns { images: string[] }\n * @param url - JSON endpoint URL\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of validated URLs\n */\n private async processJson(url: string, filter: IImageFilter): Promise<string[]> {\n\n this.log(`Fetching JSON endpoint: ${url}`);\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 10000);\n\n try {\n const response = await fetch(url, { signal: controller.signal });\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status} fetching ${url}`);\n }\n\n const data = await response.json();\n\n if (!data || !Array.isArray(data.images)) {\n throw new Error(`JSON source must return JSON with shape { \"images\": [\"url1\", \"url2\", ...] }`);\n }\n\n this.log(`JSON endpoint returned ${data.images.length} image(s)`);\n\n // Process the URLs through the standard URL processing pipeline\n return await this.processUrls(data.images, filter);\n } catch (error) {\n clearTimeout(timeoutId);\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Timeout fetching JSON endpoint: ${url}`);\n }\n throw error;\n }\n }\n\n /**\n * Validate a single URL using HEAD request\n * @param url - URL to validate\n * @returns Promise resolving to true if valid and accessible\n */\n private async validateUrl(url: string): Promise<boolean> {\n if (this.validationMethod === 'none') {\n return true;\n }\n\n if (this.validationMethod === 'simple') {\n // Basic URL format check\n try {\n if (typeof window !== 'undefined') {\n new URL(url, window.location.origin);\n } else {\n new URL(url);\n }\n return true;\n } catch {\n return false;\n }\n }\n\n // validationMethod === 'head' (default)\n // For cross-origin URLs, we can't validate due to CORS\n // So we only validate same-origin URLs\n if (typeof window === 'undefined') {\n return true; // In non-browser environment, assume valid\n }\n\n const isSameOrigin = url.startsWith(window.location.origin) ||\n url.startsWith('/');\n\n if (!isSameOrigin) {\n // Cross-origin URL - assume valid, can't validate due to CORS\n this.log(`Skipping validation for cross-origin URL: ${url}`);\n return true;\n }\n\n // Same-origin URL - validate with HEAD request\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.validationTimeout);\n\n const response = await fetch(url, {\n method: 'HEAD',\n signal: controller.signal\n });\n\n clearTimeout(timeoutId);\n\n if (response.ok) {\n return true;\n } else {\n this.log(`Validation failed for ${url}: HTTP ${response.status}`);\n return false;\n }\n\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n this.log(`Validation timeout for ${url}`);\n } else {\n this.log(`Validation failed for ${url}:`, error.message);\n }\n }\n return false;\n }\n }\n\n /**\n * Construct full URL from basePath and filename\n * @param basePath - Base path (relative or absolute)\n * @param filename - Filename to append\n * @returns Complete URL\n */\n private constructUrl(basePath: string, filename: string): string {\n // Remove trailing slash from basePath\n const cleanBase = basePath.replace(/\\/$/, '');\n\n // Check if basePath is absolute URL\n if (this.isAbsoluteUrl(basePath)) {\n return `${cleanBase}/${filename}`;\n }\n\n // Relative path - prepend current origin\n if (typeof window === 'undefined') {\n return `${cleanBase}/${filename}`; // In non-browser environment, return as-is\n }\n\n const origin = window.location.origin;\n // Ensure basePath starts with /\n const normalizedPath = basePath.startsWith('/') ? basePath : '/' + basePath;\n const cleanPath = normalizedPath.replace(/\\/$/, '');\n\n return `${origin}${cleanPath}/${filename}`;\n }\n\n /**\n * Check if URL is absolute (contains protocol)\n * @param url - URL to check\n * @returns True if absolute URL\n */\n private isAbsoluteUrl(url: string): boolean {\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Debug logging helper\n * @param args - Arguments to log\n */\n private log(...args: unknown[]): void {\n if (this.debugLogging && typeof console !== 'undefined') {\n console.log(...args);\n }\n }\n}\n","import { LoaderRegistry } from '../engines/LoaderRegistry';\nimport { StaticImageLoader } from './StaticImageLoader';\n\nLoaderRegistry.registerLoader('static', StaticImageLoader);\n\nexport { StaticImageLoader };\n"],"names":["_LoaderRegistry","name","Loader","LoaderRegistry","StaticImageLoader","config","filter","source","urls","error","validUrls","url","filename","basePath","files","file","controller","timeoutId","response","data","cleanBase","origin","cleanPath","args"],"mappings":"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"}
1
+ {"version":3,"file":"static.js","sources":["../../src/loaders/StaticImageLoader.ts","../../src/loaders/index-static.ts"],"sourcesContent":["/**\n * StaticImageLoader.ts\n * Loads images from predefined URL sources and local paths\n * Compatible with ImageCloud's loader interface\n *\n * Public API:\n * - prepare(filter) - Async discovery of images\n * - imagesLength() - Get count of discovered images\n * - imageURLs() - Get ordered list of image URLs\n * - isPrepared() - Check if loader has been prepared\n */\n\nimport type { ImageLoader, IImageFilter, StaticSource, StaticLoaderInnerConfig } from '../config/types';\n\nexport class StaticImageLoader implements ImageLoader {\n private validateUrls: boolean;\n private validationTimeout: number;\n private validationMethod: 'head' | 'simple' | 'none';\n private sources: StaticSource[];\n private debugLogging: boolean;\n\n // State for new interface\n private _prepared: boolean = false;\n private _discoveredUrls: string[] = [];\n\n constructor(config: StaticLoaderInnerConfig) {\n this.validateUrls = config.validateUrls !== false;\n this.validationTimeout = config.validationTimeout ?? 5000;\n this.validationMethod = config.validationMethod ?? 'head';\n this.debugLogging = config.debugLogging ?? false;\n this.sources = config.sources ?? [];\n\n // Validate that we have sources configured\n if (!this.sources || this.sources.length === 0) {\n throw new Error('StaticImageLoader requires at least one source to be configured');\n }\n\n this.log('StaticImageLoader initialized with config:', config);\n }\n\n /**\n * Prepare the loader by discovering all images from configured sources\n * @param filter - Filter to apply to discovered images\n */\n async prepare(filter: IImageFilter): Promise<void> {\n this._discoveredUrls = [];\n\n this.log(`Processing ${this.sources.length} source(s)`);\n\n // Process sources sequentially to preserve order\n for (const source of this.sources) {\n try {\n const urls = await this.processSource(source, filter);\n this._discoveredUrls.push(...urls);\n } catch (error) {\n console.warn('Failed to process source:', source, error);\n // Continue processing other sources\n }\n }\n\n this._prepared = true;\n this.log(`Successfully loaded ${this._discoveredUrls.length} image(s)`);\n }\n\n /**\n * Get the number of discovered images\n * @throws Error if called before prepare()\n */\n imagesLength(): number {\n if (!this._prepared) {\n throw new Error('StaticImageLoader.imagesLength() called before prepare()');\n }\n return this._discoveredUrls.length;\n }\n\n /**\n * Get the ordered list of image URLs\n * @throws Error if called before prepare()\n */\n imageURLs(): string[] {\n if (!this._prepared) {\n throw new Error('StaticImageLoader.imageURLs() called before prepare()');\n }\n return [...this._discoveredUrls];\n }\n\n /**\n * Check if the loader has been prepared\n */\n isPrepared(): boolean {\n return this._prepared;\n }\n\n /**\n * Process a single source object using shape-based detection\n * @param source - Source configuration detected by key presence\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of valid URLs from this source\n */\n private async processSource(source: StaticSource, filter: IImageFilter): Promise<string[]> {\n if (!source) {\n console.warn('Invalid source object:', source);\n return [];\n }\n\n if ('urls' in source) {\n return await this.processUrls(source.urls, filter);\n } else if ('path' in source) {\n return await this.processPath(source.path, source.files, filter);\n } else if ('json' in source) {\n return await this.processJson(source.json, filter);\n } else {\n console.warn('Unknown source shape:', source);\n return [];\n }\n }\n\n /**\n * Process a list of direct URLs\n * @param urls - Array of image URLs\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of validated URLs\n */\n private async processUrls(urls: string[], filter: IImageFilter): Promise<string[]> {\n if (!Array.isArray(urls)) {\n console.warn('URLs must be an array:', urls);\n return [];\n }\n\n const validUrls: string[] = [];\n\n for (const url of urls) {\n // Apply filter based on URL filename\n const filename = url.split('/').pop() || url;\n if (!filter.isAllowed(filename)) {\n this.log(`Skipping filtered URL: ${url}`);\n continue;\n }\n\n if (this.validateUrls) {\n const isValid = await this.validateUrl(url);\n if (isValid) {\n validUrls.push(url);\n } else {\n console.warn(`Skipping invalid/missing URL: ${url}`);\n }\n } else {\n // No validation - add all URLs\n validUrls.push(url);\n }\n }\n\n return validUrls;\n }\n\n /**\n * Process a path-based source\n * @param basePath - Base path (relative or absolute)\n * @param files - Array of filenames\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of validated URLs\n */\n private async processPath(basePath: string, files: string[], filter: IImageFilter): Promise<string[]> {\n\n if (!Array.isArray(files)) {\n console.warn('files must be an array:', files);\n return [];\n }\n\n const validUrls: string[] = [];\n\n for (const file of files) {\n // Apply filter based on filename\n if (!filter.isAllowed(file)) {\n this.log(`Skipping filtered file: ${file}`);\n continue;\n }\n\n const url = this.constructUrl(basePath, file);\n\n if (this.validateUrls) {\n const isValid = await this.validateUrl(url);\n if (isValid) {\n validUrls.push(url);\n } else {\n console.warn(`Skipping invalid/missing file: ${url}`);\n }\n } else {\n // No validation - add all URLs\n validUrls.push(url);\n }\n }\n\n return validUrls;\n }\n\n /**\n * Process a JSON endpoint source\n * Fetches a JSON endpoint that returns { images: string[] }\n * @param url - JSON endpoint URL\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of validated URLs\n */\n private async processJson(url: string, filter: IImageFilter): Promise<string[]> {\n\n this.log(`Fetching JSON endpoint: ${url}`);\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 10000);\n\n try {\n const response = await fetch(url, { signal: controller.signal });\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status} fetching ${url}`);\n }\n\n const data = await response.json();\n\n if (!data || !Array.isArray(data.images)) {\n throw new Error(`JSON source must return JSON with shape { \"images\": [\"url1\", \"url2\", ...] }`);\n }\n\n this.log(`JSON endpoint returned ${data.images.length} image(s)`);\n\n // Process the URLs through the standard URL processing pipeline\n return await this.processUrls(data.images, filter);\n } catch (error) {\n clearTimeout(timeoutId);\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Timeout fetching JSON endpoint: ${url}`);\n }\n throw error;\n }\n }\n\n /**\n * Validate a single URL using HEAD request\n * @param url - URL to validate\n * @returns Promise resolving to true if valid and accessible\n */\n private async validateUrl(url: string): Promise<boolean> {\n if (this.validationMethod === 'none') {\n return true;\n }\n\n if (this.validationMethod === 'simple') {\n // Basic URL format check\n try {\n if (typeof window !== 'undefined') {\n new URL(url, window.location.origin);\n } else {\n new URL(url);\n }\n return true;\n } catch {\n return false;\n }\n }\n\n // validationMethod === 'head' (default)\n // For cross-origin URLs, we can't validate due to CORS\n // So we only validate same-origin URLs\n if (typeof window === 'undefined') {\n return true; // In non-browser environment, assume valid\n }\n\n const isSameOrigin = url.startsWith(window.location.origin) ||\n url.startsWith('/');\n\n if (!isSameOrigin) {\n // Cross-origin URL - assume valid, can't validate due to CORS\n this.log(`Skipping validation for cross-origin URL: ${url}`);\n return true;\n }\n\n // Same-origin URL - validate with HEAD request\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.validationTimeout);\n\n const response = await fetch(url, {\n method: 'HEAD',\n signal: controller.signal\n });\n\n clearTimeout(timeoutId);\n\n if (response.ok) {\n return true;\n } else {\n this.log(`Validation failed for ${url}: HTTP ${response.status}`);\n return false;\n }\n\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n this.log(`Validation timeout for ${url}`);\n } else {\n this.log(`Validation failed for ${url}:`, error.message);\n }\n }\n return false;\n }\n }\n\n /**\n * Construct full URL from basePath and filename\n * @param basePath - Base path (relative or absolute)\n * @param filename - Filename to append\n * @returns Complete URL\n */\n private constructUrl(basePath: string, filename: string): string {\n // Remove trailing slash from basePath\n const cleanBase = basePath.replace(/\\/$/, '');\n\n // Check if basePath is absolute URL\n if (this.isAbsoluteUrl(basePath)) {\n return `${cleanBase}/${filename}`;\n }\n\n // Relative path - prepend current origin\n if (typeof window === 'undefined') {\n return `${cleanBase}/${filename}`; // In non-browser environment, return as-is\n }\n\n const origin = window.location.origin;\n // Ensure basePath starts with /\n const normalizedPath = basePath.startsWith('/') ? basePath : '/' + basePath;\n const cleanPath = normalizedPath.replace(/\\/$/, '');\n\n return `${origin}${cleanPath}/${filename}`;\n }\n\n /**\n * Check if URL is absolute (contains protocol)\n * @param url - URL to check\n * @returns True if absolute URL\n */\n private isAbsoluteUrl(url: string): boolean {\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Debug logging helper\n * @param args - Arguments to log\n */\n private log(...args: unknown[]): void {\n if (this.debugLogging && typeof console !== 'undefined') {\n console.log(...args);\n }\n }\n}\n","import { LoaderRegistry } from '@frybynite/image-cloud';\nimport { StaticImageLoader } from './StaticImageLoader';\n\nLoaderRegistry.registerLoader('static', StaticImageLoader);\n\nexport { StaticImageLoader };\n"],"names":["StaticImageLoader","config","filter","source","urls","error","validUrls","url","filename","basePath","files","file","controller","timeoutId","response","data","cleanBase","origin","cleanPath","args","LoaderRegistry"],"mappings":"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"}