Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
687ac6428b | ||
|
|
45c5958b56 | ||
|
|
15a90149be | ||
|
|
649bfee23c | ||
|
|
113c50e255 | ||
|
|
4804046d9f | ||
|
|
5a8f28b229 | ||
|
|
0e38b66461 | ||
|
|
5ea4267768 | ||
|
|
30651c41de | ||
|
|
2868870065 | ||
|
|
4ed439b3ad | ||
|
|
d2673d5326 | ||
|
|
a14c9a7e9d | ||
|
|
2ecd56fe8a | ||
|
|
41c1ed8c5d | ||
|
|
efdfba2812 | ||
|
|
eb33a59ac2 | ||
|
|
89c64ae0cb | ||
|
|
e094efabf6 | ||
|
|
2b757cc433 | ||
|
|
b890622543 | ||
|
|
cf3457a983 | ||
|
|
40b21f05de | ||
|
|
91fe1911fb | ||
|
|
6fc2968901 | ||
|
|
61fa0bda71 | ||
|
|
55149fa616 | ||
|
|
0dc8582bd6 | ||
|
|
7517614429 | ||
|
|
1cc55e210a | ||
|
|
1f0e6e7a36 | ||
|
|
548f3206ad | ||
|
|
782cf66274 | ||
|
|
21049c7fff | ||
|
|
6b0183fbe9 | ||
|
|
94c4ea3322 | ||
|
|
9ba0b53a10 | ||
|
|
86be314d2c | ||
|
|
07a2bb8ca1 | ||
|
|
d683a80f40 | ||
|
|
6d47808298 | ||
|
|
f9d1c55f25 | ||
|
|
d9b475a82f | ||
|
|
0b938cefa8 | ||
|
|
2637621671 | ||
|
|
44281abb4e | ||
|
|
007e6f1896 | ||
|
|
479fe86ca5 | ||
|
|
deb449cf4f | ||
|
|
ebd7a9cdb4 | ||
|
|
5e5c869ada | ||
|
|
6532a9a947 | ||
|
|
36c16781b5 | ||
|
|
2c4b8246c8 | ||
|
|
cacf434709 | ||
|
|
fa8a261fed | ||
|
|
dab9761e6e | ||
|
|
c32d5e565d | ||
|
|
2328294f1b | ||
|
|
7abb08cdff | ||
|
|
570c2f4c8b | ||
|
|
8c9e29c3da | ||
|
|
5a806c0d56 | ||
|
|
740c9c827e | ||
|
|
2e8ac60684 | ||
|
|
339c023447 | ||
|
|
ca9d6faaed | ||
|
|
40a0c22d56 | ||
|
|
129c296121 | ||
|
|
7491d8fd99 | ||
|
|
3db6543024 | ||
|
|
26afd28a85 | ||
|
|
fbb93ab813 | ||
|
|
2a24199756 | ||
|
|
52afad3e97 | ||
|
|
1810dad5f9 | ||
|
|
b40506bd9f | ||
|
|
dc9cff4a2d | ||
|
|
b4f3e42da3 | ||
|
|
c7e8e8625f | ||
|
|
5e94ea222c | ||
|
|
8e6898b5d5 | ||
|
|
7d1c3a153f | ||
|
|
7641b2e25c | ||
|
|
f9b92d6026 | ||
|
|
952791cb5f | ||
|
|
d4110403e6 | ||
|
|
533970d83f | ||
|
|
bcd6bb5f11 | ||
|
|
d40d476ea1 | ||
|
|
90bc10577e | ||
|
|
1e5a516679 | ||
|
|
c213366181 | ||
|
|
6d0919fcda | ||
|
|
5818db68f7 | ||
|
|
8a0a2c83eb | ||
|
|
2c516f395e | ||
|
|
7738e3ee48 | ||
|
|
f4fd865c81 | ||
|
|
8bbfa59a32 | ||
|
|
70ed3a4700 | ||
|
|
0fd98ce271 | ||
|
|
4e37ea9c8f | ||
|
|
47bf6b8867 | ||
|
|
c3ed8ae636 | ||
|
|
cd42d0dee0 | ||
|
|
dc47b749f2 | ||
|
|
c0215f0e04 | ||
|
|
644faf8700 | ||
|
|
830ecaa8af | ||
|
|
17a1ce6548 | ||
|
|
a5cf1a1cd6 | ||
|
|
23ffef7843 | ||
|
|
ec23d055cc | ||
|
|
0897aca3b9 | ||
|
|
05bcfb13ca | ||
|
|
3402c86bf2 | ||
|
|
5a307c3547 | ||
|
|
8c46f9a892 | ||
|
|
48f5853d03 | ||
|
|
8b39836353 | ||
|
|
3c6b14cee3 | ||
|
|
015e112e6b | ||
|
|
cb6bf6570d | ||
|
|
8ee71a6f08 | ||
|
|
7ad1c2098c | ||
|
|
2c0d7db7cc | ||
|
|
5ce24780c0 | ||
|
|
ad1cec76c7 | ||
|
|
bdd6ca2c3d | ||
|
|
9f05ec4460 | ||
|
|
6701dd2dd7 | ||
|
|
5bcb495569 | ||
|
|
4bdb5a0750 | ||
|
|
51c9488d0e | ||
|
|
65a1fa72a0 | ||
|
|
abc63488a1 | ||
|
|
a49819e120 | ||
|
|
980706b487 | ||
|
|
7fc789ab0f | ||
|
|
f245180882 | ||
|
|
bb7b4d3f78 | ||
|
|
d617524172 | ||
|
|
834db65ecd | ||
|
|
4238f1285c | ||
|
|
bba0ae4ba1 | ||
|
|
f612a5c703 | ||
|
|
0b6dbf6857 | ||
|
|
1358185692 | ||
|
|
b8d4ada693 | ||
|
|
b34942355f | ||
|
|
c8e217d0c9 | ||
|
|
536db58698 | ||
|
|
d6d987e962 | ||
|
|
4ae314c5c0 | ||
|
|
6868b0d761 | ||
|
|
0dd010336a | ||
|
|
d7e7121666 | ||
|
|
bfa35ca86e | ||
|
|
d7a6ac2aa7 | ||
|
|
3f843f29bf | ||
|
|
bcb8031c8f | ||
|
|
b60718a18e | ||
|
|
db5ce6b74b | ||
|
|
a1a085e526 | ||
|
|
6675267d45 | ||
|
|
00bdd4500e | ||
|
|
7a04384b7d | ||
|
|
ea0e8b9883 | ||
|
|
7ef1f1325b | ||
|
|
3b5af96ab0 | ||
|
|
3a4c437536 | ||
|
|
4de2c97fbe | ||
|
|
dcaa313c0b | ||
|
|
2f7b0973fd | ||
|
|
354ae063d7 | ||
|
|
8d727e5cac | ||
|
|
8957627cfc | ||
|
|
2c31be0b02 | ||
|
|
626a600dae | ||
|
|
045ab4df22 | ||
|
|
8956201dab | ||
|
|
0f63e777ae | ||
|
|
19a6190b56 | ||
|
|
c5348e9a81 | ||
|
|
7983b5d94c | ||
|
|
ff48d906ae | ||
|
|
ae2880c8a2 | ||
|
|
ca07ed764d | ||
|
|
76a70be939 | ||
|
|
56cd1b5a78 | ||
|
|
3b63e9a9aa | ||
|
|
b73afb400b | ||
|
|
a1b01cae2c | ||
|
|
64b213aad9 | ||
|
|
ffe260893d | ||
|
|
166d08532b | ||
|
|
a67481a4e7 | ||
|
|
b4814d7b13 | ||
|
|
45b1c9f32c | ||
|
|
2acde71f37 | ||
|
|
73fc573a2d | ||
|
|
d5a433bcdc | ||
|
|
c5de0dca9f | ||
|
|
76aef33868 | ||
|
|
dec0c58092 | ||
|
|
9801dd606d | ||
|
|
b762d1a44a | ||
|
|
88f9c3b6ea | ||
|
|
29ffa1a0a4 | ||
|
|
103f02bb90 | ||
|
|
bf8160e31e | ||
|
|
5961c7d451 | ||
|
|
cc3f4fabc2 | ||
|
|
aed37c19c7 | ||
|
|
43bac28b30 | ||
|
|
ed2057312a | ||
|
|
fdff260f7f | ||
|
|
f5878feaa5 | ||
|
|
aef1afe3f4 | ||
|
|
f2cf3e8111 | ||
|
|
32351ab24e | ||
|
|
bd3b39a2c5 | ||
|
|
6708615c48 |
+13
-3
@@ -7,8 +7,17 @@ module.exports = {
|
||||
// 'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
'prettier'
|
||||
],
|
||||
plugins: ['svelte3', 'tailwindcss', '@typescript-eslint'],
|
||||
ignorePatterns: ['docs/*', '*.cjs', '*.md', 'snapshots.js', 'svelte.config.js', 'package.json'],
|
||||
plugins: ['svelte3', 'tailwindcss', '@typescript-eslint', 'es'],
|
||||
ignorePatterns: [
|
||||
'docs/*',
|
||||
'*.cjs',
|
||||
'*.md',
|
||||
'snapshots.js',
|
||||
'svelte.config.js',
|
||||
'renovate.json',
|
||||
'package.json',
|
||||
'tsconfig.json'
|
||||
],
|
||||
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
||||
settings: {
|
||||
'svelte3/typescript': () => require('typescript')
|
||||
@@ -33,6 +42,7 @@ module.exports = {
|
||||
}
|
||||
],
|
||||
'@typescript-eslint/no-unsafe-member-access': 'off',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'off'
|
||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||
'es/no-regexp-lookbehind-assertions': 'error'
|
||||
}
|
||||
};
|
||||
|
||||
+17
-5
@@ -1,8 +1,20 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'npm'
|
||||
directory: '/'
|
||||
target-branch: 'develop'
|
||||
- package-ecosystem: docker
|
||||
directory: /
|
||||
target-branch: develop
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
day: 'friday'
|
||||
interval: weekly
|
||||
day: friday
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
target-branch: develop
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: friday
|
||||
- package-ecosystem: npm
|
||||
directory: /
|
||||
target-branch: develop
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: friday
|
||||
|
||||
@@ -9,6 +9,7 @@ on:
|
||||
jobs:
|
||||
cypress-run:
|
||||
runs-on: ubuntu-latest
|
||||
# Update after https://github.com/cypress-io/cypress-docker-images/issues/487
|
||||
container: cypress/browsers:node14.16.0-chrome90-ff88
|
||||
|
||||
steps:
|
||||
|
||||
@@ -6,13 +6,14 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '16'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
@@ -45,6 +46,7 @@ jobs:
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
if: ${{ github.ref == 'refs/heads/master' }}
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./docs
|
||||
|
||||
@@ -8,7 +8,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
- uses: actions/stale@v4
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
exempt-issue-labels: 'retained'
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
# Stop : press ctrl + c
|
||||
# or
|
||||
# docker stop mermaid-live-editor
|
||||
FROM node:14.17.0 as mermaid-live-editor-builder
|
||||
FROM node:16.9.1 as mermaid-live-editor-builder
|
||||
COPY --chown=node:node . /home
|
||||
WORKDIR /home
|
||||
RUN yarn install
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[](https://dashboard.cypress.io/projects/2ckppp/runs) [](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE)
|
||||
[](https://dashboard.cypress.io/projects/2ckppp/runs) [](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE)[](https://app.netlify.com/sites/mermaidjs/deploys)
|
||||
|
||||
# Contributors are welcome!
|
||||
|
||||
@@ -17,7 +17,7 @@ Edit, preview and share mermaid charts/diagrams.
|
||||
|
||||
## Live demo
|
||||
|
||||
You can try out a live version [here](https://mermaid-js.github.io/mermaid-live-editor/).
|
||||
You can try out a live version [here](https://mermaid.live/).
|
||||
|
||||
## Docker
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
describe('Check actions', () => {
|
||||
// eslint-disable-next-line mocha/no-hooks-for-single-case
|
||||
beforeEach(() => {
|
||||
cy.clearLocalStorage();
|
||||
cy.visit('/edit');
|
||||
cy.contains('Actions').click();
|
||||
});
|
||||
it('should update markdown code', () => {
|
||||
cy.get('#markdown')
|
||||
@@ -17,4 +15,10 @@ describe('Check actions', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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!!');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
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');
|
||||
@@ -43,7 +47,6 @@ describe('Save History', () => {
|
||||
});
|
||||
|
||||
it('should auto save history', () => {
|
||||
cy.clock();
|
||||
cy.get('#editor').type(' C --> HistoryTest');
|
||||
cy.tick(70000);
|
||||
cy.contains('Timeline').click();
|
||||
|
||||
@@ -32,6 +32,35 @@ describe('Site Loads', () => {
|
||||
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();
|
||||
});
|
||||
|
||||
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}`,
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
+35
-2
@@ -4,10 +4,19 @@ module.exports = {
|
||||
"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\":false}"
|
||||
"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": "8.2.0",
|
||||
"__version": "8.4.0",
|
||||
"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}"
|
||||
@@ -15,5 +24,29 @@ module.exports = {
|
||||
"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}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-19
@@ -15,45 +15,47 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cypress/snapshot": "^2.1.7",
|
||||
"@sveltejs/adapter-static": "1.0.0-next.16",
|
||||
"@sveltejs/adapter-static": "1.0.0-next.19",
|
||||
"@sveltejs/kit": "1.0.0-next.147",
|
||||
"@types/mermaid": "^8.2.7",
|
||||
"@typescript-eslint/eslint-plugin": "^4.29.2",
|
||||
"@typescript-eslint/parser": "^4.29.2",
|
||||
"autoprefixer": "^10.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.31.1",
|
||||
"@typescript-eslint/parser": "^4.31.1",
|
||||
"autoprefixer": "^10.3.4",
|
||||
"chai": "^4.3.4",
|
||||
"cssnano": "^5.0.8",
|
||||
"cypress": "8.3.0",
|
||||
"cypress": "8.4.0",
|
||||
"cypress-localstorage-commands": "^1.5.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"eslint-plugin-cypress": "^2.11.3",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-es": "^4.1.0",
|
||||
"eslint-plugin-mocha": "^9.0.0",
|
||||
"eslint-plugin-postcss-modules": "^1.2.1",
|
||||
"eslint-plugin-svelte3": "^3.2.0",
|
||||
"eslint-plugin-svelte3": "^3.2.1",
|
||||
"eslint-plugin-tailwindcss": "^1.14.3",
|
||||
"husky": "^7.0.1",
|
||||
"husky": "^7.0.2",
|
||||
"lint-staged": "^11.1.2",
|
||||
"mocha": "^9.1.0",
|
||||
"mocha": "^9.1.1",
|
||||
"postcss": "^8.3.6",
|
||||
"postcss-load-config": "^3.1.0",
|
||||
"prettier": "~2.3.2",
|
||||
"prettier-plugin-svelte": "^2.3.1",
|
||||
"svelte": "^3.42.2",
|
||||
"svelte-preprocess": "^4.7.4",
|
||||
"tailwindcss": "^2.2.7",
|
||||
"prettier": "~2.4.1",
|
||||
"prettier-plugin-svelte": "^2.4.0",
|
||||
"svelte": "^3.42.6",
|
||||
"svelte-preprocess": "^4.9.4",
|
||||
"tailwindcss": "^2.2.15",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.3.5"
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@analytics/google-analytics": "^0.5.3",
|
||||
"@macfja/svelte-persistent-store": "^1.2.0",
|
||||
"analytics": "^0.7.13",
|
||||
"js-base64": "^3.6.1",
|
||||
"mermaid": "^8.11.4",
|
||||
"analytics": "^0.7.14",
|
||||
"daisyui": "^1.14.0",
|
||||
"js-base64": "^3.7.1",
|
||||
"mermaid": "^8.12.1",
|
||||
"moment": "^2.29.1",
|
||||
"monaco-editor": "^0.25.2",
|
||||
"monaco-editor": "^0.27.0",
|
||||
"random-word-slugs": "^0.0.5"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
+6
-6
@@ -8,7 +8,7 @@
|
||||
name="og:image"
|
||||
content="https://github.com/mermaid-js/mermaid/raw/develop/img/header.png"
|
||||
/>
|
||||
<link rel="canonical" href="https://mermaid-js.github.io/mermaid-live-editor/" />
|
||||
<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."
|
||||
@@ -21,27 +21,27 @@
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.25.2/min/vs/editor/editor.main.min.css"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.27.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.25.2/min/vs' }
|
||||
paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.27.0/min/vs' }
|
||||
};
|
||||
</script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.25.2/min/vs/loader.min.js"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.27.0/min/vs/loader.min.js"
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.25.2/min/vs/editor/editor.main.nls.min.js"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.27.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.25.2/min/vs/editor/editor.main.js"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.27.0/min/vs/editor/editor.main.js"
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
|
||||
+3
-7
@@ -2,13 +2,9 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.nav-btn {
|
||||
@apply lg:p-2 py-3 px-0 block border-b-2 border-transparent hover:border-white;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply bg-indigo-500 hover:bg-indigo-700 rounded px-4 shadow;
|
||||
.input {
|
||||
@apply flex-1 border-primary border-solid border-2 rounded;
|
||||
}
|
||||
.action-btn {
|
||||
@apply rounded p-2 bg-indigo-400 shadow flex-auto text-white hover:bg-indigo-500;
|
||||
@apply btn btn-primary;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/env';
|
||||
|
||||
import Card from '$lib/components/card/card.svelte';
|
||||
import type { State } from '$lib/types';
|
||||
import { codeStore } from '$lib/util/state';
|
||||
@@ -105,17 +107,36 @@
|
||||
);
|
||||
};
|
||||
|
||||
const onCopyMarkdown = (event: Event) => {
|
||||
event.target.select();
|
||||
const onCopyMarkdown = () => {
|
||||
document.getElementById('markdown').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 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;
|
||||
}
|
||||
codeStore.subscribe((state: State) => {
|
||||
const stateCopy = JSON.parse(JSON.stringify(state));
|
||||
if (typeof stateCopy.mermaid === 'string') {
|
||||
@@ -128,42 +149,77 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<Card title="Actions" isOpen={false}>
|
||||
<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" /> Copy Image to clipboard
|
||||
><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" /> PNG
|
||||
<i class="fas fa-download mr-2" /> PNG
|
||||
</button>
|
||||
<button class="action-btn flex-auto" on:click={onDownloadSVG}>
|
||||
<i class="fas fa-download" /> SVG
|
||||
<i class="fas fa-download mr-2" /> SVG
|
||||
</button>
|
||||
<button class="action-btn flex-auto">
|
||||
<a target="_blank" href={iUrl}><i class="fas fa-external-link-alt" /> PNG</a>
|
||||
<a target="_blank" href={iUrl}><i class="fas fa-external-link-alt mr-2" /> PNG</a>
|
||||
</button>
|
||||
<button class="action-btn flex-auto">
|
||||
<a target="_blank" href={svgUrl}><i class="fas fa-external-link-alt" /> SVG</a>
|
||||
<a target="_blank" href={svgUrl}><i class="fas fa-external-link-alt mr-2" /> SVG</a>
|
||||
</button>
|
||||
|
||||
<div class="flex gap-2 items-center">
|
||||
PNG size
|
||||
<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 type="radio" value="height" id="height-active" bind:group={imagemodeselected} />
|
||||
<label for="height">Height</label>
|
||||
<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" type="number" min="3" max="10000" bind:value={userimagesize} />
|
||||
<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">
|
||||
<label for="markdown">Copy Markdown</label>
|
||||
<input class="flex-1" id="markdown" type="text" value={mdCode} on:click={onCopyMarkdown} />
|
||||
<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>
|
||||
|
||||
@@ -7,21 +7,22 @@
|
||||
export let tabs: Tab[] = [];
|
||||
export let title: string;
|
||||
$: isOpen = isCloseable ? isOpen : true;
|
||||
$: isTabsShown = isOpen && tabs.length > 0;
|
||||
</script>
|
||||
|
||||
<div class="bg-white rounded overflow-hidden shadow m-2 flex-grow flex flex-col">
|
||||
<div class="card rounded overflow-hidden m-2 flex-grow flex flex-col shadow-2xl">
|
||||
<div
|
||||
class="bg-indigo-400 border-gray-400 p-2 flex-none cursor-pointer"
|
||||
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 text-white">
|
||||
<div class="flex gap-x-4 items-center {isTabsShown ? '-mt-2' : ''}">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if isOpen}
|
||||
<div class="flex-grow overflow-auto" transition:slide>
|
||||
<div class="card-body p-0 flex-grow overflow-auto text-base-content" transition:slide>
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
export let title: string;
|
||||
export let isOpen = false;
|
||||
|
||||
let activeTabID: string = tabs[0]?.id;
|
||||
$: activeTabID = tabs[0]?.id;
|
||||
|
||||
const dispatch = createEventDispatcher<TabEvents>();
|
||||
const toggleTabs = (tab: Tab) => {
|
||||
@@ -17,25 +17,20 @@
|
||||
</script>
|
||||
|
||||
<div class="flex cursor-default">
|
||||
<span class="text-white mr-2 font-semibold" on:click|stopPropagation={() => (isOpen = !isOpen)}>
|
||||
<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="flex flex-wrap flex-row" transition:fade>
|
||||
<ul class="tabs" transition:fade>
|
||||
{#each tabs as tab}
|
||||
<li class="mr-2 last:mr-0 w-28 h-6 flex-auto text-center">
|
||||
<div
|
||||
class="text cursor-pointer font-semibold min-w-16 w-auto px-2 py-1 -mb-4 rounded-t block leading-normal {activeTabID ===
|
||||
tab.id
|
||||
? 'text-indigo-500 bg-white border-white'
|
||||
: 'text-white bg-indigo-500 border-indigo-500 hover:bg-indigo-600'}"
|
||||
on:click|stopPropagation={() => toggleTabs(tab)}>
|
||||
<i class={tab.icon} />
|
||||
{tab.title}
|
||||
</div>
|
||||
</li>
|
||||
<div
|
||||
class="tab tab-lifted text-primary-content {activeTabID === tab.id ? 'tab-active' : ''}"
|
||||
on:click|stopPropagation={() => toggleTabs(tab)}>
|
||||
<i class="mr-1 {tab.icon}" />
|
||||
{tab.title}
|
||||
</div>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<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 './util';
|
||||
@@ -14,11 +15,10 @@
|
||||
export let editorOptions: monaco.editor.IStandaloneEditorConstructionOptions = {
|
||||
value: text,
|
||||
language: language,
|
||||
scrollBeyondLastLine: false,
|
||||
minimap: {
|
||||
enabled: false
|
||||
},
|
||||
theme: 'myCoolTheme',
|
||||
theme: 'mermaid',
|
||||
overviewRulerLanes: 0
|
||||
};
|
||||
export let errorMarkers: monaco.editor.IMarkerData[] = [];
|
||||
@@ -34,12 +34,16 @@
|
||||
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.
|
||||
// @ts-ignore : This is a hack to handle a svelte-kit error when importing monaco.
|
||||
Monaco = monaco;
|
||||
return;
|
||||
} catch {
|
||||
@@ -50,7 +54,7 @@
|
||||
};
|
||||
onMount(async () => {
|
||||
try {
|
||||
//@ts-ignore : This is a hack to handle a svelte-kit error when importing monaco.
|
||||
// @ts-ignore : This is a hack to handle a svelte-kit error when importing monaco.
|
||||
Monaco = monaco;
|
||||
} catch {
|
||||
await loadMonaco(); // Fix https://github.com/mermaid-js/mermaid-live-editor/issues/175
|
||||
@@ -63,7 +67,7 @@
|
||||
text
|
||||
});
|
||||
});
|
||||
|
||||
Monaco?.editor.setTheme($themeStore.isDark ? 'mermaid-dark' : 'mermaid');
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
editor.layout({
|
||||
height: entries[0].contentRect.height,
|
||||
|
||||
@@ -5,73 +5,545 @@
|
||||
export const initEditor = (monacoEditor): void => {
|
||||
monacoEditor.languages.register({ id: 'mermaid' });
|
||||
|
||||
// Register a tokens provider for the language
|
||||
monacoEditor.languages.setMonarchTokensProvider('mermaid', {
|
||||
typeKeywords: [
|
||||
'graph',
|
||||
'stateDiagram',
|
||||
'sequenceDiagram',
|
||||
'classDiagram',
|
||||
'pie',
|
||||
'erDiagram',
|
||||
'flowchart',
|
||||
'gantt',
|
||||
'gitGraph',
|
||||
'journey'
|
||||
],
|
||||
keywords: ['participant', 'as'],
|
||||
arrows: ['---', '===', '-->', '==>', '->>', '->'],
|
||||
tokenizer: {
|
||||
root: [
|
||||
[/[{}]/, 'delimiter.bracket'],
|
||||
[/[a-z_$][\w$]*/, { cases: { '@typeKeywords': 'keyword', '@keywords': 'keyword' } }],
|
||||
[/[-=>ox]+/, { cases: { '@arrows': 'transition' } }],
|
||||
[/[[{(}]+.+?[)\]}]+/, 'string'],
|
||||
[/".*"/, 'string']
|
||||
const requirementDiagrams = [
|
||||
'requirement',
|
||||
'functionalRequirement',
|
||||
'interfaceRequirement',
|
||||
'performanceRequirement',
|
||||
'physicalRequirement',
|
||||
'designConstraint'
|
||||
];
|
||||
|
||||
const keywords: {
|
||||
[key: string]: {
|
||||
typeKeywords: string[];
|
||||
blockKeywords: string[];
|
||||
keywords: string[];
|
||||
};
|
||||
} = {
|
||||
flowchart: {
|
||||
typeKeywords: ['flowchart', 'flowchart-v2', 'graph'],
|
||||
blockKeywords: ['subgraph', 'end'],
|
||||
keywords: [
|
||||
'TB',
|
||||
'TD',
|
||||
'BT',
|
||||
'RL',
|
||||
'LR',
|
||||
'click',
|
||||
'call',
|
||||
'href',
|
||||
'_self',
|
||||
'_blank',
|
||||
'_parent',
|
||||
'_top',
|
||||
'linkStyle',
|
||||
'style',
|
||||
'classDef',
|
||||
'class',
|
||||
'direction',
|
||||
'interpolate'
|
||||
]
|
||||
},
|
||||
whitespace: [
|
||||
[/[ \t\r\n]+/, 'white'],
|
||||
[/%%.*$/, 'comment']
|
||||
sequenceDiagram: {
|
||||
typeKeywords: ['sequenceDiagram'],
|
||||
blockKeywords: ['alt', 'par', 'and', 'loop', 'else', 'end', 'rect', 'opt', 'alt', 'rect'],
|
||||
keywords: [
|
||||
'participant',
|
||||
'as',
|
||||
'Note',
|
||||
'note',
|
||||
'right of',
|
||||
'left of',
|
||||
'over',
|
||||
'activate',
|
||||
'deactivate',
|
||||
'autonumber',
|
||||
'title'
|
||||
]
|
||||
},
|
||||
classDiagram: {
|
||||
typeKeywords: ['classDiagram', 'classDiagram-v2'],
|
||||
blockKeywords: ['class'],
|
||||
keywords: [
|
||||
'link',
|
||||
'click',
|
||||
'callback',
|
||||
'call',
|
||||
'href',
|
||||
'cssClass',
|
||||
'direction',
|
||||
'TB',
|
||||
'BT',
|
||||
'RL',
|
||||
'LR'
|
||||
]
|
||||
},
|
||||
stateDiagram: {
|
||||
typeKeywords: ['stateDiagram', 'stateDiagram-v2'],
|
||||
blockKeywords: ['state', 'note', 'end'],
|
||||
keywords: ['state', 'as', 'hide empty description', 'direction', 'TB', 'BT', 'RL', 'LR']
|
||||
},
|
||||
erDiagram: {
|
||||
typeKeywords: ['erDiagram'],
|
||||
blockKeywords: [],
|
||||
keywords: []
|
||||
},
|
||||
journey: {
|
||||
typeKeywords: ['journey'],
|
||||
blockKeywords: ['section'],
|
||||
keywords: ['title']
|
||||
},
|
||||
info: {
|
||||
typeKeywords: ['info'],
|
||||
blockKeywords: [],
|
||||
keywords: ['showInfo']
|
||||
},
|
||||
gantt: {
|
||||
typeKeywords: ['gantt'],
|
||||
blockKeywords: [],
|
||||
keywords: [
|
||||
'title',
|
||||
'dateFormat',
|
||||
'axisFormat',
|
||||
'todayMarker',
|
||||
'section',
|
||||
'excludes',
|
||||
'inclusiveEndDates'
|
||||
]
|
||||
},
|
||||
requirementDiagram: {
|
||||
typeKeywords: ['requirement', 'requirementDiagram'],
|
||||
blockKeywords: requirementDiagrams.concat('element'),
|
||||
keywords: []
|
||||
},
|
||||
gitGraph: {
|
||||
typeKeywords: ['gitGraph'],
|
||||
blockKeywords: [],
|
||||
keywords: ['commit', 'branch', 'merge', 'reset', 'checkout', 'LR', 'BT']
|
||||
},
|
||||
pie: {
|
||||
typeKeywords: ['pie'],
|
||||
blockKeywords: [],
|
||||
keywords: ['title', 'showData']
|
||||
}
|
||||
};
|
||||
|
||||
// Register a tokens provider for the mermaid language
|
||||
monacoEditor.languages.setMonarchTokensProvider('mermaid', {
|
||||
...Object.entries(keywords)
|
||||
.map((entry) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(entry[1]).map((deepEntry) => [
|
||||
entry[0] + deepEntry[0][0].toUpperCase() + deepEntry[0].slice(1),
|
||||
deepEntry[1]
|
||||
])
|
||||
)
|
||||
)
|
||||
.reduce((overallKeywords, nextKeyword) => ({ ...overallKeywords, ...nextKeyword }), {}),
|
||||
tokenizer: {
|
||||
root: [
|
||||
[/%%(?=.*%%$)/, { token: 'string', nextEmbedded: 'json' }],
|
||||
[/%%$/, { token: 'string', nextEmbedded: '@pop' }],
|
||||
[/^\s*gitGraph/m, 'typeKeyword', 'gitGraph'],
|
||||
[/^\s*info/m, 'typeKeyword', 'info'],
|
||||
[/^\s*pie/m, 'typeKeyword', 'pie'],
|
||||
[/^\s*(flowchart|flowchart-v2|graph)/m, 'typeKeyword', 'flowchart'],
|
||||
[/^\s*sequenceDiagram/, 'typeKeyword', 'sequenceDiagram'],
|
||||
[/^\s*classDiagram(-v2)?/, 'typeKeyword', 'classDiagram'],
|
||||
[/^\s*journey/, 'typeKeyword', 'journey'],
|
||||
[/^\s*gantt/, 'typeKeyword', 'gantt'],
|
||||
[/^\s*stateDiagram(-v2)?/, 'typeKeyword', 'stateDiagram'],
|
||||
[/^\s*erDiagram/, 'typeKeyword', 'erDiagram'],
|
||||
[/^\s*requirement(Diagram)?/, 'typeKeyword', 'requirementDiagram'],
|
||||
[/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment']
|
||||
],
|
||||
gitGraph: [
|
||||
[/option(?=s)/, { token: 'typeKeyword', next: 'optionsGitGraph' }],
|
||||
[/(^\s*branch|reset|merge|checkout)(.*$)/, ['keyword', 'variable']],
|
||||
[
|
||||
/[a-zA-Z][\w$]*/,
|
||||
{
|
||||
cases: {
|
||||
'@gitGraphBlockKeywords': 'typeKeyword',
|
||||
'@gitGraphKeywords': 'keyword'
|
||||
}
|
||||
}
|
||||
],
|
||||
[/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment'],
|
||||
[/".*?"/, 'string'],
|
||||
[/\^/, 'delimiter.bracket']
|
||||
],
|
||||
optionsGitGraph: [
|
||||
[/s$/, { token: 'typeKeyword', nextEmbedded: 'json', matchOnlyAtLineStart: false }],
|
||||
['end', { token: 'typeKeyword', next: '@pop', nextEmbedded: '@pop' }]
|
||||
],
|
||||
info: [
|
||||
[
|
||||
/[a-zA-Z][\w$]*/,
|
||||
{
|
||||
cases: {
|
||||
'@infoBlockKeywords': 'typeKeyword',
|
||||
'@infoKeywords': 'keyword'
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
pie: [
|
||||
[/(title)(.*$)/, ['keyword', 'string']],
|
||||
[
|
||||
/[a-zA-Z][\w$]*/,
|
||||
{
|
||||
cases: {
|
||||
'@pieBlockKeywords': 'typeKeyword',
|
||||
'@pieKeywords': 'keyword'
|
||||
}
|
||||
}
|
||||
],
|
||||
[/".*?"/, 'string'],
|
||||
[/\s*\d+/, 'number'],
|
||||
[/:/, 'delimiter.bracket'],
|
||||
[/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment']
|
||||
],
|
||||
flowchart: [
|
||||
[/[ox]?(--+|==+)[ox]/, 'transition'],
|
||||
[
|
||||
/[a-zA-Z][\w$]*/,
|
||||
{
|
||||
cases: {
|
||||
'@flowchartBlockKeywords': 'typeKeyword',
|
||||
'@flowchartKeywords': 'keyword',
|
||||
'@default': 'variable'
|
||||
}
|
||||
}
|
||||
],
|
||||
[/\|+.+?\|+/, 'string'],
|
||||
[/\[+(\\.+?[\\/]|\/.+?[/\\])\]+/, 'string'],
|
||||
[/[[>]+[^\]|[]+?\]+/, 'string'],
|
||||
[/{+.+?}+/, 'string'],
|
||||
[/\(+.+?\)+/, 'string'],
|
||||
[/-\.+->?/, 'transition'],
|
||||
[/(-[-.])([^->]+?)(-{3,}|-{2,}>|\.-+>)/, ['transition', 'string', 'transition']],
|
||||
[/(==+)([^=]+?)(={3,}|={2,}>)/, ['transition', 'string', 'transition']],
|
||||
[/<?(--+|==+)>|===+|---+/, 'transition'],
|
||||
[/:::/, 'transition'],
|
||||
[/[;&]/, 'delimiter.bracket'],
|
||||
[/".*?"/, 'string'],
|
||||
[/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment']
|
||||
],
|
||||
sequenceDiagram: [
|
||||
[
|
||||
/[a-zA-Z][\w$]*/,
|
||||
{
|
||||
cases: {
|
||||
'@sequenceDiagramBlockKeywords': 'typeKeyword',
|
||||
'@sequenceDiagramKeywords': 'keyword',
|
||||
'@default': 'variable'
|
||||
}
|
||||
}
|
||||
],
|
||||
[/(--?>?>|--?[)x])[+-]?/, 'transition'],
|
||||
[/(:)([^:\n]*?$)/, ['delimiter.bracket', 'string']],
|
||||
[/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment']
|
||||
],
|
||||
classDiagram: [
|
||||
[/(?!class\s)([a-zA-Z]+)(\s+[a-zA-Z]+)/, ['type', 'variable']],
|
||||
[/(\*|<\|?|o)?(--|\.\.)(\*|\|?>|o)?/, 'transition'],
|
||||
[/^\s*class\s(?!.*\{)/, 'keyword'],
|
||||
[
|
||||
/[a-zA-Z][\w$]*/,
|
||||
{
|
||||
cases: {
|
||||
'@classDiagramBlockKeywords': 'typeKeyword',
|
||||
'@classDiagramKeywords': 'keyword',
|
||||
'@default': 'variable'
|
||||
}
|
||||
}
|
||||
],
|
||||
[/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment'],
|
||||
[/(<<)(.+?)(>>)/, ['delimiter.bracket', 'annotation', 'delimiter.bracket']],
|
||||
[/".*?"/, 'string'],
|
||||
[/:::/, 'transition'],
|
||||
[/:|\+|-|#|~|\*\s*$|\$\s*$|\(|\)|{|}/, 'delimiter.bracket']
|
||||
],
|
||||
journey: [
|
||||
[/(title)(.*)/, ['keyword', 'string']],
|
||||
[/(section)(.*)/, ['typeKeyword', 'string']],
|
||||
[
|
||||
/[a-zA-Z][\w$]*/,
|
||||
{
|
||||
cases: {
|
||||
'@journeyBlockKeywords': 'typeKeyword',
|
||||
'@journeyKeywords': 'keyword',
|
||||
'@default': 'variable'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
/(^\s*.+?)(:)(.*?)(:)(.*?)([,$])/,
|
||||
[
|
||||
'string',
|
||||
'delimiter.bracket',
|
||||
'number',
|
||||
'delimiter.bracket',
|
||||
'variable',
|
||||
'delimiter.bracket'
|
||||
]
|
||||
],
|
||||
[/,/, 'delimiter.bracket'],
|
||||
[/(^\s*.+?)(:)([^:]*?)$/, ['string', 'delimiter.bracket', 'variable']],
|
||||
[/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment']
|
||||
],
|
||||
gantt: [
|
||||
[/(title)(.*)/, ['keyword', 'string']],
|
||||
[/(section)(.*)/, ['typeKeyword', 'string']],
|
||||
[
|
||||
/[a-zA-Z][\w$]*/,
|
||||
{
|
||||
cases: {
|
||||
'@ganttBlockKeywords': 'typeKeyword',
|
||||
'@ganttKeywords': 'keyword'
|
||||
}
|
||||
}
|
||||
],
|
||||
[/(^\s*.*?)(:)/, ['string', 'delimiter.bracket']],
|
||||
[/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment'],
|
||||
[/:/, 'delimiter.bracket']
|
||||
],
|
||||
stateDiagram: [
|
||||
[/note[^:]*$/, { token: 'typeKeyword', next: 'stateDiagramNote' }],
|
||||
['hide empty description', 'keyword'],
|
||||
[/^\s*state\s(?!.*\{)/, 'keyword'],
|
||||
[/(<<)(fork|join|choice)(>>)/, 'annotation'],
|
||||
[/(\[\[)(fork|join|choice)(]])/, ['delimiter.bracket', 'annotation', 'delimiter.bracket']],
|
||||
[
|
||||
/[a-zA-Z][\w$]*/,
|
||||
{
|
||||
cases: {
|
||||
'@stateDiagramBlockKeywords': 'typeKeyword',
|
||||
'@stateDiagramKeywords': 'keyword',
|
||||
'@default': 'variable'
|
||||
}
|
||||
}
|
||||
],
|
||||
[/".*?"/, 'string'],
|
||||
[/(:)([^:\n]*?$)/, ['delimiter.bracket', 'string']],
|
||||
[/{|}/, 'delimiter.bracket'],
|
||||
[/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment'],
|
||||
[/-->/, 'transition'],
|
||||
[/\[.*?]/, 'string']
|
||||
],
|
||||
stateDiagramNote: [
|
||||
[/^\s*end note$/, { token: 'typeKeyword', next: '@pop' }],
|
||||
[/.*/, 'string']
|
||||
],
|
||||
erDiagram: [
|
||||
[/[}|][o|](--|\.\.)[o|][{|]/, 'transition'],
|
||||
[/".*?"/, 'string'],
|
||||
[/(:)(.*?$)/, ['delimiter.bracket', 'string']],
|
||||
[/:|{|}/, 'delimiter.bracket'],
|
||||
[/([a-zA-Z]+)(\s+[a-zA-Z]+)/, ['type', 'variable']],
|
||||
[/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment'],
|
||||
[/[a-zA-Z_-][\w$]*/, 'variable']
|
||||
],
|
||||
requirementDiagram: [
|
||||
[/->|<-|-/, 'transition'],
|
||||
[/(\d+\.)*\d+/, 'number'],
|
||||
[
|
||||
/[a-zA-Z_-][\w$]*/,
|
||||
{
|
||||
cases: {
|
||||
'@requirementDiagramBlockKeywords': 'typeKeyword',
|
||||
'@default': 'variable'
|
||||
}
|
||||
}
|
||||
],
|
||||
[/:|{|}|\//, 'delimiter.bracket'],
|
||||
[/%%[^$]([^%]*(?!%%$)%?)*$/, 'comment'],
|
||||
[/".*?"/, 'string']
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
monacoEditor.editor.defineTheme('mermaid-dark', {
|
||||
base: 'vs-dark',
|
||||
inherit: true,
|
||||
rules: [
|
||||
{ token: 'typeKeyword', foreground: '9650c8', fontStyle: 'bold' },
|
||||
{ token: 'transition', foreground: '008800', fontStyle: 'bold' }
|
||||
]
|
||||
});
|
||||
|
||||
monacoEditor.editor.defineTheme('myCoolTheme', {
|
||||
monacoEditor.editor.defineTheme('mermaid', {
|
||||
base: 'vs',
|
||||
inherit: false,
|
||||
inherit: true,
|
||||
rules: [
|
||||
{ token: 'keyword', foreground: '880000', fontStyle: 'bold' },
|
||||
{ token: 'typeKeyword', foreground: '9650c8', fontStyle: 'bold' },
|
||||
{ token: 'keyword', foreground: '649696' },
|
||||
{ token: 'custom-error', foreground: 'ff0000', fontStyle: 'bold' },
|
||||
{ token: 'string', foreground: 'AA8500' },
|
||||
{ token: 'transition', foreground: '008800', fontStyle: 'bold' },
|
||||
{ token: 'delimiter.bracket', foreground: '000000', fontStyle: 'bold' }
|
||||
{ token: 'delimiter.bracket', foreground: '000000', fontStyle: 'bold' },
|
||||
{ token: 'annotation', foreground: '4b4b96' },
|
||||
{ token: 'number', foreground: '4b4b96' },
|
||||
{ token: 'comment', foreground: '888c89' },
|
||||
{ token: 'variable', foreground: 'A22889' },
|
||||
{ token: 'type', foreground: '2BDEA8' }
|
||||
]
|
||||
});
|
||||
|
||||
// Register a completion item provider for the new language
|
||||
// Register a completion item provider for the mermaid language
|
||||
monacoEditor.languages.registerCompletionItemProvider('mermaid', {
|
||||
provideCompletionItems: () => {
|
||||
const suggestions = [
|
||||
{
|
||||
label: 'simpleText',
|
||||
kind: monacoEditor.languages.CompletionItemKind.Text,
|
||||
insertText: 'simpleText'
|
||||
},
|
||||
{
|
||||
label: 'testing',
|
||||
kind: monacoEditor.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'testing(${1:condition})',
|
||||
insertTextRules: monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet
|
||||
},
|
||||
{
|
||||
label: 'ifelse',
|
||||
label: 'loop',
|
||||
kind: monacoEditor.languages.CompletionItemKind.Snippet,
|
||||
insertText: ['if (${1:condition}) {', '\t$0', '} else {', '\t', '}'].join('\n'),
|
||||
insertText: ['loop ${1:Loop text}', '\t$0', 'end'].join('\n'),
|
||||
insertTextRules: monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: 'If-Else Statement'
|
||||
}
|
||||
documentation: 'Sequence Diagram Loops'
|
||||
},
|
||||
{
|
||||
label: 'alt',
|
||||
kind: monacoEditor.languages.CompletionItemKind.Snippet,
|
||||
insertText: ['alt ${1:Describing text}', '\t$0', 'else', '\t', 'end'].join('\n'),
|
||||
insertTextRules: monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: 'Alternative Path'
|
||||
},
|
||||
{
|
||||
label: 'opt',
|
||||
kind: monacoEditor.languages.CompletionItemKind.Snippet,
|
||||
insertText: ['opt ${1:Describing text}', '\t$0', 'end'].join('\n'),
|
||||
insertTextRules: monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: 'Optional Path'
|
||||
},
|
||||
{
|
||||
label: 'par',
|
||||
kind: monacoEditor.languages.CompletionItemKind.Snippet,
|
||||
insertText: [
|
||||
'par ${1:[Action 1]}',
|
||||
'\t$0',
|
||||
'and ${2:[Action 2]}',
|
||||
'\t',
|
||||
'and ${3:[Action 3]}',
|
||||
'\t',
|
||||
'end'
|
||||
].join('\n'),
|
||||
insertTextRules: monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: 'Parallel Actions'
|
||||
},
|
||||
{
|
||||
label: 'rect',
|
||||
kind: monacoEditor.languages.CompletionItemKind.Snippet,
|
||||
insertText: ['rect ${1:rgb(0, 255, 0)}', '\t$0', 'end'].join('\n'),
|
||||
insertTextRules: monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: 'Background Color'
|
||||
},
|
||||
{
|
||||
label: 'subgraph',
|
||||
kind: monacoEditor.languages.CompletionItemKind.Snippet,
|
||||
insertText: ['subgraph ${1:title}', '\t$0', 'end'].join('\n'),
|
||||
insertTextRules: monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: 'Subgraph'
|
||||
},
|
||||
{
|
||||
label: 'class',
|
||||
kind: monacoEditor.languages.CompletionItemKind.Snippet,
|
||||
insertText: ['class ${1:className} {', '\t$0', '}'].join('\n'),
|
||||
insertTextRules: monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: 'Class'
|
||||
},
|
||||
{
|
||||
label: 'state',
|
||||
kind: monacoEditor.languages.CompletionItemKind.Snippet,
|
||||
insertText: ['state ${1:stateName} {', '\t$0', '}'].join('\n'),
|
||||
insertTextRules: monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: 'State'
|
||||
},
|
||||
{
|
||||
label: 'note',
|
||||
kind: monacoEditor.languages.CompletionItemKind.Snippet,
|
||||
insertText: ['note ${1:right of State1}', '\t$0', 'end note'].join('\n'),
|
||||
insertTextRules: monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: 'State'
|
||||
},
|
||||
{
|
||||
label: 'section',
|
||||
kind: monacoEditor.languages.CompletionItemKind.Snippet,
|
||||
insertText: ['section ${1:Go to work}', '\t$0'].join('\n'),
|
||||
insertTextRules: monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: 'User-journey Section'
|
||||
},
|
||||
{
|
||||
label: 'element',
|
||||
kind: monacoEditor.languages.CompletionItemKind.Snippet,
|
||||
insertText: ['element ${1:test_entity} {', '\t$0', '}'].join('\n'),
|
||||
insertTextRules: monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: 'User-journey Section'
|
||||
},
|
||||
{
|
||||
label: 'options',
|
||||
kind: monacoEditor.languages.CompletionItemKind.Snippet,
|
||||
insertText: ['options', '{', ' $0', '}', 'end'].join('\n'),
|
||||
insertTextRules: monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: 'Git Graph Options'
|
||||
},
|
||||
...requirementDiagrams.map((requirementDiagramType) => ({
|
||||
label: requirementDiagramType,
|
||||
kind: monacoEditor.languages.CompletionItemKind.Snippet,
|
||||
insertText: [
|
||||
requirementDiagramType + ' ${1:test_req} {',
|
||||
'\tid: 1',
|
||||
'\ttext: the test text.',
|
||||
'\trisk: high',
|
||||
'\tverifyMethod: test',
|
||||
'}'
|
||||
].join('\n'),
|
||||
insertTextRules: monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: requirementDiagramType
|
||||
.split(/(?=[A-Z])/)
|
||||
.map((part) => part[0].toUpperCase() + part.slice(1))
|
||||
.join(' ')
|
||||
})),
|
||||
...[
|
||||
...new Set(
|
||||
Object.values(keywords)
|
||||
.map((diagramKeywords) =>
|
||||
Object.entries(diagramKeywords)
|
||||
.filter((keywordType) => keywordType[0] !== 'annotations')
|
||||
.map((entry) => entry[1])
|
||||
)
|
||||
.flat(2)
|
||||
)
|
||||
].map((keyword) => ({
|
||||
label: keyword,
|
||||
kind: monacoEditor.languages.CompletionItemKind.Keyword,
|
||||
insertText: keyword
|
||||
}))
|
||||
];
|
||||
return { suggestions: suggestions };
|
||||
}
|
||||
});
|
||||
|
||||
monacoEditor.languages.setLanguageConfiguration('mermaid', {
|
||||
autoClosingPairs: [
|
||||
{
|
||||
open: '(',
|
||||
close: ')'
|
||||
},
|
||||
{
|
||||
open: '{',
|
||||
close: '}'
|
||||
},
|
||||
{
|
||||
open: '[',
|
||||
close: ']'
|
||||
}
|
||||
],
|
||||
brackets: [
|
||||
['(', ')'],
|
||||
['{', '}'],
|
||||
['[', ']']
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,29 +3,30 @@
|
||||
import { codeStore, getStateString } from '$lib/util/state';
|
||||
import {
|
||||
addHistoryEntry,
|
||||
autoHistoryMode,
|
||||
historyModeStore,
|
||||
clearHistoryData,
|
||||
getPreviousState,
|
||||
historyStore
|
||||
historyStore,
|
||||
loaderHistoryStore
|
||||
} from './history';
|
||||
import { notify, prompt } from '$lib/util/notify';
|
||||
import { onMount } from 'svelte';
|
||||
import moment from 'moment';
|
||||
import type { State, Tab } from '$lib/types';
|
||||
import type { HistoryType, State, Tab } from '$lib/types';
|
||||
|
||||
const HISTORY_SAVE_INTERVAL = 60000;
|
||||
|
||||
const tabSelectHandler = (message: CustomEvent<Tab>) => {
|
||||
autoHistoryMode.set('timeline' === message.detail.id);
|
||||
historyModeStore.set(message.detail.id as HistoryType);
|
||||
};
|
||||
const tabs: Tab[] = [
|
||||
let tabs: Tab[] = [
|
||||
{
|
||||
id: 'saved',
|
||||
id: 'manual',
|
||||
title: 'Saved',
|
||||
icon: 'far fa-bookmark'
|
||||
},
|
||||
{
|
||||
id: 'timeline',
|
||||
id: 'auto',
|
||||
title: 'Timeline',
|
||||
icon: 'fas fa-history'
|
||||
}
|
||||
@@ -38,7 +39,7 @@
|
||||
addHistoryEntry({
|
||||
state: $codeStore,
|
||||
time: Date.now(),
|
||||
auto
|
||||
type: auto ? 'auto' : 'manual'
|
||||
});
|
||||
} else if (!auto) {
|
||||
notify('State already saved.');
|
||||
@@ -62,52 +63,76 @@
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
autoHistoryMode.set(false);
|
||||
historyModeStore.set('manual');
|
||||
setInterval(() => {
|
||||
saveHistory(true);
|
||||
}, HISTORY_SAVE_INTERVAL);
|
||||
});
|
||||
|
||||
let isOpen = true;
|
||||
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"
|
||||
class="btn btn-xs btn-success w-12"
|
||||
on:click|stopPropagation={() => saveHistory()}
|
||||
title="Save current state"><i class="far fa-save" /></button>
|
||||
<button
|
||||
id="clearHistory"
|
||||
class="btn text-red-400"
|
||||
on:click|stopPropagation={() => clearHistory()}
|
||||
title="Delete all saved states"><i class="fas fa-trash-alt" /></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 }}
|
||||
{#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">
|
||||
<span>{name}</span>
|
||||
<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="rounded px-2 w-24 bg-green-200 hover:bg-green-300"
|
||||
on:click={() => restoreHistory(state)}><i class="fas fa-undo" /> Restore</button>
|
||||
<button
|
||||
class="rounded px-2 w-24 bg-red-200 hover:bg-red-300"
|
||||
on:click={() => clearHistory(time)}><i class="fas fa-trash-alt" /> Delete</button>
|
||||
<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 text-gray-600">
|
||||
<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.
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { derived, Readable, Writable, writable, get } from 'svelte/store';
|
||||
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 } from '../../types';
|
||||
import type { HistoryEntry, HistoryType } from '$lib/types';
|
||||
|
||||
const MAX_AUTO_HISTORY_LENGTH = 30;
|
||||
|
||||
export const autoHistoryMode: Writable<boolean> = persist(
|
||||
writable(true),
|
||||
export const historyModeStore: Writable<HistoryType> = persist(
|
||||
writable('manual'),
|
||||
localStorage(),
|
||||
'autoHistoryMode'
|
||||
);
|
||||
@@ -23,17 +24,30 @@ const manualHistoryStore: Writable<HistoryEntry[]> = persist(
|
||||
'manualHistoryStore'
|
||||
);
|
||||
|
||||
export const loaderHistoryStore: Writable<HistoryEntry[]> = writable([] as HistoryEntry[]);
|
||||
|
||||
export const historyStore: Readable<HistoryEntry[]> = derived(
|
||||
[autoHistoryMode, autoHistoryStore, manualHistoryStore],
|
||||
([autoMode, autoHistories, manualHistories], set) => {
|
||||
set(autoMode ? autoHistories : manualHistories);
|
||||
[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.auto) {
|
||||
if (entry.type !== 'auto') {
|
||||
manualHistoryStore.update((entries) => [entry, ...entries]);
|
||||
return;
|
||||
}
|
||||
@@ -46,9 +60,12 @@ export const addHistoryEntry = (entry: HistoryEntry): void => {
|
||||
};
|
||||
|
||||
export const clearHistoryData = (time?: number): void => {
|
||||
(get(autoHistoryMode) ? autoHistoryStore : manualHistoryStore).update((entries) =>
|
||||
entries.filter((entry) => time && entry.time != time)
|
||||
);
|
||||
(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 => {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import Theme from './theme.svelte';
|
||||
|
||||
interface Link {
|
||||
title: string;
|
||||
href: string;
|
||||
@@ -37,11 +39,12 @@
|
||||
];
|
||||
</script>
|
||||
|
||||
<header class="p-2 bg-indigo-400 text-white font-bold flex flex-wrap items-center">
|
||||
<div class="flex-1 flex justify-between items-center">
|
||||
<a href="/">Mermaid<span class="text-xs font-thin">v{version}</span> Live Editor</a>
|
||||
<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 "
|
||||
@@ -53,28 +56,27 @@
|
||||
></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">
|
||||
<nav>
|
||||
<ul class="lg:flex items-center justify-between text-base pt-4 lg:pt-0">
|
||||
{#each links as { title, href, icon }}
|
||||
<li>
|
||||
<a class="nav-btn" target="_blank" {href}>
|
||||
{#if icon}
|
||||
<i class={icon} />
|
||||
{/if}
|
||||
{title}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</nav>
|
||||
<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>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#menu-toggle:checked + #menu {
|
||||
display: block;
|
||||
}
|
||||
header {
|
||||
.navbar {
|
||||
z-index: 10000;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -108,7 +108,8 @@ merge newbranch
|
||||
<Card title="Sample Diagrams" isOpen={false}>
|
||||
<div class="flex gap-2 flex-wrap p-2">
|
||||
{#each Object.keys(samples) as sample}
|
||||
<button class="action-btn" on:click={() => loadSampleDiagram(sample)}>{sample}</button>
|
||||
<button class="btn btn-primary normal-case btn-sm" on:click={() => loadSampleDiagram(sample)}
|
||||
>{sample}</button>
|
||||
{/each}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<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>
|
||||
@@ -3,11 +3,12 @@
|
||||
|
||||
import { codeStore } from '$lib/util/state';
|
||||
import { onMount } from 'svelte';
|
||||
import * as mermaid from 'mermaid';
|
||||
import mermaid from 'mermaid';
|
||||
|
||||
let code = '';
|
||||
let config = '';
|
||||
let container: HTMLDivElement;
|
||||
let view: HTMLDivElement;
|
||||
let error = false;
|
||||
let outOfSync = false;
|
||||
let manualUpdate = true;
|
||||
@@ -26,13 +27,13 @@
|
||||
}
|
||||
code = state.code;
|
||||
config = state.mermaid;
|
||||
const scroll = container.parentElement.parentElement.parentElement.scrollTop;
|
||||
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;
|
||||
});
|
||||
container.parentElement.parentElement.parentElement.scrollTop = scroll;
|
||||
view.parentElement.scrollTop = scroll;
|
||||
error = false;
|
||||
} else if (manualUpdate) {
|
||||
manualUpdate = false;
|
||||
@@ -55,7 +56,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="view" class="p-2" class:error class:outOfSync>
|
||||
<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>
|
||||
|
||||
|
||||
Vendored
+22
-1
@@ -29,11 +29,32 @@ export interface State {
|
||||
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;
|
||||
auto: boolean;
|
||||
type: HistoryType;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
type Loader = (url: string) => Promise<State>;
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/* 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);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
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 = '';
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
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;
|
||||
};
|
||||
+16
-5
@@ -2,9 +2,9 @@ import { writable, get, derived } from 'svelte/store';
|
||||
import { toBase64, fromBase64 } from 'js-base64';
|
||||
import { persist, localStorage } from '@macfja/svelte-persistent-store';
|
||||
import type { State } from '$lib/types';
|
||||
import { saveStatistcs } from './stats';
|
||||
import { saveStatistics } from './stats';
|
||||
|
||||
const defaultState: State = {
|
||||
export const defaultState: State = {
|
||||
code: `graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
@@ -33,7 +33,7 @@ export const loadState = (data: string): void => {
|
||||
let state: State;
|
||||
try {
|
||||
const stateStr = fromBase64(data);
|
||||
console.log(`Tring to load state: ${stateStr}`);
|
||||
console.log(`Trying to load state: ${stateStr}`);
|
||||
state = JSON.parse(stateStr);
|
||||
const mermaidConfig =
|
||||
typeof state.mermaid === 'string' ? JSON.parse(state.mermaid) : state.mermaid;
|
||||
@@ -66,12 +66,12 @@ export const updateCodeStore = (newState: State): void => {
|
||||
|
||||
let prompted = false;
|
||||
export const updateCode = (code: string, updateEditor: boolean, updateDiagram = false): void => {
|
||||
saveStatistcs(code);
|
||||
saveStatistics(code);
|
||||
const lines = (code.match(/\n/g) || '').length + 1;
|
||||
|
||||
if (lines > 50 && !prompted && get(codeStore).autoSync) {
|
||||
const turnOff = confirm(
|
||||
'Long diagram deteced. Turn off Auto Sync? Click the sync logo to manually sync.'
|
||||
'Long diagram detected. Turn off Auto Sync? Click the sync logo to manually sync.'
|
||||
);
|
||||
prompted = true;
|
||||
if (turnOff) {
|
||||
@@ -92,6 +92,17 @@ export const updateConfig = (config: string, updateEditor: boolean): void => {
|
||||
});
|
||||
};
|
||||
|
||||
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 => {
|
||||
base64State.subscribe((state: string) => {
|
||||
history.replaceState(undefined, undefined, `#${state}`);
|
||||
|
||||
@@ -34,10 +34,10 @@ const detectType = (text: string): string => {
|
||||
|
||||
// manual debounce
|
||||
let timeout;
|
||||
export const saveStatistcs = (graph: string): void => {
|
||||
export const saveStatistics = (graph: string): void => {
|
||||
if (analytics) {
|
||||
clearTimeout(timeout);
|
||||
// Only save statistcs after a 5 sec delay
|
||||
// Only save statistics after a 5 sec delay
|
||||
timeout = setTimeout(function () {
|
||||
const graphType = detectType(graph);
|
||||
console.debug('ga:', 'send', 'event', 'render', graphType);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
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 });
|
||||
};
|
||||
@@ -1,6 +1,8 @@
|
||||
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));
|
||||
@@ -14,6 +16,7 @@ export const syncDiagram = (): void => {
|
||||
|
||||
export const initHandler = async (): Promise<void> => {
|
||||
loadStateFromURL();
|
||||
await initLoading('Loading Gist...', loadDataFromUrl().catch(console.error));
|
||||
syncDiagram();
|
||||
initURLSubscription();
|
||||
await initAnalytics();
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
import { get as manifestGet } from '../manifest.json';
|
||||
export const get = manifestGet;
|
||||
@@ -2,6 +2,10 @@
|
||||
import '../app.postcss';
|
||||
import { base } from '$app/paths';
|
||||
import { onMount } from 'svelte';
|
||||
import { loadingStateStore } from '$lib/util/loading';
|
||||
import { setTheme, themeStore } from '$lib/util/theme';
|
||||
import { toggleDarkTheme } from '$lib/util/state';
|
||||
|
||||
// This can be removed once https://github.com/sveltejs/kit/issues/1612 is fixed.
|
||||
// Then move it into src and vite will bundle it automatically.
|
||||
onMount(() => {
|
||||
@@ -17,9 +21,63 @@
|
||||
console.log('Service worker registration failed, error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
|
||||
if ($themeStore.theme === undefined) {
|
||||
setTheme(isDarkMode ? 'dark' : 'light');
|
||||
}
|
||||
|
||||
themeStore.subscribe(({ theme, isDark }) => {
|
||||
if (theme) {
|
||||
document.getElementsByTagName('html')[0].setAttribute('data-theme', theme);
|
||||
toggleDarkTheme(isDark);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<main class="h-screen">
|
||||
<main class="h-screen text-primary-content">
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
{#if $loadingStateStore.loading}
|
||||
<div
|
||||
class="w-screen h-screen z-50 absolute left-0 top-0 bg-gray-600 opacity-50 flex align-middle justify-center">
|
||||
<div class="text-indigo-100 text-4xl font-bold my-auto">
|
||||
<div class="loader mx-auto" />
|
||||
<div>{$loadingStateStore.message}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.loader {
|
||||
border: 0.45em solid #f3f3f3;
|
||||
border-radius: 50%;
|
||||
border-top: 0.45em solid #6365f1;
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
-webkit-animation: spin 2s linear infinite; /* Safari */
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
/* Safari */
|
||||
@-webkit-keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+30
-27
@@ -10,11 +10,12 @@
|
||||
import { initHandler, syncDiagram } from '$lib/util/util';
|
||||
import { errorStore } from '$lib/util/error';
|
||||
import { onMount } from 'svelte';
|
||||
import * as mermaid from 'mermaid';
|
||||
import mermaid from 'mermaid';
|
||||
import type monaco from 'monaco-editor';
|
||||
import type { EditorUpdateEvent, State, Tab } from '$lib/types';
|
||||
import { base } from '$app/paths';
|
||||
|
||||
base64State; // Weird fix for error > base64State is not defined. Treeshaking?
|
||||
let selectedMode = 'code';
|
||||
const languageMap = {
|
||||
code: 'mermaid',
|
||||
@@ -120,25 +121,32 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="h-full flex flex-col overflow-hidden bg-gray-100">
|
||||
<div class="h-full flex flex-col overflow-hidden">
|
||||
<Navbar />
|
||||
<div class="flex-1 flex overflow-hidden">
|
||||
<div class="hidden md:flex flex-col" id="editorPane" style="width: 40%">
|
||||
<Card on:select={tabSelectHandler} {tabs} isCloseable={false} title="Mermaid">
|
||||
<div slot="actions">
|
||||
{#if !$codeStore.autoSync}
|
||||
<button
|
||||
class="bg-indigo-500 hover:bg-indigo-700 rounded px-4 mx-2"
|
||||
title="Sync Diagram"
|
||||
data-cy="sync"
|
||||
on:click={syncDiagram}><i class="fas fa-sync" /></button>
|
||||
{/if}
|
||||
<label
|
||||
for="autoSync"
|
||||
style="white-space: nowrap;overflow: hidden;text-overflow: ellipsis;">
|
||||
<input type="checkbox" id="autoSync" bind:checked={$codeStore.autoSync} />
|
||||
Auto sync
|
||||
</label>
|
||||
<div class="flex flex-row items-center">
|
||||
{#if !$codeStore.autoSync}
|
||||
<button
|
||||
class="btn btn-secondary btn-xs"
|
||||
title="Sync Diagram"
|
||||
data-cy="sync"
|
||||
on:click={syncDiagram}><i class="fas fa-sync" /></button>
|
||||
{/if}
|
||||
|
||||
<div class="form-control">
|
||||
<label class="cursor-pointer label" for="autoSync">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary mr-1"
|
||||
id="autoSync"
|
||||
bind:checked={$codeStore.autoSync} />
|
||||
<span> Auto sync</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Editor on:update={updateHandler} {language} bind:text {errorMarkers} />
|
||||
@@ -150,20 +158,20 @@
|
||||
<Actions />
|
||||
</div>
|
||||
</div>
|
||||
<div id="resizeHandler" />
|
||||
<div id="resizeHandler" class="hidden md:block" />
|
||||
<div class="flex-1 flex flex-col overflow-hidden">
|
||||
<Card title="Diagram" isCloseable={false}>
|
||||
<button
|
||||
slot="actions"
|
||||
class="btn"
|
||||
class="btn btn-primary btn-xs shadow-lg"
|
||||
title="View diagram in new page"
|
||||
on:click|stopPropagation={() => viewDiagram()}><i class="far fa-eye" /> View</button>
|
||||
on:click|stopPropagation={() => viewDiagram()}><i class="far fa-eye mr-2" /> View</button>
|
||||
|
||||
<div class="flex-1 overflow-auto">
|
||||
<View />
|
||||
</div>
|
||||
</Card>
|
||||
<div class="md:hidden bg-white rounded shadow p-2 mx-2">
|
||||
<div class="md:hidden rounded shadow p-2 mx-2">
|
||||
Code editing not supported on mobile. Please use a desktop browser.
|
||||
</div>
|
||||
</div>
|
||||
@@ -182,19 +190,14 @@
|
||||
top: 0;
|
||||
content: '';
|
||||
position: absolute;
|
||||
background-color: #ccc;
|
||||
background-color: hsla(var(--b3));
|
||||
margin-left: -1px;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
#resizeHandler:hover::after {
|
||||
margin-left: -2px;
|
||||
background-color: #818cf8;
|
||||
background-color: hsla(var(--p));
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
#resizeHandler {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 47 KiB |
+3
-17
@@ -1,21 +1,7 @@
|
||||
const { tailwindExtractor } = require('tailwindcss/lib/lib/purgeUnusedStyles');
|
||||
|
||||
module.exports = {
|
||||
mode: 'aot',
|
||||
purge: {
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
options: {
|
||||
defaultExtractor: (content) => [
|
||||
// If this stops working, please open an issue at https://github.com/svelte-add/tailwindcss/issues rather than bothering Tailwind Labs about it
|
||||
...tailwindExtractor(content),
|
||||
// Match Svelte class: directives (https://github.com/tailwindlabs/tailwindcss/discussions/1731)
|
||||
...[...content.matchAll(/(?:class:)*([\w\d-/:%.]+)/gm)].map(
|
||||
([_match, group, ..._rest]) => group
|
||||
)
|
||||
]
|
||||
},
|
||||
safelist: [/^svelte-[\d\w]+$/]
|
||||
},
|
||||
mode: 'jit',
|
||||
plugins: [require('daisyui')],
|
||||
purge: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
theme: {
|
||||
extend: {}
|
||||
},
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"module": "es2020",
|
||||
"lib": ["es2020", "ESNext"],
|
||||
"lib": ["es2020", "ESNext", "DOM.Iterable"],
|
||||
"target": "es2019",
|
||||
/**
|
||||
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
|
||||
|
||||
Reference in New Issue
Block a user