|
|
@@ -79,7 +79,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
const RESOLVE_URLS_STAGE = 0;
|
|
|
const REPLACE_DATA_STAGE = 1;
|
|
|
const REPLACE_DOCS_STAGE = 2;
|
|
|
- const CLEANUP_STAGE = 3;
|
|
|
+ const POST_PROCESS_STAGE = 3;
|
|
|
const STAGES = [{
|
|
|
sequential: [
|
|
|
{ action: "preProcessPage" },
|
|
|
@@ -129,9 +129,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
]
|
|
|
}, {
|
|
|
sequential: [
|
|
|
- { option: "compressHTML", action: "compressHTML" },
|
|
|
- { option: "insertSingleFileComment", action: "insertSingleFileComment" },
|
|
|
- { action: "cleanup" }
|
|
|
+ { option: "compressHTML", action: "compressHTML" }
|
|
|
]
|
|
|
}];
|
|
|
|
|
|
@@ -183,7 +181,8 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
}, this.options);
|
|
|
await this.pendingPromises;
|
|
|
await this.executeStage(REPLACE_DOCS_STAGE);
|
|
|
- await this.executeStage(CLEANUP_STAGE);
|
|
|
+ await this.executeStage(POST_PROCESS_STAGE);
|
|
|
+ await this.processor.end();
|
|
|
}
|
|
|
|
|
|
async getPageData() {
|
|
|
@@ -313,19 +312,15 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
|
|
|
async getPageData() {
|
|
|
DOM.postProcessDoc(this.doc, this.options);
|
|
|
- const titleElement = this.doc.querySelector("title");
|
|
|
- this.options.title = titleElement ? titleElement.textContent.trim() : "";
|
|
|
- this.options.info = {};
|
|
|
- const descriptionElement = this.doc.querySelector("meta[name=description]");
|
|
|
- this.options.info.description = descriptionElement ? descriptionElement.content.trim() : "";
|
|
|
- this.options.info.lang = this.doc.documentElement.lang;
|
|
|
- const authorElement = this.doc.querySelector("meta[name=author]");
|
|
|
- this.options.info.author = authorElement ? authorElement.content.trim() : "";
|
|
|
- const creatorElement = this.doc.querySelector("meta[name=creator]");
|
|
|
- this.options.info.creator = creatorElement ? creatorElement.content.trim() : "";
|
|
|
- const publisherElement = this.doc.querySelector("meta[name=publisher]");
|
|
|
- this.options.info.publisher = publisherElement ? publisherElement.content.trim() : "";
|
|
|
const url = new URL(this.baseURI);
|
|
|
+ if (this.options.insertSingleFileComment) {
|
|
|
+ let infobarContent = (this.options.infobarContent || "").replace(/\\n/g, "\n").replace(/\\t/g, "\t");
|
|
|
+ const commentNode = this.doc.createComment("\n Page saved with SingleFile" +
|
|
|
+ " \n url: " + this.options.url +
|
|
|
+ " \n saved date: " + new Date() +
|
|
|
+ (infobarContent ? " \n info: " + infobarContent : "") + "\n");
|
|
|
+ this.doc.documentElement.insertBefore(commentNode, this.doc.documentElement.firstChild);
|
|
|
+ }
|
|
|
let size;
|
|
|
if (this.options.displayStats) {
|
|
|
size = DOM.getContentSize(this.doc.documentElement.outerHTML);
|
|
|
@@ -336,7 +331,20 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
this.stats.set("processed", "HTML bytes", contentSize);
|
|
|
this.stats.add("discarded", "HTML bytes", size - contentSize);
|
|
|
}
|
|
|
- const filename = await DomProcessorHelper.getFilename(this.options, content);
|
|
|
+ let filename = await DomProcessorHelper.evalTemplate(this.options.filenameTemplate, this.options, content);
|
|
|
+ filename = filename.replace(/[~\\?%*:|"<>\x00-\x1f\x7F]+/g, "_"); // eslint-disable-line no-control-regex
|
|
|
+ filename = filename.replace(/\.\.\//g, "").replace(/^\/+/, "").replace(/\/+/g, "/").replace(/\/$/, "");
|
|
|
+ if (!this.options.backgroundSave) {
|
|
|
+ filename = filename.replace(/\//g, "_");
|
|
|
+ }
|
|
|
+ if (filename.length > 192) {
|
|
|
+ const extensionMatch = filename.match(/(\.[^.]{3,4})$/);
|
|
|
+ const extension = extensionMatch && extensionMatch[0] && extensionMatch[0].length > 1 ? extensionMatch[0] : "";
|
|
|
+ filename = filename.substring(0, 192 - extension.length) + "…" + extension;
|
|
|
+ }
|
|
|
+ if (!filename) {
|
|
|
+ filename = "Unnamed page";
|
|
|
+ }
|
|
|
const matchTitle = this.baseURI.match(/([^/]*)\/?(\.html?.*)$/) || this.baseURI.match(/\/\/([^/]*)\/?$/);
|
|
|
return {
|
|
|
stats: this.stats.data,
|
|
|
@@ -420,7 +428,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- cleanup() {
|
|
|
+ async end() {
|
|
|
const metaCharset = this.doc.head.querySelector("meta[charset]");
|
|
|
if (metaCharset) {
|
|
|
this.doc.head.insertBefore(metaCharset, this.doc.head.firstChild);
|
|
|
@@ -430,6 +438,19 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
if (this.doc.head.querySelectorAll("*").length == 1 && metaCharset && this.doc.body.childNodes.length == 0) {
|
|
|
this.doc.head.querySelector("meta[charset]").remove();
|
|
|
}
|
|
|
+ const titleElement = this.doc.querySelector("title");
|
|
|
+ this.options.title = titleElement ? titleElement.textContent.trim() : "";
|
|
|
+ this.options.info = {};
|
|
|
+ const descriptionElement = this.doc.querySelector("meta[name=description]");
|
|
|
+ this.options.info.description = descriptionElement ? descriptionElement.content.trim() : "";
|
|
|
+ this.options.info.lang = this.doc.documentElement.lang;
|
|
|
+ const authorElement = this.doc.querySelector("meta[name=author]");
|
|
|
+ this.options.info.author = authorElement ? authorElement.content.trim() : "";
|
|
|
+ const creatorElement = this.doc.querySelector("meta[name=creator]");
|
|
|
+ this.options.info.creator = creatorElement ? creatorElement.content.trim() : "";
|
|
|
+ const publisherElement = this.doc.querySelector("meta[name=publisher]");
|
|
|
+ this.options.info.publisher = publisherElement ? publisherElement.content.trim() : "";
|
|
|
+ this.options.infobarContent = await DomProcessorHelper.evalTemplate(this.options.infobarTemplate, this.options, null, true);
|
|
|
}
|
|
|
|
|
|
preProcessPage() {
|
|
|
@@ -588,11 +609,6 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- insertSingleFileComment() {
|
|
|
- const commentNode = this.doc.createComment("\n Archive processed by SingleFile \n url: " + this.options.url + " \n saved date: " + new Date() + " \n");
|
|
|
- this.doc.documentElement.insertBefore(commentNode, this.doc.documentElement.firstChild);
|
|
|
- }
|
|
|
-
|
|
|
replaceCanvasElements() {
|
|
|
if (this.options.canvasData) {
|
|
|
this.doc.querySelectorAll("canvas").forEach((canvasElement, indexCanvasElement) => {
|
|
|
@@ -890,64 +906,52 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
const SINGLE_FILE_VARIABLE_NAME_PREFIX = "--sf-img-";
|
|
|
|
|
|
class DomProcessorHelper {
|
|
|
- static async getFilename(options, content) {
|
|
|
- let filename = options.filenameTemplate;
|
|
|
+ static async evalTemplate(template, options, content, dontReplaceSlash) {
|
|
|
const date = new Date();
|
|
|
const url = new URL(options.url);
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "page-title", () => options.title || "No title");
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "page-language", () => options.info.lang || "No language");
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "page-description", () => options.info.description || "No description");
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "page-author", () => options.info.author || "No author");
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "page-creator", () => options.info.creator || "No creator");
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "page-publisher", () => options.info.publisher || "No publisher");
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "datetime-iso", () => date.toISOString());
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "date-iso", () => date.toISOString().split("T")[0]);
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "time-iso", () => date.toISOString().split("T")[1].split("Z")[0]);
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "date-locale", () => date.toLocaleDateString());
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "time-locale", () => date.toLocaleTimeString());
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "day-locale", () => String(date.getDate()).padStart(2, "0"));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "month-locale", () => String(date.getMonth() + 1).padStart(2, "0"));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "year-locale", () => String(date.getFullYear()));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "datetime-locale", () => date.toLocaleString());
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "datetime-utc", () => date.toUTCString());
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "day-utc", () => String(date.getUTCDate()).padStart(2, "0"));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "month-utc", () => String(date.getUTCMonth() + 1).padStart(2, "0"));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "year-utc", () => String(date.getUTCFullYear()));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "hours-locale", () => String(date.getHours()).padStart(2, "0"));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "minutes-locale", () => String(date.getMinutes()).padStart(2, "0"));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "seconds-locale", () => String(date.getSeconds()).padStart(2, "0"));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "hours-utc", () => String(date.getUTCHours()).padStart(2, "0"));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "minutes-utc", () => String(date.getUTCMinutes()).padStart(2, "0"));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "seconds-utc", () => String(date.getUTCSeconds()).padStart(2, "0"));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "url-hash", () => url.hash.substring(1));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "url-host", () => url.host.replace(/\/$/, ""));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "url-hostname", () => url.hostname.replace(/\/$/, ""));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "url-href", () => url.href);
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "url-password", () => url.password);
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "url-pathname", () => url.pathname.replace(/^\//, "").replace(/\/$/, ""), true);
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "url-port", () => url.port);
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "url-protocol", () => url.protocol);
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "url-search", () => url.search.substring(1));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "url-username", () => url.username);
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "tab-id", () => String(options.tabId || "No tab id"));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "url-last-segment", () => DomUtil.getLastSegment(url));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "digest-sha-256", async () => DOM.digest("SHA-256", content));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "digest-sha-384", async () => DOM.digest("SHA-384", content));
|
|
|
- filename = await DomUtil.evalTemplateVariable(filename, "digest-sha-512", async () => DOM.digest("SHA-512", content));
|
|
|
- filename = filename.replace(/[~\\?%*:|"<>\x00-\x1f\x7F]+/g, "_"); // eslint-disable-line no-control-regex
|
|
|
- filename = filename.replace(/\.\.\//g, "").replace(/^\/+/, "").replace(/\/+/g, "/").replace(/\/$/, "");
|
|
|
- if (!options.backgroundSave) {
|
|
|
- filename = filename.replace(/\//g, "_");
|
|
|
- }
|
|
|
- if (filename.length > 192) {
|
|
|
- const extensionMatch = filename.match(/(\.[^.]{3,4})$/);
|
|
|
- const extension = extensionMatch && extensionMatch[0] && extensionMatch[0].length > 1 ? extensionMatch[0] : "";
|
|
|
- filename = filename.substring(0, 192 - extension.length) + "…" + extension;
|
|
|
- }
|
|
|
- if (!filename) {
|
|
|
- filename = "Unnamed page";
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "page-title", () => options.title || "No title", dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "page-language", () => options.info.lang || "No language", dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "page-description", () => options.info.description || "No description", dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "page-author", () => options.info.author || "No author", dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "page-creator", () => options.info.creator || "No creator", dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "page-publisher", () => options.info.publisher || "No publisher", dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "datetime-iso", () => date.toISOString(), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "date-iso", () => date.toISOString().split("T")[0], dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "time-iso", () => date.toISOString().split("T")[1].split("Z")[0], dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "date-locale", () => date.toLocaleDateString(), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "time-locale", () => date.toLocaleTimeString(), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "day-locale", () => String(date.getDate()).padStart(2, "0"), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "month-locale", () => String(date.getMonth() + 1).padStart(2, "0"), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "year-locale", () => String(date.getFullYear()), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "datetime-locale", () => date.toLocaleString(), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "datetime-utc", () => date.toUTCString(), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "day-utc", () => String(date.getUTCDate()).padStart(2, "0"), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "month-utc", () => String(date.getUTCMonth() + 1).padStart(2, "0"), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "year-utc", () => String(date.getUTCFullYear()), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "hours-locale", () => String(date.getHours()).padStart(2, "0"), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "minutes-locale", () => String(date.getMinutes()).padStart(2, "0"), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "seconds-locale", () => String(date.getSeconds()).padStart(2, "0"), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "hours-utc", () => String(date.getUTCHours()).padStart(2, "0"), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "minutes-utc", () => String(date.getUTCMinutes()).padStart(2, "0"), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "seconds-utc", () => String(date.getUTCSeconds()).padStart(2, "0"), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "url-hash", () => url.hash.substring(1), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "url-host", () => url.host.replace(/\/$/, ""), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "url-hostname", () => url.hostname.replace(/\/$/, ""), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "url-href", () => url.href, dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "url-password", () => url.password, dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "url-pathname", () => url.pathname.replace(/^\//, "").replace(/\/$/, ""), dontReplaceSlash === undefined ? true : dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "url-port", () => url.port, dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "url-protocol", () => url.protocol, dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "url-search", () => url.search.substring(1), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "url-username", () => url.username, dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "tab-id", () => String(options.tabId || "No tab id"), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "url-last-segment", () => DomUtil.getLastSegment(url), dontReplaceSlash);
|
|
|
+ if (content) {
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "digest-sha-256", async () => DOM.digest("SHA-256", content), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "digest-sha-384", async () => DOM.digest("SHA-384", content), dontReplaceSlash);
|
|
|
+ template = await DomUtil.evalTemplateVariable(template, "digest-sha-512", async () => DOM.digest("SHA-512", content), dontReplaceSlash);
|
|
|
}
|
|
|
- return filename;
|
|
|
+ return template;
|
|
|
}
|
|
|
|
|
|
static setBackgroundImage(element, url, style) {
|