Compare commits

..
9 Commits
Author SHA1 Message Date
github-actions 9a59fc4fe5 Release Beta 2021-01-14 12:45:04 +00:00
Sidharth Vinod 82e17f41aa Beta Deploy 2021-01-14 18:13:51 +05:30
github-actions 84c6a85545 Release 2021-01-14 12:29:56 +00:00
Sidharth Vinod 3bd5a640ec Try add 2021-01-14 17:58:49 +05:30
Sidharth Vinod 1cb42e0a88 Test build 2021-01-14 17:56:31 +05:30
Sidharth Vinod da53dab766 CI Test 2021-01-14 17:54:12 +05:30
Sidharth Vinod 9396ed376b Fix git 2021-01-14 17:50:46 +05:30
Sidharth Vinod 4bc257bace Fix install 2021-01-14 17:48:56 +05:30
Sidharth Vinod 395ea5b7f1 Beta CI Test 2021-01-14 17:47:36 +05:30
119 changed files with 12067 additions and 6751 deletions
-5
View File
@@ -1,5 +0,0 @@
**/node_modules
**/.git
**/.svelte-kit
**/dist
**/docs
-50
View File
@@ -1,50 +0,0 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
// 'plugin:@typescript-eslint/recommended-requiring-type-checking',
'prettier'
],
plugins: ['svelte3', 'tailwindcss', '@typescript-eslint', 'es'],
ignorePatterns: [
'docs/*',
'*.cjs',
'*.js',
'*.md',
'snapshots.js',
'svelte.config.js',
'renovate.json',
'package.json',
'tsconfig.json'
],
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
settings: {
'svelte3/typescript': () => require('typescript')
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2019,
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
extraFileExtensions: ['.svelte'],
allowAutomaticSingleRunInference: true
},
env: {
browser: true,
es2017: true,
node: true
},
rules: {
'@typescript-eslint/ban-ts-comment': [
'error',
{
'ts-ignore': 'allow-with-description'
}
],
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'es/no-regexp-lookbehind-assertions': 'error'
}
};
-41
View File
@@ -1,41 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Link to Live Editor:
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
-19
View File
@@ -1,19 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
-7
View File
@@ -1,7 +0,0 @@
name: 'Mermaid CodeQL config'
paths:
- src
paths-ignore:
- node_modules
- '**/*.spec.js'
-23
View File
@@ -1,23 +0,0 @@
version: 2
updates:
- package-ecosystem: docker
directory: /
target-branch: develop
schedule:
interval: weekly
day: friday
time: '07:00'
- package-ecosystem: github-actions
directory: /
target-branch: develop
schedule:
interval: weekly
day: friday
time: '07:00'
- package-ecosystem: npm
directory: /
target-branch: develop
schedule:
interval: weekly
day: friday
time: '07:00'
+34
View File
@@ -0,0 +1,34 @@
name: Beta Deploy
on:
push:
branches:
- none
# - citest
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout docs repo
uses: actions/checkout@v2
with:
repository: mermaid-js/docs
path: docs
- name: Checkout
uses: actions/checkout@v2
with:
path: main
- uses: actions/setup-node@v2
with:
node-version: '12'
- run: |
cd main
npm i -g yarn
yarn install
yarn release-beta
git config user.name github-actions
git config user.email github-actions@github.com
+29 -30
View File
@@ -9,14 +9,14 @@
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: 'CodeQL'
name: "CodeQL"
on:
push:
branches: [master]
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
branches: [ master ]
schedule:
- cron: '36 1 * * 2'
@@ -28,41 +28,40 @@ jobs:
strategy:
fail-fast: false
matrix:
language: ['javascript']
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
config-file: ./.github/codeql/config.yml
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
# - name: Autobuild
# uses: github/codeql-action/autobuild@v1
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
-49
View File
@@ -1,49 +0,0 @@
name: Cypress Tests
on:
pull_request:
branches:
- master
- develop
jobs:
cypress-run:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: actions/cache@v3
id: yarn-and-build-cache
with:
path: |
~/.cache/Cypress
build
node_modules
key: ${{ runner.os }}-node_modules-build-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node_modules-build-
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'yarn'
- name: Build & Lint
run: |
yarn install
yarn build
yarn lint
# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@v3
with:
start: yarn preview
wait-on: 'http://localhost:3000'
record: true
headless: true
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
+28
View File
@@ -0,0 +1,28 @@
name: Deploy Live Editor Beta
on:
push:
branches:
- master
- citest
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v2
with:
node-version: '12'
- name: Checkout
uses: actions/checkout@v2
- name: Build & Deploy
run: |
npm i -g yarn
yarn install
yarn release-beta
git config user.name github-actions
git config user.email github-actions@github.com
git add ./docs/*
git commit -m "Release Beta"
git push
+14 -25
View File
@@ -2,37 +2,26 @@ name: Deploy Live Editor
on:
push:
branches:
- master
pull_request:
branches:
- master
- develop
- release
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/setup-node@v2
with:
node-version: '16'
cache: yarn
node-version: '12'
- name: Checkout
uses: actions/checkout@v2
- name: Build & Deploy
run: |
export DEPLOY=true
[ "$GITHUB_EVENT_NAME" != "pull_request" ] && rm -rf docs/_app/
npm i -g yarn
yarn install
version=$(yarn version --patch --no-git-tag-version | grep "New version" | cut -d':' -f 2)
yarn build
yarn run lint
cd ..
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
if: ${{ github.ref == 'refs/heads/master' }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs
keep_files: true
yarn release
git config user.name github-actions
git config user.email github-actions@github.com
git add ./docs/*
git commit -m "Release"
git push
-69
View File
@@ -1,69 +0,0 @@
name: Docker
on:
push:
# Publish `master` as Docker `latest` image.
branches:
- master
# Publish `v1.2.3` tags as releases.
tags:
- v*
# Run tests for all PRs to master and develop.
pull_request:
branches:
- master
- develop
env:
IMAGE_NAME: mermaid-live-editor
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tests
run: |
docker build . --file Dockerfile
push:
# Ensure test job passes before pushing image.
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v3
- name: Build image
run: docker build . --file Dockerfile --tag $IMAGE_NAME
- name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Push image
run: |
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
# Change all uppercase to lowercase
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
# Strip git ref prefix from version
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
# Strip "v" prefix from tag name
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
# Use Docker `latest` tag convention
[ "$VERSION" == "master" ] && VERSION=latest
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION
-20
View File
@@ -1,20 +0,0 @@
name: Mark stale issues and pull requests
on:
schedule:
- cron: '0 0 * * 4'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
exempt-issue-labels: 'retained'
exempt-pr-labels: 'retained'
stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 14 days'
stale-pr-message: 'This pr is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 14 days'
days-before-stale: 90
days-before-close: 14
days-before-pr-close: -1
-19
View File
@@ -1,19 +0,0 @@
name: Update Browserslist
on:
workflow_dispatch:
push:
branches:
- develop
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npx browserslist@latest --update-db
- name: Commit changes
uses: EndBug/add-and-commit@v9
with:
author_name: ${{ github.actor }}
author_email: ${{ github.actor }}@users.noreply.github.com
message: 'Update Browserslist'
@@ -1,19 +0,0 @@
name: Update Monaco-editor
on:
workflow_dispatch:
push:
branches:
- 'dependabot/npm_and_yarn/develop/monaco-editor-**'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: ./bin/update-monaco
- name: Commit changes
uses: EndBug/add-and-commit@v9
with:
author_name: ${{ github.actor }}
author_email: ${{ github.actor }}@users.noreply.github.com
message: 'Update Monaco-editor'
-7
View File
@@ -4,10 +4,3 @@ coverage/
build/
yarn-error.log
.npmrc
.DS_Store
/.svelte-kit
/build
/functions
/snapshots.js
/cypress/videos
/cypress/screenshots
-1
View File
@@ -1 +0,0 @@
_
-4
View File
@@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn pre-commit
View File
-1
View File
@@ -1 +0,0 @@
engine-strict=true
BIN
View File
Binary file not shown.
+1 -6
View File
@@ -1,6 +1 @@
docs/**
.svelte-kit/**
static/**
build/**
node_modules/**
snapshots.js
docs
+2 -7
View File
@@ -1,8 +1,3 @@
{
"singleQuote": true,
"svelteSortOrder": "options-scripts-markup-styles",
"bracketSameLine": true,
"useTabs": true,
"trailingComma": "none",
"printWidth": 100
}
"singleQuote": true
}
-4
View File
@@ -1,4 +0,0 @@
{
"editor.formatOnSave": true,
"cSpell.words": ["pako", "Serde", "serdes"]
}
+3 -2
View File
@@ -6,12 +6,13 @@
# Stop : press ctrl + c
# or
# docker stop mermaid-live-editor
FROM node:18.3.0 as mermaid-live-editor-builder
FROM node:13.6.0-alpine as mermaid-live-editor-builder
COPY --chown=node:node . /home
WORKDIR /home
RUN yarn install
RUN yarn build
FROM nginx:alpine as mermaid-live-editor-runner
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=mermaid-live-editor-builder --chown=nginx:nginx /home/docs /usr/share/nginx/html
-8
View File
@@ -1,8 +0,0 @@
FROM node:18
WORKDIR /app
COPY package.json .
COPY yarn.lock .
RUN npm install
COPY . .
RUN ls
CMD ["yarn", "dev"]
+27 -37
View File
@@ -1,6 +1,6 @@
[![Mermaid Live Editor](https://img.shields.io/endpoint?url=https://dashboard.cypress.io/badge/detailed/2ckppp/master&style=flat&logo=cypress)](https://dashboard.cypress.io/projects/2ckppp/runs) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE)[![Netlify Status](https://api.netlify.com/api/v1/badges/27fa023d-7c73-4a3f-9791-b3b657a47100/deploy-status)](https://app.netlify.com/sites/mermaidjs/deploys)
[![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE)
# Contributors are welcome!
# Contributors are welcome
If you want to speed up the progress for mermaid-live-editor, join the slack channel and contact knsv.
@@ -8,6 +8,7 @@ If you want to speed up the progress for mermaid-live-editor, join the slack cha
Edit, preview and share mermaid charts/diagrams.
## Features
- Edit and preview flowcharts, sequence diagrams, gantt diagrams in real time.
@@ -15,57 +16,46 @@ Edit, preview and share mermaid charts/diagrams.
- Get a link to a viewer of the diagram so that you can share it with others.
- Get a link to edit the diagram so that someone else can tweak it and send a new link back
## Live demo
You can try out a live version [here](https://mermaid.live/).
## Docker
### Run published image
```bash
docker run --publish 8000:80 ghcr.io/mermaid-js/mermaid-live-editor
```
### To configure renderer URL
When building, Set the Environment variable MERMAID_RENDERER_URL to the rendering service.
Default is `https://mermaid.ink`
### To configure Kroki Instance URL
When building, Set the Environment variable MERMAID_KROKI_RENDERER_URL to your Kroki instance.
Default is `https://kroki.io`
### Development
```bash
docker compose up --build
```
Then open http://localhost:8000
You can try out a live version [here](https://mermaid-js.github.io/mermaid-live-editor/).
## Setup
[Volta](https://volta.sh) is used for managing node and yarn versions.
This project is set up using [Yarn](https://classic.yarnpkg.com/en/docs/getting-started):
```
yarn install
```
or in develop branch to use the beta version of mermaid use
```
yarn install --update-checksums
```
This together with a .npmrc file:
```
registry=https://registry.npmjs.com/
@mermaid-js:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken="XXXXXXXXX"
```
## Development
```
yarn dev -- --open
yarn dev
open http://localhost:8080
```
This app is created with Svelte Kit.
This app is created with Svelte + svelte-spa-router.
## Release
When a PR is created targeting master, it will be built and deployed by Netlify.
The URL will be indicated in a Comment in the PR.
Once the PR is merged, it will automatically be released.
```
yarn release
```
-13
View File
@@ -1,13 +0,0 @@
# Security Policy
## Supported Versions
Live editor only has a single version, which will be maintained.
| Version | Supported |
| ------- | ------------------ |
| latest | :white_check_mark: |
## Reporting a Vulnerability
To report a vulnerability, please e-mail security@mermaid.live with a description of the issue, the steps you took to create the issue, affected versions, and if known, mitigations for the issue.
+2 -9
View File
@@ -4,18 +4,11 @@
# git clone git@github.com:mermaid-js/docs.git
# git clone https://github.com/mermaid-js/docs.git
set -e
rm -rf docs
yarn release
pushd .
if [ ! -d ../docs ]; then
echo "Clone https://github.com/mermaid-js/docs to parent folder before continuing."
exit
fi
rm -rf ../docs/mermaid-live-editor-beta/*
cp -r docs/* ../docs/mermaid-live-editor-beta
cd ../docs
git add .
git commit -m "Beta updated with latest changes"
git commit * -m "Beta updated with latest changes"
git push
popd
-6
View File
@@ -1,6 +0,0 @@
#!/bin/bash
pushd ../docs
# find ./ -type f -exec sed -i 's/service-worker.js/mermaid-live-editor\/service-worker.js/g' {} \;
find ./ -type f -exec sed -i 's/manifest.json/\/mermaid-live-editor\/manifest.json/g' {} \;
find ./ -type f -exec sed -i 's/favicon.png/\/mermaid-live-editor\/favicon.png/g' {} \;
popd
-12
View File
@@ -1,12 +0,0 @@
#!/bin/bash
monacoVersion="$(jq -r '.dependencies."monaco-editor"' package.json)"
monacoVersion="${monacoVersion:1}"
if [[ $(uname) == "Darwin" ]];
then
sed -i '' -E "s/monaco-editor\/[^/]*/monaco-editor\/$monacoVersion/g" ./src/app.html
else
sed -i'' -E "s/monaco-editor\/[^/]*/monaco-editor\/$monacoVersion/g" ./src/app.html
fi
-26
View File
@@ -1,26 +0,0 @@
import { defineConfig } from 'cypress';
import fs from 'fs';
export default defineConfig({
projectId: '2ckppp',
viewportWidth: 1440,
viewportHeight: 768,
snapshotFileName: './cypress/snapshots.js',
defaultCommandTimeout: 16000,
requestTimeout: 16000,
e2e: {
setupNodeEvents(on, config) {
on('task', {
readFileMaybe(filename) {
if (fs.existsSync(filename)) {
return fs.readFileSync(filename, 'utf8');
}
return null;
}
});
},
baseUrl: 'http://localhost:3000',
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}'
}
});
-12
View File
@@ -1,12 +0,0 @@
{
"plugins": ["cypress", "mocha"],
"extends": ["plugin:cypress/recommended", "plugin:mocha/recommended"],
"rules": {
"jest/expect-expect": "off",
"mocha/no-mocha-arrows": "off"
},
"env": {
"cypress/globals": true,
"mocha": true
}
}
-24
View File
@@ -1,24 +0,0 @@
describe('Check actions', () => {
beforeEach(() => {
cy.clearLocalStorage();
cy.visit('/edit');
});
it('should update markdown code', () => {
cy.get('#markdown')
.invoke('val')
.then((oldText) => {
cy.get('#editor').click('bottom').type('{enter}C --> HistoryTest');
cy.get('#markdown')
.invoke('val')
.then((newText) => {
expect(oldText).to.not.eq(newText);
});
});
});
it('should load gists from URL', () => {
cy.get('#gist').type('https://gist.github.com/sidharthv96/6268a23e673a533dcb198f241fd7012a');
cy.contains('Load Gist').click();
cy.contains('Go shopping!!');
});
});
-43
View File
@@ -1,43 +0,0 @@
describe('Auto sync tests', () => {
beforeEach(() => {
cy.clearLocalStorage();
cy.visit('/');
});
it('should dim diagram when code is edited', () => {
cy.contains('Auto sync').click();
cy.get('#view').should('not.have.class', 'outOfSync');
cy.get('#editor').type(' C --> Test');
cy.get('#view').should('have.class', 'outOfSync');
cy.getLocalStorage('codeStore').snapshot();
});
it('should show/hide sync button with auto sync', () => {
cy.get('[data-cy=sync]').should('not.exist');
cy.contains('Auto sync').click();
cy.get('[data-cy=sync]').should('exist');
cy.get('#autoSync').check();
cy.get('[data-cy=sync]').should('not.exist');
});
it('should not dim diagram when code is in sync', () => {
cy.contains('Auto sync').click();
cy.get('#view').should('not.have.class', 'outOfSync');
cy.get('#editor').type(' C --> Test');
cy.get('#view').should('have.class', 'outOfSync');
cy.get('[data-cy=sync]').click();
cy.get('#view').should('not.have.class', 'outOfSync');
cy.get('#autoSync').check();
cy.get('#editor').type('ing');
cy.get('#view').should('not.have.class', 'outOfSync');
cy.getLocalStorage('codeStore').snapshot();
});
it('supports commenting code out/in', () => {
const cmd = Cypress.platform === 'darwin' ? 'meta' : 'ctrl';
cy.get('#editor').type(`{uparrow}{${cmd}}/`);
cy.get('#view').contains('Car').should('not.exist');
cy.get('#editor').type(`{uparrow}{${cmd}}/`);
cy.get('#view').contains('Car').should('exist');
});
});
-36
View File
@@ -1,36 +0,0 @@
describe('Editor docs tests', () => {
beforeEach(() => {
cy.on('uncaught:exception', () => {
return false;
});
cy.clearLocalStorage();
cy.visit('/edit');
cy.contains('Sample Diagrams').click();
});
it('Test default loading', () => {
cy.get(`[data-cy=docs][href^="https://mermaid-js.github.io/mermaid"]`).should('exist');
});
it('Test to see if the correct URL loads when changing from one diagram to other', () => {
cy.contains('Flow Chart').click();
cy.get(`[data-cy=docs][href$="/#/flowchart"]`).should('exist');
cy.contains('Config').click();
cy.get(`[data-cy=docs][href$="/#/flowchart?id=configuration"]`).should('exist');
cy.contains('Sequence Diagram').click();
cy.get(`[data-cy=docs][href$="/#/sequenceDiagram?id=configuration"]`).should('exist');
cy.contains('Code').click();
cy.get(`[data-cy=docs][href$="/#/sequenceDiagram"]`).should('exist');
});
it("Test to check URLs for a case where config URL doesn't exist", () => {
cy.contains('State Diagram').click();
cy.get(`[data-cy=docs][href$="/#/stateDiagram"]`).should('exist');
cy.contains('Config').click();
cy.get(`[data-cy=docs][href$="/#/stateDiagram"]`).should('exist');
});
});
-65
View File
@@ -1,65 +0,0 @@
describe('Save History', () => {
beforeEach(() => {
cy.clock();
cy.clearLocalStorage();
cy.visit('/edit');
cy.contains('Actions').click();
cy.contains('History').click();
});
it('should save when clicked', () => {
cy.get('#historyList').find('li').should('have.length', 0);
cy.get('#historyList').contains('No items in History');
cy.get('#saveHistory').click();
cy.get('#historyList').find('No items in History').should('not.exist');
cy.get('#historyList').find('li').should('have.length', 1);
cy.get('#saveHistory').click();
cy.on('window:alert', (str) => {
expect(str).to.equal('State already saved.');
});
cy.on('window:confirm', () => true);
cy.get('#editor').type(' C --> HistoryTest');
cy.get('#saveHistory').click();
cy.get('#historyList').find('li').should('have.length', 2);
});
it('should be able to restore and delete', () => {
cy.get('#saveHistory').click();
cy.get('#editor').type(' C --> HistoryTest');
cy.get('#historyList').find('No items in History').should('not.exist');
cy.get('#historyList').find('li').should('have.length', 1);
cy.contains('HistoryTest');
cy.contains('Restore').click();
cy.contains('HistoryTest').should('not.exist');
cy.contains('Delete').click();
cy.get('#historyList').find('li').should('have.length', 0);
cy.get('#historyList').contains('No items in History');
cy.get('#saveHistory').click();
cy.get('#editor').type(' C --> HistoryTest');
cy.get('#saveHistory').click();
cy.get('#editor').type('ing');
cy.get('#clearHistory').click();
cy.on('window:alert', (str) => {
expect(str).to.equal('Clear all saved items?');
});
cy.on('window:confirm', () => true);
cy.get('#historyList').contains('No items in History');
});
// TODO: Fix #639
// eslint-disable-next-line mocha/no-skipped-tests
xit('should auto save history', () => {
cy.get('#editor').type(' C --> HistoryTest');
cy.tick(70000);
cy.contains('Timeline').click();
cy.get('#historyList').find('li').should('have.length', 1);
cy.get('#editor').type('ing');
cy.tick(70000);
cy.get('#historyList').find('li').should('have.length', 2);
for (let i = 0; i < 31; i++) {
cy.get('#editor').type('.');
cy.tick(70000);
}
cy.get('#historyList').find('li').should('have.length', 30);
});
});
-123
View File
@@ -1,123 +0,0 @@
import { toBase64 } from 'js-base64';
describe('Site Loads', () => {
beforeEach(() => {
cy.clearLocalStorage();
});
it('Check Home page load', () => {
cy.visit('/');
cy.url().should('include', '/edit');
cy.contains('History').click();
cy.getLocalStorage('codeStore').snapshot();
});
it('Check Redirect from old URL', () => {
cy.visit(
'/#/edit/eyJjb2RlIjoiZ3JhcGggVERcbiAgICBBW0NocmlzdG1hc10gLS0-fEdldCBtb25leXwgQihHbyBzaG9wcGluZylcbiAgICBCIC0tPiBDe0xldCBtZSB0aGlua31cbiAgICBDIC0tPnxPbmV8IERbTGFwdG9wXVxuICAgIEMgLS0-fFR3b3wgRVtpUGhvbmVdXG4gICAgQyAtLT58VGhyZWV8IEZbZmE6ZmEtY2FyIENhcl0iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ'
);
cy.url().should(
'include',
'/edit#pako:eNpVkM1qw0AMhF9F6NRC_AI-FBo7zSXQQHLz-iC8cnZJ9oe1TAm2373rmkKrk9B8MwyasAuascRbomjgWisPed6byiQ7iKOhhaJ4m48s4ILn5wz7l2OAwYQYrb-9bvx-haCaTivGIMb6-7JJ1Y__0_MMdXOiKCG2f5XrV5jh0NizyfH_FZM4uz6ansqeio4SVJRa3KHj5MjqXHtaDQrFsGOFZV419zQ-RKHyS0bHqEn4oK2EhDnmMfAOaZRwefoOS0kj_0K1pfwFtx2XbzAdW4g'
);
cy.contains('History').click();
cy.getLocalStorage('codeStore').snapshot();
});
it('should load sample diagrams when clicked', () => {
cy.contains('Sample Diagrams').click();
cy.contains('Pie Chart').click();
cy.contains('pie title Pets adopted by volunteers');
cy.contains('Class Diagram').click();
cy.contains('classDiagram');
});
it('should load diagram from gist', () => {
cy.visit(`/edit?gist=https://gist.github.com/sidharthv96/6268a23e673a533dcb198f241fd7012a`);
cy.contains('History').click();
cy.contains('Go shopping!!');
cy.contains('Revisions');
cy.contains('sidharthv96 v8f8f1e2');
cy.contains('sidharthv96 v7851e19');
cy.getLocalStorage('codeStore').snapshot();
});
it('should load diagram from gist revision', () => {
cy.visit(
'/edit?gist=https://gist.github.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/ec9b4ab0e41e4ff6287326cd3cb47affd7851e19'
);
cy.contains('History').click();
cy.contains('Party');
cy.contains('Revisions');
cy.contains('sidharthv96 v7851e19');
cy.getLocalStorage('codeStore').snapshot();
});
it('should load diagram from raw files', () => {
cy.visit(
'/edit?code=https://gist.githubusercontent.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/raw/4eb03887e6a41397e80bdcdbf94017c498f8f1e2/code.mmd&config=https://gist.githubusercontent.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/raw/4eb03887e6a41397e80bdcdbf94017c498f8f1e2/config.json'
);
cy.contains('Party');
cy.getLocalStorage('codeStore').snapshot();
});
// Disabled temporarily. Should be enabled after the issue is fixed in Mermaid.
// it('should prevent setting the "securityLevel" option via URL', () => {
// const b64State = toBase64(
// `{"code":"graph TD\\nA[\\"<img src='https://via.placeholder.com/64' width=64 />\\"]","mermaid":"{\\"securityLevel\\": \\"loose\\", \\"theme\\": \\"forest\\"}","updateEditor":true,"autoSync":true,"updateDiagram":true}`,
// true
// );
// cy.on('window:confirm', () => true);
// cy.visit(`/edit#${b64State}`);
// cy.contains('Config').click();
// cy.contains('forest');
// cy.contains('securityLevel').should('not.exist');
// cy.get('#view').find('img').should('not.exist');
// cy.get('#view').contains('<img');
// cy.get('#view').contains(`src='https://via.placeholder.com/64'`);
// });
it('should allow persisting "securityLevel" using confirm dialogue', () => {
const b64State = toBase64(
`{"code":"graph TD\\nA[\\"<img src='https://via.placeholder.com/64' width=64/>\\"]","mermaid":"{\\"securityLevel\\": \\"loose\\", \\"theme\\": \\"forest\\"}","updateEditor":true,"autoSync":true,"updateDiagram":true}`,
true
);
cy.on('window:confirm', () => false);
cy.visit(`/edit#${b64State}`);
cy.contains('Config').click();
cy.contains('forest');
cy.contains('securityLevel');
cy.get('#view').find('img').should('be.visible');
});
it('should show troubleshooting steps if loading fails', () => {
cy.visit('/#/edit/eyJjb2RlIjoiZ3JhcGggVERcbiAg');
cy.contains('Please Click here to Raise an issue in github.');
});
it('should load uncompressed URL', () => {
cy.visit(
'/edit/#eyJjb2RlIjoiZ3JhcGggVERcbiAgICBBW05ldyBZZWFyXSAtLT58R2V0IG1vbmV5fCBCKEdvIHNob3BwaW5nKVxuICAgIEIgLS0-IEN7TGV0IG1lIHRoaW5rfVxuICAgIEMgLS0-fE9uZXwgRFtMYXB0b3BdXG4gICAgQyAtLT58VHdvfCBFW2lQaG9uZV1cbiAgICBDIC0tPnxUaHJlZXwgRltmYTpmYS1jYXIgQ2FyXSIsIm1lcm1haWQiOiJ7XG4gIFwidGhlbWVcIjogXCJkZWZhdWx0XCJcbn0iLCJ1cGRhdGVFZGl0b3IiOmZhbHNlLCJhdXRvU3luYyI6dHJ1ZSwidXBkYXRlRGlhZ3JhbSI6ZmFsc2V9'
);
cy.contains('New Year');
cy.visit(
'/edit#eyJjb2RlIjoiY2xhc3NEaWFncmFtXG4gICAgQW5pbWFsIDx8LS0gRHVja1xuICAgIEFuaW1hbCA8fC0tIEZpc2hcbiAgICBBbmltYWwgPHwtLSBaZWJyYVxuICAgIEFuaW1hbCA6ICtpbnQgYWdlXG4gICAgQW5pbWFsIDogK1N0cmluZyBnZW5kZXJcbiAgICBBbmltYWw6ICtpc01hbW1hbCgpXG4gICAgQW5pbWFsOiArbWF0ZSgpXG4gICAgY2xhc3MgRHVja3tcbiAgICAgICtTdHJpbmcgYmVha0NvbG9yXG4gICAgICArc3dpbSgpXG4gICAgICArcXVhY2soKVxuICAgIH1cbiAgICBjbGFzcyBGaXNoe1xuICAgICAgLWludCBzaXplSW5GZWV0XG4gICAgICAtY2FuRWF0KClcbiAgICB9XG4gICAgY2xhc3MgWmVicmF7XG4gICAgICArYm9vbCBpc193aWxkXG4gICAgICArcnVuKClcbiAgICB9XG4gICAgICAgICAgICAiLCJtZXJtYWlkIjoie1xuICBcInRoZW1lXCI6IFwiZGFya1wiXG59IiwidXBkYXRlRWRpdG9yIjpmYWxzZSwiYXV0b1N5bmMiOnRydWUsInVwZGF0ZURpYWdyYW0iOmZhbHNlfQ'
);
cy.contains('Animal');
cy.visit(
'/edit/#base64:eyJjb2RlIjoiZ3JhcGggVERcbiAgICBBW05ldyBZZWFyXSAtLT58R2V0IG1vbmV5fCBCKEdvIHNob3BwaW5nKVxuICAgIEIgLS0-IEN7TGV0IG1lIHRoaW5rfVxuICAgIEMgLS0-fE9uZXwgRFtMYXB0b3BdXG4gICAgQyAtLT58VHdvfCBFW2lQaG9uZV1cbiAgICBDIC0tPnxUaHJlZXwgRltmYTpmYS1jYXIgQ2FyXSIsIm1lcm1haWQiOiJ7XG4gIFwidGhlbWVcIjogXCJkZWZhdWx0XCJcbn0iLCJ1cGRhdGVFZGl0b3IiOmZhbHNlLCJhdXRvU3luYyI6dHJ1ZSwidXBkYXRlRGlhZ3JhbSI6ZmFsc2V9'
);
cy.contains('New Year');
});
it('should load compressed URL', () => {
cy.visit(
'/edit#pako:eNpVkM2KwkAQhF-l6dMK5gVyEDRxvYi7sF6WjIcm0zqDzg_jBJEk725Hd2G3Tw31VVFUj23QjCWeEkUD-1p5kFs2O77BN1M6QFEshg1ncMHzfYDV2ybA1YQYrT_NXvhqgqDqtxPGkI315_ElVU__h-cB6mZLMYd4-Kvsb2GAdWM_jcT_V0xicb03RyqPVLSUoJI-OEfHyZHV0rqfDAqzYccKS3k1pbNC5Ufhuqgp81rbHBJKxuXKc6Quh6-7b7HMqeNfqLYkC7gfanwAlW1ZvQ'
);
cy.contains('New Year');
cy.visit(
'/edit#pako:eNptkU1PwzAMhv9K5BOI9Q9EXBDbJA477YYqITcxndV8QD40weh_Jy1rGR0-OY_tV2_sEyivCSQogzGuGduAtnaixINji0bcf1WVWGfVXdMtx8M1faYm4B8sxR27JLClJd6nwK4VLTlN4bI4jMQd2pLe3C4KFhNNcLQ92jv9ADGLNoTdozc-zIV4ZDsNlud7RtVN7_5Sb_jYrFcN3iN_0pPbEqUZK3QbTP_Ojyv4NdR4bwTHlyMbPcOQ3WJ2CliBpWCRdbnLqFJDOpClGmRJNYauhtr1pS-_6bKMjebkA8hXNJFWgDn5_YdTIFPINDWdb3vu6r8BaWOZRQ'
);
cy.reload();
cy.contains('Animal');
});
});
-57
View File
@@ -1,57 +0,0 @@
describe('Test themes', () => {
describe('Test light themes', () => {
beforeEach(() => {
cy.clearLocalStorage();
cy.visit('/edit', {
onBeforeLoad(win) {
cy.stub(win, 'matchMedia')
.callThrough()
.withArgs('(prefers-color-scheme: dark)')
.returns({
matches: false
});
}
});
cy.contains('Change Theme').click();
});
it('should set light theme as default', () => {
cy.contains('light').parent().should('have.class', 'bordered');
cy.contains('dark').parent().should('not.have.class', 'bordered');
cy.getLocalStorage('themeStore').snapshot();
});
it('should change themes when clicked', () => {
cy.contains('light').parent().should('have.class', 'bordered');
cy.contains('cupcake').click();
cy.contains('cupcake').parent().should('have.class', 'bordered');
cy.contains('light').parent().should('not.have.class', 'bordered');
cy.contains('dark').parent().should('not.have.class', 'bordered');
cy.getLocalStorage('themeStore').snapshot();
});
});
describe('Test dark mode', () => {
// eslint-disable-next-line mocha/no-hooks-for-single-case
beforeEach(() => {
cy.clearLocalStorage();
cy.visit('/edit', {
onBeforeLoad(win) {
cy.stub(win, 'matchMedia')
.callThrough()
.withArgs('(prefers-color-scheme: dark)')
.returns({
matches: true
});
}
});
cy.contains('Change Theme').click();
});
it('should set dark theme as default', () => {
cy.contains('light').parent().should('not.have.class', 'bordered');
cy.contains('dark').parent().should('have.class', 'bordered');
cy.getLocalStorage('themeStore').snapshot();
});
});
});
-5
View File
@@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}
-52
View File
@@ -1,52 +0,0 @@
module.exports = {
"Site Loads": {
"Check Home page load": {
"1": "{\"code\":\"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)\\n B --> C{Let me think}\\n C -->|One| D[Laptop]\\n C -->|Two| E[iPhone]\\n C -->|Three| F[fa:fa-car Car]\\n \",\"mermaid\":\"{\\n \\\"theme\\\": \\\"default\\\"\\n}\",\"updateEditor\":true,\"autoSync\":true,\"updateDiagram\":true}"
},
"Check Redirect from old URL": {
"1": "{\"code\":\"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)\\n B --> C{Let me think}\\n C -->|One| D[Laptop]\\n C -->|Two| E[iPhone]\\n C -->|Three| F[fa:fa-car Car]\",\"mermaid\":\"{\\n \\\"theme\\\": \\\"default\\\"\\n}\",\"updateEditor\":false,\"autoSync\":true,\"updateDiagram\":true}"
},
"should load diagram from gist": {
"1": "{\"code\":\"graph TD\\n A[Party] -->|Get money| B(Go shopping!!)\\n \",\"mermaid\":\"{\\n \\\"theme\\\": \\\"forest\\\",\\n \\\"test\\\": \\\"hello world\\\"\\n}\",\"updateEditor\":false,\"autoSync\":true,\"updateDiagram\":true,\"loader\":{\"type\":\"gist\",\"config\":{\"url\":\"https://gist.github.com/sidharthv96/6268a23e673a533dcb198f241fd7012a\"}}}"
},
"should load diagram from gist revision": {
"1": "{\"code\":\"graph TD\\n A[Party] -->|Get money| B(Go shopping)\\n \",\"mermaid\":\"{\\n \\\"theme\\\": \\\"forest\\\",\\n \\\"test\\\": \\\"hello\\\"\\n}\",\"updateEditor\":false,\"autoSync\":true,\"updateDiagram\":true,\"loader\":{\"type\":\"gist\",\"config\":{\"url\":\"https://gist.github.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/ec9b4ab0e41e4ff6287326cd3cb47affd7851e19\"}}}"
},
"should load diagram from raw files": {
"1": "{\"code\":\"graph TD\\n A[Party] -->|Get money| B(Go shopping!!)\\n \",\"mermaid\":\"{\\n \\\"theme\\\": \\\"forest\\\",\\n \\\"test\\\": \\\"hello world\\\"\\n}\",\"updateEditor\":false,\"autoSync\":true,\"updateDiagram\":true,\"loader\":{\"type\":\"files\",\"config\":{\"codeURL\":\"https://gist.githubusercontent.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/raw/4eb03887e6a41397e80bdcdbf94017c498f8f1e2/code.mmd\",\"configURL\":\"https://gist.githubusercontent.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/raw/4eb03887e6a41397e80bdcdbf94017c498f8f1e2/config.json\"}}}"
}
},
"__version": "10.0.3",
"Auto sync tests": {
"should dim diagram when code is edited": {
"1": "{\"code\":\"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)\\n B --> C{Let me think}\\n C -->|One| D[Laptop]\\n C -->|Two| E[iPhone]\\n C -->|Three| F[fa:fa-car Car]\\n C --> Test\",\"mermaid\":\"{\\n \\\"theme\\\": \\\"default\\\"\\n}\",\"updateEditor\":false,\"autoSync\":false,\"updateDiagram\":false}"
},
"should not dim diagram when code is in sync": {
"1": "{\"code\":\"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)\\n B --> C{Let me think}\\n C -->|One| D[Laptop]\\n C -->|Two| E[iPhone]\\n C -->|Three| F[fa:fa-car Car]\\n C --> Testing\",\"mermaid\":\"{\\n \\\"theme\\\": \\\"default\\\"\\n}\",\"updateEditor\":false,\"autoSync\":true,\"updateDiagram\":false}"
}
},
"Test themes": {
"should set light theme as default": {
"1": "{\"theme\":\"light\",\"isDark\":false}"
},
"should change themes when clicked": {
"1": "{\"theme\":\"cupcake\",\"isDark\":false}"
},
"should set dark theme as default": {
"1": "{\"theme\":\"dark\",\"isDark\":true}"
},
"Test light themes": {
"should set light theme as default": {
"1": "{\"theme\":\"light\",\"isDark\":false}"
},
"should change themes when clicked": {
"1": "{\"theme\":\"cupcake\",\"isDark\":false}"
}
},
"Test dark mode": {
"should set dark theme as default": {
"1": "{\"theme\":\"dark\",\"isDark\":true}"
}
}
}
}
-38
View File
@@ -1,38 +0,0 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
import { register } from '@cypress/snapshot';
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
register();
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
snapshot(): void;
}
}
}
import 'cypress-localstorage-commands';
-27
View File
@@ -1,27 +0,0 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands';
// Alternatively you can use CommonJS syntax:
// require('./commands')
Cypress.on('uncaught:exception', (err) => {
/* returning false here prevents Cypress from failing the test */
if (err.message.includes('ResizeObserver loop limit exceeded')) {
return false;
}
});
-7
View File
@@ -1,7 +0,0 @@
{
"compilerOptions": {
"allowJs": true,
"types": ["cypress", "cypress-localstorage-commands"]
},
"include": ["**/*.ts"]
}
-11
View File
@@ -1,11 +0,0 @@
version: '3.7'
services:
mermaid:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- ./src:/app/src
ports:
- 8080:8080
- 24678:24678
+2481
View File
File diff suppressed because it is too large Load Diff
+419
View File
File diff suppressed because one or more lines are too long
+2481
View File
File diff suppressed because it is too large Load Diff
+419
View File
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

+134
View File
@@ -0,0 +1,134 @@
@media (prefers-color-scheme: light) {
html, body {
position: relative;
width: 100%;
height: 100%;
}
#dgraph-div {
display: none;
}
body {
color: #333;
margin: 0;
padding: 8px;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
a {
color: rgb(0,100,200);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a:visited {
color: rgb(0,80,160);
}
label {
display: block;
}
input, button, select, textarea {
font-family: inherit;
font-size: inherit;
padding: 0.4em;
margin: 0 0 0.5em 0;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 2px;
}
input:disabled {
color: #ccc;
}
input[type="range"] {
height: 0;
}
button {
background-color: #f4f4f4;
outline: none;
}
button:active {
background-color: #ddd;
}
button:focus {
border-color: #666;
}
}
body {
background-color: #3f3f3f;
color: #c8c8c8;
margin: 0;
padding: 8px;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}}
@media (prefers-color-scheme: dark) {
html, body {
position: relative;
width: 100%;
height: 100%;
a {
color: rgb(0,100,200);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a:visited {
color: rgb(0,80,160);
}
label {
display: block;
}
input, button, select, textarea {
font-family: inherit;
font-size: inherit;
padding: 0.4em;
margin: 0 0 0.5em 0;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 2px;
}
input:disabled {
color: #ccc;
}
input[type="range"] {
height: 0;
}
button {
background-color: #f4f4f4;
outline: none;
}
button:active {
background-color: #ddd;
}
button:focus {
border-color: #666;
}
}
+45
View File
@@ -0,0 +1,45 @@
<!doctype html>
<html>
<head>
<meta charset='utf8'>
<meta name='viewport' content='width=device-width'>
<title>Mermaid live editor</title>
<link rel='icon' type='image/png' href='favicon.png'>
<link rel='stylesheet' href='styles.css'>
<link rel='stylesheet' href='bundle.css'>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<!-- Google Analytics -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-153180559-1', 'auto');
</script>
<!-- End Google Analytics -->
<style>
.myInlineDecoration {
/* cursor: pointer; */
/* text-decoration: underline; */
font-weight: bold;
font-style: oblique;
background: #ffcccc;
}
#dgraph-div {
display: none;
}
</style>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>Online FlowChart &amp; Diagrams Editor - Mermaid Live Editor</title>
<meta name="og:image" content="https://mermaid-js.github.io/images/header.png">
<link rel="canonical" href="https://mermaid-js.github.io/mermaid-live-editor/">
<meta name="description" content="Simplify documentation and avoid heavy tools. Open source Visio Alternative. Commonly used for explaining your code! Mermaid is a simple markdown-like script language for generating charts from text via javascript.">
</head>
<body>
<script src='bundle.js'></script>
</body>
</html>
+43
View File
@@ -0,0 +1,43 @@
html {
padding: 0;
width: 100%;
height: 100%;
}
body {
padding: 32px;
font-family: 'trebuchet ms', verdana, arial;
position: relative;
width: 100%;
height: 100%;
box-sizing: border-box;
margin: 0;
}
/* @media (prefers-color-scheme: dark) {
body {
background-color: #3f3f3f;
color: #c8c8c8;
}
a, label {
color: #d8d8d8;
}
} */
.mermaid {
/* font-family: 'trebuchet ms', verdana, arial; */
}
.marketing-links {
font-size: 16px;
}
.ant-card {
margin-bottom: 16px;
}
i {
font-size: 10px !important;
}
.mermaidTooltip {
position: absolute;
}
-9
View File
@@ -1,9 +0,0 @@
server {
listen 80;
server_name mermaid;
location / {
root /usr/share/nginx/html;
# Fallback to index.html for other paths
try_files $uri /index.html;
}
}
+45 -76
View File
@@ -1,78 +1,47 @@
{
"name": "mermaid-live-editor",
"version": "2.0.67",
"scripts": {
"dev": "svelte-kit dev --host 0.0.0.0",
"build": "svelte-kit build",
"preview": "svelte-kit preview",
"lint": "prettier --check --plugin-search-dir=. .;eslint --ignore-path .gitignore .",
"lint:fix": "prettier --write --plugin-search-dir=. .;eslint --fix --ignore-path .gitignore .",
"format": "prettier --write --plugin-search-dir=. .",
"pre-commit": "lint-staged",
"postinstall": "husky install; cypress cache prune",
"test": "cypress run",
"cy": "cypress open"
},
"devDependencies": {
"@cypress/snapshot": "^2.1.7",
"@sveltejs/adapter-static": "1.0.0-next.34",
"@sveltejs/kit": "1.0.0-next.350",
"@types/mermaid": "^8.2.9",
"@types/pako": "^1.0.3",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"autoprefixer": "^10.4.7",
"chai": "^4.3.6",
"cssnano": "^5.1.11",
"cypress": "10.0.3",
"cypress-localstorage-commands": "^2.1.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-es": "^4.1.0",
"eslint-plugin-mocha": "^10.0.5",
"eslint-plugin-postcss-modules": "^2.0.0",
"eslint-plugin-svelte3": "^3.4.1",
"eslint-plugin-tailwindcss": "^3.5.2",
"husky": "^8.0.1",
"lint-staged": "13.0.1",
"mocha": "^10.0.0",
"node-html-parser": "^5.3.3",
"postcss": "^8.4.14",
"postcss-load-config": "^4.0.1",
"prettier": "~2.6.2",
"prettier-plugin-svelte": "^2.7.0",
"svelte": "^3.48.0",
"svelte-preprocess": "4.10.7",
"tailwindcss": "^3.1.1",
"tslib": "^2.4.0",
"typescript": "^4.7.3"
},
"type": "module",
"dependencies": {
"@analytics/google-analytics": "^0.5.3",
"@macfja/svelte-persistent-store": "^1.3.0",
"analytics": "^0.8.1",
"daisyui": "2.15.2",
"js-base64": "^3.7.2",
"mermaid": "9.1.2",
"moment": "^2.29.3",
"monaco-editor": "^0.33.0",
"monaco-mermaid": "^1.0.4",
"pako": "2.0.4",
"random-word-slugs": "^0.1.6"
},
"lint-staged": {
"*.{ts,svelte,js,css,md,json}": [
"prettier --plugin-search-dir=. --write",
"eslint --ignore-path .gitignore "
]
},
"volta": {
"node": "16.15.0",
"yarn": "1.22.10"
},
"engines": {
"node": ">=16.7"
}
"name": "mermaid-live-editor",
"version": "1.2.0",
"license": "MIT",
"devDependencies": {
"cross-env": "^5.2.0",
"css-loader": "^2.1.1",
"husky": "^4.3.0",
"lint-staged": "^10.5.2",
"mini-css-extract-plugin": "^0.6.0",
"prettier": "^2.2.1",
"prettier-plugin-svelte": "^1.4.1",
"serve": "^11.0.0",
"style-loader": "^0.23.1",
"svelte": "^3.0.0",
"svelte-loader": "2.13.3",
"svelte-watch-resize": "^1.0.3",
"webpack": "^4.30.0",
"webpack-cli": "^3.3.0",
"webpack-dev-server": "^3.3.1"
},
"scripts": {
"release": "cross-env NODE_ENV=production webpack -p",
"release-beta": "yarn release --output-path ./docs/beta",
"build": "cross-env NODE_ENV=production webpack",
"dev": "webpack-dev-server --content-base docs",
"pretty": "prettier --write ./src/*"
},
"dependencies": {
"mermaid": "8.8.4",
"js-base64": "^2.5.1",
"json-lint": "^0.1.0",
"jsonlint": "^1.6.3",
"moment": "^2.24.0",
"monaco-editor": "0.18.1",
"svelte-routing": "^1.4.0",
"svelte-spa-router": "^1.3.0"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{ts,svelte,js,css,md}": "prettier --plugin-search-dir=. --write"
}
}
-23
View File
@@ -1,23 +0,0 @@
const tailwindcss = require('tailwindcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const mode = process.env.NODE_ENV;
const dev = mode === 'development';
module.exports = {
plugins: [
// Some plugins, like postcss-nested, need to run before Tailwind
tailwindcss,
// But others, like autoprefixer, need to run after
autoprefixer,
!dev &&
cssnano({
preset: 'default'
})
]
};
@@ -1,17 +1,13 @@
## :bookmark_tabs: Summary
Brief description about the content of your PR:
Resolves #<your issue id here>
## :straight_ruler: Design Decisions
Describe the way your implementation works or what design decisions you made if applicable:
### :clipboard: Tasks
Make sure you
- [ ] :book: have read the [contribution guidelines](https://github.com/mermaid-js/mermaid/blob/master/CONTRIBUTING.md)
- [ ] :computer: have added unit/e2e tests (if appropriate)
- [ ] :bookmark: targeted `develop` branch
- [ ] :book: have read the [contribution guidelines](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md)
- [ ] :computer: have added unit/e2e tests (if appropriate)
- [ ] :bookmark: targeted `master` branch
-28
View File
@@ -1,28 +0,0 @@
{
"extends": [
"config:base",
":rebaseStalePrs",
"group:allNonMajor",
"schedule:earlyMondays",
":automergeMinor",
":automergeTesters",
":automergeLinters",
":automergeTypes",
":automergePatch"
],
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
"automerge": true
},
{
"matchDatasources": ["npm"],
"stabilityDays": 3
}
],
"dependencyDashboard": true,
"major": {
"dependencyDashboardApproval": true
},
"dependencyDashboardAutoclose": true
}
+31
View File
@@ -0,0 +1,31 @@
<script>
import Router from 'svelte-spa-router';
// Used for SSR. A falsy value is ignored by the Router.
import Edit from './routes/Edit.svelte';
import View from './routes/View.svelte';
const routes = {
// Exact path
'/': Edit,
// Using named parameters, with last being optional
'/edit/:data': Edit,
// Wildcard parameter
'/view/:data': View,
// Catch-all
// This is optional, but if present it must be the last
'*': Edit,
};
</script>
<style>
</style>
<svelte:head>
<link
href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,500;0,600;0,700;0,800;0,900;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
rel="stylesheet" />
</svelte:head>
<Router {routes} />
-47
View File
@@ -1,47 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Online FlowChart &amp; Diagrams Editor - Mermaid Live Editor</title>
<meta
name="og:image"
content="https://github.com/mermaid-js/mermaid/raw/develop/img/header.png" />
<link rel="canonical" href="https://mermaid.live" />
<meta
name="description"
content="Simplify documentation and avoid heavy tools. Open source Visio Alternative. Commonly used for explaining your code! Mermaid is a simple markdown-like script language for generating charts from text via javascript." />
<link rel="icon" type="image/png" href="%sveltekit.assets%/favicon.png" />
<link rel="manifest" href="%sveltekit.assets%/manifest.json" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.33.0/min/vs/editor/editor.main.min.css"
crossorigin="anonymous"
referrerpolicy="no-referrer" />
<script>
var require = {
paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.33.0/min/vs' }
};
</script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.33.0/min/vs/loader.min.js"
crossorigin="anonymous"
referrerpolicy="no-referrer"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.33.0/min/vs/editor/editor.main.nls.min.js"
crossorigin="anonymous"
referrerpolicy="no-referrer"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.33.0/min/vs/editor/editor.main.js"
crossorigin="anonymous"
referrerpolicy="no-referrer"></script>
%sveltekit.head%
</head>
<body>
<div id="svelte">%sveltekit.body%</div>
</body>
</html>
-10
View File
@@ -1,10 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.input {
@apply flex-1 border-primary border-solid border-2 rounded;
}
.action-btn {
@apply btn btn-primary;
}
+3
View File
@@ -0,0 +1,3 @@
import { writable } from 'svelte/store';
export const codeErrorStore = writable(undefined);
+55
View File
@@ -0,0 +1,55 @@
import { writable, get } from 'svelte/store';
// import mermaid from '@mermaid-js/mermaid';
import mermaid from '@mermaid';
import { Base64 } from 'js-base64';
import { push, pop, replace } from 'svelte-spa-router';
export const codeStore = writable({});
export const fromUrl = (data) => {
let code;
let state;
const isDarkMode =
window.matchMedia('(prefers-color-scheme: dark)').matches && false;
try {
let stateStr = Base64.decode(data);
console.log('state from url', stateStr);
state = JSON.parse(stateStr);
if (state.code === undefined) {
// not valid json
// state = { code: '', mermaid: { theme: themeFromUrl } }
}
code = state.code;
} catch (e) {
// console.error('Init error', e);
code = `graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`;
state = { code, mermaid: { theme: isDarkMode ? 'dark' : 'default' } };
}
codeStore.set(state);
};
export const updateCodeStore = (newState) => {
codeStore.set(newState);
replace('/edit/' + Base64.encodeURI(JSON.stringify(newState)));
};
export const updateCode = (code, updateEditor) => {
const state = get(codeStore);
state.code = code;
state.updateEditor = updateEditor;
codeStore.set(state);
};
export const updateConfig = (config) => {
const state = get(codeStore);
state.mermaid = config;
codeStore.set(state);
};
const unsubscribe = codeStore.subscribe((state) => {
replace('/edit/' + Base64.encodeURI(JSON.stringify(state)));
});
+36
View File
@@ -0,0 +1,36 @@
<script>
export let title = '';
export let noPadding = false;
let contentClass = 'padding';
if (noPadding) {
contentClass = '';
}
</script>
<style>
#card {
border: 1px solid lightgray;
margin-bottom: 16px;
}
#title {
font-family: 'Playfair Display', serif;
font-weight: 400;
border-bottom: 1px solid lightgray;
font-size: 1.25rem;
background-color: #1e60ab;
color: #eaebef;
}
#content {
padding-top: 8px;
}
.padding {
padding: 8px;
}
</style>
<div id="card">
<div id="title" class="padding">{title}</div>
<div id="content" class={contentClass}>
<slot />
</div>
</div>
+114
View File
@@ -0,0 +1,114 @@
<script>
import {
codeStore,
updateCodeStore,
updateConfig,
updateCode,
} from '../code-store.js';
import { configErrorStore } from '../config-error-store.js';
import { onMount } from 'svelte';
import { push, pop, replace } from 'svelte-spa-router';
import { Base64 } from 'js-base64';
// import mermaid from '@mermaid-js/mermaid';
import mermaid from '@mermaid';
import Error from './Error.svelte';
import { getResizeHandler, initEditor } from './editor-utils';
import { watchResize } from 'svelte-watch-resize';
import 'monaco-editor/esm/vs/editor/browser/controller/coreCommands.js';
import 'monaco-editor/esm/vs/editor/contrib/find/findController.js';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js';
export let conf = '';
export let code = '';
export let error = false;
let edit;
let editorElem = null;
let decorations = [];
const decArr = [];
let resizeHandler = () => {};
let oldConf = {};
const handleConfUpdate = (conf) => {
try {
console.log(conf);
JSON.parse(conf);
// let newState = { code, mermaid: JSON.parse(conf) };
// oldConf = newState.mermaid;
updateConfig(JSON.parse(conf));
configErrorStore.set(undefined);
// const model = edit.getModel();
// // model.setValue(conf);
// // model.dispose();
} catch (e) {
console.log('Error in parsed', e);
configErrorStore.set(e);
const str = JSON.stringify({ code, mermaid: oldConf });
replace('/edit/' + Base64.encodeURI(str));
}
};
const unsubscribe = codeStore.subscribe((state) => {
if (editorElem === null) {
console.log('Starting stuff', document.getElementById('editor-conf'));
editorElem = document.getElementById('editor-conf');
}
if (!conf && state) {
conf = JSON.stringify(state.mermaid, null, 2);
}
if (state) {
code = state.code;
}
if (!edit && conf && editorElem !== null) {
edit = monaco.editor.create(editorElem, {
value: [conf].join('\n'),
theme: 'myCoolTheme',
language: 'JSON',
});
resizeHandler = getResizeHandler(edit);
let decorations = [];
edit.onDidChangeModelContent(function (e) {
const conf = edit.getValue();
handleConfUpdate(conf);
});
handleConfUpdate(conf);
}
});
initEditor(monaco);
const unsubscribeError = configErrorStore.subscribe((_error) => {
// console.log('Error ideintified' + _error.toString());
if (_error) {
error = _error.toString();
} else {
error = false;
}
});
onMount(async () => {
console.log('Mounting config');
self.MonacoEnvironment = {
getWorkerUrl: function (moduleId, label) {
return './editor.worker.bundle.js';
},
};
});
</script>
<style>
#editor-conf {
width: 100%;
height: 200px;
max-height: 300px;
flex: 1;
}
</style>
<div id="editor-container">
<div id="editor-conf" use:watchResize={resizeHandler} />
{#if error}
<Error errorText={error} />
{/if}
</div>
+136
View File
@@ -0,0 +1,136 @@
<script>
import { codeStore, updateCodeStore } from '../code-store.js';
import { codeErrorStore } from '../code-error-store.js';
import { onMount } from 'svelte';
import { push, pop, replace } from 'svelte-spa-router';
import { Base64 } from 'js-base64';
// import mermaid from '@mermaid-js/mermaid';
import mermaid from '@mermaid';
import Error from './Error.svelte';
import { getResizeHandler, initEditor } from './editor-utils';
import 'monaco-editor/esm/vs/editor/browser/controller/coreCommands.js';
import 'monaco-editor/esm/vs/editor/contrib/find/findController.js';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js';
import { watchResize } from 'svelte-watch-resize';
export let code = '';
const isDarkMode =
window.matchMedia('(prefers-color-scheme: dark)').matches && false;
export let conf = { theme: isDarkMode ? 'dark' : 'default' };
let edit;
export let error = false;
let decorations = [];
const decArr = [];
let editorElem = null;
let resizeHandler = () => {};
const handleCodeUpdate = (code) => {
try {
mermaid.parse(code);
let newState = { code, mermaid: conf, updateEditor: false };
updateCodeStore(newState);
decArr.forEach((decor) => {
edit.deltaDecorations(decor, []);
});
codeErrorStore.set(undefined);
const model = edit.getModel();
} catch (e) {
if (e) {
codeErrorStore.set(e);
console.log('Error in parsed', e.hash);
const str = JSON.stringify({ code: code, mermaid: conf });
replace('/edit/' + Base64.encodeURI(str));
const l = e.hash.line;
decArr.push(
edit.deltaDecorations(
[],
[
{
range: new monaco.Range(
e.hash.loc.first_line,
e.hash.loc.last_line,
e.hash.loc.first_column,
e.hash.loc.last_column
),
options: { inlineClassName: 'myInlineDecoration' },
},
]
)
);
}
}
};
const unsubscribe = codeStore.subscribe((state) => {
console.log('Code change');
if (editorElem === null) {
editorElem = document.getElementById('editor');
}
if (!code && state) {
code = state.code;
}
if (state) {
conf = state.mermaid;
}
if (!edit && code && editorElem !== null) {
edit = monaco.editor.create(editorElem, {
value: [code].join('\n'),
theme: 'myCoolTheme',
language: 'mermaid',
});
resizeHandler = getResizeHandler(edit);
let decorations = [];
edit.onDidChangeModelContent(function (e) {
const code = edit.getValue();
handleCodeUpdate(code);
});
handleCodeUpdate(code);
}
if (state && state.updateEditor && edit && code && editorElem !== null) {
edit.setValue(state.code);
handleCodeUpdate(state.code);
}
});
const unsubscribeError = codeErrorStore.subscribe((_error) => {
if (_error) {
error = true;
} else {
error = false;
}
});
initEditor(monaco);
onMount(async () => {
// editorElem = document.querySelector('#editor')
self.MonacoEnvironment = {
getWorkerUrl: function (moduleId, label) {
return './editor.worker.bundle.js';
},
};
});
// export let name;
// export let params = {};
</script>
<style>
#editor-container {
}
#editor {
width: 100%;
height: 400px;
max-height: 300px;
flex: 1;
}
</style>
<div id="editor-container">
<div id="editor" use:watchResize={resizeHandler} />
{#if error}
<Error errorText="Syntax Error" />
{/if}
</div>
+49
View File
@@ -0,0 +1,49 @@
<script>
import { codeErrorStore } from '../code-error-store.js';
import { onMount } from 'svelte';
onMount(async () => {});
// export let code = '';
export let classes = '';
// export let error = {};
// export let token = '';
// export let expected = '';
// export let params = {}
export let errorText = '';
// const unsubscribeError = codeErrorStore.subscribe( _error => {
// if(typeof _error === 'undefined') {
// classes = 'invisible';
// error = {};
// token='';
// expected='';
// } else {
// classes = 'visible';
// error = _error;
// console.log('error: ', _error);
// token = error.hash.token;
// expected = error.hash.expected.join(' ');;
// }
// });
</script>
<style>
#error {
background: darkred;
color: white;
flex: 1;
padding: 10px;
/* position: absolute; */
}
.invisible {
display: none;
}
.visible {
display: block;
}
</style>
<div id="error" class={classes}>{errorText}</div>
+228
View File
@@ -0,0 +1,228 @@
<script>
import { link } from 'svelte-spa-router';
import { Base64 } from 'js-base64';
import moment from 'moment';
import { codeStore } from '../code-store.js';
const getBase64SVG = () => {
const container = document.getElementById('container');
const svg = container.innerHTML.replaceAll('<br>', '<br/>');
return Base64.encode(svg);
};
const exportImage = (event, exporter) => {
const canvas = document.createElement('canvas');
const svg = document.querySelector('#container svg');
const box = svg.getBoundingClientRect();
canvas.width = box.width;
canvas.height = box.height;
if (imagemodeselected === 'width') {
const ratio = box.height / box.width;
canvas.width = userimagewidth;
canvas.height = userimagewidth * ratio;
} else if (imagemodeselected === 'height') {
const ratio = box.width / box.height;
canvas.width = userimageheight * ratio;
canvas.height = userimageheight;
}
const context = canvas.getContext('2d');
context.fillStyle = 'white';
context.fillRect(0, 0, canvas.width, canvas.height);
const image = new Image();
image.onload = exporter(canvas, context, image);
console.log('SVG', getBase64SVG());
image.src = `data:image/svg+xml;base64,${getBase64SVG()}`;
event.stopPropagation();
event.preventDefault();
};
const downloadImage = (canvas, context, image) => {
return () => {
context.drawImage(image, 0, 0, canvas.width, canvas.height);
const a = document.createElement('a');
a.download = `mermaid-diagram-${moment().format('YYYYMMDDHHmmss')}.png`;
a.href = canvas
.toDataURL('image/png')
.replace('image/png', 'image/octet-stream');
a.click();
};
};
const clipboardCopy = (canvas, context, image) => {
return () => {
context.drawImage(image, 0, 0, canvas.width, canvas.height);
canvas.toBlob((blob) => {
try {
navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob,
}),
]);
} catch (error) {
console.error(error);
}
});
};
};
const onCopyClipboard = (event) => {
exportImage(event, clipboardCopy);
};
const onDownloadPNG = (event) => {
exportImage(event, downloadImage);
};
const onDownloadSVG = (event) => {
console.log('event', event.target);
event.target.href = `data:image/svg+xml;base64,${getBase64SVG()}`;
event.target.download = `mermaid-diagram-${moment().format(
'YYYYMMDDHHmmss'
)}.svg`;
console.log('event', event);
};
const onCopyMarkdown = (event) => {
event.target.select();
document.execCommand('Copy');
};
let url = '/mermaid-live-editor/#/view';
let b64Code;
let iUrl;
let svgUrl;
let mdCode;
let imagemodeselected = 'auto';
let userimagewidth = 1920;
let userimageheight = 1080;
const unsubscribe = codeStore.subscribe((state) => {
b64Code = Base64.encodeURI(JSON.stringify(state));
url = `/mermaid-live-editor/#/view/${b64Code}`;
iUrl = `https://mermaid.ink/img/${b64Code}`;
svgUrl = `https://mermaid.ink/svg/${b64Code}`;
mdCode = `[![](${iUrl})](${window.location.protocol}//${window.location.host}${window.location.pathname}#/edit/${b64Code})`;
});
</script>
<style>
#links {
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid lightgray;
}
#markdown {
padding: 7px;
font-family: monospace;
font-size: 14px;
width: 95%;
margin: 1rem 0;
border: 1px solid lightgray;
}
label[for='markdown'] {
cursor: pointer;
margin: 0 auto;
}
/*
@media (prefers-color-scheme: dark) {
label[for="markdown"] {
color: #d8d8d8;
}
} */
.button-style {
background-color: #a2d9e2;
color: #33a2c4;
border-radius: 0.25rem;
padding: 0.5rem;
border: 1px solid #a2d9e2;
margin: 0.25rem;
}
.button-style:hover {
background-color: #fff;
color: #33a2c4;
border: 1px solid #33a2c4;
}
.button-style:focus {
outline: none;
}
.link-style {
text-decoration: none;
color: #33a2c4;
}
#copy-section {
padding-top: 1rem;
text-align: center;
}
</style>
<div id="links">
<button class="button-style">
<a class="link-style" href={url} download="" on:click={onCopyClipboard}>
Copy Image
</a>
</button>
<button class="button-style">
<a class="link-style" href={url} download="" on:click={onDownloadPNG}>
Download PNG
</a>
</button>
<button class="button-style">
<a class="link-style" href={url} use:link>Link to view</a>
</button>
<button class="button-style">
<a class="link-style" href={url} download="" on:click={onDownloadSVG}>
Download SVG
</a>
</button>
<button class="button-style">
<a class="link-style" href={iUrl}>Link to Image</a>
</button>
<button class="button-style">
<a class="link-style" href={svgUrl}>Link to SVG</a>
</button>
(markdown is base64 encoded for these urls)
</div>
<div id="copy-section">
<label for="markdown" class="button-style">Copy Markdown</label>
<br />
<input id="markdown" type="text" value={mdCode} on:click={onCopyMarkdown} />
</div>
<p>
<label>PNG size:</label>
<input
type="radio"
value="auto"
id="autosize"
bind:group={imagemodeselected} />
<label for="autosize">auto</label>
<input
type="radio"
value="width"
id="width-active"
bind:group={imagemodeselected} />
<label for="width">width</label>
<input
id="width"
type="number"
min="3"
max="10000"
bind:value={userimagewidth}
disabled={imagemodeselected !== 'width'} />
<input
type="radio"
value="height"
id="height-active"
bind:group={imagemodeselected} />
<label for="height">height</label>
<input
id="height"
type="number"
min="3"
max="10000"
bind:value={userimageheight}
disabled={imagemodeselected !== 'height'} /><br />
</p>
+31
View File
@@ -0,0 +1,31 @@
<script>
export let noPadding = false;
let contentClass = 'padding';
if (noPadding) {
contentClass = '';
}
</script>
<style>
#tag {
height: auto;
margin: 0 8px 0 0;
padding: 0 7px;
font-size: 12px;
line-height: 20px;
white-space: nowrap;
border: 1px solid #d9d9d9;
border-radius: 4px;
cursor: default;
font-size: smaller;
/* padding: 8px; */
color: #52c41a;
background: #f6ffed;
border-color: #b7eb8f;
margin-left: 8px;
}
</style>
<div id="tag">
<slot />
</div>
+124
View File
@@ -0,0 +1,124 @@
<script>
import { codeStore } from '../code-store.js';
import { codeErrorStore } from '../code-error-store.js';
import { configErrorStore } from '../config-error-store.js';
import { onMount } from 'svelte';
// import mermaid from '@mermaid-js/mermaid';
import mermaid from '@mermaid';
const detectType = (text) => {
text = text.replace(/^\s*%%.*\n/g, '\n');
console.debug('Detecting diagram type based on the text ' + text);
if (text.match(/^\s*sequenceDiagram/)) {
return 'sequence';
}
if (text.match(/^\s*gantt/)) {
return 'gantt';
}
if (text.match(/^\s*classDiagram/)) {
return 'class';
}
if (text.match(/^\s*stateDiagram/)) {
return 'state';
}
if (text.match(/^\s*gitGraph/)) {
return 'git';
}
if (text.match(/^\s*flowchart/)) {
return 'flowchart';
}
if (text.match(/^\s*info/)) {
return 'info';
}
if (text.match(/^\s*pie/)) {
return 'pie';
}
return 'flowchart';
};
// manual debounce
let timeout;
const saveStatistcs = (graphType) => {
clearTimeout(timeout);
// Only save statistcs after a 5 sec delay
timeout = setTimeout(function () {
console.log('ga:', 'send', 'event', 'render', graphType, graphType);
ga('send', 'event', graphType, 'render', 'render');
}, 5000);
};
let element;
let container;
onMount(async () => {
element = document.querySelector('graph-div');
});
let insertSvg = function (svgCode, bindFunctions) {
// element.innerHTML = svgCode;
};
export let code = '';
export let configClasses = '';
export let codeClasses = '';
const unsubscribe = codeStore.subscribe((state) => {
try {
if (container && state) {
code = state.code;
// mermaid.parse(code)
// Replacing special characters '<' and '>' with encoded '&lt;' and '&gt;'
let _code = code;
_code = _code.replace(/</g, '&lt;');
_code = _code.replace(/>/g, '&gt;');
container.innerHTML = _code;
saveStatistcs(detectType(code));
delete container.dataset.processed;
mermaid.initialize(Object.assign({}, state.mermaid));
mermaid.init(undefined, container);
if (code) mermaid.render('graph-div', code, insertSvg);
}
} catch (e) {
console.log('view fail', e);
}
});
const unsubscribeError = codeErrorStore.subscribe((_error) => {
if (typeof _error === 'undefined') {
codeClasses = '';
} else {
codeClasses = 'error';
console.log('code error: ', _error);
}
});
const unsubscribeConfigError = configErrorStore.subscribe((_error) => {
if (typeof _error === 'undefined') {
configClasses = '';
} else {
configClasses = 'error';
console.log('conf error: ', _error);
}
});
</script>
<style>
#view {
border: 1px solor darkred;
flex: 1;
}
#container {
overflow-x: auto;
}
.error {
opacity: 0.5;
}
</style>
<div id="view" class="{codeClasses} {configClasses}">
<div id="container" bind:this={container} />
</div>
+86
View File
@@ -0,0 +1,86 @@
export const initEditor = (monaco) => {
monaco.languages.register({ id: 'mermaid' });
// Register a tokens provider for the language
monaco.languages.setMonarchTokensProvider('mermaid', {
typeKeywords: [
'graph',
'stateDiagram',
'sequenceDiagram',
'classDiagram',
'pie',
'flowchart',
'gantt',
],
keywords: ['patricipant', 'as'],
arrows: ['---', '===', '-->', '==>'],
tokenizer: {
root: [
[/[{}]/, 'delimiter.bracket'],
[
/[a-z_$][\w$]*/,
{ cases: { '@typeKeywords': 'keyword', '@keywords': 'keyword' } },
],
[/[-=>ox]+/, { cases: { '@arrows': 'transition' } }],
[/[\[\{\(}]+.+?[\)\]\}]+/, 'string'],
[/\".*\"/, 'string'],
],
},
whitespace: [
[/[ \t\r\n]+/, 'white'],
[/\%\%.*$/, 'comment'],
],
});
monaco.editor.defineTheme('myCoolTheme', {
base: 'vs',
inherit: false,
rules: [
{ token: 'keyword', foreground: '880000', fontStyle: 'bold' },
{ token: 'custom-error', foreground: 'ff0000', fontStyle: 'bold' },
{ token: 'string', foreground: 'AA8500' },
{ token: 'transition', foreground: '008800', fontStyle: 'bold' },
{ token: 'delimiter.bracket', foreground: '000000', fontStyle: 'bold' },
],
});
// Register a completion item provider for the new language
monaco.languages.registerCompletionItemProvider('mermaid', {
provideCompletionItems: () => {
var suggestions = [
{
label: 'simpleText',
kind: monaco.languages.CompletionItemKind.Text,
insertText: 'simpleText',
},
{
label: 'testing',
kind: monaco.languages.CompletionItemKind.Keyword,
insertText: 'testing(${1:condition})',
insertTextRules:
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
},
{
label: 'ifelse',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'if (${1:condition}) {',
'\t$0',
'} else {',
'\t',
'}',
].join('\n'),
insertTextRules:
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'If-Else Statement',
},
];
return { suggestions: suggestions };
},
});
};
export const getResizeHandler = (editor) => {
return (node) => editor && editor.layout();
};
+3
View File
@@ -0,0 +1,3 @@
import { writable } from 'svelte/store';
export const configErrorStore = writable(undefined);
+168
View File
@@ -0,0 +1,168 @@
// (1) Desired editor features:
import 'monaco-editor/esm/vs/editor/browser/controller/coreCommands.js';
// import 'monaco-editor/esm/vs/editor/browser/widget/codeEditorWidget.js';
// import 'monaco-editor/esm/vs/editor/browser/widget/diffEditorWidget.js';
// import 'monaco-editor/esm/vs/editor/browser/widget/diffNavigator.js';
// import 'monaco-editor/esm/vs/editor/contrib/bracketMatching/bracketMatching.js';
// import 'monaco-editor/esm/vs/editor/contrib/caretOperations/caretOperations.js';
// import 'monaco-editor/esm/vs/editor/contrib/caretOperations/transpose.js';
// import 'monaco-editor/esm/vs/editor/contrib/clipboard/clipboard.js';
// import 'monaco-editor/esm/vs/editor/contrib/codelens/codelensController.js';
// import 'monaco-editor/esm/vs/editor/contrib/colorPicker/colorDetector.js';
// import 'monaco-editor/esm/vs/editor/contrib/comment/comment.js';
// import 'monaco-editor/esm/vs/editor/contrib/contextmenu/contextmenu.js';
// import 'monaco-editor/esm/vs/editor/contrib/cursorUndo/cursorUndo.js';
// import 'monaco-editor/esm/vs/editor/contrib/dnd/dnd.js';
import 'monaco-editor/esm/vs/editor/contrib/find/findController.js';
// import 'monaco-editor/esm/vs/editor/contrib/folding/folding.js';
// import 'monaco-editor/esm/vs/editor/contrib/format/formatActions.js';
// import 'monaco-editor/esm/vs/editor/contrib/goToDeclaration/goToDeclarationCommands.js';
// import 'monaco-editor/esm/vs/editor/contrib/goToDeclaration/goToDeclarationMouse.js';
// import 'monaco-editor/esm/vs/editor/contrib/gotoError/gotoError.js';
// import 'monaco-editor/esm/vs/editor/contrib/hover/hover.js';
// import 'monaco-editor/esm/vs/editor/contrib/inPlaceReplace/inPlaceReplace.js';
// import 'monaco-editor/esm/vs/editor/contrib/linesOperations/linesOperations.js';
// import 'monaco-editor/esm/vs/editor/contrib/links/links.js';
// import 'monaco-editor/esm/vs/editor/contrib/multicursor/multicursor.js';
// import 'monaco-editor/esm/vs/editor/contrib/parameterHints/parameterHints.js';
// import 'monaco-editor/esm/vs/editor/contrib/quickFix/quickFixCommands.js';
// import 'monaco-editor/esm/vs/editor/contrib/referenceSearch/referenceSearch.js';
// import 'monaco-editor/esm/vs/editor/contrib/rename/rename.js';
// import 'monaco-editor/esm/vs/editor/contrib/smartSelect/smartSelect.js';
// import 'monaco-editor/esm/vs/editor/contrib/snippet/snippetController2.js';
// import 'monaco-editor/esm/vs/editor/contrib/suggest/suggestController.js';
// import 'monaco-editor/esm/vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode.js';
// import 'monaco-editor/esm/vs/editor/contrib/wordHighlighter/wordHighlighter.js';
// import 'monaco-editor/esm/vs/editor/contrib/wordOperations/wordOperations.js';
// import 'monaco-editor/esm/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.js';
// import 'monaco-editor/esm/vs/editor/standalone/browser/inspectTokens/inspectTokens.js';
// import 'monaco-editor/esm/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.js';
// import 'monaco-editor/esm/vs/editor/standalone/browser/quickOpen/quickOutline.js';
// import 'monaco-editor/esm/vs/editor/standalone/browser/quickOpen/gotoLine.js';
// import 'monaco-editor/esm/vs/editor/standalone/browser/quickOpen/quickCommand.js';
// import 'monaco-editor/esm/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.js';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js';
// (2) Desired languages:
// import 'monaco-editor/esm/vs/language/typescript/monaco.contribution';
// import 'monaco-editor/esm/vs/language/css/monaco.contribution';
// import 'monaco-editor/esm/vs/language/json/monaco.contribution';
// import 'monaco-editor/esm/vs/language/html/monaco.contribution';
// import 'monaco-editor/esm/vs/basic-languages/bat/bat.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/coffee/coffee.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/cpp/cpp.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/csharp/csharp.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/csp/csp.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/css/css.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/dockerfile/dockerfile.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/fsharp/fsharp.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/go/go.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/handlebars/handlebars.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/html/html.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/ini/ini.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/java/java.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/less/less.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/lua/lua.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/markdown/markdown.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/msdax/msdax.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/mysql/mysql.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/objective-c/objective-c.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/pgsql/pgsql.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/php/php.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/postiats/postiats.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/powershell/powershell.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/pug/pug.contribution.js';
import 'monaco-editor/esm/vs/basic-languages/python/python.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/r/r.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/razor/razor.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/redis/redis.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/redshift/redshift.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/ruby/ruby.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/sb/sb.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/scss/scss.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/solidity/solidity.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/swift/swift.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/vb/vb.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/xml/xml.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution.js';
// import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution';
// import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution';
monaco.languages.register({ id: 'mermaid' });
// Register a tokens provider for the language
monaco.languages.setMonarchTokensProvider('mermaid', {
tokenizer: {
root: [
[/\[graph.*/, 'custom-error'],
[/\[notice.*/, 'custom-notice'],
[/\[info.*/, 'custom-info'],
[/\[[a-zA-Z 0-9:]+\]/, 'custom-date'],
],
},
});
self.MonacoEnvironment = {
getWorkerUrl: function (moduleId, label) {
return './editor.worker.bundle.js';
},
};
// Define a new theme that contains only rules that match this language
monaco.editor.defineTheme('myCoolTheme', {
base: 'vs',
inherit: false,
rules: [
{ token: 'custom-info', foreground: '808080' },
{ token: 'custom-error', foreground: 'ff0000', fontStyle: 'bold' },
{ token: 'custom-notice', foreground: 'FFA500' },
{ token: 'custom-date', foreground: '008800' },
],
});
// Register a completion item provider for the new language
monaco.languages.registerCompletionItemProvider('mySpecialLanguage', {
provideCompletionItems: () => {
var suggestions = [
{
label: 'simpleText',
kind: monaco.languages.CompletionItemKind.Text,
insertText: 'simpleText',
},
{
label: 'testing',
kind: monaco.languages.CompletionItemKind.Keyword,
insertText: 'testing(${1:condition})',
insertTextRules:
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
},
{
label: 'ifelse',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'if (${1:condition}) {',
'\t$0',
'} else {',
'\t',
'}',
].join('\n'),
insertTextRules:
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'If-Else Statement',
},
];
return { suggestions: suggestions };
},
});
const edit = monaco.editor.create(document.getElementById('editor'), {
value: [code].join('\n'),
theme: 'myCoolTheme',
// value: getCode(),
//language: 'mySpecialLanguage'
language: 'mermaid',
});
edit.onDidChangeModelContent(function (e) {
codeStore.set(edit.getValue());
});
-1
View File
@@ -1 +0,0 @@
/// <reference types="@sveltejs/kit" />
-9
View File
@@ -1,9 +0,0 @@
import type { Handle } from '@sveltejs/kit/types/hooks';
export const handle: Handle = async ({ event, resolve }) => {
const response = await resolve(event, {
ssr: false
});
return response;
};
-234
View File
@@ -1,234 +0,0 @@
<script lang="ts">
import { browser } from '$app/env';
import Card from '$lib/components/card/card.svelte';
import { krokiRendererUrl, rendererUrl } from '$lib/util/env';
import { pakoSerde } from '$lib/util/serde';
import { serializedState, codeStore } from '$lib/util/state';
import { toBase64 } from 'js-base64';
import moment from 'moment';
type Exporter = (context: CanvasRenderingContext2D, image: HTMLImageElement) => () => void;
const getBase64SVG = (svg?: HTMLElement, width?: number, height?: number): string => {
svg?.setAttribute('height', `${height}px`);
svg?.setAttribute('width', `${width}px`); // Workaround https://stackoverflow.com/questions/28690643/firefox-error-rendering-an-svg-image-to-html5-canvas-with-drawimage
if (!svg) {
svg = document.querySelector('#container svg');
}
const svgString = svg.outerHTML
.replaceAll('<br>', '<br/>')
.replaceAll(/<img([^>]*)>/g, (m, g: string) => `<img ${g} />`);
return toBase64(svgString);
};
const exportImage = (event: Event, exporter: Exporter) => {
const canvas: HTMLCanvasElement = document.createElement('canvas');
const svg: HTMLElement = document.querySelector('#container svg');
const box: DOMRect = svg.getBoundingClientRect();
canvas.width = box.width;
canvas.height = box.height;
if (imagemodeselected === 'width') {
const ratio = box.height / box.width;
canvas.width = userimagesize;
canvas.height = userimagesize * ratio;
} else if (imagemodeselected === 'height') {
const ratio = box.width / box.height;
canvas.width = userimagesize * ratio;
canvas.height = userimagesize;
}
const context = canvas.getContext('2d');
context.fillStyle = 'white';
context.fillRect(0, 0, canvas.width, canvas.height);
const image = new Image();
image.onload = exporter(context, image);
image.src = `data:image/svg+xml;base64,${getBase64SVG(svg, canvas.width, canvas.height)}`;
event.stopPropagation();
event.preventDefault();
};
const simulateDownload = (download: string, href: string): void => {
const a = document.createElement('a');
a.download = download;
a.href = href;
a.click();
a.remove();
};
const downloadImage: Exporter = (context, image) => {
return () => {
const { canvas } = context;
context.drawImage(image, 0, 0, canvas.width, canvas.height);
simulateDownload(
`mermaid-diagram-${moment().format('YYYYMMDDHHmmss')}.png`,
canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream')
);
};
};
const isClipboardAvailable = (): boolean => {
return Object.prototype.hasOwnProperty.call(window, 'ClipboardItem') as boolean;
};
const clipboardCopy: Exporter = (context, image) => {
return () => {
const { canvas } = context;
context.drawImage(image, 0, 0, canvas.width, canvas.height);
canvas.toBlob((blob) => {
try {
// @ts-ignore: https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1004/files
void navigator.clipboard.write([
/* eslint-disable no-undef */
// @ts-ignore: https://github.com/microsoft/TypeScript/issues/43821
new ClipboardItem({
[blob.type]: blob
})
]);
} catch (error) {
console.error(error);
}
});
};
};
const onCopyClipboard = (event: Event) => {
exportImage(event, clipboardCopy);
};
const onDownloadPNG = (event: Event) => {
exportImage(event, downloadImage);
};
const onDownloadSVG = () => {
simulateDownload(
`mermaid-diagram-${moment().format('YYYYMMDDHHmmss')}.svg`,
`data:image/svg+xml;base64,${getBase64SVG()}`
);
};
const onCopyMarkdown = () => {
(document.getElementById('markdown') as HTMLInputElement).select();
document.execCommand('Copy');
};
let gistURL = '';
codeStore.subscribe((state) => {
if (state.loader?.type === 'gist') {
// @ts-ignore Gist will have url
gistURL = state.loader.config.url;
}
});
const loadGist = () => {
if (!gistURL) {
alert('Please enter a Gist URL first');
}
window.location.href = `${window.location.pathname}?gist=${gistURL}`;
};
let iUrl: string;
let svgUrl: string;
let krokiUrl: string;
let mdCode: string;
let imagemodeselected = 'auto';
let userimagesize = 1080;
let isNetlify = false;
if (browser && ['mermaid.live', 'netlify'].some((path) => window.location.host.includes(path))) {
isNetlify = true;
}
serializedState.subscribe((encodedState: string) => {
iUrl = `${rendererUrl}/img/${encodedState}`;
svgUrl = `${rendererUrl}/svg/${encodedState}`;
krokiUrl = `${krokiRendererUrl}/mermaid/svg/${pakoSerde.serialize($codeStore.code)}`;
mdCode = `[![](${iUrl})](${window.location.protocol}//${window.location.host}${window.location.pathname}#${encodedState})`;
});
</script>
<Card title="Actions" isOpen={true}>
<div class="flex flex-wrap gap-2 m-2">
{#if isClipboardAvailable()}
<button class="action-btn w-full" on:click={onCopyClipboard}
><i class="far fa-copy mr-2" /> Copy Image to clipboard
</button>
{/if}
<button class="action-btn flex-auto" on:click={onDownloadPNG}>
<i class="fas fa-download mr-2" /> PNG
</button>
<button class="action-btn flex-auto" on:click={onDownloadSVG}>
<i class="fas fa-download mr-2" /> SVG
</button>
<a target="_blank" href={iUrl}>
<button class="action-btn flex-auto">
<i class="fas fa-external-link-alt mr-2" /> PNG
</button>
</a>
<a target="_blank" href={svgUrl}>
<button class="action-btn flex-auto">
<i class="fas fa-external-link-alt mr-2" /> SVG
</button>
</a>
<a target="_blank" href={krokiUrl}>
<button class="action-btn flex-auto">
<i class="fas fa-external-link-alt mr-2" /> Kroki
</button>
</a>
<div class="flex gap-2 items-center">
PNG size
<label for="autosize">
<input type="radio" value="auto" id="autosize" bind:group={imagemodeselected} /> Auto
</label>
<label for="width">
<input type="radio" value="width" id="width" bind:group={imagemodeselected} /> Width
</label>
<label for="height">
<input type="radio" value="height" id="height" bind:group={imagemodeselected} /> Height
</label>
{#if imagemodeselected !== 'auto'}
<input
id="height"
class="input"
type="number"
min="3"
max="10000"
bind:value={userimagesize} />
{/if}
</div>
<div class="w-full flex gap-2 items-center">
<input class="input" id="markdown" type="text" value={mdCode} on:click={onCopyMarkdown} />
<label for="markdown">
<button class="btn btn-primary btn-md flex-auto" on:click={onCopyMarkdown}>
Copy Markdown
</button>
</label>
</div>
<div class="w-full flex gap-2 items-center">
<input
class="input"
id="gist"
type="text"
bind:value={gistURL}
placeholder="Enter Gist URL" />
<label for="gist">
<button class="btn btn-primary btn-md flex-auto" on:click={loadGist}> Load Gist </button>
</label>
</div>
{#if isNetlify}
<div class="w-full flex items-center justify-center">
<a class="link underline text-gray-500 text-sm" href="https://netlify.com">
This site is powered by Netlify
</a>
</div>
{/if}
</div>
</Card>
-29
View File
@@ -1,29 +0,0 @@
<script lang="ts">
import type { Tab } from '$lib/types';
import { slide } from 'svelte/transition';
import Tabs from './tabs.svelte';
export let isCloseable = true;
export let isOpen = true;
export let tabs: Tab[] = [];
export let title: string;
$: isOpen = isCloseable ? isOpen : true;
$: isTabsShown = isOpen && tabs.length > 0;
</script>
<div class="card rounded overflow-hidden m-2 flex-grow flex flex-col shadow-2xl">
<div
class="bg-primary p-2 {isTabsShown ? 'pb-0' : ''} flex-none cursor-pointer"
on:click={() => (isOpen = !isOpen)}>
<div class="flex justify-between">
<Tabs on:select {tabs} bind:isOpen {title} {isCloseable} />
<div class="flex gap-x-4 items-center {isTabsShown ? '-mt-2' : ''}">
<slot name="actions" />
</div>
</div>
</div>
{#if isOpen}
<div class="card-body p-0 flex-grow overflow-auto text-base-content" transition:slide>
<slot />
</div>
{/if}
</div>
-46
View File
@@ -1,46 +0,0 @@
<script lang="ts">
import type { Tab, TabEvents } from '$lib/types';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
export let isCloseable = true;
export let tabs: Tab[] = [];
export let title: string;
export let isOpen = false;
$: activeTabID = tabs[0]?.id;
const dispatch = createEventDispatcher<TabEvents>();
const toggleTabs = (tab: Tab) => {
activeTabID = tab.id;
dispatch('select', tab);
};
</script>
<div class="flex cursor-default">
<span class="mr-2 font-semibold" on:click|stopPropagation={() => (isOpen = !isOpen)}>
{#if isCloseable}
<i class="fas fa-chevron-right icon" class:isOpen />
{/if}
{title}</span>
{#if isOpen && tabs}
<ul class="tabs" transition:fade>
{#each tabs as tab}
<div
class="tab tab-lifted {activeTabID === tab.id ? 'tab-active' : 'text-primary-content'}"
on:click|stopPropagation={() => toggleTabs(tab)}>
<i class="mr-1 {tab.icon}" />
{tab.title}
</div>
{/each}
</ul>
{/if}
</div>
<style>
.icon {
transition-duration: 0.5s;
}
.isOpen {
transform: rotate(90deg);
}
</style>
-85
View File
@@ -1,85 +0,0 @@
<script lang="ts">
import type { EditorEvents } from '$lib/types';
import { codeStore } from '$lib/util/state';
import { themeStore } from '$lib/util/theme';
import type monaco from 'monaco-editor';
import { createEventDispatcher, onMount } from 'svelte';
import initEditor from 'monaco-mermaid';
let divEl: HTMLDivElement = null;
let editor: monaco.editor.IStandaloneCodeEditor;
let Monaco;
export let text: string;
export let language: string;
export let editorOptions: monaco.editor.IStandaloneEditorConstructionOptions = {
value: text,
language: language,
minimap: {
enabled: false
},
theme: 'mermaid',
overviewRulerLanes: 0
};
export let errorMarkers: monaco.editor.IMarkerData[] = [];
let oldText = text;
$: editor && Monaco?.editor.setModelLanguage(editor.getModel(), language);
$: {
if (text !== oldText) {
if ($codeStore.updateEditor) {
editor?.setValue(text);
}
oldText = text;
}
editor && Monaco?.editor.setModelMarkers(editor.getModel(), 'test', errorMarkers);
}
themeStore.subscribe(({ isDark }) => {
editor && Monaco?.editor.setTheme(isDark ? 'mermaid-dark' : 'mermaid');
});
const dispatch = createEventDispatcher<EditorEvents>();
const loadMonaco = async () => {
let i = 0;
while (i++ < 10) {
try {
// @ts-ignore : This is a hack to handle a svelte-kit error when importing monaco.
Monaco = monaco;
return;
} catch {
await new Promise((r) => setTimeout(r, 500));
}
}
alert('Loading Monaco Editor failed. Please try refreshing the page.');
};
onMount(async () => {
try {
// @ts-ignore : This is a hack to handle a svelte-kit error when importing monaco.
Monaco = monaco;
} catch {
await loadMonaco(); // Fix https://github.com/mermaid-js/mermaid-live-editor/issues/175
}
initEditor(Monaco);
editor = Monaco.editor.create(divEl, editorOptions);
editor.onDidChangeModelContent(() => {
text = editor.getValue();
dispatch('update', {
text
});
});
Monaco?.editor.setTheme($themeStore.isDark ? 'mermaid-dark' : 'mermaid');
const resizeObserver = new ResizeObserver((entries) => {
editor.layout({
height: entries[0].contentRect.height,
width: entries[0].contentRect.width
});
});
resizeObserver.observe(divEl.parentElement);
return () => {
editor.dispose();
};
});
</script>
<div bind:this={divEl} id="editor" class="overflow-hidden" />
-142
View File
@@ -1,142 +0,0 @@
<script lang="ts">
import Card from '$lib/components/card/card.svelte';
import { codeStore, getStateString } from '$lib/util/state';
import {
addHistoryEntry,
historyModeStore,
clearHistoryData,
getPreviousState,
historyStore,
loaderHistoryStore
} from './history';
import { notify, prompt } from '$lib/util/notify';
import { onMount } from 'svelte';
import moment from 'moment';
import type { HistoryType, State, Tab } from '$lib/types';
const HISTORY_SAVE_INTERVAL = 60000;
const tabSelectHandler = (message: CustomEvent<Tab>) => {
historyModeStore.set(message.detail.id as HistoryType);
};
let tabs: Tab[] = [
{
id: 'manual',
title: 'Saved',
icon: 'far fa-bookmark'
},
{
id: 'auto',
title: 'Timeline',
icon: 'fas fa-history'
}
];
const saveHistory = (auto = false) => {
const currentState: string = getStateString();
const previousState: string = getPreviousState(auto);
if (previousState !== currentState) {
addHistoryEntry({
state: $codeStore,
time: Date.now(),
type: auto ? 'auto' : 'manual'
});
} else if (!auto) {
notify('State already saved.');
}
};
const clearHistory = (date?: number): void => {
if (!date && !prompt('Clear all saved items?')) {
return;
}
clearHistoryData(date);
};
const restoreHistory = (state: State): void => {
codeStore.set({ ...state, updateEditor: true, updateDiagram: true });
};
const relativeTime = (time: number) => {
const t = new Date(time);
return `${new Date(t).toLocaleString()} (${moment(t).fromNow()})`;
};
onMount(() => {
historyModeStore.set('manual');
setInterval(() => {
saveHistory(true);
}, HISTORY_SAVE_INTERVAL);
});
loaderHistoryStore.subscribe((entries) => {
if (entries.length > 0 && tabs.length === 2) {
tabs = [
{
id: 'loader',
title: 'Revisions',
icon: 'fab fa-git-alt'
},
...tabs
];
historyModeStore.set('loader');
}
});
let isOpen = false;
</script>
<Card on:select={tabSelectHandler} bind:isOpen {tabs} title="History">
<div slot="actions">
<button
id="saveHistory"
class="btn btn-xs btn-success w-12"
on:click|stopPropagation={() => saveHistory()}
title="Save current state"><i class="far fa-save" /></button>
{#if $historyModeStore !== 'loader'}
<button
id="clearHistory"
class="btn btn-xs btn-error w-12"
on:click|stopPropagation={() => clearHistory()}
title="Delete all saved states"><i class="fas fa-trash-alt" /></button>
{/if}
</div>
<ul class="p-2 space-y-2 overflow-auto h-56" id="historyList">
{#if $historyStore.length > 0}
{#each $historyStore as { state, time, name, url, type }}
<li class="rounded p-2 shadow flex-col">
<div class="flex">
<div class="flex-1">
<div class="flex flex-col text-base-content">
{#if url}
<a
href={url}
target="_blank"
title="Open revision in new tab"
class="hover:underline text-blue-500">{name}</a>
{:else}
<span>{name}</span>
{/if}
<span class="text-gray-400 text-sm">{relativeTime(time)}</span>
</div>
</div>
<div class="flex gap-2 content-center">
<button class="btn btn-success" on:click={() => restoreHistory(state)}
><i class="fas fa-undo mr-1" />Restore</button>
{#if type !== 'loader'}
<button class="btn btn-error" on:click={() => clearHistory(time)}
><i class="fas fa-trash-alt mr-1" />Delete</button>
{/if}
</div>
</div>
</li>
{/each}
{:else}
<div class="m-2">
No items in History<br />
Click the Save button to save current state and restore it later.<br />
Timeline will automatically be saved every minute.
</div>
{/if}
</ul>
</Card>
-77
View File
@@ -1,77 +0,0 @@
import { derived, writable, get } from 'svelte/store';
import type { Readable, Writable } from 'svelte/store';
import { persist, localStorage } from '@macfja/svelte-persistent-store';
import { generateSlug } from 'random-word-slugs';
import type { HistoryEntry, HistoryType } from '$lib/types';
const MAX_AUTO_HISTORY_LENGTH = 30;
export const historyModeStore: Writable<HistoryType> = persist(
writable('manual'),
localStorage(),
'autoHistoryMode'
);
const autoHistoryStore: Writable<HistoryEntry[]> = persist(
writable([]),
localStorage(),
'autoHistoryStore'
);
const manualHistoryStore: Writable<HistoryEntry[]> = persist(
writable([]),
localStorage(),
'manualHistoryStore'
);
export const loaderHistoryStore: Writable<HistoryEntry[]> = writable([] as HistoryEntry[]);
export const historyStore: Readable<HistoryEntry[]> = derived(
[historyModeStore, autoHistoryStore, manualHistoryStore, loaderHistoryStore],
([historyMode, autoHistories, manualHistories, loadedHistories], set) => {
if (historyMode === 'auto') {
set(autoHistories);
} else if (historyMode === 'manual') {
set(manualHistories);
} else if (historyMode === 'loader') {
set(loadedHistories);
} else {
set(autoHistories);
}
}
);
export const addHistoryEntry = (entry: HistoryEntry): void => {
if (entry.type === 'loader') {
loaderHistoryStore.update((entries) => [entry, ...entries]);
return;
}
entry.name = generateSlug(2);
if (entry.type !== 'auto') {
manualHistoryStore.update((entries) => [entry, ...entries]);
return;
}
autoHistoryStore.update((entries) => {
if (entries.length === MAX_AUTO_HISTORY_LENGTH) {
entries.pop();
}
return [entry, ...entries];
});
};
export const clearHistoryData = (time?: number): void => {
(get(historyModeStore) === 'auto' ? autoHistoryStore : manualHistoryStore).update((entries) => {
if (get(historyModeStore) !== 'loader') {
entries = entries.filter((entry) => time && entry.time != time);
}
return entries;
});
};
export const getPreviousState = (auto: boolean): string => {
const entries = get(auto ? autoHistoryStore : manualHistoryStore);
if (entries.length > 0) {
return JSON.stringify(entries[0].state);
}
return '';
};
-82
View File
@@ -1,82 +0,0 @@
<script context="module">
import { version } from 'mermaid/package.json';
import { analytics } from '$lib/util/stats';
analytics?.track('version', {
mermaidVersion: version
});
</script>
<script lang="ts">
import Theme from './theme.svelte';
interface Link {
title: string;
href: string;
icon?: string;
}
const links: Link[] = [
{
title: 'Documentation',
href: 'https://mermaid-js.github.io/mermaid/#/n00b-gettingStarted'
},
{
title: 'Tutorial',
href: 'https://github.com/mermaid-js/mermaid/blob/develop/docs/Tutorials.md'
},
{
title: 'Mermaid',
href: 'https://github.com/mermaid-js/mermaid'
},
{
title: 'CLI',
href: 'https://github.com/mermaid-js/mermaid-cli'
},
{
title: '',
href: 'https://github.com/mermaid-js/mermaid-live-editor',
icon: 'fab fa-github fa-lg'
}
];
</script>
<div class="navbar mb-2 shadow-lg bg-primary">
<div class="flex-1 px-2 mx-2">
<span class="text-lg font-bold">
<a href="/">Mermaid<span class="text-xs font-thin">v{version}</span> Live Editor</a>
</span>
</div>
<label for="menu-toggle" class="pointer-cursor lg:hidden block"
><svg
class="fill-current "
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
><title>Menu</title><path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z" /></svg
></label>
<input class="hidden" type="checkbox" id="menu-toggle" />
<Theme />
<div class="hidden lg:flex lg:items-center lg:w-auto w-full" id="menu">
<ul class="lg:flex items-center justify-between text-base pt-4 lg:pt-0">
{#each links as { title, href, icon }}
<li>
<a class="btn btn-ghost" target="_blank" {href}>
{#if icon}
<i class={icon} />
{/if}
{title}</a>
</li>
{/each}
</ul>
</div>
</div>
<style>
#menu-toggle:checked + #menu {
display: block;
}
.navbar {
z-index: 10000;
}
</style>
-109
View File
@@ -1,109 +0,0 @@
<script lang="ts">
import { updateCode } from '$lib/util/state';
import Card from '$lib/components/card/card.svelte';
const samples = {
'Flow Chart': `graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]`,
'Sequence Diagram': `sequenceDiagram
Alice->>+John: Hello John, how are you?
Alice->>+John: John, can you hear me?
John-->>-Alice: Hi Alice, I can hear you!
John-->>-Alice: I feel great!
`,
'Class Diagram': `classDiagram
Animal <|-- Duck
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
Animal : +String gender
Animal: +isMammal()
Animal: +mate()
class Duck{
+String beakColor
+swim()
+quack()
}
class Fish{
-int sizeInFeet
-canEat()
}
class Zebra{
+bool is_wild
+run()
}
`,
'State Diagram': `stateDiagram-v2
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
`,
'Gantt Chart': `gantt
title A Gantt Diagram
dateFormat YYYY-MM-DD
section Section
A task :a1, 2014-01-01, 30d
Another task :after a1 , 20d
section Another
Task in sec :2014-01-12 , 12d
another task : 24d
`,
'Pie Chart': `pie title Pets adopted by volunteers
"Dogs" : 386
"Cats" : 85
"Rats" : 15
`,
'ER Diagram': `erDiagram
CUSTOMER }|..|{ DELIVERY-ADDRESS : has
CUSTOMER ||--o{ ORDER : places
CUSTOMER ||--o{ INVOICE : "liable for"
DELIVERY-ADDRESS ||--o{ ORDER : receives
INVOICE ||--|{ ORDER : covers
ORDER ||--|{ ORDER-ITEM : includes
PRODUCT-CATEGORY ||--|{ PRODUCT : contains
PRODUCT ||--o{ ORDER-ITEM : "ordered in"
`,
'User Journey': ` journey
title My working day
section Go to work
Make tea: 5: Me
Go upstairs: 3: Me
Do work: 1: Me, Cat
section Go home
Go downstairs: 5: Me
Sit down: 3: Me
`,
'Git Graph': ` gitGraph
commit
commit
branch develop
checkout develop
commit
commit
checkout main
merge develop
commit
commit
`
};
const loadSampleDiagram = (diagramType: string): void => {
updateCode(samples[diagramType], true, true);
};
</script>
<Card title="Sample Diagrams" isOpen={false}>
<div class="flex gap-2 flex-wrap p-2">
{#each Object.keys(samples) as sample}
<button class="btn btn-primary normal-case btn-sm" on:click={() => loadSampleDiagram(sample)}
>{sample}</button>
{/each}
</div>
</Card>
-59
View File
@@ -1,59 +0,0 @@
<script>
import { setTheme, themeStore } from '$lib/util/theme';
const themes = [
'🌝 light',
'🌚 dark',
'🧁 cupcake',
'🐝 bumblebee',
'✳️ emerald',
'🏢 corporate',
'🌃 synthwave',
'👴 retro',
'🤖 cyberpunk',
'🌸 valentine',
'🎃 halloween',
'🌷 garden',
'🌲 forest',
'🐟 aqua',
'👓 lofi',
'🖍 pastel',
'🧚‍♀️ fantasy',
'📝 wireframe',
'🏴 black',
'💎 luxury',
'🧛‍♂️ dracula'
];
</script>
<div class="hidden lg:block dropdown">
<div tabindex="0" class="btn btn-ghost ">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="inline-block w-6 h-6 stroke-current md:mr-2"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" /></svg>
<span class="hidden md:inline"> Change Theme </span>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1792 1792"
class="inline-block w-4 h-4 ml-1 fill-current"
><path
d="M1395 736q0 13-10 23l-466 466q-10 10-23 10t-23-10l-466-466q-10-10-10-23t10-23l50-50q10-10 23-10t23 10l393 393 393-393q10-10 23-10t23 10l50 50q10 10 10 23z" /></svg>
</div>
<div
class="mt-14 overflow-y-auto shadow-2xl top-px dropdown-content h-96 w-56 bg-base-200 text-base-content">
<ul tabindex="0" class="p-4 menu compact">
{#each themes as theme}
<li class={theme.includes($themeStore.theme) ? 'bordered' : ''}>
<span class="btn btn-ghost justify-start" on:click={() => setTheme(theme)}>{theme}</span>
</li>
{/each}
</ul>
</div>
</div>
-72
View File
@@ -1,72 +0,0 @@
<script lang="ts">
import { errorStore } from '$lib/util/error';
import { codeStore } from '$lib/util/state';
import { onMount } from 'svelte';
import mermaid from 'mermaid';
let code = '';
let config = '';
let container: HTMLDivElement;
let view: HTMLDivElement;
let error = false;
let outOfSync = false;
let manualUpdate = true;
onMount(() => {
codeStore.subscribe((state) => {
try {
if (container && state && (state.updateDiagram || state.autoSync)) {
if (!state.autoSync) {
$codeStore.updateDiagram = false;
}
outOfSync = false;
manualUpdate = true;
if (code === state.code && config === state.mermaid) {
// Do not render if there is no change in Code/Config
return;
}
code = state.code;
config = state.mermaid;
const scroll = view.parentElement.scrollTop;
delete container.dataset.processed;
mermaid.initialize(Object.assign({}, JSON.parse(state.mermaid)));
mermaid.render('graph-div', code, (svgCode) => {
container.innerHTML = svgCode;
});
view.parentElement.scrollTop = scroll;
error = false;
} else if (manualUpdate) {
manualUpdate = false;
} else {
outOfSync = true;
}
} catch (e) {
console.log('view fail', e);
error = true;
}
});
errorStore.subscribe((err) => {
if (typeof err === 'undefined') {
error = false;
} else {
error = true;
console.log('Error: ', err);
}
});
});
</script>
<div id="view" bind:this={view} class="p-2" class:error class:outOfSync>
<div id="container" bind:this={container} class="flex-1 overflow-auto" />
</div>
<style>
#view {
border: 1px solor darkred;
flex: 1;
}
.error,
.outOfSync {
opacity: 0.5;
}
</style>
-67
View File
@@ -1,67 +0,0 @@
/**
* Can be made globally available by placing this
* inside `global.d.ts` and removing `export` keyword
*/
export interface Locals {
userid: string;
}
export interface EditorUpdateEvent {
text: string;
}
export interface EditorEvents {
update: EditorUpdateEvent;
}
export interface TabEvents {
select: Tab;
}
export interface Tab {
id: string;
title: string;
icon: string;
}
export interface State {
code: string;
mermaid: string;
updateEditor: boolean;
updateDiagram: boolean;
autoSync: boolean;
loader?: LoaderConfig;
}
export interface GistLoaderConfig {
url: string;
}
export interface LoadingState {
loading: boolean;
message?: string;
}
export interface FileLoaderConfig {
codeURL: string;
configURL?: string;
}
export interface LoaderConfig {
type: 'gist' | 'files';
config: GistLoaderConfig | FileLoaderConfig;
}
export type HistoryType = 'auto' | 'manual' | 'loader';
export interface HistoryEntry {
state: State;
time: number;
name?: string;
type: HistoryType;
url?: string;
}
export interface DocConfig {
[key: string]: {
code: string;
config?: string;
};
}
type Loader = (url: string) => Promise<State>;
-4
View File
@@ -1,4 +0,0 @@
export const rendererUrl: string =
(import.meta.env.MERMAID_RENDERER_URL as string) ?? 'https://mermaid.ink';
export const krokiRendererUrl: string =
(import.meta.env.MERMAID_KROKI_RENDERER_URL as string) ?? 'https://kroki.io';
-3
View File
@@ -1,3 +0,0 @@
import { writable } from 'svelte/store';
export const errorStore = writable(undefined);
-105
View File
@@ -1,105 +0,0 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { State } from '$lib/types';
import { defaultState } from '../state';
import { addHistoryEntry } from '../../components/history/history';
const codeFileName = 'code.mmd';
const configFileName = 'config.json';
const isValidGist = (files: any): boolean => {
return codeFileName in files;
};
const getFileContent = async (file: any): Promise<string> => {
if (file.truncated) {
return await (await fetch(file.raw_url)).text();
}
return file.content;
};
interface GistData {
code: string;
config?: string;
author: string;
time: number;
version: string;
url: string;
}
const getGistData = async (gistURL: string): Promise<GistData> => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, __, gistID, revisionID] = gistURL.split('github.com').pop().split('/');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { html_url, files, history } = await (
await fetch(`https://api.github.com/gists/${gistID}${revisionID ? '/' + revisionID : ''}`)
).json();
if (isValidGist(files)) {
const code = await getFileContent(files[codeFileName]);
let config: string;
if (configFileName in files) {
config = await getFileContent(files[configFileName]);
}
const currentItem = history[0];
return {
url: `${html_url}/${currentItem.version}`,
code,
config,
author: currentItem.user.login,
time: new Date(currentItem.committed_at).getTime(),
version: (currentItem.version as string).slice(-7)
};
} else {
throw 'Invalid gist provided';
}
};
const getStateFromGist = (gist: GistData, gistURL: string = gist.url): State => {
const state: State = {
...defaultState,
code: gist.code,
loader: {
type: 'gist',
config: {
url: gistURL
}
}
};
gist.config && (state.mermaid = gist.config);
return state;
};
export const loadGistData = async (gistURL: string): Promise<State> => {
try {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, __, gistID, revisionID] = gistURL.split('github.com').pop().split('/');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { history } = await (
await fetch(`https://api.github.com/gists/${gistID}${revisionID ? '/' + revisionID : ''}`)
).json();
const gistHistory: GistData[] = [];
for (const entry of history) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const data: GistData = await getGistData(entry.url).catch(() => undefined);
data && gistHistory.push(data);
}
if (gistHistory.length === 0) {
throw 'Invalid gist provided';
}
gistHistory.reverse();
const state = getStateFromGist(gistHistory.slice(-1).pop(), gistURL);
for (const gist of gistHistory) {
addHistoryEntry({
state: getStateFromGist(gist),
time: gist.time,
type: 'loader',
url: gist.url,
name: `${gist.author} v${gist.version}`
});
}
return state;
} catch (err) {
console.error(err);
}
};
-58
View File
@@ -1,58 +0,0 @@
import { loadGistData } from './gist';
import { updateCodeStore, defaultState } from '../state';
import type { Loader, State } from '$lib/types';
const loaders: Record<string, Loader> = {
gist: loadGistData
};
export const loadDataFromUrl = async (): Promise<void> => {
const searchParams = new URLSearchParams(window.location.search);
let state: State = defaultState;
let code: string, config: string;
let loaded = false;
const codeURL: string = searchParams.get('code');
const configURL: string = searchParams.get('config');
if (codeURL) {
code = await (await fetch(codeURL)).text();
loaded = true;
}
if (configURL) {
config = await (await fetch(configURL)).text();
} else {
config = defaultState.mermaid;
}
if (!code) {
for (const [key, value] of searchParams.entries()) {
if (key in loaders) {
try {
state = await loaders[key](value);
loaded = true;
break;
} catch (err) {
console.error(err);
}
}
}
} else {
state = {
code,
mermaid: config,
loader: {
type: 'files',
config: {
codeURL,
configURL
}
}
} as State;
}
loaded &&
updateCodeStore({
...state,
autoSync: true,
updateDiagram: true,
updateEditor: true
});
// window.location.search = '';
};
-20
View File
@@ -1,20 +0,0 @@
import { writable } from 'svelte/store';
import type { Writable } from 'svelte/store';
import type { LoadingState } from '$lib/types';
const defaultLoading: LoadingState = {
loading: false
};
export const loadingStateStore: Writable<LoadingState> = writable(defaultLoading);
export const initLoading = async <T>(message: string, task: Promise<T>): Promise<T> => {
loadingStateStore.set({
loading: true,
message
});
const result: T = await task;
loadingStateStore.set({
loading: false
});
return result;
};
-7
View File
@@ -1,7 +0,0 @@
export const notify = (message: string): void => {
alert(message);
};
export const prompt = (message: string): boolean => {
return confirm(message);
};
-61
View File
@@ -1,61 +0,0 @@
import { deflate, inflate } from 'pako';
import { toUint8Array, fromUint8Array, toBase64, fromBase64 } from 'js-base64';
import type { State } from '$lib/types';
interface Serde {
serialize: (state: string) => string;
deserialize: (state: string) => string;
}
const base64Serde: Serde = {
serialize: (state: string): string => {
return toBase64(state, true);
},
deserialize: (state: string): string => {
return fromBase64(state);
}
};
export const pakoSerde: Serde = {
serialize: (state: string): string => {
const data = new TextEncoder().encode(state);
const compressed = deflate(data, { level: 9 });
return fromUint8Array(compressed, true);
},
deserialize: (state: string): string => {
const data = toUint8Array(state);
return inflate(data, { to: 'string' });
}
};
const serdes: { [key: string]: Serde } = {
base64: base64Serde,
pako: pakoSerde
};
type SerdeType = keyof typeof serdes;
export const serializeState = (state: State): string => {
const json = JSON.stringify(state);
const defaultSerde: SerdeType = 'pako';
const serialized = serdes[defaultSerde].serialize(json);
return `${defaultSerde}:${serialized}`;
};
export const deserializeState = (state: string): State => {
let type: SerdeType, serialized: string;
if (state.includes(':')) {
let tempType: string;
[tempType, serialized] = state.split(':');
if (tempType in serdes) {
type = tempType;
} else {
throw new Error(`Unknown serde type: ${tempType}`);
}
} else {
type = 'base64';
serialized = state;
}
const json = serdes[type].deserialize(serialized);
return JSON.parse(json) as State;
};
-125
View File
@@ -1,125 +0,0 @@
import { writable, get, derived } from 'svelte/store';
import type { Readable } from 'svelte/store';
import { persist, localStorage } from '@macfja/svelte-persistent-store';
import type { State } from '$lib/types';
import { saveStatistics } from './stats';
import { serializeState, deserializeState } from './serde';
export const defaultState: State = {
code: `graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
mermaid: JSON.stringify(
{
theme: 'default'
},
null,
2
),
updateEditor: false,
autoSync: true,
updateDiagram: true
};
const urlParseFailedState = `graph TD
A[Loading URL failed. We can try to figure out why.] -->|Decode JSON| B(Please check the console to see the JSON and error details.)
B --> C{Is the JSON correct?}
C -->|Yes| D(Please Click here to Raise an issue in github.<br/>Including the broken link in the issue <br/> will speed up the fix.)
C -->|No| E{Did someone <br/>send you this link?}
E -->|Yes| F[Ask them to send <br/>you the complete link]
E -->|No| G{Did you copy <br/> the complete URL?}
G --> |Yes| D
G --> |"No :("| H(Try using the Timeline tab in History <br/>from same browser you used to create the diagram.)
click D href "https://github.com/mermaid-js/mermaid-live-editor/issues/new?assignees=&labels=bug&template=bug_report.md&title=Broken%20link" "Raise issue"`;
export const codeStore = persist(writable(defaultState), localStorage(), 'codeStore');
export const serializedState: Readable<string> = derived([codeStore], ([code], set) => {
set(serializeState(code));
});
export const loadState = (data: string): void => {
let state: State;
console.log('Loading', data);
try {
state = deserializeState(data);
const mermaidConfig: { [key: string]: string } =
typeof state.mermaid === 'string' ? JSON.parse(state.mermaid) : state.mermaid;
if (
mermaidConfig.securityLevel &&
mermaidConfig.securityLevel !== 'strict' &&
confirm(
`Removing "securityLevel":"${mermaidConfig.securityLevel}" from the config for safety.\nClick Cancel if you trust the source of this Diagram.`
)
) {
delete mermaidConfig.securityLevel; // Prevent setting overriding securityLevel when loading state to mitigate possible XSS attack
}
state.mermaid = JSON.stringify(mermaidConfig, null, 2);
} catch (e) {
state = get(codeStore);
if (data) {
console.error('Init error', e);
state.code = urlParseFailedState;
}
}
updateCodeStore({ ...state, updateEditor: true });
};
export const updateCodeStore = (newState: State): void => {
codeStore.update((state) => {
return { ...state, ...newState };
});
};
let prompted = false;
export const updateCode = (code: string, updateEditor: boolean, updateDiagram = false): void => {
saveStatistics(code);
const lines = (code.match(/\n/g) || '').length + 1;
if (lines > 50 && !prompted && get(codeStore).autoSync) {
const turnOff = confirm(
'Long diagram detected. Turn off Auto Sync? Click the sync logo to manually sync.'
);
prompted = true;
if (turnOff) {
updateCodeStore({
autoSync: false
} as State);
}
}
codeStore.update((state) => {
return { ...state, code, updateEditor, updateDiagram };
});
};
export const updateConfig = (config: string, updateEditor: boolean): void => {
codeStore.update((state) => {
return { ...state, mermaid: config, updateEditor };
});
};
export const toggleDarkTheme = (dark: boolean): void => {
codeStore.update((state) => {
const config = JSON.parse(state.mermaid);
if (!config.theme || ['dark', 'default'].includes(config.theme)) {
config.theme = dark ? 'dark' : 'default';
}
return { ...state, mermaid: JSON.stringify(config, null, 2), updateEditor: true };
});
};
export const initURLSubscription = (): void => {
serializedState.subscribe((state: string) => {
history.replaceState(undefined, undefined, `#${state}`);
});
};
export const getStateString = (): string => {
return JSON.stringify(get(codeStore));
};
-49
View File
@@ -1,49 +0,0 @@
import { browser } from '$app/env';
import type { AnalyticsInstance } from 'analytics';
export let analytics: AnalyticsInstance;
export const initAnalytics = async (): Promise<void> => {
if (browser && !analytics) {
try {
const { Analytics } = await import('analytics');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const googleAnalytics = await import('@analytics/google-analytics');
analytics = Analytics({
app: 'mermaid-live-editor',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
plugins: [
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
googleAnalytics.init({
trackingId: 'UA-153180559-1'
})
]
});
} catch {
console.info('Analytics blocked ;)');
}
}
};
const detectType = (text: string): string => {
return text
.replace(/^\s*%%.*\n/g, '\n')
.trimStart()
.split(' ')[0];
};
// manual debounce
let timeout;
export const saveStatistics = (graph: string): void => {
if (analytics) {
clearTimeout(timeout);
// Only save statistics after a 5 sec delay
timeout = setTimeout(function () {
const graphType = detectType(graph);
console.debug('ga:', 'send', 'event', 'render', graphType);
void analytics.track('render', {
graphType
});
}, 5000);
}
};
-36
View File
@@ -1,36 +0,0 @@
import { writable } from 'svelte/store';
import type { Writable } from 'svelte/store';
import { persist, localStorage } from '@macfja/svelte-persistent-store';
export interface ThemeConfig {
isDark: boolean;
theme?: string;
}
export const themeStore: Writable<ThemeConfig> = persist(
writable({
isDark: false
}),
localStorage(),
'themeStore'
);
const darkThemes = [
'dark',
'synthwave',
'halloween',
'aqua',
'forest',
'luxury',
'black',
'dracula'
];
export const setTheme = (theme: string): void => {
if (theme.includes(' ')) {
theme = theme.split(' ')[1].trim();
}
const isDark = darkThemes.includes(theme);
console.log('Setting theme', theme);
themeStore.set({ theme, isDark });
};
-24
View File
@@ -1,24 +0,0 @@
import type { State } from '$lib/types';
import { initURLSubscription, loadState, updateCodeStore } from './state';
import { analytics, initAnalytics } from './stats';
import { loadDataFromUrl } from './fileLoaders/loader';
import { initLoading } from './loading';
export const loadStateFromURL = (): void => {
loadState(window.location.hash.slice(1));
};
export const syncDiagram = (): void => {
updateCodeStore({
updateDiagram: true
} as State);
};
export const initHandler = async (): Promise<void> => {
loadStateFromURL();
await initLoading('Loading Gist...', loadDataFromUrl().catch(console.error));
syncDiagram();
initURLSubscription();
await initAnalytics();
analytics?.page();
};
+12
View File
@@ -0,0 +1,12 @@
import App from './App.svelte';
const app = new App({
target: document.body,
props: {
name: 'world',
},
});
window.app = app;
export default app;
@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { registerLanguage } from 'monaco-editor/dev/vs/basic-languages/_.contribution.js';
registerLanguage({
id: 'mermaid',
extensions: ['.mmd'],
aliases: ['mermaid', 'MERMAID'],
loader: function () {
return import('./mermaid.js');
},
});
+498
View File
@@ -0,0 +1,498 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export var conf = {
comments: {
lineComment: '*',
},
brackets: [
['[', ']'],
['(', ')'],
],
};
var abapKeywords = [
'abstract',
'add',
'add-corresponding',
'adjacent',
'alias',
'aliases',
'all',
'append',
'appending',
'ascending',
'as',
'assert',
'assign',
'assigned',
'assigning',
'association',
'authority-check',
'back',
'begin',
'binary',
'block',
'bound',
'break-point',
'by',
'byte',
'class',
'call',
'cast',
'changing',
'check',
'class-data',
'class-method',
'class-methods',
'clear',
'close',
'cnt',
'collect',
'commit',
'cond',
'character',
'corresponding',
'communication',
'component',
'compute',
'concatenate',
'condense',
'constants',
'conv',
'count',
'controls',
'convert',
'create',
'currency',
'data',
'descending',
'default',
'define',
'deferred',
'delete',
'describe',
'detail',
'display',
'divide',
'divide-corresponding',
'display-mode',
'duplicates',
'deleting',
'editor-call',
'end',
'endexec',
'endfunction',
'ending',
'endmodule',
'end-of-definition',
'end-of-page',
'end-of-selection',
'end-test-injection',
'end-test-seam',
'exit-command',
'endclass',
'endmethod',
'endform',
'endinterface',
'endprovide',
'endselect',
'endtry',
'endwhile',
'enum',
'event',
'events',
'exec',
'exit',
'export',
'exporting',
'extract',
'exception',
'exceptions',
'field-symbols',
'field-groups',
'field',
'first',
'fetch',
'fields',
'format',
'frame',
'free',
'from',
'function',
'find',
'for',
'found',
'function-pool',
'generate',
'get',
'handle',
'hide',
'hashed',
'include',
'import',
'importing',
'index',
'infotypes',
'initial',
'initialization',
'id',
'is',
'in',
'interface',
'interfaces',
'init',
'input',
'insert',
'instance',
'into',
'key',
'left-justified',
'leave',
'like',
'line',
'line-count',
'line-size',
'load',
'local',
'log-point',
'length',
'left',
'leading',
'lower',
'matchcode',
'method',
'mesh',
'message',
'message-id',
'methods',
'modify',
'module',
'move',
'move-corresponding',
'multiply',
'multiply-corresponding',
'match',
'new',
'new-line',
'new-page',
'new-section',
'next',
'no',
'no-gap',
'no-gaps',
'no-sign',
'no-zero',
'non-unique',
'number',
'occurrence',
'object',
'obligatory',
'of',
'output',
'overlay',
'optional',
'others',
'occurrences',
'occurs',
'offset',
'options',
'pack',
'parameters',
'perform',
'places',
'position',
'print-control',
'private',
'program',
'protected',
'provide',
'public',
'put',
'radiobutton',
'raising',
'ranges',
'receive',
'receiving',
'redefinition',
'reduce',
'reference',
'refresh',
'regex',
'reject',
'results',
'requested',
'ref',
'replace',
'report',
'reserve',
'restore',
'result',
'return',
'returning',
'right-justified',
'rollback',
'read',
'read-only',
'rp-provide-from-last',
'run',
'scan',
'screen',
'scroll',
'search',
'select',
'select-options',
'selection-screen',
'stamp',
'source',
'subkey',
'separated',
'set',
'shift',
'single',
'skip',
'sort',
'sorted',
'split',
'standard',
'stamp',
'starting',
'start-of-selection',
'sum',
'subtract-corresponding',
'statics',
'step',
'stop',
'structure',
'submatches',
'submit',
'subtract',
'summary',
'supplied',
'suppress',
'section',
'syntax-check',
'syntax-trace',
'system-call',
'switch',
'tables',
'table',
'task',
'testing',
'test-seam',
'test-injection',
'then',
'time',
'times',
'title',
'titlebar',
'to',
'top-of-page',
'trailing',
'transfer',
'transformation',
'translate',
'transporting',
'types',
'type',
'type-pool',
'type-pools',
'unassign',
'unique',
'uline',
'unpack',
'update',
'upper',
'using',
'value',
'when',
'while',
'window',
'write',
'where',
'with',
'work',
'at',
'case',
'catch',
'continue',
'do',
'elseif',
'else',
'endat',
'endcase',
'enddo',
'endif',
'endloop',
'endon',
'if',
'loop',
'on',
'raise',
'try',
'abs',
'sign',
'ceil',
'floor',
'trunc',
'frac',
'acos',
'asin',
'atan',
'cos',
'sin',
'tan',
'cosh',
'sinh',
'tanh',
'exp',
'log',
'log10',
'sqrt',
'strlen',
'xstrlen',
'charlen',
'lines',
'numofchar',
'dbmaxlen',
'round',
'rescale',
'nmax',
'nmin',
'cmax',
'cmin',
'boolc',
'boolx',
'xsdbool',
'contains',
'contains_any_of',
'contains_any_not_of',
'matches',
'line_exists',
'ipow',
'char_off',
'count',
'count_any_of',
'count_any_not_of',
'distance',
'condense',
'concat_lines_of',
'escape',
'find',
'find_end',
'find_any_of',
'find_any_not_of',
'insert',
'match',
'repeat',
'replace',
'reverse',
'segment',
'shift_left',
'shift_right',
'substring',
'substring_after',
'substring_from',
'substring_before',
'substring_to',
'to_upper',
'to_lower',
'to_mixed',
'from_mixed',
'translate',
'bit-set',
'line_index',
'definition',
'implementation',
'public',
'inheriting',
'final',
];
export var language = {
defaultToken: 'invalid',
ignoreCase: true,
tokenPostfix: '.abap',
keywords: abapKeywords,
typeKeywords: [
'abap_bool',
'string',
'xstring',
'any',
'clike',
'csequence',
'numeric',
'xsequence',
'c',
'n',
'i',
'p',
'f',
'd',
't',
'x',
],
operators: [
'+',
'-',
'/',
'*',
'=',
'<',
'>',
'<=',
'>=',
'<>',
'><',
'=<',
'=>',
'EQ',
'NE',
'GE',
'LE',
'CS',
'CN',
'CA',
'CO',
'CP',
'NS',
'NA',
'NP',
],
symbols: /[=><!~?&+\-*\/\^%]+/,
tokenizer: {
root: [
[
/[a-z_$][\w$]*/,
{
cases: {
'@typeKeywords': 'keyword',
'@keywords': 'keyword',
'@default': 'identifier',
},
},
],
{ include: '@whitespace' },
[/[:,.]/, 'delimiter'],
[/[{}()\[\]]/, '@brackets'],
[/@symbols/, { cases: { '@operators': 'operator', '@default': '' } }],
[/'/, { token: 'string', bracket: '@open', next: '@stringquote' }],
[/\|/, { token: 'string', bracket: '@open', next: '@stringtemplate' }],
[/\d+/, 'number'],
],
stringtemplate: [
[/[^\\\|]+/, 'string'],
[/\\\|/, 'string'],
[/\|/, { token: 'string', bracket: '@close', next: '@pop' }],
],
stringquote: [
[/[^\\']+/, 'string'],
[/'/, { token: 'string', bracket: '@close', next: '@pop' }],
],
whitespace: [
[/[ \t\r\n]+/, ''],
[/^\*.*$/, 'comment'],
[/\".*$/, 'comment'],
],
},
};

Some files were not shown because too many files have changed in this diff Show More