2022-11-17 09:50:36 +05:00
|
|
|
/*
|
|
|
|
|
This file is part of the Notesnook project (https://notesnook.com/)
|
|
|
|
|
|
2023-01-16 13:44:52 +05:00
|
|
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
2022-11-17 09:50:36 +05:00
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
2024-09-23 12:13:46 +05:00
|
|
|
import { constructUrl, FetchOptions } from "./fetch.js";
|
2025-06-13 14:07:15 +05:00
|
|
|
import { inlineAll } from "./inliner.js";
|
2022-11-17 09:50:36 +05:00
|
|
|
|
2025-06-16 12:14:37 +05:00
|
|
|
async function resolveImports(options?: FetchOptions) {
|
2022-11-17 09:50:36 +05:00
|
|
|
for (const sheet of document.styleSheets) {
|
2023-11-16 13:06:51 +05:00
|
|
|
const rulesToDelete = [];
|
2024-08-13 09:31:18 +05:00
|
|
|
if (await skipStyleSheet(sheet, options)) continue;
|
2022-11-17 09:50:36 +05:00
|
|
|
|
2023-11-16 13:06:51 +05:00
|
|
|
for (let i = 0; i < sheet.cssRules.length; ++i) {
|
|
|
|
|
const rule = sheet.cssRules.item(i);
|
|
|
|
|
if (!rule) continue;
|
|
|
|
|
|
2022-11-17 09:50:36 +05:00
|
|
|
if (rule.type === CSSRule.IMPORT_RULE) {
|
|
|
|
|
const href = (rule as CSSImportRule).href;
|
|
|
|
|
const result = await downloadStylesheet(href, options);
|
2025-06-16 12:14:37 +05:00
|
|
|
if (result) {
|
|
|
|
|
if (sheet.ownerNode) sheet.ownerNode.before(result);
|
|
|
|
|
else document.head.appendChild(result);
|
|
|
|
|
rulesToDelete.push(i);
|
|
|
|
|
}
|
2022-11-17 09:50:36 +05:00
|
|
|
}
|
|
|
|
|
}
|
2023-11-16 13:06:51 +05:00
|
|
|
|
2025-06-16 12:14:37 +05:00
|
|
|
if (sheet.cssRules.length !== 0) {
|
|
|
|
|
for (const ruleIndex of rulesToDelete) sheet.deleteRule(ruleIndex);
|
|
|
|
|
}
|
2022-11-17 09:50:36 +05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-11 11:30:59 +05:00
|
|
|
async function downloadStylesheet(href: string, options?: FetchOptions) {
|
2022-11-17 09:50:36 +05:00
|
|
|
try {
|
|
|
|
|
const style = document.createElement("style");
|
|
|
|
|
const response = await fetch(constructUrl(href, options));
|
|
|
|
|
if (!response.ok) return false;
|
2025-06-12 12:10:01 +05:00
|
|
|
style.innerHTML = await response.text();
|
2022-11-17 09:50:36 +05:00
|
|
|
style.setAttribute("href", href);
|
|
|
|
|
return style;
|
|
|
|
|
} catch (e) {
|
2022-12-03 08:58:30 +05:00
|
|
|
console.error("Failed to inline stylesheet", href, e);
|
2022-11-17 09:50:36 +05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-13 09:31:18 +05:00
|
|
|
async function skipStyleSheet(sheet: CSSStyleSheet, options?: FetchOptions) {
|
|
|
|
|
try {
|
|
|
|
|
sheet.cssRules.length;
|
|
|
|
|
} catch (_e) {
|
|
|
|
|
const node = sheet.ownerNode;
|
2025-06-16 09:00:28 +05:00
|
|
|
if (sheet.href && node instanceof HTMLLinkElement) {
|
|
|
|
|
if (isStylesheetForPrint(node)) return true;
|
|
|
|
|
|
2024-08-13 09:31:18 +05:00
|
|
|
const styleNode = await downloadStylesheet(node.href, options);
|
|
|
|
|
if (styleNode) node.replaceWith(styleNode);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-12 13:57:54 +05:00
|
|
|
return isStylesheetForPrint(sheet);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-14 13:11:10 +05:00
|
|
|
function isStylesheetForPrint(sheet: CSSStyleSheet | HTMLLinkElement) {
|
|
|
|
|
const mediaText =
|
|
|
|
|
typeof sheet.media === "string" ? sheet.media : sheet.media.mediaText;
|
|
|
|
|
return mediaText
|
2022-11-17 09:50:36 +05:00
|
|
|
.split(",")
|
|
|
|
|
.map((t) => t.trim())
|
|
|
|
|
.includes("print");
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-16 12:14:37 +05:00
|
|
|
export async function addStylesToHead(
|
|
|
|
|
head: HTMLHeadElement,
|
|
|
|
|
options?: FetchOptions
|
|
|
|
|
) {
|
|
|
|
|
await resolveImports(options);
|
|
|
|
|
|
2025-06-11 11:30:59 +05:00
|
|
|
for (const sheet of document.styleSheets) {
|
2025-06-12 13:57:54 +05:00
|
|
|
if (isStylesheetForPrint(sheet)) continue;
|
2025-06-16 09:00:28 +05:00
|
|
|
|
2025-06-16 12:14:37 +05:00
|
|
|
const href =
|
|
|
|
|
sheet.href && sheet.ownerNode instanceof HTMLLinkElement
|
|
|
|
|
? sheet.ownerNode.href
|
|
|
|
|
: sheet.ownerNode instanceof HTMLStyleElement
|
|
|
|
|
? sheet.ownerNode.getAttribute("href")
|
|
|
|
|
: null;
|
|
|
|
|
if (href) {
|
|
|
|
|
const result = await downloadStylesheet(href, options);
|
|
|
|
|
if (!result) continue;
|
|
|
|
|
const cssStylesheet = new CSSStyleSheet();
|
2025-10-27 11:29:52 +05:00
|
|
|
await cssStylesheet.replace(result.innerHTML);
|
2025-06-16 12:14:37 +05:00
|
|
|
await inlineBackgroundImages(cssStylesheet, options);
|
|
|
|
|
const toAppend = rulesToStyleNode(cssStylesheet.cssRules);
|
|
|
|
|
head.appendChild(toAppend);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sheet.cssRules.length > 0) {
|
|
|
|
|
await inlineBackgroundImages(sheet, options);
|
|
|
|
|
const styleNode = rulesToStyleNode(sheet.cssRules);
|
|
|
|
|
head.appendChild(styleNode);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sheet.ownerNode instanceof HTMLStyleElement) {
|
|
|
|
|
head.appendChild(sheet.ownerNode.cloneNode(true));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-06-11 11:30:59 +05:00
|
|
|
}
|
|
|
|
|
}
|
2025-06-12 13:52:10 +05:00
|
|
|
|
|
|
|
|
function rulesToStyleNode(cssRules: CSSRuleList) {
|
|
|
|
|
const cssText = Array.from(cssRules)
|
|
|
|
|
.map((r) => r.cssText)
|
2025-06-16 12:14:37 +05:00
|
|
|
.reduce((acc, text) => acc + text, "");
|
2025-06-12 13:52:10 +05:00
|
|
|
const style = document.createElement("style");
|
|
|
|
|
style.innerHTML = cssText;
|
|
|
|
|
return style;
|
|
|
|
|
}
|
2025-06-13 14:07:15 +05:00
|
|
|
|
|
|
|
|
async function inlineBackgroundImages(
|
2025-06-14 13:11:10 +05:00
|
|
|
sheet: CSSStyleSheet,
|
2025-06-13 14:07:15 +05:00
|
|
|
options?: FetchOptions
|
|
|
|
|
) {
|
2025-06-14 13:11:10 +05:00
|
|
|
const promises: Promise<void>[] = [];
|
|
|
|
|
for (const rule of sheet.cssRules) {
|
2025-06-13 14:07:15 +05:00
|
|
|
if (rule.type === CSSRule.STYLE_RULE) {
|
2025-06-14 13:11:10 +05:00
|
|
|
promises.push(processStyleRule(sheet, rule as CSSStyleRule, options));
|
2025-06-13 14:07:15 +05:00
|
|
|
} else if (rule.type === CSSRule.MEDIA_RULE) {
|
2025-06-14 13:11:10 +05:00
|
|
|
const mediaRule = rule as CSSMediaRule;
|
|
|
|
|
const mediaMatches = window.matchMedia(mediaRule.media.mediaText).matches;
|
|
|
|
|
for (const innerRule of mediaRule.cssRules) {
|
|
|
|
|
if (innerRule && innerRule.type === CSSRule.STYLE_RULE) {
|
|
|
|
|
promises.push(
|
|
|
|
|
processStyleRule(
|
|
|
|
|
sheet,
|
|
|
|
|
innerRule as CSSStyleRule,
|
|
|
|
|
options,
|
|
|
|
|
mediaMatches
|
|
|
|
|
)
|
|
|
|
|
);
|
2025-06-13 14:07:15 +05:00
|
|
|
}
|
2025-06-14 13:11:10 +05:00
|
|
|
}
|
|
|
|
|
} else if (rule.type === CSSRule.SUPPORTS_RULE) {
|
|
|
|
|
const supportsRule = rule as CSSSupportsRule;
|
|
|
|
|
for (const innerRule of supportsRule.cssRules) {
|
2025-06-13 14:07:15 +05:00
|
|
|
if (innerRule && innerRule.type === CSSRule.STYLE_RULE) {
|
2025-06-14 13:11:10 +05:00
|
|
|
promises.push(
|
|
|
|
|
processStyleRule(sheet, innerRule as CSSStyleRule, options, false)
|
|
|
|
|
);
|
2025-06-13 14:07:15 +05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-14 13:11:10 +05:00
|
|
|
}
|
|
|
|
|
await Promise.allSettled(promises);
|
|
|
|
|
}
|
2025-06-13 14:07:15 +05:00
|
|
|
|
2025-06-14 13:11:10 +05:00
|
|
|
async function processStyleRule(
|
|
|
|
|
sheet: CSSStyleSheet,
|
|
|
|
|
rule: CSSStyleRule,
|
|
|
|
|
options?: FetchOptions,
|
|
|
|
|
inline = true
|
|
|
|
|
) {
|
2025-06-16 12:38:43 +05:00
|
|
|
const baseUrl = sheet.href || document.location.href;
|
2025-06-14 13:11:10 +05:00
|
|
|
for (const property of rule.style) {
|
|
|
|
|
const oldValue = rule.style.getPropertyValue(property);
|
2025-06-16 13:05:21 +05:00
|
|
|
if (!oldValue) continue;
|
2025-06-14 13:11:10 +05:00
|
|
|
const resolved = await inlineAll(oldValue, options, baseUrl, !inline);
|
|
|
|
|
rule.style.setProperty(property, resolved);
|
2025-06-13 14:07:15 +05:00
|
|
|
}
|
|
|
|
|
}
|