clipper: bring back node processing

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
This commit is contained in:
01zulfi
2025-06-12 10:23:40 +05:00
committed by Abdullah Atta
parent 0b56121a26
commit a4b0e5bfef
3 changed files with 190 additions and 100 deletions

View File

@@ -0,0 +1,183 @@
/*
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/>.
*/
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 CloneNodeOptions = {
images?: boolean;
styles?: boolean;
};
export function cloneNode(node: HTMLElement, options: CloneNodeOptions) {
const clone = node.cloneNode(true) as HTMLElement;
processNode(clone, options);
return clone;
}
function processNode(node: HTMLElement, options: CloneNodeOptions) {
try {
if (!options.images && node instanceof HTMLImageElement) {
node.remove();
return;
}
if (
!options.styles &&
(node instanceof HTMLButtonElement ||
node instanceof HTMLFormElement ||
node instanceof HTMLSelectElement ||
node instanceof HTMLInputElement ||
node instanceof HTMLTextAreaElement)
) {
node.remove();
return;
}
if (node.nodeType === Node.COMMENT_NODE) {
node.remove();
return;
}
if (isInvalidElement(node)) {
node.remove();
return;
}
if (node.nodeType !== Node.TEXT_NODE && !isSVGElement(node)) {
const { display, width, height } = window.getComputedStyle(node);
if (display === "none" || (width === "0px" && height === "0px")) {
node.remove();
return;
}
if (isCustomElement(node)) {
const isInline = display.includes("inline");
const element = document.createElement(isInline ? "span" : "div");
for (const attribute of node.attributes) {
element.setAttribute(attribute.name, attribute.value);
}
node.replaceWith(element);
}
}
node.childNodes.forEach((child) =>
processNode(child as HTMLElement, options)
);
} catch (e) {
console.error("Failed to process node", e);
return null;
}
}
function isInvalidElement(element: HTMLElement) {
if (!element || !element.tagName) return false;
return INVALID_ELEMENTS.includes(element.tagName.toLowerCase());
}
export function isSVGElement(element: HTMLElement) {
if (!element || !element.tagName) return false;
return SVGElements.includes(element.tagName.toLowerCase());
}
function isCustomElement(element: HTMLElement) {
if (!element || !element.tagName) return false;
return (
!SVGElements.includes(element.tagName.toLowerCase()) &&
element.tagName.includes("-")
);
}

View File

@@ -20,15 +20,9 @@ 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,
isSVGElement
} from "./utils.js";
import { canvasToBlob, delay, escapeXhtml, height, width } from "./utils.js";
import { inlineStylesheets } from "./styles.js";
import { cloneNode, isSVGElement } from "./clone.js";
// Default impl options
const defaultOptions: Options = {
@@ -41,15 +35,13 @@ async function getInlinedNode(node: HTMLElement, options: Options) {
if (stylesheets) await inlineStylesheets(options.fetchOptions);
let clone = node.cloneNode(true) as HTMLElement;
let clone = cloneNode(node, {
images,
styles: options.styles
});
if (!clone || clone instanceof Text) return;
if (!images) {
const images = clone.querySelectorAll("img");
images.forEach((image) => image.remove());
}
if (fonts) clone = await embedFonts(clone, options.fetchOptions);
if (inlineImages) await inlineAllImages(clone, options.fetchOptions);

View File

@@ -165,90 +165,6 @@ function getRootStylesheet() {
return null;
}
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,
@@ -263,6 +179,5 @@ export {
asArray,
escapeXhtml,
width,
height,
isSVGElement
height
};