single-file-core 1.2.34 → 1.2.36
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/core/helper.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* Source.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
/* global globalThis
|
|
24
|
+
/* global globalThis */
|
|
25
25
|
|
|
26
26
|
import * as cssUnescape from "./../vendor/css-unescape.js";
|
|
27
27
|
import * as hooksFrames from "./../processors/hooks/content/content-hooks-frames.js";
|
|
@@ -77,6 +77,10 @@ const EMPTY_RESOURCE = "data:,";
|
|
|
77
77
|
const addEventListener = (type, listener, options) => globalThis.addEventListener(type, listener, options);
|
|
78
78
|
const dispatchEvent = event => { try { globalThis.dispatchEvent(event); } catch (error) { /* ignored */ } };
|
|
79
79
|
const JSON = globalThis.JSON;
|
|
80
|
+
const crypto = globalThis.crypto;
|
|
81
|
+
const TextEncoder = globalThis.TextEncoder;
|
|
82
|
+
const Blob = globalThis.Blob;
|
|
83
|
+
const CustomEvent = globalThis.CustomEvent;
|
|
80
84
|
|
|
81
85
|
export {
|
|
82
86
|
initUserScriptHandler,
|
|
@@ -90,6 +94,8 @@ export {
|
|
|
90
94
|
normalizeFontFamily,
|
|
91
95
|
getShadowRoot,
|
|
92
96
|
appendInfobar,
|
|
97
|
+
getContentSize,
|
|
98
|
+
digest,
|
|
93
99
|
ON_BEFORE_CAPTURE_EVENT_NAME,
|
|
94
100
|
ON_AFTER_CAPTURE_EVENT_NAME,
|
|
95
101
|
WIN_ID_ATTRIBUTE_NAME,
|
|
@@ -596,6 +602,33 @@ function getFontWeight(weight) {
|
|
|
596
602
|
return FONT_WEIGHTS[weight.toLowerCase().trim()] || weight;
|
|
597
603
|
}
|
|
598
604
|
|
|
605
|
+
function getContentSize(content) {
|
|
606
|
+
return new Blob([content]).size;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
async function digest(algo, text) {
|
|
610
|
+
try {
|
|
611
|
+
const hash = await crypto.subtle.digest(algo, new TextEncoder("utf-8").encode(text));
|
|
612
|
+
return hex(hash);
|
|
613
|
+
} catch (error) {
|
|
614
|
+
return "";
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
|
|
619
|
+
function hex(buffer) {
|
|
620
|
+
const hexCodes = [];
|
|
621
|
+
const view = new DataView(buffer);
|
|
622
|
+
for (let i = 0; i < view.byteLength; i += 4) {
|
|
623
|
+
const value = view.getUint32(i);
|
|
624
|
+
const stringValue = value.toString(16);
|
|
625
|
+
const padding = "00000000";
|
|
626
|
+
const paddedValue = (padding + stringValue).slice(-padding.length);
|
|
627
|
+
hexCodes.push(paddedValue);
|
|
628
|
+
}
|
|
629
|
+
return hexCodes.join("");
|
|
630
|
+
}
|
|
631
|
+
|
|
599
632
|
function flatten(array) {
|
|
600
633
|
return array.flat ? array.flat() : array.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
|
|
601
634
|
}
|
package/core/index.js
CHANGED
|
@@ -102,6 +102,7 @@ const RESOLVE_URLS_STAGE = 0;
|
|
|
102
102
|
const REPLACE_DATA_STAGE = 1;
|
|
103
103
|
const REPLACE_DOCS_STAGE = 2;
|
|
104
104
|
const POST_PROCESS_STAGE = 3;
|
|
105
|
+
const FINALIZE_STAGE = 4;
|
|
105
106
|
const STAGES = [{
|
|
106
107
|
sequential: [
|
|
107
108
|
{ action: "preProcessPage" },
|
|
@@ -164,6 +165,11 @@ const STAGES = [{
|
|
|
164
165
|
{ option: "enableMaff", action: "insertMAFFMetaData" },
|
|
165
166
|
{ action: "setDocInfo" }
|
|
166
167
|
]
|
|
168
|
+
}, {
|
|
169
|
+
sequential: [
|
|
170
|
+
{ action: "loadOptionsFromPage" },
|
|
171
|
+
{ option: "saveOptionsIntoPage", action: "saveOptionsIntoPage" },
|
|
172
|
+
]
|
|
167
173
|
}];
|
|
168
174
|
|
|
169
175
|
class Runner {
|
|
@@ -247,6 +253,7 @@ class Runner {
|
|
|
247
253
|
this.options.win = null;
|
|
248
254
|
await this.executeStage(REPLACE_DOCS_STAGE);
|
|
249
255
|
await this.executeStage(POST_PROCESS_STAGE);
|
|
256
|
+
await this.executeStage(FINALIZE_STAGE);
|
|
250
257
|
this.processor.finalize();
|
|
251
258
|
}
|
|
252
259
|
|
|
@@ -410,6 +417,7 @@ class BatchRequest {
|
|
|
410
417
|
// ---------
|
|
411
418
|
const SHADOWROOT_ATTRIBUTE_NAME = "shadowrootmode";
|
|
412
419
|
const SCRIPT_TEMPLATE_SHADOW_ROOT = "data-template-shadow-root";
|
|
420
|
+
const SCRIPT_OPTIONS = "data-single-file-options";
|
|
413
421
|
const UTF8_CHARSET = "utf-8";
|
|
414
422
|
|
|
415
423
|
class Processor {
|
|
@@ -632,6 +640,44 @@ class Processor {
|
|
|
632
640
|
}
|
|
633
641
|
}
|
|
634
642
|
|
|
643
|
+
loadOptionsFromPage() {
|
|
644
|
+
const optionsElement = this.doc.body.querySelector("script[type=\"application/json\"][" + SCRIPT_OPTIONS + "]");
|
|
645
|
+
if (optionsElement) {
|
|
646
|
+
const options = JSON.parse(optionsElement.textContent);
|
|
647
|
+
Object.keys(options).forEach(option => this.options[option] = options[option]);
|
|
648
|
+
this.options.saveDate = new Date(this.options.saveDate);
|
|
649
|
+
this.options.visitDate = new Date(this.options.visitDate);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
saveOptionsIntoPage() {
|
|
654
|
+
const optionsElement = this.doc.createElement("script");
|
|
655
|
+
optionsElement.type = "application/json";
|
|
656
|
+
optionsElement.setAttribute(SCRIPT_OPTIONS, "");
|
|
657
|
+
optionsElement.textContent = JSON.stringify({
|
|
658
|
+
saveUrl: this.options.url,
|
|
659
|
+
saveDate: this.options.saveDate.getTime(),
|
|
660
|
+
visitDate: this.options.visitDate.getTime(),
|
|
661
|
+
filenameTemplate: this.options.filenameTemplate,
|
|
662
|
+
filenameReplacedCharacters: this.options.filenameReplacedCharacters,
|
|
663
|
+
filenameReplacementCharacter: this.options.filenameReplacementCharacter,
|
|
664
|
+
filenameMaxLengthUnit: this.options.filenameMaxLengthUnit,
|
|
665
|
+
filenameMaxLength: this.options.filenameMaxLength,
|
|
666
|
+
replaceEmojisInFilename: this.options.replaceEmojisInFilename,
|
|
667
|
+
compressContent: this.options.compressContent,
|
|
668
|
+
selfExtractingArchive: this.options.selfExtractingArchive,
|
|
669
|
+
extractDataFromPage: this.options.extractDataFromPage,
|
|
670
|
+
referrer: this.options.referrer,
|
|
671
|
+
title: this.options.title,
|
|
672
|
+
info: this.options.info
|
|
673
|
+
});
|
|
674
|
+
if (this.doc.body.firstChild) {
|
|
675
|
+
this.doc.body.insertBefore(optionsElement, this.doc.body.firstChild);
|
|
676
|
+
} else {
|
|
677
|
+
this.doc.body.appendChild(optionsElement);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
635
681
|
replaceStyleContents() {
|
|
636
682
|
if (this.options.stylesheets) {
|
|
637
683
|
this.doc.querySelectorAll("style").forEach((styleElement, styleIndex) => {
|
|
@@ -781,7 +827,7 @@ class Processor {
|
|
|
781
827
|
element.setAttribute("src", DISABLED_SCRIPT);
|
|
782
828
|
}
|
|
783
829
|
});
|
|
784
|
-
const scriptElements = this.doc.querySelectorAll("script:not([type=\"application/ld+json\"]):not([" + SCRIPT_TEMPLATE_SHADOW_ROOT + "])");
|
|
830
|
+
const scriptElements = this.doc.querySelectorAll("script:not([type=\"application/ld+json\"]):not([" + SCRIPT_TEMPLATE_SHADOW_ROOT + "]):not([" + SCRIPT_OPTIONS + "])");
|
|
785
831
|
this.stats.set("discarded", "scripts", scriptElements.length);
|
|
786
832
|
this.stats.set("processed", "scripts", scriptElements.length);
|
|
787
833
|
scriptElements.forEach(element => element.remove());
|
package/core/util.js
CHANGED
|
@@ -72,9 +72,7 @@ const DOMParser = globalThis.DOMParser;
|
|
|
72
72
|
const Blob = globalThis.Blob;
|
|
73
73
|
const FileReader = globalThis.FileReader;
|
|
74
74
|
const fetch = (url, options) => globalThis.fetch(url, options);
|
|
75
|
-
const crypto = globalThis.crypto;
|
|
76
75
|
const TextDecoder = globalThis.TextDecoder;
|
|
77
|
-
const TextEncoder = globalThis.TextEncoder;
|
|
78
76
|
|
|
79
77
|
export {
|
|
80
78
|
getInstance
|
|
@@ -147,21 +145,16 @@ function getInstance(utilOptions) {
|
|
|
147
145
|
}
|
|
148
146
|
},
|
|
149
147
|
async digest(algo, text) {
|
|
150
|
-
|
|
151
|
-
const hash = await crypto.subtle.digest(algo, new TextEncoder("utf-8").encode(text));
|
|
152
|
-
return hex(hash);
|
|
153
|
-
} catch (error) {
|
|
154
|
-
return "";
|
|
155
|
-
}
|
|
148
|
+
return helper.digest(algo, text);
|
|
156
149
|
},
|
|
157
150
|
getContentSize(content) {
|
|
158
|
-
return
|
|
151
|
+
return helper.getContentSize(content);
|
|
159
152
|
},
|
|
160
153
|
formatFilename(content, doc, options) {
|
|
161
|
-
return modules.templateFormatter.formatFilename(content, doc, options
|
|
154
|
+
return modules.templateFormatter.formatFilename(content, doc, options);
|
|
162
155
|
},
|
|
163
156
|
async evalTemplate(template, options, content, doc, dontReplaceSlash) {
|
|
164
|
-
return modules.templateFormatter.evalTemplate(template, options,
|
|
157
|
+
return modules.templateFormatter.evalTemplate(template, options, content, doc, dontReplaceSlash);
|
|
165
158
|
},
|
|
166
159
|
minifyHTML(doc, options) {
|
|
167
160
|
return modules.htmlMinifier.process(doc, options);
|
|
@@ -484,20 +477,6 @@ function guessMIMEType(expectedType, buffer, asBinary) {
|
|
|
484
477
|
}
|
|
485
478
|
}
|
|
486
479
|
|
|
487
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
|
|
488
|
-
function hex(buffer) {
|
|
489
|
-
const hexCodes = [];
|
|
490
|
-
const view = new DataView(buffer);
|
|
491
|
-
for (let i = 0; i < view.byteLength; i += 4) {
|
|
492
|
-
const value = view.getUint32(i);
|
|
493
|
-
const stringValue = value.toString(16);
|
|
494
|
-
const padding = "00000000";
|
|
495
|
-
const paddedValue = (padding + stringValue).slice(-padding.length);
|
|
496
|
-
hexCodes.push(paddedValue);
|
|
497
|
-
}
|
|
498
|
-
return hexCodes.join("");
|
|
499
|
-
}
|
|
500
|
-
|
|
501
480
|
function getDoctypeString(doc) {
|
|
502
481
|
const docType = doc.doctype;
|
|
503
482
|
let docTypeString = "";
|
|
@@ -300,7 +300,7 @@ function getUsedFontWeight(fontInfo, fontStyle, fontWeights) {
|
|
|
300
300
|
let fontWeight = Number(fontInfo[1]);
|
|
301
301
|
if (fontWeights.length > 1) {
|
|
302
302
|
if (fontWeight >= 400 && fontWeight <= 500) {
|
|
303
|
-
foundWeight = fontWeights.find(weights => weights[0] >= fontWeight && weights[
|
|
303
|
+
foundWeight = fontWeights.find(weights => weights[0] >= fontWeight && weights[0] <= 500);
|
|
304
304
|
if (!foundWeight) {
|
|
305
305
|
foundWeight = findDescendingFontWeight(fontWeight, fontWeights);
|
|
306
306
|
}
|
|
@@ -39,7 +39,7 @@ export {
|
|
|
39
39
|
function process(stylesheets) {
|
|
40
40
|
const stats = { processed: 0, discarded: 0 };
|
|
41
41
|
stylesheets.forEach((stylesheetInfo, key) => {
|
|
42
|
-
if (matchesMediaType(stylesheetInfo.mediaText || MEDIA_ALL
|
|
42
|
+
if (matchesMediaType(stylesheetInfo.mediaText || MEDIA_ALL) && stylesheetInfo.stylesheet.children) {
|
|
43
43
|
const removedRules = processRules(stylesheetInfo.stylesheet.children, stats);
|
|
44
44
|
removedRules.forEach(({ cssRules, cssRule }) => cssRules.remove(cssRule));
|
|
45
45
|
} else {
|
|
@@ -57,7 +57,7 @@ function processRules(cssRules, stats, removedRules = []) {
|
|
|
57
57
|
const ruleData = cssRule.data;
|
|
58
58
|
if (ruleData.type == "Atrule" && ruleData.name == "media" && ruleData.block && ruleData.block.children && ruleData.prelude && ruleData.prelude.children) {
|
|
59
59
|
stats.processed++;
|
|
60
|
-
if (matchesMediaType(cssTree.generate(ruleData.prelude)
|
|
60
|
+
if (matchesMediaType(cssTree.generate(ruleData.prelude))) {
|
|
61
61
|
processRules(ruleData.block.children, stats, removedRules);
|
|
62
62
|
} else {
|
|
63
63
|
removedRules.push({ cssRules, cssRule });
|
|
@@ -68,10 +68,10 @@ function processRules(cssRules, stats, removedRules = []) {
|
|
|
68
68
|
return removedRules;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function matchesMediaType(mediaText
|
|
72
|
-
const foundMediaTypes = helper.flatten(mediaQueryParser.parseMediaList(mediaText).map(node => getMediaTypes(node,
|
|
73
|
-
return foundMediaTypes.find(mediaTypeInfo => (!mediaTypeInfo.not && (mediaTypeInfo.value ==
|
|
74
|
-
|| (mediaTypeInfo.not && (mediaTypeInfo.value == MEDIA_ALL || mediaTypeInfo.value
|
|
71
|
+
function matchesMediaType(mediaText) {
|
|
72
|
+
const foundMediaTypes = helper.flatten(mediaQueryParser.parseMediaList(mediaText).map(node => getMediaTypes(node, MEDIA_SCREEN)));
|
|
73
|
+
return foundMediaTypes.find(mediaTypeInfo => (!mediaTypeInfo.not && (mediaTypeInfo.value == MEDIA_SCREEN || mediaTypeInfo.value == MEDIA_ALL))
|
|
74
|
+
|| (mediaTypeInfo.not && (mediaTypeInfo.value == MEDIA_ALL || mediaTypeInfo.value == MEDIA_SCREEN)));
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
function getMediaTypes(parentNode, mediaType, mediaTypes = []) {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
/* global Blob, FileReader, URL, URLSearchParams */
|
|
25
25
|
|
|
26
26
|
import { parse } from "./template-parser.js";
|
|
27
|
+
import { getContentSize, digest } from "./../core/helper.js";
|
|
27
28
|
|
|
28
29
|
// eslint-disable-next-line quotes
|
|
29
30
|
const DEFAULT_REPLACED_CHARACTERS = ["~", "+", "\\\\", "?", "%", "*", ":", "|", '"', "<", ">", "\x00-\x1f", "\x7F"];
|
|
@@ -16865,8 +16866,8 @@ enterprisecloud.nu
|
|
|
16865
16866
|
|
|
16866
16867
|
export { formatFilename, evalTemplate };
|
|
16867
16868
|
|
|
16868
|
-
async function formatFilename(content, doc, options
|
|
16869
|
-
let filename = (await evalTemplate(options.filenameTemplate, options,
|
|
16869
|
+
async function formatFilename(content, doc, options) {
|
|
16870
|
+
let filename = (await evalTemplate(options.filenameTemplate, options, content, doc)) || "";
|
|
16870
16871
|
filename = filename.trim();
|
|
16871
16872
|
if (options.replaceEmojisInFilename) {
|
|
16872
16873
|
EMOJIS.forEach(emoji => (filename = replaceAll(filename, emoji, " _" + EMOJI_NAMES[emoji] + "_ ")));
|
|
@@ -16876,7 +16877,7 @@ async function formatFilename(content, doc, options, util) {
|
|
|
16876
16877
|
if (!options.backgroundSave) {
|
|
16877
16878
|
filename = filename.replace(/\//g, replacementCharacter);
|
|
16878
16879
|
}
|
|
16879
|
-
if (!options.keepFilename && ((options.filenameMaxLengthUnit == "bytes" &&
|
|
16880
|
+
if (!options.keepFilename && ((options.filenameMaxLengthUnit == "bytes" && getContentSize(filename) > options.filenameMaxLength) || filename.length > options.filenameMaxLength)) {
|
|
16880
16881
|
const extensionMatch = filename.match(/(\.[^.]{3,4})$/);
|
|
16881
16882
|
const extension = extensionMatch && extensionMatch[0] && extensionMatch[0].length > 1 ? extensionMatch[0] : "";
|
|
16882
16883
|
filename = options.filenameMaxLengthUnit == "bytes" ? await truncateText(filename, options.filenameMaxLength - extension.length) : filename.substring(0, options.filenameMaxLength - extension.length);
|
|
@@ -16891,7 +16892,7 @@ async function formatFilename(content, doc, options, util) {
|
|
|
16891
16892
|
return filename.trim();
|
|
16892
16893
|
}
|
|
16893
16894
|
|
|
16894
|
-
async function evalTemplate(template = "", options,
|
|
16895
|
+
async function evalTemplate(template = "", options, content, doc, dontReplaceSlash) {
|
|
16895
16896
|
const url = new URL(options.saveUrl);
|
|
16896
16897
|
const urlHref = decode(url.href);
|
|
16897
16898
|
const params = Array.from(new URLSearchParams(url.search));
|
|
@@ -16923,7 +16924,7 @@ async function evalTemplate(template = "", options, util, content, doc, dontRepl
|
|
|
16923
16924
|
"url-hostname-root": { getter: () => urlRoot },
|
|
16924
16925
|
"url-hostname-subdomains": { getter: () => urlSubDomains },
|
|
16925
16926
|
"url-href": { getter: () => urlHref, dontReplaceSlash: dontReplaceSlashIfUndefined },
|
|
16926
|
-
"url-href-digest-sha-1": { getter: urlHref ? async () =>
|
|
16927
|
+
"url-href-digest-sha-1": { getter: urlHref ? async () => digest("SHA-1", urlHref) : "" },
|
|
16927
16928
|
"url-href-flat": { getter: () => decode(url.href), dontReplaceSlash: false },
|
|
16928
16929
|
"url-referrer": { getter: () => decode(options.referrer), dontReplaceSlash: dontReplaceSlashIfUndefined },
|
|
16929
16930
|
"url-referrer-flat": { getter: () => decode(options.referrer), dontReplaceSlash: false },
|
|
@@ -16943,9 +16944,9 @@ async function evalTemplate(template = "", options, util, content, doc, dontRepl
|
|
|
16943
16944
|
"filename-extension": { getter: () => getFilenameExtension(options) }
|
|
16944
16945
|
};
|
|
16945
16946
|
if (content) {
|
|
16946
|
-
variables["digest-sha-256"] = { getter: async () =>
|
|
16947
|
-
variables["digest-sha-384"] = { getter: async () =>
|
|
16948
|
-
variables["digest-sha-512"] = { getter: async () =>
|
|
16947
|
+
variables["digest-sha-256"] = { getter: async () => digest("SHA-256", content) };
|
|
16948
|
+
variables["digest-sha-384"] = { getter: async () => digest("SHA-384", content) };
|
|
16949
|
+
variables["digest-sha-512"] = { getter: async () => digest("SHA-512", content) };
|
|
16949
16950
|
}
|
|
16950
16951
|
if (options.saveDate) {
|
|
16951
16952
|
addDateVariables(options.saveDate);
|
package/package.json
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import * as serializer from "./modules/html-serializer.js";
|
|
25
|
+
import { formatFilename } from "./modules/template-formatter.js";
|
|
25
26
|
import * as infobar from "./core/infobar.js";
|
|
26
27
|
import { getInstance } from "./core/util.js";
|
|
27
28
|
import * as zip from "./vendor/zip/zip.js";
|
|
@@ -48,6 +49,7 @@ const helper = {
|
|
|
48
49
|
zip,
|
|
49
50
|
extract,
|
|
50
51
|
display,
|
|
52
|
+
formatFilename,
|
|
51
53
|
INFOBAR_TAGNAME: infobar.INFOBAR_TAGNAME
|
|
52
54
|
};
|
|
53
55
|
|