image-exporter 0.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/.gitattributes +2 -0
- package/.prettierrc +5 -0
- package/LICENSE.md +201 -0
- package/README.md +3 -0
- package/dist/image-exporter.es.js +3807 -0
- package/dist/image-exporter.umd.js +3813 -0
- package/example/example.css +122 -0
- package/example/example.html +152 -0
- package/example/github.jpg +0 -0
- package/example/poll-h.svg +1 -0
- package/package.json +50 -0
- package/src/capture-images.ts +129 -0
- package/src/clean-up.ts +50 -0
- package/src/cors-proxy/index.ts +22 -0
- package/src/cors-proxy/proxy-css.ts +52 -0
- package/src/cors-proxy/proxy-images.ts +90 -0
- package/src/default-options.ts +58 -0
- package/src/download-images.ts +52 -0
- package/src/get-capture-element.test.html +21 -0
- package/src/get-capture-element.test.ts +36 -0
- package/src/get-capture-element.ts +175 -0
- package/src/get-options/get-input-options.test.html +217 -0
- package/src/get-options/get-input-options.test.ts +109 -0
- package/src/get-options/get-input-options.ts +40 -0
- package/src/get-options/get-item-options.ts +46 -0
- package/src/get-options/get-wrapper-options.test.html +33 -0
- package/src/get-options/get-wrapper-options.test.ts +109 -0
- package/src/get-options/get-wrapper-options.ts +84 -0
- package/src/get-options/index.ts +28 -0
- package/src/image-exporter.ts +108 -0
- package/src/index.ts +8 -0
- package/src/types/image.ts +2 -0
- package/src/types/index.ts +2 -0
- package/src/types/options.ts +69 -0
- package/src/utils/convert-to-slug.ts +15 -0
- package/src/utils/get-attribute-values.ts +68 -0
- package/src/utils/get-date-MMDDYY.ts +11 -0
- package/src/utils/ignore-items.ts +11 -0
- package/src/utils/index.ts +18 -0
- package/src/utils/is-valid-url.ts +20 -0
- package/src/utils/is-visible.ts +12 -0
- package/src/utils/parse-labels.ts +55 -0
- package/src/utils/push-to-window.ts +3 -0
- package/tests/index.html +88 -0
- package/tests/input-tests.html +169 -0
- package/vite.config.js +39 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as types from "../types";
|
|
2
|
+
import { pushToWindow } from "../utils/push-to-window";
|
|
3
|
+
import { optionSafetyCheck } from "./get-wrapper-options";
|
|
4
|
+
|
|
5
|
+
// Helper function to handle common logic
|
|
6
|
+
async function handleOptions(optionsType: any, key: string) {
|
|
7
|
+
const selector = optionsType[key].inputSelector;
|
|
8
|
+
|
|
9
|
+
const inputElement = document.querySelector(`[${selector}]`) as HTMLInputElement;
|
|
10
|
+
if (!inputElement) return;
|
|
11
|
+
|
|
12
|
+
if (inputElement.getAttribute("type") === "checkbox") {
|
|
13
|
+
optionsType[key].value = inputElement.checked;
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (inputElement.value) {
|
|
18
|
+
const safeValue = optionSafetyCheck(key, inputElement.value);
|
|
19
|
+
if (safeValue === null) return;
|
|
20
|
+
|
|
21
|
+
optionsType[key].value = safeValue;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getInputOptions(options: types.Options): types.Options {
|
|
26
|
+
try {
|
|
27
|
+
// Use the helper function for both 'image' and 'zip' options
|
|
28
|
+
Promise.all(
|
|
29
|
+
Object.keys(options.image).map((key) => handleOptions(options.image, key))
|
|
30
|
+
);
|
|
31
|
+
Promise.all(Object.keys(options.zip).map((key) => handleOptions(options.zip, key)));
|
|
32
|
+
|
|
33
|
+
if (options.debug) pushToWindow("getInputOptionsDebug", options);
|
|
34
|
+
|
|
35
|
+
return options;
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error("ImageExporter: Error in getUserOptions", e);
|
|
38
|
+
return options;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as types from "../types";
|
|
2
|
+
import { parseImageLabel } from "../utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extracts and customizes settings for a specific element based on its attributes and additional criteria.
|
|
6
|
+
*
|
|
7
|
+
* This function creates a copy of the provided options object and then customizes it for an individual
|
|
8
|
+
* element. It checks for specific attributes on the element (defined in the 'attributesToCheck' parameter)
|
|
9
|
+
* and, if present, overwrites the corresponding properties in the options object with the attribute values.
|
|
10
|
+
* Additionally, it sets a 'slug' property based on the content of a specific child element or generates a
|
|
11
|
+
* unique name based on the index.
|
|
12
|
+
*
|
|
13
|
+
* @param {HTMLElement} element - The HTML element for which the settings are being determined.
|
|
14
|
+
* @param {Object} options - The base options object that provides default settings.
|
|
15
|
+
* @param {number} index - An index value, typically representing the element's position in a collection.
|
|
16
|
+
* @param {Object} attributesToCheck - An object mapping option keys to attribute names to be checked on the element.
|
|
17
|
+
* @returns {Object} An object containing the customized settings for the element.
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
export function getItemOptions(
|
|
21
|
+
element: HTMLElement,
|
|
22
|
+
options: types.Options,
|
|
23
|
+
index: number
|
|
24
|
+
): types.ItemOptions {
|
|
25
|
+
let itemOptions: types.ItemOptions = {
|
|
26
|
+
...options,
|
|
27
|
+
id: index,
|
|
28
|
+
userSlug: "",
|
|
29
|
+
slug: "",
|
|
30
|
+
fileName: "",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
Object.keys(options.image).forEach((key) => {
|
|
34
|
+
const attributeValue = element.getAttribute(options.image[key].attributeSelector);
|
|
35
|
+
if (attributeValue !== null) {
|
|
36
|
+
console.log("Capture item option:", key, "=", attributeValue);
|
|
37
|
+
itemOptions.image[key].value = attributeValue;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
itemOptions.id = index;
|
|
42
|
+
itemOptions.userSlug = element.querySelector(options.selectors.slug)?.textContent || "";
|
|
43
|
+
itemOptions.slug = parseImageLabel(itemOptions);
|
|
44
|
+
|
|
45
|
+
return itemOptions;
|
|
46
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Tests</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div
|
|
10
|
+
ie="wrapper"
|
|
11
|
+
ie-scale="2"
|
|
12
|
+
ie-quality="0.55"
|
|
13
|
+
ie-format="png"
|
|
14
|
+
ie-img-label-date="false"
|
|
15
|
+
ie-img-label-scale="false"
|
|
16
|
+
ie-zip-label="yay"
|
|
17
|
+
ie-zip-label-date="false"
|
|
18
|
+
ie-zip-label-scale="false"
|
|
19
|
+
></div>
|
|
20
|
+
<div
|
|
21
|
+
test="wrapper"
|
|
22
|
+
test-scale="3"
|
|
23
|
+
test-quality="0.65"
|
|
24
|
+
test-format="png"
|
|
25
|
+
test-img-label-date="false"
|
|
26
|
+
test-img-label-scale="false"
|
|
27
|
+
test-zip-label="test"
|
|
28
|
+
test-zip-label-date="false"
|
|
29
|
+
test-zip-label-scale="false"
|
|
30
|
+
></div>
|
|
31
|
+
<script src="../../dist/image-exporter.umd.js"></script>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
+
import puppeteer from "puppeteer";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import * as types from "../types";
|
|
5
|
+
import { defaultOptions } from "../default-options";
|
|
6
|
+
|
|
7
|
+
describe("get-wrapper-options", () => {
|
|
8
|
+
let browser;
|
|
9
|
+
let page;
|
|
10
|
+
|
|
11
|
+
beforeAll(async () => {
|
|
12
|
+
// Setup for puppeteer with Vitest
|
|
13
|
+
browser = await puppeteer.launch();
|
|
14
|
+
page = await browser.newPage();
|
|
15
|
+
await page.goto(`file://${path.join(__dirname, "get-wrapper-options.test.html")}`);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterAll(async () => {
|
|
19
|
+
// Teardown for puppeteer with Vitest
|
|
20
|
+
await browser.close();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("standard attributes", async () => {
|
|
24
|
+
const result = await page.evaluate(() => {
|
|
25
|
+
const js = `const options = { downloadImages: false, debug: true };
|
|
26
|
+
const imageExporter = new ImageExporter(options);
|
|
27
|
+
const images = imageExporter.captureAll();`;
|
|
28
|
+
//insert at the end of the body
|
|
29
|
+
const script = document.createElement("script");
|
|
30
|
+
script.text = js;
|
|
31
|
+
document.body.appendChild(script);
|
|
32
|
+
|
|
33
|
+
return (window as any).getWrapperOptionsDebug;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Assertions can then be made based on the result object
|
|
37
|
+
expect(result.image.scale.value).toBe(2);
|
|
38
|
+
expect(result.image.quality.value).toBe(0.55);
|
|
39
|
+
expect(result.image.format.value).toBe("png");
|
|
40
|
+
expect(result.image.dateInLabel.value).toBe(false);
|
|
41
|
+
expect(result.image.scaleInLabel.value).toBe(false);
|
|
42
|
+
expect(result.zip.label.value).toBe("yay");
|
|
43
|
+
expect(result.zip.dateInLabel.value).toBe(false);
|
|
44
|
+
expect(result.zip.scaleInLabel.value).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("custom attributes", async () => {
|
|
48
|
+
await page.reload();
|
|
49
|
+
const result = await page.evaluate(() => {
|
|
50
|
+
const js = `const options = {
|
|
51
|
+
downloadImages: false,
|
|
52
|
+
selectors: {
|
|
53
|
+
wrapper: "[test='wrapper']",
|
|
54
|
+
capture: "[test='capture']",
|
|
55
|
+
trigger: "[test='trigger']",
|
|
56
|
+
slug: "[test='slug']",
|
|
57
|
+
ignore: "[test='ignore']",
|
|
58
|
+
},
|
|
59
|
+
image: {
|
|
60
|
+
scale: {
|
|
61
|
+
attributeSelector: "test-scale",
|
|
62
|
+
},
|
|
63
|
+
quality: {
|
|
64
|
+
attributeSelector: "test-quality",
|
|
65
|
+
},
|
|
66
|
+
format: {
|
|
67
|
+
attributeSelector: "test-format",
|
|
68
|
+
},
|
|
69
|
+
dateInLabel: {
|
|
70
|
+
attributeSelector: "test-img-label-date",
|
|
71
|
+
},
|
|
72
|
+
scaleInLabel: {
|
|
73
|
+
attributeSelector: "test-img-label-scale",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
zip: {
|
|
77
|
+
label: {
|
|
78
|
+
attributeSelector: "test-zip-label",
|
|
79
|
+
},
|
|
80
|
+
dateInLabel: {
|
|
81
|
+
attributeSelector: "test-zip-label-date",
|
|
82
|
+
},
|
|
83
|
+
scaleInLabel: {
|
|
84
|
+
attributeSelector: "test-zip-label-scale",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
debug: true,
|
|
88
|
+
};
|
|
89
|
+
const imageExporter = new ImageExporter(options);
|
|
90
|
+
const images = imageExporter.captureAll();`;
|
|
91
|
+
//insert at the end of the body
|
|
92
|
+
const script = document.createElement("script");
|
|
93
|
+
script.text = js;
|
|
94
|
+
document.body.appendChild(script);
|
|
95
|
+
|
|
96
|
+
return (window as any).getWrapperOptionsDebug;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Assertions can then be made based on the result object
|
|
100
|
+
expect(result.image.scale.value).toBe(3);
|
|
101
|
+
expect(result.image.quality.value).toBe(0.65);
|
|
102
|
+
expect(result.image.format.value).toBe("png");
|
|
103
|
+
expect(result.image.dateInLabel.value).toBe(false);
|
|
104
|
+
expect(result.image.scaleInLabel.value).toBe(false);
|
|
105
|
+
expect(result.zip.label.value).toBe("test");
|
|
106
|
+
expect(result.zip.dateInLabel.value).toBe(false);
|
|
107
|
+
expect(result.zip.scaleInLabel.value).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as types from "../types";
|
|
2
|
+
import { pushToWindow } from "../utils/push-to-window";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Retrieves and updates the options based on the attributes of the wrapper element.
|
|
6
|
+
*
|
|
7
|
+
* This function queries the DOM for the wrapper element specified in the options.
|
|
8
|
+
* If the wrapper element is found, it iterates over the image settings and updates
|
|
9
|
+
* them with the values provided in the wrapper element's attributes.
|
|
10
|
+
*
|
|
11
|
+
* @param {types.Options} options - The initial settings object, which may contain default settings.
|
|
12
|
+
* @returns {types.Options} The updated settings object with values from the wrapper element.
|
|
13
|
+
*/
|
|
14
|
+
export function getWrapperOptions(options: types.Options): types.Options {
|
|
15
|
+
try {
|
|
16
|
+
const wrapper = document.querySelector(options.selectors.wrapper) as HTMLElement;
|
|
17
|
+
|
|
18
|
+
if (!wrapper) {
|
|
19
|
+
new Error("Wrapper element not found");
|
|
20
|
+
return options;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// For each image setting, see if value is provided in the wrapper element
|
|
24
|
+
// If so, update that setting with the new value
|
|
25
|
+
Object.keys(options.image).forEach((key) => {
|
|
26
|
+
const attrValue = wrapper.getAttribute(options.image[key].attributeSelector);
|
|
27
|
+
if (attrValue !== null) {
|
|
28
|
+
const safeValue = optionSafetyCheck(key, attrValue);
|
|
29
|
+
if (safeValue === null) return;
|
|
30
|
+
options.image[key].value = safeValue;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
Object.keys(options.zip).forEach((key) => {
|
|
35
|
+
const attrValue = wrapper.getAttribute(options.zip[key].attributeSelector);
|
|
36
|
+
if (attrValue !== null) {
|
|
37
|
+
const safeValue = optionSafetyCheck(key, attrValue);
|
|
38
|
+
if (safeValue === null) return;
|
|
39
|
+
options.zip[key].value = safeValue;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (options.debug) pushToWindow("getWrapperOptionsDebug", options);
|
|
44
|
+
|
|
45
|
+
return options;
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.error("ImageExporter: Error in getWrapperOptions", e);
|
|
48
|
+
return options;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Validates and converts the provided value based on the key.
|
|
54
|
+
*/
|
|
55
|
+
export function optionSafetyCheck(key: string, value: any): any {
|
|
56
|
+
// Handle checkbox inputs
|
|
57
|
+
if (value === "on") return true;
|
|
58
|
+
if (value === "off") return false;
|
|
59
|
+
|
|
60
|
+
// Handle number inputs
|
|
61
|
+
if (key === "scale" || key === "quality") {
|
|
62
|
+
if (typeof value === "number") return value;
|
|
63
|
+
value = value.trim();
|
|
64
|
+
value = parseFloat(value);
|
|
65
|
+
if (isNaN(value)) return null;
|
|
66
|
+
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
// Handle boolean inputs
|
|
70
|
+
if (key === "dateInLabel" || key === "scaleInLabel") {
|
|
71
|
+
if (typeof value === "boolean") return value;
|
|
72
|
+
value = value.trim();
|
|
73
|
+
|
|
74
|
+
return value === "true";
|
|
75
|
+
}
|
|
76
|
+
// Handle image format
|
|
77
|
+
if (key === "format") {
|
|
78
|
+
value = value.trim();
|
|
79
|
+
if (value === "jpg" || value === "png") return value;
|
|
80
|
+
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getWrapperOptions } from "./get-wrapper-options";
|
|
2
|
+
import { getInputOptions } from "./get-input-options";
|
|
3
|
+
import { getItemOptions } from "./get-item-options";
|
|
4
|
+
import * as types from "../types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Asynchronously determines and finalizes settings by aggregating them from multiple sources.
|
|
8
|
+
*
|
|
9
|
+
* This function works through a sequence of steps to build a comprehensive settings object. Initially,
|
|
10
|
+
* it displays a loading message. It then sequentially updates the settings based on the attributes of a
|
|
11
|
+
* wrapper element and user inputs, respectively. Each step potentially overwrites the settings from the
|
|
12
|
+
* previous steps, allowing for a layered approach to setting configuration.
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} options - The initial settings object, which may contain default settings.
|
|
15
|
+
* @returns {Promise<Object>} A promise that resolves to the fully determined settings object.
|
|
16
|
+
*
|
|
17
|
+
*/
|
|
18
|
+
function determineOptions(options: types.Options): types.Options {
|
|
19
|
+
// If settings exist on the wrapper, overwrite the default options
|
|
20
|
+
options = getWrapperOptions(options);
|
|
21
|
+
|
|
22
|
+
// If settings exist via user input, overwrite the default/wrapper options
|
|
23
|
+
options = getInputOptions(options);
|
|
24
|
+
|
|
25
|
+
return options;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { getItemOptions, determineOptions };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as types from "./types";
|
|
2
|
+
import { determineOptions, getItemOptions } from "./get-options";
|
|
3
|
+
import { findMultiScaleElements, getCaptureElements } from "./get-capture-element";
|
|
4
|
+
import { captureImage, captureImages } from "./capture-images";
|
|
5
|
+
import { downloadImages } from "./download-images";
|
|
6
|
+
import { runCorsProxy } from "./cors-proxy";
|
|
7
|
+
import { ignoreFilter } from "./utils/ignore-items";
|
|
8
|
+
import { defaultOptions } from "./default-options";
|
|
9
|
+
import { cleanUp } from "./clean-up";
|
|
10
|
+
|
|
11
|
+
// TODO: ignored nodes not working
|
|
12
|
+
|
|
13
|
+
export class ImageExporter {
|
|
14
|
+
options: types.Options;
|
|
15
|
+
|
|
16
|
+
constructor(userOptions = {}) {
|
|
17
|
+
this.options = { ...defaultOptions, ...userOptions };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Captures images from all elements specified in the options.
|
|
22
|
+
* If downloadImages is set to true, the images will be downloaded.
|
|
23
|
+
*
|
|
24
|
+
* @returns {types.Image[]} An array of captured images.
|
|
25
|
+
*/
|
|
26
|
+
async captureAll(): Promise<types.Image[]> {
|
|
27
|
+
console.log(this.options);
|
|
28
|
+
this.options = determineOptions(this.options);
|
|
29
|
+
|
|
30
|
+
const captureElements = getCaptureElements(this.options);
|
|
31
|
+
|
|
32
|
+
const images = await captureImages(this.options, captureElements);
|
|
33
|
+
|
|
34
|
+
if (this.options.downloadImages) downloadImages(images, this.options);
|
|
35
|
+
|
|
36
|
+
cleanUp(this.options, captureElements);
|
|
37
|
+
|
|
38
|
+
return images;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Captures an image from a single element.
|
|
43
|
+
* If downloadImages is set to true, the image will be downloaded.
|
|
44
|
+
*
|
|
45
|
+
* If multiscale elements are found,
|
|
46
|
+
* the element will be cloned and captured at each scale.
|
|
47
|
+
*/
|
|
48
|
+
async captureElement(element: HTMLElement): Promise<types.Image | types.Image[]> {
|
|
49
|
+
this.options = determineOptions(this.options);
|
|
50
|
+
|
|
51
|
+
await runCorsProxy(this.options);
|
|
52
|
+
|
|
53
|
+
const ignoredNodes = ignoreFilter(this.options);
|
|
54
|
+
|
|
55
|
+
const multiScale = findMultiScaleElements(this.options);
|
|
56
|
+
|
|
57
|
+
if (multiScale) {
|
|
58
|
+
const elements = Array.from(
|
|
59
|
+
document.querySelectorAll(
|
|
60
|
+
"[ie-clone], [ie-clone-source]"
|
|
61
|
+
) as NodeListOf<HTMLElement>
|
|
62
|
+
);
|
|
63
|
+
const images = await captureImages(this.options, elements);
|
|
64
|
+
|
|
65
|
+
if (this.options.downloadImages) downloadImages(images, this.options);
|
|
66
|
+
|
|
67
|
+
cleanUp(this.options, elements);
|
|
68
|
+
|
|
69
|
+
return images;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const image = await captureImage(
|
|
73
|
+
element,
|
|
74
|
+
getItemOptions(element, this.options, 1),
|
|
75
|
+
ignoredNodes
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (this.options.downloadImages) downloadImages([image], this.options);
|
|
79
|
+
|
|
80
|
+
cleanUp(this.options, [element]);
|
|
81
|
+
|
|
82
|
+
return image;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Adds a click event listener to the trigger element.
|
|
87
|
+
* If no element is provided, the captureAll method will be run on click.
|
|
88
|
+
* If an element is provided, provided element will be captured on click.
|
|
89
|
+
*/
|
|
90
|
+
addTrigger(triggerSelector: string, element: HTMLElement | null = null) {
|
|
91
|
+
const triggerElement = document.querySelector(triggerSelector);
|
|
92
|
+
if (!triggerElement) throw new Error("Trigger element not found");
|
|
93
|
+
|
|
94
|
+
if (!element) {
|
|
95
|
+
// add event listener to trigger element. on click, run captureAll
|
|
96
|
+
triggerElement.addEventListener("click", () => {
|
|
97
|
+
this.captureAll();
|
|
98
|
+
});
|
|
99
|
+
console.log("Listener added to trigger element");
|
|
100
|
+
} else {
|
|
101
|
+
// add event listener to trigger element. on click, run captureElement
|
|
102
|
+
triggerElement.addEventListener("click", () => {
|
|
103
|
+
this.captureElement(element);
|
|
104
|
+
});
|
|
105
|
+
console.log("Listener added to trigger element");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export interface Options {
|
|
2
|
+
corsProxyBaseUrl: string;
|
|
3
|
+
downloadImages: boolean;
|
|
4
|
+
selectors: Selectors;
|
|
5
|
+
image: ImageOptions;
|
|
6
|
+
zip: ZipOptions;
|
|
7
|
+
debug: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface Selectors {
|
|
11
|
+
wrapper: string;
|
|
12
|
+
capture: string;
|
|
13
|
+
trigger: string;
|
|
14
|
+
slug: string;
|
|
15
|
+
ignore: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ImageOptions {
|
|
19
|
+
scale: NumberSetting;
|
|
20
|
+
quality: NumberSetting;
|
|
21
|
+
format: ImageSetting;
|
|
22
|
+
dateInLabel: BooleanSetting;
|
|
23
|
+
scaleInLabel: BooleanSetting;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ZipOptions {
|
|
27
|
+
label: {
|
|
28
|
+
value: string;
|
|
29
|
+
attributeSelector: string;
|
|
30
|
+
inputSelector: string;
|
|
31
|
+
};
|
|
32
|
+
dateInLabel: {
|
|
33
|
+
value: boolean;
|
|
34
|
+
attributeSelector: string;
|
|
35
|
+
inputSelector: string;
|
|
36
|
+
};
|
|
37
|
+
scaleInLabel: {
|
|
38
|
+
value: boolean;
|
|
39
|
+
attributeSelector: string;
|
|
40
|
+
inputSelector: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ItemOptions extends Options {
|
|
45
|
+
// Options +
|
|
46
|
+
id: number;
|
|
47
|
+
userSlug: string;
|
|
48
|
+
slug: string;
|
|
49
|
+
fileName: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface Setting {
|
|
53
|
+
attributeSelector: string;
|
|
54
|
+
inputSelector: string;
|
|
55
|
+
value: string | number | boolean | "jpg" | "png";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface StringSetting extends Setting {
|
|
59
|
+
value: string;
|
|
60
|
+
}
|
|
61
|
+
export interface NumberSetting extends Setting {
|
|
62
|
+
value: number;
|
|
63
|
+
}
|
|
64
|
+
export interface BooleanSetting extends Setting {
|
|
65
|
+
value: boolean;
|
|
66
|
+
}
|
|
67
|
+
export interface ImageSetting extends Setting {
|
|
68
|
+
value: "jpg" | "png";
|
|
69
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a given string to a URL-friendly slug.
|
|
3
|
+
*
|
|
4
|
+
* @param input - The string to be converted to a slug.
|
|
5
|
+
* @returns The input string transformed into a slug format.
|
|
6
|
+
*/
|
|
7
|
+
export function convertToSlug(input: string) {
|
|
8
|
+
if (!input) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
input = input.toLowerCase();
|
|
12
|
+
input = input.replace(/[^a-z0-9_@ -]/g, "");
|
|
13
|
+
input = input.replace(/\s+/g, "-");
|
|
14
|
+
return input;
|
|
15
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as types from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Option determination order:
|
|
5
|
+
* 1. User input
|
|
6
|
+
* 2. Item attribute
|
|
7
|
+
* 3. Wrapper attribute
|
|
8
|
+
* 4. Default
|
|
9
|
+
*
|
|
10
|
+
* Meaning wrapper overwrites default, item overwrites wrapper, and user input overwrites all.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Checks if the user provided a value via attribute on an input element.
|
|
15
|
+
* If the user provided a value, it returns the value. Otherwise, it returns null.
|
|
16
|
+
*/
|
|
17
|
+
export function getUserInputValue(imageSetting: types.Setting): string | null {
|
|
18
|
+
try {
|
|
19
|
+
const inputElement = document.querySelector(
|
|
20
|
+
imageSetting.attributeSelector
|
|
21
|
+
) as HTMLInputElement | null;
|
|
22
|
+
|
|
23
|
+
if (inputElement) {
|
|
24
|
+
return inputElement.value;
|
|
25
|
+
} else return null;
|
|
26
|
+
} catch (e) {
|
|
27
|
+
console.error("ImageExporter: Error in getUserInputValue", e);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Checks if the user provided a value via attribute on the wrapper element.
|
|
34
|
+
* If the user provided a value, it returns the value. Otherwise, it returns null.
|
|
35
|
+
*/
|
|
36
|
+
export function getWrapperValue(
|
|
37
|
+
options: types.Options,
|
|
38
|
+
attribute: string,
|
|
39
|
+
wrapperSelector: string
|
|
40
|
+
): string | null {
|
|
41
|
+
try {
|
|
42
|
+
console.log("📣 - wrapperSelector:", wrapperSelector);
|
|
43
|
+
const wrapperElement = document.querySelector(wrapperSelector);
|
|
44
|
+
console.log(wrapperElement ? wrapperElement.getAttribute(attribute) : null);
|
|
45
|
+
return wrapperElement ? wrapperElement.getAttribute(attribute) : null;
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.error("ImageExporter: Error in getWrapperValue", e);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Checks if the user provided a value via attribute on an item element.
|
|
54
|
+
* If the user provided a value, it returns the value. Otherwise, it returns null.
|
|
55
|
+
*/
|
|
56
|
+
export function getItemValue(options: types.Options, attribute, element): string | null {
|
|
57
|
+
try {
|
|
58
|
+
const itemValue = element.querySelector(attribute);
|
|
59
|
+
if (itemValue) {
|
|
60
|
+
return itemValue;
|
|
61
|
+
} else {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.error("ImageExporter: Error in getItemValue", e);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @returns {string} Current date formatted as MMDDYY
|
|
4
|
+
*/
|
|
5
|
+
export function getDateMMDDYY() {
|
|
6
|
+
return (
|
|
7
|
+
String(new Date().getMonth() + 1).padStart(2, "0") +
|
|
8
|
+
String(new Date().getDate()).padStart(2, "0") +
|
|
9
|
+
new Date().getFullYear().toString().slice(-2)
|
|
10
|
+
);
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as types from "../types";
|
|
2
|
+
|
|
3
|
+
// Return a function that checks if a node does not match the ignore selector
|
|
4
|
+
export function ignoreFilter(options: types.Options) {
|
|
5
|
+
return (node: HTMLElement) => {
|
|
6
|
+
if (!(node instanceof HTMLElement)) {
|
|
7
|
+
throw new Error("The provided node is not an HTMLElement");
|
|
8
|
+
}
|
|
9
|
+
return !node.matches(options.selectors.ignore);
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getItemValue, getUserInputValue, getWrapperValue } from "./get-attribute-values";
|
|
2
|
+
import { convertToSlug } from "./convert-to-slug";
|
|
3
|
+
import { isVisible } from "./is-visible";
|
|
4
|
+
import { getDateMMDDYY } from "./get-date-MMDDYY";
|
|
5
|
+
import { parseImageLabel, parseZipLabel } from "./parse-labels";
|
|
6
|
+
import { isValidUrl } from "./is-valid-url";
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
isValidUrl,
|
|
10
|
+
getItemValue,
|
|
11
|
+
getWrapperValue,
|
|
12
|
+
getUserInputValue as getUserValue,
|
|
13
|
+
isVisible,
|
|
14
|
+
convertToSlug,
|
|
15
|
+
getDateMMDDYY,
|
|
16
|
+
parseZipLabel,
|
|
17
|
+
parseImageLabel,
|
|
18
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Helper function to check if a URL is valid and not a data URL
|
|
2
|
+
export function isValidUrl(string: string): boolean {
|
|
3
|
+
try {
|
|
4
|
+
const url = new URL(string);
|
|
5
|
+
|
|
6
|
+
// Check if the URL is a data URL
|
|
7
|
+
if (url.protocol === "data:") {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Optionally, check for HTTP and HTTPS protocols specifically
|
|
12
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return true;
|
|
17
|
+
} catch (_) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|