mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 06:59:31 +01:00
clipper: remove clone related functions
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
This commit is contained in:
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<ChildNode>,
|
||||
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;
|
||||
});
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user