diff --git a/.env b/.env
index c9bdfa82..d0a789a3 100644
--- a/.env
+++ b/.env
@@ -1,7 +1,7 @@
MERMAID_DOMAIN=''
MERMAID_ANALYTICS_URL=''
MERMAID_RENDERER_URL='https://mermaid.ink'
-MERMAID_KROKI_RENDERER='https://kroki.io'
+MERMAID_KROKI_RENDERER_URL='https://kroki.io'
MERMAID_IS_ENABLED_MERMAID_CHART_LINKS=''
# cp .env .env.local to make local changes
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index bc6f10b9..8300399a 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -11,7 +11,15 @@ module.exports = {
'plugin:svelte/prettier',
'prettier'
],
- plugins: ['tailwindcss', '@typescript-eslint', 'es', 'vitest', 'no-only-tests', 'unicorn'],
+ plugins: [
+ 'tailwindcss',
+ '@typescript-eslint',
+ 'es',
+ 'vitest',
+ 'no-only-tests',
+ 'sort-keys',
+ 'unicorn'
+ ],
ignorePatterns: [
'docs/*',
'*.cjs',
@@ -40,6 +48,15 @@ module.exports = {
'plugin:@typescript-eslint/strict',
'prettier'
]
+ },
+ {
+ files: ['**/components/ui/**'],
+ rules: {
+ 'unicorn/prefer-export-from': 'off',
+ 'unicorn/prevent-abbreviations': 'off',
+ 'unicorn/explicit-length-check': 'off',
+ 'sort-keys/sort-keys-fix': 'off'
+ }
}
],
parserOptions: {
@@ -54,6 +71,7 @@ module.exports = {
es2020: true
},
rules: {
+ 'sort-keys/sort-keys-fix': ['error', 'asc', { minKeys: 5 }],
'@typescript-eslint/ban-ts-comment': [
'error',
{
@@ -77,6 +95,7 @@ module.exports = {
'error',
{
allowList: {
+ args: true,
ctx: true,
db: true,
doc: true,
@@ -87,6 +106,7 @@ module.exports = {
k: true,
param: true,
Props: true,
+ props: true,
req: true,
res: true,
str: true,
diff --git a/.prettierrc b/.prettierrc
index e0366fc9..a0a85589 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -4,6 +4,6 @@
"bracketSameLine": true,
"trailingComma": "none",
"printWidth": 100,
- "tailwindConfig": "./tailwind.config.cjs",
+ "tailwindConfig": "./tailwind.config.js",
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"]
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 4d231aa1..5c2a6e7b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -7,7 +7,6 @@
"ckppp",
"corg",
"cssnano",
- "daisyui",
"esserializer",
"gantt",
"gitgraph",
@@ -22,6 +21,7 @@
"roughjs",
"Serde",
"serdes",
+ "Stackable",
"tailwindcss",
"uparrow"
],
@@ -29,5 +29,13 @@
"vitest.enable": true,
"testing.autoRun.mode": "rerun",
"svelte.enable-ts-plugin": true,
- "githubPullRequests.ignoredPullRequestBranches": ["develop"]
+ "githubPullRequests.ignoredPullRequestBranches": ["develop"],
+ "[svelte]": {
+ "editor.defaultFormatter": "svelte.svelte-vscode"
+ },
+ "tailwindCSS.classAttributes": ["class", "className", ".*Classes"],
+ "tailwindCSS.experimental.classRegex": [
+ ["([\"'`][^\"'`]*.*?[\"'`])", "[\"'`]([^\"'`]*).*?[\"'`]"]
+ ],
+ "tailwindCSS.emmetCompletions": true
}
diff --git a/components.json b/components.json
new file mode 100644
index 00000000..4398a0f7
--- /dev/null
+++ b/components.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://next.shadcn-svelte.com/schema.json",
+ "style": "new-york",
+ "tailwind": {
+ "config": "tailwind.config.js",
+ "css": "src/app.postcss",
+ "baseColor": "slate"
+ },
+ "aliases": {
+ "components": "$lib/components",
+ "utils": "$lib/utils",
+ "ui": "$lib/components/ui",
+ "hooks": "$lib/hooks"
+ },
+ "typescript": true,
+ "registry": "https://next.shadcn-svelte.com/registry"
+}
diff --git a/package.json b/package.json
index 408e7ffc..d9e43dd5 100644
--- a/package.json
+++ b/package.json
@@ -24,11 +24,11 @@
},
"devDependencies": {
"@fortawesome/fontawesome-free": "^6.5.2",
+ "@iconify-json/material-symbols": "^1.2.15",
+ "@iconify-json/mdi": "^1.2.3",
"@sveltejs/adapter-static": "3.0.8",
"@sveltejs/kit": "2.20.0",
"@sveltejs/vite-plugin-svelte": "^4.0.4",
- "@testing-library/jest-dom": "^6.6.3",
- "@testing-library/svelte": "^5.2.7",
"@types/lodash-es": "^4.17.12",
"@types/pako": "2.0.3",
"@types/uuid": "9.0.8",
@@ -37,14 +37,17 @@
"@vitest/coverage-v8": "2.1.9",
"@vitest/ui": "^2.1.9",
"autoprefixer": "^10.4.21",
+ "bits-ui": "^1.3.12",
"c8": "7.14.0",
"chai": "^4.3.10",
+ "clsx": "^2.1.1",
"cssnano": "^6.0.5",
"eslint": "8.57.1",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-es": "^4.1.0",
"eslint-plugin-no-only-tests": "^3.3.0",
"eslint-plugin-postcss-modules": "^2.0.0",
+ "eslint-plugin-sort-keys": "^2.3.5",
"eslint-plugin-svelte": "^2.46.1",
"eslint-plugin-tailwindcss": "^3.18.0",
"eslint-plugin-unicorn": "^50.0.1",
@@ -53,7 +56,9 @@
"husky": "^8.0.3",
"jsdom": "^25.0.1",
"lint-staged": "^15.2.11",
+ "lucide-svelte": "^0.479.0",
"node-html-parser": "^6.1.13",
+ "paneforge": "^1.0.0-next.4",
"postcss": "^8.4.49",
"postcss-load-config": "5.1.0",
"prettier": "^3.1.1",
@@ -61,22 +66,28 @@
"prettier-plugin-tailwindcss": "^0.6.11",
"svelte": "^5.23.1",
"svelte-preprocess": "^6.0.3",
+ "svelte-sonner": "^0.3.28",
+ "tailwind-merge": "^3.0.2",
+ "tailwind-variants": "^0.3.1",
"tailwindcss": "^3.4.17",
+ "tailwindcss-animate": "^1.0.7",
"tslib": "^2.6.3",
"typescript": "^5.5.4",
+ "unplugin-icons": "^22.1.0",
"vite": "^5.4.14",
"vitest": "^2.1.9",
"vitest-dom": "^0.1.1",
"@playwright/test": "^1.42.1"
},
"dependencies": {
+ "@fontsource-variable/recursive": "^5.2.5",
"@mermaid-js/layout-elk": "^0.1.7",
"@mermaid-js/mermaid-zenuml": "^0.2.0",
- "daisyui": "2.52.0",
"dayjs": "^1.11.13",
"js-base64": "3.7.7",
"lodash-es": "^4.17.21",
"mermaid": "^11.5.0",
+ "mode-watcher": "^0.5.1",
"monaco-editor": "0.52.2",
"pako": "2.1.0",
"plausible-tracker": "^0.3.9",
diff --git a/playwright.config.ts b/playwright.config.ts
index 89202d15..8386822f 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -1,12 +1,17 @@
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
- testDir: './tests',
- fullyParallel: true,
forbidOnly: !!process.env.CI,
- retries: process.env.CI ? 2 : 0,
- workers: process.env.CI ? 3 : undefined,
+ fullyParallel: true,
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] }
+ }
+ ],
reporter: process.env.CI ? [['github'], ['list']] : 'list',
+ retries: process.env.CI ? 2 : 0,
+ testDir: './tests',
use: {
baseURL: 'http://localhost:3000',
browserName: 'chromium',
@@ -14,15 +19,10 @@ export default defineConfig({
trace: 'retain-on-failure',
viewport: { width: 1920, height: 1080 }
},
- projects: [
- {
- name: 'chromium',
- use: { ...devices['Desktop Chrome'] }
- }
- ],
webServer: {
command: `pnpm ${process.env.CI ? 'preview' : 'dev'}`,
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI
- }
+ },
+ workers: process.env.CI ? 3 : undefined
});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9a6e4407..93265e3c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,15 +8,15 @@ importers:
.:
dependencies:
+ '@fontsource-variable/recursive':
+ specifier: ^5.2.5
+ version: 5.2.5
'@mermaid-js/layout-elk':
specifier: ^0.1.7
version: 0.1.7(mermaid@11.5.0)
'@mermaid-js/mermaid-zenuml':
specifier: ^0.2.0
version: 0.2.0(mermaid@11.5.0)(typescript@5.8.2)
- daisyui:
- specifier: 2.52.0
- version: 2.52.0(autoprefixer@10.4.21(postcss@8.5.3))(postcss@8.5.3)
dayjs:
specifier: ^1.11.13
version: 1.11.13
@@ -29,6 +29,9 @@ importers:
mermaid:
specifier: ^11.5.0
version: 11.5.0
+ mode-watcher:
+ specifier: ^0.5.1
+ version: 0.5.1(svelte@5.23.1)
monaco-editor:
specifier: 0.52.2
version: 0.52.2
@@ -54,6 +57,12 @@ importers:
'@fortawesome/fontawesome-free':
specifier: ^6.5.2
version: 6.7.2
+ '@iconify-json/material-symbols':
+ specifier: ^1.2.15
+ version: 1.2.16
+ '@iconify-json/mdi':
+ specifier: ^1.2.3
+ version: 1.2.3
'@playwright/test':
specifier: ^1.42.1
version: 1.51.1
@@ -66,12 +75,6 @@ importers:
'@sveltejs/vite-plugin-svelte':
specifier: ^4.0.4
version: 4.0.4(svelte@5.23.1)(vite@5.4.14)
- '@testing-library/jest-dom':
- specifier: ^6.6.3
- version: 6.6.3
- '@testing-library/svelte':
- specifier: ^5.2.7
- version: 5.2.7(svelte@5.23.1)(vite@5.4.14)(vitest@2.1.9)
'@types/lodash-es':
specifier: ^4.17.12
version: 4.17.12
@@ -96,12 +99,18 @@ importers:
autoprefixer:
specifier: ^10.4.21
version: 10.4.21(postcss@8.5.3)
+ bits-ui:
+ specifier: ^1.3.12
+ version: 1.3.12(svelte@5.23.1)
c8:
specifier: 7.14.0
version: 7.14.0
chai:
specifier: ^4.3.10
version: 4.5.0
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
cssnano:
specifier: ^6.0.5
version: 6.1.2(postcss@8.5.3)
@@ -120,6 +129,9 @@ importers:
eslint-plugin-postcss-modules:
specifier: ^2.0.0
version: 2.0.0(eslint@8.57.1)
+ eslint-plugin-sort-keys:
+ specifier: ^2.3.5
+ version: 2.3.5
eslint-plugin-svelte:
specifier: ^2.46.1
version: 2.46.1(eslint@8.57.1)(svelte@5.23.1)
@@ -144,9 +156,15 @@ importers:
lint-staged:
specifier: ^15.2.11
version: 15.5.0
+ lucide-svelte:
+ specifier: ^0.479.0
+ version: 0.479.0(svelte@5.23.1)
node-html-parser:
specifier: ^6.1.13
version: 6.1.13
+ paneforge:
+ specifier: ^1.0.0-next.4
+ version: 1.0.0-next.4(svelte@5.23.1)
postcss:
specifier: ^8.4.49
version: 8.5.3
@@ -168,15 +186,30 @@ importers:
svelte-preprocess:
specifier: ^6.0.3
version: 6.0.3(postcss-load-config@5.1.0(jiti@1.21.7)(postcss@8.5.3))(postcss@8.5.3)(svelte@5.23.1)(typescript@5.8.2)
+ svelte-sonner:
+ specifier: ^0.3.28
+ version: 0.3.28(svelte@5.23.1)
+ tailwind-merge:
+ specifier: ^3.0.2
+ version: 3.0.2
+ tailwind-variants:
+ specifier: ^0.3.1
+ version: 0.3.1(tailwindcss@3.4.17)
tailwindcss:
specifier: ^3.4.17
version: 3.4.17
+ tailwindcss-animate:
+ specifier: ^1.0.7
+ version: 1.0.7(tailwindcss@3.4.17)
tslib:
specifier: ^2.6.3
version: 2.8.1
typescript:
specifier: ^5.5.4
version: 5.8.2
+ unplugin-icons:
+ specifier: ^22.1.0
+ version: 22.1.0(@vue/compiler-sfc@3.5.13)(svelte@5.23.1)
vite:
specifier: ^5.4.14
version: 5.4.14
@@ -189,9 +222,6 @@ importers:
packages:
- '@adobe/css-tools@4.4.2':
- resolution: {integrity: sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==}
-
'@alloc/quick-lru@5.2.0':
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
@@ -226,10 +256,6 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
- '@babel/runtime@7.26.10':
- resolution: {integrity: sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==}
- engines: {node: '>=6.9.0'}
-
'@babel/types@7.26.10':
resolution: {integrity: sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==}
engines: {node: '>=6.9.0'}
@@ -451,6 +477,9 @@ packages:
'@floating-ui/vue@1.1.6':
resolution: {integrity: sha512-XFlUzGHGv12zbgHNk5FN2mUB7ROul3oG2ENdTpWdE+qMFxyNxWSRmsoyhiEnpmabNm6WnUvR1OvJfUfN4ojC1A==}
+ '@fontsource-variable/recursive@5.2.5':
+ resolution: {integrity: sha512-tgz93CoozxkF23TeAr+ihEwwKKvZ9lFosrCjx63XPn7keeMl3nZS+iFMDsY8s1U6+jVHMHpOU8ZAOorsWNtdsA==}
+
'@fortawesome/fontawesome-free@6.7.2':
resolution: {integrity: sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==}
engines: {node: '>=6'}
@@ -486,12 +515,21 @@ packages:
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
deprecated: Use @eslint/object-schema instead
+ '@iconify-json/material-symbols@1.2.16':
+ resolution: {integrity: sha512-hCUbIYLWPX9YMChvqEScG5tvFLZ2arXl+bnsvUQDrtsGq00wu4S4qIofH3uZgClYxXhvApkDKtJDlXhoygzmMQ==}
+
+ '@iconify-json/mdi@1.2.3':
+ resolution: {integrity: sha512-O3cLwbDOK7NNDf2ihaQOH5F9JglnulNDFV7WprU2dSoZu3h3cWH//h74uQAB87brHmvFVxIOkuBX2sZSzYhScg==}
+
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
'@iconify/utils@2.3.0':
resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==}
+ '@internationalized/date@3.7.0':
+ resolution: {integrity: sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==}
+
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
@@ -684,6 +722,9 @@ packages:
svelte: ^5.0.0-next.96 || ^5.0.0
vite: ^5.0.0
+ '@swc/helpers@0.5.15':
+ resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+
'@tanstack/virtual-core@3.13.4':
resolution: {integrity: sha512-fNGO9fjjSLns87tlcto106enQQLycCKR4DPNpgq3djP5IdcPFdPAmaKjsgzIeRhH7hWrELgW12hYnRthS5kLUw==}
@@ -692,34 +733,10 @@ packages:
peerDependencies:
vue: ^2.7.0 || ^3.0.0
- '@testing-library/dom@10.4.0':
- resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
- engines: {node: '>=18'}
-
- '@testing-library/jest-dom@6.6.3':
- resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==}
- engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
-
- '@testing-library/svelte@5.2.7':
- resolution: {integrity: sha512-aGhUaFmEXEVost4QOsbHUUbHLwi7ZZRRxAHFDO2Cmr0BZD3/3+XvaYEPq70Rdw0NRNjdqZHdARBEcrCOkPuAqw==}
- engines: {node: '>= 10'}
- peerDependencies:
- svelte: ^3 || ^4 || ^5 || ^5.0.0-next.0
- vite: '*'
- vitest: '*'
- peerDependenciesMeta:
- vite:
- optional: true
- vitest:
- optional: true
-
'@trysound/sax@0.2.0':
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'}
- '@types/aria-query@5.0.4':
- resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
-
'@types/assert@1.5.11':
resolution: {integrity: sha512-FjS1mxq2dlGr9N4z72/DO+XmyRS3ZZIoVn998MEopAN/OmyN28F4yumRL5pOw2z+hbFLuWGYuF2rrw5p11xM5A==}
@@ -1064,10 +1081,6 @@ packages:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
- ansi-styles@5.2.0:
- resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
- engines: {node: '>=10'}
-
ansi-styles@6.2.1:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'}
@@ -1089,9 +1102,6 @@ packages:
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
- aria-query@5.3.0:
- resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
-
aria-query@5.3.2:
resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
engines: {node: '>= 0.4'}
@@ -1138,6 +1148,12 @@ packages:
bindings@1.5.0:
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
+ bits-ui@1.3.12:
+ resolution: {integrity: sha512-RhPvg2e7mTQSXR9WMdsyR5eTC2DSa4ch5MT/TjIaN3suMJ3RGlbzYPNvf4n56Lgck3W4Xm+L4jSgrzTdynuM8Q==}
+ engines: {node: '>=18', pnpm: '>=8.7.0'}
+ peerDependencies:
+ svelte: ^5.11.0
+
boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
@@ -1202,10 +1218,6 @@ packages:
resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==}
engines: {node: '>=12'}
- chalk@3.0.0:
- resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==}
- engines: {node: '>=8'}
-
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@@ -1266,10 +1278,6 @@ packages:
color-string@1.9.1:
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
- color@4.2.3:
- resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
- engines: {node: '>=12.5.0'}
-
colord@2.9.3:
resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
@@ -1334,9 +1342,6 @@ packages:
css-select@5.1.0:
resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
- css-selector-tokenizer@0.8.0:
- resolution: {integrity: sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==}
-
css-tree@2.2.1:
resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
@@ -1542,12 +1547,6 @@ packages:
dagre-d3-es@7.0.11:
resolution: {integrity: sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==}
- daisyui@2.52.0:
- resolution: {integrity: sha512-LQTA5/IVXAJHBMFoeaEMfd7/akAFPPcdQPR3O9fzzcFiczneJFM73CFPnScmW2sOgn/D83cvkP854ep2T9OfTg==}
- peerDependencies:
- autoprefixer: ^10.0.2
- postcss: ^8.1.6
-
data-urls@5.0.0:
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
engines: {node: '>=18'}
@@ -1593,10 +1592,6 @@ packages:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
- dequal@2.0.3:
- resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
- engines: {node: '>=6'}
-
devalue@5.1.1:
resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==}
@@ -1614,9 +1609,6 @@ packages:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'}
- dom-accessibility-api@0.5.16:
- resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
-
dom-accessibility-api@0.6.3:
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
@@ -1735,6 +1727,9 @@ packages:
peerDependencies:
eslint: '>=0.8.0'
+ eslint-plugin-sort-keys@2.3.5:
+ resolution: {integrity: sha512-2j/XKQ9sNJwK8kIp/U0EvuF6stS6/8aIc53/NskE4C5NRNh4dt3xzbZyOdrVC11cTH6Zo59/pdzA0Kb+2fQGWg==}
+
eslint-plugin-svelte@2.46.1:
resolution: {integrity: sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==}
engines: {node: ^14.17.0 || >=16.0.0}
@@ -1866,9 +1861,6 @@ packages:
resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==}
engines: {node: '>=6'}
- fastparse@1.1.2:
- resolution: {integrity: sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==}
-
fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
@@ -2100,6 +2092,9 @@ packages:
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+ inline-style-parser@0.2.4:
+ resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
+
internmap@1.0.1:
resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==}
@@ -2326,9 +2321,10 @@ packages:
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
- lz-string@1.5.0:
- resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
- hasBin: true
+ lucide-svelte@0.479.0:
+ resolution: {integrity: sha512-epCj6WL86ykxg7oCQTmPEth5e11pwJUzIfG9ROUsWsTP+WPtb3qat+VmAjfx/r4TRW7memTFcbTPvMrZvKthqw==}
+ peerDependencies:
+ svelte: ^3 || ^4 || ^5.0.0-next.42
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
@@ -2412,6 +2408,11 @@ packages:
mlly@1.7.4:
resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==}
+ mode-watcher@0.5.1:
+ resolution: {integrity: sha512-adEC6T7TMX/kzQlaO/MtiQOSFekZfQu4MC+lXyoceQG+U5sKpJWZ4yKXqw846ExIuWJgedkOIPqAYYRk/xHm+w==}
+ peerDependencies:
+ svelte: ^4.0.0 || ^5.0.0-next.1
+
monaco-editor@0.52.2:
resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==}
@@ -2523,6 +2524,11 @@ packages:
pako@2.1.0:
resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
+ paneforge@1.0.0-next.4:
+ resolution: {integrity: sha512-xB7iqIx6sT+XDRx/Eps+lddN3X7zRy8RIqXXFimGuFF6d+3s4YoXrk8SdNwEK5U/mqwupeKX0BbSfy0z9W9feA==}
+ peerDependencies:
+ svelte: ^5.20.0
+
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -2981,10 +2987,6 @@ packages:
engines: {node: '>=14'}
hasBin: true
- pretty-format@27.5.1:
- resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
- engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
-
process-warning@3.0.0:
resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==}
@@ -3011,9 +3013,6 @@ packages:
random-word-slugs@0.1.7:
resolution: {integrity: sha512-8cyzxOIDeLFvwSPTgCItMXHGT5ZPkjhuFKUTww06Xg1dNMXuGxIKlARvS7upk6JXIm41ZKXmtlKR1iCRWklKmg==}
- react-is@17.0.2:
- resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
-
read-cache@1.0.0:
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
@@ -3037,17 +3036,10 @@ packages:
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
engines: {node: '>= 12.13.0'}
- redent@3.0.0:
- resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
- engines: {node: '>=8'}
-
redent@4.0.0:
resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==}
engines: {node: '>=12'}
- regenerator-runtime@0.14.1:
- resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
-
regexp-tree@0.1.27:
resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==}
hasBin: true
@@ -3109,6 +3101,11 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ runed@0.23.4:
+ resolution: {integrity: sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==}
+ peerDependencies:
+ svelte: ^5.7.0
+
rw@1.3.3:
resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
@@ -3251,6 +3248,9 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
+ style-to-object@1.0.8:
+ resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==}
+
stylehacks@6.1.1:
resolution: {integrity: sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==}
engines: {node: ^14 || ^16 || >=18.0}
@@ -3319,6 +3319,17 @@ packages:
typescript:
optional: true
+ svelte-sonner@0.3.28:
+ resolution: {integrity: sha512-K3AmlySeFifF/cKgsYNv5uXqMVNln0NBAacOYgmkQStLa/UoU0LhfAACU6Gr+YYC8bOCHdVmFNoKuDbMEsppJg==}
+ peerDependencies:
+ svelte: ^3.0.0 || ^4.0.0 || ^5.0.0-next.1
+
+ svelte-toolbelt@0.7.1:
+ resolution: {integrity: sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ==}
+ engines: {node: '>=18', pnpm: '>=8.7.0'}
+ peerDependencies:
+ svelte: ^5.0.0
+
svelte@5.23.1:
resolution: {integrity: sha512-DUu3e5tQDO+PtKffjqJ548YfeKtw2Rqc9/+nlP26DZ0AopWTJNylkNnTOP/wcgIt1JSnovyISxEZ/lDR1OhbOw==}
engines: {node: '>=18'}
@@ -3341,6 +3352,26 @@ packages:
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+ tabbable@6.2.0:
+ resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
+
+ tailwind-merge@2.5.4:
+ resolution: {integrity: sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==}
+
+ tailwind-merge@3.0.2:
+ resolution: {integrity: sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==}
+
+ tailwind-variants@0.3.1:
+ resolution: {integrity: sha512-krn67M3FpPwElg4FsZrOQd0U26o7UDH/QOkK8RNaiCCrr052f6YJPBUfNKnPo/s/xRzNPtv1Mldlxsg8Tb46BQ==}
+ engines: {node: '>=16.x', pnpm: '>=7.x'}
+ peerDependencies:
+ tailwindcss: '*'
+
+ tailwindcss-animate@1.0.7:
+ resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || insiders'
+
tailwindcss@3.4.17:
resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
engines: {node: '>=14.0.0'}
@@ -3462,6 +3493,33 @@ packages:
ufo@1.5.4:
resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
+ unplugin-icons@22.1.0:
+ resolution: {integrity: sha512-ect2ZNtk1Zgwb0NVHd0C1IDW/MV+Jk/xaq4t8o6rYdVS3+L660ZdD5kTSQZvsgdwCvquRw+/wYn75hsweRjoIA==}
+ peerDependencies:
+ '@svgr/core': '>=7.0.0'
+ '@svgx/core': ^1.0.1
+ '@vue/compiler-sfc': ^3.0.2 || ^2.7.0
+ svelte: ^3.0.0 || ^4.0.0 || ^5.0.0
+ vue-template-compiler: ^2.6.12
+ vue-template-es2015-compiler: ^1.9.0
+ peerDependenciesMeta:
+ '@svgr/core':
+ optional: true
+ '@svgx/core':
+ optional: true
+ '@vue/compiler-sfc':
+ optional: true
+ svelte:
+ optional: true
+ vue-template-compiler:
+ optional: true
+ vue-template-es2015-compiler:
+ optional: true
+
+ unplugin@2.2.0:
+ resolution: {integrity: sha512-m1ekpSwuOT5hxkJeZGRxO7gXbXT3gF26NjQ7GdVHoLoF8/nopLcd/QfPigpCy7i51oFHiRJg/CyHhj4vs2+KGw==}
+ engines: {node: '>=18.12.0'}
+
update-browserslist-db@1.1.3:
resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
hasBin: true
@@ -3615,6 +3673,9 @@ packages:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'}
+ webpack-virtual-modules@0.6.2:
+ resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
+
whatwg-encoding@3.1.1:
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
engines: {node: '>=18'}
@@ -3705,8 +3766,6 @@ packages:
snapshots:
- '@adobe/css-tools@4.4.2': {}
-
'@alloc/quick-lru@5.2.0': {}
'@ampproject/remapping@2.3.0':
@@ -3743,10 +3802,6 @@ snapshots:
dependencies:
'@babel/types': 7.26.10
- '@babel/runtime@7.26.10':
- dependencies:
- regenerator-runtime: 0.14.1
-
'@babel/types@7.26.10':
dependencies:
'@babel/helper-string-parser': 7.25.9
@@ -3905,6 +3960,8 @@ snapshots:
- '@vue/composition-api'
- vue
+ '@fontsource-variable/recursive@5.2.5': {}
+
'@fortawesome/fontawesome-free@6.7.2': {}
'@headlessui-float/vue@0.14.4(@headlessui/vue@1.7.23(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2))':
@@ -3938,6 +3995,14 @@ snapshots:
'@humanwhocodes/object-schema@2.0.3': {}
+ '@iconify-json/material-symbols@1.2.16':
+ dependencies:
+ '@iconify/types': 2.0.0
+
+ '@iconify-json/mdi@1.2.3':
+ dependencies:
+ '@iconify/types': 2.0.0
+
'@iconify/types@2.0.0': {}
'@iconify/utils@2.3.0':
@@ -3953,6 +4018,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@internationalized/date@3.7.0':
+ dependencies:
+ '@swc/helpers': 0.5.15
+
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
@@ -4125,6 +4194,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@swc/helpers@0.5.15':
+ dependencies:
+ tslib: 2.8.1
+
'@tanstack/virtual-core@3.13.4': {}
'@tanstack/vue-virtual@3.13.4(vue@3.5.13(typescript@5.8.2))':
@@ -4132,39 +4205,8 @@ snapshots:
'@tanstack/virtual-core': 3.13.4
vue: 3.5.13(typescript@5.8.2)
- '@testing-library/dom@10.4.0':
- dependencies:
- '@babel/code-frame': 7.26.2
- '@babel/runtime': 7.26.10
- '@types/aria-query': 5.0.4
- aria-query: 5.3.0
- chalk: 4.1.2
- dom-accessibility-api: 0.5.16
- lz-string: 1.5.0
- pretty-format: 27.5.1
-
- '@testing-library/jest-dom@6.6.3':
- dependencies:
- '@adobe/css-tools': 4.4.2
- aria-query: 5.3.2
- chalk: 3.0.0
- css.escape: 1.5.1
- dom-accessibility-api: 0.6.3
- lodash: 4.17.21
- redent: 3.0.0
-
- '@testing-library/svelte@5.2.7(svelte@5.23.1)(vite@5.4.14)(vitest@2.1.9)':
- dependencies:
- '@testing-library/dom': 10.4.0
- svelte: 5.23.1
- optionalDependencies:
- vite: 5.4.14
- vitest: 2.1.9(@vitest/ui@2.1.9)(jsdom@25.0.1)
-
'@trysound/sax@0.2.0': {}
- '@types/aria-query@5.0.4': {}
-
'@types/assert@1.5.11': {}
'@types/cookie@0.6.0': {}
@@ -4632,8 +4674,6 @@ snapshots:
dependencies:
color-convert: 2.0.1
- ansi-styles@5.2.0: {}
-
ansi-styles@6.2.1: {}
antlr4@4.11.0: {}
@@ -4649,10 +4689,6 @@ snapshots:
argparse@2.0.1: {}
- aria-query@5.3.0:
- dependencies:
- dequal: 2.0.3
-
aria-query@5.3.2: {}
array-union@2.1.0: {}
@@ -4687,6 +4723,17 @@ snapshots:
dependencies:
file-uri-to-path: 1.0.0
+ bits-ui@1.3.12(svelte@5.23.1):
+ dependencies:
+ '@floating-ui/core': 1.6.9
+ '@floating-ui/dom': 1.6.13
+ '@internationalized/date': 3.7.0
+ esm-env: 1.2.2
+ runed: 0.23.4(svelte@5.23.1)
+ svelte: 5.23.1
+ svelte-toolbelt: 0.7.1(svelte@5.23.1)
+ tabbable: 6.2.0
+
boolbase@1.0.0: {}
brace-expansion@1.1.11:
@@ -4771,11 +4818,6 @@ snapshots:
loupe: 3.1.3
pathval: 2.0.0
- chalk@3.0.0:
- dependencies:
- ansi-styles: 4.3.0
- supports-color: 7.2.0
-
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
@@ -4849,11 +4891,6 @@ snapshots:
color-name: 1.1.4
simple-swizzle: 0.2.2
- color@4.2.3:
- dependencies:
- color-convert: 2.0.1
- color-string: 1.9.1
-
colord@2.9.3: {}
colorette@2.0.20: {}
@@ -4910,11 +4947,6 @@ snapshots:
domutils: 3.2.2
nth-check: 2.1.1
- css-selector-tokenizer@0.8.0:
- dependencies:
- cssesc: 3.0.0
- fastparse: 1.1.2
-
css-tree@2.2.1:
dependencies:
mdn-data: 2.0.28
@@ -5170,17 +5202,6 @@ snapshots:
d3: 7.9.0
lodash-es: 4.17.21
- daisyui@2.52.0(autoprefixer@10.4.21(postcss@8.5.3))(postcss@8.5.3):
- dependencies:
- autoprefixer: 10.4.21(postcss@8.5.3)
- color: 4.2.3
- css-selector-tokenizer: 0.8.0
- postcss: 8.5.3
- postcss-js: 4.0.1(postcss@8.5.3)
- tailwindcss: 3.4.17
- transitivePeerDependencies:
- - ts-node
-
data-urls@5.0.0:
dependencies:
whatwg-mimetype: 4.0.0
@@ -5215,8 +5236,6 @@ snapshots:
delayed-stream@1.0.0: {}
- dequal@2.0.3: {}
-
devalue@5.1.1: {}
didyoumean@1.2.2: {}
@@ -5231,8 +5250,6 @@ snapshots:
dependencies:
esutils: 2.0.3
- dom-accessibility-api@0.5.16: {}
-
dom-accessibility-api@0.6.3: {}
dom-serializer@2.0.0:
@@ -5367,6 +5384,10 @@ snapshots:
transitivePeerDependencies:
- ts-node
+ eslint-plugin-sort-keys@2.3.5:
+ dependencies:
+ natural-compare: 1.4.0
+
eslint-plugin-svelte@2.46.1(eslint@8.57.1)(svelte@5.23.1):
dependencies:
'@eslint-community/eslint-utils': 4.5.1(eslint@8.57.1)
@@ -5551,8 +5572,6 @@ snapshots:
fast-redact@3.5.0: {}
- fastparse@1.1.2: {}
-
fastq@1.19.1:
dependencies:
reusify: 1.1.0
@@ -5771,6 +5790,8 @@ snapshots:
inherits@2.0.4: {}
+ inline-style-parser@0.2.4: {}
+
internmap@1.0.1: {}
internmap@2.0.3: {}
@@ -6001,7 +6022,9 @@ snapshots:
lru-cache@10.4.3: {}
- lz-string@1.5.0: {}
+ lucide-svelte@0.479.0(svelte@5.23.1):
+ dependencies:
+ svelte: 5.23.1
magic-string@0.30.17:
dependencies:
@@ -6094,6 +6117,10 @@ snapshots:
pkg-types: 1.3.1
ufo: 1.5.4
+ mode-watcher@0.5.1(svelte@5.23.1):
+ dependencies:
+ svelte: 5.23.1
+
monaco-editor@0.52.2: {}
mri@1.2.0: {}
@@ -6195,6 +6222,11 @@ snapshots:
pako@2.1.0: {}
+ paneforge@1.0.0-next.4(svelte@5.23.1):
+ dependencies:
+ svelte: 5.23.1
+ svelte-toolbelt: 0.7.1(svelte@5.23.1)
+
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
@@ -6549,12 +6581,6 @@ snapshots:
prettier@3.5.3: {}
- pretty-format@27.5.1:
- dependencies:
- ansi-regex: 5.0.1
- ansi-styles: 5.2.0
- react-is: 17.0.2
-
process-warning@3.0.0: {}
process@0.11.10: {}
@@ -6571,8 +6597,6 @@ snapshots:
random-word-slugs@0.1.7: {}
- react-is@17.0.2: {}
-
read-cache@1.0.0:
dependencies:
pify: 2.3.0
@@ -6604,18 +6628,11 @@ snapshots:
real-require@0.2.0: {}
- redent@3.0.0:
- dependencies:
- indent-string: 4.0.0
- strip-indent: 3.0.0
-
redent@4.0.0:
dependencies:
indent-string: 5.0.0
strip-indent: 4.0.0
- regenerator-runtime@0.14.1: {}
-
regexp-tree@0.1.27: {}
regexpp@3.2.0: {}
@@ -6689,6 +6706,11 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
+ runed@0.23.4(svelte@5.23.1):
+ dependencies:
+ esm-env: 1.2.2
+ svelte: 5.23.1
+
rw@1.3.3: {}
sade@1.8.1:
@@ -6815,6 +6837,10 @@ snapshots:
strip-json-comments@3.1.1: {}
+ style-to-object@1.0.8:
+ dependencies:
+ inline-style-parser: 0.2.4
+
stylehacks@6.1.1(postcss@8.5.3):
dependencies:
browserslist: 4.24.4
@@ -6857,6 +6883,17 @@ snapshots:
postcss-load-config: 5.1.0(jiti@1.21.7)(postcss@8.5.3)
typescript: 5.8.2
+ svelte-sonner@0.3.28(svelte@5.23.1):
+ dependencies:
+ svelte: 5.23.1
+
+ svelte-toolbelt@0.7.1(svelte@5.23.1):
+ dependencies:
+ clsx: 2.1.1
+ runed: 0.23.4(svelte@5.23.1)
+ style-to-object: 1.0.8
+ svelte: 5.23.1
+
svelte@5.23.1:
dependencies:
'@ampproject/remapping': 2.3.0
@@ -6896,6 +6933,21 @@ snapshots:
symbol-tree@3.2.4: {}
+ tabbable@6.2.0: {}
+
+ tailwind-merge@2.5.4: {}
+
+ tailwind-merge@3.0.2: {}
+
+ tailwind-variants@0.3.1(tailwindcss@3.4.17):
+ dependencies:
+ tailwind-merge: 2.5.4
+ tailwindcss: 3.4.17
+
+ tailwindcss-animate@1.0.7(tailwindcss@3.4.17):
+ dependencies:
+ tailwindcss: 3.4.17
+
tailwindcss@3.4.17:
dependencies:
'@alloc/quick-lru': 5.2.0
@@ -7014,6 +7066,24 @@ snapshots:
ufo@1.5.4: {}
+ unplugin-icons@22.1.0(@vue/compiler-sfc@3.5.13)(svelte@5.23.1):
+ dependencies:
+ '@antfu/install-pkg': 1.0.0
+ '@iconify/utils': 2.3.0
+ debug: 4.4.0
+ local-pkg: 1.1.1
+ unplugin: 2.2.0
+ optionalDependencies:
+ '@vue/compiler-sfc': 3.5.13
+ svelte: 5.23.1
+ transitivePeerDependencies:
+ - supports-color
+
+ unplugin@2.2.0:
+ dependencies:
+ acorn: 8.14.1
+ webpack-virtual-modules: 0.6.2
+
update-browserslist-db@1.1.3(browserslist@4.24.4):
dependencies:
browserslist: 4.24.4
@@ -7159,6 +7229,8 @@ snapshots:
webidl-conversions@7.0.0: {}
+ webpack-virtual-modules@0.6.2: {}
+
whatwg-encoding@3.1.1:
dependencies:
iconv-lite: 0.6.3
diff --git a/src/app.html b/src/app.html
index 827bd6bc..229ff125 100644
--- a/src/app.html
+++ b/src/app.html
@@ -17,7 +17,7 @@
%sveltekit.head%
-
+
%sveltekit.body%
diff --git a/src/app.postcss b/src/app.postcss
index 1f611bac..f5df0bf3 100644
--- a/src/app.postcss
+++ b/src/app.postcss
@@ -1,19 +1,67 @@
@import '@fortawesome/fontawesome-free/css/all.min.css';
+@import '@fontsource-variable/recursive/crsv.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
-.input {
- @apply flex-1 rounded border-2 border-solid border-primary;
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 222.2 84% 4.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 222.2 84% 4.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 222.2 84% 4.9%;
+ --primary: 240 10% 91%;
+ --primary-foreground: 255 20% 15%;
+ --secondary: 210 40% 96.1%;
+ --secondary-foreground: 222.2 47.4% 11.2%;
+ --muted: 228 24% 96%;
+ --muted-foreground: 215.4 16.3% 46.9%;
+ --accent: 340 100% 44%;
+ --accent-foreground: 210 40% 98%;
+ --destructive: 0 72.22% 50.59%;
+ --destructive-foreground: 210 40% 98%;
+ --border: 214.3 31.8% 91.4%;
+ --input: 214.3 31.8% 91.4%;
+ --ring: 222.2 84% 4.9%;
+ --radius: 0.75rem;
+ }
+ .dark {
+ --background: 222.2 84% 4.9%;
+ --foreground: 210 40% 98%;
+ --card: 222.2 84% 4.9%;
+ --card-foreground: 210 40% 98%;
+ --popover: 222.2 84% 4.9%;
+ --popover-foreground: 210 40% 98%;
+ --primary: 210 40% 30%;
+ --primary-foreground: 222.2 47.4% 90%;
+ --secondary: 217.2 32.6% 17.5%;
+ --secondary-foreground: 210 40% 98%;
+ --muted: 217.2 32.6% 17.5%;
+ --muted-foreground: 215 20.2% 65.1%;
+ --accent: 340 100% 44%;
+ --accent-foreground: 210 40% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 210 40% 98%;
+ --border: 217.2 32.6% 17.5%;
+ --input: 217.2 32.6% 17.5%;
+ --ring: 212.7 26.8% 83.9%;
+ }
}
-.action-btn {
- @apply btn btn-primary;
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply h-screen w-screen overflow-hidden bg-background text-foreground;
+ }
}
-#graph-div sub,
-#graph-div sup {
- position: initial;
- vertical-align: revert;
+
+body {
+ font-family: 'Recursive Variable', sans-serif;
}
.d {
diff --git a/src/lib/components/Actions.svelte b/src/lib/components/Actions.svelte
index 8e703412..a1773723 100644
--- a/src/lib/components/Actions.svelte
+++ b/src/lib/components/Actions.svelte
@@ -1,23 +1,37 @@
-
-
- {#if isClipboardAvailable()}
-
Copy Image to clipboard
-
- {/if}
-
- PNG
-
-
- SVG
-
- {#if rendererUrl}
-
-
- PNG
-
-
-
-
- SVG
-
-
- {/if}
- {#if krokiRendererUrl}
-
-
- Kroki
-
-
+{#snippet dualActionButton(text: string, download: (event: Event) => unknown, url?: string)}
+
+
+
+ {text}
+
+ {#if url}
+
+
+
{/if}
+
+{/snippet}
-
+
+
+
PNG size
-
- Auto
-
-
-
- Width
-
-
-
- Height
-
-
- {#if imagemodeselected !== 'auto'}
-
+
+ Auto
+ Width
+ Height
+
+ {#if imageSizeMode !== 'auto'}
+
+ {/if}
+
+
+
+ {@render dualActionButton('PNG', onDownloadPNG, $urlsStore.png)}
+ {@render dualActionButton('SVG', onDownloadSVG, $urlsStore.svg)}
+ {#if env.krokiRendererUrl}
+
+
+ Kroki
+
+
{/if}
-
- {#if rendererUrl}
-
-
-
-
- Copy Markdown
-
-
-
+
+ {#if isClipboardAvailable()}
+
+ {/if}
+ {#if $urlsStore.mdCode}
+
{/if}
-
-
- Load Gist
-
+
+ Load Gist
{#if isNetlify}
diff --git a/src/lib/components/Card/Card.svelte b/src/lib/components/Card/Card.svelte
index dbc92dc7..3cc2f078 100644
--- a/src/lib/components/Card/Card.svelte
+++ b/src/lib/components/Card/Card.svelte
@@ -1,26 +1,35 @@
-
+
-
-
-
- {@render actions?.()}
-
-
+ {#if icon || title}
+
+ {#if icon}
+
+ {/if}
+ {title}
+
+ {/if}
+ {#if isOpen && tabs && tabs.length > 0}
+
+ {/if}
+
+ {@render actions?.()}
+
+ {#if isOpen && isClosable}
+
+ {/if}
{#if isOpen}
-
- {@render children?.()}
+
+ {@render children()}
{/if}
diff --git a/src/lib/components/Card/Tabs.svelte b/src/lib/components/Card/Tabs.svelte
index 6796ed8a..363644f6 100644
--- a/src/lib/components/Card/Tabs.svelte
+++ b/src/lib/components/Card/Tabs.svelte
@@ -1,19 +1,15 @@
-
-
- {#if isClosable}
-
- {/if}
- {title}
-
- {#if isOpen && tabs}
-
- {#each tabs as tab}
-
-
- {tab.title}
-
- {/each}
-
- {/if}
-
+
+
+ {#each tabs as tab, index}
+
+
+ {tab.title}
+
-
+ {#if index < tabs.length - 1}
+
+
+
+ {/if}
+ {/each}
+
+
diff --git a/src/lib/components/Card/card.test.ts b/src/lib/components/Card/card.test.ts
deleted file mode 100644
index 8c79fc41..00000000
--- a/src/lib/components/Card/card.test.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { cleanup, render } from '@testing-library/svelte';
-import { afterEach, describe, expect, it } from 'vitest';
-import Card from './Card.svelte';
-
-describe('card.svelte', () => {
- // TODO: @testing-library/svelte claims to add this automatically but it doesn't work without explicit afterEach
- afterEach(() => cleanup());
-
- it('mounts', () => {
- const { container } = render(Card, {
- title: 'TabTest',
- tabs: [
- { id: 't1', title: 'title1', icon: 'fab fa-git-alt' },
- { id: 't2', title: 'title2', icon: 'far fa-bookmark' }
- ]
- });
- expect(container).toBeTruthy();
- expect(container).toHaveTextContent('TabTest');
- expect(container).toHaveTextContent('title1');
- expect(container).toHaveTextContent('title2');
- expect(container).not.toHaveTextContent('title3');
- });
-});
diff --git a/src/lib/components/CopyButton.svelte b/src/lib/components/CopyButton.svelte
new file mode 100644
index 00000000..0ccb3c74
--- /dev/null
+++ b/src/lib/components/CopyButton.svelte
@@ -0,0 +1,41 @@
+
+
+
{
+ try {
+ showCheckIcon = true;
+ setTimeout(() => {
+ showCheckIcon = false;
+ }, 1000);
+ await onclick(event);
+ } catch {
+ notify('Failed to copy');
+ }
+ }}>
+
+ {#key showCheckIcon}
+
+ {#if showCheckIcon}
+
+ {:else}
+
+ {/if}
+
+ {/key}
+
+ {label}
+
diff --git a/src/lib/components/CopyInput.svelte b/src/lib/components/CopyInput.svelte
new file mode 100644
index 00000000..e3eeac8f
--- /dev/null
+++ b/src/lib/components/CopyInput.svelte
@@ -0,0 +1,25 @@
+
+
+
+ {
+ event.currentTarget.setSelectionRange(0, event.currentTarget.value.length);
+ }} />
+
+ copyToClipboard(value)} {label} />
+
diff --git a/src/lib/components/DiagramDocumentationButton.svelte b/src/lib/components/DiagramDocumentationButton.svelte
new file mode 100644
index 00000000..b2449c01
--- /dev/null
+++ b/src/lib/components/DiagramDocumentationButton.svelte
@@ -0,0 +1,109 @@
+
+
+
+
+ Docs
+
diff --git a/src/lib/components/DropdownNavMenu.svelte b/src/lib/components/DropdownNavMenu.svelte
index abc1881f..3ba8beb8 100644
--- a/src/lib/components/DropdownNavMenu.svelte
+++ b/src/lib/components/DropdownNavMenu.svelte
@@ -1,34 +1,28 @@
-
-
- {#if icon}
-
- {/if}
- {#if label}
- {label}
- {/if}
-
-
-
-
+
+
diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte
index ff29e56c..e6c25350 100644
--- a/src/lib/components/Editor.svelte
+++ b/src/lib/components/Editor.svelte
@@ -1,14 +1,19 @@
-
+
{#if $stateStore.error instanceof Error}
-
-
-
-
Diagram syntax error
+
+
+
+
+
+
Syntax error
+ {#if env.isEnabledMermaidChartLinks}
+
Create a free account to repair with AI
+ {/if}
+
+
+
+
+
+ AI Repair
+
+
-
+
{$stateStore.error?.toString()}
diff --git a/src/lib/components/FloatingToolbar.svelte b/src/lib/components/FloatingToolbar.svelte
new file mode 100644
index 00000000..128078b8
--- /dev/null
+++ b/src/lib/components/FloatingToolbar.svelte
@@ -0,0 +1,13 @@
+
+
+
+ {@render children()}
+
diff --git a/src/lib/components/History/History.svelte b/src/lib/components/History/History.svelte
index 811b1668..2f2b3ba0 100644
--- a/src/lib/components/History/History.svelte
+++ b/src/lib/components/History/History.svelte
@@ -1,6 +1,4 @@
-
+
{#snippet actions()}
-
-
+ uploadHistory())}
- title="Upload history"
- aria-label="Upload history">
+ onclick={uploadHistory}
+ title="Upload history">
{#if $historyStore.length > 0}
- downloadHistory())}
- title="Download history"
- aria-label="Download history">
+ size="icon"
+ variant="ghost"
+ onclick={downloadHistory}
+ title="Download history">
{/if}
- |
-
+ saveHistory())}
- title="Save current state"
- aria-label="Save current state">
+ size="icon"
+ variant="ghost"
+ onclick={() => saveHistory()}
+ title="Save current state">
{#if $historyModeStore !== 'loader'}
- clearHistory())}
- title="Delete all saved states"
- aria-label="Delete all saved states">
+ size="icon"
+ variant="ghost"
+ class="hover:text-destructive"
+ onclick={() => clearHistory()}
+ title="Delete all saved states">
{/if}
{/snippet}
-
+
{#if $historyStore.length > 0}
- {#each $historyStore as { id, state, time, name, url, type }}
-
-
-
-
- {#if url}
-
{name}
- {:else}
-
{name}
- {/if}
-
{relativeTime(time)}
-
+ {#each $historyStore as { id, state, time, name, url, type } (id)}
+
+
+
+ {#if url}
+
{name}
+ {:else}
+
{name}
+ {/if}
+
+ {new Date(time).toLocaleString()}
+
-
-
restoreHistoryItem(state)}
- > Restore
+
+
+
+ {dayjs(time).fromNow()}
+
+ restoreHistoryItem(state)}>
+
+
{#if type !== 'loader'}
- clearHistory(id)}
- > Delete
+ clearHistory(id)}>
+
+
{/if}
+
{/each}
{:else}
-
+
No items in History
Click the Save button to save current state and restore it later.
Timeline will automatically be saved every minute.
diff --git a/src/lib/components/History/history.ts b/src/lib/components/History/history.ts
index 97cf5ea4..8965def3 100644
--- a/src/lib/components/History/history.ts
+++ b/src/lib/components/History/history.ts
@@ -1,10 +1,10 @@
-import { derived, writable, get } from 'svelte/store';
-import type { Readable, Writable } from 'svelte/store';
-import { persist, localStorage } from '$lib/util/persist';
-import { generateSlug } from 'random-word-slugs';
import type { HistoryEntry, HistoryType, Optional } from '$lib/types';
-import { v4 as uuidV4 } from 'uuid';
+import { localStorage, persist } from '$lib/util/persist';
import { logEvent } from '$lib/util/stats';
+import { generateSlug } from 'random-word-slugs';
+import type { Readable, Writable } from 'svelte/store';
+import { derived, get, writable } from 'svelte/store';
+import { v4 as uuidV4 } from 'uuid';
const MAX_AUTO_HISTORY_LENGTH = 30;
diff --git a/src/lib/components/MainMenu.svelte b/src/lib/components/MainMenu.svelte
new file mode 100644
index 00000000..35d31815
--- /dev/null
+++ b/src/lib/components/MainMenu.svelte
@@ -0,0 +1,83 @@
+
+
+{#snippet menuItem(options: { label: string; icon: Component; href: string; class?: string })}
+
+
+ {options.label}
+
+{/snippet}
+
+
+
+
+
+
+ {#each menuItems as item}
+ {@render menuItem(item)}
+ {/each}
+
+
+
+
+ Dark Mode
+
+ setMode(dark ? 'dark' : 'light')} />
+
+
+ {#each mermaidChartMenuItems as item}
+
+ {@render menuItem({
+ ...item,
+ class: 'text-accent bg-background hover:bg-muted'
+ })}
+
+ {/each}
+
+
diff --git a/src/lib/components/McWrapper.svelte b/src/lib/components/McWrapper.svelte
new file mode 100644
index 00000000..5fedf0b1
--- /dev/null
+++ b/src/lib/components/McWrapper.svelte
@@ -0,0 +1,50 @@
+
+
+{#if env.isEnabledMermaidChartLinks}
+
+
+
+
+ {@render children()}
+
+
+
+
+ {#if shouldDisableComponent}
+
+ This diagram type is not supported in MermaidChart.com
+
+ {:else}
+
+
+ {labelPrefix}
+ MermaidChart.com
+
+ {/if}
+
+
+
+
+{/if}
diff --git a/src/lib/components/MermaidChartIcon.svelte b/src/lib/components/MermaidChartIcon.svelte
new file mode 100644
index 00000000..98c54110
--- /dev/null
+++ b/src/lib/components/MermaidChartIcon.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/lib/components/Navbar.svelte b/src/lib/components/Navbar.svelte
index 50ad17e0..93964362 100644
--- a/src/lib/components/Navbar.svelte
+++ b/src/lib/components/Navbar.svelte
@@ -1,29 +1,34 @@
-
+
+
+ panZoomState.reset()}>
+
+
+
+ panZoomState.zoomOut()}>
+
+
+ panZoomState.zoomIn()}>
+
+
+
+
+
+
+
diff --git a/src/lib/components/Preset.svelte b/src/lib/components/Preset.svelte
index 8d5f6498..43447ea6 100644
--- a/src/lib/components/Preset.svelte
+++ b/src/lib/components/Preset.svelte
@@ -1,20 +1,30 @@
-
-
+
+
{#each diagramOrder as sample}
- loadSampleDiagram(sample)}>
{sample}
- {#if newDiagrams.includes(sample)}
-
- {/if}
-
+
{/each}
diff --git a/src/lib/components/Privacy.svelte b/src/lib/components/Privacy.svelte
index 3644076a..6567def9 100644
--- a/src/lib/components/Privacy.svelte
+++ b/src/lib/components/Privacy.svelte
@@ -1,31 +1,46 @@
-
- Security
-
+
-
-
-
-
Data security
+
+
+
+
+
+
+
+
+ Data security
+
+
+
+
+ The content of the diagrams you create never leaves your browser.
+
+ It's only stored in the URL, and your browser's local storage.
-
- It's stored in the URL, and the browser's local storage only.
+ Mermaid Live Editor is a fully open source, client side application, deployed transparently on
+ GitHub Pages
+ .
- Mermaid live editor is a fully client side application, that will also work as an offline
- PWA.
+ It will also work as a fully offline
+
+ Progressive Web App.
The only server we have is a self hosted version of the open source and privacy friendly
- Plausible Analytics. We only collect data related to actions performed, like the type of
- diagram rendered, number of times a feature was used, etc. All the data we collect is
- anonymized and
-
+ Plausible Analytics. We only collect anonymous data related to actions performed, like the
+ type of diagram rendered, number of times a feature was used, etc.
+
+
+ All the data we collect is anonymized and
+
available publicly.
@@ -34,9 +49,5 @@
Additional services like the external PNG/SVG/Kroki links and "Save to Mermaid Chart" feature
will share your diagram with the respective 3rd party service.
-
-
- X
-
-
-
+
+
diff --git a/src/lib/components/Share.svelte b/src/lib/components/Share.svelte
new file mode 100644
index 00000000..b0dddf5c
--- /dev/null
+++ b/src/lib/components/Share.svelte
@@ -0,0 +1,48 @@
+
+
+
+ Share
+
+
+
+ Shareable links
+
+ Share your diagrams with others.
+
+
+
+
+
+
+ Mermaid Live Editor
+
+
+
+ The content of the diagrams you create never leaves your browser.
+
+
+ {#if env.isEnabledMermaidChartLinks}
+
+
+
+
+ Mermaid Chart Playground
+
+
+
+ Opens the Mermaid Chart Playground with Mermaid AI, Visual Editor, and more.
+
+
+ {/if}
+
+
+
diff --git a/src/lib/components/SyncRoughToolbar.svelte b/src/lib/components/SyncRoughToolbar.svelte
new file mode 100644
index 00000000..62b4be58
--- /dev/null
+++ b/src/lib/components/SyncRoughToolbar.svelte
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/components/Theme.svelte b/src/lib/components/Theme.svelte
deleted file mode 100644
index f60c09ac..00000000
--- a/src/lib/components/Theme.svelte
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
diff --git a/src/lib/components/ThemeIcon.svelte b/src/lib/components/ThemeIcon.svelte
new file mode 100644
index 00000000..2a072a20
--- /dev/null
+++ b/src/lib/components/ThemeIcon.svelte
@@ -0,0 +1,39 @@
+
+
+
+ {#key $mode}
+
+ {#if $mode === 'dark'}
+
+ {:else}
+
+ {/if}
+
+ {/key}
+
diff --git a/src/lib/components/VersionSecurityToolbar.svelte b/src/lib/components/VersionSecurityToolbar.svelte
new file mode 100644
index 00000000..14b319bf
--- /dev/null
+++ b/src/lib/components/VersionSecurityToolbar.svelte
@@ -0,0 +1,28 @@
+
+
+
+ v{version}
+
+
+
+
+
+ setMode($mode === 'dark' ? 'light' : 'dark')}>
+
+
+
diff --git a/src/lib/components/View.svelte b/src/lib/components/View.svelte
index 0fb12dd7..e0702f64 100644
--- a/src/lib/components/View.svelte
+++ b/src/lib/components/View.svelte
@@ -1,56 +1,39 @@
-{#if outOfSync}
-
- Diagram out of sync.
- {#if $stateStore.autoSync}
- It will be updated automatically.
- {:else}
- Press (Sync button) or {cmdKey} + Enter to sync.
- {/if}
-
-{/if}
-
-
+
diff --git a/src/lib/components/ui/button/button.svelte b/src/lib/components/ui/button/button.svelte
new file mode 100644
index 00000000..88920c9b
--- /dev/null
+++ b/src/lib/components/ui/button/button.svelte
@@ -0,0 +1,69 @@
+
+
+
+
+{#if href}
+
+ {@render children?.()}
+
+{:else}
+
+ {@render children?.()}
+
+{/if}
diff --git a/src/lib/components/ui/button/index.ts b/src/lib/components/ui/button/index.ts
new file mode 100644
index 00000000..b79e44a8
--- /dev/null
+++ b/src/lib/components/ui/button/index.ts
@@ -0,0 +1,9 @@
+export {
+ default as Button,
+ buttonVariants,
+ default as Root,
+ type ButtonProps,
+ type ButtonSize,
+ type ButtonVariant,
+ type ButtonProps as Props
+} from './button.svelte';
diff --git a/src/lib/components/ui/dialog/dialog-content.svelte b/src/lib/components/ui/dialog/dialog-content.svelte
new file mode 100644
index 00000000..b1de6663
--- /dev/null
+++ b/src/lib/components/ui/dialog/dialog-content.svelte
@@ -0,0 +1,36 @@
+
+
+
+
+
+ {@render children?.()}
+
+
+ Close
+
+
+
diff --git a/src/lib/components/ui/dialog/dialog-description.svelte b/src/lib/components/ui/dialog/dialog-description.svelte
new file mode 100644
index 00000000..6d2b04fd
--- /dev/null
+++ b/src/lib/components/ui/dialog/dialog-description.svelte
@@ -0,0 +1,15 @@
+
+
+
diff --git a/src/lib/components/ui/dialog/dialog-footer.svelte b/src/lib/components/ui/dialog/dialog-footer.svelte
new file mode 100644
index 00000000..88af15c0
--- /dev/null
+++ b/src/lib/components/ui/dialog/dialog-footer.svelte
@@ -0,0 +1,19 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/dialog/dialog-header.svelte b/src/lib/components/ui/dialog/dialog-header.svelte
new file mode 100644
index 00000000..0bc0d2ad
--- /dev/null
+++ b/src/lib/components/ui/dialog/dialog-header.svelte
@@ -0,0 +1,19 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/dialog/dialog-overlay.svelte b/src/lib/components/ui/dialog/dialog-overlay.svelte
new file mode 100644
index 00000000..07932582
--- /dev/null
+++ b/src/lib/components/ui/dialog/dialog-overlay.svelte
@@ -0,0 +1,18 @@
+
+
+
diff --git a/src/lib/components/ui/dialog/dialog-title.svelte b/src/lib/components/ui/dialog/dialog-title.svelte
new file mode 100644
index 00000000..5da14f94
--- /dev/null
+++ b/src/lib/components/ui/dialog/dialog-title.svelte
@@ -0,0 +1,15 @@
+
+
+
diff --git a/src/lib/components/ui/dialog/index.ts b/src/lib/components/ui/dialog/index.ts
new file mode 100644
index 00000000..c3483281
--- /dev/null
+++ b/src/lib/components/ui/dialog/index.ts
@@ -0,0 +1,36 @@
+import { Dialog as DialogPrimitive } from 'bits-ui';
+import Content from './dialog-content.svelte';
+import Description from './dialog-description.svelte';
+import Footer from './dialog-footer.svelte';
+import Header from './dialog-header.svelte';
+import Overlay from './dialog-overlay.svelte';
+import Title from './dialog-title.svelte';
+
+const Root: typeof DialogPrimitive.Root = DialogPrimitive.Root;
+const Trigger: typeof DialogPrimitive.Trigger = DialogPrimitive.Trigger;
+const Close: typeof DialogPrimitive.Close = DialogPrimitive.Close;
+const Portal: typeof DialogPrimitive.Portal = DialogPrimitive.Portal;
+
+export {
+ Close,
+ Content,
+ Description,
+ //
+ Root as Dialog,
+ Close as DialogClose,
+ Content as DialogContent,
+ Description as DialogDescription,
+ Footer as DialogFooter,
+ Header as DialogHeader,
+ Overlay as DialogOverlay,
+ Portal as DialogPortal,
+ Title as DialogTitle,
+ Trigger as DialogTrigger,
+ Footer,
+ Header,
+ Overlay,
+ Portal,
+ Root,
+ Title,
+ Trigger
+};
diff --git a/src/lib/components/ui/input/index.ts b/src/lib/components/ui/input/index.ts
new file mode 100644
index 00000000..be0783a3
--- /dev/null
+++ b/src/lib/components/ui/input/index.ts
@@ -0,0 +1,7 @@
+import Root from './input.svelte';
+
+export {
+ Root,
+ //
+ Root as Input
+};
diff --git a/src/lib/components/ui/input/input.svelte b/src/lib/components/ui/input/input.svelte
new file mode 100644
index 00000000..5d1c70e3
--- /dev/null
+++ b/src/lib/components/ui/input/input.svelte
@@ -0,0 +1,43 @@
+
+
+{#if type === 'file'}
+
+{:else}
+
+{/if}
diff --git a/src/lib/components/ui/popover/index.ts b/src/lib/components/ui/popover/index.ts
new file mode 100644
index 00000000..244795d8
--- /dev/null
+++ b/src/lib/components/ui/popover/index.ts
@@ -0,0 +1,17 @@
+import { Popover as PopoverPrimitive } from 'bits-ui';
+import Content from './popover-content.svelte';
+const Root = PopoverPrimitive.Root;
+const Trigger = PopoverPrimitive.Trigger;
+const Close = PopoverPrimitive.Close;
+
+export {
+ Root,
+ Content,
+ Trigger,
+ Close,
+ //
+ Root as Popover,
+ Content as PopoverContent,
+ Trigger as PopoverTrigger,
+ Close as PopoverClose
+};
diff --git a/src/lib/components/ui/popover/popover-content.svelte b/src/lib/components/ui/popover/popover-content.svelte
new file mode 100644
index 00000000..abe2949f
--- /dev/null
+++ b/src/lib/components/ui/popover/popover-content.svelte
@@ -0,0 +1,27 @@
+
+
+
+
+
diff --git a/src/lib/components/ui/resizable/index.ts b/src/lib/components/ui/resizable/index.ts
new file mode 100644
index 00000000..927c86d7
--- /dev/null
+++ b/src/lib/components/ui/resizable/index.ts
@@ -0,0 +1,13 @@
+import { Pane } from 'paneforge';
+import Handle from './resizable-handle.svelte';
+import PaneGroup from './resizable-pane-group.svelte';
+
+export {
+ PaneGroup,
+ Pane,
+ Handle,
+ //
+ PaneGroup as ResizablePaneGroup,
+ Pane as ResizablePane,
+ Handle as ResizableHandle
+};
diff --git a/src/lib/components/ui/resizable/resizable-handle.svelte b/src/lib/components/ui/resizable/resizable-handle.svelte
new file mode 100644
index 00000000..22755a57
--- /dev/null
+++ b/src/lib/components/ui/resizable/resizable-handle.svelte
@@ -0,0 +1,29 @@
+
+
+
div]:rotate-90',
+ className
+ )}
+ {...restProps}>
+ {#if withHandle}
+
+
+
+ {/if}
+
diff --git a/src/lib/components/ui/resizable/resizable-pane-group.svelte b/src/lib/components/ui/resizable/resizable-pane-group.svelte
new file mode 100644
index 00000000..c798226b
--- /dev/null
+++ b/src/lib/components/ui/resizable/resizable-pane-group.svelte
@@ -0,0 +1,21 @@
+
+
+
diff --git a/src/lib/components/ui/separator/index.ts b/src/lib/components/ui/separator/index.ts
new file mode 100644
index 00000000..2402e13e
--- /dev/null
+++ b/src/lib/components/ui/separator/index.ts
@@ -0,0 +1,7 @@
+import Root from './separator.svelte';
+
+export {
+ Root,
+ //
+ Root as Separator
+};
diff --git a/src/lib/components/ui/separator/separator.svelte b/src/lib/components/ui/separator/separator.svelte
new file mode 100644
index 00000000..a1590bf4
--- /dev/null
+++ b/src/lib/components/ui/separator/separator.svelte
@@ -0,0 +1,21 @@
+
+
+
diff --git a/src/lib/components/ui/sonner/index.ts b/src/lib/components/ui/sonner/index.ts
new file mode 100644
index 00000000..fcaf06bf
--- /dev/null
+++ b/src/lib/components/ui/sonner/index.ts
@@ -0,0 +1 @@
+export { default as Toaster } from './sonner.svelte';
diff --git a/src/lib/components/ui/sonner/sonner.svelte b/src/lib/components/ui/sonner/sonner.svelte
new file mode 100644
index 00000000..06609dcb
--- /dev/null
+++ b/src/lib/components/ui/sonner/sonner.svelte
@@ -0,0 +1,20 @@
+
+
+
diff --git a/src/lib/components/ui/switch/index.ts b/src/lib/components/ui/switch/index.ts
new file mode 100644
index 00000000..7fdb93ac
--- /dev/null
+++ b/src/lib/components/ui/switch/index.ts
@@ -0,0 +1,7 @@
+import Root from './switch.svelte';
+
+export {
+ Root,
+ //
+ Root as Switch
+};
diff --git a/src/lib/components/ui/switch/switch.svelte b/src/lib/components/ui/switch/switch.svelte
new file mode 100644
index 00000000..158e1836
--- /dev/null
+++ b/src/lib/components/ui/switch/switch.svelte
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/src/lib/components/ui/toggle-group/index.ts b/src/lib/components/ui/toggle-group/index.ts
new file mode 100644
index 00000000..1ee4c602
--- /dev/null
+++ b/src/lib/components/ui/toggle-group/index.ts
@@ -0,0 +1,10 @@
+import Root from './toggle-group.svelte';
+import Item from './toggle-group-item.svelte';
+
+export {
+ Root,
+ Item,
+ //
+ Root as ToggleGroup,
+ Item as ToggleGroupItem
+};
diff --git a/src/lib/components/ui/toggle-group/toggle-group-item.svelte b/src/lib/components/ui/toggle-group/toggle-group-item.svelte
new file mode 100644
index 00000000..087ccba3
--- /dev/null
+++ b/src/lib/components/ui/toggle-group/toggle-group-item.svelte
@@ -0,0 +1,29 @@
+
+
+
diff --git a/src/lib/components/ui/toggle-group/toggle-group.svelte b/src/lib/components/ui/toggle-group/toggle-group.svelte
new file mode 100644
index 00000000..027ba590
--- /dev/null
+++ b/src/lib/components/ui/toggle-group/toggle-group.svelte
@@ -0,0 +1,40 @@
+
+
+
+
+
+
diff --git a/src/lib/components/ui/toggle/index.ts b/src/lib/components/ui/toggle/index.ts
new file mode 100644
index 00000000..4a0d9d7e
--- /dev/null
+++ b/src/lib/components/ui/toggle/index.ts
@@ -0,0 +1,13 @@
+import Root from './toggle.svelte';
+export {
+ toggleVariants,
+ type ToggleSize,
+ type ToggleVariant,
+ type ToggleVariants
+} from './toggle.svelte';
+
+export {
+ Root,
+ //
+ Root as Toggle
+};
diff --git a/src/lib/components/ui/toggle/toggle.svelte b/src/lib/components/ui/toggle/toggle.svelte
new file mode 100644
index 00000000..6cc3e5a4
--- /dev/null
+++ b/src/lib/components/ui/toggle/toggle.svelte
@@ -0,0 +1,50 @@
+
+
+
+
+
diff --git a/src/lib/components/ui/tooltip/index.ts b/src/lib/components/ui/tooltip/index.ts
new file mode 100644
index 00000000..eae8afaf
--- /dev/null
+++ b/src/lib/components/ui/tooltip/index.ts
@@ -0,0 +1,18 @@
+import { Tooltip as TooltipPrimitive } from 'bits-ui';
+import Content from './tooltip-content.svelte';
+
+const Root = TooltipPrimitive.Root;
+const Trigger = TooltipPrimitive.Trigger;
+const Provider = TooltipPrimitive.Provider;
+
+export {
+ Content,
+ Provider,
+ Root,
+ //
+ Root as Tooltip,
+ Content as TooltipContent,
+ Provider as TooltipProvider,
+ Trigger as TooltipTrigger,
+ Trigger
+};
diff --git a/src/lib/components/ui/tooltip/tooltip-content.svelte b/src/lib/components/ui/tooltip/tooltip-content.svelte
new file mode 100644
index 00000000..8402ed93
--- /dev/null
+++ b/src/lib/components/ui/tooltip/tooltip-content.svelte
@@ -0,0 +1,20 @@
+
+
+
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
new file mode 100644
index 00000000..85e0b108
--- /dev/null
+++ b/src/lib/constants.ts
@@ -0,0 +1,8 @@
+export const TID = {
+ copyMarkdown: 'copy-markdown',
+ diagramDocumentationButton: 'diagram-documentation-button',
+ downloadPNG: 'download-PNG',
+ downloadSVG: 'download-SVG',
+ errorContainer: 'error-container',
+ themeToggleButton: 'theme-toggle-button'
+} as const;
diff --git a/src/lib/types.d.ts b/src/lib/types.d.ts
index 7092203d..8e49b1cc 100644
--- a/src/lib/types.d.ts
+++ b/src/lib/types.d.ts
@@ -1,3 +1,7 @@
+import type { Component } from 'svelte';
+import type { HTMLInputTypeAttribute } from 'svelte/elements';
+import 'unplugin-icons/types/svelte';
+
export interface MarkerData {
severity: number;
message: string;
@@ -15,17 +19,19 @@ export interface TabEvents {
export interface Tab {
id: string;
title: string;
- icon: string;
+ icon: Component;
}
export interface State {
code: string;
mermaid: string;
updateDiagram: boolean;
- autoSync: boolean;
rough: boolean;
- editorMode?: EditorMode;
+ // All new options must be optional, as users would have old states saved
+ renderCount?: number;
panZoom?: boolean;
+ grid?: boolean;
+ editorMode?: EditorMode;
pan?: { x: number; y: number };
zoom?: number;
loader?: LoaderConfig;
@@ -33,6 +39,7 @@ export interface State {
export interface ValidatedState extends State {
editorMode: EditorMode;
+ diagramType?: string;
error?: Error;
errorMarkers: MarkerData[];
serialized: string;
@@ -50,10 +57,15 @@ export interface FileLoaderConfig {
codeURL: string;
configURL?: string;
}
-export interface LoaderConfig {
- type: 'gist' | 'files';
- config: GistLoaderConfig | FileLoaderConfig;
-}
+export type LoaderConfig =
+ | {
+ type: 'gist';
+ config: GistLoaderConfig;
+ }
+ | {
+ type: 'files';
+ config: FileLoaderConfig;
+ };
export type HistoryType = 'auto' | 'manual' | 'loader';
export type HistoryEntry = { id: string; state: State; time: number; url?: string } & (
| {
@@ -87,3 +99,5 @@ export interface ErrorHash {
last_column: number;
};
}
+
+export type InputType = Exclude
;
diff --git a/src/lib/util/autoSync.ts b/src/lib/util/autoSync.ts
index 7b7faf06..ca5557e6 100644
--- a/src/lib/util/autoSync.ts
+++ b/src/lib/util/autoSync.ts
@@ -1,6 +1,4 @@
import debounce from 'lodash-es/debounce';
-import { get } from 'svelte/store';
-import { stateStore } from './state';
let shouldSync = true;
let updater: () => void;
@@ -16,10 +14,6 @@ const debouncedRender = debounce(() => {
export const recordRenderTime = (renderTimeMs: number, updaterFunction: () => void): void => {
resolveRenderPromise?.();
- const { autoSync } = get(stateStore);
- if (!autoSync) {
- return;
- }
updater = updaterFunction;
const isSlow = renderTimeMs > slowRenderThreshold;
if (!shouldSync) {
diff --git a/src/lib/util/env.ts b/src/lib/util/env.ts
index c9961211..28350049 100644
--- a/src/lib/util/env.ts
+++ b/src/lib/util/env.ts
@@ -1,7 +1,7 @@
export const env = {
- rendererUrl: import.meta.env.MERMAID_RENDERER_URL ?? '',
- krokiRendererUrl: import.meta.env.MERMAID_KROKI_RENDERER_URL ?? '',
analyticsUrl: import.meta.env.MERMAID_ANALYTICS_URL ?? '',
domain: import.meta.env.MERMAID_DOMAIN ?? '',
- isEnabledMermaidChartLinks: import.meta.env.MERMAID_IS_ENABLED_MERMAID_CHART_LINKS == 'true'
+ isEnabledMermaidChartLinks: import.meta.env.MERMAID_IS_ENABLED_MERMAID_CHART_LINKS === 'true',
+ krokiRendererUrl: import.meta.env.MERMAID_KROKI_RENDERER_URL ?? '',
+ rendererUrl: import.meta.env.MERMAID_RENDERER_URL ?? ''
} as const;
diff --git a/src/lib/util/fileLoaders/gist.ts b/src/lib/util/fileLoaders/gist.ts
index 8875e4b9..828a07e7 100644
--- a/src/lib/util/fileLoaders/gist.ts
+++ b/src/lib/util/fileLoaders/gist.ts
@@ -61,12 +61,12 @@ const getGistData = async (gistURL: string): Promise => {
}
const currentItem = history[0];
return {
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
- url: `${html_url}/${currentItem.version}`,
+ author: currentItem.user.login,
code,
config,
- author: currentItem.user.login,
time: new Date(currentItem.committed_at).getTime(),
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ url: `${html_url}/${currentItem.version}`,
version: currentItem.version.slice(-7)
};
} else {
@@ -79,10 +79,10 @@ const getStateFromGist = (gist: GistData, gistURL: string = gist.url): State =>
...defaultState,
code: gist.code,
loader: {
- type: 'gist',
config: {
url: gistURL
- }
+ },
+ type: 'gist'
}
};
gist.config && (state.mermaid = gist.config);
@@ -120,11 +120,11 @@ export const loadGistData = async (gistURL: string): Promise => {
const state = getStateFromGist(entry, gistURL);
for (const gist of gistHistory) {
addHistoryEntry({
+ name: `${gist.author} v${gist.version}`,
state: getStateFromGist(gist),
time: gist.time,
type: 'loader',
- url: gist.url,
- name: `${gist.author} v${gist.version}`
+ url: gist.url
});
}
return state;
diff --git a/src/lib/util/fileLoaders/loader.ts b/src/lib/util/fileLoaders/loader.ts
index ce2ea348..137b26c9 100644
--- a/src/lib/util/fileLoaders/loader.ts
+++ b/src/lib/util/fileLoaders/loader.ts
@@ -1,7 +1,7 @@
-import { loadGistData } from './gist';
-import { updateCodeStore, defaultState } from '$lib/util/state';
-import { fetchText } from '$lib/util/util';
import type { Loader, State } from '$lib/types';
+import { defaultState, updateCodeStore } from '$lib/util/state';
+import { fetchText } from '$lib/util/util';
+import { loadGistData } from './gist';
const loaders: Record = {
gist: loadGistData
@@ -27,14 +27,14 @@ export const loadDataFromUrl = async (): Promise => {
}
state = {
code,
- mermaid: config,
loader: {
- type: 'files',
config: {
codeURL,
configURL
- }
- }
+ },
+ type: 'files'
+ },
+ mermaid: config
};
} else {
for (const [key, value] of searchParams.entries()) {
@@ -52,7 +52,6 @@ export const loadDataFromUrl = async (): Promise => {
loaded &&
updateCodeStore({
...state,
- autoSync: true,
updateDiagram: true
});
};
diff --git a/src/lib/util/mermaid.ts b/src/lib/util/mermaid.ts
index c4a5c6c5..12693c10 100644
--- a/src/lib/util/mermaid.ts
+++ b/src/lib/util/mermaid.ts
@@ -18,6 +18,24 @@ export const render = async (
return await mermaid.render(id, code);
};
-export const parse = async (code: string): Promise => {
+export const parse = async (code: string) => {
return await mermaid.parse(code);
};
+
+export const standardizeDiagramType = (diagramType: string) => {
+ switch (diagramType) {
+ case 'class':
+ case 'classDiagram': {
+ return 'classDiagram';
+ }
+ case 'graph':
+ case 'flowchart':
+ case 'flowchart-elk':
+ case 'flowchart-v2': {
+ return 'flowchart';
+ }
+ default: {
+ return diagramType;
+ }
+ }
+};
diff --git a/src/lib/util/monacoExtra.ts b/src/lib/util/monacoExtra.ts
index a0b4db76..e54d1777 100644
--- a/src/lib/util/monacoExtra.ts
+++ b/src/lib/util/monacoExtra.ts
@@ -20,139 +20,7 @@ export const initEditor = (monacoEditor: typeof Monaco): void => {
keywords: string[];
}
> = {
- flowchart: {
- typeKeywords: ['flowchart', 'flowchart-v2', 'graph'],
- blockKeywords: ['subgraph', 'end'],
- keywords: [
- 'TB',
- 'TD',
- 'BT',
- 'RL',
- 'LR',
- 'click',
- 'call',
- 'href',
- '_self',
- '_blank',
- '_parent',
- '_top',
- 'linkStyle',
- 'style',
- 'classDef',
- 'class',
- 'direction',
- 'interpolate'
- ]
- },
- sequenceDiagram: {
- typeKeywords: ['sequenceDiagram'],
- blockKeywords: ['alt', 'par', 'and', 'loop', 'else', 'end', 'rect', 'opt', 'alt', 'rect'],
- keywords: [
- 'participant',
- 'as',
- 'Note',
- 'note',
- 'right of',
- 'left of',
- 'over',
- 'activate',
- 'deactivate',
- 'autonumber',
- 'title',
- 'actor',
- 'accDescription',
- 'link',
- 'links'
- ]
- },
- classDiagram: {
- typeKeywords: ['classDiagram', 'classDiagram-v2'],
- blockKeywords: ['class'],
- keywords: [
- 'link',
- 'click',
- 'callback',
- 'call',
- 'href',
- 'cssClass',
- 'direction',
- 'TB',
- 'BT',
- 'RL',
- 'LR',
- 'title',
- 'accDescription',
- 'order'
- ]
- },
- stateDiagram: {
- typeKeywords: ['stateDiagram', 'stateDiagram-v2'],
- blockKeywords: ['state', 'note', 'end'],
- keywords: ['state', 'as', 'hide empty description', 'direction', 'TB', 'BT', 'RL', 'LR']
- },
- erDiagram: {
- typeKeywords: ['erDiagram'],
- blockKeywords: [],
- keywords: ['title', 'accDescription']
- },
- journey: {
- typeKeywords: ['journey'],
- blockKeywords: ['section'],
- keywords: ['title']
- },
- info: {
- typeKeywords: ['info'],
- blockKeywords: [],
- keywords: ['showInfo']
- },
- gantt: {
- typeKeywords: ['gantt'],
- blockKeywords: [],
- keywords: [
- 'title',
- 'dateFormat',
- 'axisFormat',
- 'todayMarker',
- 'section',
- 'excludes',
- 'inclusiveEndDates'
- ]
- },
- requirementDiagram: {
- typeKeywords: ['requirement', 'requirementDiagram'],
- blockKeywords: [...requirementDiagrams, 'element'],
- keywords: []
- },
- gitGraph: {
- typeKeywords: ['gitGraph'],
- blockKeywords: [],
- keywords: [
- 'accTitle',
- 'accDescr',
- 'commit',
- 'cherry-pick',
- 'branch',
- 'merge',
- 'reset',
- 'checkout',
- 'LR',
- 'BT',
- 'id',
- 'msg',
- 'type',
- 'tag',
- 'NORMAL',
- 'REVERSE',
- 'HIGHLIGHT'
- ]
- },
- pie: {
- typeKeywords: ['pie'],
- blockKeywords: [],
- keywords: ['showData', 'title', 'accDescr', 'accTitle']
- },
c4Diagram: {
- typeKeywords: ['C4Context', 'C4Container', 'C4Component', 'C4Dynamic', 'C4Deployment'],
blockKeywords: [
'Boundary',
'Enterprise_Boundary',
@@ -203,21 +71,153 @@ export const initEditor = (monacoEditor: typeof Monaco): void => {
'Rel_R',
'Rel_Back',
'RelIndex'
- ]
+ ],
+ typeKeywords: ['C4Context', 'C4Container', 'C4Component', 'C4Dynamic', 'C4Deployment']
+ },
+ classDiagram: {
+ blockKeywords: ['class'],
+ keywords: [
+ 'link',
+ 'click',
+ 'callback',
+ 'call',
+ 'href',
+ 'cssClass',
+ 'direction',
+ 'TB',
+ 'BT',
+ 'RL',
+ 'LR',
+ 'title',
+ 'accDescription',
+ 'order'
+ ],
+ typeKeywords: ['classDiagram', 'classDiagram-v2']
+ },
+ erDiagram: {
+ blockKeywords: [],
+ keywords: ['title', 'accDescription'],
+ typeKeywords: ['erDiagram']
+ },
+ flowchart: {
+ blockKeywords: ['subgraph', 'end'],
+ keywords: [
+ 'TB',
+ 'TD',
+ 'BT',
+ 'RL',
+ 'LR',
+ 'click',
+ 'call',
+ 'href',
+ '_self',
+ '_blank',
+ '_parent',
+ '_top',
+ 'linkStyle',
+ 'style',
+ 'classDef',
+ 'class',
+ 'direction',
+ 'interpolate'
+ ],
+ typeKeywords: ['flowchart', 'flowchart-v2', 'graph']
+ },
+ gantt: {
+ blockKeywords: [],
+ keywords: [
+ 'title',
+ 'dateFormat',
+ 'axisFormat',
+ 'todayMarker',
+ 'section',
+ 'excludes',
+ 'inclusiveEndDates'
+ ],
+ typeKeywords: ['gantt']
+ },
+ gitGraph: {
+ blockKeywords: [],
+ keywords: [
+ 'accTitle',
+ 'accDescr',
+ 'commit',
+ 'cherry-pick',
+ 'branch',
+ 'merge',
+ 'reset',
+ 'checkout',
+ 'LR',
+ 'BT',
+ 'id',
+ 'msg',
+ 'type',
+ 'tag',
+ 'NORMAL',
+ 'REVERSE',
+ 'HIGHLIGHT'
+ ],
+ typeKeywords: ['gitGraph']
+ },
+ info: {
+ blockKeywords: [],
+ keywords: ['showInfo'],
+ typeKeywords: ['info']
+ },
+ journey: {
+ blockKeywords: ['section'],
+ keywords: ['title'],
+ typeKeywords: ['journey']
+ },
+ pie: {
+ blockKeywords: [],
+ keywords: ['showData', 'title', 'accDescr', 'accTitle'],
+ typeKeywords: ['pie']
+ },
+ requirementDiagram: {
+ blockKeywords: [...requirementDiagrams, 'element'],
+ keywords: [],
+ typeKeywords: ['requirement', 'requirementDiagram']
},
sankey: {
- typeKeywords: ['sankey-beta'],
blockKeywords: [],
- keywords: []
+ keywords: [],
+ typeKeywords: ['sankey-beta']
+ },
+ sequenceDiagram: {
+ blockKeywords: ['alt', 'par', 'and', 'loop', 'else', 'end', 'rect', 'opt', 'alt', 'rect'],
+ keywords: [
+ 'participant',
+ 'as',
+ 'Note',
+ 'note',
+ 'right of',
+ 'left of',
+ 'over',
+ 'activate',
+ 'deactivate',
+ 'autonumber',
+ 'title',
+ 'actor',
+ 'accDescription',
+ 'link',
+ 'links'
+ ],
+ typeKeywords: ['sequenceDiagram']
+ },
+ stateDiagram: {
+ blockKeywords: ['state', 'note', 'end'],
+ keywords: ['state', 'as', 'hide empty description', 'direction', 'TB', 'BT', 'RL', 'LR'],
+ typeKeywords: ['stateDiagram', 'stateDiagram-v2']
}
};
const configDirectiveHandler = [
/^\s*%%(?={)/,
{
- token: 'string',
next: '@configDirective',
- nextEmbedded: 'javascript'
+ nextEmbedded: 'javascript',
+ token: 'string'
}
] as Monaco.languages.IShortMonarchLanguageRule1;
@@ -241,157 +241,26 @@ export const initEditor = (monacoEditor: typeof Monaco): void => {
{}
),
tokenizer: {
- root: [
- [/^\s*gitGraph/m, 'typeKeyword', 'gitGraph'],
- [/^\s*info/m, 'typeKeyword', 'info'],
- [/^\s*pie/m, 'typeKeyword', 'pie'],
- [/^\s*(flowchart|flowchart-v2|graph)/m, 'typeKeyword', 'flowchart'],
- [/^\s*sequenceDiagram/, 'typeKeyword', 'sequenceDiagram'],
- [/^\s*classDiagram(-v2)?/, 'typeKeyword', 'classDiagram'],
- [/^\s*journey/, 'typeKeyword', 'journey'],
- [/^\s*gantt/, 'typeKeyword', 'gantt'],
- [/^\s*stateDiagram(-v2)?/, 'typeKeyword', 'stateDiagram'],
- [/^\s*er(Diagram)?/, 'typeKeyword', 'erDiagram'],
- [/^\s*requirement(Diagram)?/, 'typeKeyword', 'requirementDiagram'],
- [/^\s*sankey-beta/m, 'typeKeyword', 'sankey'],
- [
- /^\s*(C4Context|C4Container|C4Component|C4Dynamic|C4Deployment)/m,
- 'typeKeyword',
- 'c4Diagram'
- ],
- configDirectiveHandler,
- [/%%[^${].*$/, 'comment']
- ],
- configDirective: [[/%%$/, { token: 'string', next: '@pop', nextEmbedded: '@pop' }]],
- gitGraph: [
- configDirectiveHandler,
- [/option(?=s)/, { token: 'typeKeyword', next: 'optionsGitGraph' }],
- [/(accTitle|accDescr)(\s*:)(\s*[^\n\r]+$)/, ['keyword', 'delimiter.bracket', 'string']],
- [
- /(^\s*branch)(.*?)(\s+order)(:\s*)(\d+\s*$)/,
- ['keyword', 'variable', 'keyword', 'delimiter.bracket', 'number']
- ],
- [/".*?"/, 'string'],
- [
- /(^\s*)(branch|reset|merge|checkout)(\s*\S+)/m,
- ['delimiter.bracket', 'keyword', 'variable']
- ],
- [
- /[A-Za-z][\w$]*/,
- {
- cases: {
- '@gitGraphBlockKeywords': 'typeKeyword',
- '@gitGraphKeywords': 'keyword'
- }
- }
- ],
- [/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment'],
- [/\^/, 'delimiter.bracket']
- ],
- optionsGitGraph: [
- [
- /s$/,
- {
- token: 'typeKeyword',
- nextEmbedded: 'json'
- }
- ],
- ['end', { token: 'typeKeyword', next: '@pop', nextEmbedded: '@pop' }]
- ],
- info: [
- [
- /[A-Za-z][\w$]*/,
- {
- cases: {
- '@infoBlockKeywords': 'typeKeyword',
- '@infoKeywords': 'keyword'
- }
- }
- ]
- ],
- pie: [
+ c4Diagram: [
configDirectiveHandler,
[/(title|accDescription)(.*$)/, ['keyword', 'string']],
+ [/\(/, { next: 'c4DiagramParenthesis', token: 'delimiter.bracket' }],
[
- /[A-Za-z][\w$]*/,
+ /[A-Z_a-z-][\w$]*/,
{
cases: {
- '@pieBlockKeywords': 'typeKeyword',
- '@pieKeywords': 'keyword'
- }
- }
- ],
- [/".*?"/, 'string'],
- [/\s*\d+/, 'number'],
- [/:/, 'delimiter.bracket'],
- [/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment']
- ],
- flowchart: [
- configDirectiveHandler,
- [/[ox]?(--+|==+)[ox]/, 'transition'],
- [
- /[A-Za-z][\w$]*/,
- {
- cases: {
- '@flowchartBlockKeywords': 'typeKeyword',
- '@flowchartKeywords': 'keyword',
+ '@c4DiagramBlockKeywords': 'typeKeyword',
+ '@c4DiagramKeywords': 'keyword',
'@default': 'variable'
}
}
],
- [/\|+.+?\|+/, 'string'],
- [/\[+(\\.+?[/\\]|\/.+?[/\\])]+/, 'string'],
- [/[>[]+[^[\]|]+?]+/, 'string'],
- [/{+.+?}+/, 'string'],
- [/\(+.+?\)+/, 'string'],
- [/-\.+->?/, 'transition'],
- [/(-[.-])([^>-][^-]+?)(-{3,}|-{2,}>|\.-+>)/, ['transition', 'string', 'transition']],
- [/(==+)([^=]+?)(={3,}|={2,}>)/, ['transition', 'string', 'transition']],
- [/(--+|==+)>|===+|---+/, 'transition'],
- [/:::/, 'transition'],
- [/[&;]/, 'delimiter.bracket'],
- [/".*?"/, 'string'],
[/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment']
],
- sequenceDiagram: [
- configDirectiveHandler,
- [/(title:?|accDescription)([^\n\r;]*$)/, ['keyword', 'string']],
- [/(autonumber)([^\S\n\r]+off[^\S\n\r]*$)/, ['keyword', 'keyword']],
- [/(autonumber)((?:[^\S\n\r]+\d+){2}[^\S\n\r]*$)/, ['keyword', 'number']],
- [/(autonumber)([^\S\n\r]+\d+[^\S\n\r]*$)/, ['keyword', 'number']],
- [
- /(link\s+)(.*?)(:)(\s*.*?)(\s*@)(\s*[^\n\r;]+)/,
- ['keyword', 'variable', 'delimiter.bracket', 'string', 'delimiter.bracket', 'string']
- ],
- [
- /((?:links|properties)\s+)([^\n\r:]*?)(:\s+)/,
- [
- { token: 'keyword' },
- { token: 'variable' },
- {
- token: 'delimiter.bracket',
- nextEmbedded: 'javascript',
- next: '@sequenceDiagramLinksProps'
- }
- ]
- ],
- [
- /[A-Za-z][\w$]*/,
- {
- cases: {
- '@sequenceDiagramBlockKeywords': 'typeKeyword',
- '@sequenceDiagramKeywords': 'keyword',
- '@default': 'variable'
- }
- }
- ],
- [/(--?>?>|--?[)x])[+-]?/, 'transition'],
- [/(:)([^\n:]*?$)/, ['delimiter.bracket', 'string']],
- [/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment']
- ],
- sequenceDiagramLinksProps: [
- // [/^:/, { token: 'delimiter.bracket', nextEmbedded: 'json' }],
- [/$|;/, { nextEmbedded: '@pop', next: '@pop', token: 'delimiter.bracket' }]
+ c4DiagramParenthesis: [
+ [/,/, 'delimiter.bracket'],
+ [/\)/, { next: '@pop', token: 'delimiter.bracket' }],
+ [/[^),]/, 'string']
],
classDiagram: [
configDirectiveHandler,
@@ -419,33 +288,43 @@ export const initEditor = (monacoEditor: typeof Monaco): void => {
[/:::/, 'transition'],
[/:|\+|-|#|~|\*\s*$|\$\s*$|\(|\)|{|}/, 'delimiter.bracket']
],
- journey: [
+ configDirective: [[/%%$/, { next: '@pop', nextEmbedded: '@pop', token: 'string' }]],
+ erDiagram: [
configDirectiveHandler,
- [/(title)(.*)/, ['keyword', 'string']],
- [/(section)(.*)/, ['typeKeyword', 'string']],
+ [/(title|accDescription)(.*$)/, ['keyword', 'string']],
+ [/[|}][o|](--|\.\.)[o|][{|]/, 'transition'],
+ [/".*?"/, 'string'],
+ [/(:)(.*?$)/, ['delimiter.bracket', 'string']],
+ [/[:{}]/, 'delimiter.bracket'],
+ [/([A-Za-z]+)(\s+[A-Za-z]+)/, ['type', 'variable']],
+ [/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment'],
+ [/[A-Z_a-z-][\w$]*/, 'variable']
+ ],
+ flowchart: [
+ configDirectiveHandler,
+ [/[ox]?(--+|==+)[ox]/, 'transition'],
[
/[A-Za-z][\w$]*/,
{
cases: {
- '@journeyBlockKeywords': 'typeKeyword',
- '@journeyKeywords': 'keyword',
- '@default': 'variable'
+ '@default': 'variable',
+ '@flowchartBlockKeywords': 'typeKeyword',
+ '@flowchartKeywords': 'keyword'
}
}
],
- [
- /(^\s*.+?)(:)(.*?)(:)(.*?)([$,])/,
- [
- 'string',
- 'delimiter.bracket',
- 'number',
- 'delimiter.bracket',
- 'variable',
- 'delimiter.bracket'
- ]
- ],
- [/,/, 'delimiter.bracket'],
- [/(^\s*.+?)(:)([^:]*?)$/, ['string', 'delimiter.bracket', 'variable']],
+ [/\|+.+?\|+/, 'string'],
+ [/\[+(\\.+?[/\\]|\/.+?[/\\])]+/, 'string'],
+ [/[>[]+[^[\]|]+?]+/, 'string'],
+ [/{+.+?}+/, 'string'],
+ [/\(+.+?\)+/, 'string'],
+ [/-\.+->?/, 'transition'],
+ [/(-[.-])([^>-][^-]+?)(-{3,}|-{2,}>|\.-+>)/, ['transition', 'string', 'transition']],
+ [/(==+)([^=]+?)(={3,}|={2,}>)/, ['transition', 'string', 'transition']],
+ [/(--+|==+)>|===+|---+/, 'transition'],
+ [/:::/, 'transition'],
+ [/[&;]/, 'delimiter.bracket'],
+ [/".*?"/, 'string'],
[/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment']
],
gantt: [
@@ -465,9 +344,189 @@ export const initEditor = (monacoEditor: typeof Monaco): void => {
[/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment'],
[/:/, 'delimiter.bracket']
],
+ gitGraph: [
+ configDirectiveHandler,
+ [/option(?=s)/, { next: 'optionsGitGraph', token: 'typeKeyword' }],
+ [/(accTitle|accDescr)(\s*:)(\s*[^\n\r]+$)/, ['keyword', 'delimiter.bracket', 'string']],
+ [
+ /(^\s*branch)(.*?)(\s+order)(:\s*)(\d+\s*$)/,
+ ['keyword', 'variable', 'keyword', 'delimiter.bracket', 'number']
+ ],
+ [/".*?"/, 'string'],
+ [
+ /(^\s*)(branch|reset|merge|checkout)(\s*\S+)/m,
+ ['delimiter.bracket', 'keyword', 'variable']
+ ],
+ [
+ /[A-Za-z][\w$]*/,
+ {
+ cases: {
+ '@gitGraphBlockKeywords': 'typeKeyword',
+ '@gitGraphKeywords': 'keyword'
+ }
+ }
+ ],
+ [/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment'],
+ [/\^/, 'delimiter.bracket']
+ ],
+ info: [
+ [
+ /[A-Za-z][\w$]*/,
+ {
+ cases: {
+ '@infoBlockKeywords': 'typeKeyword',
+ '@infoKeywords': 'keyword'
+ }
+ }
+ ]
+ ],
+ journey: [
+ configDirectiveHandler,
+ [/(title)(.*)/, ['keyword', 'string']],
+ [/(section)(.*)/, ['typeKeyword', 'string']],
+ [
+ /[A-Za-z][\w$]*/,
+ {
+ cases: {
+ '@default': 'variable',
+ '@journeyBlockKeywords': 'typeKeyword',
+ '@journeyKeywords': 'keyword'
+ }
+ }
+ ],
+ [
+ /(^\s*.+?)(:)(.*?)(:)(.*?)([$,])/,
+ [
+ 'string',
+ 'delimiter.bracket',
+ 'number',
+ 'delimiter.bracket',
+ 'variable',
+ 'delimiter.bracket'
+ ]
+ ],
+ [/,/, 'delimiter.bracket'],
+ [/(^\s*.+?)(:)([^:]*?)$/, ['string', 'delimiter.bracket', 'variable']],
+ [/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment']
+ ],
+ optionsGitGraph: [
+ [
+ /s$/,
+ {
+ nextEmbedded: 'json',
+ token: 'typeKeyword'
+ }
+ ],
+ ['end', { next: '@pop', nextEmbedded: '@pop', token: 'typeKeyword' }]
+ ],
+ pie: [
+ configDirectiveHandler,
+ [/(title|accDescription)(.*$)/, ['keyword', 'string']],
+ [
+ /[A-Za-z][\w$]*/,
+ {
+ cases: {
+ '@pieBlockKeywords': 'typeKeyword',
+ '@pieKeywords': 'keyword'
+ }
+ }
+ ],
+ [/".*?"/, 'string'],
+ [/\s*\d+/, 'number'],
+ [/:/, 'delimiter.bracket'],
+ [/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment']
+ ],
+ requirementDiagram: [
+ configDirectiveHandler,
+ [/->|<-|-/, 'transition'],
+ [/(\d+\.)*\d+/, 'number'],
+ [
+ /[A-Z_a-z-][\w$]*/,
+ {
+ cases: {
+ '@default': 'variable',
+ '@requirementDiagramBlockKeywords': 'typeKeyword'
+ }
+ }
+ ],
+ [/[/:{}]/, 'delimiter.bracket'],
+ [/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment'],
+ [/".*?"/, 'string']
+ ],
+ root: [
+ [/^\s*gitGraph/m, 'typeKeyword', 'gitGraph'],
+ [/^\s*info/m, 'typeKeyword', 'info'],
+ [/^\s*pie/m, 'typeKeyword', 'pie'],
+ [/^\s*(flowchart|flowchart-v2|graph)/m, 'typeKeyword', 'flowchart'],
+ [/^\s*sequenceDiagram/, 'typeKeyword', 'sequenceDiagram'],
+ [/^\s*classDiagram(-v2)?/, 'typeKeyword', 'classDiagram'],
+ [/^\s*journey/, 'typeKeyword', 'journey'],
+ [/^\s*gantt/, 'typeKeyword', 'gantt'],
+ [/^\s*stateDiagram(-v2)?/, 'typeKeyword', 'stateDiagram'],
+ [/^\s*er(Diagram)?/, 'typeKeyword', 'erDiagram'],
+ [/^\s*requirement(Diagram)?/, 'typeKeyword', 'requirementDiagram'],
+ [/^\s*sankey-beta/m, 'typeKeyword', 'sankey'],
+ [
+ /^\s*(C4Context|C4Container|C4Component|C4Dynamic|C4Deployment)/m,
+ 'typeKeyword',
+ 'c4Diagram'
+ ],
+ configDirectiveHandler,
+ [/%%[^${].*$/, 'comment']
+ ],
+ sankey: [
+ configDirectiveHandler,
+ [/(title)(.*)/, ['keyword', 'string']],
+ [/(accTitle|accDescr)(\s*:)(\s*[^\n\r]+$)/, ['keyword', 'delimiter.bracket', 'string']],
+ [/".*?"/, 'string'],
+ [/[A-Za-z]+/, 'string'],
+ [/\s*\d+/, 'number'],
+ [/,/, 'delimiter.bracket'],
+ [/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment']
+ ],
+ sequenceDiagram: [
+ configDirectiveHandler,
+ [/(title:?|accDescription)([^\n\r;]*$)/, ['keyword', 'string']],
+ [/(autonumber)([^\S\n\r]+off[^\S\n\r]*$)/, ['keyword', 'keyword']],
+ [/(autonumber)((?:[^\S\n\r]+\d+){2}[^\S\n\r]*$)/, ['keyword', 'number']],
+ [/(autonumber)([^\S\n\r]+\d+[^\S\n\r]*$)/, ['keyword', 'number']],
+ [
+ /(link\s+)(.*?)(:)(\s*.*?)(\s*@)(\s*[^\n\r;]+)/,
+ ['keyword', 'variable', 'delimiter.bracket', 'string', 'delimiter.bracket', 'string']
+ ],
+ [
+ /((?:links|properties)\s+)([^\n\r:]*?)(:\s+)/,
+ [
+ { token: 'keyword' },
+ { token: 'variable' },
+ {
+ next: '@sequenceDiagramLinksProps',
+ nextEmbedded: 'javascript',
+ token: 'delimiter.bracket'
+ }
+ ]
+ ],
+ [
+ /[A-Za-z][\w$]*/,
+ {
+ cases: {
+ '@default': 'variable',
+ '@sequenceDiagramBlockKeywords': 'typeKeyword',
+ '@sequenceDiagramKeywords': 'keyword'
+ }
+ }
+ ],
+ [/(--?>?>|--?[)x])[+-]?/, 'transition'],
+ [/(:)([^\n:]*?$)/, ['delimiter.bracket', 'string']],
+ [/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment']
+ ],
+ sequenceDiagramLinksProps: [
+ // [/^:/, { token: 'delimiter.bracket', nextEmbedded: 'json' }],
+ [/$|;/, { next: '@pop', nextEmbedded: '@pop', token: 'delimiter.bracket' }]
+ ],
stateDiagram: [
configDirectiveHandler,
- [/note[^:]*$/, { token: 'typeKeyword', next: 'stateDiagramNote' }],
+ [/note[^:]*$/, { next: 'stateDiagramNote', token: 'typeKeyword' }],
['hide empty description', 'keyword'],
[/^\s*state\s(?!.*{)/, 'keyword'],
[/(<<)(fork|join|choice)(>>)/, 'annotation'],
@@ -476,9 +535,9 @@ export const initEditor = (monacoEditor: typeof Monaco): void => {
/[A-Za-z][\w$]*/,
{
cases: {
+ '@default': 'variable',
'@stateDiagramBlockKeywords': 'typeKeyword',
- '@stateDiagramKeywords': 'keyword',
- '@default': 'variable'
+ '@stateDiagramKeywords': 'keyword'
}
}
],
@@ -490,99 +549,40 @@ export const initEditor = (monacoEditor: typeof Monaco): void => {
[/\[.*?]/, 'string']
],
stateDiagramNote: [
- [/^\s*end note$/, { token: 'typeKeyword', next: '@pop' }],
+ [/^\s*end note$/, { next: '@pop', token: 'typeKeyword' }],
[/.*/, 'string']
- ],
- erDiagram: [
- configDirectiveHandler,
- [/(title|accDescription)(.*$)/, ['keyword', 'string']],
- [/[|}][o|](--|\.\.)[o|][{|]/, 'transition'],
- [/".*?"/, 'string'],
- [/(:)(.*?$)/, ['delimiter.bracket', 'string']],
- [/[:{}]/, 'delimiter.bracket'],
- [/([A-Za-z]+)(\s+[A-Za-z]+)/, ['type', 'variable']],
- [/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment'],
- [/[A-Z_a-z-][\w$]*/, 'variable']
- ],
- requirementDiagram: [
- configDirectiveHandler,
- [/->|<-|-/, 'transition'],
- [/(\d+\.)*\d+/, 'number'],
- [
- /[A-Z_a-z-][\w$]*/,
- {
- cases: {
- '@requirementDiagramBlockKeywords': 'typeKeyword',
- '@default': 'variable'
- }
- }
- ],
- [/[/:{}]/, 'delimiter.bracket'],
- [/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment'],
- [/".*?"/, 'string']
- ],
- c4Diagram: [
- configDirectiveHandler,
- [/(title|accDescription)(.*$)/, ['keyword', 'string']],
- [/\(/, { token: 'delimiter.bracket', next: 'c4DiagramParenthesis' }],
- [
- /[A-Z_a-z-][\w$]*/,
- {
- cases: {
- '@c4DiagramBlockKeywords': 'typeKeyword',
- '@c4DiagramKeywords': 'keyword',
- '@default': 'variable'
- }
- }
- ],
- [/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment']
- ],
- c4DiagramParenthesis: [
- [/,/, 'delimiter.bracket'],
- [/\)/, { next: '@pop', token: 'delimiter.bracket' }],
- [/[^),]/, 'string']
- ],
- sankey: [
- configDirectiveHandler,
- [/(title)(.*)/, ['keyword', 'string']],
- [/(accTitle|accDescr)(\s*:)(\s*[^\n\r]+$)/, ['keyword', 'delimiter.bracket', 'string']],
- [/".*?"/, 'string'],
- [/[A-Za-z]+/, 'string'],
- [/\s*\d+/, 'number'],
- [/,/, 'delimiter.bracket'],
- [/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment']
]
}
});
monacoEditor.editor.defineTheme('mermaid-dark', {
base: 'vs-dark',
- inherit: true,
colors: {},
+ inherit: true,
rules: [
- { token: 'typeKeyword', foreground: '9650c8', fontStyle: 'bold' },
- { token: 'transition', foreground: '008800', fontStyle: 'bold' },
- { token: 'identifier', foreground: '9cdcfe' }
+ { fontStyle: 'bold', foreground: '9650c8', token: 'typeKeyword' },
+ { fontStyle: 'bold', foreground: '008800', token: 'transition' },
+ { foreground: '9cdcfe', token: 'identifier' }
]
});
monacoEditor.editor.defineTheme('mermaid', {
base: 'vs',
- inherit: true,
colors: {},
+ inherit: true,
rules: [
- { token: 'typeKeyword', foreground: '9650c8', fontStyle: 'bold' },
- { token: 'keyword', foreground: '649696' },
- { token: 'custom-error', foreground: 'ff0000', fontStyle: 'bold' },
- { token: 'string', foreground: 'AA8500' },
- { token: 'transition', foreground: '008800', fontStyle: 'bold' },
- { token: 'delimiter.bracket', foreground: '000000', fontStyle: 'bold' },
- { token: 'annotation', foreground: '4b4b96' },
- { token: 'number', foreground: '4b4b96' },
- { token: 'comment', foreground: '888c89' },
- { token: 'variable', foreground: 'A22889' },
- { token: 'type', foreground: '2BDEA8' },
- { token: 'identifier', foreground: '9cdcfe' }
+ { fontStyle: 'bold', foreground: '9650c8', token: 'typeKeyword' },
+ { foreground: '649696', token: 'keyword' },
+ { fontStyle: 'bold', foreground: 'ff0000', token: 'custom-error' },
+ { foreground: 'AA8500', token: 'string' },
+ { fontStyle: 'bold', foreground: '008800', token: 'transition' },
+ { fontStyle: 'bold', foreground: '000000', token: 'delimiter.bracket' },
+ { foreground: '4b4b96', token: 'annotation' },
+ { foreground: '4b4b96', token: 'number' },
+ { foreground: '888c89', token: 'comment' },
+ { foreground: 'A22889', token: 'variable' },
+ { foreground: '2BDEA8', token: 'type' },
+ { foreground: '9cdcfe', token: 'identifier' }
]
});
@@ -597,16 +597,16 @@ export const initEditor = (monacoEditor: typeof Monaco): void => {
monacoEditor.languages.setLanguageConfiguration('mermaid', {
autoClosingPairs: [
{
- open: '(',
- close: ')'
+ close: ')',
+ open: '('
},
{
- open: '{',
- close: '}'
+ close: '}',
+ open: '{'
},
{
- open: '[',
- close: ']'
+ close: ']',
+ open: '['
}
],
brackets: [
diff --git a/src/lib/util/notify.ts b/src/lib/util/notify.ts
index 3c9e6ded..5c1c7023 100644
--- a/src/lib/util/notify.ts
+++ b/src/lib/util/notify.ts
@@ -1,5 +1,7 @@
+import { toast } from 'svelte-sonner';
+
export const notify = (message: string): void => {
- alert(message);
+ toast(message);
};
export const prompt = (message: string): boolean => {
diff --git a/src/lib/util/panZoom.ts b/src/lib/util/panZoom.ts
new file mode 100644
index 00000000..97844bd6
--- /dev/null
+++ b/src/lib/util/panZoom.ts
@@ -0,0 +1,108 @@
+import type { State } from '$/types';
+import type { Point } from 'mermaid/dist/types.js';
+import panzoom from 'svg-pan-zoom';
+type PanZoom = typeof panzoom;
+
+export class PanZoomState {
+ private pan?: Point;
+ private zoom?: number;
+ private pzoom: PanZoom | undefined;
+ private isDirty = false;
+ private resizeObserver: ResizeObserver;
+
+ public isPanEnabled: boolean;
+ public onPanZoomChange?: (pan: Point, zoom: number) => void;
+
+ constructor() {
+ this.isPanEnabled = true;
+ this.resizeObserver = new ResizeObserver(() => {
+ this.resize();
+ if (!this.isDirty) {
+ this.reset();
+ }
+ });
+ }
+
+ public updateElement(diagramView: SVGElement, { pan, zoom }: Pick) {
+ this.pzoom = panzoom(diagramView, {
+ center: true,
+ controlIconsEnabled: false,
+ fit: true,
+ maxZoom: 12,
+ minZoom: 0.2,
+ onPan: (pan) => {
+ this.pan = pan;
+ this.zoom = this.pzoom?.getZoom();
+ this.isDirty = true;
+ if (this.zoom) {
+ this.onPanZoomChange?.(this.pan, this.zoom);
+ }
+ },
+ onZoom: (zoom) => {
+ this.zoom = zoom;
+ this.pan = this.pzoom?.getPan();
+ this.isDirty = true;
+ if (this.pan) {
+ this.onPanZoomChange?.(this.pan, this.zoom);
+ }
+ },
+ panEnabled: true,
+ zoomEnabled: true
+ });
+
+ this.pzoom.disableDblClickZoom();
+
+ this.resizeObserver.disconnect();
+ this.resizeObserver.observe(diagramView);
+
+ // TODO: Investigate why this is necessary
+ if (pan !== undefined && zoom !== undefined && Number.isFinite(zoom)) {
+ this.restorePanZoom(pan, zoom);
+ }
+
+ // we start out with both pan and zoom enabled so that the tool can auto position view refreshed
+ // then set enable/disable pan based on state
+ if (this.isPanEnabled) {
+ this.pzoom.enablePan();
+ this.pzoom.enableZoom();
+ } else {
+ this.pzoom.disableZoom();
+ this.pzoom.disablePan();
+ }
+
+ if (pan === undefined && zoom === undefined) {
+ this.reset();
+ }
+ }
+
+ public restorePanZoom(pan: Point, zoom: number) {
+ if (!this.pzoom) {
+ console.error('PanZoomState.restorePanZoom: pzoom is not initialized');
+ return;
+ }
+ this.pzoom.zoom(zoom);
+ this.pzoom.pan(pan);
+ }
+
+ public resize() {
+ this.pzoom?.resize();
+ if (!this.isDirty) {
+ this.reset();
+ }
+ }
+
+ public zoomIn() {
+ this.pzoom?.zoomIn();
+ }
+
+ public zoomOut() {
+ this.pzoom?.zoomOut();
+ }
+
+ public reset() {
+ this.pzoom?.reset();
+ // Zoom out a bit to avoid overlap with the toolbar
+ this.pzoom?.zoom(0.875);
+ this.isDirty = false;
+ }
+}
diff --git a/src/lib/util/persist.ts b/src/lib/util/persist.ts
index 2c63b9a2..dfdbaa13 100644
--- a/src/lib/util/persist.ts
+++ b/src/lib/util/persist.ts
@@ -204,6 +204,13 @@ function getBrowserStorage(
connect();
}
},
+ deleteValue(key: string) {
+ browserStorage.removeItem(key);
+ },
+ getValue(key: string): any {
+ const value = browserStorage.getItem(key);
+ return deserialize(value);
+ },
removeListener(key: string, listener: (newValue: any) => void) {
const index = listeners.indexOf({ key, listener });
if (index !== -1) {
@@ -213,13 +220,6 @@ function getBrowserStorage(
disconnect();
}
},
- getValue(key: string): any {
- const value = browserStorage.getItem(key);
- return deserialize(value);
- },
- deleteValue(key: string) {
- browserStorage.removeItem(key);
- },
setValue(key: string, value: any) {
browserStorage.setItem(key, serialize(value));
}
diff --git a/src/lib/util/promos/January2025.svelte b/src/lib/util/promos/January2025.svelte
index 339a4fb2..f59dd24a 100644
--- a/src/lib/util/promos/January2025.svelte
+++ b/src/lib/util/promos/January2025.svelte
@@ -1,4 +1,5 @@
-
- {@render children?.()}
+
+
+
+
+ {@render children()}
{#if $loadingStateStore.loading}
diff --git a/src/routes/edit/+page.svelte b/src/routes/edit/+page.svelte
index e8c19b47..ad91fe2d 100644
--- a/src/routes/edit/+page.svelte
+++ b/src/routes/edit/+page.svelte
@@ -1,263 +1,112 @@
-
+
+
+
+
+
+
+
+
+ Save diagram
+
+
+
+
-
-
- {#snippet actions()}
-
-
-
- Auto sync
-
-
-
+
+
+
+
+ {#snippet actions()}
+
+ {/snippet}
+
+
- {#if !$stateStore.autoSync}
-
- {/if}
-
-
-
- Docs
-
-
+
- {/snippet}
-
-
-
-
-
-
-
-
-
- {#snippet actions()}
-
- {/snippet}
-
-
-
-
-
- Code editing not supported on mobile. Please use a desktop browser.
-
-
+
+
+
+
+
+
+
+
+ Code editing not supported on mobile. Please use a desktop browser.
+
+
+ {#if isHistoryOpen}
+
+
+
+
+ {/if}
+
-
-
diff --git a/src/routes/view/+page.svelte b/src/routes/view/+page.svelte
index b2d25334..0f915b09 100644
--- a/src/routes/view/+page.svelte
+++ b/src/routes/view/+page.svelte
@@ -1,6 +1,6 @@
@@ -9,4 +9,4 @@
-
+
diff --git a/src/tests/setup.ts b/src/tests/setup.ts
index b2e0c4d6..2240083c 100644
--- a/src/tests/setup.ts
+++ b/src/tests/setup.ts
@@ -1,6 +1,4 @@
-import '@testing-library/jest-dom/vitest';
import { beforeAll, vi } from 'vitest';
-import 'vitest-dom/extend-expect';
// TODO: Remove once https://github.com/sveltejs/kit/issues/6259 is closed.
beforeAll(() => {
diff --git a/static/icons/code.svg b/static/icons/code.svg
new file mode 100644
index 00000000..84006ab9
--- /dev/null
+++ b/static/icons/code.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/mermaidchart-logo.svg b/static/mermaidchart-logo.svg
index d7c8b59e..040245d2 100755
--- a/static/mermaidchart-logo.svg
+++ b/static/mermaidchart-logo.svg
@@ -1 +1,4 @@
-
+
+
+
+
diff --git a/svelte.config.js b/svelte.config.js
index 0180e1c2..7d17fce0 100644
--- a/svelte.config.js
+++ b/svelte.config.js
@@ -11,6 +11,9 @@ const config = {
})
],
kit: {
+ alias: {
+ '$/*': './src/lib/*'
+ },
adapter: adapter({
pages: 'docs'
})
diff --git a/tailwind.config.cjs b/tailwind.config.cjs
deleted file mode 100644
index 18cb32c1..00000000
--- a/tailwind.config.cjs
+++ /dev/null
@@ -1,10 +0,0 @@
-module.exports = {
- plugins: [require('daisyui')],
- content: ['./src/**/*.{html,js,svelte,ts}'],
- theme: {
- extend: {}
- },
- variants: {
- extend: {}
- }
-};
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 00000000..8eb91f36
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,96 @@
+import tailwindcssAnimate from 'tailwindcss-animate';
+import { fontFamily } from 'tailwindcss/defaultTheme';
+
+/** @type {import('tailwindcss').Config} */
+const config = {
+ darkMode: ['class'],
+ content: ['./src/**/*.{html,js,svelte,ts}'],
+ safelist: ['dark'],
+ theme: {
+ container: {
+ center: true,
+ padding: '2rem',
+ screens: {
+ '2xl': '1400px'
+ }
+ },
+ extend: {
+ colors: {
+ border: 'hsl(var(--border) /
)',
+ input: 'hsl(var(--input) / )',
+ ring: 'hsl(var(--ring) / )',
+ background: 'hsl(var(--background) / )',
+ foreground: 'hsl(var(--foreground) / )',
+ primary: {
+ DEFAULT: 'hsl(var(--primary) / )',
+ foreground: 'hsl(var(--primary-foreground) / )'
+ },
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary) / )',
+ foreground: 'hsl(var(--secondary-foreground) / )'
+ },
+ destructive: {
+ DEFAULT: 'hsl(var(--destructive) / )',
+ foreground: 'hsl(var(--destructive-foreground) / )'
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--muted) / )',
+ foreground: 'hsl(var(--muted-foreground) / )'
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--accent) / )',
+ foreground: 'hsl(var(--accent-foreground) / )'
+ },
+ popover: {
+ DEFAULT: 'hsl(var(--popover) / )',
+ foreground: 'hsl(var(--popover-foreground) / )'
+ },
+ card: {
+ DEFAULT: 'hsl(var(--card) / )',
+ foreground: 'hsl(var(--card-foreground) / )'
+ },
+ sidebar: {
+ DEFAULT: 'hsl(var(--sidebar-background))',
+ foreground: 'hsl(var(--sidebar-foreground))',
+ primary: 'hsl(var(--sidebar-primary))',
+ 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
+ accent: 'hsl(var(--sidebar-accent))',
+ 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
+ border: 'hsl(var(--sidebar-border))',
+ ring: 'hsl(var(--sidebar-ring))'
+ }
+ },
+ borderRadius: {
+ xl: 'calc(var(--radius) + 4px)',
+ lg: 'var(--radius)',
+ md: 'calc(var(--radius) - 2px)',
+ sm: 'calc(var(--radius) - 4px)'
+ },
+ fontFamily: {
+ sans: [...fontFamily.sans]
+ },
+ keyframes: {
+ 'accordion-down': {
+ from: { height: '0' },
+ to: { height: 'var(--bits-accordion-content-height)' }
+ },
+ 'accordion-up': {
+ from: { height: 'var(--bits-accordion-content-height)' },
+ to: { height: '0' }
+ },
+ 'caret-blink': {
+ '0%,70%,100%': { opacity: '1' },
+ '20%,50%': { opacity: '0' }
+ }
+ },
+ animation: {
+ 'accordion-down': 'accordion-down 0.2s ease-out',
+ 'accordion-up': 'accordion-up 0.2s ease-out',
+ 'caret-blink': 'caret-blink 1.25s ease-out infinite'
+ }
+ }
+ },
+ plugins: [tailwindcssAnimate]
+};
+
+export default config;
diff --git a/tests/actions.spec.ts b/tests/actions.spec.ts
index f710e55c..4f9aae94 100644
--- a/tests/actions.spec.ts
+++ b/tests/actions.spec.ts
@@ -1,17 +1,14 @@
-import { expect, test } from '@playwright/test';
-import { typeInEditor, verifyFileSizeGreaterThan } from './utils';
+import { expect, test } from './test';
test.describe('Check actions', () => {
- test.beforeEach(async ({ page }) => {
- await page.goto('/edit');
- await page.getByText('Actions', { exact: true }).click();
+ test.beforeEach(async ({ editPage }) => {
+ await editPage.toggleActions();
});
- test('should update markdown code', async ({ page }) => {
- const markdown = page.locator('#markdown');
- const oldText = await markdown.inputValue();
- await typeInEditor(page, 'C --> HistoryTest', { bottom: true, newline: true });
- const newText = await markdown.inputValue();
+ test('should update markdown code', async ({ editPage }) => {
+ const oldText = await editPage.markdownInput.inputValue();
+ await editPage.typeInEditor('C --> HistoryTest', { bottom: true, newline: true });
+ const newText = await editPage.markdownInput.inputValue();
expect(newText).not.toBe(oldText);
});
@@ -23,26 +20,16 @@ test.describe('Check actions', () => {
await expect(page.getByText('Go shopping!!')).toBeVisible();
});
- test('should download png and svg', async ({ page }) => {
- const downloadPNGPromise = verifyFileSizeGreaterThan(page, 'diagram', 'png', 29_000);
- await page.locator('#downloadPNG').click();
- const firstPngSize = await downloadPNGPromise;
-
- const downloadSVGPromise = verifyFileSizeGreaterThan(page, 'diagram', 'svg', 10_000);
- await page.locator('#downloadSVG').click();
- const firstSvgSize = await downloadSVGPromise;
+ test('should download png and svg', async ({ editPage }) => {
+ const firstPngSize = await editPage.checkAndDownloadPNG(80_000);
+ const firstSvgSize = await editPage.downloadSVG(10_000);
// Verify downloaded file is different for different diagrams
- await page.getByText('Sample Diagrams').click();
- await page.getByText('ER', { exact: true }).click();
+ await editPage.toggleSampleDiagrams();
+ await editPage.loadSampleDiagram('ER');
- const downloadPNGPromise2 = verifyFileSizeGreaterThan(page, 'diagram', 'png', 35_000);
- await page.locator('#downloadPNG').click();
- const secondPngSize = await downloadPNGPromise2;
-
- const downloadSVGPromise2 = verifyFileSizeGreaterThan(page, 'diagram', 'svg', 11_000);
- await page.locator('#downloadSVG').click();
- const secondSvgSize = await downloadSVGPromise2;
+ const secondPngSize = await editPage.checkAndDownloadPNG(110_000);
+ const secondSvgSize = await editPage.downloadSVG(11_000);
// Verify files are actually different
expect(firstPngSize).not.toBe(secondPngSize);
diff --git a/tests/diagramUpdate.spec.ts b/tests/diagramUpdate.spec.ts
index aaa1dfec..08a1d778 100644
--- a/tests/diagramUpdate.spec.ts
+++ b/tests/diagramUpdate.spec.ts
@@ -1,63 +1,10 @@
-import { expect, test } from '@playwright/test';
-import { typeInEditor } from './utils';
+import { test } from './test';
test.describe('Auto sync tests', () => {
- test.beforeEach(async ({ page }) => {
- await page.goto('/');
- await expect(page).toHaveURL(/.*\/edit#pako/);
- });
-
- test('should dim diagram when code is edited', async ({ page }) => {
- await page.getByText('Auto sync').click();
- await expect(page.locator('#view')).not.toHaveClass(/outOfSync/);
- await expect(page.locator('#errorContainer')).not.toBeVisible();
- await typeInEditor(page, ' C --> Test', { bottom: true });
- await expect(page.locator('#view')).toHaveClass(/outOfSync/);
- await expect(page.locator('#errorContainer')).toContainText('Diagram out of sync.');
- const codeStore = await page.evaluate(() => localStorage.getItem('codeStore'));
- expect(codeStore).toBeTruthy();
- });
-
- test('should update diagram when shortcut is used', async ({ page }) => {
- await page.getByText('Auto sync').click();
- await expect(page.locator('#view')).not.toHaveClass(/outOfSync/);
- await typeInEditor(page, ' C --> Test');
- await expect(page.locator('#view')).toHaveClass(/outOfSync/);
- await expect(page.locator('#errorContainer')).toContainText('Diagram out of sync.');
- await page.locator('#editor').click();
- await page.keyboard.press('Control+Enter');
- await expect(page.locator('#errorContainer')).not.toBeVisible();
- await expect(page.locator('#view')).not.toHaveClass(/outOfSync/);
- });
-
- test('should show/hide sync button with auto sync', async ({ page }) => {
- await expect(page.locator('[data-cy=sync]')).not.toBeVisible();
- await page.getByText('Auto sync').click();
- await expect(page.locator('[data-cy=sync]')).toBeVisible();
- await page.locator('#autoSync').check();
- await expect(page.locator('[data-cy=sync]')).not.toBeVisible();
- });
-
- test('should not dim diagram when code is in sync', async ({ page }) => {
- await page.getByText('Auto sync').click();
- await expect(page.locator('#view')).not.toHaveClass(/outOfSync/);
- await typeInEditor(page, ' C --> Test');
- await expect(page.locator('#view')).toHaveClass(/outOfSync/);
- await page.locator('[data-cy=sync]').click();
- await expect(page.locator('#view')).not.toHaveClass(/outOfSync/);
- await page.locator('#autoSync').check();
- await typeInEditor(page, 'ing');
- await expect(page.locator('#view')).not.toHaveClass(/outOfSync/);
- const codeStore = await page.evaluate(() => localStorage.getItem('codeStore'));
- expect(codeStore).toBeTruthy();
- });
-
test('should automatically defer rendering when complex diagrams are edited', async ({
- page
+ editPage
}) => {
- await expect(page.locator('#view')).not.toHaveClass(/outOfSync/);
- await typeInEditor(
- page,
+ await editPage.typeInEditor(
`
A & B & C & D & E --> F & G & H & I & J & K
A & B & C & D & E --> F & G & H & I & J & K
@@ -66,62 +13,35 @@ A & B & C & D & E --> F & G & H & I & J & K
A & B & C & D & E --> F & G & H & I & J & K
A & B & C & D & E --> F & G & H & I & J & K
A & B & C & D & E --> F & G & H & I & J & K
-A & B & C & D & E --> F & G & H & I & J & K`
+A & B & C & D & E --> F & G & H & I & J & K & LongTest`
);
- await expect(page.locator('#view')).toHaveClass(/outOfSync/);
- await expect(page.locator('#errorContainer')).toContainText(
- 'It will be updated automatically.'
- );
- // The class should be removed automatically after 1 second
- await expect(page.locator('#view')).not.toHaveClass(/outOfSync/, { timeout: 2000 });
+ await editPage.checkTextNotInView('LongTest');
+ await editPage.checkTextInView('LongTest');
});
- test('supports commenting code out/in', async ({ page }) => {
- await page.locator('#editor').getByText('fa-car').click();
- await page.waitForTimeout(1000);
- await page.keyboard.press('Control+/');
-
- await expect(page.locator('#view').getByText('Car')).not.toBeVisible();
-
- await page.keyboard.press('Control+/');
- await expect(page.locator('#view').getByText('Car')).toBeVisible();
+ test('supports commenting code out/in', async ({ editPage }) => {
+ await editPage.toggleComment('fa-car');
+ await editPage.checkTextNotInView('Car');
+ await editPage.toggleComment('fa-car');
+ await editPage.checkTextInView('Car');
});
- test('supports editing code when code is incorrect', async ({ page }) => {
- await page.goto(
+ test('supports editing code when code is incorrect', async ({ editPage }) => {
+ await editPage.start(
'/edit#pako:eNpljjEKwzAMRa8SNOcEnlt6gK5eVFvYJsgOqkwpIXevg9smEE1PnyfxF3DFExgISW-CczQ2D21cYU7a-SGYXRwyvTp9jUhuKlVP-eHy7zA-leQsMEmg_QOM0BLG5FujZVMsaCQmC6ahR5ks2Lw2r84ela4-aREwKpVGwKrl_s7ut3fnkjAIcg_XDzuaUhs'
);
- await expect(page.getByText('Diagram syntax error')).not.toBeVisible();
- await typeInEditor(page, 'branch test', { bottom: true, newline: true });
- await expect(page.locator('#editor')).toContainText('branch test');
- await expect(
- page.getByText('Error: Trying to checkout branch which is not yet created.')
- ).toBeVisible();
+ await editPage.typeInEditor('branch test', { bottom: true, newline: true });
+ await editPage.checkTextNotInView('test');
+ await editPage.checkInEditor('branch test');
+ await editPage.checkError('Error: Trying to checkout branch which is not yet created.');
});
- test('should update diagram after entire text is removed', async ({ page }) => {
- await page.locator('#editor').click();
- await page.keyboard.press('Control+KeyA');
- await page.keyboard.press('Backspace');
- await typeInEditor(
- page,
+ test('should update diagram after entire text is removed', async ({ editPage }) => {
+ await editPage.clearEditor();
+ await editPage.typeInEditor(
`graph LR
A-->Bike`
);
- await expect(page.locator('#view').getByText('Bike')).toBeVisible();
- });
-});
-
-test.describe('Pan and Zoom', () => {
- test.beforeEach(async ({ page }) => {
- await page.goto('/');
- });
-
- test('should toggle pan and zoom', async ({ page }) => {
- await expect(page.locator('#svg-pan-zoom-reset-pan-zoom')).not.toBeVisible();
- await page.getByText('Pan & Zoom').click();
- await expect(page.locator('#svg-pan-zoom-reset-pan-zoom')).toBeVisible();
- await page.getByText('Pan & Zoom').click();
- await expect(page.locator('#svg-pan-zoom-reset-pan-zoom')).not.toBeVisible();
+ await editPage.checkTextInView('Bike');
});
});
diff --git a/tests/docMap.spec.ts b/tests/docMap.spec.ts
index d7c35daf..649ac56a 100644
--- a/tests/docMap.spec.ts
+++ b/tests/docMap.spec.ts
@@ -1,42 +1,38 @@
-import { expect, test } from '@playwright/test';
+import { test } from './test';
test.describe('Editor docs tests', () => {
- test.beforeEach(async ({ page }) => {
- await page.goto('/edit');
- await page.getByText('Sample Diagrams').click();
+ test.beforeEach(async ({ editPage }) => {
+ await editPage.toggleSampleDiagrams();
});
- test('Test default loading', async ({ page }) => {
- await expect(page.locator('[data-cy=docs][href^="https://mermaid.js.org/"]')).toBeVisible();
+ test('Test default loading', async ({ editPage }) => {
+ await editPage.checkDocURL(/mermaid\.js\.org\//);
});
test('Test to see if the correct URL loads when changing from one diagram to other', async ({
- page
+ editPage
}) => {
- await page.getByText('Flow', { exact: true }).click();
- await expect(page.locator('[data-cy=docs][href$="/syntax/flowchart.html"]')).toBeVisible();
+ await editPage.loadSampleDiagram('Flow');
+ await editPage.checkDocURL(/syntax\/flowchart\.html/);
+ await editPage.setEditorMode('Config');
+ await editPage.checkDocURL(/syntax\/flowchart\.html#configuration/);
+ await editPage.setEditorMode('Code');
+ await editPage.checkDocURL(/syntax\/flowchart\.html/);
- await page.getByText('Config').click();
- await expect(
- page.locator('[data-cy=docs][href$="/syntax/flowchart.html#configuration"]')
- ).toBeVisible();
-
- await page.getByText('Sequence').click();
- await expect(
- page.locator('[data-cy=docs][href$="/syntax/sequenceDiagram.html#configuration"]')
- ).toBeVisible();
-
- await page.getByText('Code', { exact: true }).click();
- await expect(
- page.locator('[data-cy=docs][href$="/syntax/sequenceDiagram.html"]')
- ).toBeVisible();
+ await editPage.loadSampleDiagram('Sequence');
+ await editPage.checkDocURL(/syntax\/sequenceDiagram\.html/);
+ await editPage.setEditorMode('Config');
+ await editPage.checkDocURL(/syntax\/sequenceDiagram\.html#configuration/);
+ await editPage.setEditorMode('Code');
+ await editPage.checkDocURL(/syntax\/sequenceDiagram\.html/);
});
- test("Test to check URLs for a case where config URL doesn't exist", async ({ page }) => {
- await page.getByText('State').click();
- await expect(page.locator('[data-cy=docs][href$="/syntax/stateDiagram.html"]')).toBeVisible();
-
- await page.getByText('Config').click();
- await expect(page.locator('[data-cy=docs][href$="/syntax/stateDiagram.html"]')).toBeVisible();
+ test("Test to check URLs for a case where config URL doesn't exist", async ({ editPage }) => {
+ await editPage.loadSampleDiagram('State');
+ await editPage.checkDocURL(/syntax\/stateDiagram\.html/);
+ await editPage.setEditorMode('Config');
+ await editPage.checkDocURL(/syntax\/stateDiagram\.html/);
+ await editPage.setEditorMode('Code');
+ await editPage.checkDocURL(/syntax\/stateDiagram\.html/);
});
});
diff --git a/tests/history.spec.ts b/tests/history.spec.ts
index 6b0d003d..0b81569e 100644
--- a/tests/history.spec.ts
+++ b/tests/history.spec.ts
@@ -1,7 +1,7 @@
import { expect, test } from '@playwright/test';
import { typeInEditor } from './utils';
-test.describe('Save History', () => {
+test.describe.skip('Save History', () => {
test.beforeEach(async ({ page }) => {
await page.addInitScript(() => {
Object.defineProperty(Date, 'now', {
diff --git a/tests/loadSite.spec.ts b/tests/loadSite.spec.ts
index fff88111..1e78791f 100644
--- a/tests/loadSite.spec.ts
+++ b/tests/loadSite.spec.ts
@@ -1,46 +1,43 @@
-import { expect, test } from '@playwright/test';
+import { expect, test } from './test';
test.describe('Site Loads', () => {
- test.beforeEach(async ({ page }) => {
- await page.goto('/');
- await expect(page).toHaveURL(/.*\/edit#pako/);
- });
-
- test('Check Home page load', async ({ page }) => {
- await expect(page).toHaveURL(/.*\/edit/);
- await page.getByText('History').click();
- const codeStore = await page.evaluate(() => localStorage.getItem('codeStore'));
+ test('Check Home page load', async ({ editPage }) => {
+ await editPage.setEditorMode('Config');
+ const codeStore = await editPage.page.evaluate(() => localStorage.getItem('codeStore'));
expect(codeStore).toBeTruthy();
});
- test('should keep code after reload', async ({ page }) => {
- await expect(page.locator('#editor')).toContainText('Car');
+ test('should keep code after reload', async ({ editPage, page }) => {
+ await editPage.checkInEditor('Car');
+ await editPage.checkTextInView('Car');
await page.reload();
- await expect(page.locator('#editor')).toContainText('Car');
+ await editPage.checkInEditor('Car');
+ await editPage.checkTextInView('Car');
});
- test('Check Redirect from old URL', async ({ page }) => {
- await page.goto(
+ test('Check Redirect from old URL', async ({ page, editPage }) => {
+ await editPage.start(
'/#/edit/eyJjb2RlIjoiZ3JhcGggVERcbiAgICBBW0NocmlzdG1hc10gLS0-fEdldCBtb25leXwgQihHbyBzaG9wcGluZylcbiAgICBCIC0tPiBDe0xldCBtZSB0aGlua31cbiAgICBDIC0tPnxPbmV8IERbTGFwdG9wXVxuICAgIEMgLS0-fFR3b3wgRVtpUGhvbmVdXG4gICAgQyAtLT58VGhyZWV8IEZbZmE6ZmEtY2FyIENhcl0iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ'
);
await expect(page).toHaveURL(/.*\/edit#pako:eNp/);
});
- test('should load sample diagrams when clicked', async ({ page }) => {
- await page.getByText('Sample Diagrams').click();
- await page.getByText('Pie').click();
- await expect(page.getByText('pie title Pets adopted by volunteers')).toBeVisible();
- await page.getByText('Class').click();
- await expect(page.getByText('classDiagram')).toBeVisible();
+ test('should load sample diagrams when clicked', async ({ editPage }) => {
+ await editPage.toggleSampleDiagrams();
+ await editPage.loadSampleDiagram('Pie');
+ await editPage.checkInEditor('pie title Pets adopted by volunteers');
+ await editPage.checkTextInView('Pets adopted by volunteers');
+ await editPage.loadSampleDiagram('Class');
+ await editPage.checkInEditor('classDiagram');
});
- test.describe('github', () => {
- test('should load diagram from gist', async ({ page }) => {
- await page.goto(
+ test.describe.skip('github', () => {
+ test('should load diagram from gist', async ({ editPage, page }) => {
+ await editPage.start(
`/edit?gist=https://gist.github.com/sidharthv96/6268a23e673a533dcb198f241fd7012a`
);
await page.getByText('History').click();
- await expect(page.locator('#view').getByText('Go shopping!!')).toBeVisible();
+ await editPage.checkTextInView('Go shopping!!');
await expect(page.getByText('Revisions')).toBeVisible();
await expect(page.getByText('sidharthv96 v8f8f1e2')).toBeVisible();
await expect(page.getByText('sidharthv96 v7851e19')).toBeVisible();
@@ -48,66 +45,64 @@ test.describe('Site Loads', () => {
expect(codeStore).toBeTruthy();
});
- test('should load diagram from gist revision', async ({ page }) => {
- await page.goto(
+ test('should load diagram from gist revision', async ({ editPage, page }) => {
+ await editPage.start(
'/edit?gist=https://gist.github.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/ec9b4ab0e41e4ff6287326cd3cb47affd7851e19'
);
await page.getByText('History').click();
- await expect(page.locator('#view').getByText('Party')).toBeVisible();
+ await editPage.checkTextInView('Party');
await expect(page.getByText('Revisions')).toBeVisible();
await expect(page.getByText('sidharthv96 v7851e19')).toBeVisible();
const codeStore = await page.evaluate(() => localStorage.getItem('codeStore'));
expect(codeStore).toBeTruthy();
});
- test('should load diagram from raw files', async ({ page }) => {
- await page.goto(
+ test('should load diagram from raw files', async ({ editPage, page }) => {
+ await editPage.start(
'/edit?code=https://gist.githubusercontent.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/raw/4eb03887e6a41397e80bdcdbf94017c498f8f1e2/code.mmd&config=https://gist.githubusercontent.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/raw/4eb03887e6a41397e80bdcdbf94017c498f8f1e2/config.json'
);
- await expect(page.locator('#view').getByText('Party')).toBeVisible();
+ await editPage.checkTextInView('Party');
const codeStore = await page.evaluate(() => localStorage.getItem('codeStore'));
expect(codeStore).toBeTruthy();
});
});
- test('should show troubleshooting steps if loading fails', async ({ page }) => {
- await page.goto('/#/edit/eyJjb2RlIjoiZ3JhcGggVERcbiAg');
+ test('should show troubleshooting steps if loading fails', async ({ editPage, page }) => {
+ await editPage.start('/#/edit/eyJjb2RlIjoiZ3JhcGggVERcbiAg');
await page.reload({ waitUntil: 'networkidle' });
- await expect(
- page.locator('#view').getByText('Please Click here to Raise an issue in github.')
- ).toBeVisible();
+ await editPage.checkTextInView('Please Click here to Raise an issue in github.');
});
});
test.describe('Verify types of URLs', () => {
- test('should load compressed URL', async ({ page }) => {
- await page.goto(
+ test('should load compressed URL', async ({ editPage }) => {
+ await editPage.start(
'/edit#pako:eNpVkM2KwkAQhF-l6dMK5gVyEDRxvYi7sF6WjIcm0zqDzg_jBJEk725Hd2G3Tw31VVFUj23QjCWeEkUD-1p5kFs2O77BN1M6QFEshg1ncMHzfYDV2ybA1YQYrT_NXvhqgqDqtxPGkI315_ElVU__h-cB6mZLMYd4-Kvsb2GAdWM_jcT_V0xicb03RyqPVLSUoJI-OEfHyZHV0rqfDAqzYccKS3k1pbNC5Ufhuqgp81rbHBJKxuXKc6Quh6-7b7HMqeNfqLYkC7gfanwAlW1ZvQ'
);
- await expect(page.locator('#view').getByText('New Year')).toBeVisible();
- await page.goto(
+ await editPage.checkTextInView('New Year');
+ await editPage.start(
'/edit#pako:eNptkU1PwzAMhv9K5BOI9Q9EXBDbJA477YYqITcxndV8QD40weh_Jy1rGR0-OY_tV2_sEyivCSQogzGuGduAtnaixINji0bcf1WVWGfVXdMtx8M1faYm4B8sxR27JLClJd6nwK4VLTlN4bI4jMQd2pLe3C4KFhNNcLQ92jv9ADGLNoTdozc-zIV4ZDsNlud7RtVN7_5Sb_jYrFcN3iN_0pPbEqUZK3QbTP_Ojyv4NdR4bwTHlyMbPcOQ3WJ2CliBpWCRdbnLqFJDOpClGmRJNYauhtr1pS-_6bKMjebkA8hXNJFWgDn5_YdTIFPINDWdb3vu6r8BaWOZRQ'
);
- await expect(page.locator('#view').getByText('Animal')).toBeVisible();
+ await editPage.checkTextInView('Animal');
});
test.describe('Uncompressed URLs', () => {
- test('should load URL without specifier', async ({ page }) => {
- await page.goto(
+ test('should load URL without specifier', async ({ editPage }) => {
+ await editPage.start(
'/edit/#eyJjb2RlIjoiZ3JhcGhcbiAgICBUZXN0TGFiZWwiLCJtZXJtYWlkIjoie1xuICBcInRoZW1lXCI6IFwiZGVmYXVsdFwiXG59IiwidXBkYXRlRWRpdG9yIjpmYWxzZSwiYXV0b1N5bmMiOnRydWUsInVwZGF0ZURpYWdyYW0iOmZhbHNlfQ'
);
- await expect(page.locator('#view').getByText('TestLabel')).toBeVisible();
- await page.goto(
+ await editPage.checkTextInView('TestLabel');
+ await editPage.start(
'/edit#eyJjb2RlIjoiY2xhc3NEaWFncmFtXG4gICAgQW5pbWFsIDx8LS0gRHVja1xuICAgIEFuaW1hbCA8fC0tIEZpc2hcbiAgICBBbmltYWwgPHwtLSBaZWJyYVxuICAgIEFuaW1hbCA6ICtpbnQgYWdlXG4gICAgQW5pbWFsIDogK1N0cmluZyBnZW5kZXJcbiAgICBBbmltYWw6ICtpc01hbW1hbCgpXG4gICAgQW5pbWFsOiArbWF0ZSgpXG4gICAgY2xhc3MgRHVja3tcbiAgICAgICtTdHJpbmcgYmVha0NvbG9yXG4gICAgICArc3dpbSgpXG4gICAgICArcXVhY2soKVxuICAgIH1cbiAgICBjbGFzcyBGaXNoe1xuICAgICAgLWludCBzaXplSW5GZWV0XG4gICAgICAtY2FuRWF0KClcbiAgICB9XG4gICAgY2xhc3MgWmVicmF7XG4gICAgICArYm9vbCBpc193aWxkXG4gICAgICArcnVuKClcbiAgICB9XG4gICAgICAgICAgICAiLCJtZXJtYWlkIjoie1xuICBcInRoZW1lXCI6IFwiZGFya1wiXG59IiwidXBkYXRlRWRpdG9yIjpmYWxzZSwiYXV0b1N5bmMiOnRydWUsInVwZGF0ZURpYWdyYW0iOmZhbHNlfQ'
);
- await expect(page.locator('#view').getByText('Animal')).toBeVisible();
+ await editPage.checkTextInView('Animal');
});
- test('should load URL with "base64" specifier', async ({ page }) => {
- await page.goto(
+ test('should load URL with "base64" specifier', async ({ editPage }) => {
+ await editPage.start(
'/edit/#base64:eyJjb2RlIjoiZ3JhcGhcbiAgICBUZXN0TGFiZWwiLCJtZXJtYWlkIjoie1xuICBcInRoZW1lXCI6IFwiZGVmYXVsdFwiXG59IiwidXBkYXRlRWRpdG9yIjpmYWxzZSwiYXV0b1N5bmMiOnRydWUsInVwZGF0ZURpYWdyYW0iOmZhbHNlfQ'
);
- await expect(page.locator('#view').getByText('TestLabel')).toBeVisible();
+ await editPage.checkTextInView('TestLabel');
});
});
});
diff --git a/tests/test.ts b/tests/test.ts
new file mode 100644
index 00000000..3c4fec2c
--- /dev/null
+++ b/tests/test.ts
@@ -0,0 +1,117 @@
+import { TID } from '$/constants';
+import { test as base, expect, type Locator, type Page } from '@playwright/test';
+import { verifyFileSizeGreaterThan, type EditorOptions } from './utils';
+
+export class EditorPage {
+ readonly editor: Locator;
+ readonly markdownInput: Locator;
+ readonly view: Locator;
+
+ constructor(readonly page: Page) {
+ this.editor = page.locator('css=.monaco-editor');
+ this.markdownInput = page.getByTestId(TID.copyMarkdown);
+ this.view = page.locator('#view');
+ }
+
+ async start(url = '/edit') {
+ await this.page.goto(url);
+ await expect(this.page)
+ .toHaveURL(/.*\/edit#pako/)
+ .catch(() => {});
+ }
+
+ async typeInEditor(text: string, { bottom = true, newline = false }: EditorOptions = {}) {
+ await this.editor.click();
+ if (bottom) {
+ await this.page.keyboard.press('PageDown');
+ }
+ if (newline) {
+ await this.page.keyboard.press('Enter');
+ }
+ await this.page.keyboard.type(text, { delay: 10 });
+ }
+
+ async clearEditor() {
+ await this.editor.click();
+ await this.page.keyboard.press('Control+KeyA');
+ await this.page.keyboard.press('Backspace');
+ }
+
+ async toggleActions() {
+ await this.page.getByText('Actions', { exact: true }).click();
+ }
+
+ async toggleSampleDiagrams() {
+ await this.page.getByText('Sample Diagrams', { exact: true }).click();
+ }
+
+ async checkAndDownloadPNG(expectedSize: number) {
+ const downloadPNGPromise = verifyFileSizeGreaterThan(this.page, 'diagram', 'png', expectedSize);
+ await this.page.getByTestId(TID.downloadPNG).click();
+ return await downloadPNGPromise;
+ }
+
+ async downloadSVG(expectedSize: number) {
+ const downloadSVGPromise = verifyFileSizeGreaterThan(this.page, 'diagram', 'svg', expectedSize);
+ await this.page.getByTestId(TID.downloadSVG).click();
+ return await downloadSVGPromise;
+ }
+
+ async loadSampleDiagram(diagramName: string) {
+ await this.page.getByText(diagramName, { exact: true }).click();
+ }
+
+ async checkTextInView(text: string) {
+ await expect(this.view).toContainText(text);
+ }
+
+ async checkTextNotInView(text: string) {
+ await expect(this.view).not.toContainText(text);
+ }
+
+ async checkError(text: string) {
+ await expect(this.page.getByTestId(TID.errorContainer)).toContainText(text);
+ }
+
+ async checkInEditor(text: string) {
+ await expect(this.editor).toContainText(text);
+ }
+
+ async toggleComment(text: string) {
+ await this.editor.getByText(text).click();
+ await this.page.keyboard.press('Control+/');
+ }
+
+ async setEditorMode(mode: 'Code' | 'Config') {
+ await this.page.getByRole('tab').getByText(mode).click();
+ }
+
+ async checkDocURL(url: string | RegExp) {
+ await expect(this.page.getByTestId(TID.diagramDocumentationButton)).toHaveAttribute(
+ 'href',
+ url
+ );
+ }
+
+ async toggleTheme() {
+ await this.page.getByTestId(TID.themeToggleButton).click();
+ }
+
+ async checkTheme(theme: 'light' | 'dark') {
+ await expect(this.page.getByTestId(TID.themeToggleButton)).toHaveAttribute(
+ 'title',
+ `Switch to ${theme === 'light' ? 'dark' : 'light'} theme`
+ );
+ }
+}
+
+export const test = base.extend<{ editPage: EditorPage }>({
+ editPage: async ({ page }, use) => {
+ const editorPage = new EditorPage(page);
+ await editorPage.start();
+ await editorPage.toggleSampleDiagrams();
+ await use(editorPage);
+ }
+});
+
+export { expect } from '@playwright/test';
diff --git a/tests/themes.spec.ts b/tests/themes.spec.ts
index 55c7f905..42c3774c 100644
--- a/tests/themes.spec.ts
+++ b/tests/themes.spec.ts
@@ -1,46 +1,26 @@
-import { expect, test } from '@playwright/test';
+import { test } from './test';
test.describe('Test themes', () => {
test.describe('Test color scheme preference', () => {
- test('should respect prefers-color-scheme light', async ({ page }) => {
+ test('should respect prefers-color-scheme light', async ({ page, editPage }) => {
// Test light mode
await page.emulateMedia({ colorScheme: 'light' });
- await page.goto('/edit');
- await expect(page.getByText('light').locator('..')).toHaveClass(/bordered/);
- await expect(page.getByText('dark').locator('..')).not.toHaveClass(/bordered/);
+ await editPage.start();
+ await editPage.checkTheme('light');
});
- test('should respect prefers-color-scheme dark', async ({ page }) => {
+ test('should respect prefers-color-scheme dark', async ({ page, editPage }) => {
// Test dark mode
await page.emulateMedia({ colorScheme: 'dark' });
- await page.goto('/edit');
- await expect(page.getByText('dark').locator('..')).toHaveClass(/bordered/);
- await expect(page.getByText('light').locator('..')).not.toHaveClass(/bordered/);
- });
- });
-
- test.describe('Test light themes', () => {
- test.beforeEach(async ({ page }) => {
- await page.setViewportSize({ width: 1440, height: 768 });
- await page.goto('/edit');
- await page.getByText('Theme').click();
+ await editPage.start();
+ await editPage.checkTheme('dark');
});
- test('should set light theme as default', async ({ page }) => {
- await expect(page.getByText('light').locator('..')).toHaveClass(/bordered/);
- await expect(page.getByText('dark').locator('..')).not.toHaveClass(/bordered/);
- const themeStore = await page.evaluate(() => localStorage.getItem('themeStore'));
- expect(themeStore).toBeTruthy();
- });
-
- test('should change themes when clicked', async ({ page }) => {
- await expect(page.getByText('light').locator('..')).toHaveClass(/bordered/);
- await page.getByText('cupcake').click();
- await expect(page.getByText('cupcake').locator('..')).toHaveClass(/bordered/);
- await expect(page.getByText('light').locator('..')).not.toHaveClass(/bordered/);
- await expect(page.getByText('dark').locator('..')).not.toHaveClass(/bordered/);
- const themeStore = await page.evaluate(() => localStorage.getItem('themeStore'));
- expect(themeStore).toBeTruthy();
+ test('should change themes when clicked', async ({ editPage }) => {
+ await editPage.toggleTheme();
+ await editPage.checkTheme('dark');
+ await editPage.toggleTheme();
+ await editPage.checkTheme('light');
});
});
});
diff --git a/tests/utils.ts b/tests/utils.ts
index 0c4864c1..f2842c9a 100644
--- a/tests/utils.ts
+++ b/tests/utils.ts
@@ -21,8 +21,8 @@ export async function typeInEditor(
text: string,
{ bottom = true, newline = false }: EditorOptions = {}
) {
- await page.locator('#editor').click();
- const editor = page.locator('#editor');
+ const editor = page.locator('css=.monaco-editor');
+ await editor.click();
if (bottom) {
await editor.locator('textarea').press('PageDown');
diff --git a/vite.config.js b/vite.config.js
index 2aabcaa0..55510981 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,11 +1,32 @@
import { sveltekit } from '@sveltejs/kit/vite';
-import { svelteTesting } from '@testing-library/svelte/vite';
+import { FileSystemIconLoader } from 'unplugin-icons/loaders';
+import Icons from 'unplugin-icons/vite';
import { defineConfig } from 'vitest/config';
+/**
+ * HMR creates state inconsistencies, so we always reload the page.
+ * @type {import('vite').PluginOption} PluginOption
+ */
+const alwaysFullReload = {
+ name: 'always-full-reload',
+ handleHotUpdate({ server }) {
+ server.ws.send({ type: 'full-reload' });
+ return [];
+ }
+};
+
export default defineConfig({
- plugins: [sveltekit(), svelteTesting()],
+ plugins: [
+ sveltekit(),
+ Icons({
+ compiler: 'svelte',
+ customCollections: {
+ custom: FileSystemIconLoader('./static/icons')
+ }
+ }),
+ alwaysFullReload
+ ],
envPrefix: 'MERMAID_',
- optimizeDeps: { include: ['mermaid'] },
server: {
port: 3000,
host: true