|
|
@@ -18,6 +18,8 @@
|
|
|
* along with SingleFile. If not, see <http://www.gnu.org/licenses/>.
|
|
|
*/
|
|
|
|
|
|
+/* global CSSRule */
|
|
|
+
|
|
|
this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
|
|
|
const SELECTED_CONTENT_ATTRIBUTE_NAME = "data-single-file-selected-content";
|
|
|
@@ -710,7 +712,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
async processStylesheets() {
|
|
|
await Promise.all(Array.from(this.doc.querySelectorAll("style")).map(async (styleElement, indexStyle) => {
|
|
|
this.stats.add("processed", "stylesheets", 1);
|
|
|
- styleElement.textContent = await DomProcessorHelper.processStylesheet(styleElement.textContent, this.baseURI, this.options, false, indexStyle, this.batchRequest);
|
|
|
+ styleElement.textContent = await DomProcessorHelper.processStylesheet(styleElement.textContent, styleElement.sheet.cssRules, this.baseURI, this.options, false, indexStyle, this.batchRequest);
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
@@ -832,7 +834,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
}
|
|
|
|
|
|
async processStyleAttributes() {
|
|
|
- await Promise.all(Array.from(this.doc.querySelectorAll("[style]")).map(async element => element.setAttribute("style", await DomProcessorHelper.processStylesheet(element.getAttribute("style"), this.baseURI, this.options, true, 0, this.batchRequest))));
|
|
|
+ await Promise.all(Array.from(this.doc.querySelectorAll("[style]")).map(async element => element.setAttribute("style", await DomProcessorHelper.processStylesheet(element.getAttribute("style"), [{ type: CSSRule.STYLE_RULE, cssText: element.getAttribute("style") }], this.baseURI, this.options, true, 0, this.batchRequest))));
|
|
|
}
|
|
|
|
|
|
async resolveLinkedStylesheetURLs() {
|
|
|
@@ -1012,34 +1014,72 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- static async processStylesheet(stylesheetContent, baseURI, options, inline, indexStyle, batchRequest) {
|
|
|
+ static async processStylesheet(stylesheetContent, cssRules, baseURI, options, inline, indexStyle, batchRequest) {
|
|
|
+ let sheetContent = "", variablesInfo = { index: 0, cssText: "" };
|
|
|
const urlFunctions = DomUtil.getUrlFunctions(stylesheetContent);
|
|
|
- let indexVariable = 0;
|
|
|
+ const variableNames = new Map();
|
|
|
+ const urlFunctionData = new Map();
|
|
|
await Promise.all(urlFunctions.map(async urlFunction => {
|
|
|
const originalResourceURL = DomUtil.matchURL(urlFunction);
|
|
|
const resourceURL = DomUtil.normalizeURL(originalResourceURL);
|
|
|
if (resourceURL && resourceURL != baseURI && DomUtil.testValidPath(resourceURL) && stylesheetContent.includes(urlFunction)) {
|
|
|
const dataURI = await batchRequest.addURL(resourceURL);
|
|
|
const regExpUrlFunction = DomUtil.getRegExp(urlFunction);
|
|
|
- if (!inline && options.compressCSS && dataURI.startsWith(PREFIX_DATA_URI_IMAGE) && !dataURI.startsWith(PREFIX_DATA_URI_IMAGE_SVG)) {
|
|
|
- const functions = stylesheetContent.match(regExpUrlFunction);
|
|
|
- if (options.groupDuplicateImages && functions && functions.length > 1) {
|
|
|
- const variableName = "--single-file-" + indexStyle + "-" + indexVariable;
|
|
|
- stylesheetContent = variableName + ":url(\"" + dataURI + "\")" + (indexVariable ? ";" : "}") + stylesheetContent;
|
|
|
- stylesheetContent = stylesheetContent.replace(regExpUrlFunction, "var(" + variableName + ")");
|
|
|
- indexVariable++;
|
|
|
- } else {
|
|
|
- stylesheetContent = stylesheetContent.replace(regExpUrlFunction, urlFunction.replace(originalResourceURL, dataURI));
|
|
|
- }
|
|
|
- } else {
|
|
|
- stylesheetContent = stylesheetContent.replace(regExpUrlFunction, urlFunction.replace(originalResourceURL, dataURI));
|
|
|
+ const functions = stylesheetContent.match(regExpUrlFunction);
|
|
|
+ if (options.groupDuplicateImages && functions.length > 1) {
|
|
|
+ variableNames.set(urlFunction, "--single-file-" + indexStyle + "-" + variableNames.size);
|
|
|
}
|
|
|
+ urlFunctionData.set(urlFunction, { regExpUrlFunction, dataURI });
|
|
|
}
|
|
|
}));
|
|
|
- if (indexVariable) {
|
|
|
- stylesheetContent = ":root{" + stylesheetContent;
|
|
|
+ const rulesContent = processRules(cssRules);
|
|
|
+ if (variablesInfo.cssText) {
|
|
|
+ sheetContent += ":root{" + variablesInfo.cssText + "}";
|
|
|
+ }
|
|
|
+ return sheetContent + rulesContent;
|
|
|
+
|
|
|
+ function processRules(cssRules) {
|
|
|
+ let rulesContent = "";
|
|
|
+ Array.from(cssRules).forEach(cssRule => {
|
|
|
+ if (cssRule.type == CSSRule.MEDIA_RULE) {
|
|
|
+ const mediaRulesContent = processRules(cssRule.cssRules);
|
|
|
+ rulesContent += "@media " + Array.from(cssRule.media).join(",") + "{" + mediaRulesContent + "}";
|
|
|
+ } else if (cssRule.type == CSSRule.STYLE_RULE) {
|
|
|
+ rulesContent += processURLFunctions(cssRule.cssText, !inline && options.compressCSS);
|
|
|
+ } else {
|
|
|
+ rulesContent += processURLFunctions(cssRule.cssText);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return rulesContent;
|
|
|
+ }
|
|
|
+
|
|
|
+ function processURLFunctions(cssText, useCustomProperties) {
|
|
|
+ const urlFunctions = DomUtil.getUrlFunctions(cssText);
|
|
|
+ urlFunctions.forEach(urlFunction => {
|
|
|
+ const originalResourceURL = DomUtil.matchURL(urlFunction);
|
|
|
+ const resourceURL = DomUtil.normalizeURL(originalResourceURL);
|
|
|
+ if (resourceURL && resourceURL != baseURI && DomUtil.testValidPath(resourceURL) && cssText.includes(urlFunction)) {
|
|
|
+ const resourceInfo = urlFunctionData.get(urlFunction);
|
|
|
+ const dataURI = resourceInfo.dataURI;
|
|
|
+ if (useCustomProperties) {
|
|
|
+ const variableName = variableNames.get(urlFunction);
|
|
|
+ if (variableName) {
|
|
|
+ cssText = cssText.replace(resourceInfo.regExpUrlFunction, "var(" + variableName + ")");
|
|
|
+ if (variablesInfo.cssText) {
|
|
|
+ variablesInfo.cssText += ";";
|
|
|
+ }
|
|
|
+ variablesInfo.cssText += variableName + ":url(\"" + dataURI + "\")";
|
|
|
+ variablesInfo.index++;
|
|
|
+ } else {
|
|
|
+ cssText = cssText.replace(resourceInfo.regExpUrlFunction, urlFunction.replace(originalResourceURL, dataURI));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ cssText = cssText.replace(resourceInfo.regExpUrlFunction, urlFunction.replace(originalResourceURL, dataURI));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return cssText;
|
|
|
}
|
|
|
- return stylesheetContent;
|
|
|
}
|
|
|
|
|
|
static async processAttribute(resourceElements, attributeName, prefixDataURI, baseURI, batchRequest) {
|