image-exporter 0.0.1 → 1.0.1
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.
- package/README.md +137 -2
- package/bun.lockb +0 -0
- package/dist/image-exporter.es.js +511 -612
- package/dist/image-exporter.umd.js +518 -619
- package/package.json +18 -15
- package/src/capture/capture-element.ts +78 -0
- package/src/capture/determine-total-elements.ts +40 -0
- package/src/capture/download-images.ts +69 -0
- package/src/capture/get-image-options.ts +196 -0
- package/src/capture/handle-filenames.ts +52 -0
- package/src/capture/index.ts +99 -0
- package/src/capture/remove-hidden-elements.ts +19 -0
- package/src/config.ts +19 -0
- package/src/cors-proxy/cleanup.ts +43 -0
- package/src/cors-proxy/index.ts +6 -21
- package/src/{utils → cors-proxy}/is-valid-url.ts +5 -3
- package/src/cors-proxy/proxy-css.ts +51 -44
- package/src/cors-proxy/proxy-images.ts +21 -77
- package/src/cors-proxy/run.ts +26 -0
- package/src/index.ts +10 -4
- package/src/logger.ts +61 -0
- package/src/types.d.ts +51 -0
- package/vite.config.js +3 -7
- package/example/example.css +0 -122
- package/example/example.html +0 -152
- package/example/github.jpg +0 -0
- package/example/poll-h.svg +0 -1
- package/src/capture-images.ts +0 -129
- package/src/clean-up.ts +0 -50
- package/src/default-options.ts +0 -58
- package/src/download-images.ts +0 -52
- package/src/get-capture-element.test.html +0 -21
- package/src/get-capture-element.test.ts +0 -36
- package/src/get-capture-element.ts +0 -175
- package/src/get-options/get-input-options.test.html +0 -217
- package/src/get-options/get-input-options.test.ts +0 -109
- package/src/get-options/get-input-options.ts +0 -40
- package/src/get-options/get-item-options.ts +0 -46
- package/src/get-options/get-wrapper-options.test.html +0 -33
- package/src/get-options/get-wrapper-options.test.ts +0 -109
- package/src/get-options/get-wrapper-options.ts +0 -84
- package/src/get-options/index.ts +0 -28
- package/src/image-exporter.ts +0 -108
- package/src/types/image.ts +0 -2
- package/src/types/index.ts +0 -2
- package/src/types/options.ts +0 -69
- package/src/utils/convert-to-slug.ts +0 -15
- package/src/utils/get-attribute-values.ts +0 -68
- package/src/utils/get-date-MMDDYY.ts +0 -11
- package/src/utils/ignore-items.ts +0 -11
- package/src/utils/index.ts +0 -18
- package/src/utils/is-visible.ts +0 -12
- package/src/utils/parse-labels.ts +0 -55
- package/src/utils/push-to-window.ts +0 -3
- package/tests/index.html +0 -88
- 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.
|
|
383
|
-
|
|
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
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
769
|
-
|
|
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 =
|
|
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,
|
|
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
|
|
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,84 @@
|
|
|
1016
779
|
const canvas = await toCanvas(node, options);
|
|
1017
780
|
return canvas.toDataURL("image/jpeg", options.quality || 1);
|
|
1018
781
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
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
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
816
|
+
quality: imageOptions.quality,
|
|
1143
817
|
// Ensure scale is a number
|
|
1144
|
-
pixelRatio:
|
|
1145
|
-
//
|
|
1146
|
-
|
|
818
|
+
pixelRatio: imageOptions.scale,
|
|
819
|
+
// Ignores elements with data-ignore-capture attribute
|
|
820
|
+
filter
|
|
1147
821
|
};
|
|
1148
|
-
|
|
822
|
+
const styles = getComputedStyle(element);
|
|
823
|
+
const backgroundColor = styles.backgroundColor;
|
|
824
|
+
const backgroundImage = styles.backgroundImage;
|
|
825
|
+
let cleanUpBackground = false;
|
|
826
|
+
if (backgroundColor === "rgba(0, 0, 0, 0)" && backgroundImage === "none" && imageOptions.format === "jpg") {
|
|
827
|
+
element.style.backgroundColor = "#FFFFFF";
|
|
828
|
+
cleanUpBackground = true;
|
|
829
|
+
}
|
|
830
|
+
switch (imageOptions.format) {
|
|
1149
831
|
case "jpg":
|
|
1150
832
|
dataURL = await toJpeg(element, htmlToImageOptions);
|
|
1151
|
-
itemOptions.fileName = `${itemOptions.slug}.jpg`;
|
|
1152
833
|
break;
|
|
1153
834
|
case "png":
|
|
1154
|
-
default:
|
|
1155
835
|
dataURL = await toPng(element, htmlToImageOptions);
|
|
1156
|
-
|
|
836
|
+
break;
|
|
837
|
+
case "svg":
|
|
838
|
+
dataURL = await toSvg(element, htmlToImageOptions);
|
|
1157
839
|
break;
|
|
1158
840
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
return ["", ""];
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
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;
|
|
841
|
+
if (cleanUpBackground) {
|
|
842
|
+
element.style.backgroundColor = "";
|
|
843
|
+
element.style.backgroundImage = "";
|
|
1180
844
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
845
|
+
return {
|
|
846
|
+
dataURL,
|
|
847
|
+
fileName: handleFileNames(imageOptions, filenames)
|
|
848
|
+
};
|
|
849
|
+
} catch (error) {
|
|
850
|
+
console.error("ImageExporter: Error in captureImage", error);
|
|
851
|
+
return { dataURL: "", fileName: "" };
|
|
1184
852
|
}
|
|
1185
853
|
}
|
|
854
|
+
const filter = (node) => {
|
|
855
|
+
if (node instanceof HTMLElement) {
|
|
856
|
+
return !node.hasAttribute("data-ignore-capture");
|
|
857
|
+
}
|
|
858
|
+
return true;
|
|
859
|
+
};
|
|
1186
860
|
var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
|
|
1187
861
|
function getDefaultExportFromCjs(x) {
|
|
1188
862
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
@@ -1312,15 +986,15 @@
|
|
|
1312
986
|
var jszip_min = { exports: {} };
|
|
1313
987
|
/*!
|
|
1314
988
|
|
|
1315
|
-
|
|
1316
|
-
|
|
989
|
+
JSZip v3.10.1 - A JavaScript class for generating and reading zip files
|
|
990
|
+
<http://stuartk.com/jszip>
|
|
1317
991
|
|
|
1318
|
-
|
|
1319
|
-
|
|
992
|
+
(c) 2009-2016 Stuart Knightley <stuart [at] stuartk.com>
|
|
993
|
+
Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown.
|
|
1320
994
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
995
|
+
JSZip uses the library pako released under the MIT license :
|
|
996
|
+
https://github.com/nodeca/pako/blob/main/LICENSE
|
|
997
|
+
*/
|
|
1324
998
|
(function(module2, exports3) {
|
|
1325
999
|
!function(e) {
|
|
1326
1000
|
module2.exports = e();
|
|
@@ -3627,187 +3301,412 @@
|
|
|
3627
3301
|
})(jszip_min);
|
|
3628
3302
|
var jszip_minExports = jszip_min.exports;
|
|
3629
3303
|
const JSZip = /* @__PURE__ */ getDefaultExportFromCjs(jszip_minExports);
|
|
3630
|
-
async function downloadImages(images,
|
|
3304
|
+
async function downloadImages(images, config) {
|
|
3631
3305
|
if (images.length === 1) {
|
|
3632
|
-
const
|
|
3633
|
-
await download(dataURL, fileName);
|
|
3306
|
+
const image = images[0];
|
|
3307
|
+
await download(image.dataURL, image.fileName);
|
|
3634
3308
|
} else if (images.length > 1) {
|
|
3635
|
-
const
|
|
3636
|
-
|
|
3637
|
-
await download(zipBlob, zipName);
|
|
3309
|
+
const imagesBlob = await zipUpImages(images);
|
|
3310
|
+
if (imagesBlob) await download(imagesBlob, parseLabel(config));
|
|
3638
3311
|
}
|
|
3639
3312
|
}
|
|
3640
3313
|
async function zipUpImages(images) {
|
|
3641
3314
|
const zip = new JSZip();
|
|
3642
|
-
images.forEach(([dataURL, filename]) => {
|
|
3643
|
-
const content = dataURL.split(",")[1];
|
|
3644
|
-
zip.file(filename, content, { base64: true });
|
|
3645
|
-
});
|
|
3646
3315
|
try {
|
|
3647
|
-
|
|
3648
|
-
|
|
3316
|
+
images.forEach((image) => {
|
|
3317
|
+
const content = image.dataURL.split(",")[1];
|
|
3318
|
+
zip.file(image.fileName, content, { base64: true });
|
|
3319
|
+
});
|
|
3320
|
+
} catch (error) {
|
|
3321
|
+
console.error("Image Exporter - Error adding images to ZIP:", error);
|
|
3322
|
+
return;
|
|
3323
|
+
}
|
|
3324
|
+
try {
|
|
3325
|
+
const imagesBlob = await zip.generateAsync({ type: "blob" });
|
|
3326
|
+
return imagesBlob;
|
|
3649
3327
|
} catch (error) {
|
|
3650
|
-
console.error("Error
|
|
3651
|
-
|
|
3328
|
+
console.error("Image Exporter - Error generating ZIP:", error);
|
|
3329
|
+
return;
|
|
3652
3330
|
}
|
|
3653
3331
|
}
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3332
|
+
function parseLabel(config) {
|
|
3333
|
+
try {
|
|
3334
|
+
let label = config.zipLabel;
|
|
3335
|
+
label = label.replace(/\s+/g, "-");
|
|
3336
|
+
return label.replace(/[^a-zA-Z0-9-_]/g, "");
|
|
3337
|
+
} catch (error) {
|
|
3338
|
+
console.error(error);
|
|
3339
|
+
return "images";
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
const log = {
|
|
3343
|
+
info: (...messages) => logAction(messages, "info"),
|
|
3344
|
+
error: (...messages) => logAction(messages, "error"),
|
|
3345
|
+
verbose: (...messages) => logAction(messages, "verbose"),
|
|
3346
|
+
progress: (progress, total) => logProgress(progress, total)
|
|
3347
|
+
};
|
|
3348
|
+
async function logAction(messages, type = "info") {
|
|
3349
|
+
const combinedMessage = messages.map((msg) => typeof msg === "object" ? JSON.stringify(msg) : msg).join(" ");
|
|
3350
|
+
switch (type) {
|
|
3351
|
+
case "info":
|
|
3352
|
+
if (loggingLevel === "info" || loggingLevel === "verbose") {
|
|
3353
|
+
console.log(...messages);
|
|
3354
|
+
}
|
|
3355
|
+
break;
|
|
3356
|
+
case "error":
|
|
3357
|
+
if (loggingLevel === "error" || loggingLevel === "verbose") {
|
|
3358
|
+
console.error(...messages);
|
|
3359
|
+
}
|
|
3360
|
+
break;
|
|
3361
|
+
case "verbose":
|
|
3362
|
+
if (loggingLevel === "verbose") {
|
|
3363
|
+
console.log(...messages);
|
|
3364
|
+
}
|
|
3365
|
+
break;
|
|
3366
|
+
}
|
|
3367
|
+
if (windowLogging) window.imageExporterLogs.push({ message: combinedMessage, type });
|
|
3368
|
+
}
|
|
3369
|
+
async function logProgress(progress, total) {
|
|
3370
|
+
if (windowLogging) {
|
|
3371
|
+
window.imageExporterProgress.push([progress, total]);
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
window.imageExporterLogs = [];
|
|
3375
|
+
window.imageExporterProgress = [];
|
|
3376
|
+
function isValidUrl(string) {
|
|
3377
|
+
try {
|
|
3378
|
+
const url = new URL(string);
|
|
3379
|
+
if (url.protocol === "data:") {
|
|
3380
|
+
return false;
|
|
3381
|
+
}
|
|
3382
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
3383
|
+
return false;
|
|
3384
|
+
}
|
|
3385
|
+
return true;
|
|
3386
|
+
} catch (_) {
|
|
3387
|
+
return false;
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
async function proxyCSS(config) {
|
|
3391
|
+
const stylesheetElements = document.querySelectorAll('link[rel="stylesheet"]');
|
|
3392
|
+
log.verbose("stylesheet elements to proxy", stylesheetElements.length);
|
|
3393
|
+
for (let stylesheetElement of stylesheetElements) {
|
|
3394
|
+
const stylesheetURL = stylesheetElement.getAttribute("href");
|
|
3395
|
+
if (!stylesheetURL) continue;
|
|
3396
|
+
if (stylesheetURL.startsWith("data:")) continue;
|
|
3397
|
+
if (stylesheetURL.startsWith(config.corsProxyBaseUrl)) continue;
|
|
3398
|
+
if (!isValidUrl(stylesheetURL)) continue;
|
|
3399
|
+
stylesheetElement.setAttribute("crossorigin", "anonymous");
|
|
3400
|
+
const proxiedURL = config.corsProxyBaseUrl + encodeURIComponent(stylesheetURL);
|
|
3401
|
+
try {
|
|
3402
|
+
const response = await fetch(proxiedURL);
|
|
3403
|
+
let cssContent = await response.text();
|
|
3404
|
+
cssContent = cssContent.replace(
|
|
3405
|
+
/url\(['"]?(https?:\/\/[^'")\s]+)['"]?\)/g,
|
|
3406
|
+
(match, url) => {
|
|
3407
|
+
if (url.startsWith(config.corsProxyBaseUrl)) return match;
|
|
3408
|
+
return `url("${config.corsProxyBaseUrl}${encodeURIComponent(url)}")`;
|
|
3409
|
+
}
|
|
3410
|
+
);
|
|
3411
|
+
const styleElement = document.createElement("style");
|
|
3412
|
+
styleElement.textContent = cssContent;
|
|
3413
|
+
styleElement.setAttribute(
|
|
3414
|
+
"original-link-element",
|
|
3415
|
+
encodeURIComponent(stylesheetElement.outerHTML)
|
|
3416
|
+
);
|
|
3417
|
+
stylesheetElement.insertAdjacentElement("afterend", styleElement);
|
|
3418
|
+
stylesheetElement.remove();
|
|
3419
|
+
log.verbose("Proxied: ", stylesheetURL);
|
|
3420
|
+
} catch (error) {
|
|
3421
|
+
console.error("Error fetching CSS:", error);
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
async function proxyImages(config, elements) {
|
|
3426
|
+
try {
|
|
3427
|
+
const elementArray = Array.from(elements);
|
|
3428
|
+
if (!elementArray.length) return;
|
|
3429
|
+
log.verbose("images to proxy", elementArray.length);
|
|
3430
|
+
for (const element of elementArray) {
|
|
3431
|
+
const images = Array.from(element.querySelectorAll("img"));
|
|
3432
|
+
for (const img of images) {
|
|
3433
|
+
if (isValidUrl(img.src) && !img.src.startsWith(config.corsProxyBaseUrl)) {
|
|
3434
|
+
img.setAttribute("original-src", img.src);
|
|
3435
|
+
img.src = config.corsProxyBaseUrl + encodeURIComponent(img.src);
|
|
3436
|
+
log.verbose("Proxied: ", img.src);
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
} catch (e) {
|
|
3441
|
+
console.error("ImageExporter: Error in proxyImages", e);
|
|
3442
|
+
return;
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
async function runCorsProxy(config, elements) {
|
|
3446
|
+
try {
|
|
3447
|
+
log.verbose("running CORS proxy");
|
|
3448
|
+
if (!config.corsProxyBaseUrl || !isValidUrl(config.corsProxyBaseUrl)) return;
|
|
3449
|
+
await proxyCSS(config);
|
|
3450
|
+
await proxyImages(config, elements);
|
|
3451
|
+
} catch (e) {
|
|
3452
|
+
console.error("ImageExporter: Error in runCorsProxy", e);
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
async function cleanUpCorsProxy() {
|
|
3456
|
+
await restoreCSS();
|
|
3457
|
+
await restoreImages();
|
|
3458
|
+
}
|
|
3459
|
+
async function restoreCSS() {
|
|
3460
|
+
const styleElements = document.querySelectorAll("style[original-link-element]");
|
|
3461
|
+
for (let styleElement of styleElements) {
|
|
3462
|
+
const originalLinkElementHTML = decodeURIComponent(
|
|
3463
|
+
styleElement.getAttribute("original-link-element")
|
|
3464
|
+
);
|
|
3465
|
+
const tempContainer = document.createElement("div");
|
|
3466
|
+
tempContainer.innerHTML = originalLinkElementHTML;
|
|
3467
|
+
styleElement.parentNode.insertBefore(tempContainer.firstChild, styleElement);
|
|
3468
|
+
styleElement.remove();
|
|
3469
|
+
log.verbose("Restored: ", originalLinkElementHTML);
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
async function restoreImages() {
|
|
3473
|
+
const imageElements = document.querySelectorAll(
|
|
3474
|
+
"img[original-src]"
|
|
3475
|
+
);
|
|
3476
|
+
for (let imageElement of imageElements) {
|
|
3477
|
+
const originalSrc = imageElement.getAttribute("original-src");
|
|
3478
|
+
imageElement.src = originalSrc;
|
|
3479
|
+
imageElement.removeAttribute("original-src");
|
|
3480
|
+
log.verbose("Restored: ", originalSrc);
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
const corsProxy = {
|
|
3484
|
+
run: runCorsProxy,
|
|
3485
|
+
cleanUp: cleanUpCorsProxy
|
|
3486
|
+
};
|
|
3487
|
+
async function getImageOptions(element, config) {
|
|
3488
|
+
return {
|
|
3489
|
+
label: parseLabel2(),
|
|
3490
|
+
format: parseFormat(),
|
|
3491
|
+
scale: parseScale(),
|
|
3492
|
+
quality: parseQuality(),
|
|
3493
|
+
includeScaleInLabel: parseIncludeScaleInLabel()
|
|
3494
|
+
};
|
|
3495
|
+
function parseLabel2() {
|
|
3496
|
+
try {
|
|
3497
|
+
const label = element.dataset.label || config.defaultImageLabel;
|
|
3498
|
+
if (label === "") return config.defaultImageLabel;
|
|
3499
|
+
const regex = /[^a-zA-Z0-9-_]/g;
|
|
3500
|
+
return label.replace(regex, "");
|
|
3501
|
+
} catch (error) {
|
|
3502
|
+
console.error(error);
|
|
3503
|
+
return config.defaultImageLabel;
|
|
3689
3504
|
}
|
|
3690
|
-
}
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3505
|
+
}
|
|
3506
|
+
function parseFormat() {
|
|
3507
|
+
try {
|
|
3508
|
+
let format = element.dataset.format || config.format;
|
|
3509
|
+
format = format.trim().toLowerCase();
|
|
3510
|
+
if (format === "jpg" || format === "png" || format === "svg") {
|
|
3511
|
+
return format;
|
|
3512
|
+
} else {
|
|
3513
|
+
throw new Error(
|
|
3514
|
+
`ImageExporter: provided format is not valid.
|
|
3515
|
+
Provided: ${format}
|
|
3516
|
+
Element: ${element}
|
|
3517
|
+
Accepted values: jpg, png, svg
|
|
3518
|
+
Defaulting to: ${config.format}`
|
|
3519
|
+
);
|
|
3520
|
+
}
|
|
3521
|
+
} catch (error) {
|
|
3522
|
+
console.error(error);
|
|
3523
|
+
return config.format;
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
function parseScale() {
|
|
3527
|
+
try {
|
|
3528
|
+
const scaleAsString = element.dataset.scale;
|
|
3529
|
+
if (!scaleAsString) return config.scale;
|
|
3530
|
+
if (scaleAsString.includes(",")) {
|
|
3531
|
+
const scales = scaleAsString.trim().split(",").map((scale) => parseFloat(scale));
|
|
3532
|
+
if (scales.some((scale) => isNaN(scale))) {
|
|
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 scales;
|
|
3542
|
+
} else {
|
|
3543
|
+
const scaleAsNumber = parseFloat(scaleAsString.trim());
|
|
3544
|
+
if (isNaN(scaleAsNumber)) {
|
|
3545
|
+
throw new Error(
|
|
3546
|
+
`ImageExporter: provided scale is not valid.
|
|
3547
|
+
Provided: ${scaleAsString}
|
|
3548
|
+
Element: ${element}
|
|
3549
|
+
Accepted values: number or csv numbers e.g. (1,2)
|
|
3550
|
+
Defaulting to: ${config.scale}`
|
|
3551
|
+
);
|
|
3552
|
+
}
|
|
3553
|
+
return scaleAsNumber;
|
|
3554
|
+
}
|
|
3555
|
+
} catch (error) {
|
|
3556
|
+
console.error(error);
|
|
3557
|
+
return config.scale;
|
|
3706
3558
|
}
|
|
3707
|
-
}
|
|
3708
|
-
|
|
3559
|
+
}
|
|
3560
|
+
function parseQuality() {
|
|
3561
|
+
try {
|
|
3562
|
+
const qualityAsString = element.dataset.quality;
|
|
3563
|
+
if (!qualityAsString) return config.quality;
|
|
3564
|
+
const qualityAsNumber = parseFloat(qualityAsString.trim());
|
|
3565
|
+
if (isNaN(qualityAsNumber)) {
|
|
3566
|
+
throw new Error(
|
|
3567
|
+
`ImageExporter: provided quality is not valid.
|
|
3568
|
+
Provided: ${qualityAsString}
|
|
3569
|
+
Element: ${element}
|
|
3570
|
+
Accepted values: number
|
|
3571
|
+
Defaulting to: ${config.quality}`
|
|
3572
|
+
);
|
|
3573
|
+
}
|
|
3574
|
+
return qualityAsNumber;
|
|
3575
|
+
} catch (error) {
|
|
3576
|
+
console.error(error);
|
|
3577
|
+
return config.quality;
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
function parseIncludeScaleInLabel() {
|
|
3581
|
+
try {
|
|
3582
|
+
let includeScaleInLabel = element.dataset.includeScaleInLabel;
|
|
3583
|
+
if (!includeScaleInLabel) return config.includeScaleInLabel;
|
|
3584
|
+
includeScaleInLabel = includeScaleInLabel.trim();
|
|
3585
|
+
if (includeScaleInLabel === "true" || includeScaleInLabel === "false") {
|
|
3586
|
+
return includeScaleInLabel === "true";
|
|
3587
|
+
} else {
|
|
3588
|
+
throw new Error(
|
|
3589
|
+
`ImageExporter: provided includeScaleInLabel is not valid.
|
|
3590
|
+
Provided: ${includeScaleInLabel}
|
|
3591
|
+
Element: ${element}
|
|
3592
|
+
Accepted values: true or false
|
|
3593
|
+
Defaulting to: ${config.includeScaleInLabel}`
|
|
3594
|
+
);
|
|
3595
|
+
}
|
|
3596
|
+
} catch (error) {
|
|
3597
|
+
console.error(error);
|
|
3598
|
+
return config.includeScaleInLabel;
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
const defaultImageOptions = {
|
|
3603
|
+
label: "image",
|
|
3604
|
+
format: "jpg",
|
|
3605
|
+
scale: 1,
|
|
3606
|
+
quality: 1,
|
|
3607
|
+
includeScaleInLabel: false
|
|
3608
|
+
};
|
|
3609
|
+
const defaultConfig = {
|
|
3610
|
+
...defaultImageOptions,
|
|
3611
|
+
downloadImages: true,
|
|
3612
|
+
defaultImageLabel: "image",
|
|
3613
|
+
zipLabel: "images",
|
|
3614
|
+
corsProxyBaseUrl: "",
|
|
3615
|
+
enableWindowLogging: true,
|
|
3616
|
+
loggingLevel: "none"
|
|
3709
3617
|
};
|
|
3710
|
-
function
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3618
|
+
function removeHiddenElements(elements) {
|
|
3619
|
+
elements = Array.from(elements);
|
|
3620
|
+
return elements.filter((element) => isVisible(element));
|
|
3621
|
+
}
|
|
3622
|
+
function isVisible(element) {
|
|
3623
|
+
const computedStyle = window.getComputedStyle(element);
|
|
3624
|
+
return element.offsetParent !== null && element.style.display !== "none" && computedStyle.visibility === "visible" && computedStyle.opacity !== "0" && computedStyle.width !== "0" && computedStyle.height !== "0";
|
|
3625
|
+
}
|
|
3626
|
+
async function determineTotalElements(elements) {
|
|
3627
|
+
try {
|
|
3628
|
+
let totalElements = 0;
|
|
3629
|
+
for (const element of elements) {
|
|
3630
|
+
const scaleAsString = element.dataset.scale;
|
|
3631
|
+
if (!scaleAsString) continue;
|
|
3632
|
+
if (scaleAsString.includes(",")) {
|
|
3633
|
+
const scales = scaleAsString.trim().split(",").map((scale) => parseFloat(scale));
|
|
3634
|
+
if (scales.some((scale) => isNaN(scale))) continue;
|
|
3635
|
+
totalElements += scales.length;
|
|
3636
|
+
} else {
|
|
3637
|
+
const scaleAsNumber = parseFloat(scaleAsString.trim());
|
|
3638
|
+
if (isNaN(scaleAsNumber)) {
|
|
3639
|
+
continue;
|
|
3731
3640
|
} else {
|
|
3732
|
-
|
|
3641
|
+
totalElements++;
|
|
3733
3642
|
}
|
|
3734
|
-
lastCloneParent.remove();
|
|
3735
3643
|
}
|
|
3736
|
-
}
|
|
3644
|
+
}
|
|
3645
|
+
return totalElements;
|
|
3646
|
+
} catch (error) {
|
|
3647
|
+
return 1;
|
|
3737
3648
|
}
|
|
3738
3649
|
}
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
return images;
|
|
3756
|
-
}
|
|
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
|
-
)
|
|
3650
|
+
let windowLogging = true;
|
|
3651
|
+
let loggingLevel = "none";
|
|
3652
|
+
async function capture(elements, userConfig = defaultConfig) {
|
|
3653
|
+
try {
|
|
3654
|
+
const config = { ...defaultConfig, ...userConfig };
|
|
3655
|
+
windowLogging = config.enableWindowLogging;
|
|
3656
|
+
loggingLevel = config.loggingLevel;
|
|
3657
|
+
log.verbose("config", config);
|
|
3658
|
+
if (elements instanceof HTMLElement) elements = [elements];
|
|
3659
|
+
const originalLength = elements.length;
|
|
3660
|
+
elements = removeHiddenElements(elements);
|
|
3661
|
+
const totalElements = await determineTotalElements(elements);
|
|
3662
|
+
if (originalLength !== elements.length)
|
|
3663
|
+
log.verbose(
|
|
3664
|
+
"skipping capture of hidden elements: ",
|
|
3665
|
+
originalLength - elements.length
|
|
3774
3666
|
);
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
const
|
|
3781
|
-
element,
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3667
|
+
log.verbose("element to capture", elements.length);
|
|
3668
|
+
if (userConfig.corsProxyBaseUrl) await corsProxy.run(config, elements);
|
|
3669
|
+
let images = [];
|
|
3670
|
+
let filenames = [];
|
|
3671
|
+
let imageNumber = 1;
|
|
3672
|
+
for (const element of elements) {
|
|
3673
|
+
const imageOptions = await getImageOptions(element, config);
|
|
3674
|
+
log.verbose("image options", imageOptions);
|
|
3675
|
+
if (imageOptions.scale instanceof Array) {
|
|
3676
|
+
log.verbose("multi-scale capture");
|
|
3677
|
+
imageOptions.includeScaleInLabel = true;
|
|
3678
|
+
for (const scale of imageOptions.scale) {
|
|
3679
|
+
log.progress(imageNumber++, totalElements);
|
|
3680
|
+
const image = await captureElement(
|
|
3681
|
+
element,
|
|
3682
|
+
{ ...imageOptions, scale },
|
|
3683
|
+
filenames
|
|
3684
|
+
);
|
|
3685
|
+
images.push(image);
|
|
3686
|
+
}
|
|
3687
|
+
} else if (typeof imageOptions.scale === "number") {
|
|
3688
|
+
log.progress(imageNumber++, totalElements);
|
|
3689
|
+
log.verbose("single-scale capture");
|
|
3690
|
+
const image = await captureElement(
|
|
3691
|
+
element,
|
|
3692
|
+
imageOptions,
|
|
3693
|
+
filenames
|
|
3694
|
+
);
|
|
3695
|
+
images.push(image);
|
|
3696
|
+
}
|
|
3804
3697
|
}
|
|
3698
|
+
if (userConfig.downloadImages) downloadImages(images, config);
|
|
3699
|
+
if (userConfig.corsProxyBaseUrl) await corsProxy.cleanUp();
|
|
3700
|
+
return images;
|
|
3701
|
+
} catch (error) {
|
|
3702
|
+
log.error(error);
|
|
3703
|
+
return null;
|
|
3805
3704
|
}
|
|
3806
3705
|
}
|
|
3807
3706
|
if (typeof window !== "undefined") {
|
|
3808
|
-
window.
|
|
3707
|
+
window.imageExporter = capture;
|
|
3809
3708
|
}
|
|
3810
|
-
exports2.
|
|
3709
|
+
exports2.capture = capture;
|
|
3811
3710
|
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
|
|
3812
3711
|
});
|
|
3813
3712
|
})()
|