Files
notesnook/packages/clipper/src/utils.ts
2025-10-25 15:10:21 +05:00

269 lines
5.9 KiB
TypeScript

/*
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/>.
*/
/*
* Only WOFF and EOT mime types for fonts are 'real'
* see http://www.iana.org/assignments/media-types/media-types.xhtml
*/
const WOFF = "application/font-woff";
const JPEG = "image/jpeg";
const mimes = {
woff: WOFF,
woff2: WOFF,
ttf: "application/font-truetype",
eot: "application/vnd.ms-fontobject",
png: "image/png",
jpg: JPEG,
jpeg: JPEG,
gif: "image/gif",
tiff: "image/tiff",
svg: "image/svg+xml"
};
function parseExtension(url: string) {
const match = /\.([^./]*?)(\?|$)/g.exec(url);
if (match) return match[1];
else return "";
}
function mimeType(url: string) {
const extension = parseExtension(url).toLowerCase();
return mimes[extension as keyof typeof mimes] || "";
}
function isDataUrl(url: string) {
return url.search(/^(data:)/) !== -1;
}
function asBlob(canvas: HTMLCanvasElement) {
const binaryString = atob(canvas.toDataURL().split(",")[1]);
const length = binaryString.length;
const binaryArray = new Uint8Array(length);
for (let i = 0; i < length; i++) binaryArray[i] = binaryString.charCodeAt(i);
return new Blob([binaryArray], {
type: "image/png"
});
}
function canvasToBlob(canvas: HTMLCanvasElement) {
if (canvas.toBlob)
return new Promise<Blob | null>(function (resolve) {
canvas.toBlob(resolve);
});
return Promise.resolve(asBlob(canvas));
}
function resolveUrl(url: string, baseUrl: string) {
const doc = document.implementation.createHTMLDocument();
const base = doc.createElement("base");
doc.head.appendChild(base);
const a = doc.createElement("a");
doc.body.appendChild(a);
base.href = baseUrl;
a.href = url;
return a.href;
}
let index = 0;
function uid() {
return "u" + fourRandomChars() + index++;
function fourRandomChars() {
/* see http://stackoverflow.com/a/6248722/2519373 */
return (
"0000" + ((Math.random() * Math.pow(36, 4)) << 0).toString(36)
).slice(-4);
}
}
function dataAsUrl(content: string, type: string) {
return "data:" + type + ";base64," + content;
}
function escape(string: string) {
return string.replace(/([.*+?^${}()|[\]/\\])/g, "\\$1");
}
function delay(ms: number) {
return function <T>(arg: T) {
return new Promise<T>(function (resolve) {
setTimeout(function () {
resolve(arg);
}, ms);
});
};
}
function asArray<T>(arrayLike: ArrayLike<T>) {
const array = [];
const length = arrayLike.length;
for (let i = 0; i < length; i++) array.push(arrayLike[i]);
return array;
}
function escapeXhtml(string: string) {
return string.replace(/%/g, "%25").replace(/#/g, "%23").replace(/\n/g, "%0A");
}
function width(node: HTMLElement) {
const leftBorder = px(node, "border-left-width");
const rightBorder = px(node, "border-right-width");
return node.scrollWidth + leftBorder + rightBorder;
}
function height(node: HTMLElement) {
const topBorder = px(node, "border-top-width");
const bottomBorder = px(node, "border-bottom-width");
return node.scrollHeight + topBorder + bottomBorder;
}
function px(node: HTMLElement, styleProperty: string) {
const value = getComputedStyle(node).getPropertyValue(styleProperty);
return parseFloat(value.replace("px", ""));
}
function injectCss(rules: string, id: string) {
const variableCss = document.getElementById(id);
const head = document.getElementsByTagName("head")[0];
if (variableCss) {
head.removeChild(variableCss);
}
const css = document.createElement("style");
css.type = "text/css";
css.id = id;
css.appendChild(document.createTextNode(rules));
head.insertBefore(css, getRootStylesheet());
}
function getRootStylesheet() {
for (const sty of document.querySelectorAll("style")) {
if (sty.innerHTML.includes("#root")) {
return sty;
}
}
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,
parseExtension,
mimeType,
dataAsUrl,
isDataUrl,
canvasToBlob,
resolveUrl,
uid,
delay,
asArray,
escapeXhtml,
width,
height,
isSVGElement
};