mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 15:09:33 +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
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import { isSVGElement } from "./clone.js";
|
|
||||||
import { createImage, FetchOptions } from "./fetch.js";
|
import { createImage, FetchOptions } from "./fetch.js";
|
||||||
import { resolveAll } from "./fontfaces.js";
|
import { resolveAll } from "./fontfaces.js";
|
||||||
import { inlineAllImages } from "./images.js";
|
import { inlineAllImages } from "./images.js";
|
||||||
import { Options } from "./types.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";
|
import { inlineStylesheets } from "./styles.js";
|
||||||
|
|
||||||
// Default impl options
|
// Default impl options
|
||||||
|
|||||||
@@ -455,9 +455,10 @@ async function getPage(
|
|||||||
config?: Config,
|
config?: Config,
|
||||||
onlyVisible = false
|
onlyVisible = false
|
||||||
) {
|
) {
|
||||||
|
const fetchOptions = resolveFetchOptions(config);
|
||||||
const body = await getInlinedNode(document.body, {
|
const body = await getInlinedNode(document.body, {
|
||||||
raster: true,
|
raster: true,
|
||||||
fetchOptions: resolveFetchOptions(config),
|
fetchOptions,
|
||||||
inlineOptions: {
|
inlineOptions: {
|
||||||
fonts: false,
|
fonts: false,
|
||||||
inlineImages: config?.inlineImages,
|
inlineImages: config?.inlineImages,
|
||||||
@@ -478,7 +479,7 @@ async function getPage(
|
|||||||
head.appendChild(title);
|
head.appendChild(title);
|
||||||
|
|
||||||
if (config?.styles) {
|
if (config?.styles) {
|
||||||
await addStylesToHead(head, resolveFetchOptions(config));
|
await addStylesToHead(head, fetchOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
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 {
|
export {
|
||||||
injectCss,
|
injectCss,
|
||||||
escape,
|
escape,
|
||||||
@@ -188,5 +272,6 @@ export {
|
|||||||
escapeXhtml,
|
escapeXhtml,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
safeQuerySelectorAll
|
safeQuerySelectorAll,
|
||||||
|
isSVGElement
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user