Compare commits

..

2 Commits

Author SHA1 Message Date
dwelle dd27e48c13 docs: release @excalidraw/excalidraw@0.15.3 2023-08-16 15:17:47 +02:00
Christopher Chedeau 139b901ff1 fix: stronger enforcement of normalizeLink (#6728)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-08-16 15:14:55 +02:00
20 changed files with 145 additions and 213 deletions
+1 -1
View File
@@ -18,7 +18,7 @@
"@docusaurus/core": "2.2.0",
"@docusaurus/preset-classic": "2.2.0",
"@docusaurus/theme-live-codeblock": "2.2.0",
"@excalidraw/excalidraw": "0.15.2",
"@excalidraw/excalidraw": "0.15.0",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
"docusaurus-plugin-sass": "0.2.3",
+4 -4
View File
@@ -1631,10 +1631,10 @@
url-loader "^4.1.1"
webpack "^5.73.0"
"@excalidraw/excalidraw@0.15.2":
version "0.15.2"
resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.15.2.tgz#7dba4f6e10c52015a007efb75a9fc1afe598574c"
integrity sha512-rTI02kgWSTXiUdIkBxt9u/581F3eXcqQgJdIxmz54TFtG3ughoxO5fr4t7Fr2LZIturBPqfocQHGKZ0t2KLKgw==
"@excalidraw/excalidraw@0.15.0":
version "0.15.0"
resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.15.0.tgz#47170de8d3ff006e9d09dfede2815682b0d4485b"
integrity sha512-PJmh1VcuRHG4l+Zgt9qhezxrJ16tYCZFZ8if5IEfmTL9A/7c5mXxY/qrPTqiGlVC7jYs+ciePXQ0YUDzfOfbzw==
"@hapi/hoek@^9.0.0":
version "9.3.0"
+1
View File
@@ -20,6 +20,7 @@
},
"dependencies": {
"@dwelle/tunnel-rat": "0.1.1",
"@braintree/sanitize-url": "6.0.2",
"@sentry/browser": "6.2.5",
"@sentry/integrations": "6.2.5",
"@testing-library/jest-dom": "5.16.2",
+6 -6
View File
@@ -10,7 +10,7 @@ import {
computeBoundTextPosition,
computeContainerDimensionForBoundText,
getBoundTextElement,
measureTextElement,
measureText,
redrawTextBoundingBox,
} from "../element/textElement";
import {
@@ -31,6 +31,7 @@ import {
} from "../element/types";
import { getSelectedElements } from "../scene";
import { AppState } from "../types";
import { getFontString } from "../utils";
import { register } from "./register";
export const actionUnbindText = register({
@@ -50,11 +51,10 @@ export const actionUnbindText = register({
selectedElements.forEach((element) => {
const boundTextElement = getBoundTextElement(element);
if (boundTextElement) {
const { width, height, baseline } = measureTextElement(
boundTextElement,
{
text: boundTextElement.originalText,
},
const { width, height, baseline } = measureText(
boundTextElement.originalText,
getFontString(boundTextElement),
boundTextElement.lineHeight,
);
const originalContainerHeight = getOriginalContainerHeightFromCache(
element.id,
+11 -5
View File
@@ -279,13 +279,12 @@ import {
} from "../element/textElement";
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
import {
normalizeLink,
showHyperlinkTooltip,
hideHyperlinkToolip,
Hyperlink,
isPointHittingLinkIcon,
isLocalLink,
} from "../element/Hyperlink";
import { isLocalLink, normalizeLink } from "../data/url";
import { shouldShowBoundingBox } from "../element/transformHandles";
import { Fonts } from "../scene/Fonts";
import { actionPaste } from "../actions/actionClipboard";
@@ -2947,12 +2946,19 @@ class App extends React.Component<AppProps, AppState> {
this.device.isMobile,
);
if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
const url = this.hitLinkElement.link;
let url = this.hitLinkElement.link;
if (url) {
url = normalizeLink(url);
let customEvent;
if (this.props.onLinkOpen) {
customEvent = wrapEvent(EVENT.EXCALIDRAW_LINK, event.nativeEvent);
this.props.onLinkOpen(this.hitLinkElement, customEvent);
this.props.onLinkOpen(
{
...this.hitLinkElement,
link: url,
},
customEvent,
);
}
if (!customEvent?.defaultPrevented) {
const target = isLocalLink(url) ? "_self" : "_blank";
@@ -2960,7 +2966,7 @@ class App extends React.Component<AppProps, AppState> {
// https://mathiasbynens.github.io/rel-noopener/
if (newWindow) {
newWindow.opener = null;
newWindow.location = normalizeLink(url);
newWindow.location = url;
}
}
}
+10 -9
View File
@@ -31,15 +31,16 @@ import {
import { getDefaultAppState } from "../appState";
import { LinearElementEditor } from "../element/linearElementEditor";
import { bumpVersion } from "../element/mutateElement";
import { getUpdatedTimestamp, updateActiveTool } from "../utils";
import { getFontString, getUpdatedTimestamp, updateActiveTool } from "../utils";
import { arrayToMap } from "../utils";
import oc from "open-color";
import { MarkOptional, Mutable } from "../utility-types";
import {
detectLineHeight,
getDefaultLineHeight,
measureTextElement,
measureBaseline,
} from "../element/textElement";
import { normalizeLink } from "./url";
type RestoredAppState = Omit<
AppState,
@@ -80,8 +81,7 @@ const getFontFamilyByName = (fontFamilyName: string): FontFamilyValues => {
};
const restoreElementWithProperties = <
T extends Required<Omit<ExcalidrawElement, "subtype" | "customData">> & {
subtype?: ExcalidrawElement["subtype"];
T extends Required<Omit<ExcalidrawElement, "customData">> & {
customData?: ExcalidrawElement["customData"];
/** @deprecated */
boundElementIds?: readonly ExcalidrawElement["id"][];
@@ -140,13 +140,10 @@ const restoreElementWithProperties = <
? element.boundElementIds.map((id) => ({ type: "arrow", id }))
: element.boundElements ?? [],
updated: element.updated ?? getUpdatedTimestamp(),
link: element.link ?? null,
link: element.link ? normalizeLink(element.link) : null,
locked: element.locked ?? false,
};
if ("subtype" in element) {
base.subtype = element.subtype;
}
if ("customData" in element) {
base.customData = element.customData;
}
@@ -192,7 +189,11 @@ const restoreElement = (
: // no element height likely means programmatic use, so default
// to a fixed line height
getDefaultLineHeight(element.fontFamily));
const baseline = measureTextElement(element, { text }).baseline;
const baseline = measureBaseline(
element.text,
getFontString(element),
lineHeight,
);
element = restoreElementWithProperties(element, {
fontSize,
fontFamily,
+30
View File
@@ -0,0 +1,30 @@
import { normalizeLink } from "./url";
describe("normalizeLink", () => {
// NOTE not an extensive XSS test suite, just to check if we're not
// regressing in sanitization
it("should sanitize links", () => {
expect(
// eslint-disable-next-line no-script-url
normalizeLink(`javascript://%0aalert(document.domain)`).startsWith(
// eslint-disable-next-line no-script-url
`javascript:`,
),
).toBe(false);
expect(normalizeLink("ola")).toBe("ola");
expect(normalizeLink(" ola")).toBe("ola");
expect(normalizeLink("https://www.excalidraw.com")).toBe(
"https://www.excalidraw.com",
);
expect(normalizeLink("www.excalidraw.com")).toBe("www.excalidraw.com");
expect(normalizeLink("/ola")).toBe("/ola");
expect(normalizeLink("http://test")).toBe("http://test");
expect(normalizeLink("ftp://test")).toBe("ftp://test");
expect(normalizeLink("file://")).toBe("file://");
expect(normalizeLink("file://")).toBe("file://");
expect(normalizeLink("[test](https://test)")).toBe("[test](https://test)");
expect(normalizeLink("[[test]]")).toBe("[[test]]");
expect(normalizeLink("<test>")).toBe("<test>");
});
});
+9
View File
@@ -0,0 +1,9 @@
import { sanitizeUrl } from "@braintree/sanitize-url";
export const normalizeLink = (link: string) => {
return sanitizeUrl(link);
};
export const isLocalLink = (link: string | null) => {
return !!(link?.includes(location.origin) || link?.startsWith("/"));
};
+9 -17
View File
@@ -29,6 +29,7 @@ import { getTooltipDiv, updateTooltipPosition } from "../components/Tooltip";
import { getSelectedElements } from "../scene";
import { isPointHittingElementBoundingBox } from "./collision";
import { getElementAbsoluteCoords } from "./";
import { isLocalLink, normalizeLink } from "../data/url";
import "./Hyperlink.scss";
import { trackEvent } from "../analytics";
@@ -166,7 +167,7 @@ export const Hyperlink = ({
/>
) : (
<a
href={element.link || ""}
href={normalizeLink(element.link || "")}
className={clsx("excalidraw-hyperlinkContainer-link", {
"d-none": isEditing,
})}
@@ -177,7 +178,13 @@ export const Hyperlink = ({
EVENT.EXCALIDRAW_LINK,
event.nativeEvent,
);
onLinkOpen(element, customEvent);
onLinkOpen(
{
...element,
link: normalizeLink(element.link),
},
customEvent,
);
if (customEvent.defaultPrevented) {
event.preventDefault();
}
@@ -231,21 +238,6 @@ const getCoordsForPopover = (
return { x, y };
};
export const normalizeLink = (link: string) => {
link = link.trim();
if (link) {
// prefix with protocol if not fully-qualified
if (!link.includes("://") && !/^[[\\/]/.test(link)) {
link = `https://${link}`;
}
}
return link;
};
export const isLocalLink = (link: string | null) => {
return !!(link?.includes(location.origin) || link?.startsWith("/"));
};
export const actionLink = register({
name: "hyperlink",
perform: (elements, appState) => {
+22 -17
View File
@@ -13,7 +13,12 @@ import {
FontFamilyValues,
ExcalidrawTextContainer,
} from "../element/types";
import { arrayToMap, getUpdatedTimestamp, isTestEnv } from "../utils";
import {
arrayToMap,
getFontString,
getUpdatedTimestamp,
isTestEnv,
} from "../utils";
import { randomInteger, randomId } from "../random";
import { mutateElement, newElementWith } from "./mutateElement";
import { getNewGroupIdsForDuplication } from "../groups";
@@ -25,9 +30,9 @@ import {
getBoundTextElementOffset,
getContainerDims,
getContainerElement,
measureTextElement,
measureText,
normalizeText,
wrapTextElement,
wrapText,
getMaxContainerWidth,
getDefaultLineHeight,
} from "./textElement";
@@ -53,8 +58,6 @@ type ElementConstructorOpts = MarkOptional<
| "version"
| "versionNonce"
| "link"
| "subtype"
| "customData"
| "strokeStyle"
| "fillStyle"
| "strokeColor"
@@ -164,12 +167,10 @@ export const newTextElement = (
const fontSize = opts.fontSize || DEFAULT_FONT_SIZE;
const lineHeight = opts.lineHeight || getDefaultLineHeight(fontFamily);
const text = normalizeText(opts.text);
const metrics = measureTextElement(
{ ...opts, fontSize, fontFamily, lineHeight },
{
text,
customData: opts.customData,
},
const metrics = measureText(
text,
getFontString({ fontFamily, fontSize }),
lineHeight,
);
const textAlign = opts.textAlign || DEFAULT_TEXT_ALIGN;
const verticalAlign = opts.verticalAlign || DEFAULT_VERTICAL_ALIGN;
@@ -216,9 +217,7 @@ const getAdjustedDimensions = (
width: nextWidth,
height: nextHeight,
baseline: nextBaseline,
} = measureTextElement(element, {
text: nextText,
});
} = measureText(nextText, getFontString(element), element.lineHeight);
const { textAlign, verticalAlign } = element;
let x: number;
let y: number;
@@ -227,7 +226,11 @@ const getAdjustedDimensions = (
verticalAlign === VERTICAL_ALIGN.MIDDLE &&
!element.containerId
) {
const prevMetrics = measureTextElement(element);
const prevMetrics = measureText(
element.text,
getFontString(element),
element.lineHeight,
);
const offsets = getTextElementPositionOffsets(element, {
width: nextWidth - prevMetrics.width,
height: nextHeight - prevMetrics.height,
@@ -304,9 +307,11 @@ export const refreshTextDimensions = (
}
const container = getContainerElement(textElement);
if (container) {
text = wrapTextElement(textElement, getMaxContainerWidth(container), {
text = wrapText(
text,
});
getFontString(textElement),
getMaxContainerWidth(container),
);
}
const dimensions = getAdjustedDimensions(textElement, text);
return { text, ...dimensions };
+20 -39
View File
@@ -1,4 +1,3 @@
import { getSubtypeMethods, SubtypeMethods } from "../subtypes";
import { getFontString, arrayToMap, isTestEnv } from "../utils";
import {
ExcalidrawElement,
@@ -35,30 +34,6 @@ import {
} from "./textWysiwyg";
import { ExtractSetType } from "../utility-types";
export const measureTextElement = function (element, next) {
const map = getSubtypeMethods(element.subtype);
if (map?.measureText) {
return map.measureText(element, next);
}
const fontSize = next?.fontSize ?? element.fontSize;
const font = getFontString({ fontSize, fontFamily: element.fontFamily });
const text = next?.text ?? element.text;
return measureText(text, font, element.lineHeight);
} as SubtypeMethods["measureText"];
export const wrapTextElement = function (element, containerWidth, next) {
const map = getSubtypeMethods(element.subtype);
if (map?.wrapText) {
return map.wrapText(element, containerWidth, next);
}
const fontSize = next?.fontSize ?? element.fontSize;
const font = getFontString({ fontSize, fontFamily: element.fontFamily });
const text = next?.text ?? element.originalText;
return wrapText(text, font, containerWidth);
} as SubtypeMethods["wrapText"];
export const normalizeText = (text: string) => {
return (
text
@@ -91,24 +66,22 @@ export const redrawTextBoundingBox = (
if (container) {
maxWidth = getMaxContainerWidth(container);
boundTextUpdates.text = wrapTextElement(textElement, maxWidth);
boundTextUpdates.text = wrapText(
textElement.originalText,
getFontString(textElement),
maxWidth,
);
}
const metrics = measureTextElement(textElement, {
text: boundTextUpdates.text,
});
const metrics = measureText(
boundTextUpdates.text,
getFontString(textElement),
textElement.lineHeight,
);
boundTextUpdates.width = metrics.width;
boundTextUpdates.height = metrics.height;
boundTextUpdates.baseline = metrics.baseline;
// Maintain coordX for non left-aligned text in case the width has changed
if (!container) {
if (textElement.textAlign === TEXT_ALIGN.RIGHT) {
boundTextUpdates.x += textElement.width - metrics.width;
} else if (textElement.textAlign === TEXT_ALIGN.CENTER) {
boundTextUpdates.x += textElement.width / 2 - metrics.width / 2;
}
}
if (container) {
if (isArrowElement(container)) {
const centerX = textElement.x + textElement.width / 2;
@@ -216,9 +189,17 @@ export const handleBindTextResize = (
let nextBaseLine = textElement.baseline;
if (transformHandleType !== "n" && transformHandleType !== "s") {
if (text) {
text = wrapTextElement(textElement, maxWidth);
text = wrapText(
textElement.originalText,
getFontString(textElement),
maxWidth,
);
}
const metrics = measureTextElement(textElement, { text });
const metrics = measureText(
text,
getFontString(textElement),
textElement.lineHeight,
);
nextHeight = metrics.height;
nextWidth = metrics.width;
nextBaseLine = metrics.baseline;
+8 -58
View File
@@ -47,7 +47,6 @@ import { LinearElementEditor } from "./linearElementEditor";
import { parseClipboard } from "../clipboard";
const getTransform = (
offsetX: number,
width: number,
height: number,
angle: number,
@@ -65,7 +64,7 @@ const getTransform = (
if (height > maxHeight && zoom.value !== 1) {
translateY = (maxHeight * (zoom.value - 1)) / 2;
}
return `translate(${translateX}px, ${translateY}px) scale(${zoom.value}) rotate(${degree}deg) translate(${offsetX}px, 0px)`;
return `translate(${translateX}px, ${translateY}px) scale(${zoom.value}) rotate(${degree}deg)`;
};
const originalContainerCache: {
@@ -159,30 +158,13 @@ export const textWysiwyg = ({
const container = getContainerElement(updatedTextElement);
let maxWidth = updatedTextElement.width;
// Editing metrics
const eMetrics = measureText(
container && updatedTextElement.containerId
? wrapText(
updatedTextElement.originalText,
getFontString(updatedTextElement),
getMaxContainerWidth(container),
)
: updatedTextElement.originalText,
getFontString(updatedTextElement),
updatedTextElement.lineHeight,
);
let maxHeight = eMetrics.height;
let textElementWidth = Math.max(updatedTextElement.width, eMetrics.width);
let maxHeight = updatedTextElement.height;
let textElementWidth = updatedTextElement.width;
// Set to element height by default since that's
// what is going to be used for unbounded text
let textElementHeight = Math.max(updatedTextElement.height, maxHeight);
let textElementHeight = updatedTextElement.height;
if (container && updatedTextElement.containerId) {
textElementHeight = Math.min(
getMaxContainerHeight(container),
textElementHeight,
);
if (isArrowElement(container)) {
const boundTextCoords =
LinearElementEditor.getBoundTextElementPosition(
@@ -191,8 +173,6 @@ export const textWysiwyg = ({
);
coordX = boundTextCoords.x;
coordY = boundTextCoords.y;
} else {
coordX = Math.max(coordX, getContainerCoords(container).x);
}
const propertiesUpdated = textPropertiesUpdated(
updatedTextElement,
@@ -206,18 +186,7 @@ export const textWysiwyg = ({
}
if (propertiesUpdated) {
// update height of the editor after properties updated
const font = getFontString(updatedTextElement);
textElementHeight =
updatedTextElement.lineHeight *
wrapText(
updatedTextElement.originalText,
font,
getMaxContainerWidth(container),
).split("\n").length;
textElementHeight = Math.max(
textElementHeight,
updatedTextElement.height,
);
textElementHeight = updatedTextElement.height;
}
let originalContainerData;
@@ -297,29 +266,12 @@ export const textWysiwyg = ({
editable.selectionEnd = editable.value.length - diff;
}
let transformWidth = updatedTextElement.width;
if (!container) {
maxWidth = (appState.width - 8 - viewportX) / appState.zoom.value;
textElementWidth = Math.min(textElementWidth, maxWidth);
} else {
textElementWidth += 0.5;
transformWidth += 0.5;
}
// Horizontal offset in case updatedTextElement has a non-WYSIWYG subtype
const offWidth = container
? Math.min(
0,
updatedTextElement.width - Math.min(maxWidth, eMetrics.width),
)
: Math.min(maxWidth, updatedTextElement.width) -
Math.min(maxWidth, eMetrics.width);
const offsetX =
textAlign === "right"
? offWidth
: textAlign === "center"
? offWidth / 2
: 0;
const { width: w, height: h } = updatedTextElement;
let lineHeight = updatedTextElement.lineHeight;
@@ -338,15 +290,13 @@ export const textWysiwyg = ({
font: getFontString(updatedTextElement),
// must be defined *after* font ¯\_(ツ)_/¯
lineHeight,
width: `${Math.min(textElementWidth, maxWidth)}px`,
width: `${textElementWidth}px`,
height: `${textElementHeight}px`,
left: `${viewportX}px`,
top: `${viewportY}px`,
transformOrigin: `${w / 2}px ${h / 2}px`,
transform: getTransform(
offsetX,
transformWidth,
updatedTextElement.height,
textElementWidth,
textElementHeight,
getTextElementAngle(updatedTextElement),
appState,
maxWidth,
-2
View File
@@ -1,4 +1,3 @@
import { Subtype } from "../subtypes";
import { Point } from "../types";
import {
FONT_FAMILY,
@@ -65,7 +64,6 @@ type _ExcalidrawElementBase = Readonly<{
updated: number;
link: string | null;
locked: boolean;
subtype?: Subtype;
customData?: Record<string, any>;
}>;
+4
View File
@@ -11,6 +11,10 @@ The change should be grouped under one of the below section and must contain PR
Please add the latest change on the top under the correct section.
-->
## 0.15.3 (2023-08-16)
- Properly sanitize element `link` urls. [#6728](https://github.com/excalidraw/excalidraw/pull/6728).
## 0.15.2 (2023-04-20)
### Docs
+1
View File
@@ -245,3 +245,4 @@ export { MainMenu };
export { useDevice } from "../../components/App";
export { WelcomeScreen };
export { LiveCollaborationTrigger };
export { normalizeLink } from "../../data/url";
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@excalidraw/excalidraw",
"version": "0.15.2",
"version": "0.15.3",
"main": "main.js",
"types": "types/packages/excalidraw/index.d.ts",
"files": [
+2 -1
View File
@@ -48,6 +48,7 @@ import {
getMaxContainerWidth,
} from "../element/textElement";
import { LinearElementEditor } from "../element/linearElementEditor";
import { normalizeLink } from "../data/url";
// using a stronger invert (100% vs our regular 93%) and saturate
// as a temp hack to make images in dark theme look closer to original
@@ -1140,7 +1141,7 @@ export const renderElementToSvg = (
// if the element has a link, create an anchor tag and make that the new root
if (element.link) {
const anchorTag = svgRoot.ownerDocument!.createElementNS(SVG_NS, "a");
anchorTag.setAttribute("href", element.link);
anchorTag.setAttribute("href", normalizeLink(element.link));
root.appendChild(anchorTag);
root = anchorTag;
}
-52
View File
@@ -1,52 +0,0 @@
import { ExcalidrawElement, ExcalidrawTextElement } from "./element/types";
// Subtype Names
export type Subtype = string;
// Subtype Methods
export type SubtypeMethods = {
measureText: (
element: Pick<
ExcalidrawTextElement,
| "subtype"
| "customData"
| "fontSize"
| "fontFamily"
| "text"
| "lineHeight"
>,
next?: {
fontSize?: number;
text?: string;
customData?: ExcalidrawElement["customData"];
},
) => { width: number; height: number; baseline: number };
wrapText: (
element: Pick<
ExcalidrawTextElement,
| "subtype"
| "customData"
| "fontSize"
| "fontFamily"
| "originalText"
| "lineHeight"
>,
containerWidth: number,
next?: {
fontSize?: number;
text?: string;
customData?: ExcalidrawElement["customData"];
},
) => string;
};
type MethodMap = { subtype: Subtype; methods: Partial<SubtypeMethods> };
const methodMaps = [] as Array<MethodMap>;
// Use `getSubtypeMethods` to call subtype-specialized methods, like `render`.
export const getSubtypeMethods = (
subtype: Subtype | undefined,
): Partial<SubtypeMethods> | undefined => {
const map = methodMaps.find((method) => method.subtype === subtype);
return map?.methods;
};
@@ -5,7 +5,7 @@ exports[`Test Linear Elements Test bound text element should match styles for te
class="excalidraw-wysiwyg"
data-type="wysiwyg"
dir="auto"
style="position: absolute; display: inline-block; min-height: 1em; margin: 0px; padding: 0px; border: 0px; outline: 0; resize: none; background: transparent; overflow: hidden; z-index: var(--zIndex-wysiwyg); word-break: break-word; white-space: pre-wrap; overflow-wrap: break-word; box-sizing: content-box; width: 10.5px; height: 25px; left: 35px; top: 7.5px; transform-origin: 5px 12.5px; transform: translate(0px, 0px) scale(1) rotate(0deg) translate(0px, 0px); text-align: center; vertical-align: middle; color: rgb(0, 0, 0); opacity: 1; filter: var(--theme-filter); max-height: -7.5px; font: Emoji 20px 20px; line-height: 1.25; font-family: Virgil, Segoe UI Emoji;"
style="position: absolute; display: inline-block; min-height: 1em; margin: 0px; padding: 0px; border: 0px; outline: 0; resize: none; background: transparent; overflow: hidden; z-index: var(--zIndex-wysiwyg); word-break: break-word; white-space: pre-wrap; overflow-wrap: break-word; box-sizing: content-box; width: 10.5px; height: 25px; left: 35px; top: 7.5px; transform: translate(0px, 0px) scale(1) rotate(0deg); text-align: center; vertical-align: middle; color: rgb(0, 0, 0); opacity: 1; filter: var(--theme-filter); max-height: -7.5px; font: Emoji 20px 20px; line-height: 1.25; font-family: Virgil, Segoe UI Emoji;"
tabindex="0"
wrap="off"
/>
+5
View File
@@ -1326,6 +1326,11 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@braintree/sanitize-url@6.0.2":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz#6110f918d273fe2af8ea1c4398a88774bb9fc12f"
integrity sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==
"@csstools/normalize.css@*":
version "12.0.0"
resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-12.0.0.tgz#a9583a75c3f150667771f30b60d9f059473e62c4"