Merge branch 'develop' into janPromo

* develop:
  Fix XSS vulnerability
  chore: Update eslint plugin and fix errors
  build: update Browserslist db
  fix: Unit tests
  chore: Fix issues
  Add node-version
  chore: Auto format and fix warnings
  chore: Upgrade dependencies
  build: update Browserslist db
  build: update Browserslist db
This commit is contained in:
Sidharth Vinod
2024-01-17 21:50:59 +05:30
27 changed files with 1130 additions and 1076 deletions
+13 -16
View File
@@ -7,17 +7,11 @@ module.exports = {
// 'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:@typescript-eslint/strict',
'plugin:unicorn/recommended',
'plugin:svelte/recommended',
'plugin:svelte/prettier',
'prettier'
],
plugins: [
'svelte3',
'tailwindcss',
'@typescript-eslint',
'es',
'vitest',
'no-only-tests',
'unicorn'
],
plugins: ['tailwindcss', '@typescript-eslint', 'es', 'vitest', 'no-only-tests', 'unicorn'],
ignorePatterns: [
'docs/*',
'*.cjs',
@@ -30,7 +24,13 @@ module.exports = {
'tsconfig.json'
],
overrides: [
{ files: ['*.svelte'], processor: 'svelte3/svelte3' },
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
}
},
{
files: ['*.ts'],
extends: [
@@ -42,16 +42,12 @@ module.exports = {
]
}
],
settings: {
'svelte3/typescript': () => require('typescript')
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
extraFileExtensions: ['.svelte'],
allowAutomaticSingleRunInference: true
project: './tsconfig.json',
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
@@ -76,6 +72,7 @@ module.exports = {
case: 'camelCase'
}
],
'unicorn/filename-case': 'off',
'unicorn/prevent-abbreviations': [
'error',
{
+1
View File
@@ -0,0 +1 @@
18
+3 -1
View File
@@ -3,5 +3,7 @@
"svelteSortOrder": "options-scripts-markup-styles",
"bracketSameLine": true,
"trailingComma": "none",
"printWidth": 100
"printWidth": 100,
"tailwindConfig": "./tailwind.config.cjs",
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"]
}
+3
View File
@@ -10,12 +10,15 @@
"daisyui",
"esserializer",
"gantt",
"gitgraph",
"KROKI",
"localstorage",
"mermaidchart",
"mindmap",
"Pageview",
"pako",
"panzoom",
"pzoom",
"Serde",
"serdes",
"tailwindcss",
+1 -1
View File
@@ -11,7 +11,7 @@ export const typeInEditor = (
) => {
cy.window().should('have.property', 'editorLoaded', true);
cy.get('#editor').click();
cy.get('#editor').within(($editor) => {
cy.get('#editor').within(() => {
if (bottom) {
cy.get('textarea').type('{pageDown}', { force: true });
}
+34 -32
View File
@@ -9,9 +9,9 @@
"dev:test": "yarn dev",
"build": "vite build",
"preview": "vite preview",
"lint": "prettier --check --cache --plugin-search-dir=. .;eslint --ignore-path .gitignore .",
"lint:fix": "prettier --write --cache --plugin-search-dir=. .;eslint --fix --ignore-path .gitignore .",
"format": "prettier --write --cache --plugin-search-dir=. .",
"lint": "prettier --check --cache . && eslint --ignore-path .gitignore .",
"lint:fix": "prettier --write --cache . && eslint --fix --ignore-path .gitignore .",
"format": "prettier --write --cache .",
"pre-commit": "lint-staged",
"postinstall": "husky install && svelte-kit sync && (git config blame.ignoreRevsFile .git-blame-ignore-revs || true)",
"test:unit": "vitest",
@@ -23,15 +23,15 @@
},
"devDependencies": {
"@cypress/snapshot": "2.1.7",
"@sveltejs/adapter-static": "2.0.3",
"@sveltejs/kit": "1.28.0",
"@testing-library/jest-dom": "5.17.0",
"@testing-library/svelte": "3.2.2",
"@sveltejs/adapter-static": "3.0.1",
"@sveltejs/kit": "2.3.0",
"@sveltejs/vite-plugin-svelte": "^3.0.1",
"@testing-library/svelte": "4.0.5",
"@types/pako": "2.0.3",
"@types/uuid": "9.0.7",
"@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0",
"@vitest/ui": "^0.34.0",
"@typescript-eslint/eslint-plugin": "6.18.1",
"@typescript-eslint/parser": "6.18.1",
"@vitest/ui": "^1.1.3",
"autoprefixer": "^10.4.14",
"c8": "7.14.0",
"chai": "^4.3.7",
@@ -39,33 +39,35 @@
"cy-verify-downloads": "0.2.2",
"cypress": "12.17.4",
"cypress-localstorage-commands": "2.2.5",
"eslint": "8.55.0",
"eslint-config-prettier": "8.10.0",
"eslint": "8.56.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-cypress": "2.15.1",
"eslint-plugin-es": "4.1.0",
"eslint-plugin-es": "^4.1.0",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-postcss-modules": "2.0.0",
"eslint-plugin-svelte3": "4.0.0",
"eslint-plugin-tailwindcss": "3.13.0",
"eslint-plugin-unicorn": "^46.0.0",
"eslint-plugin-vitest": "^0.3.0",
"esserializer": "1.3.11",
"eslint-plugin-postcss-modules": "^2.0.0",
"eslint-plugin-svelte": "^2.35.1",
"eslint-plugin-tailwindcss": "^3.13.1",
"eslint-plugin-unicorn": "^50.0.1",
"eslint-plugin-vitest": "^0.3.20",
"esserializer": "^1.3.11",
"font-awesome": "^4.7.0",
"husky": "^8.0.3",
"jsdom": "21.1.2",
"lint-staged": "13.3.0",
"jsdom": "^21.1.2",
"lint-staged": "^15.2.0",
"node-html-parser": "^6.1.5",
"postcss": "^8.4.21",
"postcss-load-config": "4.0.2",
"prettier": "2.8.8",
"prettier-plugin-svelte": "^2.10.0",
"svelte": "3.59.2",
"svelte-preprocess": "5.1.1",
"tailwindcss": "^3.3.1",
"tslib": "^2.5.0",
"typescript": "5.3.2",
"vite": "^4.5.1",
"vitest": "^0.34.0"
"postcss": "^8.4.33",
"postcss-load-config": "5.0.2",
"prettier": "^3.1.0",
"prettier-plugin-svelte": "^3.1.2",
"prettier-plugin-tailwindcss": "^0.5.11",
"svelte": "^4.2.8",
"svelte-preprocess": "^5.1.3",
"tailwindcss": "^3.4.1",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
"vite": "^5.0.11",
"vitest": "^1.1.3",
"vitest-dom": "^0.1.1"
},
"dependencies": {
"daisyui": "2.52.0",
+1 -1
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
+1 -1
View File
@@ -3,7 +3,7 @@
@tailwind utilities;
.input {
@apply flex-1 border-primary border-solid border-2 rounded;
@apply flex-1 rounded border-2 border-solid border-primary;
}
.action-btn {
@apply btn btn-primary;
-15
View File
@@ -1,16 +1 @@
/* eslint-disable @typescript-eslint/no-empty-interface */
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-unused-vars */
/// <reference types="@sveltejs/kit" />
import type { TestingLibraryMatchers } from '@testing-library/jest-dom/matchers';
declare global {
namespace jest {
interface Matchers<R = void>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
// eslint-disable-next-line no-undef
extends TestingLibraryMatchers<typeof expect.stringContaining, R> {}
}
}
+24 -26
View File
@@ -7,12 +7,12 @@
import { logEvent } from '$lib/util/stats';
import { toBase64 } from 'js-base64';
import dayjs from 'dayjs';
const { krokiRendererUrl, rendererUrl } = env;
const { krokiRendererUrl, rendererUrl } = env;
type Exporter = (context: CanvasRenderingContext2D, image: HTMLImageElement) => () => void;
const getFileName = (ext: string) =>
`mermaid-diagram-${dayjs().format('YYYY-MM-DD-HHmmss')}.${ext}`;
const getFileName = (extension: string) =>
`mermaid-diagram-${dayjs().format('YYYY-MM-DD-HHmmss')}.${extension}`;
const getBase64SVG = (svg?: HTMLElement, width?: number, height?: number): string => {
if (svg) {
@@ -22,7 +22,7 @@
height && svg?.setAttribute('height', `${height}px`);
width && svg?.setAttribute('width', `${width}px`); // Workaround https://stackoverflow.com/questions/28690643/firefox-error-rendering-an-svg-image-to-html5-canvas-with-drawimage
if (!svg) {
svg = getSvgEl();
svg = getSvgElement();
}
const svgString = svg.outerHTML
.replaceAll('<br>', '<br/>')
@@ -32,7 +32,7 @@
const exportImage = (event: Event, exporter: Exporter) => {
const canvas: HTMLCanvasElement = document.createElement('canvas');
const svg: HTMLElement | null = document.querySelector('#container svg');
const svg = document.querySelector<HTMLElement>('#container svg');
if (!svg) {
throw new Error('svg not found');
}
@@ -57,28 +57,26 @@
context.fillRect(0, 0, canvas.width, canvas.height);
const image = new Image();
image.onload = exporter(context, image);
image.addEventListener('load', exporter(context, image));
image.src = `data:image/svg+xml;base64,${getBase64SVG(svg, canvas.width, canvas.height)}`;
event.stopPropagation();
event.preventDefault();
};
const getSvgEl = () => {
const svgEl: HTMLElement = document
.querySelector('#container svg')!
.cloneNode(true) as HTMLElement;
svgEl.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
const fontAwesomeCdnUrl = Array.from(document.head.getElementsByTagName('link'))
.map((l) => l.href)
.find((h) => h.includes('font-awesome'));
const getSvgElement = () => {
const svgElement = document.querySelector('#container svg')?.cloneNode(true) as HTMLElement;
svgElement.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
const fontAwesomeCdnUrl = [...document.head.querySelectorAll('link')]
.map((link) => link.href)
.find((url) => url.includes('font-awesome'));
if (fontAwesomeCdnUrl == null) {
return svgEl;
return svgElement;
}
const styleEl = document.createElement('style');
styleEl.innerText = `@import url("${fontAwesomeCdnUrl}");'`;
svgEl.prepend(styleEl);
return svgEl;
const styleElement = document.createElement('style');
styleElement.textContent = `@import url("${fontAwesomeCdnUrl}");'`;
svgElement.prepend(styleElement);
return svgElement;
};
const simulateDownload = (download: string, href: string): void => {
@@ -144,7 +142,7 @@
};
const onCopyMarkdown = () => {
(document.getElementById('markdown') as HTMLInputElement).select();
document.querySelector<HTMLInputElement>('#markdown')?.select();
document.execCommand('Copy');
logEvent('copyMarkdown');
};
@@ -185,7 +183,7 @@
</script>
<Card title="Actions" isOpen={false}>
<div class="flex flex-wrap gap-2 m-2">
<div class="m-2 flex flex-wrap gap-2">
{#if isClipboardAvailable()}
<button class="action-btn w-full" on:click={onCopyClipboard}
><i class="far fa-copy mr-2" /> Copy Image to clipboard
@@ -213,7 +211,7 @@
</button>
</a>
<div class="flex gap-2 items-center">
<div class="flex items-center gap-2">
PNG size
<label for="autosize">
<input type="radio" value="auto" id="autosize" bind:group={imagemodeselected} /> Auto
@@ -238,7 +236,7 @@
{/if}
</div>
<div class="w-full flex gap-2 items-center">
<div class="flex w-full items-center gap-2">
<input class="input" id="markdown" type="text" value={mdCode} on:click={onCopyMarkdown} />
<label for="markdown">
<button class="btn btn-primary btn-md flex-auto" on:click={onCopyMarkdown}>
@@ -247,7 +245,7 @@
</label>
</div>
<div class="w-full flex gap-2 items-center">
<div class="flex w-full items-center gap-2">
<input
class="input"
id="gist"
@@ -259,8 +257,8 @@
</label>
</div>
{#if isNetlify}
<div class="w-full flex items-center justify-center">
<a class="link underline text-gray-500 text-sm" href="https://netlify.com">
<div class="flex w-full items-center justify-center">
<a class="link text-sm text-gray-500 underline" href="https://netlify.com">
This site is powered by Netlify
</a>
</div>
+5 -3
View File
@@ -11,20 +11,22 @@
$: isTabsShown = isOpen && tabs.length > 0;
</script>
<div class="card rounded overflow-hidden m-2 flex-grow flex flex-col shadow-2xl">
<div class="card m-2 flex flex-grow flex-col overflow-hidden rounded shadow-2xl">
<div
role="toolbar"
tabindex="0"
class="bg-primary p-2 {isTabsShown ? 'pb-0' : ''} flex-none cursor-pointer"
on:click={() => (isOpen = !isOpen)}
on:keypress={() => (isOpen = !isOpen)}>
<div class="flex justify-between">
<Tabs on:select {tabs} bind:isOpen {title} {isCloseable} {activeTabID} />
<div class="flex gap-x-4 items-center {isTabsShown ? '-mt-2' : ''}">
<div class="flex items-center gap-x-4 {isTabsShown ? '-mt-2' : ''}">
<slot name="actions" />
</div>
</div>
</div>
{#if isOpen}
<div class="card-body p-0 flex-grow overflow-auto text-base-content" transition:slide>
<div class="card-body flex-grow overflow-auto p-0 text-base-content" transition:slide>
<slot />
</div>
{/if}
+4
View File
@@ -20,6 +20,8 @@
<div class="flex cursor-default">
<span
role="menubar"
tabindex="0"
class="mr-2 font-semibold"
on:click|stopPropagation={() => (isOpen = !isOpen)}
on:keypress|stopPropagation={() => (isOpen = !isOpen)}>
@@ -31,6 +33,8 @@
<ul class="tabs" transition:fade>
{#each tabs as tab}
<div
role="tab"
tabindex="0"
class="tab tab-lifted {activeTabID === tab.id ? 'tab-active' : 'text-primary-content'}"
on:click|stopPropagation={() => toggleTabs(tab)}
on:keypress|stopPropagation={() => toggleTabs(tab)}>
+17 -12
View File
@@ -1,3 +1,12 @@
<script lang="ts" context="module">
declare global {
interface Window {
Cypress: boolean;
editorLoaded: boolean;
}
}
</script>
<script lang="ts">
import type { EditorMode } from '$lib/types';
import { stateStore, updateCode, updateConfig } from '$lib/util/state';
@@ -10,7 +19,7 @@
import { initEditor } from '$lib/util/monacoExtra';
import { logEvent } from '$lib/util/stats';
let divEl: HTMLDivElement | undefined = undefined;
let divElement: HTMLDivElement | undefined;
let editor: monaco.editor.IStandaloneCodeEditor | undefined;
let editorOptions: monaco.editor.IStandaloneEditorConstructionOptions = {
minimap: {
@@ -64,7 +73,7 @@
}
};
onMount(async () => {
onMount(() => {
self.MonacoEnvironment = {
getWorker(_, label) {
if (label === 'json') {
@@ -74,16 +83,15 @@
}
};
if (!divEl) {
if (!divElement) {
throw new Error('divEl is undefined');
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
initEditor(monaco);
errorDebug(100);
editor = monaco.editor.create(divEl, editorOptions);
editor.onDidChangeModelContent(({ isFlush, changes }) => {
editor = monaco.editor.create(divElement, editorOptions);
editor.onDidChangeModelContent(({ isFlush }) => {
const newText = editor?.getValue();
// console.log('editor onDidChangeModelContent', { text, newText, isFlush, changes });
if (!newText || text === newText || isFlush) {
return;
}
@@ -109,20 +117,17 @@
});
});
if (divEl.parentElement) {
resizeObserver.observe(divEl.parentElement);
if (divElement.parentElement) {
resizeObserver.observe(divElement.parentElement);
}
// @ts-ignore
if (window.Cypress) {
// @ts-ignore
window.editorLoaded = true;
}
return () => {
// console.log(`editor disposed`);
editor?.dispose();
};
});
</script>
<div bind:this={divEl} id="editor" class="overflow-hidden" />
<div bind:this={divElement} id="editor" class="overflow-hidden" />
+14 -18
View File
@@ -20,7 +20,7 @@
dayjs.extend(dayjsRelativeTime);
const HISTORY_SAVE_INTERVAL = 60000;
const HISTORY_SAVE_INTERVAL = 60_000;
const tabSelectHandler = (message: CustomEvent<Tab>) => {
historyModeStore.set(message.detail.id as HistoryType);
@@ -56,17 +56,13 @@
const input = document.createElement('input');
input.type = 'file';
input.accept = 'application/json';
input.addEventListener('change', ({ target }: Event) => {
const file = (<HTMLInputElement>target).files[0];
input.addEventListener('change', async ({ target }: Event) => {
const file = (target as HTMLInputElement)?.files?.[0];
if (!file) {
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const data: HistoryEntry[] = JSON.parse(e.target.result as string);
restoreHistory(data);
};
reader.readAsText(file);
const data: HistoryEntry[] = JSON.parse(await file.text());
restoreHistory(data);
});
input.click();
};
@@ -129,34 +125,34 @@
<div slot="actions">
<button
id="uploadHistory"
class="btn btn-xs btn-secondary w-12"
class="btn btn-secondary btn-xs w-12"
on:click|stopPropagation={() => uploadHistory()}
title="Upload history"><i class="fa fa-upload" /></button>
{#if $historyStore.length > 0}
<button
id="downloadHistory"
class="btn btn-xs btn-secondary w-12"
class="btn btn-secondary btn-xs w-12"
on:click|stopPropagation={() => downloadHistory()}
title="Download history"><i class="fa fa-download" /></button>
{/if}
|
<button
id="saveHistory"
class="btn btn-xs btn-success w-12"
class="btn btn-success btn-xs w-12"
on:click|stopPropagation={() => saveHistory()}
title="Save current state"><i class="far fa-save" /></button>
{#if $historyModeStore !== 'loader'}
<button
id="clearHistory"
class="btn btn-xs btn-error w-12"
class="btn btn-error btn-xs w-12"
on:click|stopPropagation={() => clearHistory()}
title="Delete all saved states"><i class="fas fa-trash-alt" /></button>
{/if}
</div>
<ul class="p-2 space-y-2 overflow-auto h-56" id="historyList">
<ul class="h-56 space-y-2 overflow-auto p-2" id="historyList">
{#if $historyStore.length > 0}
{#each $historyStore as { id, state, time, name, url, type }}
<li class="rounded p-2 shadow flex-col">
<li class="flex-col rounded p-2 shadow">
<div class="flex">
<div class="flex-1">
<div class="flex flex-col text-base-content">
@@ -165,14 +161,14 @@
href={url}
target="_blank"
title="Open revision in new tab"
class="hover:underline text-blue-500">{name}</a>
class="text-blue-500 hover:underline">{name}</a>
{:else}
<span>{name}</span>
{/if}
<span class="text-gray-400 text-sm">{relativeTime(time)}</span>
<span class="text-sm text-gray-400">{relativeTime(time)}</span>
</div>
</div>
<div class="flex gap-2 content-center">
<div class="flex content-center gap-2">
<button class="btn btn-success" on:click={() => restoreHistoryItem(state)}
><i class="fas fa-undo mr-1" />Restore</button>
{#if type !== 'loader'}
+13 -8
View File
@@ -62,8 +62,13 @@
{#if activePromotion}
<div
class="z-10 w-full h-fit top-bar bg-gradient-to-r from-[#bd34fe] to-[#ff3670] flex items-center text-center justify-center p-1 text-white">
<div class="flex flex-grow" on:click={trackBannerClick} on:keypress={trackBannerClick}>
class="top-bar z-10 flex h-fit w-full items-center justify-center bg-gradient-to-r from-[#bd34fe] to-[#ff3670] p-1 text-center text-white">
<div
class="flex flex-grow"
role="button"
tabindex="0"
on:click={trackBannerClick}
on:keypress={trackBannerClick}>
<svelte:component this={activePromotion.component} />
</div>
<button
@@ -77,8 +82,8 @@
</div>
{/if}
<div class="navbar shadow-lg bg-primary p-0">
<div class="flex-1 px-2 mx-2">
<div class="navbar bg-primary p-0 shadow-lg">
<div class="mx-2 flex-1 px-2">
<span class="text-lg font-bold">
<a href="/">Mermaid<span class="text-xs font-thin">v{version}</span> Live Editor</a>
</span>
@@ -86,7 +91,7 @@
<label
for="menu-toggle"
class={isMenuOpen ? 'hidden' : 'pointer-cursor lg:hidden fixed right-4 z-[1000]'}>
class={isMenuOpen ? 'hidden' : 'pointer-cursor fixed right-4 z-[1000] lg:hidden'}>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
@@ -102,7 +107,7 @@
<!-- Cross SVG -->
<label
for="menu-toggle"
class={isMenuOpen ? 'pointer-cursor lg:hidden fixed right-4 z-[1000]' : 'hidden'}>
class={isMenuOpen ? 'pointer-cursor fixed right-4 z-[1000] lg:hidden' : 'hidden'}>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
@@ -123,9 +128,9 @@
bind:checked={isMenuOpen}
on:click={toggleMenu} />
<div class="hidden lg:flex lg:items-center lg:w-auto w-full" id="menu">
<div class="hidden w-full lg:flex lg:w-auto lg:items-center" id="menu">
<Theme />
<ul class="lg:flex items-center justify-between text-base pt-4 lg:pt-0">
<ul class="items-center justify-between pt-4 text-base lg:flex lg:pt-0">
{#each links as { title, href, icon, img }}
<li>
<a class="btn btn-ghost" target="_blank" {href}>
+3 -3
View File
@@ -153,14 +153,14 @@
</script>
<Card title="Sample Diagrams" isOpen={false}>
<div class="flex flex-wrap p-2 gap-2">
<div class="flex flex-wrap gap-2 p-2">
{#each diagramOrder as sample}
<button
class="btn btn-sm btn-primary w-28 normal-case flex-grow"
class="btn btn-primary btn-sm w-28 flex-grow normal-case"
on:click={() => loadSampleDiagram(sample)}>
{sample}
{#if newDiagrams.includes(sample)}
<span class="ml-2 fa fa-heart" />
<span class="fa fa-heart ml-2" />
{/if}
</button>
{/each}
+7 -5
View File
@@ -26,14 +26,14 @@
];
</script>
<div class="hidden lg:block dropdown">
<div class="dropdown hidden lg:block">
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div tabindex="0" class="btn btn-ghost">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="inline-block w-6 h-6 stroke-current md:mr-2"
class="inline-block h-6 w-6 stroke-current md:mr-2"
><path
stroke-linecap="round"
stroke-linejoin="round"
@@ -43,17 +43,19 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1792 1792"
class="inline-block w-4 h-4 ml-1 fill-current"
class="ml-1 inline-block h-4 w-4 fill-current"
><path
d="M1395 736q0 13-10 23l-466 466q-10 10-23 10t-23-10l-466-466q-10-10-10-23t10-23l50-50q10-10 23-10t23 10l393 393 393-393q10-10 23-10t23 10l50 50q10 10 10 23z" /></svg>
</div>
<div
class="mt-14 overflow-y-auto shadow-2xl top-px dropdown-content h-96 w-56 bg-base-200 text-base-content">
class="dropdown-content top-px mt-14 h-96 w-56 overflow-y-auto bg-base-200 text-base-content shadow-2xl">
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<ul tabindex="0" class="p-4 menu compact">
<ul tabindex="0" class="menu compact p-4">
{#each themes as theme}
<li class:bordered={$themeStore.theme !== undefined && theme.includes($themeStore.theme)}>
<span
role="menuitem"
tabindex="0"
class="btn btn-ghost justify-start"
on:click={() => setTheme(theme)}
on:keypress={() => setTheme(theme)}>{theme}</span>
+15 -10
View File
@@ -13,6 +13,7 @@
let container: HTMLDivElement;
let view: HTMLDivElement;
let error = false;
let errorLines: string[] = [];
let outOfSync = false;
let hide = false;
let manualUpdate = true;
@@ -37,7 +38,7 @@
pzoom?.destroy();
pzoom = undefined;
void Promise.resolve().then(() => {
const graphDiv = document.getElementById('graph-div');
const graphDiv = document.querySelector<HTMLElement>('#graph-div');
if (!graphDiv) {
return;
}
@@ -60,6 +61,7 @@
const handleStateChange = async (state: ValidatedState) => {
if (state.error !== undefined) {
error = true;
errorLines = state.error.toString().split('\n');
return;
}
error = false;
@@ -77,7 +79,7 @@
code = state.code;
config = state.mermaid;
panZoomEnabled = state.panZoom;
const scroll = view.parentElement!.scrollTop;
const scroll = view.parentElement?.scrollTop;
delete container.dataset.processed;
const { svg, bindFunctions } = await renderDiagram(
Object.assign({}, JSON.parse(state.mermaid)) as MermaidConfig,
@@ -89,7 +91,7 @@
handlePanZoom(state);
container.innerHTML = svg;
console.log({ svg });
const graphDiv = document.getElementById('graph-div');
const graphDiv = document.querySelector<HTMLElement>('#graph-div');
if (!graphDiv) {
throw new Error('graph-div not found');
}
@@ -99,16 +101,17 @@
bindFunctions(graphDiv);
}
}
view.parentElement!.scrollTop = scroll;
if (view.parentElement && scroll) {
view.parentElement.scrollTop = scroll;
}
error = false;
} else if (manualUpdate) {
manualUpdate = false;
} else if (code !== state.code || config !== state.mermaid) {
outOfSync = true;
}
} catch (e) {
console.error('view fail', e);
} catch (error_) {
console.error('view fail', error_);
error = true;
}
};
@@ -127,12 +130,14 @@
{#if (error && $stateStore.error instanceof Error) || outOfSync}
<div
class="absolute w-full p-2 z-10 font-mono {error
class="absolute z-10 w-full p-2 font-mono {error
? 'text-red-600'
: 'text-yellow-600'} bg-base-100 bg-opacity-80 text-left"
id="errorContainer">
{#if error}
{@html $stateStore.error?.toString().replace(/\n/g, '<br />')}
{#each errorLines as line}
{line}<br />
{/each}
{:else}
Diagram out of sync. <br />
Press <i class="fas fa-sync" /> (Sync button) or <kbd>{cmdKey} + Enter</kbd> to sync.
@@ -140,7 +145,7 @@
</div>
{/if}
<div id="view" bind:this={view} class="p-2 h-full" class:error class:outOfSync>
<div id="view" bind:this={view} class="h-full p-2" class:error class:outOfSync>
<div id="container" bind:this={container} class="h-full overflow-auto" class:hide />
</div>
+1 -1
View File
@@ -109,7 +109,7 @@ export const loadGistData = async (gistURL: string): Promise<State> => {
throw new Error('Invalid gist provided');
}
gistHistory.reverse();
const entry = gistHistory.slice(-1).pop();
const entry = gistHistory.at(-1);
if (!entry) {
throw new Error('Invalid gist provided');
}
+1 -1
View File
@@ -213,7 +213,7 @@ function getBrowserStorage(
disconnect();
}
},
getValue(key: string): any | null {
getValue(key: string): any {
const value = browserStorage.getItem(key);
return deserialize(value);
},
+1 -1
View File
@@ -1,7 +1,7 @@
<a
href="https://www.mermaidchart.com/app/user/billing/checkout?coupon=HOLIDAYS2023"
target="_blank"
class="tracking-wide flex-grow">
class="flex-grow tracking-wide">
Get AI, team collaboration, storage, and more with
<span class="font-bold underline">Mermaid Chart Pro. Start free trial today & get 25% off.</span>
</a>
+1 -1
View File
@@ -14,7 +14,7 @@ const promotions: Promotion[] = [
{
id: 'holiday-2023',
startDate: new Date('2023-11-27'),
endDate: new Date('2024-01-10'),
endDate: new Date('2024-01-09'),
component: Holiday2023
},
{
+17 -17
View File
@@ -35,7 +35,7 @@ export const detectType = (text: string): string | undefined => {
'mindmap'
];
const firstLine = text
.replace(/^\s*%%.*\n/g, '\n')
.replaceAll(/^\s*%%.*\n/g, '\n')
.trimStart()
.split(' ')[0]
.toLowerCase();
@@ -57,22 +57,22 @@ export const saveStatistics = (graph: string): void => {
length < 10
? '0-10'
: length < 25
? '10-25'
: length < 50
? '25-50'
: length < 100
? '50-100'
: length < 200
? '100-200'
: length < 500
? '200-500'
: length < 700
? '500-700'
: length < 1000
? '700-1000'
: length < 1500
? '1000-1500'
: '1500+';
? '10-25'
: length < 50
? '25-50'
: length < 100
? '50-100'
: length < 200
? '100-200'
: length < 500
? '200-500'
: length < 700
? '500-700'
: length < 1000
? '700-1000'
: length < 1500
? '1000-1500'
: '1500+';
logEvent('render', { graphType, length, lengthBucket });
};
+3 -3
View File
@@ -34,7 +34,7 @@
themeStore.subscribe(({ theme, isDark }) => {
if (theme) {
document.getElementsByTagName('html')[0].setAttribute('data-theme', theme);
document.querySelectorAll('html')[0].dataset.theme = theme;
toggleDarkTheme(isDark);
}
});
@@ -47,8 +47,8 @@
{#if $loadingStateStore.loading}
<div
class="w-screen h-screen z-50 absolute left-0 top-0 bg-gray-600 opacity-50 flex align-middle justify-center">
<div class="text-indigo-100 text-4xl font-bold my-auto">
class="absolute left-0 top-0 z-50 flex h-screen w-screen justify-center bg-gray-600 align-middle opacity-50">
<div class="my-auto text-4xl font-bold text-indigo-100">
<div class="loader mx-auto" />
<div>{$loadingStateStore.message}</div>
</div>
+15 -15
View File
@@ -59,7 +59,7 @@
let docKey = '';
stateStore.subscribe(({ code, editorMode }: ValidatedState) => {
activeTabID = editorMode;
const codeTypeMatch = /([\S]+)[\s\n]/.exec(code);
const codeTypeMatch = /(\S+)\s/.exec(code);
if (codeTypeMatch && codeTypeMatch.length > 1) {
docKey = codeTypeMatch[1];
const docConfig = docMap[docKey] ?? { code: '' };
@@ -87,14 +87,14 @@
onMount(async () => {
await initHandler();
const resizer = document.getElementById('resizeHandler');
const element = document.getElementById('editorPane');
const resizer = document.querySelector<HTMLElement>('#resizeHandler');
const element = document.querySelector<HTMLElement>('#editorPane');
if (!resizer || !element) {
console.debug('Failed to find resize handler or editor pane', { resizer, element });
return;
}
const resize = (e: { pageX: number }) => {
const newWidth = e.pageX - element.getBoundingClientRect().left;
const resize = ({ pageX }: { pageX: number }) => {
const newWidth = pageX - element.getBoundingClientRect().left;
if (newWidth > 50) {
element.style.width = `${newWidth}px`;
}
@@ -103,22 +103,22 @@
const stopResize = () => {
window.removeEventListener('mousemove', resize);
};
resizer.addEventListener('mousedown', (e) => {
e.preventDefault();
resizer.addEventListener('mousedown', (event) => {
event.preventDefault();
window.addEventListener('mousemove', resize);
window.addEventListener('mouseup', stopResize);
});
});
</script>
<div class="h-full flex flex-col overflow-hidden">
<div class="flex h-full flex-col overflow-hidden">
<Navbar />
<div class="flex-1 flex overflow-hidden">
<div class="hidden md:flex flex-col" id="editorPane" style="width: 40%">
<div class="flex flex-1 overflow-hidden">
<div class="hidden flex-col md:flex" id="editorPane" style="width: 40%">
<Card on:select={tabSelectHandler} {tabs} isCloseable={false} {activeTabID} title="Mermaid">
<div slot="actions" class="flex flex-row items-center">
<div class="form-control flex-row items-center">
<label class="cursor-pointer label" for="autoSync">
<label class="label cursor-pointer" for="autoSync">
<span> Auto sync</span>
<input
type="checkbox"
@@ -155,10 +155,10 @@
</div>
</div>
<div id="resizeHandler" class="hidden md:block" />
<div class="flex-1 flex flex-col overflow-hidden">
<div class="flex flex-1 flex-col overflow-hidden">
<Card title="Diagram" isCloseable={false}>
<div slot="actions" class="flex flex-row items-center gap-2">
<label class="cursor-pointer label py-0" for="panZoom">
<label class="label cursor-pointer py-0" for="panZoom">
<span>Pan & Zoom</span>
<input
type="checkbox"
@@ -176,7 +176,7 @@
target="_blank"
class="btn btn-secondary btn-xs gap-1 bg-[#FF3570]"
title="Save diagram in Mermaid Chart"
><img src="./mermaidchart-logo.svg" class="w-5 h-5" alt="Mermaid chart logo" />Save to
><img src="./mermaidchart-logo.svg" class="h-5 w-5" alt="Mermaid chart logo" />Save to
Mermaid Chart</a>
</div>
@@ -184,7 +184,7 @@
<View />
</div>
</Card>
<div class="md:hidden rounded shadow p-2 mx-2">
<div class="mx-2 rounded p-2 shadow md:hidden">
Code editing not supported on mobile. Please use a desktop browser.
</div>
</div>
+2 -4
View File
@@ -1,7 +1,5 @@
import matchers from '@testing-library/jest-dom/matchers';
import { expect, beforeAll, vi } from 'vitest';
expect.extend(matchers);
import 'vitest-dom/extend-expect';
import { beforeAll, vi } from 'vitest';
// TODO: Remove once https://github.com/sveltejs/kit/issues/6259 is closed.
beforeAll(() => {
+930 -881
View File
File diff suppressed because it is too large Load Diff