Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
449dead2e5 | ||
|
|
aaec5912b4 | ||
|
|
187edf0f9f | ||
|
|
5efc9ae10d | ||
|
|
77522abef6 | ||
|
|
90a4db2644 | ||
|
|
7de5b3bfb1 | ||
|
|
826ccd983f | ||
|
|
78d3ed6e6c | ||
|
|
36083c849b | ||
|
|
29fc3fef21 | ||
|
|
1f3c74ae19 | ||
|
|
433fd4d045 | ||
|
|
814ce1aee3 | ||
|
|
ce0517b782 | ||
|
|
260d4970ae | ||
|
|
7f22aea3eb | ||
|
|
b90cf45eff | ||
|
|
020075da92 | ||
|
|
de41008284 | ||
|
|
bc9d53b701 | ||
|
|
c21db93399 | ||
|
|
9777be9a7b | ||
|
|
00b80d224c | ||
|
|
2aa4666dab | ||
|
|
19c0c698ce |
@@ -35,7 +35,7 @@ describe('Check actions', () => {
|
|||||||
|
|
||||||
// Verify downloaded file is different for different diagrams
|
// Verify downloaded file is different for different diagrams
|
||||||
cy.contains('Sample Diagrams').click();
|
cy.contains('Sample Diagrams').click();
|
||||||
cy.contains('ER Diagram').click();
|
cy.contains('ER').click();
|
||||||
|
|
||||||
cy.get(`#downloadPNG`).click();
|
cy.get(`#downloadPNG`).click();
|
||||||
verifyFileSize('diagram', 'png', 46_000);
|
verifyFileSize('diagram', 'png', 46_000);
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ describe('Editor docs tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Test to see if the correct URL loads when changing from one diagram to other', () => {
|
it('Test to see if the correct URL loads when changing from one diagram to other', () => {
|
||||||
cy.contains('Flow Chart').click();
|
cy.contains('Flow').click();
|
||||||
cy.get(`[data-cy=docs][href$="/#/flowchart"]`).should('exist');
|
cy.get(`[data-cy=docs][href$="/#/flowchart"]`).should('exist');
|
||||||
|
|
||||||
cy.contains('Config').click();
|
cy.contains('Config').click();
|
||||||
cy.get(`[data-cy=docs][href$="/#/flowchart?id=configuration"]`).should('exist');
|
cy.get(`[data-cy=docs][href$="/#/flowchart?id=configuration"]`).should('exist');
|
||||||
|
|
||||||
cy.contains('Sequence Diagram').click();
|
cy.contains('Sequence').click();
|
||||||
cy.get(`[data-cy=docs][href$="/#/sequenceDiagram?id=configuration"]`).should('exist');
|
cy.get(`[data-cy=docs][href$="/#/sequenceDiagram?id=configuration"]`).should('exist');
|
||||||
|
|
||||||
cy.contains('Code').click();
|
cy.contains('Code').click();
|
||||||
@@ -27,7 +27,7 @@ describe('Editor docs tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Test to check URLs for a case where config URL doesn't exist", () => {
|
it("Test to check URLs for a case where config URL doesn't exist", () => {
|
||||||
cy.contains('State Diagram').click();
|
cy.contains('State').click();
|
||||||
cy.get(`[data-cy=docs][href$="/#/stateDiagram"]`).should('exist');
|
cy.get(`[data-cy=docs][href$="/#/stateDiagram"]`).should('exist');
|
||||||
|
|
||||||
cy.contains('Config').click();
|
cy.contains('Config').click();
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ describe('Site Loads', () => {
|
|||||||
|
|
||||||
it('should load sample diagrams when clicked', () => {
|
it('should load sample diagrams when clicked', () => {
|
||||||
cy.contains('Sample Diagrams').click();
|
cy.contains('Sample Diagrams').click();
|
||||||
cy.contains('Pie Chart').click();
|
cy.contains('Pie').click();
|
||||||
cy.contains('pie title Pets adopted by volunteers');
|
cy.contains('pie title Pets adopted by volunteers');
|
||||||
cy.contains('Class Diagram').click();
|
cy.contains('Class').click();
|
||||||
cy.contains('classDiagram');
|
cy.contains('classDiagram');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -5,6 +5,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
|
"dev:force": "MERMAID_LOCAL=true yarn dev --force",
|
||||||
"dev:test": "yarn dev",
|
"dev:test": "yarn dev",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
"@sveltejs/kit": "1.0.0-next.516",
|
"@sveltejs/kit": "1.0.0-next.516",
|
||||||
"@testing-library/jest-dom": "5.16.5",
|
"@testing-library/jest-dom": "5.16.5",
|
||||||
"@testing-library/svelte": "3.2.2",
|
"@testing-library/svelte": "3.2.2",
|
||||||
"@types/mermaid": "9.1.0",
|
"@types/async": "^3.2.15",
|
||||||
"@types/pako": "2.0.0",
|
"@types/pako": "2.0.0",
|
||||||
"@types/uuid": "8.3.4",
|
"@types/uuid": "8.3.4",
|
||||||
"@typescript-eslint/eslint-plugin": "5.40.1",
|
"@typescript-eslint/eslint-plugin": "5.40.1",
|
||||||
@@ -67,9 +68,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"analytics": "0.8.1",
|
"analytics": "0.8.1",
|
||||||
"analytics-plugin-plausible": "0.0.6",
|
"analytics-plugin-plausible": "0.0.6",
|
||||||
|
"async": "^3.2.4",
|
||||||
"daisyui": "2.31.0",
|
"daisyui": "2.31.0",
|
||||||
"js-base64": "3.7.2",
|
"js-base64": "3.7.2",
|
||||||
"mermaid": "9.1.7",
|
"mermaid": "9.2.0-rc6",
|
||||||
"moment": "2.29.4",
|
"moment": "2.29.4",
|
||||||
"monaco-editor": "0.34.1",
|
"monaco-editor": "0.34.1",
|
||||||
"monaco-mermaid": "1.0.6",
|
"monaco-mermaid": "1.0.6",
|
||||||
|
|||||||
Vendored
+14
@@ -0,0 +1,14 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly MERMAID_RENDERER_URL: string;
|
||||||
|
readonly MERMAID_KROKI_RENDERER_URL: string;
|
||||||
|
readonly MERMAID_CDN_URL: string;
|
||||||
|
readonly MERMAID_BASE_URL: string;
|
||||||
|
readonly MERMAID_LOCAL: boolean;
|
||||||
|
// more env variables...
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import Card from '$lib/components/card/card.svelte';
|
import Card from '$lib/components/card/card.svelte';
|
||||||
import { krokiRendererUrl, rendererUrl } from '$lib/util/env';
|
import { env } from '$lib/util/env';
|
||||||
import { pakoSerde } from '$lib/util/serde';
|
import { pakoSerde } from '$lib/util/serde';
|
||||||
import { stateStore } from '$lib/util/state';
|
import { stateStore } from '$lib/util/state';
|
||||||
import { logEvent } from '$lib/util/stats';
|
import { logEvent } from '$lib/util/stats';
|
||||||
import { toBase64 } from 'js-base64';
|
import { toBase64 } from 'js-base64';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
const { krokiRendererUrl, rendererUrl } = env;
|
||||||
|
|
||||||
type Exporter = (context: CanvasRenderingContext2D, image: HTMLImageElement) => () => void;
|
type Exporter = (context: CanvasRenderingContext2D, image: HTMLImageElement) => () => void;
|
||||||
|
|
||||||
|
|||||||
@@ -53,15 +53,15 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadMonaco = async () => {
|
const loadMonaco = async () => {
|
||||||
|
console.log('Loading Monaco...');
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while (i++ < 500) {
|
while (i++ < 500) {
|
||||||
try {
|
// @ts-ignore : This is a hack to handle a svelte-kit error when importing monaco.
|
||||||
// @ts-ignore : This is a hack to handle a svelte-kit error when importing monaco.
|
Monaco = window.monaco;
|
||||||
Monaco = window.monaco;
|
if (Monaco !== undefined) {
|
||||||
return;
|
return;
|
||||||
} catch {
|
|
||||||
await new Promise((r) => setTimeout(r, 100));
|
|
||||||
}
|
}
|
||||||
|
await new Promise((r) => setTimeout(r, 100));
|
||||||
}
|
}
|
||||||
alert('Loading Monaco Editor failed. Please try refreshing the page.');
|
alert('Loading Monaco Editor failed. Please try refreshing the page.');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,19 +4,18 @@
|
|||||||
import { logEvent } from '$lib/util/stats';
|
import { logEvent } from '$lib/util/stats';
|
||||||
|
|
||||||
const samples = {
|
const samples = {
|
||||||
'Flow Chart': `graph TD
|
Flow: `graph TD
|
||||||
A[Christmas] -->|Get money| B(Go shopping)
|
A[Christmas] -->|Get money| B(Go shopping)
|
||||||
B --> C{Let me think}
|
B --> C{Let me think}
|
||||||
C -->|One| D[Laptop]
|
C -->|One| D[Laptop]
|
||||||
C -->|Two| E[iPhone]
|
C -->|Two| E[iPhone]
|
||||||
C -->|Three| F[fa:fa-car Car]`,
|
C -->|Three| F[fa:fa-car Car]`,
|
||||||
'Sequence Diagram': `sequenceDiagram
|
Sequence: `sequenceDiagram
|
||||||
Alice->>+John: Hello John, how are you?
|
Alice->>+John: Hello John, how are you?
|
||||||
Alice->>+John: John, can you hear me?
|
Alice->>+John: John, can you hear me?
|
||||||
John-->>-Alice: Hi Alice, I can hear you!
|
John-->>-Alice: Hi Alice, I can hear you!
|
||||||
John-->>-Alice: I feel great!
|
John-->>-Alice: I feel great!`,
|
||||||
`,
|
Class: `classDiagram
|
||||||
'Class Diagram': `classDiagram
|
|
||||||
Animal <|-- Duck
|
Animal <|-- Duck
|
||||||
Animal <|-- Fish
|
Animal <|-- Fish
|
||||||
Animal <|-- Zebra
|
Animal <|-- Zebra
|
||||||
@@ -36,17 +35,15 @@
|
|||||||
class Zebra{
|
class Zebra{
|
||||||
+bool is_wild
|
+bool is_wild
|
||||||
+run()
|
+run()
|
||||||
}
|
}`,
|
||||||
`,
|
State: `stateDiagram-v2
|
||||||
'State Diagram': `stateDiagram-v2
|
|
||||||
[*] --> Still
|
[*] --> Still
|
||||||
Still --> [*]
|
Still --> [*]
|
||||||
Still --> Moving
|
Still --> Moving
|
||||||
Moving --> Still
|
Moving --> Still
|
||||||
Moving --> Crash
|
Moving --> Crash
|
||||||
Crash --> [*]
|
Crash --> [*]`,
|
||||||
`,
|
Gantt: `gantt
|
||||||
'Gantt Chart': `gantt
|
|
||||||
title A Gantt Diagram
|
title A Gantt Diagram
|
||||||
dateFormat YYYY-MM-DD
|
dateFormat YYYY-MM-DD
|
||||||
section Section
|
section Section
|
||||||
@@ -54,24 +51,21 @@
|
|||||||
Another task :after a1 , 20d
|
Another task :after a1 , 20d
|
||||||
section Another
|
section Another
|
||||||
Task in sec :2014-01-12 , 12d
|
Task in sec :2014-01-12 , 12d
|
||||||
another task : 24d
|
another task : 24d`,
|
||||||
`,
|
Pie: `pie title Pets adopted by volunteers
|
||||||
'Pie Chart': `pie title Pets adopted by volunteers
|
|
||||||
"Dogs" : 386
|
"Dogs" : 386
|
||||||
"Cats" : 85
|
"Cats" : 85
|
||||||
"Rats" : 15
|
"Rats" : 15`,
|
||||||
`,
|
ER: `erDiagram
|
||||||
'ER Diagram': `erDiagram
|
CUSTOMER }|..|{ DELIVERY-ADDRESS : has
|
||||||
CUSTOMER }|..|{ DELIVERY-ADDRESS : has
|
CUSTOMER ||--o{ ORDER : places
|
||||||
CUSTOMER ||--o{ ORDER : places
|
CUSTOMER ||--o{ INVOICE : "liable for"
|
||||||
CUSTOMER ||--o{ INVOICE : "liable for"
|
DELIVERY-ADDRESS ||--o{ ORDER : receives
|
||||||
DELIVERY-ADDRESS ||--o{ ORDER : receives
|
INVOICE ||--|{ ORDER : covers
|
||||||
INVOICE ||--|{ ORDER : covers
|
ORDER ||--|{ ORDER-ITEM : includes
|
||||||
ORDER ||--|{ ORDER-ITEM : includes
|
PRODUCT-CATEGORY ||--|{ PRODUCT : contains
|
||||||
PRODUCT-CATEGORY ||--|{ PRODUCT : contains
|
PRODUCT ||--o{ ORDER-ITEM : "ordered in"`,
|
||||||
PRODUCT ||--o{ ORDER-ITEM : "ordered in"
|
'User Journey': `journey
|
||||||
`,
|
|
||||||
'User Journey': ` journey
|
|
||||||
title My working day
|
title My working day
|
||||||
section Go to work
|
section Go to work
|
||||||
Make tea: 5: Me
|
Make tea: 5: Me
|
||||||
@@ -79,20 +73,35 @@
|
|||||||
Do work: 1: Me, Cat
|
Do work: 1: Me, Cat
|
||||||
section Go home
|
section Go home
|
||||||
Go downstairs: 5: Me
|
Go downstairs: 5: Me
|
||||||
Sit down: 3: Me
|
Sit down: 3: Me`,
|
||||||
`,
|
Git: `gitGraph
|
||||||
'Git Graph': ` gitGraph
|
commit
|
||||||
commit
|
commit
|
||||||
commit
|
branch develop
|
||||||
branch develop
|
checkout develop
|
||||||
checkout develop
|
commit
|
||||||
commit
|
commit
|
||||||
commit
|
checkout main
|
||||||
checkout main
|
merge develop
|
||||||
merge develop
|
commit
|
||||||
commit
|
commit`,
|
||||||
commit
|
Mindmap: `mindmap
|
||||||
`
|
root((mindmap))
|
||||||
|
Origins
|
||||||
|
Long history
|
||||||
|
::icon(fa fa-book)
|
||||||
|
Popularisation
|
||||||
|
British popular psychology author Tony Buzan
|
||||||
|
Research
|
||||||
|
On effectivness<br/>and features
|
||||||
|
On Automatic creation
|
||||||
|
Uses
|
||||||
|
Creative techniques
|
||||||
|
Strategic planning
|
||||||
|
Argument mapping
|
||||||
|
Tools
|
||||||
|
Pen and paper
|
||||||
|
Mermaid`
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadSampleDiagram = (diagramType: string): void => {
|
const loadSampleDiagram = (diagramType: string): void => {
|
||||||
@@ -102,13 +111,34 @@
|
|||||||
});
|
});
|
||||||
logEvent('loadSampleDiagram', { diagramType });
|
logEvent('loadSampleDiagram', { diagramType });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Adding in this array will add an icon to the preset menu
|
||||||
|
const newDiagrams: Array<keyof typeof samples> = ['Mindmap'];
|
||||||
|
const diagramOrder: Array<keyof typeof samples> = [
|
||||||
|
'Sequence',
|
||||||
|
'Flow',
|
||||||
|
'Class',
|
||||||
|
'State',
|
||||||
|
'ER',
|
||||||
|
'Gantt',
|
||||||
|
'User Journey',
|
||||||
|
'Git',
|
||||||
|
'Pie',
|
||||||
|
'Mindmap'
|
||||||
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card title="Sample Diagrams" isOpen={false}>
|
<Card title="Sample Diagrams" isOpen={false}>
|
||||||
<div class="flex gap-2 flex-wrap p-2">
|
<div class="flex flex-wrap p-2 gap-2">
|
||||||
{#each Object.keys(samples) as sample}
|
{#each diagramOrder as sample}
|
||||||
<button class="btn btn-primary normal-case btn-sm" on:click={() => loadSampleDiagram(sample)}
|
<button
|
||||||
>{sample}</button>
|
class="btn btn-sm btn-primary w-28 normal-case flex-grow"
|
||||||
|
on:click={() => loadSampleDiagram(sample)}>
|
||||||
|
{sample}
|
||||||
|
{#if newDiagrams.includes(sample)}
|
||||||
|
<span class="ml-2 fa fa-heart" />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { inputStateStore, stateStore, updateCodeStore } from '$lib/util/state';
|
import { inputStateStore, stateStore, updateCodeStore } from '$lib/util/state';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import mermaid from 'mermaid';
|
|
||||||
import panzoom from 'svg-pan-zoom';
|
import panzoom from 'svg-pan-zoom';
|
||||||
import type { State, ValidatedState } from '$lib/types';
|
import type { State, ValidatedState } from '$lib/types';
|
||||||
import { logEvent } from '$lib/util/stats';
|
import { logEvent } from '$lib/util/stats';
|
||||||
import { cmdKey } from '$lib/util/util';
|
import { cmdKey } from '$lib/util/util';
|
||||||
|
import { render as renderDiagram, init as mermaidInit } from '$lib/util/mermaid';
|
||||||
|
const init = mermaidInit();
|
||||||
let code = '';
|
let code = '';
|
||||||
let config = '';
|
let config = '';
|
||||||
let container: HTMLDivElement;
|
let container: HTMLDivElement;
|
||||||
@@ -72,14 +72,21 @@
|
|||||||
panZoomEnabled = state.panZoom;
|
panZoomEnabled = state.panZoom;
|
||||||
const scroll = view.parentElement.scrollTop;
|
const scroll = view.parentElement.scrollTop;
|
||||||
delete container.dataset.processed;
|
delete container.dataset.processed;
|
||||||
mermaid.initialize(Object.assign({}, JSON.parse(state.mermaid)));
|
renderDiagram({
|
||||||
mermaid.render('graph-div', code, (svgCode) => {
|
config: Object.assign({}, JSON.parse(state.mermaid)),
|
||||||
if (svgCode.length > 0) {
|
code,
|
||||||
handlePanZoom(state);
|
id: 'graph-div',
|
||||||
container.innerHTML = svgCode;
|
callback: (svgCode, bindFunctions) => {
|
||||||
const graphDiv = document.getElementById('graph-div');
|
if (svgCode.length > 0) {
|
||||||
graphDiv.setAttribute('height', '100%');
|
handlePanZoom(state);
|
||||||
graphDiv.style.maxWidth = '100%';
|
container.innerHTML = svgCode;
|
||||||
|
const graphDiv = document.getElementById('graph-div');
|
||||||
|
graphDiv.setAttribute('height', '100%');
|
||||||
|
graphDiv.style.maxWidth = '100%';
|
||||||
|
if (bindFunctions) {
|
||||||
|
bindFunctions(graphDiv);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
view.parentElement.scrollTop = scroll;
|
view.parentElement.scrollTop = scroll;
|
||||||
@@ -94,6 +101,7 @@
|
|||||||
error = true;
|
error = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
stateStore.subscribe((state) => {
|
stateStore.subscribe((state) => {
|
||||||
handleStateChange(state);
|
handleStateChange(state);
|
||||||
@@ -106,19 +114,23 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if error && $stateStore.error instanceof Error}
|
{#await init}
|
||||||
<div class="p-2 text-red-600" id="errorContainer">{$stateStore.error}</div>
|
Loading...
|
||||||
{/if}
|
{:then}
|
||||||
|
{#if error && $stateStore.error instanceof Error}
|
||||||
|
<div class="p-2 text-red-600" id="errorContainer">{$stateStore.error}</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if outOfSync}
|
{#if outOfSync}
|
||||||
<div class="absolute w-full p-2 z-10 text-yellow-600 bg-base-100 bg-opacity-80 text-center">
|
<div class="absolute w-full p-2 z-10 text-yellow-600 bg-base-100 bg-opacity-80 text-center">
|
||||||
Diagram out of sync. <br />
|
Diagram out of sync. <br />
|
||||||
Press <i class="fas fa-sync" /> (Sync button) or <kbd>{cmdKey} + Enter</kbd> to sync.
|
Press <i class="fas fa-sync" /> (Sync button) or <kbd>{cmdKey} + Enter</kbd> to sync.
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div id="view" bind:this={view} class="p-2 h-full" class:error class:outOfSync>
|
||||||
|
<div id="container" bind:this={container} class="h-full overflow-auto" class:hide />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/await}
|
||||||
<div id="view" bind:this={view} class="p-2 h-full" class:error class:outOfSync>
|
|
||||||
<div id="container" bind:this={container} class="h-full overflow-auto" class:hide />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#view {
|
#view {
|
||||||
|
|||||||
+9
-4
@@ -1,4 +1,9 @@
|
|||||||
export const rendererUrl: string =
|
export const env = {
|
||||||
(import.meta.env.MERMAID_RENDERER_URL as string) ?? 'https://mermaid.ink';
|
rendererUrl: import.meta.env.MERMAID_RENDERER_URL ?? 'https://mermaid.ink',
|
||||||
export const krokiRendererUrl: string =
|
krokiRendererUrl: import.meta.env.MERMAID_KROKI_RENDERER_URL ?? 'https://kroki.io',
|
||||||
(import.meta.env.MERMAID_KROKI_RENDERER_URL as string) ?? 'https://kroki.io';
|
mermaidCDNUrl: import.meta.env.MERMAID_CDN_URL ?? 'https://unpkg.com/@mermaid-js',
|
||||||
|
mermaidBaseURL: import.meta.env.MERMAID_BASE_URL ?? 'http://localhost:9000',
|
||||||
|
useLocalMermaid: import.meta.env.MERMAID_LOCAL ?? false,
|
||||||
|
isDev: import.meta.env.DEV,
|
||||||
|
baseURL: import.meta.env.BASE_URL
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import mermaid from 'mermaid';
|
||||||
|
// We need to export MermaidConfig and all related types from mermaid.
|
||||||
|
import type { MermaidConfig } from 'mermaid/dist/config.type';
|
||||||
|
import { env } from './env';
|
||||||
|
import queue from 'async/queue';
|
||||||
|
import type { QueueObject } from 'async';
|
||||||
|
const { mermaidBaseURL, mermaidCDNUrl, useLocalMermaid } = env;
|
||||||
|
|
||||||
|
const getDiagramURL = (name: string, version: string): string => {
|
||||||
|
if (useLocalMermaid) {
|
||||||
|
return `${mermaidBaseURL}/${name}-detector.esm.mjs`;
|
||||||
|
}
|
||||||
|
return `${mermaidCDNUrl}/${name}@${version}/dist/${name}-detector.esm.mjs`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialize = mermaid.initializeAsync({
|
||||||
|
logLevel: 0,
|
||||||
|
lazyLoadedDiagrams: [getDiagramURL('mermaid-mindmap', '9.2.0-rc4')],
|
||||||
|
loadExternalDiagramsAtStartup: true
|
||||||
|
});
|
||||||
|
|
||||||
|
export const init = async () => {
|
||||||
|
await initialize;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface RenderPayload {
|
||||||
|
config: MermaidConfig;
|
||||||
|
code: string;
|
||||||
|
id: string;
|
||||||
|
callback: Parameters<typeof mermaid.render>[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParsePayload {
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
interface MermaidTask {
|
||||||
|
action: 'render' | 'parse';
|
||||||
|
payload: RenderPayload | ParsePayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mermaidQueue: QueueObject<MermaidTask> = queue(async (task: MermaidTask) => {
|
||||||
|
console.log('adding ', task);
|
||||||
|
if (task.action === 'render') {
|
||||||
|
const { config, code, id, callback } = task.payload as RenderPayload;
|
||||||
|
await mermaid.mermaidAPI.renderAsync(id, code, callback);
|
||||||
|
}
|
||||||
|
console.log('done', task);
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
mermaidQueue.error(function (err, task) {
|
||||||
|
console.error('task experienced an error', task, err);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const render = async (payload: RenderPayload): Promise<void> => {
|
||||||
|
// Should be able to call this multiple times without any issues.
|
||||||
|
// await mermaid.initialize({
|
||||||
|
// ...config,
|
||||||
|
// lazyLoadedDiagrams: [
|
||||||
|
// // We should make SRI mandatory for all lazy-loaded diagrams.
|
||||||
|
// 'https://unpkg.com/@mermaid-js/mermaid-mindmap@9.2.0-rc2/dist/mermaid-mindmap-detector.esm.mjs'
|
||||||
|
// ]
|
||||||
|
// });
|
||||||
|
// console.log('Rendering', code);
|
||||||
|
// mermaid.mermaidAPI.render(id, code, callback);
|
||||||
|
await mermaidQueue.push({
|
||||||
|
action: 'render',
|
||||||
|
payload
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parse = async (code: string): Promise<boolean> => {
|
||||||
|
await init();
|
||||||
|
return mermaid.parseAsync(code);
|
||||||
|
};
|
||||||
@@ -3,11 +3,13 @@ import { persist, localStorage } from './persist';
|
|||||||
import { saveStatistics, countLines } from './stats';
|
import { saveStatistics, countLines } from './stats';
|
||||||
import { serializeState, deserializeState } from './serde';
|
import { serializeState, deserializeState } from './serde';
|
||||||
import { cmdKey } from './util';
|
import { cmdKey } from './util';
|
||||||
import mermaid from 'mermaid';
|
import { parse, init } from './mermaid';
|
||||||
|
|
||||||
import type { Readable } from 'svelte/store';
|
import type { Readable } from 'svelte/store';
|
||||||
import type { MarkerData, State, ValidatedState } from '$lib/types';
|
import type { MarkerData, State, ValidatedState } from '$lib/types';
|
||||||
|
|
||||||
|
void init();
|
||||||
|
|
||||||
export const defaultState: State = {
|
export const defaultState: State = {
|
||||||
code: `graph TD
|
code: `graph TD
|
||||||
A[Christmas] -->|Get money| B(Go shopping)
|
A[Christmas] -->|Get money| B(Go shopping)
|
||||||
@@ -54,7 +56,7 @@ export const stateStore: Readable<ValidatedState> = derived([inputStateStore], (
|
|||||||
// No changes should be done to fields part of `state`.
|
// No changes should be done to fields part of `state`.
|
||||||
try {
|
try {
|
||||||
processed.serialized = serializeState(state);
|
processed.serialized = serializeState(state);
|
||||||
mermaid.parse(state.code);
|
// await parse(state.code);
|
||||||
JSON.parse(state.mermaid);
|
JSON.parse(state.mermaid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
processed.error = e;
|
processed.error = e;
|
||||||
@@ -103,12 +105,11 @@ export const loadState = (data: string): void => {
|
|||||||
state.mermaid = defaultState.mermaid;
|
state.mermaid = defaultState.mermaid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateCodeStore({ ...state });
|
updateCodeStore(state);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateCodeStore = (newState: Partial<State>): void => {
|
export const updateCodeStore = (newState: Partial<State>): void => {
|
||||||
inputStateStore.update((state) => {
|
inputStateStore.update((state) => {
|
||||||
// console.log({ newState, state });
|
|
||||||
return { ...state, ...newState };
|
return { ...state, ...newState };
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -164,7 +165,10 @@ export const toggleDarkTheme = (dark: boolean): void => {
|
|||||||
|
|
||||||
let urlDebounce: number;
|
let urlDebounce: number;
|
||||||
export const initURLSubscription = (): void => {
|
export const initURLSubscription = (): void => {
|
||||||
stateStore.subscribe(({ serialized }) => {
|
stateStore.subscribe(({ serialized }: ValidatedState) => {
|
||||||
|
if (serialized.length < 5) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
clearTimeout(urlDebounce);
|
clearTimeout(urlDebounce);
|
||||||
urlDebounce = window.setTimeout(() => {
|
urlDebounce = window.setTimeout(() => {
|
||||||
history.replaceState(undefined, undefined, `#${serialized}`);
|
history.replaceState(undefined, undefined, `#${serialized}`);
|
||||||
|
|||||||
@@ -375,6 +375,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
|
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
|
||||||
integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==
|
integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==
|
||||||
|
|
||||||
|
"@types/async@^3.2.15":
|
||||||
|
version "3.2.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/async/-/async-3.2.15.tgz#26d4768fdda0e466f18d6c9918ca28cc89a4e1fe"
|
||||||
|
integrity sha512-PAmPfzvFA31mRoqZyTVsgJMsvbynR429UTTxhmfsUCrWGh3/fxOrzqBtaTPJsn4UtzTv4Vb0+/O7CARWb69N4g==
|
||||||
|
|
||||||
"@types/chai-subset@^1.3.3":
|
"@types/chai-subset@^1.3.3":
|
||||||
version "1.3.3"
|
version "1.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94"
|
resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94"
|
||||||
@@ -410,11 +415,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
||||||
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
|
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
|
||||||
|
|
||||||
"@types/mermaid@9.1.0":
|
|
||||||
version "9.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/mermaid/-/mermaid-9.1.0.tgz#e9ba511d8a6793749d6be84f86325f0f58553154"
|
|
||||||
integrity sha512-rc8QqhveKAY7PouzY/p8ljS+eBSNCv7o79L97RSub/Ic2SQ34ph1Ng3s8wFLWVjvaEt6RLOWtSCsgYWd95NY8A==
|
|
||||||
|
|
||||||
"@types/node@*":
|
"@types/node@*":
|
||||||
version "15.0.2"
|
version "15.0.2"
|
||||||
resolved "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz"
|
resolved "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz"
|
||||||
@@ -851,6 +851,11 @@ async@^3.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
|
resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
|
||||||
integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
|
integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
|
||||||
|
|
||||||
|
async@^3.2.4:
|
||||||
|
version "3.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c"
|
||||||
|
integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==
|
||||||
|
|
||||||
asynckit@^0.4.0:
|
asynckit@^0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
|
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
|
||||||
@@ -1031,9 +1036,9 @@ caniuse-api@^3.0.0:
|
|||||||
lodash.uniq "^4.5.0"
|
lodash.uniq "^4.5.0"
|
||||||
|
|
||||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001370, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001407:
|
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001370, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001407:
|
||||||
version "1.0.30001420"
|
version "1.0.30001421"
|
||||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001420.tgz"
|
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001421.tgz"
|
||||||
integrity sha512-OnyeJ9ascFA9roEj72ok2Ikp7PHJTKubtEJIQ/VK3fdsS50q4KWy+Z5X0A1/GswEItKX0ctAp8n4SYDE7wTu6A==
|
integrity sha512-Sw4eLbgUJAEhjLs1Fa+mk45sidp1wRn5y6GtDpHGBaNJ9OCDJaVh2tIaWWUnGfuXfKf1JCBaIarak3FkVAvEeA==
|
||||||
|
|
||||||
caseless@~0.12.0:
|
caseless@~0.12.0:
|
||||||
version "0.12.0"
|
version "0.12.0"
|
||||||
@@ -2740,6 +2745,11 @@ extsprintf@^1.2.0:
|
|||||||
resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz"
|
resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz"
|
||||||
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
||||||
|
|
||||||
|
fast-clone@^1.5.13:
|
||||||
|
version "1.5.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-clone/-/fast-clone-1.5.13.tgz#7fe17542ae1c872e71bf80d177d00c11f51c2ea7"
|
||||||
|
integrity sha512-0ez7coyFBQFjZtId+RJqJ+EQs61w9xARfqjqK0AD9vIUkSxWD4HvPt80+5evebZ1tTnv1GYKrPTipx7kOW5ipA==
|
||||||
|
|
||||||
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
|
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
|
||||||
@@ -3672,20 +3682,24 @@ merge2@^1.3.0, merge2@^1.4.1:
|
|||||||
resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
|
resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
|
||||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||||
|
|
||||||
mermaid@9.1.7:
|
mermaid@9.2.0-rc6:
|
||||||
version "9.1.7"
|
version "9.2.0-rc6"
|
||||||
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-9.1.7.tgz#e24de9b2d36c8cb25a09d72ffce966941b24bd6e"
|
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-9.2.0-rc6.tgz#8c8ab4403c5bb9eca73cb8f1aba6fbd00fd8ede9"
|
||||||
integrity sha512-MRVHXy5FLjnUQUG7YS3UN9jEN6FXCJbFCXVGJQjVIbiR6Vhw0j/6pLIjqsiah9xoHmQU6DEaKOvB3S1g/1nBPA==
|
integrity sha512-zrbaufHxlWs/i81BZePSh3PtQ/FpS3koY3cX9MS2bclchLqXrzO6wJ5S31Tl95j+7fMzV02HFu3sexSRfJOkhQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@braintree/sanitize-url" "^6.0.0"
|
"@braintree/sanitize-url" "^6.0.0"
|
||||||
d3 "^7.0.0"
|
d3 "^7.0.0"
|
||||||
dagre "^0.8.5"
|
dagre "^0.8.5"
|
||||||
dagre-d3 "^0.6.4"
|
dagre-d3 "^0.6.4"
|
||||||
dompurify "2.4.0"
|
dompurify "2.4.0"
|
||||||
|
fast-clone "^1.5.13"
|
||||||
graphlib "^2.1.8"
|
graphlib "^2.1.8"
|
||||||
khroma "^2.0.0"
|
khroma "^2.0.0"
|
||||||
moment-mini "2.24.0"
|
lodash "^4.17.21"
|
||||||
stylis "^4.0.10"
|
moment-mini "^2.24.0"
|
||||||
|
non-layered-tidy-tree-layout "^2.0.2"
|
||||||
|
stylis "^4.1.2"
|
||||||
|
uuid "^9.0.0"
|
||||||
|
|
||||||
micromatch@^4.0.4, micromatch@^4.0.5:
|
micromatch@^4.0.4, micromatch@^4.0.5:
|
||||||
version "4.0.5"
|
version "4.0.5"
|
||||||
@@ -3746,10 +3760,10 @@ mkdirp@^0.5.1, mkdirp@~0.5.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minimist "^1.2.5"
|
minimist "^1.2.5"
|
||||||
|
|
||||||
moment-mini@2.24.0:
|
moment-mini@^2.24.0:
|
||||||
version "2.24.0"
|
version "2.29.4"
|
||||||
resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.24.0.tgz#fa68d98f7fe93ae65bf1262f6abb5fb6983d8d18"
|
resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.29.4.tgz#cbbcdc58ce1b267506f28ea6668dbe060a32758f"
|
||||||
integrity sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ==
|
integrity sha512-uhXpYwHFeiTbY9KSgPPRoo1nt8OxNVdMVoTBYHfSEKeRkIkwGpO+gERmhuhBtzfaeOyTkykSrm2+noJBgqt3Hg==
|
||||||
|
|
||||||
moment@2.29.4:
|
moment@2.29.4:
|
||||||
version "2.29.4"
|
version "2.29.4"
|
||||||
@@ -3819,6 +3833,11 @@ node-releases@^2.0.6:
|
|||||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
|
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
|
||||||
integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==
|
integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==
|
||||||
|
|
||||||
|
non-layered-tidy-tree-layout@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz#57d35d13c356643fc296a55fb11ac15e74da7804"
|
||||||
|
integrity sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==
|
||||||
|
|
||||||
nopt@~4.0.1:
|
nopt@~4.0.1:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz"
|
resolved "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz"
|
||||||
@@ -4913,10 +4932,10 @@ stylehacks@^5.1.0:
|
|||||||
browserslist "^4.16.6"
|
browserslist "^4.16.6"
|
||||||
postcss-selector-parser "^6.0.4"
|
postcss-selector-parser "^6.0.4"
|
||||||
|
|
||||||
stylis@^4.0.10:
|
stylis@^4.1.2:
|
||||||
version "4.0.10"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.10.tgz#446512d1097197ab3f02fb3c258358c3f7a14240"
|
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.2.tgz#870b3c1c2275f51b702bb3da9e94eedad87bba41"
|
||||||
integrity sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg==
|
integrity sha512-Nn2CCrG2ZaFziDxaZPN43CXqn+j7tcdjPFCkRBkFue8QYXC2HdEwnw5TCBo4yQZ2WxKYeSi0fdoOrtEqgDrXbA==
|
||||||
|
|
||||||
supports-color@^2.0.0:
|
supports-color@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
@@ -5239,7 +5258,7 @@ util-deprecate@^1.0.2:
|
|||||||
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
||||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||||
|
|
||||||
uuid@9.0.0:
|
uuid@9.0.0, uuid@^9.0.0:
|
||||||
version "9.0.0"
|
version "9.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
|
||||||
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
||||||
|
|||||||
Reference in New Issue
Block a user