image-exporter 0.0.1 → 1.0.0

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 (56) hide show
  1. package/README.md +137 -2
  2. package/bun.lockb +0 -0
  3. package/dist/image-exporter.es.js +499 -612
  4. package/dist/image-exporter.umd.js +506 -619
  5. package/package.json +18 -15
  6. package/src/capture/capture-element.ts +58 -0
  7. package/src/capture/determine-total-elements.ts +40 -0
  8. package/src/capture/download-images.ts +69 -0
  9. package/src/capture/get-image-options.ts +196 -0
  10. package/src/capture/handle-filenames.ts +52 -0
  11. package/src/capture/index.ts +99 -0
  12. package/src/capture/remove-hidden-elements.ts +19 -0
  13. package/src/config.ts +19 -0
  14. package/src/cors-proxy/cleanup.ts +43 -0
  15. package/src/cors-proxy/index.ts +6 -21
  16. package/src/{utils → cors-proxy}/is-valid-url.ts +5 -3
  17. package/src/cors-proxy/proxy-css.ts +51 -44
  18. package/src/cors-proxy/proxy-images.ts +21 -77
  19. package/src/cors-proxy/run.ts +26 -0
  20. package/src/index.ts +10 -4
  21. package/src/logger.ts +61 -0
  22. package/src/types.d.ts +51 -0
  23. package/vite.config.js +3 -7
  24. package/example/example.css +0 -122
  25. package/example/example.html +0 -152
  26. package/example/github.jpg +0 -0
  27. package/example/poll-h.svg +0 -1
  28. package/src/capture-images.ts +0 -129
  29. package/src/clean-up.ts +0 -50
  30. package/src/default-options.ts +0 -58
  31. package/src/download-images.ts +0 -52
  32. package/src/get-capture-element.test.html +0 -21
  33. package/src/get-capture-element.test.ts +0 -36
  34. package/src/get-capture-element.ts +0 -175
  35. package/src/get-options/get-input-options.test.html +0 -217
  36. package/src/get-options/get-input-options.test.ts +0 -109
  37. package/src/get-options/get-input-options.ts +0 -40
  38. package/src/get-options/get-item-options.ts +0 -46
  39. package/src/get-options/get-wrapper-options.test.html +0 -33
  40. package/src/get-options/get-wrapper-options.test.ts +0 -109
  41. package/src/get-options/get-wrapper-options.ts +0 -84
  42. package/src/get-options/index.ts +0 -28
  43. package/src/image-exporter.ts +0 -108
  44. package/src/types/image.ts +0 -2
  45. package/src/types/index.ts +0 -2
  46. package/src/types/options.ts +0 -69
  47. package/src/utils/convert-to-slug.ts +0 -15
  48. package/src/utils/get-attribute-values.ts +0 -68
  49. package/src/utils/get-date-MMDDYY.ts +0 -11
  50. package/src/utils/ignore-items.ts +0 -11
  51. package/src/utils/index.ts +0 -18
  52. package/src/utils/is-visible.ts +0 -12
  53. package/src/utils/parse-labels.ts +0 -55
  54. package/src/utils/push-to-window.ts +0 -3
  55. package/tests/index.html +0 -88
  56. package/tests/input-tests.html +0 -169
@@ -3,284 +3,6 @@
3
3
  typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global2 = typeof globalThis !== "undefined" ? globalThis : global2 || self, factory(global2["image-exporter"] = {}));
4
4
  })(this, function(exports2) {
5
5
  "use strict";
6
- function pushToWindow(propertyName, value) {
7
- window[propertyName] = value;
8
- }
9
- function getWrapperOptions(options) {
10
- try {
11
- const wrapper = document.querySelector(options.selectors.wrapper);
12
- if (!wrapper) {
13
- new Error("Wrapper element not found");
14
- return options;
15
- }
16
- Object.keys(options.image).forEach((key) => {
17
- const attrValue = wrapper.getAttribute(options.image[key].attributeSelector);
18
- if (attrValue !== null) {
19
- const safeValue = optionSafetyCheck(key, attrValue);
20
- if (safeValue === null) return;
21
- options.image[key].value = safeValue;
22
- }
23
- });
24
- Object.keys(options.zip).forEach((key) => {
25
- const attrValue = wrapper.getAttribute(options.zip[key].attributeSelector);
26
- if (attrValue !== null) {
27
- const safeValue = optionSafetyCheck(key, attrValue);
28
- if (safeValue === null) return;
29
- options.zip[key].value = safeValue;
30
- }
31
- });
32
- if (options.debug) pushToWindow("getWrapperOptionsDebug", options);
33
- return options;
34
- } catch (e) {
35
- console.error("ImageExporter: Error in getWrapperOptions", e);
36
- return options;
37
- }
38
- }
39
- function optionSafetyCheck(key, value) {
40
- if (value === "on") return true;
41
- if (value === "off") return false;
42
- if (key === "scale" || key === "quality") {
43
- if (typeof value === "number") return value;
44
- value = value.trim();
45
- value = parseFloat(value);
46
- if (isNaN(value)) return null;
47
- return value;
48
- }
49
- if (key === "dateInLabel" || key === "scaleInLabel") {
50
- if (typeof value === "boolean") return value;
51
- value = value.trim();
52
- return value === "true";
53
- }
54
- if (key === "format") {
55
- value = value.trim();
56
- if (value === "jpg" || value === "png") return value;
57
- return null;
58
- }
59
- return value;
60
- }
61
- async function handleOptions(optionsType, key) {
62
- const selector = optionsType[key].inputSelector;
63
- const inputElement = document.querySelector(`[${selector}]`);
64
- if (!inputElement) return;
65
- if (inputElement.getAttribute("type") === "checkbox") {
66
- optionsType[key].value = inputElement.checked;
67
- return;
68
- }
69
- if (inputElement.value) {
70
- const safeValue = optionSafetyCheck(key, inputElement.value);
71
- if (safeValue === null) return;
72
- optionsType[key].value = safeValue;
73
- }
74
- }
75
- function getInputOptions(options) {
76
- try {
77
- Promise.all(
78
- Object.keys(options.image).map((key) => handleOptions(options.image, key))
79
- );
80
- Promise.all(Object.keys(options.zip).map((key) => handleOptions(options.zip, key)));
81
- if (options.debug) pushToWindow("getInputOptionsDebug", options);
82
- return options;
83
- } catch (e) {
84
- console.error("ImageExporter: Error in getUserOptions", e);
85
- return options;
86
- }
87
- }
88
- function convertToSlug(input) {
89
- if (!input) {
90
- return null;
91
- }
92
- input = input.toLowerCase();
93
- input = input.replace(/[^a-z0-9_@ -]/g, "");
94
- input = input.replace(/\s+/g, "-");
95
- return input;
96
- }
97
- function isVisible(element) {
98
- if (!(element instanceof HTMLElement)) return false;
99
- if (element.offsetParent === null) return false;
100
- const rect = element.getBoundingClientRect();
101
- return rect.width > 0 && rect.height > 0;
102
- }
103
- function getDateMMDDYY() {
104
- return String((/* @__PURE__ */ new Date()).getMonth() + 1).padStart(2, "0") + String((/* @__PURE__ */ new Date()).getDate()).padStart(2, "0") + (/* @__PURE__ */ new Date()).getFullYear().toString().slice(-2);
105
- }
106
- function parseZipLabel(options) {
107
- const date = getDateMMDDYY();
108
- const zipScale = convertStringToBoolean(options.zip.scaleInLabel.value) ? `_@${options.image.scale.value}x` : "";
109
- const zipDate = convertStringToBoolean(options.zip.dateInLabel.value) ? `_${date}` : "";
110
- const zipNameSlug = convertToSlug(options.zip.label.value);
111
- const zipLabel = `${zipNameSlug}${zipDate}${zipScale}.zip`;
112
- return zipLabel;
113
- }
114
- function parseImageLabel(itemOptions) {
115
- const date = getDateMMDDYY();
116
- const imgScale = itemOptions.image.scaleInLabel.value ? `_@${itemOptions.image.scale.value}x` : "";
117
- const imgDate = itemOptions.image.dateInLabel.value ? `_${date}` : "";
118
- const imgNameSlug = convertToSlug(itemOptions.userSlug) || `img-${itemOptions.id}`;
119
- const imgLabel = `${imgNameSlug}${imgDate}${imgScale}`;
120
- return imgLabel;
121
- }
122
- function convertStringToBoolean(str) {
123
- if (str === "true") {
124
- return true;
125
- } else if (str === "false") {
126
- return false;
127
- }
128
- return str;
129
- }
130
- function isValidUrl(string) {
131
- try {
132
- const url = new URL(string);
133
- if (url.protocol === "data:") {
134
- return false;
135
- }
136
- if (url.protocol !== "http:" && url.protocol !== "https:") {
137
- return false;
138
- }
139
- return true;
140
- } catch (_) {
141
- return false;
142
- }
143
- }
144
- function getItemOptions(element, options, index) {
145
- var _a;
146
- let itemOptions = {
147
- ...options,
148
- id: index,
149
- userSlug: "",
150
- slug: "",
151
- fileName: ""
152
- };
153
- Object.keys(options.image).forEach((key) => {
154
- const attributeValue = element.getAttribute(options.image[key].attributeSelector);
155
- if (attributeValue !== null) {
156
- itemOptions.image[key].value = attributeValue;
157
- }
158
- });
159
- itemOptions.id = index;
160
- itemOptions.userSlug = ((_a = element.querySelector(options.selectors.slug)) == null ? void 0 : _a.textContent) || "";
161
- itemOptions.slug = parseImageLabel(itemOptions);
162
- return itemOptions;
163
- }
164
- function determineOptions(options) {
165
- options = getWrapperOptions(options);
166
- options = getInputOptions(options);
167
- return options;
168
- }
169
- function getCaptureElements(options) {
170
- try {
171
- if (!document.querySelector(options.selectors.capture)) {
172
- console.error("ImageExporter: No capture items found in the wrapper.");
173
- return [];
174
- }
175
- findMultiScaleElements(options);
176
- const elements = Array.from(
177
- document.querySelectorAll(
178
- `${options.selectors.wrapper} ${options.selectors.capture}`
179
- )
180
- );
181
- const visibleElements = elements.filter((element) => isVisible(element));
182
- return visibleElements;
183
- } catch (e) {
184
- console.error("ImageExporter: Error in getCaptureElements", e);
185
- return [];
186
- }
187
- }
188
- function findMultiScaleElements(options) {
189
- try {
190
- const elements = Array.from(
191
- document.querySelectorAll(
192
- `${options.selectors.wrapper} ${options.selectors.capture}`
193
- )
194
- );
195
- if (elements) {
196
- const elementsWithScaleAttribute = elements.filter(
197
- (element) => element.hasAttribute(options.image.scale.attributeSelector)
198
- );
199
- elementsWithScaleAttribute.forEach((element) => {
200
- const scaleValue = element.getAttribute(
201
- options.image.scale.attributeSelector
202
- );
203
- if (scaleValue.includes(",")) {
204
- const scaleArray = scaleValue.split(",").map(Number);
205
- encapsulateMultiScaleElements(options, element, scaleArray, scaleValue);
206
- if (options.debug)
207
- pushToWindow("findMultiScaleElementsTest", element.outerHTML);
208
- }
209
- });
210
- return true;
211
- } else {
212
- return false;
213
- }
214
- } catch (e) {
215
- console.error("ImageExporter: Error in findMultiScaleElements", e);
216
- return;
217
- }
218
- }
219
- function encapsulateMultiScaleElements(options, element, scaleArray, scaleValue) {
220
- try {
221
- element.setAttribute(options.image.scale.attributeSelector, scaleArray[0].toString());
222
- element.setAttribute(options.image.scaleInLabel.attributeSelector, "true");
223
- element.setAttribute("ie-clone-source", scaleValue);
224
- for (let i = 1; i < scaleArray.length; i++) {
225
- const newElement = cloneElementAttributes(options, element);
226
- newElement.setAttribute(
227
- options.image.scale.attributeSelector,
228
- scaleArray[i].toString()
229
- );
230
- newElement.setAttribute(options.image.scaleInLabel.attributeSelector, "true");
231
- if (element.parentNode) {
232
- element.parentNode.insertBefore(newElement, element);
233
- newElement.appendChild(element);
234
- }
235
- }
236
- } catch (e) {
237
- console.error("ImageExporter: Error in encapsulateMultiScaleElements", e);
238
- return;
239
- }
240
- }
241
- function cloneElementAttributes(options, originalElement) {
242
- try {
243
- const clonedElement = document.createElement("div");
244
- const arrayOfPossibleAttributes = Object.keys(options.image).map(
245
- (key) => options.image[key].attributeSelector
246
- );
247
- const { prefix, value } = parseStringAttribute(options.selectors.capture);
248
- clonedElement.setAttribute(prefix, value);
249
- clonedElement.setAttribute("ie-clone", "true");
250
- setExplicitDimensions(originalElement, clonedElement);
251
- Array.from(originalElement.attributes).forEach((attr) => {
252
- if (attr.name in arrayOfPossibleAttributes) {
253
- clonedElement.setAttribute(attr.name, attr.value);
254
- }
255
- });
256
- return clonedElement;
257
- } catch (e) {
258
- console.error("ImageExporter: Error in cloneElementAttributes", e);
259
- return originalElement;
260
- }
261
- }
262
- function parseStringAttribute(attributeValue) {
263
- if (!attributeValue.includes("=")) {
264
- throw new Error("Invalid attribute format. Expected format: [prefix=value]");
265
- }
266
- const attributeArray = attributeValue.split("=");
267
- if (attributeArray.length !== 2) {
268
- throw new Error("Invalid attribute format. Expected format: [prefix=value]");
269
- }
270
- const prefix = attributeArray[0].trim().replace(/^\[|\]$/g, "");
271
- const value = attributeArray[1].trim().replace(/^\[|\]$/g, "").replace(/^'|'$/g, "");
272
- if (!prefix || !value) {
273
- throw new Error("Invalid attribute format. Prefix or value is missing.");
274
- }
275
- return { prefix, value };
276
- }
277
- function setExplicitDimensions(originalElement, clonedElement) {
278
- const originalElementStyle = window.getComputedStyle(originalElement);
279
- const originalElementWidth = originalElementStyle.getPropertyValue("width");
280
- const originalElementHeight = originalElementStyle.getPropertyValue("height");
281
- clonedElement.style.width = originalElementWidth;
282
- clonedElement.style.height = originalElementHeight;
283
- }
284
6
  function resolveUrl(url, baseUrl) {
285
7
  if (url.match(/^[a-z]+:\/\//i)) {
286
8
  return url;
@@ -320,6 +42,18 @@
320
42
  }
321
43
  return arr;
322
44
  }
45
+ let styleProps = null;
46
+ function getStyleProperties(options = {}) {
47
+ if (styleProps) {
48
+ return styleProps;
49
+ }
50
+ if (options.includeStyleProperties) {
51
+ styleProps = options.includeStyleProperties;
52
+ return styleProps;
53
+ }
54
+ styleProps = toArray(window.getComputedStyle(document.documentElement));
55
+ return styleProps;
56
+ }
323
57
  function px(node, styleProperty) {
324
58
  const win = node.ownerDocument.defaultView || window;
325
59
  const val = win.getComputedStyle(node).getPropertyValue(styleProperty);
@@ -379,8 +113,11 @@
379
113
  function createImage(url) {
380
114
  return new Promise((resolve, reject) => {
381
115
  const img = new Image();
382
- img.decode = () => resolve(img);
383
- img.onload = () => resolve(img);
116
+ img.onload = () => {
117
+ img.decode().then(() => {
118
+ requestAnimationFrame(() => resolve(img));
119
+ });
120
+ };
384
121
  img.onerror = reject;
385
122
  img.crossOrigin = "anonymous";
386
123
  img.decoding = "async";
@@ -418,19 +155,19 @@
418
155
  const content = style.getPropertyValue("content");
419
156
  return `${style.cssText} content: '${content.replace(/'|"/g, "")}';`;
420
157
  }
421
- function formatCSSProperties(style) {
422
- return toArray(style).map((name) => {
158
+ function formatCSSProperties(style, options) {
159
+ return getStyleProperties(options).map((name) => {
423
160
  const value = style.getPropertyValue(name);
424
161
  const priority = style.getPropertyPriority(name);
425
162
  return `${name}: ${value}${priority ? " !important" : ""};`;
426
163
  }).join(" ");
427
164
  }
428
- function getPseudoElementStyle(className, pseudo, style) {
165
+ function getPseudoElementStyle(className, pseudo, style, options) {
429
166
  const selector = `.${className}:${pseudo}`;
430
- const cssText = style.cssText ? formatCSSText(style) : formatCSSProperties(style);
167
+ const cssText = style.cssText ? formatCSSText(style) : formatCSSProperties(style, options);
431
168
  return document.createTextNode(`${selector}{${cssText}}`);
432
169
  }
433
- function clonePseudoElement(nativeNode, clonedNode, pseudo) {
170
+ function clonePseudoElement(nativeNode, clonedNode, pseudo, options) {
434
171
  const style = window.getComputedStyle(nativeNode, pseudo);
435
172
  const content = style.getPropertyValue("content");
436
173
  if (content === "" || content === "none") {
@@ -443,12 +180,12 @@
443
180
  return;
444
181
  }
445
182
  const styleElement = document.createElement("style");
446
- styleElement.appendChild(getPseudoElementStyle(className, pseudo, style));
183
+ styleElement.appendChild(getPseudoElementStyle(className, pseudo, style, options));
447
184
  clonedNode.appendChild(styleElement);
448
185
  }
449
- function clonePseudoElements(nativeNode, clonedNode) {
450
- clonePseudoElement(nativeNode, clonedNode, ":before");
451
- clonePseudoElement(nativeNode, clonedNode, ":after");
186
+ function clonePseudoElements(nativeNode, clonedNode, options) {
187
+ clonePseudoElement(nativeNode, clonedNode, ":before", options);
188
+ clonePseudoElement(nativeNode, clonedNode, ":after", options);
452
189
  }
453
190
  const WOFF = "application/font-woff";
454
191
  const JPEG = "image/jpeg";
@@ -564,11 +301,11 @@
564
301
  const dataURL = await resourceToDataURL(poster, contentType, options);
565
302
  return createImage(dataURL);
566
303
  }
567
- async function cloneIFrameElement(iframe) {
304
+ async function cloneIFrameElement(iframe, options) {
568
305
  var _a;
569
306
  try {
570
307
  if ((_a = iframe === null || iframe === void 0 ? void 0 : iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.body) {
571
- return await cloneNode(iframe.contentDocument.body, {}, true);
308
+ return await cloneNode(iframe.contentDocument.body, options, true);
572
309
  }
573
310
  } catch (_b) {
574
311
  }
@@ -582,13 +319,17 @@
582
319
  return cloneVideoElement(node, options);
583
320
  }
584
321
  if (isInstanceOfElement(node, HTMLIFrameElement)) {
585
- return cloneIFrameElement(node);
322
+ return cloneIFrameElement(node, options);
586
323
  }
587
- return node.cloneNode(false);
324
+ return node.cloneNode(isSVGElement(node));
588
325
  }
589
326
  const isSlotElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SLOT";
327
+ const isSVGElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SVG";
590
328
  async function cloneChildren(nativeNode, clonedNode, options) {
591
329
  var _a, _b;
330
+ if (isSVGElement(clonedNode)) {
331
+ return clonedNode;
332
+ }
592
333
  let children = [];
593
334
  if (isSlotElement(nativeNode) && nativeNode.assignedNodes) {
594
335
  children = toArray(nativeNode.assignedNodes());
@@ -607,7 +348,7 @@
607
348
  }), Promise.resolve());
608
349
  return clonedNode;
609
350
  }
610
- function cloneCSSStyle(nativeNode, clonedNode) {
351
+ function cloneCSSStyle(nativeNode, clonedNode, options) {
611
352
  const targetStyle = clonedNode.style;
612
353
  if (!targetStyle) {
613
354
  return;
@@ -617,7 +358,7 @@
617
358
  targetStyle.cssText = sourceStyle.cssText;
618
359
  targetStyle.transformOrigin = sourceStyle.transformOrigin;
619
360
  } else {
620
- toArray(sourceStyle).forEach((name) => {
361
+ getStyleProperties(options).forEach((name) => {
621
362
  let value = sourceStyle.getPropertyValue(name);
622
363
  if (name === "font-size" && value.endsWith("px")) {
623
364
  const reducedFont = Math.floor(parseFloat(value.substring(0, value.length - 2))) - 0.1;
@@ -650,10 +391,10 @@
650
391
  }
651
392
  }
652
393
  }
653
- function decorate(nativeNode, clonedNode) {
394
+ function decorate(nativeNode, clonedNode, options) {
654
395
  if (isInstanceOfElement(clonedNode, Element)) {
655
- cloneCSSStyle(nativeNode, clonedNode);
656
- clonePseudoElements(nativeNode, clonedNode);
396
+ cloneCSSStyle(nativeNode, clonedNode, options);
397
+ clonePseudoElements(nativeNode, clonedNode, options);
657
398
  cloneInputValue(nativeNode, clonedNode);
658
399
  cloneSelectValue(nativeNode, clonedNode);
659
400
  }
@@ -699,7 +440,7 @@
699
440
  if (!isRoot && options.filter && !options.filter(node)) {
700
441
  return null;
701
442
  }
702
- return Promise.resolve(node).then((clonedNode) => cloneSingleNode(clonedNode, options)).then((clonedNode) => cloneChildren(node, clonedNode, options)).then((clonedNode) => decorate(node, clonedNode)).then((clonedNode) => ensureSVGSymbols(clonedNode, options));
443
+ return Promise.resolve(node).then((clonedNode) => cloneSingleNode(clonedNode, options)).then((clonedNode) => cloneChildren(node, clonedNode, options)).then((clonedNode) => decorate(node, clonedNode, options)).then((clonedNode) => ensureSVGSymbols(clonedNode, options));
703
444
  }
704
445
  const URL_REGEX = /url\((['"]?)([^'"]+?)\1\)/g;
705
446
  const URL_WITH_FORMAT_REGEX = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g;
@@ -765,12 +506,8 @@
765
506
  return false;
766
507
  }
767
508
  async function embedBackground(clonedNode, options) {
768
- if (!await embedProp("background", clonedNode, options)) {
769
- await embedProp("background-image", clonedNode, options);
770
- }
771
- if (!await embedProp("mask", clonedNode, options)) {
772
- await embedProp("mask-image", clonedNode, options);
773
- }
509
+ await embedProp("background", clonedNode, options) || await embedProp("background-image", clonedNode, options);
510
+ await embedProp("mask", clonedNode, options) || await embedProp("-webkit-mask", clonedNode, options) || await embedProp("mask-image", clonedNode, options) || await embedProp("-webkit-mask-image", clonedNode, options);
774
511
  }
775
512
  async function embedImageNode(clonedNode, options) {
776
513
  const isImageElement = isInstanceOfElement(clonedNode, HTMLImageElement);
@@ -781,7 +518,13 @@
781
518
  const dataURL = await resourceToDataURL(url, getMimeType(url), options);
782
519
  await new Promise((resolve, reject) => {
783
520
  clonedNode.onload = resolve;
784
- clonedNode.onerror = reject;
521
+ clonedNode.onerror = options.onImageErrorHandler ? (...attributes) => {
522
+ try {
523
+ resolve(options.onImageErrorHandler(...attributes));
524
+ } catch (error) {
525
+ reject(error);
526
+ }
527
+ } : reject;
785
528
  const image = clonedNode;
786
529
  if (image.decode) {
787
530
  image.decode = resolve;
@@ -920,7 +663,7 @@
920
663
  const inline = styleSheets.find((a) => a.href == null) || document.styleSheets[0];
921
664
  if (sheet.href != null) {
922
665
  deferreds.push(fetchCSS(sheet.href).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => {
923
- inline.insertRule(rule, sheet.cssRules.length);
666
+ inline.insertRule(rule, inline.cssRules.length);
924
667
  })).catch((err) => {
925
668
  console.error("Error loading remote stylesheet", err);
926
669
  }));
@@ -955,9 +698,29 @@
955
698
  const cssRules = await getCSSRules(styleSheets, options);
956
699
  return getWebFontRules(cssRules);
957
700
  }
701
+ function normalizeFontFamily(font) {
702
+ return font.trim().replace(/["']/g, "");
703
+ }
704
+ function getUsedFonts(node) {
705
+ const fonts = /* @__PURE__ */ new Set();
706
+ function traverse(node2) {
707
+ const fontFamily = node2.style.fontFamily || getComputedStyle(node2).fontFamily;
708
+ fontFamily.split(",").forEach((font) => {
709
+ fonts.add(normalizeFontFamily(font));
710
+ });
711
+ Array.from(node2.children).forEach((child) => {
712
+ if (child instanceof HTMLElement) {
713
+ traverse(child);
714
+ }
715
+ });
716
+ }
717
+ traverse(node);
718
+ return fonts;
719
+ }
958
720
  async function getWebFontCSS(node, options) {
959
721
  const rules = await parseWebFontRules(node, options);
960
- const cssTexts = await Promise.all(rules.map((rule) => {
722
+ const usedFonts = getUsedFonts(node);
723
+ const cssTexts = await Promise.all(rules.filter((rule) => usedFonts.has(normalizeFontFamily(rule.style.fontFamily))).map((rule) => {
961
724
  const baseUrl = rule.parentStyleSheet ? rule.parentStyleSheet.href : null;
962
725
  return embedResources(rule.cssText, baseUrl, options);
963
726
  }));
@@ -1016,173 +779,72 @@
1016
779
  const canvas = await toCanvas(node, options);
1017
780
  return canvas.toDataURL("image/jpeg", options.quality || 1);
1018
781
  }
1019
- async function proxyCSS(options) {
1020
- try {
1021
- const css = document.querySelectorAll('link[rel="stylesheet"]');
1022
- for (let stylesheetElement of css) {
1023
- let stylesheetURL = stylesheetElement.getAttribute("href");
1024
- if (stylesheetURL && !stylesheetURL.startsWith("data:") && isValidUrl(stylesheetURL) && !stylesheetURL.startsWith(options.corsProxyBaseUrl)) {
1025
- const url = options.corsProxyBaseUrl + encodeURIComponent(stylesheetURL);
1026
- try {
1027
- const response = await fetch(url);
1028
- const css2 = await response.text();
1029
- const styleEl = document.createElement("style");
1030
- styleEl.textContent = css2;
1031
- document.head.appendChild(styleEl);
1032
- stylesheetElement.remove();
1033
- } catch (error) {
1034
- console.error("Error fetching CSS:", error);
1035
- }
1036
- }
1037
- }
1038
- } catch (e) {
1039
- console.error("ImageExporter: Error in proxyCSS", e);
1040
- }
1041
- }
1042
- async function proxyImages(options) {
1043
- try {
1044
- const links = document.querySelectorAll("link");
1045
- links.forEach((link) => {
1046
- link.setAttribute("crossorigin", "anonymous");
1047
- });
1048
- const wrapper = document.querySelector(options.selectors.wrapper);
1049
- if (!wrapper) {
1050
- console.error("ImageExporter: Wrapper element not found.");
1051
- return;
782
+ function handleFileNames(imageOptions, filenames) {
783
+ let proposedFilename = imageOptions.label;
784
+ if (imageOptions.includeScaleInLabel) proposedFilename += `_@${imageOptions.scale}x`;
785
+ proposedFilename += `.${imageOptions.format}`;
786
+ if (!filenames.includes(proposedFilename)) {
787
+ filenames.push(proposedFilename);
788
+ return proposedFilename;
789
+ }
790
+ const numberPattern = /-(\d+)$/;
791
+ const match = proposedFilename.match(numberPattern);
792
+ if (match) {
793
+ const baseFilename = proposedFilename.replace(numberPattern, "");
794
+ let counter = parseInt(match[1], 10);
795
+ while (filenames.includes(`${baseFilename}-${counter}`)) {
796
+ counter++;
1052
797
  }
1053
- const images = Array.from(wrapper.querySelectorAll("img"));
1054
- const srcMap = /* @__PURE__ */ new Map();
1055
- images.forEach((img) => {
1056
- const srcs = srcMap.get(img.src) || [];
1057
- srcs.push(img);
1058
- srcMap.set(img.src, srcs);
1059
- });
1060
- for (const [src, duplicates] of srcMap) {
1061
- if (!isValidUrl(src) || options.corsProxyBaseUrl && src.startsWith(options.corsProxyBaseUrl)) {
1062
- continue;
1063
- }
1064
- if (duplicates.length > 1) {
1065
- try {
1066
- const response = await fetch(
1067
- options.corsProxyBaseUrl + encodeURIComponent(src)
1068
- );
1069
- const blob = await response.blob();
1070
- const dataURL = await blobToDataURL(blob);
1071
- duplicates.forEach((dupImg) => {
1072
- if (dupImg.src === src) {
1073
- dupImg.src = dataURL;
1074
- }
1075
- });
1076
- } catch (error) {
1077
- console.error("Error fetching image:", error);
1078
- }
1079
- } else {
1080
- images.forEach((img) => {
1081
- if (img.src === src) {
1082
- img.src = options.corsProxyBaseUrl + encodeURIComponent(src);
1083
- }
1084
- });
1085
- }
1086
- }
1087
- } catch (e) {
1088
- console.error("ImageExporter: Error in proxyImages", e);
1089
- return;
1090
- }
1091
- }
1092
- function blobToDataURL(blob) {
1093
- return new Promise((resolve, reject) => {
1094
- const reader = new FileReader();
1095
- reader.onloadend = () => resolve(reader.result);
1096
- reader.onerror = reject;
1097
- reader.readAsDataURL(blob);
1098
- });
1099
- }
1100
- async function runCorsProxy(options) {
1101
- try {
1102
- if (!options.corsProxyBaseUrl || !isValidUrl(options.corsProxyBaseUrl)) return;
1103
- await proxyCSS(options);
1104
- await proxyImages(options);
1105
- return;
1106
- } catch (e) {
1107
- console.error("ImageExporter: Error in runCorsProxy", e);
1108
- }
1109
- }
1110
- function ignoreFilter(options) {
1111
- return (node) => {
1112
- if (!(node instanceof HTMLElement)) {
1113
- throw new Error("The provided node is not an HTMLElement");
798
+ const newFilename = `${baseFilename}-${counter}`;
799
+ filenames.push(newFilename);
800
+ return newFilename;
801
+ } else {
802
+ let counter = 2;
803
+ while (filenames.includes(`${proposedFilename}-${counter}`)) {
804
+ counter++;
1114
805
  }
1115
- return !node.matches(options.selectors.ignore);
1116
- };
1117
- }
1118
- async function captureImages(options, captureElements) {
1119
- try {
1120
- await runCorsProxy(options);
1121
- const images = await Promise.all(
1122
- captureElements.map(
1123
- (element, index) => captureImage(
1124
- element,
1125
- getItemOptions(element, options, index + 1),
1126
- ignoreFilter(options)
1127
- )
1128
- )
1129
- );
1130
- return images;
1131
- } catch (e) {
1132
- console.error("ImageExporter: Error in captureImages", e);
1133
- return [];
806
+ const newFilename = `${proposedFilename}-${counter}`;
807
+ filenames.push(newFilename);
808
+ return newFilename;
1134
809
  }
1135
810
  }
1136
- async function captureImage(element, itemOptions, ignoreFilter2) {
811
+ async function captureElement(element, imageOptions, filenames) {
1137
812
  try {
1138
- itemOptions.slug = ensureUniqueSlug(itemOptions.slug);
1139
813
  let dataURL = "";
1140
814
  let htmlToImageOptions = {
1141
815
  // Ensure quality is a number
1142
- quality: itemOptions.image.quality.value,
816
+ quality: imageOptions.quality,
1143
817
  // Ensure scale is a number
1144
- pixelRatio: itemOptions.image.scale.value
1145
- // Function that returns false if the element should be ignored
1146
- // filter: ignoreFilter,
818
+ pixelRatio: imageOptions.scale,
819
+ // Ignores elements with data-ignore-capture attribute
820
+ filter
1147
821
  };
1148
- switch (itemOptions.image.format.value.toLowerCase()) {
822
+ switch (imageOptions.format) {
1149
823
  case "jpg":
1150
824
  dataURL = await toJpeg(element, htmlToImageOptions);
1151
- itemOptions.fileName = `${itemOptions.slug}.jpg`;
1152
825
  break;
1153
826
  case "png":
1154
- default:
1155
827
  dataURL = await toPng(element, htmlToImageOptions);
1156
- itemOptions.fileName = `${itemOptions.slug}.png`;
828
+ break;
829
+ case "svg":
830
+ dataURL = await toSvg(element, htmlToImageOptions);
1157
831
  break;
1158
832
  }
1159
- return [dataURL, itemOptions.fileName];
1160
- } catch (e) {
1161
- console.error("ImageExporter: Error in captureImage", e);
1162
- return ["", ""];
833
+ return {
834
+ dataURL,
835
+ fileName: handleFileNames(imageOptions, filenames)
836
+ };
837
+ } catch (error) {
838
+ console.error("ImageExporter: Error in captureImage", error);
839
+ return { dataURL: "", fileName: "" };
1163
840
  }
1164
841
  }
1165
- let usedSlugs = [];
1166
- function ensureUniqueSlug(slug) {
1167
- try {
1168
- if (usedSlugs.includes(slug)) {
1169
- let counter = 1;
1170
- let newSlug = `${slug}-${counter}`;
1171
- while (usedSlugs.includes(newSlug)) {
1172
- counter++;
1173
- newSlug = `${slug}-${counter}`;
1174
- }
1175
- usedSlugs.push(newSlug);
1176
- return newSlug;
1177
- } else {
1178
- usedSlugs.push(slug);
1179
- return slug;
1180
- }
1181
- } catch (e) {
1182
- console.error("ImageExporter: Error in ensureUniqueSlug", e);
1183
- return slug;
842
+ const filter = (node) => {
843
+ if (node instanceof HTMLElement) {
844
+ return !node.hasAttribute("data-ignore-capture");
1184
845
  }
1185
- }
846
+ return true;
847
+ };
1186
848
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
1187
849
  function getDefaultExportFromCjs(x) {
1188
850
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
@@ -1312,15 +974,15 @@
1312
974
  var jszip_min = { exports: {} };
1313
975
  /*!
1314
976
 
1315
- JSZip v3.10.1 - A JavaScript class for generating and reading zip files
1316
- <http://stuartk.com/jszip>
977
+ JSZip v3.10.1 - A JavaScript class for generating and reading zip files
978
+ <http://stuartk.com/jszip>
1317
979
 
1318
- (c) 2009-2016 Stuart Knightley <stuart [at] stuartk.com>
1319
- Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown.
980
+ (c) 2009-2016 Stuart Knightley <stuart [at] stuartk.com>
981
+ Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown.
1320
982
 
1321
- JSZip uses the library pako released under the MIT license :
1322
- https://github.com/nodeca/pako/blob/main/LICENSE
1323
- */
983
+ JSZip uses the library pako released under the MIT license :
984
+ https://github.com/nodeca/pako/blob/main/LICENSE
985
+ */
1324
986
  (function(module2, exports3) {
1325
987
  !function(e) {
1326
988
  module2.exports = e();
@@ -3627,187 +3289,412 @@
3627
3289
  })(jszip_min);
3628
3290
  var jszip_minExports = jszip_min.exports;
3629
3291
  const JSZip = /* @__PURE__ */ getDefaultExportFromCjs(jszip_minExports);
3630
- async function downloadImages(images, options) {
3292
+ async function downloadImages(images, config) {
3631
3293
  if (images.length === 1) {
3632
- const [dataURL, fileName] = images[0];
3633
- await download(dataURL, fileName);
3294
+ const image = images[0];
3295
+ await download(image.dataURL, image.fileName);
3634
3296
  } else if (images.length > 1) {
3635
- const zipName = parseZipLabel(options);
3636
- const zipBlob = await zipUpImages(images);
3637
- await download(zipBlob, zipName);
3297
+ const imagesBlob = await zipUpImages(images);
3298
+ if (imagesBlob) await download(imagesBlob, parseLabel(config));
3638
3299
  }
3639
3300
  }
3640
3301
  async function zipUpImages(images) {
3641
3302
  const zip = new JSZip();
3642
- images.forEach(([dataURL, filename]) => {
3643
- const content = dataURL.split(",")[1];
3644
- zip.file(filename, content, { base64: true });
3645
- });
3646
3303
  try {
3647
- const zipBlob = await zip.generateAsync({ type: "blob" });
3648
- return zipBlob;
3304
+ images.forEach((image) => {
3305
+ const content = image.dataURL.split(",")[1];
3306
+ zip.file(image.fileName, content, { base64: true });
3307
+ });
3308
+ } catch (error) {
3309
+ console.error("Image Exporter - Error adding images to ZIP:", error);
3310
+ return;
3311
+ }
3312
+ try {
3313
+ const imagesBlob = await zip.generateAsync({ type: "blob" });
3314
+ return imagesBlob;
3649
3315
  } catch (error) {
3650
- console.error("Error creating ZIP:", error);
3651
- throw error;
3316
+ console.error("Image Exporter - Error generating ZIP:", error);
3317
+ return;
3652
3318
  }
3653
3319
  }
3654
- const defaultOptions = {
3655
- corsProxyBaseUrl: "",
3656
- downloadImages: true,
3657
- selectors: {
3658
- wrapper: "[ie='wrapper']",
3659
- capture: "[ie='capture']",
3660
- trigger: "[ie='trigger']",
3661
- slug: "[ie='slug']",
3662
- ignore: "[ie='ignore']"
3663
- },
3664
- image: {
3665
- scale: {
3666
- value: 1,
3667
- attributeSelector: "ie-scale",
3668
- inputSelector: "ie-scale-input"
3669
- },
3670
- quality: {
3671
- value: 1,
3672
- attributeSelector: "ie-quality",
3673
- inputSelector: "ie-quality-input"
3674
- },
3675
- format: {
3676
- value: "jpg",
3677
- attributeSelector: "ie-format",
3678
- inputSelector: "ie-format-input"
3679
- },
3680
- dateInLabel: {
3681
- value: true,
3682
- attributeSelector: "ie-img-label-date",
3683
- inputSelector: "ie-img-label-date-input"
3684
- },
3685
- scaleInLabel: {
3686
- value: true,
3687
- attributeSelector: "ie-img-label-scale",
3688
- inputSelector: "ie-img-label-scale-input"
3320
+ function parseLabel(config) {
3321
+ try {
3322
+ let label = config.zipLabel;
3323
+ label = label.replace(/\s+/g, "-");
3324
+ return label.replace(/[^a-zA-Z0-9-_]/g, "");
3325
+ } catch (error) {
3326
+ console.error(error);
3327
+ return "images";
3328
+ }
3329
+ }
3330
+ const log = {
3331
+ info: (...messages) => logAction(messages, "info"),
3332
+ error: (...messages) => logAction(messages, "error"),
3333
+ verbose: (...messages) => logAction(messages, "verbose"),
3334
+ progress: (progress, total) => logProgress(progress, total)
3335
+ };
3336
+ async function logAction(messages, type = "info") {
3337
+ const combinedMessage = messages.map((msg) => typeof msg === "object" ? JSON.stringify(msg) : msg).join(" ");
3338
+ switch (type) {
3339
+ case "info":
3340
+ if (loggingLevel === "info" || loggingLevel === "verbose") {
3341
+ console.log(...messages);
3342
+ }
3343
+ break;
3344
+ case "error":
3345
+ if (loggingLevel === "error" || loggingLevel === "verbose") {
3346
+ console.error(...messages);
3347
+ }
3348
+ break;
3349
+ case "verbose":
3350
+ if (loggingLevel === "verbose") {
3351
+ console.log(...messages);
3352
+ }
3353
+ break;
3354
+ }
3355
+ if (windowLogging) window.imageExporterLogs.push({ message: combinedMessage, type });
3356
+ }
3357
+ async function logProgress(progress, total) {
3358
+ if (windowLogging) {
3359
+ window.imageExporterProgress.push([progress, total]);
3360
+ }
3361
+ }
3362
+ window.imageExporterLogs = [];
3363
+ window.imageExporterProgress = [];
3364
+ function isValidUrl(string) {
3365
+ try {
3366
+ const url = new URL(string);
3367
+ if (url.protocol === "data:") {
3368
+ return false;
3689
3369
  }
3690
- },
3691
- zip: {
3692
- label: {
3693
- value: "images",
3694
- attributeSelector: "ie-zip-label",
3695
- inputSelector: "ie-zip-label-input"
3696
- },
3697
- dateInLabel: {
3698
- value: true,
3699
- attributeSelector: "ie-zip-label-date",
3700
- inputSelector: "ie-zip-label-date-input"
3701
- },
3702
- scaleInLabel: {
3703
- value: true,
3704
- attributeSelector: "ie-zip-label-scale",
3705
- inputSelector: "ie-zip-label-scale-input"
3370
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
3371
+ return false;
3706
3372
  }
3707
- },
3708
- debug: false
3373
+ return true;
3374
+ } catch (_) {
3375
+ return false;
3376
+ }
3377
+ }
3378
+ async function proxyCSS(config) {
3379
+ const stylesheetElements = document.querySelectorAll('link[rel="stylesheet"]');
3380
+ log.verbose("stylesheet elements to proxy", stylesheetElements.length);
3381
+ for (let stylesheetElement of stylesheetElements) {
3382
+ const stylesheetURL = stylesheetElement.getAttribute("href");
3383
+ if (!stylesheetURL) continue;
3384
+ if (stylesheetURL.startsWith("data:")) continue;
3385
+ if (stylesheetURL.startsWith(config.corsProxyBaseUrl)) continue;
3386
+ if (!isValidUrl(stylesheetURL)) continue;
3387
+ stylesheetElement.setAttribute("crossorigin", "anonymous");
3388
+ const proxiedURL = config.corsProxyBaseUrl + encodeURIComponent(stylesheetURL);
3389
+ try {
3390
+ const response = await fetch(proxiedURL);
3391
+ let cssContent = await response.text();
3392
+ cssContent = cssContent.replace(
3393
+ /url\(['"]?(https?:\/\/[^'")\s]+)['"]?\)/g,
3394
+ (match, url) => {
3395
+ if (url.startsWith(config.corsProxyBaseUrl)) return match;
3396
+ return `url("${config.corsProxyBaseUrl}${encodeURIComponent(url)}")`;
3397
+ }
3398
+ );
3399
+ const styleElement = document.createElement("style");
3400
+ styleElement.textContent = cssContent;
3401
+ styleElement.setAttribute(
3402
+ "original-link-element",
3403
+ encodeURIComponent(stylesheetElement.outerHTML)
3404
+ );
3405
+ stylesheetElement.insertAdjacentElement("afterend", styleElement);
3406
+ stylesheetElement.remove();
3407
+ log.verbose("Proxied: ", stylesheetURL);
3408
+ } catch (error) {
3409
+ console.error("Error fetching CSS:", error);
3410
+ }
3411
+ }
3412
+ }
3413
+ async function proxyImages(config, elements) {
3414
+ try {
3415
+ const elementArray = Array.from(elements);
3416
+ if (!elementArray.length) return;
3417
+ log.verbose("images to proxy", elementArray.length);
3418
+ for (const element of elementArray) {
3419
+ const images = Array.from(element.querySelectorAll("img"));
3420
+ for (const img of images) {
3421
+ if (isValidUrl(img.src) && !img.src.startsWith(config.corsProxyBaseUrl)) {
3422
+ img.setAttribute("original-src", img.src);
3423
+ img.src = config.corsProxyBaseUrl + encodeURIComponent(img.src);
3424
+ log.verbose("Proxied: ", img.src);
3425
+ }
3426
+ }
3427
+ }
3428
+ } catch (e) {
3429
+ console.error("ImageExporter: Error in proxyImages", e);
3430
+ return;
3431
+ }
3432
+ }
3433
+ async function runCorsProxy(config, elements) {
3434
+ try {
3435
+ log.verbose("running CORS proxy");
3436
+ if (!config.corsProxyBaseUrl || !isValidUrl(config.corsProxyBaseUrl)) return;
3437
+ await proxyCSS(config);
3438
+ await proxyImages(config, elements);
3439
+ } catch (e) {
3440
+ console.error("ImageExporter: Error in runCorsProxy", e);
3441
+ }
3442
+ }
3443
+ async function cleanUpCorsProxy() {
3444
+ await restoreCSS();
3445
+ await restoreImages();
3446
+ }
3447
+ async function restoreCSS() {
3448
+ const styleElements = document.querySelectorAll("style[original-link-element]");
3449
+ for (let styleElement of styleElements) {
3450
+ const originalLinkElementHTML = decodeURIComponent(
3451
+ styleElement.getAttribute("original-link-element")
3452
+ );
3453
+ const tempContainer = document.createElement("div");
3454
+ tempContainer.innerHTML = originalLinkElementHTML;
3455
+ styleElement.parentNode.insertBefore(tempContainer.firstChild, styleElement);
3456
+ styleElement.remove();
3457
+ log.verbose("Restored: ", originalLinkElementHTML);
3458
+ }
3459
+ }
3460
+ async function restoreImages() {
3461
+ const imageElements = document.querySelectorAll(
3462
+ "img[original-src]"
3463
+ );
3464
+ for (let imageElement of imageElements) {
3465
+ const originalSrc = imageElement.getAttribute("original-src");
3466
+ imageElement.src = originalSrc;
3467
+ imageElement.removeAttribute("original-src");
3468
+ log.verbose("Restored: ", originalSrc);
3469
+ }
3470
+ }
3471
+ const corsProxy = {
3472
+ run: runCorsProxy,
3473
+ cleanUp: cleanUpCorsProxy
3709
3474
  };
3710
- function cleanUp(options, captureElements) {
3711
- const sourceElements = document.querySelectorAll("[ie-clone-source]");
3712
- if (sourceElements) {
3713
- sourceElements.forEach((sourceElement) => {
3714
- const attributeValue = sourceElement.getAttribute("ie-clone-source");
3715
- sourceElement.removeAttribute("ie-clone-source");
3716
- if (attributeValue) {
3717
- sourceElement.setAttribute(options.image.scale.attributeSelector, attributeValue);
3718
- }
3719
- let parentElement = sourceElement.parentElement;
3720
- let lastCloneParent = null;
3721
- while (parentElement) {
3722
- if (parentElement.hasAttribute("ie-clone")) {
3723
- lastCloneParent = parentElement;
3724
- }
3725
- parentElement = parentElement.parentElement;
3726
- }
3727
- if (lastCloneParent) {
3728
- const nextSibling = lastCloneParent.nextElementSibling;
3729
- if (nextSibling) {
3730
- nextSibling.parentNode.insertBefore(sourceElement, nextSibling);
3731
- } else {
3732
- lastCloneParent.parentNode.appendChild(sourceElement);
3475
+ async function getImageOptions(element, config) {
3476
+ return {
3477
+ label: parseLabel2(),
3478
+ format: parseFormat(),
3479
+ scale: parseScale(),
3480
+ quality: parseQuality(),
3481
+ includeScaleInLabel: parseIncludeScaleInLabel()
3482
+ };
3483
+ function parseLabel2() {
3484
+ try {
3485
+ const label = element.dataset.label || config.defaultImageLabel;
3486
+ if (label === "") return config.defaultImageLabel;
3487
+ const regex = /[^a-zA-Z0-9-_]/g;
3488
+ return label.replace(regex, "");
3489
+ } catch (error) {
3490
+ console.error(error);
3491
+ return config.defaultImageLabel;
3492
+ }
3493
+ }
3494
+ function parseFormat() {
3495
+ try {
3496
+ let format = element.dataset.format || config.format;
3497
+ format = format.trim().toLowerCase();
3498
+ if (format === "jpg" || format === "png" || format === "svg") {
3499
+ return format;
3500
+ } else {
3501
+ throw new Error(
3502
+ `ImageExporter: provided format is not valid.
3503
+ Provided: ${format}
3504
+ Element: ${element}
3505
+ Accepted values: jpg, png, svg
3506
+ Defaulting to: ${config.format}`
3507
+ );
3508
+ }
3509
+ } catch (error) {
3510
+ console.error(error);
3511
+ return config.format;
3512
+ }
3513
+ }
3514
+ function parseScale() {
3515
+ try {
3516
+ const scaleAsString = element.dataset.scale;
3517
+ if (!scaleAsString) return config.scale;
3518
+ if (scaleAsString.includes(",")) {
3519
+ const scales = scaleAsString.trim().split(",").map((scale) => parseFloat(scale));
3520
+ if (scales.some((scale) => isNaN(scale))) {
3521
+ throw new Error(
3522
+ `ImageExporter: provided scale is not valid.
3523
+ Provided: ${scaleAsString}
3524
+ Element: ${element}
3525
+ Accepted values: number or csv numbers e.g. (1,2)
3526
+ Defaulting to ${config.scale}`
3527
+ );
3733
3528
  }
3734
- lastCloneParent.remove();
3529
+ return scales;
3530
+ } else {
3531
+ const scaleAsNumber = parseFloat(scaleAsString.trim());
3532
+ if (isNaN(scaleAsNumber)) {
3533
+ throw new Error(
3534
+ `ImageExporter: provided scale is not valid.
3535
+ Provided: ${scaleAsString}
3536
+ Element: ${element}
3537
+ Accepted values: number or csv numbers e.g. (1,2)
3538
+ Defaulting to: ${config.scale}`
3539
+ );
3540
+ }
3541
+ return scaleAsNumber;
3735
3542
  }
3736
- });
3543
+ } catch (error) {
3544
+ console.error(error);
3545
+ return config.scale;
3546
+ }
3547
+ }
3548
+ function parseQuality() {
3549
+ try {
3550
+ const qualityAsString = element.dataset.quality;
3551
+ if (!qualityAsString) return config.quality;
3552
+ const qualityAsNumber = parseFloat(qualityAsString.trim());
3553
+ if (isNaN(qualityAsNumber)) {
3554
+ throw new Error(
3555
+ `ImageExporter: provided quality is not valid.
3556
+ Provided: ${qualityAsString}
3557
+ Element: ${element}
3558
+ Accepted values: number
3559
+ Defaulting to: ${config.quality}`
3560
+ );
3561
+ }
3562
+ return qualityAsNumber;
3563
+ } catch (error) {
3564
+ console.error(error);
3565
+ return config.quality;
3566
+ }
3567
+ }
3568
+ function parseIncludeScaleInLabel() {
3569
+ try {
3570
+ let includeScaleInLabel = element.dataset.includeScaleInLabel;
3571
+ if (!includeScaleInLabel) return config.includeScaleInLabel;
3572
+ includeScaleInLabel = includeScaleInLabel.trim();
3573
+ if (includeScaleInLabel === "true" || includeScaleInLabel === "false") {
3574
+ return includeScaleInLabel === "true";
3575
+ } else {
3576
+ throw new Error(
3577
+ `ImageExporter: provided includeScaleInLabel is not valid.
3578
+ Provided: ${includeScaleInLabel}
3579
+ Element: ${element}
3580
+ Accepted values: true or false
3581
+ Defaulting to: ${config.includeScaleInLabel}`
3582
+ );
3583
+ }
3584
+ } catch (error) {
3585
+ console.error(error);
3586
+ return config.includeScaleInLabel;
3587
+ }
3737
3588
  }
3738
3589
  }
3739
- class ImageExporter {
3740
- constructor(userOptions = {}) {
3741
- this.options = { ...defaultOptions, ...userOptions };
3742
- }
3743
- /**
3744
- * Captures images from all elements specified in the options.
3745
- * If downloadImages is set to true, the images will be downloaded.
3746
- *
3747
- * @returns {types.Image[]} An array of captured images.
3748
- */
3749
- async captureAll() {
3750
- this.options = determineOptions(this.options);
3751
- const captureElements = getCaptureElements(this.options);
3752
- const images = await captureImages(this.options, captureElements);
3753
- if (this.options.downloadImages) downloadImages(images, this.options);
3754
- cleanUp(this.options);
3755
- return images;
3590
+ const defaultImageOptions = {
3591
+ label: "image",
3592
+ format: "jpg",
3593
+ scale: 1,
3594
+ quality: 1,
3595
+ includeScaleInLabel: false
3596
+ };
3597
+ const defaultConfig = {
3598
+ ...defaultImageOptions,
3599
+ downloadImages: true,
3600
+ defaultImageLabel: "image",
3601
+ zipLabel: "images",
3602
+ corsProxyBaseUrl: "",
3603
+ enableWindowLogging: true,
3604
+ loggingLevel: "none"
3605
+ };
3606
+ function removeHiddenElements(elements) {
3607
+ elements = Array.from(elements);
3608
+ return elements.filter((element) => isVisible(element));
3609
+ }
3610
+ function isVisible(element) {
3611
+ const computedStyle = window.getComputedStyle(element);
3612
+ return element.offsetParent !== null && element.style.display !== "none" && computedStyle.visibility === "visible" && computedStyle.opacity !== "0" && computedStyle.width !== "0" && computedStyle.height !== "0";
3613
+ }
3614
+ async function determineTotalElements(elements) {
3615
+ try {
3616
+ let totalElements = 0;
3617
+ for (const element of elements) {
3618
+ const scaleAsString = element.dataset.scale;
3619
+ if (!scaleAsString) continue;
3620
+ if (scaleAsString.includes(",")) {
3621
+ const scales = scaleAsString.trim().split(",").map((scale) => parseFloat(scale));
3622
+ if (scales.some((scale) => isNaN(scale))) continue;
3623
+ totalElements += scales.length;
3624
+ } else {
3625
+ const scaleAsNumber = parseFloat(scaleAsString.trim());
3626
+ if (isNaN(scaleAsNumber)) {
3627
+ continue;
3628
+ } else {
3629
+ totalElements++;
3630
+ }
3631
+ }
3632
+ }
3633
+ return totalElements;
3634
+ } catch (error) {
3635
+ return 1;
3756
3636
  }
3757
- /**
3758
- * Captures an image from a single element.
3759
- * If downloadImages is set to true, the image will be downloaded.
3760
- *
3761
- * If multiscale elements are found,
3762
- * the element will be cloned and captured at each scale.
3763
- */
3764
- async captureElement(element) {
3765
- this.options = determineOptions(this.options);
3766
- await runCorsProxy(this.options);
3767
- ignoreFilter(this.options);
3768
- const multiScale = findMultiScaleElements(this.options);
3769
- if (multiScale) {
3770
- const elements = Array.from(
3771
- document.querySelectorAll(
3772
- "[ie-clone], [ie-clone-source]"
3773
- )
3637
+ }
3638
+ let windowLogging = true;
3639
+ let loggingLevel = "none";
3640
+ async function capture(elements, userConfig) {
3641
+ try {
3642
+ const config = { ...defaultConfig, ...userConfig };
3643
+ windowLogging = config.enableWindowLogging;
3644
+ loggingLevel = config.loggingLevel;
3645
+ log.verbose("config", config);
3646
+ if (elements instanceof HTMLElement) elements = [elements];
3647
+ const originalLength = elements.length;
3648
+ elements = removeHiddenElements(elements);
3649
+ const totalElements = await determineTotalElements(elements);
3650
+ if (originalLength !== elements.length)
3651
+ log.verbose(
3652
+ "skipping capture of hidden elements: ",
3653
+ originalLength - elements.length
3774
3654
  );
3775
- const images = await captureImages(this.options, elements);
3776
- if (this.options.downloadImages) downloadImages(images, this.options);
3777
- cleanUp(this.options);
3778
- return images;
3779
- }
3780
- const image = await captureImage(
3781
- element,
3782
- getItemOptions(element, this.options, 1)
3783
- );
3784
- if (this.options.downloadImages) downloadImages([image], this.options);
3785
- cleanUp(this.options);
3786
- return image;
3787
- }
3788
- /**
3789
- * Adds a click event listener to the trigger element.
3790
- * If no element is provided, the captureAll method will be run on click.
3791
- * If an element is provided, provided element will be captured on click.
3792
- */
3793
- addTrigger(triggerSelector, element = null) {
3794
- const triggerElement = document.querySelector(triggerSelector);
3795
- if (!triggerElement) throw new Error("Trigger element not found");
3796
- if (!element) {
3797
- triggerElement.addEventListener("click", () => {
3798
- this.captureAll();
3799
- });
3800
- } else {
3801
- triggerElement.addEventListener("click", () => {
3802
- this.captureElement(element);
3803
- });
3655
+ log.verbose("element to capture", elements.length);
3656
+ if (userConfig.corsProxyBaseUrl) await corsProxy.run(config, elements);
3657
+ let images = [];
3658
+ let filenames = [];
3659
+ let imageNumber = 1;
3660
+ for (const element of elements) {
3661
+ const imageOptions = await getImageOptions(element, config);
3662
+ log.verbose("image options", imageOptions);
3663
+ if (imageOptions.scale instanceof Array) {
3664
+ log.verbose("multi-scale capture");
3665
+ imageOptions.includeScaleInLabel = true;
3666
+ for (const scale of imageOptions.scale) {
3667
+ log.progress(imageNumber++, totalElements);
3668
+ const image = await captureElement(
3669
+ element,
3670
+ { ...imageOptions, scale },
3671
+ filenames
3672
+ );
3673
+ images.push(image);
3674
+ }
3675
+ } else if (typeof imageOptions.scale === "number") {
3676
+ log.progress(imageNumber++, totalElements);
3677
+ log.verbose("single-scale capture");
3678
+ const image = await captureElement(
3679
+ element,
3680
+ imageOptions,
3681
+ filenames
3682
+ );
3683
+ images.push(image);
3684
+ }
3804
3685
  }
3686
+ if (userConfig.downloadImages) downloadImages(images, config);
3687
+ if (userConfig.corsProxyBaseUrl) await corsProxy.cleanUp();
3688
+ return images;
3689
+ } catch (error) {
3690
+ log.error(error);
3691
+ return null;
3805
3692
  }
3806
3693
  }
3807
3694
  if (typeof window !== "undefined") {
3808
- window.ImageExporter = ImageExporter;
3695
+ window.imageExporter = capture;
3809
3696
  }
3810
- exports2.ImageExporter = ImageExporter;
3697
+ exports2.capture = capture;
3811
3698
  Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
3812
3699
  });
3813
3700
  })()