mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-20 07:49:43 +02:00
Merge branch '10.2.2' into develop
This commit is contained in:
@@ -60,7 +60,7 @@ export const renderGraph = (graphStr, options, api) => {
|
|||||||
openURLAndVerifyRendering(url, options);
|
openURLAndVerifyRendering(url, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openURLAndVerifyRendering = (url, options, validation = undefined) => {
|
export const openURLAndVerifyRendering = (url, options, validation = undefined) => {
|
||||||
const useAppli = Cypress.env('useAppli');
|
const useAppli = Cypress.env('useAppli');
|
||||||
const name = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
|
const name = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { urlSnapshotTest } from '../../helpers/util.js';
|
import { urlSnapshotTest, openURLAndVerifyRendering } from '../../helpers/util.js';
|
||||||
|
|
||||||
describe('CSS injections', () => {
|
describe('CSS injections', () => {
|
||||||
it('should not allow CSS injections outside of the diagram', () => {
|
it('should not allow CSS injections outside of the diagram', () => {
|
||||||
@@ -13,4 +13,11 @@ describe('CSS injections', () => {
|
|||||||
flowchart: { htmlLabels: false },
|
flowchart: { htmlLabels: false },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('should not allow manipulating styletags using arrowheads', () => {
|
||||||
|
openURLAndVerifyRendering('http://localhost:9000/xss23-css.html', {
|
||||||
|
logLevel: 1,
|
||||||
|
arrowMarkerAbsolute: false,
|
||||||
|
flowchart: { htmlLabels: true },
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
85
cypress/platform/xss23-css.html
Normal file
85
cypress/platform/xss23-css.html
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
|
||||||
|
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
/* background: rgb(221, 208, 208); */
|
||||||
|
/* background:#333; */
|
||||||
|
font-family: 'Arial';
|
||||||
|
/* font-size: 18px !important; */
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
.mermaid2 {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.mermaid svg {
|
||||||
|
/* font-size: 18px !important; */
|
||||||
|
}
|
||||||
|
.malware {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 150px;
|
||||||
|
background: red;
|
||||||
|
color: black;
|
||||||
|
display: flex;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 72px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>Security check</div>
|
||||||
|
<div class="flex">
|
||||||
|
<div id="diagram" class="mermaid"></div>
|
||||||
|
<div id="graph-div"></div>
|
||||||
|
<div id="res" class=""></div>
|
||||||
|
</div>
|
||||||
|
<script type="module">
|
||||||
|
import mermaid from './mermaid.esm.mjs';
|
||||||
|
mermaid.parseError = function (err, hash) {
|
||||||
|
// console.error('Mermaid error: ', err);
|
||||||
|
};
|
||||||
|
mermaid.initialize({
|
||||||
|
theme: 'base',
|
||||||
|
startOnLoad: false,
|
||||||
|
flowcharts: { htmlLabels: true },
|
||||||
|
});
|
||||||
|
function callback() {
|
||||||
|
alert('It worked');
|
||||||
|
}
|
||||||
|
function xssAttack() {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.id = 'the-malware';
|
||||||
|
div.className = 'malware';
|
||||||
|
div.innerHTML = 'XSS Succeeded';
|
||||||
|
document.getElementsByTagName('body')[0].appendChild(div);
|
||||||
|
throw new Error('XSS Succeeded');
|
||||||
|
}
|
||||||
|
|
||||||
|
let diagram = `graph TD
|
||||||
|
A[["a marker-end=#quot;url(<s title='#<style>*{background:red}</style>'>b"]]
|
||||||
|
`;
|
||||||
|
const el = document.querySelector('#graph-div');
|
||||||
|
console.log(diagram);
|
||||||
|
const { svg } = await mermaid.render('graph-div', diagram);
|
||||||
|
document.querySelector('#res').innerHTML = svg;
|
||||||
|
window.rendered = true;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -96,7 +96,7 @@ mermaid.initialize(config);
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[mermaidAPI.ts:667](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L667)
|
[mermaidAPI.ts:670](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L670)
|
||||||
|
|
||||||
## Functions
|
## Functions
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ Return the last node appended
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[mermaidAPI.ts:306](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L306)
|
[mermaidAPI.ts:309](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L309)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -295,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[mermaidAPI.ts:285](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L285)
|
[mermaidAPI.ts:288](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L288)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -320,4 +320,4 @@ Remove any existing elements from the given document
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[mermaidAPI.ts:356](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L356)
|
[mermaidAPI.ts:359](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L359)
|
||||||
|
@@ -46,9 +46,6 @@ quadrantChart
|
|||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
|
|
||||||
> **Note**
|
|
||||||
> In place of `<text>` you can use text like `this is a sample text` or inside **double quotes** like `"This type of text may contain unicode like ❤"`.
|
|
||||||
|
|
||||||
> **Note**
|
> **Note**
|
||||||
> If there is no points available in the chart both **axis** text and **quadrant** will be rendered in the center of the respective quadrant.
|
> If there is no points available in the chart both **axis** text and **quadrant** will be rendered in the center of the respective quadrant.
|
||||||
> If there are points **x-axis** labels will rendered from left of the respective quadrant also they will be displayed in bottom of the chart, and **y-axis** lables will be rendered in bottom of the respective quadrant, the quadrant text will render at top of the respective quadrant.
|
> If there are points **x-axis** labels will rendered from left of the respective quadrant also they will be displayed in bottom of the chart, and **y-axis** lables will be rendered in bottom of the respective quadrant, the quadrant text will render at top of the respective quadrant.
|
||||||
@@ -152,7 +149,7 @@ Points are used to plot a circle inside the quadrantChart. The syntax is `<text>
|
|||||||
%%{init: {"quadrantChart": {"chartWidth": 400, "chartHeight": 400}, "themeVariables": {"quadrant1TextFill": "#ff0000"} }}%%
|
%%{init: {"quadrantChart": {"chartWidth": 400, "chartHeight": 400}, "themeVariables": {"quadrant1TextFill": "#ff0000"} }}%%
|
||||||
quadrantChart
|
quadrantChart
|
||||||
x-axis Urgent --> Not Urgent
|
x-axis Urgent --> Not Urgent
|
||||||
y-axis Not Important --> important
|
y-axis Not Important --> "Important ❤"
|
||||||
quadrant-1 Plan
|
quadrant-1 Plan
|
||||||
quadrant-2 Do
|
quadrant-2 Do
|
||||||
quadrant-3 Deligate
|
quadrant-3 Deligate
|
||||||
@@ -163,7 +160,7 @@ quadrantChart
|
|||||||
%%{init: {"quadrantChart": {"chartWidth": 400, "chartHeight": 400}, "themeVariables": {"quadrant1TextFill": "#ff0000"} }}%%
|
%%{init: {"quadrantChart": {"chartWidth": 400, "chartHeight": 400}, "themeVariables": {"quadrant1TextFill": "#ff0000"} }}%%
|
||||||
quadrantChart
|
quadrantChart
|
||||||
x-axis Urgent --> Not Urgent
|
x-axis Urgent --> Not Urgent
|
||||||
y-axis Not Important --> important
|
y-axis Not Important --> "Important ❤"
|
||||||
quadrant-1 Plan
|
quadrant-1 Plan
|
||||||
quadrant-2 Do
|
quadrant-2 Do
|
||||||
quadrant-3 Deligate
|
quadrant-3 Deligate
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "mermaid-monorepo",
|
"name": "mermaid-monorepo",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "10.2.0",
|
"version": "10.2.2",
|
||||||
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@8.5.1",
|
"packageManager": "pnpm@8.5.1",
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mermaid",
|
"name": "mermaid",
|
||||||
"version": "10.2.0",
|
"version": "10.2.2",
|
||||||
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "./dist/mermaid.core.mjs",
|
"module": "./dist/mermaid.core.mjs",
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts",
|
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts",
|
||||||
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts --verify",
|
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts --verify",
|
||||||
"docs:pre:vitepress": "rimraf src/vitepress && pnpm docs:code && ts-node-esm src/docs.mts --vitepress",
|
"docs:pre:vitepress": "rimraf src/vitepress && pnpm docs:code && ts-node-esm src/docs.mts --vitepress",
|
||||||
"docs:build:vitepress": "pnpm docs:pre:vitepress && (cd src/vitepress && pnpm --filter ./ install --no-frozen-lockfile && pnpm run build) && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing",
|
"docs:build:vitepress": "pnpm docs:pre:vitepress && (cd src/vitepress && pnpm --filter ./ install --no-frozen-lockfile --ignore-scripts && pnpm run build) && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing",
|
||||||
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./ src/vitepress dev\" \"ts-node-esm src/docs.mts --watch --vitepress\"",
|
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./ src/vitepress dev\" \"ts-node-esm src/docs.mts --watch --vitepress\"",
|
||||||
"docs:serve": "pnpm docs:build:vitepress && vitepress serve src/vitepress",
|
"docs:serve": "pnpm docs:build:vitepress && vitepress serve src/vitepress",
|
||||||
"docs:spellcheck": "cspell --config ../../cSpell.json \"src/docs/**/*.md\"",
|
"docs:spellcheck": "cspell --config ../../cSpell.json \"src/docs/**/*.md\"",
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||||
import dayjs from 'dayjs/esm/index.js';
|
import dayjs from 'dayjs';
|
||||||
import dayjsIsoWeek from 'dayjs/esm/plugin/isoWeek/index.js';
|
import dayjsIsoWeek from 'dayjs/plugin/isoWeek.js';
|
||||||
import dayjsCustomParseFormat from 'dayjs/esm/plugin/customParseFormat/index.js';
|
import dayjsCustomParseFormat from 'dayjs/plugin/customParseFormat.js';
|
||||||
import dayjsAdvancedFormat from 'dayjs/esm/plugin/advancedFormat/index.js';
|
import dayjsAdvancedFormat from 'dayjs/plugin/advancedFormat.js';
|
||||||
import { log } from '../../logger.js';
|
import { log } from '../../logger.js';
|
||||||
import * as configApi from '../../config.js';
|
import * as configApi from '../../config.js';
|
||||||
import utils from '../../utils.js';
|
import utils from '../../utils.js';
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
// @ts-nocheck TODO: Fix TS
|
// @ts-nocheck TODO: Fix TS
|
||||||
import dayjs from 'dayjs/esm/index.js';
|
import dayjs from 'dayjs';
|
||||||
import ganttDb from './ganttDb.js';
|
import ganttDb from './ganttDb.js';
|
||||||
import { convert } from '../../tests/util.js';
|
import { convert } from '../../tests/util.js';
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import dayjs from 'dayjs/esm/index.js';
|
import dayjs from 'dayjs';
|
||||||
import { log } from '../../logger.js';
|
import { log } from '../../logger.js';
|
||||||
import {
|
import {
|
||||||
select,
|
select,
|
||||||
|
@@ -5,12 +5,12 @@ import { contributors } from '../contributors';
|
|||||||
<template>
|
<template>
|
||||||
<div flex="~ wrap gap2" justify-center>
|
<div flex="~ wrap gap2" justify-center>
|
||||||
<a
|
<a
|
||||||
v-for="{ name, avatar } of contributors"
|
v-for="{ username, avatar } of contributors"
|
||||||
:key="name"
|
:key="username"
|
||||||
:href="`https://github.com/${name}`"
|
:href="`https://github.com/${username}`"
|
||||||
m-0
|
m-0
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
:aria-label="`${name} on GitHub`"
|
:aria-label="`${username} on GitHub`"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
@@ -20,7 +20,7 @@ import { contributors } from '../contributors';
|
|||||||
rounded-full
|
rounded-full
|
||||||
h-12
|
h-12
|
||||||
w-12
|
w-12
|
||||||
:alt="`${name}'s avatar`"
|
:alt="`${username}'s avatar`"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
import { mkdir, writeFile, readFile } from 'node:fs/promises';
|
import { mkdir, writeFile, readFile } from 'node:fs/promises';
|
||||||
import { existsSync } from 'node:fs';
|
import { existsSync } from 'node:fs';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
@@ -12,22 +13,23 @@ async function download(url: string, fileName: URL) {
|
|||||||
if (existsSync(fileName)) {
|
if (existsSync(fileName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line no-console
|
console.log('downloading', url);
|
||||||
console.log('downloading', fileName);
|
|
||||||
try {
|
try {
|
||||||
const image = await fetch(url);
|
const image = await fetch(url);
|
||||||
await writeFile(fileName, Buffer.from(await image.arrayBuffer()));
|
await writeFile(fileName, Buffer.from(await image.arrayBuffer()));
|
||||||
} catch {}
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchAvatars() {
|
async function fetchAvatars() {
|
||||||
await mkdir(fileURLToPath(new URL('..', getAvatarPath('none'))), { recursive: true });
|
await mkdir(fileURLToPath(new URL(getAvatarPath('none'))).replace('none.png', ''), {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
contributors = JSON.parse(await readFile(pathContributors, { encoding: 'utf-8' }));
|
contributors = JSON.parse(await readFile(pathContributors, { encoding: 'utf-8' }));
|
||||||
await Promise.allSettled(
|
for (const name of contributors) {
|
||||||
contributors.map((name) =>
|
await download(`https://github.com/${name}.png?size=100`, getAvatarPath(name));
|
||||||
download(`https://github.com/${name}.png?size=100`, getAvatarPath(name))
|
}
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchAvatars();
|
fetchAvatars();
|
||||||
|
@@ -23,9 +23,8 @@ async function fetchContributors() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
data = await response.json();
|
data = await response.json();
|
||||||
console.log(response.status, response.statusText);
|
|
||||||
console.log(data);
|
|
||||||
collaborators.push(...data.map((i) => i.login));
|
collaborators.push(...data.map((i) => i.login));
|
||||||
|
console.log(`Fetched page ${page}`);
|
||||||
page++;
|
page++;
|
||||||
} while (data.length === 100);
|
} while (data.length === 100);
|
||||||
return collaborators.filter((name) => !name.includes('[bot]'));
|
return collaborators.filter((name) => !name.includes('[bot]'));
|
||||||
|
@@ -23,10 +23,6 @@ quadrantChart
|
|||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
|
|
||||||
```note
|
|
||||||
In place of `<text>` you can use text like `this is a sample text` or inside **double quotes** like `"This type of text may contain unicode like ❤"`.
|
|
||||||
```
|
|
||||||
|
|
||||||
```note
|
```note
|
||||||
If there is no points available in the chart both **axis** text and **quadrant** will be rendered in the center of the respective quadrant.
|
If there is no points available in the chart both **axis** text and **quadrant** will be rendered in the center of the respective quadrant.
|
||||||
If there are points **x-axis** labels will rendered from left of the respective quadrant also they will be displayed in bottom of the chart, and **y-axis** lables will be rendered in bottom of the respective quadrant, the quadrant text will render at top of the respective quadrant.
|
If there are points **x-axis** labels will rendered from left of the respective quadrant also they will be displayed in bottom of the chart, and **y-axis** lables will be rendered in bottom of the respective quadrant, the quadrant text will render at top of the respective quadrant.
|
||||||
@@ -134,7 +130,7 @@ Points are used to plot a circle inside the quadrantChart. The syntax is `<text>
|
|||||||
%%{init: {"quadrantChart": {"chartWidth": 400, "chartHeight": 400}, "themeVariables": {"quadrant1TextFill": "#ff0000"} }}%%
|
%%{init: {"quadrantChart": {"chartWidth": 400, "chartHeight": 400}, "themeVariables": {"quadrant1TextFill": "#ff0000"} }}%%
|
||||||
quadrantChart
|
quadrantChart
|
||||||
x-axis Urgent --> Not Urgent
|
x-axis Urgent --> Not Urgent
|
||||||
y-axis Not Important --> important
|
y-axis Not Important --> "Important ❤"
|
||||||
quadrant-1 Plan
|
quadrant-1 Plan
|
||||||
quadrant-2 Do
|
quadrant-2 Do
|
||||||
quadrant-3 Deligate
|
quadrant-3 Deligate
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import dayjs from 'dayjs/esm/index.js';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
||||||
|
|
||||||
|
@@ -263,7 +263,10 @@ export const cleanUpSvgCode = (
|
|||||||
|
|
||||||
// Replace marker-end urls with just the # anchor (remove the preceding part of the URL)
|
// Replace marker-end urls with just the # anchor (remove the preceding part of the URL)
|
||||||
if (!useArrowMarkerUrls && !inSandboxMode) {
|
if (!useArrowMarkerUrls && !inSandboxMode) {
|
||||||
cleanedUpSvg = cleanedUpSvg.replace(/marker-end="url\(.*?#/g, 'marker-end="url(#');
|
cleanedUpSvg = cleanedUpSvg.replace(
|
||||||
|
/marker-end="url\([\d+./:=?A-Za-z-]*?#/g,
|
||||||
|
'marker-end="url(#'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanedUpSvg = decodeEntities(cleanedUpSvg);
|
cleanedUpSvg = decodeEntities(cleanedUpSvg);
|
||||||
|
Reference in New Issue
Block a user