diff --git a/packages/clipper/src/clone.ts b/packages/clipper/src/clone.ts
deleted file mode 100644
index 9067b5495..000000000
--- a/packages/clipper/src/clone.ts
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
-This file is part of the Notesnook project (https://notesnook.com/)
-
-Copyright (C) 2023 Streetwriters (Private) Limited
-
-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 .
-*/
-import { createImage, FetchOptions } from "./fetch.js";
-import { Filter } from "./types.js";
-import { uid } from "./utils.js";
-
-const SVGElements = [
- "altGlyph",
- "altGlyphDef",
- "altGlyphItem",
- "animate",
- "animateColor",
- "animateMotion",
- "animateTransform",
- "circle",
- "clipPath",
- "color-profile",
- "cursor",
- "defs",
- "desc",
- "ellipse",
- "feBlend",
- "feColorMatrix",
- "feComponentTransfer",
- "feComposite",
- "feConvolveMatrix",
- "feDiffuseLighting",
- "feDisplacementMap",
- "feDistantLight",
- "feFlood",
- "feFuncA",
- "feFuncB",
- "feFuncG",
- "feFuncR",
- "feGaussianBlur",
- "feImage",
- "feMerge",
- "feMergeNode",
- "feMorphology",
- "feOffset",
- "fePointLight",
- "feSpecularLighting",
- "feSpotLight",
- "feTile",
- "feTurbulence",
- "filter",
- "font-face",
- "font-face-format",
- "font-face-name",
- "font-face-src",
- "font-face-uri",
- "foreignObject",
- "g",
- "glyph",
- "glyphRef",
- "hkern",
- "image",
- "line",
- "linearGradient",
- "marker",
- "mask",
- "metadata",
- "missing-glyph",
- "mpath",
- "path",
- "pattern",
- "polygon",
- "polyline",
- "radialGradient",
- "rect",
- "set",
- "stop",
- "svg",
- "switch",
- "symbol",
- "text",
- "textPath",
- "title",
- "tref",
- "tspan",
- "use",
- "view",
- "vkern"
-].map((a) => a.toLowerCase());
-
-const INVALID_ELEMENTS = ["script"].map((a) => a.toLowerCase());
-
-type CloneProps = {
- filter?: Filter;
- root: boolean;
- vector: boolean;
- styles?: boolean;
- getElementStyles?: (element: HTMLElement) => CSSStyleDeclaration | undefined;
- getPseudoElementStyles?: (
- element: HTMLElement,
- pseudoElement: string
- ) => CSSStyleDeclaration | undefined;
- fetchOptions?: FetchOptions;
- images?: boolean;
-};
-
-export async function cloneNode(node: HTMLElement, options: CloneProps) {
- const { root, filter } = options;
- if (!root && filter && !filter(node)) return null;
-
- let clone = await makeNodeCopy(node, options);
-
- if (!clone) return null;
- clone = await cloneChildren(node, clone, options);
-
- const processed = processClone(node, clone, options);
- return processed;
-}
-
-function makeNodeCopy(original: HTMLElement, options?: CloneProps) {
- try {
- if (original instanceof HTMLCanvasElement && options?.images)
- return createImage(original.toDataURL(), options?.fetchOptions);
-
- if (!options?.images && original instanceof HTMLImageElement) return null;
-
- if (
- !options?.styles &&
- (original instanceof HTMLButtonElement ||
- original instanceof HTMLFormElement ||
- original instanceof HTMLSelectElement ||
- original instanceof HTMLInputElement ||
- original instanceof HTMLTextAreaElement)
- )
- return null;
-
- if (original.nodeType === Node.COMMENT_NODE) return null;
-
- if (isInvalidElement(original)) return null;
-
- if (original.nodeType !== Node.TEXT_NODE && !isSVGElement(original)) {
- const { display, width, height } = window.getComputedStyle(original);
- if (display === "none" || (width === "0px" && height === "0px"))
- return null;
-
- if (isCustomElement(original)) {
- const isInline = display.includes("inline");
- const element = document.createElement(isInline ? "span" : "div");
- for (const attribute of original.attributes) {
- element.setAttribute(attribute.name, attribute.value);
- }
- return element;
- }
- }
-
- return original.cloneNode(false) as HTMLElement;
- } catch (e) {
- console.error("Failed to clone element", e);
- return null;
- }
-}
-
-function isCustomElement(element: HTMLElement) {
- if (!element || !element.tagName) return false;
- return (
- !SVGElements.includes(element.tagName.toLowerCase()) &&
- element.tagName.includes("-")
- );
-}
-
-export function isSVGElement(element: HTMLElement) {
- if (!element || !element.tagName) return false;
- return SVGElements.includes(element.tagName.toLowerCase());
-}
-
-function isInvalidElement(element: HTMLElement) {
- if (!element || !element.tagName) return false;
- return INVALID_ELEMENTS.includes(element.tagName.toLowerCase());
-}
-
-async function cloneChildren(
- original: HTMLElement,
- clone: HTMLElement,
- options: CloneProps
-) {
- const children = original.childNodes;
- if (children.length === 0) return clone;
-
- await cloneChildrenInOrder(clone, children, options);
- return clone;
-}
-
-async function cloneChildrenInOrder(
- parent: HTMLElement,
- childs: NodeListOf,
- options: CloneProps
-) {
- for (const node of childs) {
- const childClone = await cloneNode(node as HTMLElement, {
- ...options,
- root: false
- });
- if (childClone) parent.appendChild(childClone);
- }
-}
-
-function processClone(
- original: HTMLElement,
- clone: HTMLElement,
- options: CloneProps
-) {
- if (!(clone instanceof Element)) return clone;
-
- // if (clone instanceof HTMLElement) removeAttributes(clone);
-
- if (options.styles) {
- copyStyle(original, clone, options);
- clonePseudoElements(original, clone, options);
- }
- fixRelativeUrl(clone);
- copyUserInput(original, clone);
- fixSvg(clone);
- return clone;
-}
-
-function fixRelativeUrl(node: HTMLElement) {
- const attributes = ["href", "src"];
- const baseUrl = window.location.href;
- for (const attribute of attributes) {
- const url = node.getAttribute(attribute);
- const relativeUrl = url?.startsWith("http") ? undefined : url;
- if (relativeUrl) {
- const absoluteUrl = new URL(relativeUrl, baseUrl).href;
- node.setAttribute(attribute, absoluteUrl);
- }
- }
-}
-
-function copyFont(source: CSSStyleDeclaration, target: CSSStyleDeclaration) {
- target.font = source.font;
- target.fontFamily = source.fontFamily;
- target.fontFeatureSettings = source.fontFeatureSettings;
- target.fontKerning = source.fontKerning;
- target.fontSize = source.fontSize;
- target.fontStretch = source.fontStretch;
- target.fontStyle = source.fontStyle;
- target.fontVariant = source.fontVariant;
- target.fontVariantCaps = source.fontVariantCaps;
- target.fontVariantEastAsian = source.fontVariantEastAsian;
- target.fontVariantLigatures = source.fontVariantLigatures;
- target.fontVariantNumeric = source.fontVariantNumeric;
- target.fontVariationSettings = source.fontVariationSettings;
- target.fontWeight = source.fontWeight;
-}
-
-function copyStyle(
- sourceElement: HTMLElement,
- targetElement: HTMLElement,
- options: CloneProps
-) {
- const { getElementStyles } = options;
- const sourceComputedStyles =
- getElementStyles && getElementStyles(sourceElement);
- if (!sourceComputedStyles) return;
-
- targetElement.style.cssText = sourceComputedStyles.cssText;
-
- if (sourceElement.tagName.toLowerCase() === "body") {
- copyFont(getComputedStyle(sourceElement), targetElement.style);
- }
-
- const styles = targetElement.getAttribute("style");
- if (styles) targetElement.setAttribute("style", minifyStyles(styles));
-}
-
-function clonePseudoElements(
- original: HTMLElement,
- clone: HTMLElement,
- options: CloneProps
-) {
- const { getPseudoElementStyles } = options;
- let hasPseudoElements = false;
-
- const styleElement = document.createElement("style");
- const className = `pseudo--${uid()}`;
-
- for (const element of [":before", ":after"]) {
- const style =
- (getPseudoElementStyles && getPseudoElementStyles(original, element)) ||
- getComputedStyle(original, element);
-
- if (!style.cssText) continue;
-
- const selector = `.${className}:${element} {
- ${style.cssText}
- }`;
-
- styleElement.appendChild(document.createTextNode(selector));
- hasPseudoElements = true;
- }
-
- if (hasPseudoElements) {
- clone.className = className;
- clone.appendChild(styleElement);
- }
-
- return hasPseudoElements;
-}
-
-function copyUserInput(original: HTMLElement, clone: HTMLElement) {
- if (
- original instanceof HTMLInputElement ||
- original instanceof HTMLTextAreaElement
- )
- clone.setAttribute("value", original.value);
-}
-
-function fixSvg(clone: Element) {
- if (!(clone instanceof SVGElement)) return;
- clone.setAttribute("xmlns", "http://www.w3.org/2000/svg");
-
- // if (!(clone instanceof SVGRectElement)) return;
- ["width", "height"].forEach(function (attribute) {
- const value = clone.getAttribute(attribute);
- if (!value || !!clone.style.getPropertyValue(attribute)) return;
-
- clone.style.setProperty(attribute, value);
- });
-}
-
-function minifyStyles(text: string) {
- return text.replace(/(:?[:;])(:? +)/gm, (_full, sep) => {
- return sep;
- });
-}
diff --git a/packages/clipper/src/domtoimage.ts b/packages/clipper/src/domtoimage.ts
index ed57a7a90..bd41553ac 100644
--- a/packages/clipper/src/domtoimage.ts
+++ b/packages/clipper/src/domtoimage.ts
@@ -16,12 +16,18 @@ 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 .
*/
-import { isSVGElement } from "./clone.js";
import { createImage, FetchOptions } from "./fetch.js";
import { resolveAll } from "./fontfaces.js";
import { inlineAllImages } from "./images.js";
import { Options } from "./types.js";
-import { canvasToBlob, delay, escapeXhtml, height, width } from "./utils.js";
+import {
+ canvasToBlob,
+ delay,
+ escapeXhtml,
+ height,
+ width,
+ isSVGElement
+} from "./utils.js";
import { inlineStylesheets } from "./styles.js";
// Default impl options
diff --git a/packages/clipper/src/index.ts b/packages/clipper/src/index.ts
index c01c1f382..051a17bd4 100644
--- a/packages/clipper/src/index.ts
+++ b/packages/clipper/src/index.ts
@@ -455,9 +455,10 @@ async function getPage(
config?: Config,
onlyVisible = false
) {
+ const fetchOptions = resolveFetchOptions(config);
const body = await getInlinedNode(document.body, {
raster: true,
- fetchOptions: resolveFetchOptions(config),
+ fetchOptions,
inlineOptions: {
fonts: false,
inlineImages: config?.inlineImages,
@@ -478,7 +479,7 @@ async function getPage(
head.appendChild(title);
if (config?.styles) {
- await addStylesToHead(head, resolveFetchOptions(config));
+ await addStylesToHead(head, fetchOptions);
}
return {
diff --git a/packages/clipper/src/utils.ts b/packages/clipper/src/utils.ts
index 8e3432551..e9e570006 100644
--- a/packages/clipper/src/utils.ts
+++ b/packages/clipper/src/utils.ts
@@ -173,6 +173,90 @@ function safeQuerySelectorAll(root: Node, selector: string) {
}
}
+const SVGElements = [
+ "altGlyph",
+ "altGlyphDef",
+ "altGlyphItem",
+ "animate",
+ "animateColor",
+ "animateMotion",
+ "animateTransform",
+ "circle",
+ "clipPath",
+ "color-profile",
+ "cursor",
+ "defs",
+ "desc",
+ "ellipse",
+ "feBlend",
+ "feColorMatrix",
+ "feComponentTransfer",
+ "feComposite",
+ "feConvolveMatrix",
+ "feDiffuseLighting",
+ "feDisplacementMap",
+ "feDistantLight",
+ "feFlood",
+ "feFuncA",
+ "feFuncB",
+ "feFuncG",
+ "feFuncR",
+ "feGaussianBlur",
+ "feImage",
+ "feMerge",
+ "feMergeNode",
+ "feMorphology",
+ "feOffset",
+ "fePointLight",
+ "feSpecularLighting",
+ "feSpotLight",
+ "feTile",
+ "feTurbulence",
+ "filter",
+ "font-face",
+ "font-face-format",
+ "font-face-name",
+ "font-face-src",
+ "font-face-uri",
+ "foreignObject",
+ "g",
+ "glyph",
+ "glyphRef",
+ "hkern",
+ "image",
+ "line",
+ "linearGradient",
+ "marker",
+ "mask",
+ "metadata",
+ "missing-glyph",
+ "mpath",
+ "path",
+ "pattern",
+ "polygon",
+ "polyline",
+ "radialGradient",
+ "rect",
+ "set",
+ "stop",
+ "svg",
+ "switch",
+ "symbol",
+ "text",
+ "textPath",
+ "title",
+ "tref",
+ "tspan",
+ "use",
+ "view",
+ "vkern"
+].map((a) => a.toLowerCase());
+
+function isSVGElement(element: HTMLElement) {
+ if (!element || !element.tagName) return false;
+ return SVGElements.includes(element.tagName.toLowerCase());
+}
+
export {
injectCss,
escape,
@@ -188,5 +272,6 @@ export {
escapeXhtml,
width,
height,
- safeQuerySelectorAll
+ safeQuerySelectorAll,
+ isSVGElement
};