image-exporter 1.1.0 → 1.2.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.
- package/README.md +133 -81
- package/dist/capture/capture-element.d.ts +8 -0
- package/dist/capture/capture-element.d.ts.map +1 -0
- package/dist/capture/determine-total-elements.d.ts +9 -0
- package/dist/capture/determine-total-elements.d.ts.map +1 -0
- package/dist/capture/download-images.d.ts +10 -0
- package/dist/capture/download-images.d.ts.map +1 -0
- package/dist/capture/get-image-options.d.ts +15 -0
- package/dist/capture/get-image-options.d.ts.map +1 -0
- package/dist/capture/handle-filenames.d.ts +11 -0
- package/dist/capture/handle-filenames.d.ts.map +1 -0
- package/dist/capture/index.d.ts +10 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/remove-hidden-elements.d.ts +2 -0
- package/dist/capture/remove-hidden-elements.d.ts.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/cors-proxy/cleanup.d.ts +7 -0
- package/dist/cors-proxy/cleanup.d.ts.map +1 -0
- package/dist/cors-proxy/index.d.ts +7 -0
- package/dist/cors-proxy/index.d.ts.map +1 -0
- package/dist/cors-proxy/is-valid-url.d.ts +7 -0
- package/dist/cors-proxy/is-valid-url.d.ts.map +1 -0
- package/dist/cors-proxy/proxy-css.d.ts +9 -0
- package/dist/cors-proxy/proxy-css.d.ts.map +1 -0
- package/dist/cors-proxy/proxy-images.d.ts +9 -0
- package/dist/cors-proxy/proxy-images.d.ts.map +1 -0
- package/dist/cors-proxy/run.d.ts +9 -0
- package/dist/cors-proxy/run.d.ts.map +1 -0
- package/dist/index.browser.js +40 -0
- package/dist/index.browser.js.map +84 -0
- package/dist/index.cjs +14878 -0
- package/dist/index.cjs.map +84 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14869 -0
- package/dist/index.js.map +83 -0
- package/dist/logger.d.ts +26 -0
- package/dist/logger.d.ts.map +1 -0
- package/package.json +19 -14
- package/.gitattributes +0 -2
- package/.prettierrc +0 -5
- package/bun.lockb +0 -0
- package/dist/image-exporter.es.js +0 -4508
- package/dist/image-exporter.umd.js +0 -4514
- package/src/capture/capture-element.ts +0 -79
- package/src/capture/determine-total-elements.ts +0 -40
- package/src/capture/download-images.ts +0 -69
- package/src/capture/get-image-options.ts +0 -207
- package/src/capture/handle-filenames.ts +0 -54
- package/src/capture/index.ts +0 -102
- package/src/capture/remove-hidden-elements.ts +0 -19
- package/src/config.ts +0 -19
- package/src/cors-proxy/cleanup.ts +0 -43
- package/src/cors-proxy/index.ts +0 -7
- package/src/cors-proxy/is-valid-url.ts +0 -22
- package/src/cors-proxy/proxy-css.ts +0 -48
- package/src/cors-proxy/proxy-images.ts +0 -34
- package/src/cors-proxy/run.ts +0 -26
- package/src/index.ts +0 -14
- package/src/logger.ts +0 -71
- package/src/types.d.ts +0 -51
- package/vite.config.js +0 -35
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { Image, ParsedImageOptions } from "../types";
|
|
2
|
-
import { handleFileNames } from "./handle-filenames";
|
|
3
|
-
import * as modernScreenshot from "modern-screenshot";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* captureElement
|
|
7
|
-
*
|
|
8
|
-
* Captures an image from an HTML element and returns it.
|
|
9
|
-
*/
|
|
10
|
-
export async function captureElement(
|
|
11
|
-
element: HTMLElement,
|
|
12
|
-
imageOptions: ParsedImageOptions,
|
|
13
|
-
filenames: string[]
|
|
14
|
-
): Promise<Image> {
|
|
15
|
-
try {
|
|
16
|
-
let dataURL = "";
|
|
17
|
-
// Final settings for capturing images.
|
|
18
|
-
let htmlToImageOptions: modernScreenshot.Options = {
|
|
19
|
-
// Ensure quality is a number
|
|
20
|
-
quality: imageOptions.quality,
|
|
21
|
-
// Ensure scale is a number
|
|
22
|
-
scale: imageOptions.scale,
|
|
23
|
-
// Ignores elements with data-ignore-capture attribute
|
|
24
|
-
filter: filter,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// If element has no background and is a JPG, default to white background
|
|
28
|
-
const styles = getComputedStyle(element);
|
|
29
|
-
const backgroundColor = styles.backgroundColor;
|
|
30
|
-
const backgroundImage = styles.backgroundImage;
|
|
31
|
-
let cleanUpBackground = false;
|
|
32
|
-
if (
|
|
33
|
-
backgroundColor === "rgba(0, 0, 0, 0)" &&
|
|
34
|
-
backgroundImage === "none" &&
|
|
35
|
-
imageOptions.format === "jpg"
|
|
36
|
-
) {
|
|
37
|
-
element.style.backgroundColor = "#FFFFFF";
|
|
38
|
-
cleanUpBackground = true;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Captures image based on format
|
|
42
|
-
switch (imageOptions.format) {
|
|
43
|
-
case "jpg":
|
|
44
|
-
dataURL = await modernScreenshot.domToJpeg(element, htmlToImageOptions);
|
|
45
|
-
break;
|
|
46
|
-
case "png":
|
|
47
|
-
dataURL = await modernScreenshot.domToPng(element, htmlToImageOptions);
|
|
48
|
-
break;
|
|
49
|
-
case "svg":
|
|
50
|
-
dataURL = await modernScreenshot.domToSvg(element, htmlToImageOptions);
|
|
51
|
-
break;
|
|
52
|
-
case "webp":
|
|
53
|
-
dataURL = await modernScreenshot.domToWebp(element, htmlToImageOptions);
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (cleanUpBackground) {
|
|
58
|
-
element.style.backgroundColor = "";
|
|
59
|
-
element.style.backgroundImage = "";
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
dataURL,
|
|
64
|
-
fileName: handleFileNames(imageOptions, filenames),
|
|
65
|
-
};
|
|
66
|
-
} catch (error) {
|
|
67
|
-
console.error("ImageExporter: Error in captureImage", error);
|
|
68
|
-
return { dataURL: "", fileName: "" };
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const filter = (node: Node) => {
|
|
73
|
-
// Check if the node is an HTMLElement
|
|
74
|
-
if (node instanceof HTMLElement) {
|
|
75
|
-
return !node.hasAttribute("data-ignore-capture");
|
|
76
|
-
}
|
|
77
|
-
// If not an HTMLElement, return true to include the node
|
|
78
|
-
return true;
|
|
79
|
-
};
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* determineTotalElements
|
|
3
|
-
*
|
|
4
|
-
* Just used for progress logging to show progress of all element captures.
|
|
5
|
-
*
|
|
6
|
-
* This emcompasses multi-scale captures unlike elements.length.
|
|
7
|
-
*/
|
|
8
|
-
export async function determineTotalElements(
|
|
9
|
-
elements: HTMLElement[] | NodeListOf<HTMLElement>
|
|
10
|
-
): Promise<number> {
|
|
11
|
-
try {
|
|
12
|
-
let totalElements = 0;
|
|
13
|
-
|
|
14
|
-
for (const element of elements) {
|
|
15
|
-
const scaleAsString = element.dataset.scale;
|
|
16
|
-
if (!scaleAsString) continue;
|
|
17
|
-
|
|
18
|
-
if (scaleAsString.includes(",")) {
|
|
19
|
-
const scales = scaleAsString
|
|
20
|
-
.trim()
|
|
21
|
-
.split(",")
|
|
22
|
-
.map((scale) => parseFloat(scale));
|
|
23
|
-
|
|
24
|
-
if (scales.some((scale) => isNaN(scale))) continue;
|
|
25
|
-
totalElements += scales.length;
|
|
26
|
-
} else {
|
|
27
|
-
const scaleAsNumber = parseFloat(scaleAsString.trim());
|
|
28
|
-
if (isNaN(scaleAsNumber)) {
|
|
29
|
-
continue;
|
|
30
|
-
} else {
|
|
31
|
-
totalElements++;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return totalElements;
|
|
37
|
-
} catch (error) {
|
|
38
|
-
return 1;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { Config, Image, Label } from "../types";
|
|
2
|
-
import download from "downloadjs";
|
|
3
|
-
import JSZip from "jszip";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* downloadImages
|
|
7
|
-
*
|
|
8
|
-
* If one image is provided, it will be downloaded as a file.
|
|
9
|
-
*
|
|
10
|
-
* If multiple images are provided, they will be zipped and downloaded as a file.
|
|
11
|
-
*/
|
|
12
|
-
export async function downloadImages(images: Image[], config: Config) {
|
|
13
|
-
if (images.length === 1) {
|
|
14
|
-
const image = images[0];
|
|
15
|
-
|
|
16
|
-
await download(image.dataURL, image.fileName);
|
|
17
|
-
} else if (images.length > 1) {
|
|
18
|
-
const imagesBlob = await zipUpImages(images);
|
|
19
|
-
if (imagesBlob) await download(imagesBlob, parseLabel(config));
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* zipUpImages
|
|
25
|
-
*
|
|
26
|
-
* Zips up the images and returns the zip file as a Blob.
|
|
27
|
-
*/
|
|
28
|
-
async function zipUpImages(images: Image[]): Promise<Blob | undefined> {
|
|
29
|
-
const zip = new JSZip();
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
// Loop through each image tuple and add to the zip
|
|
33
|
-
images.forEach((image) => {
|
|
34
|
-
// Extract the content from the data URL
|
|
35
|
-
const content = image.dataURL.split(",")[1]; // Assumes base64 encoding
|
|
36
|
-
zip.file(image.fileName, content, { base64: true });
|
|
37
|
-
});
|
|
38
|
-
} catch (error) {
|
|
39
|
-
console.error("Image Exporter - Error adding images to ZIP:", error);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
// Generate the ZIP file
|
|
45
|
-
const imagesBlob = await zip.generateAsync({ type: "blob" });
|
|
46
|
-
return imagesBlob;
|
|
47
|
-
} catch (error) {
|
|
48
|
-
console.error("Image Exporter - Error generating ZIP:", error);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* parseLabel
|
|
55
|
-
*
|
|
56
|
-
* Parses the zip label from the config and returns a valid label.
|
|
57
|
-
*/
|
|
58
|
-
function parseLabel(config: Config): Label {
|
|
59
|
-
try {
|
|
60
|
-
// Replace spaces with dashes
|
|
61
|
-
let label = config.zipLabel;
|
|
62
|
-
label = label.replace(/\s+/g, "-");
|
|
63
|
-
// Allowed characters: a-z, A-Z, 0-9, -, _
|
|
64
|
-
return label.replace(/[^a-zA-Z0-9-_]/g, "");
|
|
65
|
-
} catch (error) {
|
|
66
|
-
console.error(error);
|
|
67
|
-
return "images";
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Quality,
|
|
3
|
-
Label,
|
|
4
|
-
Config,
|
|
5
|
-
Format,
|
|
6
|
-
ImageOptions,
|
|
7
|
-
Scale,
|
|
8
|
-
IncludeScaleInLabel,
|
|
9
|
-
} from "../types";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Retrieves the image options for the given element or configuration.
|
|
13
|
-
*
|
|
14
|
-
* Data attributes:
|
|
15
|
-
* - data-label: string
|
|
16
|
-
* - data-format: "jpg" | "png" | "svg"
|
|
17
|
-
* - data-scale: number | number[]
|
|
18
|
-
* - data-quality: number
|
|
19
|
-
* - data-include-scale-in-label: boolean
|
|
20
|
-
*
|
|
21
|
-
* @returns {Promise<ImageOptions>} - The parsed image options.
|
|
22
|
-
*/
|
|
23
|
-
export async function getImageOptions(
|
|
24
|
-
element: HTMLElement,
|
|
25
|
-
config: Config
|
|
26
|
-
): Promise<ImageOptions> {
|
|
27
|
-
return {
|
|
28
|
-
label: parseLabel(),
|
|
29
|
-
format: parseFormat(),
|
|
30
|
-
scale: parseScale(),
|
|
31
|
-
quality: parseQuality(),
|
|
32
|
-
includeScaleInLabel: parseIncludeScaleInLabel(),
|
|
33
|
-
};
|
|
34
|
-
/**
|
|
35
|
-
* # Helper functions
|
|
36
|
-
*
|
|
37
|
-
* Format:
|
|
38
|
-
*
|
|
39
|
-
* 1. Attempt to get value from dataset
|
|
40
|
-
* 2. If found, process to correct data type and return it
|
|
41
|
-
* 3. If not found, return value from config
|
|
42
|
-
*
|
|
43
|
-
* If dataset value is invalid, returns default value.
|
|
44
|
-
*/
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Parses the label property from the element's dataset.
|
|
48
|
-
* If the property is not present, it returns the default value from the config.
|
|
49
|
-
* If the property is present, it removes any characters that are not a-z, A-Z, 0-9, -, or _ and returns the cleaned up label.
|
|
50
|
-
* If an error occurs, it returns the default value from the config.
|
|
51
|
-
*/
|
|
52
|
-
function parseLabel(): Label {
|
|
53
|
-
try {
|
|
54
|
-
const label = element.dataset.label || config.defaultImageLabel;
|
|
55
|
-
|
|
56
|
-
if (label === "") return config.defaultImageLabel;
|
|
57
|
-
|
|
58
|
-
// Check if the label ends with '@#x'
|
|
59
|
-
const endsWithSpecial = /@\d+x$/.test(label);
|
|
60
|
-
let cleanedLabel = label;
|
|
61
|
-
|
|
62
|
-
// Allowed characters: a-z, A-Z, 0-9, -, _
|
|
63
|
-
// Remove all other characters using regex, except '@Nx' at the end
|
|
64
|
-
const regex = /[^a-zA-Z0-9-_]/g;
|
|
65
|
-
if (endsWithSpecial) {
|
|
66
|
-
const match = label.match(/@\d+x$/);
|
|
67
|
-
if (!match) return config.defaultImageLabel;
|
|
68
|
-
cleanedLabel = label.slice(0, -match[0].length).replace(regex, "") + match[0];
|
|
69
|
-
} else {
|
|
70
|
-
cleanedLabel = label.replace(regex, "");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return cleanedLabel;
|
|
74
|
-
} catch (error) {
|
|
75
|
-
console.error(error);
|
|
76
|
-
return config.defaultImageLabel;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Parses the format property from the element's dataset.
|
|
82
|
-
* If the property is not present, it returns the default value from the config.
|
|
83
|
-
* If the property is present, it checks if it's a valid format (jpg, png, or svg) and returns it.
|
|
84
|
-
* If the value is not valid, it throws an error and returns the default value from the config.
|
|
85
|
-
*/
|
|
86
|
-
function parseFormat(): Format {
|
|
87
|
-
try {
|
|
88
|
-
let format = element.dataset.format || config.format;
|
|
89
|
-
format = format.trim().toLowerCase();
|
|
90
|
-
if (format === "jpg" || format === "png" || format === "svg" || format === "webp") {
|
|
91
|
-
return format;
|
|
92
|
-
} else {
|
|
93
|
-
throw new Error(
|
|
94
|
-
`ImageExporter: provided format is not valid.
|
|
95
|
-
Provided: ${format}
|
|
96
|
-
Element: ${element}
|
|
97
|
-
Accepted values: jpg, png, svg,
|
|
98
|
-
Defaulting to: ${config.format}`
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
} catch (error) {
|
|
102
|
-
console.error(error);
|
|
103
|
-
return config.format;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Parses the scale property from the element's dataset.
|
|
109
|
-
* If the property is not present, it returns the default value from the config.
|
|
110
|
-
* If the property is present, it checks if it's a valid number or a comma-separated list of numbers and returns it.
|
|
111
|
-
* If the value is not valid, it throws an error and returns the default value from the config.
|
|
112
|
-
*/
|
|
113
|
-
function parseScale(): Scale {
|
|
114
|
-
try {
|
|
115
|
-
const scaleAsString = element.dataset.scale;
|
|
116
|
-
if (!scaleAsString) return config.scale;
|
|
117
|
-
if (scaleAsString.includes(",")) {
|
|
118
|
-
const scales = scaleAsString
|
|
119
|
-
.trim()
|
|
120
|
-
.split(",")
|
|
121
|
-
.map((scale) => parseFloat(scale));
|
|
122
|
-
if (scales.some((scale) => isNaN(scale))) {
|
|
123
|
-
throw new Error(
|
|
124
|
-
`ImageExporter: provided scale is not valid.
|
|
125
|
-
Provided: ${scaleAsString}
|
|
126
|
-
Element: ${element}
|
|
127
|
-
Accepted values: number or csv numbers e.g. (1,2)
|
|
128
|
-
Defaulting to ${config.scale}`
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
return scales;
|
|
132
|
-
} else {
|
|
133
|
-
const scaleAsNumber = parseFloat(scaleAsString.trim());
|
|
134
|
-
if (isNaN(scaleAsNumber)) {
|
|
135
|
-
throw new Error(
|
|
136
|
-
`ImageExporter: provided scale is not valid.
|
|
137
|
-
Provided: ${scaleAsString}
|
|
138
|
-
Element: ${element}
|
|
139
|
-
Accepted values: number or csv numbers e.g. (1,2)
|
|
140
|
-
Defaulting to: ${config.scale}`
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
return scaleAsNumber;
|
|
144
|
-
}
|
|
145
|
-
} catch (error) {
|
|
146
|
-
console.error(error);
|
|
147
|
-
return config.scale;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Parses the quality property from the element's dataset.
|
|
153
|
-
* If the property is not present, it returns the default value from the config.
|
|
154
|
-
* If the property is present, it checks if it's a valid number and returns it.
|
|
155
|
-
* If the value is not valid, it throws an error and returns the default value from the config.
|
|
156
|
-
*/
|
|
157
|
-
function parseQuality(): Quality {
|
|
158
|
-
try {
|
|
159
|
-
const qualityAsString = element.dataset.quality;
|
|
160
|
-
if (!qualityAsString) return config.quality;
|
|
161
|
-
const qualityAsNumber = parseFloat(qualityAsString.trim());
|
|
162
|
-
if (isNaN(qualityAsNumber)) {
|
|
163
|
-
throw new Error(
|
|
164
|
-
`ImageExporter: provided quality is not valid.
|
|
165
|
-
Provided: ${qualityAsString}
|
|
166
|
-
Element: ${element}
|
|
167
|
-
Accepted values: number
|
|
168
|
-
Defaulting to: ${config.quality}`
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
return qualityAsNumber;
|
|
172
|
-
} catch (error) {
|
|
173
|
-
console.error(error);
|
|
174
|
-
return config.quality;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Parses the includeScaleInLabel property from the element's dataset.
|
|
180
|
-
* If the property is not present, it returns the default value from the config.
|
|
181
|
-
* If the property is present, it checks if it's a valid value (true or false) and returns it.
|
|
182
|
-
* If the value is not valid, it throws an error and returns the default value from the config.
|
|
183
|
-
*/
|
|
184
|
-
function parseIncludeScaleInLabel(): IncludeScaleInLabel {
|
|
185
|
-
try {
|
|
186
|
-
let includeScaleInLabel = element.dataset.includeScaleInLabel;
|
|
187
|
-
if (!includeScaleInLabel) return config.includeScaleInLabel;
|
|
188
|
-
|
|
189
|
-
includeScaleInLabel = includeScaleInLabel.trim();
|
|
190
|
-
|
|
191
|
-
if (includeScaleInLabel === "true" || includeScaleInLabel === "false") {
|
|
192
|
-
return includeScaleInLabel === "true";
|
|
193
|
-
} else {
|
|
194
|
-
throw new Error(
|
|
195
|
-
`ImageExporter: provided includeScaleInLabel is not valid.
|
|
196
|
-
Provided: ${includeScaleInLabel}
|
|
197
|
-
Element: ${element}
|
|
198
|
-
Accepted values: true or false
|
|
199
|
-
Defaulting to: ${config.includeScaleInLabel}`
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
} catch (error) {
|
|
203
|
-
console.error(error);
|
|
204
|
-
return config.includeScaleInLabel;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { ImageOptions, Label } from "../types";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Handles the generation of unique filenames based on a proposed filename and an array of existing filenames.
|
|
5
|
-
*
|
|
6
|
-
* If the proposed filename is unique, it is added to the filenames array and returned as-is.
|
|
7
|
-
* If the proposed filename is not unique, the function will check if it already ends with a "-n" pattern.
|
|
8
|
-
* If it does, the function will increment the number until a unique filename is found.
|
|
9
|
-
* If it doesn't, the function will start with "-2" and increment the number until a unique filename is found.
|
|
10
|
-
*/
|
|
11
|
-
export function handleFileNames(imageOptions: ImageOptions, filenames: string[]): Label {
|
|
12
|
-
// Finish altering filenames before checking for uniqueness
|
|
13
|
-
let proposedFilename = imageOptions.label;
|
|
14
|
-
// Add scale to filename if includeScaleInLabel is true
|
|
15
|
-
if (imageOptions.includeScaleInLabel) proposedFilename += `_@${imageOptions.scale}x`;
|
|
16
|
-
// Add format to filename last
|
|
17
|
-
const extension = `.${imageOptions.format}`;
|
|
18
|
-
proposedFilename += extension;
|
|
19
|
-
|
|
20
|
-
// If filename is unique, add it to array and return as-is
|
|
21
|
-
if (!filenames.includes(proposedFilename)) {
|
|
22
|
-
filenames.push(proposedFilename);
|
|
23
|
-
return proposedFilename;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Check if filename already ends with -n pattern
|
|
27
|
-
const numberPattern = /-(\d+)$/;
|
|
28
|
-
const match = proposedFilename.match(numberPattern);
|
|
29
|
-
|
|
30
|
-
if (match) {
|
|
31
|
-
// File ends with -n, increment the number until we find a unique name
|
|
32
|
-
const baseFilename = proposedFilename.replace(numberPattern, "");
|
|
33
|
-
let counter = parseInt(match[1], 10);
|
|
34
|
-
|
|
35
|
-
while (filenames.includes(`${baseFilename}-${counter}${extension}`)) {
|
|
36
|
-
counter++;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const newFilename = `${baseFilename}-${counter}${extension}`;
|
|
40
|
-
filenames.push(newFilename);
|
|
41
|
-
return newFilename;
|
|
42
|
-
} else {
|
|
43
|
-
// File doesn't end with -n, start with -2 and increment if needed
|
|
44
|
-
const baseFilename = proposedFilename.replace(extension, "");
|
|
45
|
-
let counter = 2;
|
|
46
|
-
while (filenames.includes(`${baseFilename}-${counter}${extension}`)) {
|
|
47
|
-
counter++;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const newFilename = `${baseFilename}-${counter}${extension}`;
|
|
51
|
-
filenames.push(newFilename);
|
|
52
|
-
return newFilename;
|
|
53
|
-
}
|
|
54
|
-
}
|
package/src/capture/index.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { captureElement } from "./capture-element";
|
|
2
|
-
import { downloadImages } from "./download-images";
|
|
3
|
-
import { corsProxy } from "../cors-proxy";
|
|
4
|
-
import { Config, Image, ParsedImageOptions } from "../types";
|
|
5
|
-
import { getImageOptions } from "./get-image-options";
|
|
6
|
-
import { defaultConfig } from "../config";
|
|
7
|
-
import { removeHiddenElements } from "./remove-hidden-elements";
|
|
8
|
-
import { log } from "../logger";
|
|
9
|
-
import { determineTotalElements } from "./determine-total-elements";
|
|
10
|
-
|
|
11
|
-
export let windowLogging = true;
|
|
12
|
-
export let loggingLevel = "none";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* capture
|
|
16
|
-
*
|
|
17
|
-
* Captures images from HTML elements and returns them or downloads them.
|
|
18
|
-
*/
|
|
19
|
-
export async function capture(
|
|
20
|
-
elements: HTMLElement[] | NodeListOf<HTMLElement> | HTMLElement,
|
|
21
|
-
userConfig: Partial<Config> = defaultConfig
|
|
22
|
-
): Promise<Image[] | null> {
|
|
23
|
-
log.group.open("image-exporter");
|
|
24
|
-
try {
|
|
25
|
-
/* --------------------------------- Config --------------------------------- */
|
|
26
|
-
const config = { ...defaultConfig, ...userConfig };
|
|
27
|
-
windowLogging = config.enableWindowLogging;
|
|
28
|
-
loggingLevel = config.loggingLevel;
|
|
29
|
-
log.verbose("config", config);
|
|
30
|
-
|
|
31
|
-
/** If the user provided a single element, convert it to an array */
|
|
32
|
-
if (elements instanceof HTMLElement) elements = [elements];
|
|
33
|
-
const originalLength = elements.length;
|
|
34
|
-
elements = removeHiddenElements(elements);
|
|
35
|
-
|
|
36
|
-
const totalElements = await determineTotalElements(elements);
|
|
37
|
-
|
|
38
|
-
if (originalLength !== elements.length)
|
|
39
|
-
log.verbose(
|
|
40
|
-
"Skipping capture of hidden elements: ",
|
|
41
|
-
originalLength - elements.length
|
|
42
|
-
);
|
|
43
|
-
log.verbose("Element to capture", elements.length);
|
|
44
|
-
|
|
45
|
-
/* ------------------------------- CORS proxy ------------------------------- */
|
|
46
|
-
if (userConfig.corsProxyBaseUrl) await corsProxy.run(config, elements);
|
|
47
|
-
|
|
48
|
-
/* --------------------------------- Capture -------------------------------- */
|
|
49
|
-
let images: Image[] = [];
|
|
50
|
-
let filenames: string[] = [];
|
|
51
|
-
let imageNumber = 1;
|
|
52
|
-
|
|
53
|
-
for (const element of elements) {
|
|
54
|
-
const imageOptions = await getImageOptions(element, config);
|
|
55
|
-
log.verbose("Image options", imageOptions);
|
|
56
|
-
|
|
57
|
-
if (imageOptions.scale instanceof Array) {
|
|
58
|
-
/* --------------------------- Multi-scale capture -------------------------- */
|
|
59
|
-
log.verbose("Multi-scale capture");
|
|
60
|
-
|
|
61
|
-
imageOptions.includeScaleInLabel = true;
|
|
62
|
-
|
|
63
|
-
for (const scale of imageOptions.scale) {
|
|
64
|
-
log.progress(imageNumber++, totalElements);
|
|
65
|
-
const image = await captureElement(
|
|
66
|
-
element,
|
|
67
|
-
{ ...imageOptions, scale: scale } as ParsedImageOptions,
|
|
68
|
-
filenames
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
images.push(image);
|
|
72
|
-
}
|
|
73
|
-
} else if (typeof imageOptions.scale === "number") {
|
|
74
|
-
log.progress(imageNumber++, totalElements);
|
|
75
|
-
/* -------------------------- Single scale capture -------------------------- */
|
|
76
|
-
log.verbose("Single-scale capture");
|
|
77
|
-
|
|
78
|
-
const image = await captureElement(
|
|
79
|
-
element,
|
|
80
|
-
imageOptions as ParsedImageOptions,
|
|
81
|
-
filenames
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
images.push(image);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/* -------------------------------- Download -------------------------------- */
|
|
89
|
-
if (userConfig.downloadImages) await downloadImages(images, config);
|
|
90
|
-
|
|
91
|
-
/* --------------------------- Clean up CORS proxy -------------------------- */
|
|
92
|
-
if (userConfig.corsProxyBaseUrl) await corsProxy.cleanUp();
|
|
93
|
-
|
|
94
|
-
/** Return images optionally */
|
|
95
|
-
return images;
|
|
96
|
-
} catch (error) {
|
|
97
|
-
log.error(error);
|
|
98
|
-
return null;
|
|
99
|
-
} finally {
|
|
100
|
-
log.group.close();
|
|
101
|
-
}
|
|
102
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export function removeHiddenElements(
|
|
2
|
-
elements: HTMLElement[] | NodeListOf<HTMLElement>
|
|
3
|
-
): HTMLElement[] {
|
|
4
|
-
elements = Array.from(elements);
|
|
5
|
-
return elements.filter((element) => isVisible(element));
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function isVisible(element: HTMLElement): boolean {
|
|
9
|
-
const computedStyle = window.getComputedStyle(element);
|
|
10
|
-
|
|
11
|
-
return (
|
|
12
|
-
element.offsetParent !== null &&
|
|
13
|
-
element.style.display !== "none" &&
|
|
14
|
-
computedStyle.visibility === "visible" &&
|
|
15
|
-
computedStyle.opacity !== "0" &&
|
|
16
|
-
computedStyle.width !== "0" &&
|
|
17
|
-
computedStyle.height !== "0"
|
|
18
|
-
);
|
|
19
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Config, ImageOptions } from "./types";
|
|
2
|
-
|
|
3
|
-
export const defaultImageOptions: ImageOptions = {
|
|
4
|
-
label: "image",
|
|
5
|
-
format: "jpg",
|
|
6
|
-
scale: 1,
|
|
7
|
-
quality: 1,
|
|
8
|
-
includeScaleInLabel: false,
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export const defaultConfig: Config = {
|
|
12
|
-
...defaultImageOptions,
|
|
13
|
-
downloadImages: true,
|
|
14
|
-
defaultImageLabel: "image",
|
|
15
|
-
zipLabel: "images",
|
|
16
|
-
corsProxyBaseUrl: "",
|
|
17
|
-
enableWindowLogging: true,
|
|
18
|
-
loggingLevel: "none",
|
|
19
|
-
};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { log } from "../logger";
|
|
2
|
-
/**
|
|
3
|
-
* cleanUpCorsProxy
|
|
4
|
-
*
|
|
5
|
-
* Restores the CSS and images to their original state.
|
|
6
|
-
*/
|
|
7
|
-
export async function cleanUpCorsProxy() {
|
|
8
|
-
await restoreCSS();
|
|
9
|
-
await restoreImages();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async function restoreCSS() {
|
|
13
|
-
const styleElements = document.querySelectorAll("style[original-link-element]");
|
|
14
|
-
|
|
15
|
-
for (let styleElement of styleElements) {
|
|
16
|
-
const originalLinkElementHTML = decodeURIComponent(
|
|
17
|
-
styleElement.getAttribute("original-link-element")!
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
// Create a temporary container to parse the HTML string
|
|
21
|
-
const tempContainer = document.createElement("div");
|
|
22
|
-
tempContainer.innerHTML = originalLinkElementHTML;
|
|
23
|
-
|
|
24
|
-
// Insert the parsed HTML before the style element
|
|
25
|
-
styleElement.parentNode!.insertBefore(tempContainer.firstChild!, styleElement);
|
|
26
|
-
|
|
27
|
-
styleElement.remove();
|
|
28
|
-
log.verbose("Restored: ", originalLinkElementHTML);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function restoreImages() {
|
|
33
|
-
const imageElements = document.querySelectorAll(
|
|
34
|
-
"img[original-src]"
|
|
35
|
-
) as NodeListOf<HTMLImageElement>;
|
|
36
|
-
|
|
37
|
-
for (let imageElement of imageElements) {
|
|
38
|
-
const originalSrc = imageElement.getAttribute("original-src")!;
|
|
39
|
-
imageElement.src = originalSrc;
|
|
40
|
-
imageElement.removeAttribute("original-src");
|
|
41
|
-
log.verbose("Restored: ", originalSrc);
|
|
42
|
-
}
|
|
43
|
-
}
|
package/src/cors-proxy/index.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* isValidUrl
|
|
3
|
-
*
|
|
4
|
-
* Checks if a string is a valid external URL.
|
|
5
|
-
*/
|
|
6
|
-
export function isValidUrl(string: string): boolean {
|
|
7
|
-
try {
|
|
8
|
-
const url = new URL(string);
|
|
9
|
-
|
|
10
|
-
if (url.protocol === "data:") {
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return true;
|
|
19
|
-
} catch (_) {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
}
|