diff --git a/.eslintrc.cjs b/.eslintrc.cjs index f4f6f285..7be5a785 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,50 +1,50 @@ 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', 'vitest'], - 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: 2020, - tsconfigRootDir: __dirname, - project: ['./tsconfig.json'], - extraFileExtensions: ['.svelte'], - allowAutomaticSingleRunInference: true - }, - env: { - browser: true, - es2020: 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', - curly: ['error', 'all'] - } + 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', 'vitest'], + 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: 2020, + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + extraFileExtensions: ['.svelte'], + allowAutomaticSingleRunInference: true + }, + env: { + browser: true, + es2020: 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', + curly: ['error', 'all'] + } }; diff --git a/.prettierrc b/.prettierrc index 53609cfb..ab65daf2 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,8 +1,7 @@ { - "singleQuote": true, - "svelteSortOrder": "options-scripts-markup-styles", - "bracketSameLine": true, - "useTabs": true, - "trailingComma": "none", - "printWidth": 100 + "singleQuote": true, + "svelteSortOrder": "options-scripts-markup-styles", + "bracketSameLine": true, + "trailingComma": "none", + "printWidth": 100 } diff --git a/.vscode/launch.json b/.vscode/launch.json index 197e0480..1689524c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,15 +1,15 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "pwa-chrome", - "request": "launch", - "name": "Launch Chrome against localhost", - "url": "http://localhost:3000", - "webRoot": "${workspaceFolder}" - } - ] + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:3000", + "webRoot": "${workspaceFolder}" + } + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 47967284..a7a60240 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,9 @@ { - "editor.formatOnSave": true, - "cSpell.words": ["asyncable", "pako", "Serde", "serdes"], - "vitest.commandLine": "yarn test:unit", - "vitest.enable": true, - "testing.autoRun.mode": "rerun", - "svelte.enable-ts-plugin": true, - "githubPullRequests.ignoredPullRequestBranches": ["develop"] + "editor.formatOnSave": true, + "cSpell.words": ["asyncable", "pako", "Serde", "serdes"], + "vitest.commandLine": "yarn test:unit", + "vitest.enable": true, + "testing.autoRun.mode": "rerun", + "svelte.enable-ts-plugin": true, + "githubPullRequests.ignoredPullRequestBranches": ["develop"] } diff --git a/bin/update-monaco.js b/bin/update-monaco.js index 3d81bdfc..875639ba 100755 --- a/bin/update-monaco.js +++ b/bin/update-monaco.js @@ -9,37 +9,37 @@ const monacoVersion = packageJson.dependencies['monaco-editor'].replace('^', '') // fetch monaco sri info from cdnjs api const cdnjsAPIResp = await fetch( - `https://api.cdnjs.com/libraries/monaco-editor/${monacoVersion}?fields=sri` + `https://api.cdnjs.com/libraries/monaco-editor/${monacoVersion}?fields=sri` ); if (cdnjsAPIResp.ok) { - const respJson = await cdnjsAPIResp.json(); - const htmlPath = path.join('src', 'app.html'); - const appHtml = fs - .readFileSync(htmlPath, 'utf8') - // update monaco version of every asset in app.html - .replaceAll(/[0-9.]+\/min\/vs/g, `${monacoVersion}/min/vs`); - const root = parse(appHtml); - const updateIntegrity = (tag, attr) => { - for (const node of root - .getElementsByTagName(tag) - .filter((node) => node.getAttribute(attr)?.includes('monaco-editor'))) { - const file = node.getAttribute(attr).split(`${monacoVersion}/`)[1]; - node.setAttribute('integrity', respJson.sri[file]); - } - }; + const respJson = await cdnjsAPIResp.json(); + const htmlPath = path.join('src', 'app.html'); + const appHtml = fs + .readFileSync(htmlPath, 'utf8') + // update monaco version of every asset in app.html + .replaceAll(/[0-9.]+\/min\/vs/g, `${monacoVersion}/min/vs`); + const root = parse(appHtml); + const updateIntegrity = (tag, attr) => { + for (const node of root + .getElementsByTagName(tag) + .filter((node) => node.getAttribute(attr)?.includes('monaco-editor'))) { + const file = node.getAttribute(attr).split(`${monacoVersion}/`)[1]; + node.setAttribute('integrity', respJson.sri[file]); + } + }; - updateIntegrity('script', 'src'); - updateIntegrity('link', 'href'); + updateIntegrity('script', 'src'); + updateIntegrity('link', 'href'); - fs.writeFileSync( - htmlPath, - prettier.format(root.toString(), { - singleQuote: false, - parser: 'html', - bracketSameLine: true, - useTabs: true - }) - ); + fs.writeFileSync( + htmlPath, + prettier.format(root.toString(), { + singleQuote: false, + parser: 'html', + bracketSameLine: true, + useTabs: true + }) + ); } else { - throw Error('Unable to fetch monaco sri data from cdnjs api.'); + throw Error('Unable to fetch monaco sri data from cdnjs api.'); } diff --git a/cypress.config.js b/cypress.config.js index 32775249..0e2c5933 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -2,34 +2,34 @@ import { defineConfig } from 'cypress'; import fs from 'fs'; import { isFileExist, findFiles } from 'cy-verify-downloads'; export default defineConfig({ - projectId: '2ckppp', - viewportWidth: 1440, - viewportHeight: 768, - snapshotFileName: './cypress/snapshots.js', - defaultCommandTimeout: 16000, - requestTimeout: 16000, - retries: { - runMode: 4, - openMode: 0 - }, - e2e: { - setupNodeEvents(on, config) { - on('task', { - isFileExist, - findFiles, - deleteFile(path) { - fs.rmSync(path); - return null; - }, - readFileMaybe(filename) { - if (fs.existsSync(filename)) { - return fs.readFileSync(filename, 'utf8'); - } - return null; - } - }); - }, - baseUrl: 'http://localhost:3000', - specPattern: 'cypress/e2e/**/*.spec.ts' - } + projectId: '2ckppp', + viewportWidth: 1440, + viewportHeight: 768, + snapshotFileName: './cypress/snapshots.js', + defaultCommandTimeout: 16000, + requestTimeout: 16000, + retries: { + runMode: 4, + openMode: 0 + }, + e2e: { + setupNodeEvents(on, config) { + on('task', { + isFileExist, + findFiles, + deleteFile(path) { + fs.rmSync(path); + return null; + }, + readFileMaybe(filename) { + if (fs.existsSync(filename)) { + return fs.readFileSync(filename, 'utf8'); + } + return null; + } + }); + }, + baseUrl: 'http://localhost:3000', + specPattern: 'cypress/e2e/**/*.spec.ts' + } }); diff --git a/cypress/.eslintrc b/cypress/.eslintrc index 43c00855..01edfeeb 100644 --- a/cypress/.eslintrc +++ b/cypress/.eslintrc @@ -1,10 +1,10 @@ { - "plugins": ["cypress"], - "extends": ["plugin:cypress/recommended"], - "rules": { - "jest/expect-expect": "off" - }, - "env": { - "cypress/globals": true - } + "plugins": ["cypress"], + "extends": ["plugin:cypress/recommended"], + "rules": { + "jest/expect-expect": "off" + }, + "env": { + "cypress/globals": true + } } diff --git a/cypress/e2e/actions.spec.ts b/cypress/e2e/actions.spec.ts index b54c5f24..d004e81a 100644 --- a/cypress/e2e/actions.spec.ts +++ b/cypress/e2e/actions.spec.ts @@ -1,48 +1,48 @@ import { verifyFileSize } from './util'; describe('Check actions', () => { - beforeEach(() => { - cy.clearLocalStorage(); - cy.visit('/edit'); - }); + 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 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!!'); - }); + 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!!'); + }); - it('should download png and svg', () => { - cy.clock(new Date(2022, 0, 1).getTime()); + it('should download png and svg', () => { + cy.clock(new Date(2022, 0, 1).getTime()); - cy.get(`#downloadPNG`).click(); - verifyFileSize('diagram', 'png', 21_000); + cy.get(`#downloadPNG`).click(); + verifyFileSize('diagram', 'png', 21_000); - cy.get(`#downloadSVG`).click(); - verifyFileSize('diagram', 'svg', 10_000); + cy.get(`#downloadSVG`).click(); + verifyFileSize('diagram', 'svg', 10_000); - // Verify downloaded file is different for different diagrams - cy.contains('Sample Diagrams').click(); - cy.contains('ER').click(); + // Verify downloaded file is different for different diagrams + cy.contains('Sample Diagrams').click(); + cy.contains('ER').click(); - cy.get(`#downloadPNG`).click(); - verifyFileSize('diagram', 'png', 46_000); + cy.get(`#downloadPNG`).click(); + verifyFileSize('diagram', 'png', 46_000); - cy.get(`#downloadSVG`).click(); - verifyFileSize('diagram', 'svg', 12_000); + cy.get(`#downloadSVG`).click(); + verifyFileSize('diagram', 'svg', 12_000); - cy.clock().invoke('restore'); - }); + cy.clock().invoke('restore'); + }); }); diff --git a/cypress/e2e/diagramUpdate.spec.ts b/cypress/e2e/diagramUpdate.spec.ts index dd09a0a1..07bd430a 100644 --- a/cypress/e2e/diagramUpdate.spec.ts +++ b/cypress/e2e/diagramUpdate.spec.ts @@ -1,86 +1,86 @@ import { getEditor, cmd } from './util'; describe('Auto sync tests', () => { - beforeEach(() => { - cy.clearLocalStorage(); - cy.visit('/'); - }); + 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('#errorContainer').should('not.exist'); - getEditor({ bottom: true, newline: true }).type(' C --> Test'); - cy.get('#view').should('have.class', 'outOfSync'); - cy.get('#errorContainer').should('contain.text', 'Diagram out of sync.'); - cy.getLocalStorage('codeStore').snapshot(); - }); + it('should dim diagram when code is edited', () => { + cy.contains('Auto sync').click(); + cy.get('#view').should('not.have.class', 'outOfSync'); + cy.get('#errorContainer').should('not.exist'); + getEditor({ bottom: true, newline: true }).type(' C --> Test'); + cy.get('#view').should('have.class', 'outOfSync'); + cy.get('#errorContainer').should('contain.text', 'Diagram out of sync.'); + cy.getLocalStorage('codeStore').snapshot(); + }); - it('should update diagram when shortcut is used', () => { - cy.contains('Auto sync').click(); - cy.get('#view').should('not.have.class', 'outOfSync'); - getEditor().type(' C --> Test'); - cy.get('#view').should('have.class', 'outOfSync'); - cy.get('#errorContainer').should('contain.text', 'Diagram out of sync.'); - getEditor().type(`${cmd}{enter}`); - cy.get('#errorContainer').should('not.exist'); - cy.get('#view').should('not.have.class', 'outOfSync'); - }); + it('should update diagram when shortcut is used', () => { + cy.contains('Auto sync').click(); + cy.get('#view').should('not.have.class', 'outOfSync'); + getEditor().type(' C --> Test'); + cy.get('#view').should('have.class', 'outOfSync'); + cy.get('#errorContainer').should('contain.text', 'Diagram out of sync.'); + getEditor().type(`${cmd}{enter}`); + cy.get('#errorContainer').should('not.exist'); + cy.get('#view').should('not.have.class', 'outOfSync'); + }); - 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 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'); - getEditor().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(); - getEditor().type('ing'); - cy.get('#view').should('not.have.class', 'outOfSync'); - cy.getLocalStorage('codeStore').snapshot(); - }); + it('should not dim diagram when code is in sync', () => { + cy.contains('Auto sync').click(); + cy.get('#view').should('not.have.class', 'outOfSync'); + getEditor().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(); + getEditor().type('ing'); + cy.get('#view').should('not.have.class', 'outOfSync'); + cy.getLocalStorage('codeStore').snapshot(); + }); - it('supports commenting code out/in', () => { - getEditor().type(`{uparrow}${cmd}/`); - cy.get('#view').contains('Car').should('not.exist'); + it('supports commenting code out/in', () => { + getEditor().type(`{uparrow}${cmd}/`); + cy.get('#view').contains('Car').should('not.exist'); - getEditor().type(`{uparrow}${cmd}/`); - cy.get('#view').contains('Car').should('exist'); - }); + getEditor().type(`{uparrow}${cmd}/`); + cy.get('#view').contains('Car').should('exist'); + }); - it('supports editing code when code is incorrect', () => { - cy.visit( - '/edit#pako:eNpljjEKwzAMRa8SNOcEnlt6gK5eVFvYJsgOqkwpIXevg9smEE1PnyfxF3DFExgISW-CczQ2D21cYU7a-SGYXRwyvTp9jUhuKlVP-eHy7zA-leQsMEmg_QOM0BLG5FujZVMsaCQmC6ahR5ks2Lw2r84ela4-aREwKpVGwKrl_s7ut3fnkjAIcg_XDzuaUhs' - ); - cy.get('#errorContainer').should('not.exist'); - getEditor({ newline: true }).type(`branch test`); - cy.get('#editor').contains('branch test').should('exist'); - cy.get('#errorContainer') - .contains( - 'Error: Trying to checkout branch which is not yet created. (Help try using "branch master")' - ) - .should('exist'); - }); + it('supports editing code when code is incorrect', () => { + cy.visit( + '/edit#pako:eNpljjEKwzAMRa8SNOcEnlt6gK5eVFvYJsgOqkwpIXevg9smEE1PnyfxF3DFExgISW-CczQ2D21cYU7a-SGYXRwyvTp9jUhuKlVP-eHy7zA-leQsMEmg_QOM0BLG5FujZVMsaCQmC6ahR5ks2Lw2r84ela4-aREwKpVGwKrl_s7ut3fnkjAIcg_XDzuaUhs' + ); + cy.get('#errorContainer').should('not.exist'); + getEditor({ newline: true }).type(`branch test`); + cy.get('#editor').contains('branch test').should('exist'); + cy.get('#errorContainer') + .contains( + 'Error: Trying to checkout branch which is not yet created. (Help try using "branch master")' + ) + .should('exist'); + }); }); describe('Pan and Zoom', () => { - beforeEach(() => { - cy.clearLocalStorage(); - cy.visit('/'); - }); - it('should toggle pan and zoom', () => { - cy.get('#svg-pan-zoom-reset-pan-zoom').should('not.exist'); - cy.contains('Pan & Zoom').click(); - cy.get('#svg-pan-zoom-reset-pan-zoom').should('exist'); - cy.contains('Pan & Zoom').click(); - cy.get('#svg-pan-zoom-reset-pan-zoom').should('not.exist'); - }); + beforeEach(() => { + cy.clearLocalStorage(); + cy.visit('/'); + }); + it('should toggle pan and zoom', () => { + cy.get('#svg-pan-zoom-reset-pan-zoom').should('not.exist'); + cy.contains('Pan & Zoom').click(); + cy.get('#svg-pan-zoom-reset-pan-zoom').should('exist'); + cy.contains('Pan & Zoom').click(); + cy.get('#svg-pan-zoom-reset-pan-zoom').should('not.exist'); + }); }); diff --git a/cypress/e2e/docMap.spec.ts b/cypress/e2e/docMap.spec.ts index 0d0dfc0d..5c7e8201 100755 --- a/cypress/e2e/docMap.spec.ts +++ b/cypress/e2e/docMap.spec.ts @@ -1,36 +1,36 @@ describe('Editor docs tests', () => { - beforeEach(() => { - cy.on('uncaught:exception', () => { - return false; - }); - cy.clearLocalStorage(); - cy.visit('/edit'); - cy.contains('Sample Diagrams').click(); - }); + 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 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').click(); - cy.get(`[data-cy=docs][href$="/#/flowchart"]`).should('exist'); + it('Test to see if the correct URL loads when changing from one diagram to other', () => { + cy.contains('Flow').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('Config').click(); + cy.get(`[data-cy=docs][href$="/#/flowchart?id=configuration"]`).should('exist'); - cy.contains('Sequence').click(); - cy.get(`[data-cy=docs][href$="/#/sequenceDiagram?id=configuration"]`).should('exist'); + cy.contains('Sequence').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'); - }); + 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').click(); - cy.get(`[data-cy=docs][href$="/#/stateDiagram"]`).should('exist'); + it("Test to check URLs for a case where config URL doesn't exist", () => { + cy.contains('State').click(); + cy.get(`[data-cy=docs][href$="/#/stateDiagram"]`).should('exist'); - cy.contains('Config').click(); - cy.get(`[data-cy=docs][href$="/#/stateDiagram"]`).should('exist'); - }); + cy.contains('Config').click(); + cy.get(`[data-cy=docs][href$="/#/stateDiagram"]`).should('exist'); + }); }); diff --git a/cypress/e2e/history.spec.ts b/cypress/e2e/history.spec.ts index e9e32fda..af5305c9 100644 --- a/cypress/e2e/history.spec.ts +++ b/cypress/e2e/history.spec.ts @@ -1,105 +1,105 @@ import { getEditor, verifyFileSnapshot } from './util'; describe('Save History', () => { - beforeEach(() => { - cy.clock(new Date(2022, 0, 1).getTime()); - cy.clearLocalStorage(); - cy.visit('/edit'); + beforeEach(() => { + cy.clock(new Date(2022, 0, 1).getTime()); + cy.clearLocalStorage(); + cy.visit('/edit'); - cy.contains('Actions').click(); - cy.contains('History').click(); - }); + cy.contains('Actions').click(); + cy.contains('History').click(); + }); - afterEach(() => { - cy.clock().invoke('restore'); - }); + afterEach(() => { + cy.clock().invoke('restore'); + }); - it('should load history from localstorage', () => { - cy.setLocalStorage( - 'manualHistoryStore', - '[{"state":{"code":"graph TD\\n A[Halloween] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":false},"time":0,"type":"manual","id":"d7ea820e-21dd-418a-b984-fd58acde09df","name":"hollow-art"},{"state":{"code":"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":true},"time":0,"type":"manual","id":"b749ffc6-522b-4a44-86cf-7c1ffc3146b3","name":"helpful-ocean"}]' - ); - cy.setLocalStorage( - 'autoHistoryStore', - '[{"state":{"code":"graph TD\\n A[New Year] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":false},"time":0,"type":"auto","id":"69ea820e-522b-4a44-86cf-fd58acde09df","name":"barking-dog"},{"state":{"code":"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":true},"time":0,"type":"manual","id":"x749ffc6-21dd-418a-b984-7c1ffc3146b3","name":"needy-mosquito"}]' - ); - cy.reload(); - cy.contains('Actions').click(); - cy.contains('History').click(); - cy.get('#historyList').find('li').should('have.length', 2); - cy.get('#historyList').find('No items in History').should('not.exist'); - cy.get('#historyList').contains('helpful-ocean'); - cy.get('#historyList').contains('hollow-art'); - cy.contains('Restore').click(); - cy.contains('Halloween'); - cy.contains('Timeline').click(); + it('should load history from localstorage', () => { + cy.setLocalStorage( + 'manualHistoryStore', + '[{"state":{"code":"graph TD\\n A[Halloween] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":false},"time":0,"type":"manual","id":"d7ea820e-21dd-418a-b984-fd58acde09df","name":"hollow-art"},{"state":{"code":"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":true},"time":0,"type":"manual","id":"b749ffc6-522b-4a44-86cf-7c1ffc3146b3","name":"helpful-ocean"}]' + ); + cy.setLocalStorage( + 'autoHistoryStore', + '[{"state":{"code":"graph TD\\n A[New Year] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":false},"time":0,"type":"auto","id":"69ea820e-522b-4a44-86cf-fd58acde09df","name":"barking-dog"},{"state":{"code":"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":true},"time":0,"type":"manual","id":"x749ffc6-21dd-418a-b984-7c1ffc3146b3","name":"needy-mosquito"}]' + ); + cy.reload(); + cy.contains('Actions').click(); + cy.contains('History').click(); + cy.get('#historyList').find('li').should('have.length', 2); + cy.get('#historyList').find('No items in History').should('not.exist'); + cy.get('#historyList').contains('helpful-ocean'); + cy.get('#historyList').contains('hollow-art'); + cy.contains('Restore').click(); + cy.contains('Halloween'); + cy.contains('Timeline').click(); - cy.get('#historyList').find('li').should('have.length', 2); - cy.get('#historyList').find('No items in History').should('not.exist'); - cy.get('#historyList').contains('needy-mosquito'); - cy.get('#historyList').contains('barking-dog'); - cy.contains('Restore').click(); - cy.contains('New Year'); - }); + cy.get('#historyList').find('li').should('have.length', 2); + cy.get('#historyList').find('No items in History').should('not.exist'); + cy.get('#historyList').contains('needy-mosquito'); + cy.get('#historyList').contains('barking-dog'); + cy.contains('Restore').click(); + cy.contains('New Year'); + }); - 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); - getEditor().type(' C --> HistoryTest'); - cy.get('#saveHistory').click(); - cy.get('#historyList').find('li').should('have.length', 2); - }); + 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); + getEditor().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(); - getEditor().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(); - getEditor().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'); - }); + it('should be able to restore and delete', () => { + cy.get('#saveHistory').click(); + getEditor().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(); + getEditor().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 - xit('should auto save history', () => { - getEditor().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); - }); + // TODO: Fix #639 + xit('should auto save history', () => { + getEditor().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); + }); - it('should download history', () => { - cy.get('#saveHistory').click(); - cy.get(`#downloadHistory`).click(); - verifyFileSnapshot('history', 'json', 'A[Christmas] -->|Get money| B(Go shopping)'); - }); + it('should download history', () => { + cy.get('#saveHistory').click(); + cy.get(`#downloadHistory`).click(); + verifyFileSnapshot('history', 'json', 'A[Christmas] -->|Get money| B(Go shopping)'); + }); }); diff --git a/cypress/e2e/loadSite.spec.ts b/cypress/e2e/loadSite.spec.ts index f39b2957..b80104e2 100644 --- a/cypress/e2e/loadSite.spec.ts +++ b/cypress/e2e/loadSite.spec.ts @@ -1,122 +1,122 @@ import { toBase64 } from 'js-base64'; describe('Site Loads', () => { - beforeEach(() => { - cy.clearLocalStorage(); - cy.visit('/'); - }); + beforeEach(() => { + cy.clearLocalStorage(); + cy.visit('/'); + }); - it('Check Home page load', () => { - cy.url().should('include', '/edit'); - cy.contains('History').click(); - cy.getLocalStorage('codeStore').snapshot(); - }); + it('Check Home page load', () => { + 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:eNp'); - }); + it('Check Redirect from old URL', () => { + cy.visit( + '/#/edit/eyJjb2RlIjoiZ3JhcGggVERcbiAgICBBW0NocmlzdG1hc10gLS0-fEdldCBtb25leXwgQihHbyBzaG9wcGluZylcbiAgICBCIC0tPiBDe0xldCBtZSB0aGlua31cbiAgICBDIC0tPnxPbmV8IERbTGFwdG9wXVxuICAgIEMgLS0-fFR3b3wgRVtpUGhvbmVdXG4gICAgQyAtLT58VGhyZWV8IEZbZmE6ZmEtY2FyIENhcl0iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ' + ); + cy.url().should('include', '/edit#pako:eNp'); + }); - it('should load sample diagrams when clicked', () => { - cy.contains('Sample Diagrams').click(); - cy.contains('Pie').click(); - cy.contains('pie title Pets adopted by volunteers'); - cy.contains('Class').click(); - cy.contains('classDiagram'); - }); + it('should load sample diagrams when clicked', () => { + cy.contains('Sample Diagrams').click(); + cy.contains('Pie').click(); + cy.contains('pie title Pets adopted by volunteers'); + cy.contains('Class').click(); + cy.contains('classDiagram'); + }); - (Cypress.env('CI') === 'true' ? describe : describe.skip)('github', () => { - 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(); - }); + (Cypress.env('CI') === 'true' ? describe : describe.skip)('github', () => { + 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 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 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[\\"\\"]","mermaid":"{\\"securityLevel\\": \\"loose\\", \\"theme\\": \\"forest\\"}","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(' { + // const b64State = toBase64( + // `{"code":"graph TD\\nA[\\"\\"]","mermaid":"{\\"securityLevel\\": \\"loose\\", \\"theme\\": \\"forest\\"}","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(' { - const b64State = toBase64( - `{"code":"graph TD\\nA[\\"\\"]","mermaid":"{\\"securityLevel\\": \\"loose\\", \\"theme\\": \\"forest\\"}","autoSync":true,"updateDiagram":true}`, - true - ); - cy.on('window:confirm', () => false); - cy.visit(`/edit#${b64State}`); - cy.get('#editor').type(' '); - cy.contains('Config').click(); - cy.contains('forest'); - cy.contains('securityLevel'); - cy.get('#view').find('img').should('be.visible'); - }); + it.skip('should allow persisting "securityLevel" using confirm dialogue', () => { + const b64State = toBase64( + `{"code":"graph TD\\nA[\\"\\"]","mermaid":"{\\"securityLevel\\": \\"loose\\", \\"theme\\": \\"forest\\"}","autoSync":true,"updateDiagram":true}`, + true + ); + cy.on('window:confirm', () => false); + cy.visit(`/edit#${b64State}`); + cy.get('#editor').type(' '); + 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.reload(true); - cy.contains('Please Click here to Raise an issue in github.'); - }); + it('should show troubleshooting steps if loading fails', () => { + cy.visit('/#/edit/eyJjb2RlIjoiZ3JhcGggVERcbiAg'); + cy.reload(true); + 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 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.contains('Animal'); - }); + 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.contains('Animal'); + }); }); diff --git a/cypress/e2e/themes.spec.ts b/cypress/e2e/themes.spec.ts index a5a5c77f..c7d986ff 100644 --- a/cypress/e2e/themes.spec.ts +++ b/cypress/e2e/themes.spec.ts @@ -1,56 +1,56 @@ 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(); - }); + 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 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(); - }); - }); + 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', () => { - 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(); - }); + describe('Test dark mode', () => { + 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(); - }); - }); + 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(); + }); + }); }); diff --git a/cypress/e2e/util.ts b/cypress/e2e/util.ts index cf0303ba..73fe6dd2 100644 --- a/cypress/e2e/util.ts +++ b/cypress/e2e/util.ts @@ -1,41 +1,41 @@ export const cmd = `{${Cypress.platform === 'darwin' ? 'meta' : 'ctrl'}}`; export const getEditor = ({ bottom = true, newline = false } = {}) => - cy - .get('#editor textarea:first') - .click() - .focused() - .type(`${bottom ? '{pageDown}' : cmd}`) - .type(`${newline ? '{enter}' : cmd}`); + cy + .get('#editor textarea:first') + .click() + .focused() + .type(`${bottom ? '{pageDown}' : cmd}`) + .type(`${newline ? '{enter}' : cmd}`); const downloadsFolder = Cypress.config('downloadsFolder'); export const verifyFileSize = ( - fileType: 'history' | 'diagram', - extension: string, - size: number + fileType: 'history' | 'diagram', + extension: string, + size: number ) => { - const fileName = `mermaid-${fileType}-2022-01-01-000000.${extension}`; - const filePath = `${downloadsFolder}/${fileName}`; - cy.verifyDownload(fileName); - cy.readFile(filePath, null, { - log: false - }).then((buffer) => expect((buffer as ArrayBuffer).byteLength).to.be.gt(size)); - cy.task('deleteFile', filePath); + const fileName = `mermaid-${fileType}-2022-01-01-000000.${extension}`; + const filePath = `${downloadsFolder}/${fileName}`; + cy.verifyDownload(fileName); + cy.readFile(filePath, null, { + log: false + }).then((buffer) => expect((buffer as ArrayBuffer).byteLength).to.be.gt(size)); + cy.task('deleteFile', filePath); }; export const verifyFileSnapshot = ( - fileType: 'history' | 'diagram', - extension: string, - content: string + fileType: 'history' | 'diagram', + extension: string, + content: string ) => { - const fileName = `mermaid-${fileType}-2022-01-01-000000.${extension}`; - const filePath = `${downloadsFolder}/${fileName}`; - cy.verifyDownload(fileName); - cy.readFile(filePath, null, { - log: false - }).then((buffer) => - expect(new TextDecoder('utf-8').decode(buffer as ArrayBuffer)).to.contain(content) - ); - cy.task('deleteFile', filePath); + const fileName = `mermaid-${fileType}-2022-01-01-000000.${extension}`; + const filePath = `${downloadsFolder}/${fileName}`; + cy.verifyDownload(fileName); + cy.readFile(filePath, null, { + log: false + }).then((buffer) => + expect(new TextDecoder('utf-8').decode(buffer as ArrayBuffer)).to.contain(content) + ); + cy.task('deleteFile', filePath); }; diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json index 0ff2d645..02e42543 100644 --- a/cypress/fixtures/example.json +++ b/cypress/fixtures/example.json @@ -1,5 +1,5 @@ { - "name": "Using fixtures to represent data", - "email": "hello@cypress.io", - "body": "Fixtures are a great way to mock data for responses to routes" + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" } diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index fe4e2603..0f4147cd 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -27,12 +27,12 @@ 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; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + snapshot(): void; + } + } } import 'cypress-localstorage-commands'; diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index af0278d8..d9401131 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -21,8 +21,8 @@ require('cy-verify-downloads').addCustomCommand(); // 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; - } + /* returning false here prevents Cypress from failing the test */ + if (err.message.includes('ResizeObserver loop limit exceeded')) { + return false; + } }); diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index efeb6a0f..ec647435 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -1,7 +1,7 @@ { - "compilerOptions": { - "allowJs": true, - "types": ["cypress", "cypress-localstorage-commands", "cy-verify-downloads"] - }, - "include": ["**/*.ts"] + "compilerOptions": { + "allowJs": true, + "types": ["cypress", "cypress-localstorage-commands", "cy-verify-downloads"] + }, + "include": ["**/*.ts"] } diff --git a/package.json b/package.json index 4b7b9c1a..0975b5da 100644 --- a/package.json +++ b/package.json @@ -1,94 +1,94 @@ { - "name": "mermaid-live-editor", - "version": "2.0.67", - "type": "module", - "license": "MIT", - "scripts": { - "dev": "vite dev", - "dev:force": "MERMAID_LOCAL=true yarn dev --force", - "dev:test": "yarn dev", - "build": "vite build", - "preview": "vite preview", - "lint": "prettier --check --cache --plugin-search-dir=. .;eslint --ignore-path .gitignore .", - "lint:fix": "prettier --write --cache --plugin-search-dir=. .;eslint --fix --ignore-path .gitignore .", - "format": "prettier --write --cache --plugin-search-dir=. .", - "pre-commit": "lint-staged", - "postinstall": "husky install; svelte-kit sync", - "test:unit": "vitest", - "test:ui": "vitest --ui", - "test:coverage": "vitest run --coverage", - "test:browser": "cypress run", - "test": "test:unit && test:browser", - "cy": "cypress open" - }, - "devDependencies": { - "@cypress/snapshot": "2.1.7", - "@sveltejs/adapter-static": "1.0.0-next.46", - "@sveltejs/kit": "1.0.0-next.522", - "@testing-library/jest-dom": "5.16.5", - "@testing-library/svelte": "3.2.2", - "@types/pako": "2.0.0", - "@types/uuid": "8.3.4", - "@typescript-eslint/eslint-plugin": "5.42.0", - "@typescript-eslint/parser": "5.42.0", - "@vitest/ui": "0.24.3", - "autoprefixer": "10.4.12", - "c8": "7.12.0", - "chai": "4.3.6", - "cssnano": "5.1.13", - "cy-verify-downloads": "0.1.11", - "cypress": "10.11.0", - "cypress-localstorage-commands": "2.2.1", - "eslint": "8.26.0", - "eslint-config-prettier": "8.5.0", - "eslint-plugin-cypress": "2.12.1", - "eslint-plugin-es": "4.1.0", - "eslint-plugin-postcss-modules": "2.0.0", - "eslint-plugin-svelte3": "4.0.0", - "eslint-plugin-tailwindcss": "3.6.2", - "eslint-plugin-vitest": "0.0.11", - "esserializer": "1.3.2", - "husky": "8.0.1", - "jsdom": "20.0.1", - "lint-staged": "13.0.3", - "node-html-parser": "6.1.1", - "postcss": "8.4.18", - "postcss-load-config": "4.0.1", - "prettier": "2.7.1", - "prettier-plugin-svelte": "2.8.0", - "svelte": "3.52.0", - "svelte-preprocess": "4.10.7", - "tailwindcss": "3.2.1", - "tslib": "2.4.0", - "typescript": "4.8.4", - "vite": "3.2.2", - "vitest": "0.24.3" - }, - "dependencies": { - "analytics": "0.8.1", - "analytics-plugin-plausible": "0.0.6", - "daisyui": "2.37.0", - "js-base64": "3.7.2", - "mermaid": "9.2.0", - "moment": "2.29.4", - "monaco-editor": "0.34.1", - "monaco-mermaid": "1.0.6", - "pako": "2.0.4", - "random-word-slugs": "0.1.6", - "svg-pan-zoom": "3.6.1", - "uuid": "9.0.0" - }, - "lint-staged": { - "*.{ts,svelte,js,css,md,json}": [ - "prettier --plugin-search-dir=. --write", - "eslint --ignore-path .gitignore " - ] - }, - "volta": { - "node": "18.12.0", - "yarn": "1.22.19" - }, - "engines": { - "node": ">=16.7" - } + "name": "mermaid-live-editor", + "version": "2.0.67", + "type": "module", + "license": "MIT", + "scripts": { + "dev": "vite dev", + "dev:force": "MERMAID_LOCAL=true yarn dev --force", + "dev:test": "yarn dev", + "build": "vite build", + "preview": "vite preview", + "lint": "prettier --check --cache --plugin-search-dir=. .;eslint --ignore-path .gitignore .", + "lint:fix": "prettier --write --cache --plugin-search-dir=. .;eslint --fix --ignore-path .gitignore .", + "format": "prettier --write --cache --plugin-search-dir=. .", + "pre-commit": "lint-staged", + "postinstall": "husky install; svelte-kit sync", + "test:unit": "vitest", + "test:ui": "vitest --ui", + "test:coverage": "vitest run --coverage", + "test:browser": "cypress run", + "test": "test:unit && test:browser", + "cy": "cypress open" + }, + "devDependencies": { + "@cypress/snapshot": "2.1.7", + "@sveltejs/adapter-static": "1.0.0-next.46", + "@sveltejs/kit": "1.0.0-next.522", + "@testing-library/jest-dom": "5.16.5", + "@testing-library/svelte": "3.2.2", + "@types/pako": "2.0.0", + "@types/uuid": "8.3.4", + "@typescript-eslint/eslint-plugin": "5.42.0", + "@typescript-eslint/parser": "5.42.0", + "@vitest/ui": "0.24.3", + "autoprefixer": "10.4.12", + "c8": "7.12.0", + "chai": "4.3.6", + "cssnano": "5.1.13", + "cy-verify-downloads": "0.1.11", + "cypress": "10.11.0", + "cypress-localstorage-commands": "2.2.1", + "eslint": "8.26.0", + "eslint-config-prettier": "8.5.0", + "eslint-plugin-cypress": "2.12.1", + "eslint-plugin-es": "4.1.0", + "eslint-plugin-postcss-modules": "2.0.0", + "eslint-plugin-svelte3": "4.0.0", + "eslint-plugin-tailwindcss": "3.6.2", + "eslint-plugin-vitest": "0.0.11", + "esserializer": "1.3.2", + "husky": "8.0.1", + "jsdom": "20.0.1", + "lint-staged": "13.0.3", + "node-html-parser": "6.1.1", + "postcss": "8.4.18", + "postcss-load-config": "4.0.1", + "prettier": "2.7.1", + "prettier-plugin-svelte": "2.8.0", + "svelte": "3.52.0", + "svelte-preprocess": "4.10.7", + "tailwindcss": "3.2.1", + "tslib": "2.4.0", + "typescript": "4.8.4", + "vite": "3.2.2", + "vitest": "0.24.3" + }, + "dependencies": { + "analytics": "0.8.1", + "analytics-plugin-plausible": "0.0.6", + "daisyui": "2.37.0", + "js-base64": "3.7.2", + "mermaid": "9.2.0", + "moment": "2.29.4", + "monaco-editor": "0.34.1", + "monaco-mermaid": "1.0.6", + "pako": "2.0.4", + "random-word-slugs": "0.1.6", + "svg-pan-zoom": "3.6.1", + "uuid": "9.0.0" + }, + "lint-staged": { + "*.{ts,svelte,js,css,md,json}": [ + "prettier --plugin-search-dir=. --write", + "eslint --ignore-path .gitignore " + ] + }, + "volta": { + "node": "18.12.0", + "yarn": "1.22.19" + }, + "engines": { + "node": ">=16.7" + } } diff --git a/postcss.config.cjs b/postcss.config.cjs index 483734f1..efb41d9e 100644 --- a/postcss.config.cjs +++ b/postcss.config.cjs @@ -6,18 +6,18 @@ const mode = process.env.NODE_ENV; const dev = mode === 'development'; module.exports = { - plugins: [ - // Some plugins, like postcss-nested, need to run before Tailwind + plugins: [ + // Some plugins, like postcss-nested, need to run before Tailwind - tailwindcss, + tailwindcss, - // But others, like autoprefixer, need to run after + // But others, like autoprefixer, need to run after - autoprefixer, + autoprefixer, - !dev && - cssnano({ - preset: 'default' - }) - ] + !dev && + cssnano({ + preset: 'default' + }) + ] }; diff --git a/renovate.json b/renovate.json index 619bbb26..1413a3ce 100644 --- a/renovate.json +++ b/renovate.json @@ -1,24 +1,24 @@ { - "extends": [ - "config:base", - ":rebaseStalePrs", - "group:allNonMajor", - "schedule:earlyMondays", - ":automergeMinor", - ":automergeTesters", - ":automergeLinters", - ":automergeTypes", - ":automergePatch" - ], - "packageRules": [ - { - "matchUpdateTypes": ["minor", "patch", "pin", "digest"], - "automerge": true - } - ], - "dependencyDashboard": true, - "major": { - "dependencyDashboardApproval": true - }, - "dependencyDashboardAutoclose": true + "extends": [ + "config:base", + ":rebaseStalePrs", + "group:allNonMajor", + "schedule:earlyMondays", + ":automergeMinor", + ":automergeTesters", + ":automergeLinters", + ":automergeTypes", + ":automergePatch" + ], + "packageRules": [ + { + "matchUpdateTypes": ["minor", "patch", "pin", "digest"], + "automerge": true + } + ], + "dependencyDashboard": true, + "major": { + "dependencyDashboardApproval": true + }, + "dependencyDashboardAutoclose": true } diff --git a/src/app.html b/src/app.html index 4224cc58..3af662f7 100644 --- a/src/app.html +++ b/src/app.html @@ -1,57 +1,57 @@ - - - - Online FlowChart & Diagrams Editor - Mermaid Live Editor - - - - - - - - - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- + + + + Online FlowChart & Diagrams Editor - Mermaid Live Editor + + + + + + + + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ diff --git a/src/app.postcss b/src/app.postcss index 1b5c3824..888c737e 100644 --- a/src/app.postcss +++ b/src/app.postcss @@ -3,8 +3,8 @@ @tailwind utilities; .input { - @apply flex-1 border-primary border-solid border-2 rounded; + @apply flex-1 border-primary border-solid border-2 rounded; } .action-btn { - @apply btn btn-primary; + @apply btn btn-primary; } diff --git a/src/env.d.ts b/src/env.d.ts index 571447df..e3cf245e 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -1,14 +1,14 @@ /// interface ImportMetaEnv { - readonly MERMAID_RENDERER_URL: string; - readonly MERMAID_KROKI_RENDERER_URL: string; - readonly MERMAID_CDN_URL: string; - readonly MERMAID_BASE_URL: string; - readonly MERMAID_LOCAL: boolean; - // more env variables... + readonly MERMAID_RENDERER_URL: string; + readonly MERMAID_KROKI_RENDERER_URL: string; + readonly MERMAID_CDN_URL: string; + readonly MERMAID_BASE_URL: string; + readonly MERMAID_LOCAL: boolean; + // more env variables... } interface ImportMeta { - readonly env: ImportMetaEnv; + readonly env: ImportMetaEnv; } diff --git a/src/global.d.ts b/src/global.d.ts index 17fa2e24..81ca3d20 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -6,10 +6,10 @@ import type { TestingLibraryMatchers } from '@testing-library/jest-dom/matchers'; declare global { - namespace jest { - interface Matchers - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - extends TestingLibraryMatchers {} - } + namespace jest { + interface Matchers + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + extends TestingLibraryMatchers {} + } } diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 75d6e91a..49af22b0 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,6 +1,6 @@ import type { Handle } from '@sveltejs/kit'; export const handle: Handle = async ({ event, resolve }) => { - const response = await resolve(event, {}); - return response; + const response = await resolve(event, {}); + return response; }; diff --git a/src/lib/components/actions.svelte b/src/lib/components/actions.svelte index e1634e10..ed2acfd3 100644 --- a/src/lib/components/actions.svelte +++ b/src/lib/components/actions.svelte @@ -1,260 +1,260 @@ -
- {#if isClipboardAvailable()} - - {/if} - - - - - - - - - - - +
+ {#if isClipboardAvailable()} + + {/if} + + + + + + + + + + + -
- PNG size - +
+ PNG size + - + - + - {#if imagemodeselected !== 'auto'} - - {/if} -
+ {#if imagemodeselected !== 'auto'} + + {/if} +
-
- - -
+
+ + +
-
- - -
- {#if isNetlify} - - {/if} -
+
+ + +
+ {#if isNetlify} + + {/if} +
diff --git a/src/lib/components/card/card.svelte b/src/lib/components/card/card.svelte index 24ea56f4..ce1c0ad7 100644 --- a/src/lib/components/card/card.svelte +++ b/src/lib/components/card/card.svelte @@ -1,31 +1,31 @@
-
(isOpen = !isOpen)} - on:keypress={() => (isOpen = !isOpen)}> -
- -
- -
-
-
- {#if isOpen} -
- -
- {/if} +
(isOpen = !isOpen)} + on:keypress={() => (isOpen = !isOpen)}> +
+ +
+ +
+
+
+ {#if isOpen} +
+ +
+ {/if}
diff --git a/src/lib/components/card/card.test.ts b/src/lib/components/card/card.test.ts index 98ffccd3..764cad64 100644 --- a/src/lib/components/card/card.test.ts +++ b/src/lib/components/card/card.test.ts @@ -3,22 +3,22 @@ import { describe, expect, it, afterEach } from 'vitest'; import Card from './card.svelte'; describe('card.svelte', () => { - // TODO: @testing-library/svelte claims to add this automatically but it doesn't work without explicit afterEach - afterEach(() => cleanup()); + // TODO: @testing-library/svelte claims to add this automatically but it doesn't work without explicit afterEach + afterEach(() => cleanup()); - it('mounts', () => { - const { container } = render(Card, { - title: 'TabTest', - tabs: [ - { id: 't1', title: 'title1' }, - { id: 't2', title: 'title2' } - ] - }); - expect(container).toBeTruthy(); - expect(container).toHaveTextContent('TabTest'); - expect(container).toHaveTextContent('title1'); - expect(container).toHaveTextContent('title2'); - expect(container).not.toHaveTextContent('title3'); - expect(container.innerHTML).toMatchSnapshot(); - }); + it('mounts', () => { + const { container } = render(Card, { + title: 'TabTest', + tabs: [ + { id: 't1', title: 'title1' }, + { id: 't2', title: 'title2' } + ] + }); + expect(container).toBeTruthy(); + expect(container).toHaveTextContent('TabTest'); + expect(container).toHaveTextContent('title1'); + expect(container).toHaveTextContent('title2'); + expect(container).not.toHaveTextContent('title3'); + expect(container.innerHTML).toMatchSnapshot(); + }); }); diff --git a/src/lib/components/card/tabs.svelte b/src/lib/components/card/tabs.svelte index 433d6628..3c7c8f9d 100644 --- a/src/lib/components/card/tabs.svelte +++ b/src/lib/components/card/tabs.svelte @@ -1,52 +1,52 @@
- (isOpen = !isOpen)} - on:keypress|stopPropagation={() => (isOpen = !isOpen)}> - {#if isCloseable} - - {/if} - {title} - {#if isOpen && tabs} -
    - {#each tabs as tab} -
    toggleTabs(tab)} - on:keypress|stopPropagation={() => toggleTabs(tab)}> - - {tab.title} -
    - {/each} -
- {/if} + (isOpen = !isOpen)} + on:keypress|stopPropagation={() => (isOpen = !isOpen)}> + {#if isCloseable} + + {/if} + {title} + {#if isOpen && tabs} +
    + {#each tabs as tab} +
    toggleTabs(tab)} + on:keypress|stopPropagation={() => toggleTabs(tab)}> + + {tab.title} +
    + {/each} +
+ {/if}
diff --git a/src/lib/components/editor.svelte b/src/lib/components/editor.svelte index fbf4ec49..b84683c8 100644 --- a/src/lib/components/editor.svelte +++ b/src/lib/components/editor.svelte @@ -1,115 +1,115 @@
diff --git a/src/lib/components/history/history.svelte b/src/lib/components/history/history.svelte index 35d3da43..37527bcc 100644 --- a/src/lib/components/history/history.svelte +++ b/src/lib/components/history/history.svelte @@ -1,192 +1,192 @@ -
- - {#if $historyStore.length > 0} - - {/if} - | - - {#if $historyModeStore !== 'loader'} - - {/if} -
-
    - {#if $historyStore.length > 0} - {#each $historyStore as { id, state, time, name, url, type }} -
  • -
    -
    -
    - {#if url} - {name} - {:else} - {name} - {/if} - {relativeTime(time)} -
    -
    -
    - - {#if type !== 'loader'} - - {/if} -
    -
    -
  • - {/each} - {:else} -
    - No items in History
    - Click the Save button to save current state and restore it later.
    - Timeline will automatically be saved every minute. -
    - {/if} -
+
+ + {#if $historyStore.length > 0} + + {/if} + | + + {#if $historyModeStore !== 'loader'} + + {/if} +
+
    + {#if $historyStore.length > 0} + {#each $historyStore as { id, state, time, name, url, type }} +
  • +
    +
    +
    + {#if url} + {name} + {:else} + {name} + {/if} + {relativeTime(time)} +
    +
    +
    + + {#if type !== 'loader'} + + {/if} +
    +
    +
  • + {/each} + {:else} +
    + No items in History
    + Click the Save button to save current state and restore it later.
    + Timeline will automatically be saved every minute. +
    + {/if} +
diff --git a/src/lib/components/history/history.test.ts b/src/lib/components/history/history.test.ts index 2d329e37..d5a0242b 100644 --- a/src/lib/components/history/history.test.ts +++ b/src/lib/components/history/history.test.ts @@ -1,120 +1,120 @@ import type { HistoryEntry } from '$lib/types'; import { describe, it, expect } from 'vitest'; import { - addHistoryEntry, - injectHistoryIDs, - clearHistoryData, - historyModeStore, - historyStore + addHistoryEntry, + injectHistoryIDs, + clearHistoryData, + historyModeStore, + historyStore } from './history'; import { defaultState } from '../../util/state'; import { get } from 'svelte/store'; describe('history', () => { - it('should handle saving individual history entry', () => { - expect(window.localStorage.getItem('manualHistoryStore')).toBe('[]'); - expect(window.localStorage.getItem('autoHistoryStore')).toBe('[]'); + it('should handle saving individual history entry', () => { + expect(window.localStorage.getItem('manualHistoryStore')).toBe('[]'); + expect(window.localStorage.getItem('autoHistoryStore')).toBe('[]'); - addHistoryEntry({ - state: defaultState, - time: 12345, - type: 'manual' - }); + addHistoryEntry({ + state: defaultState, + time: 12345, + type: 'manual' + }); - const [manualEntry]: HistoryEntry[] = JSON.parse( - window.localStorage.getItem('manualHistoryStore') - ); + const [manualEntry]: HistoryEntry[] = JSON.parse( + window.localStorage.getItem('manualHistoryStore') + ); - expect(manualEntry.time).toBe(12345); - expect(manualEntry.type).toBe('manual'); - expect(manualEntry.name).not.toBeNull(); - expect(manualEntry.state).not.toBeNull(); + expect(manualEntry.time).toBe(12345); + expect(manualEntry.type).toBe('manual'); + expect(manualEntry.name).not.toBeNull(); + expect(manualEntry.state).not.toBeNull(); - addHistoryEntry({ - state: defaultState, - time: 54321, - type: 'auto' - }); + addHistoryEntry({ + state: defaultState, + time: 54321, + type: 'auto' + }); - const [autoEntry]: HistoryEntry[] = JSON.parse(window.localStorage.getItem('autoHistoryStore')); + const [autoEntry]: HistoryEntry[] = JSON.parse(window.localStorage.getItem('autoHistoryStore')); - expect(autoEntry.time).toBe(54321); - expect(autoEntry.type).toBe('auto'); - expect(autoEntry.name).not.toBeNull(); - expect(autoEntry.state).not.toBeNull(); + expect(autoEntry.time).toBe(54321); + expect(autoEntry.type).toBe('auto'); + expect(autoEntry.name).not.toBeNull(); + expect(autoEntry.state).not.toBeNull(); - historyModeStore.set('manual'); - clearHistoryData(); - historyModeStore.set('auto'); - clearHistoryData(); - expect(window.localStorage.getItem('manualHistoryStore')).toBe('[]'); - expect(window.localStorage.getItem('autoHistoryStore')).toBe('[]'); - }); + historyModeStore.set('manual'); + clearHistoryData(); + historyModeStore.set('auto'); + clearHistoryData(); + expect(window.localStorage.getItem('manualHistoryStore')).toBe('[]'); + expect(window.localStorage.getItem('autoHistoryStore')).toBe('[]'); + }); - it('should clear history entries', () => { - addHistoryEntry({ - state: defaultState, - time: 12345, - type: 'manual' - }); - addHistoryEntry({ - state: { ...defaultState, code: 'graph TD\\n A[Christmas] -->|Get money| B(Go shopping)' }, - time: 123456, - type: 'manual' - }); + it('should clear history entries', () => { + addHistoryEntry({ + state: defaultState, + time: 12345, + type: 'manual' + }); + addHistoryEntry({ + state: { ...defaultState, code: 'graph TD\\n A[Christmas] -->|Get money| B(Go shopping)' }, + time: 123456, + type: 'manual' + }); - historyModeStore.set('manual'); - const store: HistoryEntry[] = get(historyStore); - expect(store.length).toBe(2); - clearHistoryData(store[1].id); - expect(get(historyStore).length).toBe(1); - clearHistoryData(); - expect(get(historyStore).length).toBe(0); + historyModeStore.set('manual'); + const store: HistoryEntry[] = get(historyStore); + expect(store.length).toBe(2); + clearHistoryData(store[1].id); + expect(get(historyStore).length).toBe(1); + clearHistoryData(); + expect(get(historyStore).length).toBe(0); - historyModeStore.set('auto'); - addHistoryEntry({ - state: defaultState, - time: 54321, - type: 'auto' - }); - addHistoryEntry({ - state: { ...defaultState, code: 'graph TD\\n A[Christmas] -->|Get money| B(Go shopping)' }, - time: 654321, - type: 'auto' - }); - expect(get(historyStore).length).toBe(2); - clearHistoryData(); - expect(get(historyStore).length).toBe(0); - // Test calling when history is empty - clearHistoryData(); - expect(get(historyStore).length).toBe(0); - }); + historyModeStore.set('auto'); + addHistoryEntry({ + state: defaultState, + time: 54321, + type: 'auto' + }); + addHistoryEntry({ + state: { ...defaultState, code: 'graph TD\\n A[Christmas] -->|Get money| B(Go shopping)' }, + time: 654321, + type: 'auto' + }); + expect(get(historyStore).length).toBe(2); + clearHistoryData(); + expect(get(historyStore).length).toBe(0); + // Test calling when history is empty + clearHistoryData(); + expect(get(historyStore).length).toBe(0); + }); }); describe('history migration', () => { - it('should inject history IDs as migration', () => { - window.localStorage.setItem( - 'manualHistoryStore', - '[{"state":{"code":"graph TD\\n A[Halloween] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":false},"time":0,"type":"manual","name":"hollow-art"},{"state":{"code":"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":true},"time":0,"type":"manual","name":"helpful-ocean"}]' - ); - window.localStorage.setItem( - 'autoHistoryStore', - '[{"state":{"code":"graph TD\\n A[New Year] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":false},"time":0,"type":"auto","name":"barking-dog"},{"state":{"code":"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":true},"time":0,"type":"manual","name":"needy-mosquito"}]' - ); - let manualHistoryStore: HistoryEntry[] = JSON.parse( - window.localStorage.getItem('manualHistoryStore') - ); - let autoHistoryStore: HistoryEntry[] = JSON.parse( - window.localStorage.getItem('autoHistoryStore') - ); - expect(manualHistoryStore.every(({ id }) => id !== undefined)).toBe(false); - expect(autoHistoryStore.every(({ id }) => id !== undefined)).toBe(false); + it('should inject history IDs as migration', () => { + window.localStorage.setItem( + 'manualHistoryStore', + '[{"state":{"code":"graph TD\\n A[Halloween] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":false},"time":0,"type":"manual","name":"hollow-art"},{"state":{"code":"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":true},"time":0,"type":"manual","name":"helpful-ocean"}]' + ); + window.localStorage.setItem( + 'autoHistoryStore', + '[{"state":{"code":"graph TD\\n A[New Year] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":false},"time":0,"type":"auto","name":"barking-dog"},{"state":{"code":"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":true},"time":0,"type":"manual","name":"needy-mosquito"}]' + ); + let manualHistoryStore: HistoryEntry[] = JSON.parse( + window.localStorage.getItem('manualHistoryStore') + ); + let autoHistoryStore: HistoryEntry[] = JSON.parse( + window.localStorage.getItem('autoHistoryStore') + ); + expect(manualHistoryStore.every(({ id }) => id !== undefined)).toBe(false); + expect(autoHistoryStore.every(({ id }) => id !== undefined)).toBe(false); - injectHistoryIDs(); + injectHistoryIDs(); - manualHistoryStore = JSON.parse(window.localStorage.getItem('manualHistoryStore')); - autoHistoryStore = JSON.parse(window.localStorage.getItem('autoHistoryStore')); - expect(manualHistoryStore.every(({ id }) => id !== undefined)).toBe(true); - expect(autoHistoryStore.every(({ id }) => id !== undefined)).toBe(true); - }); + manualHistoryStore = JSON.parse(window.localStorage.getItem('manualHistoryStore')); + autoHistoryStore = JSON.parse(window.localStorage.getItem('autoHistoryStore')); + expect(manualHistoryStore.every(({ id }) => id !== undefined)).toBe(true); + expect(autoHistoryStore.every(({ id }) => id !== undefined)).toBe(true); + }); }); diff --git a/src/lib/components/history/history.ts b/src/lib/components/history/history.ts index e5f53c2d..20f19ba3 100644 --- a/src/lib/components/history/history.ts +++ b/src/lib/components/history/history.ts @@ -9,133 +9,133 @@ import { logEvent } from '$lib/util/stats'; const MAX_AUTO_HISTORY_LENGTH = 30; export const historyModeStore: Writable = persist( - writable('manual'), - localStorage(), - 'autoHistoryMode' + writable('manual'), + localStorage(), + 'autoHistoryMode' ); const autoHistoryStore: Writable = persist( - writable([]), - localStorage(), - 'autoHistoryStore' + writable([]), + localStorage(), + 'autoHistoryStore' ); const manualHistoryStore: Writable = persist( - writable([]), - localStorage(), - 'manualHistoryStore' + writable([]), + localStorage(), + 'manualHistoryStore' ); export const loaderHistoryStore: Writable = writable([] as HistoryEntry[]); export const historyStore: Readable = 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); - } - } + [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 = (entryToAdd: Optional): void => { - const entry: HistoryEntry = { - ...entryToAdd, - id: uuidV4() - }; + const entry: HistoryEntry = { + ...entryToAdd, + id: uuidV4() + }; - if (entry.type === 'loader') { - loaderHistoryStore.update((entries) => [entry, ...entries]); - return; - } + if (entry.type === 'loader') { + loaderHistoryStore.update((entries) => [entry, ...entries]); + return; + } - if (!entry.name) { - entry.name = generateSlug(2); - } + if (!entry.name) { + entry.name = generateSlug(2); + } - if (entry.type === 'auto') { - autoHistoryStore.update((entries) => { - if (entries.length >= MAX_AUTO_HISTORY_LENGTH) { - entries = entries.slice(0, MAX_AUTO_HISTORY_LENGTH - 1); - } - return [entry, ...entries]; - }); - } else if (entry.type === 'manual') { - manualHistoryStore.update((entries) => [entry, ...entries]); - logEvent('history', { action: 'save' }); - } + if (entry.type === 'auto') { + autoHistoryStore.update((entries) => { + if (entries.length >= MAX_AUTO_HISTORY_LENGTH) { + entries = entries.slice(0, MAX_AUTO_HISTORY_LENGTH - 1); + } + return [entry, ...entries]; + }); + } else if (entry.type === 'manual') { + manualHistoryStore.update((entries) => [entry, ...entries]); + logEvent('history', { action: 'save' }); + } }; export const clearHistoryData = (idToClear?: string): void => { - (get(historyModeStore) === 'auto' ? autoHistoryStore : manualHistoryStore).update((entries) => { - if (get(historyModeStore) !== 'loader') { - entries = entries.filter(({ id }) => idToClear && id != idToClear); - logEvent('history', { action: 'clear', type: idToClear ? 'single' : 'all' }); - } - return entries; - }); + (get(historyModeStore) === 'auto' ? autoHistoryStore : manualHistoryStore).update((entries) => { + if (get(historyModeStore) !== 'loader') { + entries = entries.filter(({ id }) => idToClear && id != idToClear); + logEvent('history', { action: 'clear', type: idToClear ? 'single' : 'all' }); + } + 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 ''; + const entries = get(auto ? autoHistoryStore : manualHistoryStore); + if (entries.length > 0) { + return JSON.stringify(entries[0].state); + } + return ''; }; export const restoreHistory = (data: HistoryEntry[]) => { - const entries = data.filter(validateEntry); - const invalidEntryCount = data.length - entries.length; - if (invalidEntryCount > 0) { - console.error(`${invalidEntryCount} invalid history entries were removed.`); - console.error(data); - } - if (entries.length > 0) { - let entryCount = 0; - (entries[0].type === 'auto' ? autoHistoryStore : manualHistoryStore).update((existing) => { - const existingIDs = new Set(existing.map(({ id }) => id)); - const newEntries = entries.filter(({ id }) => !existingIDs.has(id)); - entryCount = newEntries.length; - const combined = [...existing, ...newEntries]; - combined.sort((a, b) => b.time - a.time); - return combined; - }); + const entries = data.filter(validateEntry); + const invalidEntryCount = data.length - entries.length; + if (invalidEntryCount > 0) { + console.error(`${invalidEntryCount} invalid history entries were removed.`); + console.error(data); + } + if (entries.length > 0) { + let entryCount = 0; + (entries[0].type === 'auto' ? autoHistoryStore : manualHistoryStore).update((existing) => { + const existingIDs = new Set(existing.map(({ id }) => id)); + const newEntries = entries.filter(({ id }) => !existingIDs.has(id)); + entryCount = newEntries.length; + const combined = [...existing, ...newEntries]; + combined.sort((a, b) => b.time - a.time); + return combined; + }); - alert( - `${entryCount} entries restored. ${invalidEntryCount} invalid, ${ - entries.length - entryCount - } duplicates.` - ); - logEvent('history', { - action: 'restore', - success: entryCount, - invalid: invalidEntryCount, - duplicates: entries.length - entryCount - }); - } else { - alert('No valid entries found.'); - } + alert( + `${entryCount} entries restored. ${invalidEntryCount} invalid, ${ + entries.length - entryCount + } duplicates.` + ); + logEvent('history', { + action: 'restore', + success: entryCount, + invalid: invalidEntryCount, + duplicates: entries.length - entryCount + }); + } else { + alert('No valid entries found.'); + } }; export const injectHistoryIDs = (): void => { - const setIDs = (entries: HistoryEntry[]) => { - for (const entry of entries) { - if (!entry.id) { - entry.id = uuidV4(); - } - } - return entries; - }; - autoHistoryStore.update(setIDs); - manualHistoryStore.update(setIDs); + const setIDs = (entries: HistoryEntry[]) => { + for (const entry of entries) { + if (!entry.id) { + entry.id = uuidV4(); + } + } + return entries; + }; + autoHistoryStore.update(setIDs); + manualHistoryStore.update(setIDs); }; const validateEntry = (entry: HistoryEntry): boolean => { - return entry.type && entry.state && entry.time && true; + return entry.type && entry.state && entry.time && true; }; diff --git a/src/lib/components/navbar.svelte b/src/lib/components/navbar.svelte index 99e3a309..9a179bb9 100644 --- a/src/lib/components/navbar.svelte +++ b/src/lib/components/navbar.svelte @@ -1,82 +1,82 @@ diff --git a/src/lib/components/preset.svelte b/src/lib/components/preset.svelte index ec36c2f2..c1d466b5 100644 --- a/src/lib/components/preset.svelte +++ b/src/lib/components/preset.svelte @@ -1,21 +1,21 @@ -
- {#each diagramOrder as sample} - - {/each} -
+
+ {#each diagramOrder as sample} + + {/each} +
diff --git a/src/lib/components/theme.svelte b/src/lib/components/theme.svelte index 8c786aa3..2e860077 100644 --- a/src/lib/components/theme.svelte +++ b/src/lib/components/theme.svelte @@ -1,64 +1,64 @@ diff --git a/src/lib/components/view.svelte b/src/lib/components/view.svelte index 7807be04..dc5cfc6d 100644 --- a/src/lib/components/view.svelte +++ b/src/lib/components/view.svelte @@ -1,157 +1,157 @@ {#if (error && $stateStore.error instanceof Error) || outOfSync} -
- {#if error} - {$stateStore.error} - {:else} - Diagram out of sync.
- Press (Sync button) or {cmdKey} + Enter to sync. - {/if} -
+
+ {#if error} + {$stateStore.error} + {:else} + Diagram out of sync.
+ Press (Sync button) or {cmdKey} + Enter to sync. + {/if} +
{/if}
-
+
diff --git a/src/lib/types.d.ts b/src/lib/types.d.ts index 04da50b8..30f94b9c 100644 --- a/src/lib/types.d.ts +++ b/src/lib/types.d.ts @@ -3,81 +3,81 @@ * inside `global.d.ts` and removing `export` keyword */ export interface Locals { - userid: string; + userid: string; } export interface MarkerData { - severity: number; - message: string; - source?: string; - startLineNumber: number; - startColumn: number; - endLineNumber: number; - endColumn: number; + severity: number; + message: string; + source?: string; + startLineNumber: number; + startColumn: number; + endLineNumber: number; + endColumn: number; } export interface TabEvents { - select: Tab; + select: Tab; } export interface Tab { - id: string; - title: string; - icon: string; + id: string; + title: string; + icon: string; } export interface State { - code: string; - mermaid: string; - updateDiagram: boolean; - autoSync: boolean; - editorMode?: EditorMode; - panZoom?: boolean; - pan?: { x: number; y: number }; - zoom?: number; - loader?: LoaderConfig; + code: string; + mermaid: string; + updateDiagram: boolean; + autoSync: boolean; + editorMode?: EditorMode; + panZoom?: boolean; + pan?: { x: number; y: number }; + zoom?: number; + loader?: LoaderConfig; } export interface ValidatedState extends State { - editorMode: EditorMode; - error: unknown; - errorMarkers: MarkerData[]; - serialized: string; + editorMode: EditorMode; + error: unknown; + errorMarkers: MarkerData[]; + serialized: string; } export interface GistLoaderConfig { - url: string; + url: string; } export interface LoadingState { - loading: boolean; - message?: string; + loading: boolean; + message?: string; } export interface FileLoaderConfig { - codeURL: string; - configURL?: string; + codeURL: string; + configURL?: string; } export interface LoaderConfig { - type: 'gist' | 'files'; - config: GistLoaderConfig | FileLoaderConfig; + type: 'gist' | 'files'; + config: GistLoaderConfig | FileLoaderConfig; } export type HistoryType = 'auto' | 'manual' | 'loader'; export type HistoryEntry = { id: string; state: State; time: number; url?: string } & ( - | { - type: 'loader'; - name: string; - } - | { - type: HistoryType; - name?: string; - } + | { + type: 'loader'; + name: string; + } + | { + type: HistoryType; + name?: string; + } ); export interface DocConfig { - [key: string]: { - code: string; - config?: string; - }; + [key: string]: { + code: string; + config?: string; + }; } export type EditorMode = 'code' | 'config'; diff --git a/src/lib/util/env.ts b/src/lib/util/env.ts index f5738f65..2c14a52e 100644 --- a/src/lib/util/env.ts +++ b/src/lib/util/env.ts @@ -1,9 +1,9 @@ export const env = { - rendererUrl: import.meta.env.MERMAID_RENDERER_URL ?? 'https://mermaid.ink', - krokiRendererUrl: import.meta.env.MERMAID_KROKI_RENDERER_URL ?? 'https://kroki.io', - mermaidCDNUrl: import.meta.env.MERMAID_CDN_URL ?? 'https://unpkg.com/@mermaid-js', - mermaidBaseURL: import.meta.env.MERMAID_BASE_URL ?? 'http://localhost:9000', - useLocalMermaid: import.meta.env.MERMAID_LOCAL ?? false, - isDev: import.meta.env.DEV, - baseURL: import.meta.env.BASE_URL + rendererUrl: import.meta.env.MERMAID_RENDERER_URL ?? 'https://mermaid.ink', + krokiRendererUrl: import.meta.env.MERMAID_KROKI_RENDERER_URL ?? 'https://kroki.io', + mermaidCDNUrl: import.meta.env.MERMAID_CDN_URL ?? 'https://unpkg.com/@mermaid-js', + mermaidBaseURL: import.meta.env.MERMAID_BASE_URL ?? 'http://localhost:9000', + useLocalMermaid: import.meta.env.MERMAID_LOCAL ?? false, + isDev: import.meta.env.DEV, + baseURL: import.meta.env.BASE_URL }; diff --git a/src/lib/util/fileLoaders/gist.ts b/src/lib/util/fileLoaders/gist.ts index 6304f9c5..f2cb7ed9 100644 --- a/src/lib/util/fileLoaders/gist.ts +++ b/src/lib/util/fileLoaders/gist.ts @@ -10,98 +10,98 @@ const codeFileName = 'code.mmd'; const configFileName = 'config.json'; const isValidGist = (files: any): boolean => { - return codeFileName in files; + return codeFileName in files; }; const getFileContent = async (file: any): Promise => { - if (file.truncated) { - return await (await fetch(file.raw_url)).text(); - } - return file.content; + 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; + code: string; + config?: string; + author: string; + time: number; + version: string; + url: string; } const getGistData = async (gistURL: string): Promise => { - // 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 { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - 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'; - } + // 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 { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + 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; + 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 => { - 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); - } + 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); + } }; diff --git a/src/lib/util/fileLoaders/loader.ts b/src/lib/util/fileLoaders/loader.ts index 9e8df432..e40a988f 100644 --- a/src/lib/util/fileLoaders/loader.ts +++ b/src/lib/util/fileLoaders/loader.ts @@ -2,58 +2,58 @@ import { loadGistData } from './gist'; import { updateCodeStore, defaultState } from '../state'; import type { Loader, State } from '$lib/types'; const loaders: Record = { - gist: loadGistData + gist: loadGistData }; export const loadDataFromUrl = async (): Promise => { - const searchParams = new URLSearchParams(window.location.search); - let state: Partial = defaultState; - let code: string, config: string; - let loaded = false; - const codeURL: string = searchParams.get('code'); - const configURL: string = searchParams.get('config'); + const searchParams = new URLSearchParams(window.location.search); + let state: Partial = 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) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - 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 - } - } - }; - } - loaded && - updateCodeStore({ - ...state, - autoSync: true, - updateDiagram: true - }); + if (codeURL) { + code = await (await fetch(codeURL)).text(); + loaded = true; + } + if (configURL) { + config = await (await fetch(configURL)).text(); + } else { + config = defaultState.mermaid; + } + if (!code) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + 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 + } + } + }; + } + loaded && + updateCodeStore({ + ...state, + autoSync: true, + updateDiagram: true + }); }; diff --git a/src/lib/util/loading.ts b/src/lib/util/loading.ts index 0d483c91..cebe1947 100644 --- a/src/lib/util/loading.ts +++ b/src/lib/util/loading.ts @@ -3,18 +3,18 @@ import type { Writable } from 'svelte/store'; import type { LoadingState } from '$lib/types'; const defaultLoading: LoadingState = { - loading: false + loading: false }; export const loadingStateStore: Writable = writable(defaultLoading); export const initLoading = async (message: string, task: Promise): Promise => { - loadingStateStore.set({ - loading: true, - message - }); - const result: T = await task; - loadingStateStore.set({ - loading: false - }); - return result; + loadingStateStore.set({ + loading: true, + message + }); + const result: T = await task; + loadingStateStore.set({ + loading: false + }); + return result; }; diff --git a/src/lib/util/mermaid.ts b/src/lib/util/mermaid.ts index 1b95b5f2..5e91c2e8 100644 --- a/src/lib/util/mermaid.ts +++ b/src/lib/util/mermaid.ts @@ -5,37 +5,37 @@ import { env } from './env'; const { mermaidBaseURL, mermaidCDNUrl, useLocalMermaid } = env; const getDiagramURL = (name: string, version: string): string => { - if (useLocalMermaid) { - return `${mermaidBaseURL}/${name}-detector.esm.mjs`; - } - return `${mermaidCDNUrl}/${name}@${version}/dist/${name}-detector.esm.mjs`; + if (useLocalMermaid) { + return `${mermaidBaseURL}/${name}-detector.esm.mjs`; + } + return `${mermaidCDNUrl}/${name}@${version}/dist/${name}-detector.esm.mjs`; }; const initialize = mermaid.initializeAsync({ - // logLevel: 0, - lazyLoadedDiagrams: [getDiagramURL('mermaid-mindmap', '9.2.0-rc4')], - loadExternalDiagramsAtStartup: true + // logLevel: 0, + lazyLoadedDiagrams: [getDiagramURL('mermaid-mindmap', '9.2.0-rc4')], + loadExternalDiagramsAtStartup: true }); export const init = async () => { - await initialize; + await initialize; }; export const render = async ( - config: MermaidConfig, - code: string, - id: string, - callback: Parameters[2] + config: MermaidConfig, + code: string, + id: string, + callback: Parameters[2] ): Promise => { - // Should be able to call this multiple times without any issues. - mermaid.initialize(config); - // console.log('Rendering', code); - // mermaid.mermaidAPI.render(id, code, callback); - await init(); - await mermaid.mermaidAPI.renderAsync(id, code, callback); + // Should be able to call this multiple times without any issues. + mermaid.initialize(config); + // console.log('Rendering', code); + // mermaid.mermaidAPI.render(id, code, callback); + await init(); + await mermaid.mermaidAPI.renderAsync(id, code, callback); }; export const parse = async (code: string): Promise => { - await init(); - return mermaid.parseAsync(code); + await init(); + return mermaid.parseAsync(code); }; diff --git a/src/lib/util/migrations.test.ts b/src/lib/util/migrations.test.ts index b1227960..4b1f79a4 100644 --- a/src/lib/util/migrations.test.ts +++ b/src/lib/util/migrations.test.ts @@ -2,33 +2,33 @@ import type { HistoryEntry } from '$lib/types'; import { describe, it, expect, beforeEach } from 'vitest'; describe('migrations', () => { - beforeEach(() => { - window.localStorage.setItem( - 'manualHistoryStore', - '[{"state":{"code":"graph TD\\n A[Halloween] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":false},"time":0,"type":"manual","name":"hollow-art"},{"state":{"code":"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":true},"time":0,"type":"manual","name":"helpful-ocean"}]' - ); - window.localStorage.setItem( - 'autoHistoryStore', - '[{"state":{"code":"graph TD\\n A[New Year] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":false},"time":0,"type":"auto","name":"barking-dog"},{"state":{"code":"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":true},"time":0,"type":"manual","name":"needy-mosquito"}]' - ); - }); + beforeEach(() => { + window.localStorage.setItem( + 'manualHistoryStore', + '[{"state":{"code":"graph TD\\n A[Halloween] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":false},"time":0,"type":"manual","name":"hollow-art"},{"state":{"code":"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":true},"time":0,"type":"manual","name":"helpful-ocean"}]' + ); + window.localStorage.setItem( + 'autoHistoryStore', + '[{"state":{"code":"graph TD\\n A[New Year] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":false},"time":0,"type":"auto","name":"barking-dog"},{"state":{"code":"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":true},"time":0,"type":"manual","name":"needy-mosquito"}]' + ); + }); - it('should migrate from v0 to v1', async () => { - const { applyMigrations } = await import('./migrations'); - let manualHistoryStore: HistoryEntry[] = JSON.parse( - window.localStorage.getItem('manualHistoryStore') - ); - let autoHistoryStore: HistoryEntry[] = JSON.parse( - window.localStorage.getItem('autoHistoryStore') - ); - expect(manualHistoryStore.every(({ id }) => id !== undefined)).toBe(false); - expect(autoHistoryStore.every(({ id }) => id !== undefined)).toBe(false); + it('should migrate from v0 to v1', async () => { + const { applyMigrations } = await import('./migrations'); + let manualHistoryStore: HistoryEntry[] = JSON.parse( + window.localStorage.getItem('manualHistoryStore') + ); + let autoHistoryStore: HistoryEntry[] = JSON.parse( + window.localStorage.getItem('autoHistoryStore') + ); + expect(manualHistoryStore.every(({ id }) => id !== undefined)).toBe(false); + expect(autoHistoryStore.every(({ id }) => id !== undefined)).toBe(false); - applyMigrations(); + applyMigrations(); - manualHistoryStore = JSON.parse(window.localStorage.getItem('manualHistoryStore')); - autoHistoryStore = JSON.parse(window.localStorage.getItem('autoHistoryStore')); - expect(manualHistoryStore.every(({ id }) => id !== undefined)).toBe(true); - expect(autoHistoryStore.every(({ id }) => id !== undefined)).toBe(true); - }); + manualHistoryStore = JSON.parse(window.localStorage.getItem('manualHistoryStore')); + autoHistoryStore = JSON.parse(window.localStorage.getItem('autoHistoryStore')); + expect(manualHistoryStore.every(({ id }) => id !== undefined)).toBe(true); + expect(autoHistoryStore.every(({ id }) => id !== undefined)).toBe(true); + }); }); diff --git a/src/lib/util/migrations.ts b/src/lib/util/migrations.ts index cd57c732..f739bda6 100644 --- a/src/lib/util/migrations.ts +++ b/src/lib/util/migrations.ts @@ -4,32 +4,32 @@ import { injectHistoryIDs } from '$lib/components/history/history'; import { logEvent } from './stats'; interface MigrationState { - version: number; + version: number; } const migrations: { [key: string]: () => void } = { - injectHistoryIDs + injectHistoryIDs }; const migrationStore: Writable = persist( - writable({ version: -1 }), - localStorage(), - 'migrations' + writable({ version: -1 }), + localStorage(), + 'migrations' ); export const applyMigrations = (): void => { - const { version }: MigrationState = get(migrationStore); - const allMigrations = Object.entries(migrations); - if (version === allMigrations.length - 1) { - return; - } - console.log(`Current migration version: v${version}. Migrating to v${allMigrations.length - 1}.`); - for (let i = version + 1; i < allMigrations.length; i++) { - const [key, fn] = allMigrations[i]; - console.log(`Applying migration ${i}: ${key}.`); - fn(); - logEvent('migration', { key }); - migrationStore.set({ version: i }); - } - logEvent('migration', { status: 'complete', from: version, to: allMigrations.length - 1 }); + const { version }: MigrationState = get(migrationStore); + const allMigrations = Object.entries(migrations); + if (version === allMigrations.length - 1) { + return; + } + console.log(`Current migration version: v${version}. Migrating to v${allMigrations.length - 1}.`); + for (let i = version + 1; i < allMigrations.length; i++) { + const [key, fn] = allMigrations[i]; + console.log(`Applying migration ${i}: ${key}.`); + fn(); + logEvent('migration', { key }); + migrationStore.set({ version: i }); + } + logEvent('migration', { status: 'complete', from: version, to: allMigrations.length - 1 }); }; diff --git a/src/lib/util/notify.ts b/src/lib/util/notify.ts index b9c54a27..3c9e6ded 100644 --- a/src/lib/util/notify.ts +++ b/src/lib/util/notify.ts @@ -1,7 +1,7 @@ export const notify = (message: string): void => { - alert(message); + alert(message); }; export const prompt = (message: string): boolean => { - return confirm(message); + return confirm(message); }; diff --git a/src/lib/util/persist.ts b/src/lib/util/persist.ts index ef275f49..cf032b89 100644 --- a/src/lib/util/persist.ts +++ b/src/lib/util/persist.ts @@ -29,7 +29,7 @@ import type { Writable } from 'svelte/store'; * Disabled warnings about missing/unavailable storages */ export function disableWarnings(): void { - noWarnings = true; + noWarnings = true; } /** * If set to true, no warning will be emitted if the requested Storage is not found. @@ -46,18 +46,18 @@ const alreadyWarnFor: Array = []; * @param {string} storageName */ const warnStorageNotFound = (storageName) => { - const isProduction = typeof process !== 'undefined' && process.env?.NODE_ENV === 'production'; + const isProduction = typeof process !== 'undefined' && process.env?.NODE_ENV === 'production'; - if (!noWarnings && alreadyWarnFor.indexOf(storageName) === -1 && !isProduction) { - let message = `Unable to find the ${storageName}. No data will be persisted.`; - if (typeof window === 'undefined') { - message += - '\n' + - 'Are you running on a server? Most of storages are not available while running on a server.'; - } - console.warn(message); - alreadyWarnFor.push(storageName); - } + if (!noWarnings && alreadyWarnFor.indexOf(storageName) === -1 && !isProduction) { + let message = `Unable to find the ${storageName}. No data will be persisted.`; + if (typeof window === 'undefined') { + message += + '\n' + + 'Are you running on a server? Most of storages are not available while running on a server.'; + } + console.warn(message); + alreadyWarnFor.push(storageName); + } }; const allowedClasses = []; @@ -66,83 +66,83 @@ const allowedClasses = []; * @param classDef The class to add to the list */ export const addSerializableClass = (classDef: () => unknown): void => { - allowedClasses.push(classDef); + allowedClasses.push(classDef); }; const serialize = (value: unknown): string => ESSerializer.serialize(value); const deserialize = (value: string): unknown => { - // @TODO: to remove in the next major - if (value === 'undefined') { - return undefined; - } + // @TODO: to remove in the next major + if (value === 'undefined') { + return undefined; + } - if (value !== null && value !== undefined) { - try { - return ESSerializer.deserialize(value, allowedClasses); - } catch (e) { - // Do nothing - // use the value "as is" - } - try { - return JSON.parse(value); - } catch (e) { - // Do nothing - // use the value "as is" - } - } - return value; + if (value !== null && value !== undefined) { + try { + return ESSerializer.deserialize(value, allowedClasses); + } catch (e) { + // Do nothing + // use the value "as is" + } + try { + return JSON.parse(value); + } catch (e) { + // Do nothing + // use the value "as is" + } + } + return value; }; /** * A store that keep it's value in time. */ export interface PersistentStore extends Writable { - /** - * Delete the store value from the persistent storage - */ - delete(): void; + /** + * Delete the store value from the persistent storage + */ + delete(): void; } /** * Storage interface */ export interface StorageInterface { - /** - * Get a value from the storage. - * - * If the value doesn't exists in the storage, `null` should be returned. - * This method MUST be synchronous. - * @param key The key/name of the value to retrieve - */ - getValue(key: string): T | null; + /** + * Get a value from the storage. + * + * If the value doesn't exists in the storage, `null` should be returned. + * This method MUST be synchronous. + * @param key The key/name of the value to retrieve + */ + getValue(key: string): T | null; - /** - * Save a value in the storage. - * @param key The key/name of the value to save - * @param value The value to save - */ - setValue(key: string, value: T): void; + /** + * Save a value in the storage. + * @param key The key/name of the value to save + * @param value The value to save + */ + setValue(key: string, value: T): void; - /** - * Remove a value from the storage - * @param key The key/name of the value to remove - */ - deleteValue(key: string): void; + /** + * Remove a value from the storage + * @param key The key/name of the value to remove + */ + deleteValue(key: string): void; } export interface SelfUpdateStorageInterface extends StorageInterface { - /** - * Add a listener to the storage values changes - * @param {string} key The key to listen - * @param {(newValue: T) => void} listener The listener callback function - */ - addListener(key: string, listener: (newValue: T) => void): void; - /** - * Remove a listener from the storage values changes - * @param {string} key The key that was listened - * @param {(newValue: T) => void} listener The listener callback function to remove - */ - removeListener(key: string, listener: (newValue: T) => void): void; + /** + * Add a listener to the storage values changes + * @param {string} key The key to listen + * @param {(newValue: T) => void} listener The listener callback function + */ + addListener(key: string, listener: (newValue: T) => void): void; + /** + * Remove a listener from the storage values changes + * @param {string} key The key that was listened + * @param {(newValue: T) => void} listener The listener callback function to remove + */ + removeListener(key: string, listener: (newValue: T) => void): void; } /** @@ -152,87 +152,87 @@ export interface SelfUpdateStorageInterface extends StorageInterface { * @param {string} key The name of the data key */ export function persist( - store: Writable, - storage: StorageInterface, - key: string + store: Writable, + storage: StorageInterface, + key: string ): PersistentStore { - const initialValue = storage.getValue(key); + const initialValue = storage.getValue(key); - if (null !== initialValue) { - store.set(initialValue); - } + if (null !== initialValue) { + store.set(initialValue); + } - if ((storage as SelfUpdateStorageInterface).addListener) { - (storage as SelfUpdateStorageInterface).addListener(key, (newValue) => { - store.set(newValue); - }); - } + if ((storage as SelfUpdateStorageInterface).addListener) { + (storage as SelfUpdateStorageInterface).addListener(key, (newValue) => { + store.set(newValue); + }); + } - store.subscribe((value) => { - storage.setValue(key, value); - }); + store.subscribe((value) => { + storage.setValue(key, value); + }); - return { - ...store, - delete() { - storage.deleteValue(key); - } - }; + return { + ...store, + delete() { + storage.deleteValue(key); + } + }; } function getBrowserStorage( - browserStorage: Storage, - listenExternalChanges = false + browserStorage: Storage, + listenExternalChanges = false ): SelfUpdateStorageInterface { - const listeners: Array<{ key: string; listener: (newValue: any) => void }> = []; - const listenerFunction = (event: StorageEvent) => { - const eventKey = event.key; - if (event.storageArea === browserStorage) { - listeners - .filter(({ key }) => key === eventKey) - .forEach(({ listener }) => { - listener(deserialize(event.newValue)); - }); - } - }; - const connect = () => { - if (listenExternalChanges && typeof window !== 'undefined' && window?.addEventListener) { - window.addEventListener('storage', listenerFunction); - } - }; - const disconnect = () => { - if (listenExternalChanges && typeof window !== 'undefined' && window?.removeEventListener) { - window.removeEventListener('storage', listenerFunction); - } - }; + const listeners: Array<{ key: string; listener: (newValue: any) => void }> = []; + const listenerFunction = (event: StorageEvent) => { + const eventKey = event.key; + if (event.storageArea === browserStorage) { + listeners + .filter(({ key }) => key === eventKey) + .forEach(({ listener }) => { + listener(deserialize(event.newValue)); + }); + } + }; + const connect = () => { + if (listenExternalChanges && typeof window !== 'undefined' && window?.addEventListener) { + window.addEventListener('storage', listenerFunction); + } + }; + const disconnect = () => { + if (listenExternalChanges && typeof window !== 'undefined' && window?.removeEventListener) { + window.removeEventListener('storage', listenerFunction); + } + }; - return { - addListener(key: string, listener: (newValue: any) => void) { - listeners.push({ key, listener }); - if (listeners.length === 1) { - connect(); - } - }, - removeListener(key: string, listener: (newValue: any) => void) { - const index = listeners.indexOf({ key, listener }); - if (index !== -1) { - listeners.splice(index, 1); - } - if (listeners.length === 0) { - disconnect(); - } - }, - getValue(key: string): any | null { - const value = browserStorage.getItem(key); - return deserialize(value); - }, - deleteValue(key: string) { - browserStorage.removeItem(key); - }, - setValue(key: string, value: any) { - browserStorage.setItem(key, serialize(value)); - } - }; + return { + addListener(key: string, listener: (newValue: any) => void) { + listeners.push({ key, listener }); + if (listeners.length === 1) { + connect(); + } + }, + removeListener(key: string, listener: (newValue: any) => void) { + const index = listeners.indexOf({ key, listener }); + if (index !== -1) { + listeners.splice(index, 1); + } + if (listeners.length === 0) { + disconnect(); + } + }, + getValue(key: string): any | null { + const value = browserStorage.getItem(key); + return deserialize(value); + }, + deleteValue(key: string) { + browserStorage.removeItem(key); + }, + setValue(key: string, value: any) { + browserStorage.setItem(key, serialize(value)); + } + }; } /** @@ -240,26 +240,26 @@ function getBrowserStorage( * @param {boolean} listenExternalChanges - Update the store if the localStorage is updated from another page */ export function localStorage(listenExternalChanges = false): StorageInterface { - if (typeof window !== 'undefined' && window?.localStorage) { - return getBrowserStorage(window.localStorage, listenExternalChanges); - } - warnStorageNotFound('window.localStorage'); - return noopStorage(); + if (typeof window !== 'undefined' && window?.localStorage) { + return getBrowserStorage(window.localStorage, listenExternalChanges); + } + warnStorageNotFound('window.localStorage'); + return noopStorage(); } /** * Storage implementation that do nothing */ export function noopStorage(): StorageInterface { - return { - getValue(): null { - return null; - }, - deleteValue() { - // Do nothing - }, - setValue() { - // Do nothing - } - }; + return { + getValue(): null { + return null; + }, + deleteValue() { + // Do nothing + }, + setValue() { + // Do nothing + } + }; } diff --git a/src/lib/util/serde.test.ts b/src/lib/util/serde.test.ts index b219c64c..c66f7f0f 100644 --- a/src/lib/util/serde.test.ts +++ b/src/lib/util/serde.test.ts @@ -4,37 +4,37 @@ import { defaultState } from './state'; import type { State } from '$lib/types'; describe('Serde tests', () => { - const verifySerde = (state: State, serde?: SerdeType): string => { - const serialized = serializeState(state, serde); - const deserialized = deserializeState(serialized); - expect(deserialized).to.deep.equal(state); - return serialized; - }; + const verifySerde = (state: State, serde?: SerdeType): string => { + const serialized = serializeState(state, serde); + const deserialized = deserializeState(serialized); + expect(deserialized).to.deep.equal(state); + return serialized; + }; - it('should serialize and deserialize with default serde', () => { - expect(verifySerde(defaultState)).toMatchInlineSnapshot( - '"pako:eNpVj81qw0AMhF9F6NRC_AI-BGK7zSXQQHLz5iBsObuk-8Naphjb7551fEl1EjPfiNGEjW8Zc7xHChqulXKQ5lCXOppeLPU3yLL9fGQB6x2PMxQfRw-99iEYd__c-GKFoJxOK8Yg2rjHslnlK__jeIaqPlEQH27vzvXPz_BVm7NO5_87OnJKfdcd5R1lDUUoKb4Q3KHlaMm0qfq0KgpFs2WFeVpb7mj4FYXKLQmlQfxldA3mEgfe4RBaEq4MpaftJi5PNtJU8w"' - ); - }); + it('should serialize and deserialize with default serde', () => { + expect(verifySerde(defaultState)).toMatchInlineSnapshot( + '"pako:eNpVj81qw0AMhF9F6NRC_AI-BGK7zSXQQHLz5iBsObuk-8Naphjb7551fEl1EjPfiNGEjW8Zc7xHChqulXKQ5lCXOppeLPU3yLL9fGQB6x2PMxQfRw-99iEYd__c-GKFoJxOK8Yg2rjHslnlK__jeIaqPlEQH27vzvXPz_BVm7NO5_87OnJKfdcd5R1lDUUoKb4Q3KHlaMm0qfq0KgpFs2WFeVpb7mj4FYXKLQmlQfxldA3mEgfe4RBaEq4MpaftJi5PNtJU8w"' + ); + }); - it('should serialize and deserialize with base64 serde', () => { - expect(verifySerde(defaultState, 'base64')).toMatchInlineSnapshot( - '"base64:eyJjb2RlIjoiZ3JhcGggVERcbiAgICBBW0NocmlzdG1hc10gLS0-fEdldCBtb25leXwgQihHbyBzaG9wcGluZylcbiAgICBCIC0tPiBDe0xldCBtZSB0aGlua31cbiAgICBDIC0tPnxPbmV8IERbTGFwdG9wXVxuICAgIEMgLS0-fFR3b3wgRVtpUGhvbmVdXG4gICAgQyAtLT58VGhyZWV8IEZbZmE6ZmEtY2FyIENhcl1cbiAgIiwibWVybWFpZCI6IntcbiAgXCJ0aGVtZVwiOiBcImRlZmF1bHRcIlxufSIsImF1dG9TeW5jIjp0cnVlLCJ1cGRhdGVEaWFncmFtIjp0cnVlfQ"' - ); - }); + it('should serialize and deserialize with base64 serde', () => { + expect(verifySerde(defaultState, 'base64')).toMatchInlineSnapshot( + '"base64:eyJjb2RlIjoiZ3JhcGggVERcbiAgICBBW0NocmlzdG1hc10gLS0-fEdldCBtb25leXwgQihHbyBzaG9wcGluZylcbiAgICBCIC0tPiBDe0xldCBtZSB0aGlua31cbiAgICBDIC0tPnxPbmV8IERbTGFwdG9wXVxuICAgIEMgLS0-fFR3b3wgRVtpUGhvbmVdXG4gICAgQyAtLT58VGhyZWV8IEZbZmE6ZmEtY2FyIENhcl1cbiAgIiwibWVybWFpZCI6IntcbiAgXCJ0aGVtZVwiOiBcImRlZmF1bHRcIlxufSIsImF1dG9TeW5jIjp0cnVlLCJ1cGRhdGVEaWFncmFtIjp0cnVlfQ"' + ); + }); - it('should serialize and deserialize with pako serde', () => { - expect(verifySerde(defaultState, 'pako')).toMatchInlineSnapshot( - '"pako:eNpVj81qw0AMhF9F6NRC_AI-BGK7zSXQQHLz5iBsObuk-8Naphjb7551fEl1EjPfiNGEjW8Zc7xHChqulXKQ5lCXOppeLPU3yLL9fGQB6x2PMxQfRw-99iEYd__c-GKFoJxOK8Yg2rjHslnlK__jeIaqPlEQH27vzvXPz_BVm7NO5_87OnJKfdcd5R1lDUUoKb4Q3KHlaMm0qfq0KgpFs2WFeVpb7mj4FYXKLQmlQfxldA3mEgfe4RBaEq4MpaftJi5PNtJU8w"' - ); - }); + it('should serialize and deserialize with pako serde', () => { + expect(verifySerde(defaultState, 'pako')).toMatchInlineSnapshot( + '"pako:eNpVj81qw0AMhF9F6NRC_AI-BGK7zSXQQHLz5iBsObuk-8Naphjb7551fEl1EjPfiNGEjW8Zc7xHChqulXKQ5lCXOppeLPU3yLL9fGQB6x2PMxQfRw-99iEYd__c-GKFoJxOK8Yg2rjHslnlK__jeIaqPlEQH27vzvXPz_BVm7NO5_87OnJKfdcd5R1lDUUoKb4Q3KHlaMm0qfq0KgpFs2WFeVpb7mj4FYXKLQmlQfxldA3mEgfe4RBaEq4MpaftJi5PNtJU8w"' + ); + }); - it('should throw error for unrecognized serde', () => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - expect(() => serializeState(defaultState, 'unknown')).toThrowError( - 'Unknown serde type: unknown' - ); - expect(() => deserializeState('unknown:hello')).toThrowError('Unknown serde type: unknown'); - }); + it('should throw error for unrecognized serde', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + expect(() => serializeState(defaultState, 'unknown')).toThrowError( + 'Unknown serde type: unknown' + ); + expect(() => deserializeState('unknown:hello')).toThrowError('Unknown serde type: unknown'); + }); }); diff --git a/src/lib/util/serde.ts b/src/lib/util/serde.ts index 643401ff..3d2df939 100644 --- a/src/lib/util/serde.ts +++ b/src/lib/util/serde.ts @@ -3,61 +3,61 @@ import { toUint8Array, fromUint8Array, toBase64, fromBase64 } from 'js-base64'; import type { State } from '$lib/types'; interface Serde { - serialize: (state: string) => string; - deserialize: (state: string) => string; + 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); - } + 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' }); - } + 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' }); + } }; export type SerdeType = 'base64' | 'pako'; const serdes: { [key in SerdeType]: Serde } = { - base64: base64Serde, - pako: pakoSerde + base64: base64Serde, + pako: pakoSerde }; export const serializeState = (state: State, serde: SerdeType = 'pako'): string => { - if (serdes[serde] === undefined) { - throw new Error(`Unknown serde type: ${serde}`); - } - const json = JSON.stringify(state); - const serialized = serdes[serde].serialize(json); - return `${serde}:${serialized}`; + if (serdes[serde] === undefined) { + throw new Error(`Unknown serde type: ${serde}`); + } + const json = JSON.stringify(state); + const serialized = serdes[serde].serialize(json); + return `${serde}:${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 as SerdeType; - } 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; + let type: SerdeType, serialized: string; + if (state.includes(':')) { + let tempType: string; + [tempType, serialized] = state.split(':'); + if (tempType in serdes) { + type = tempType as SerdeType; + } 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; }; diff --git a/src/lib/util/state.ts b/src/lib/util/state.ts index 75728ac9..39337655 100644 --- a/src/lib/util/state.ts +++ b/src/lib/util/state.ts @@ -7,22 +7,22 @@ import { parse } from './mermaid'; import type { MarkerData, State, ValidatedState } from '$lib/types'; export const defaultState: State = { - code: `graph TD + 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 - ), - autoSync: true, - updateDiagram: true + mermaid: JSON.stringify( + { + theme: 'default' + }, + null, + 2 + ), + autoSync: true, + updateDiagram: true }; const urlParseFailedState = `graph TD @@ -40,165 +40,165 @@ const urlParseFailedState = `graph TD export const inputStateStore = persist(writable(defaultState), localStorage(), 'codeStore'); export const currentState: ValidatedState = (() => { - const state = get(inputStateStore); - return { - ...state, - serialized: serializeState(state), - errorMarkers: [], - error: undefined, - editorMode: state.editorMode ?? 'code' - }; + const state = get(inputStateStore); + return { + ...state, + serialized: serializeState(state), + errorMarkers: [], + error: undefined, + editorMode: state.editorMode ?? 'code' + }; })(); let q: AsyncQueue; const processState = async (state: State) => { - const processed: ValidatedState = { - ...state, - serialized: '', - errorMarkers: [], - error: undefined, - editorMode: state.editorMode ?? 'code' - }; - // No changes should be done to fields part of `state`. - try { - processed.serialized = serializeState(state); - await parse(state.code); - JSON.parse(state.mermaid); - } catch (e) { - processed.error = e; - errorDebug(); - console.error(e); - if (e.hash) { - try { - const marker: MarkerData = { - severity: 8, // Error - startLineNumber: e.hash.loc.first_line, - startColumn: e.hash.loc.first_column, - endLineNumber: e.hash.loc.last_line, - endColumn: (e.hash.loc.last_column as number) + 1, - message: e.str - }; - processed.errorMarkers = [marker]; - } catch (err) { - console.error('Error without line helper', err); - } - } - } - return processed; + const processed: ValidatedState = { + ...state, + serialized: '', + errorMarkers: [], + error: undefined, + editorMode: state.editorMode ?? 'code' + }; + // No changes should be done to fields part of `state`. + try { + processed.serialized = serializeState(state); + await parse(state.code); + JSON.parse(state.mermaid); + } catch (e) { + processed.error = e; + errorDebug(); + console.error(e); + if (e.hash) { + try { + const marker: MarkerData = { + severity: 8, // Error + startLineNumber: e.hash.loc.first_line, + startColumn: e.hash.loc.first_column, + endLineNumber: e.hash.loc.last_line, + endColumn: (e.hash.loc.last_column as number) + 1, + message: e.str + }; + processed.errorMarkers = [marker]; + } catch (err) { + console.error('Error without line helper', err); + } + } + } + return processed; }; // All internal reads should be done via stateStore, but it should not be persisted/shared externally. export const stateStore: Readable = derived( - [inputStateStore], - ([state], set) => { - if (!q) { - // Initialize the queue for first time. - q = new AsyncQueue(async (state: State) => { - const newState = await processState(state); - set(newState); - }); - } - q.process(state); - }, - currentState + [inputStateStore], + ([state], set) => { + if (!q) { + // Initialize the queue for first time. + q = new AsyncQueue(async (state: State) => { + const newState = await processState(state); + set(newState); + }); + } + q.process(state); + }, + currentState ); 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(inputStateStore); - if (data) { - console.error('Init error', e); - state.code = urlParseFailedState; - state.mermaid = defaultState.mermaid; - } - } - updateCodeStore(state); + 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(inputStateStore); + if (data) { + console.error('Init error', e); + state.code = urlParseFailedState; + state.mermaid = defaultState.mermaid; + } + } + updateCodeStore(state); }; export const updateCodeStore = (newState: Partial): void => { - inputStateStore.update((state) => { - return { ...state, ...newState }; - }); + inputStateStore.update((state) => { + return { ...state, ...newState }; + }); }; let prompted = false; export const updateCode = ( - code: string, - { - updateDiagram = false, - resetPanZoom = false - }: { updateDiagram?: boolean; resetPanZoom?: boolean } = {} + code: string, + { + updateDiagram = false, + resetPanZoom = false + }: { updateDiagram?: boolean; resetPanZoom?: boolean } = {} ): void => { - console.log('updateCode', code); - const lines = countLines(code); - saveStatistics(code); - errorDebug(); - if (lines > 50 && !prompted && get(stateStore).autoSync) { - const turnOff = confirm( - `Long diagram detected. Turn off Auto Sync? Use ${cmdKey} + Enter or click the sync logo to manually sync.` - ); - prompted = true; - if (turnOff) { - updateCodeStore({ - autoSync: false - }); - } - } + console.log('updateCode', code); + const lines = countLines(code); + saveStatistics(code); + errorDebug(); + if (lines > 50 && !prompted && get(stateStore).autoSync) { + const turnOff = confirm( + `Long diagram detected. Turn off Auto Sync? Use ${cmdKey} + Enter or click the sync logo to manually sync.` + ); + prompted = true; + if (turnOff) { + updateCodeStore({ + autoSync: false + }); + } + } - inputStateStore.update((state) => { - if (resetPanZoom) { - state.pan = undefined; - state.zoom = undefined; - } - return { ...state, code, updateDiagram }; - }); + inputStateStore.update((state) => { + if (resetPanZoom) { + state.pan = undefined; + state.zoom = undefined; + } + return { ...state, code, updateDiagram }; + }); }; export const updateConfig = (config: string): void => { - console.log('updateConfig', config); - inputStateStore.update((state) => { - return { ...state, mermaid: config }; - }); + console.log('updateConfig', config); + inputStateStore.update((state) => { + return { ...state, mermaid: config }; + }); }; export const toggleDarkTheme = (dark: boolean): void => { - inputStateStore.update((state) => { - const config = JSON.parse(state.mermaid); - if (!config.theme || ['dark', 'default'].includes(config.theme)) { - config.theme = dark ? 'dark' : 'default'; - } + inputStateStore.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) }; - }); + return { ...state, mermaid: JSON.stringify(config, null, 2) }; + }); }; let urlDebounce: number; export const initURLSubscription = (): void => { - stateStore.subscribe(({ serialized }) => { - clearTimeout(urlDebounce); - urlDebounce = window.setTimeout(() => { - history.replaceState(undefined, undefined, `#${serialized}`); - }, 250); - }); + stateStore.subscribe(({ serialized }) => { + clearTimeout(urlDebounce); + urlDebounce = window.setTimeout(() => { + history.replaceState(undefined, undefined, `#${serialized}`); + }, 250); + }); }; export const getStateString = (): string => { - return JSON.stringify(get(inputStateStore)); + return JSON.stringify(get(inputStateStore)); }; diff --git a/src/lib/util/stats.test.ts b/src/lib/util/stats.test.ts index 09db84f5..518a55b1 100644 --- a/src/lib/util/stats.test.ts +++ b/src/lib/util/stats.test.ts @@ -1,19 +1,19 @@ import { describe, it, expect } from 'vitest'; import { detectType } from './stats'; describe('diagram detection', () => { - it('should detect diagrams correctly', () => { - expect( - detectType(`%%{{ + it('should detect diagrams correctly', () => { + expect( + detectType(`%%{{ graph`) - ).toBe('graph'); - expect(detectType(`gitGraph`)).toBe('gitGraph'); - expect( - detectType(`%%{{ + ).toBe('graph'); + expect(detectType(`gitGraph`)).toBe('gitGraph'); + expect( + detectType(`%%{{ flowChart graph`) - ).toBe('flowChart'); - expect(detectType(`loki -> thor`)).toBe(undefined); - }); + ).toBe('flowChart'); + expect(detectType(`loki -> thor`)).toBe(undefined); + }); }); diff --git a/src/lib/util/stats.ts b/src/lib/util/stats.ts index 9b4b9209..b86405ce 100644 --- a/src/lib/util/stats.ts +++ b/src/lib/util/stats.ts @@ -3,96 +3,96 @@ import type { AnalyticsInstance } from 'analytics'; export let analytics: AnalyticsInstance; export const initAnalytics = async (): Promise => { - if (browser && !analytics) { - try { - const [{ Analytics }, { default: plausible }] = await Promise.all([ - import('analytics'), - import('analytics-plugin-plausible') - ]); - analytics = Analytics({ - app: 'mermaid-live-editor', - plugins: [ - plausible({ - domain: 'mermaid.live', - hashMode: false, - trackLocalhost: false, // By default 'false' - apiHost: 'https://plausible.io' - }) - ] - }); - } catch (e) { - console.log(e); - console.info('Analytics blocked ;)'); - } - } + if (browser && !analytics) { + try { + const [{ Analytics }, { default: plausible }] = await Promise.all([ + import('analytics'), + import('analytics-plugin-plausible') + ]); + analytics = Analytics({ + app: 'mermaid-live-editor', + plugins: [ + plausible({ + domain: 'mermaid.live', + hashMode: false, + trackLocalhost: false, // By default 'false' + apiHost: 'https://plausible.io' + }) + ] + }); + } catch (e) { + console.log(e); + console.info('Analytics blocked ;)'); + } + } }; export const detectType = (text: string): string => { - const possibleDiagramTypes = [ - 'classDiagram', - 'erDiagram', - 'flowChart', - 'gantt', - 'gitGraph', - 'graph', - 'journey', - 'pie', - 'stateDiagram' - ]; - const firstLine = text - .replace(/^\s*%%.*\n/g, '\n') - .trimStart() - .split(' ')[0] - .toLowerCase(); - const detectedDiagram = possibleDiagramTypes.find((d) => firstLine.includes(d.toLowerCase())); - return detectedDiagram; + const possibleDiagramTypes = [ + 'classDiagram', + 'erDiagram', + 'flowChart', + 'gantt', + 'gitGraph', + 'graph', + 'journey', + 'pie', + 'stateDiagram' + ]; + const firstLine = text + .replace(/^\s*%%.*\n/g, '\n') + .trimStart() + .split(' ')[0] + .toLowerCase(); + const detectedDiagram = possibleDiagramTypes.find((d) => firstLine.includes(d.toLowerCase())); + return detectedDiagram; }; export const countLines = (code: string): number => { - return (code.match(/\n/g) || '').length + 1; + return (code.match(/\n/g) || '').length + 1; }; export const saveStatistics = (graph: string): void => { - const graphType = detectType(graph); - if (!graphType) { - return; - } - const length = countLines(graph); - logEvent('render', { graphType, length }); + const graphType = detectType(graph); + if (!graphType) { + return; + } + const length = countLines(graph); + logEvent('render', { graphType, length }); }; const minutesToMilliSeconds = (minutes: number): number => { - return minutes * 60_000; + return minutes * 60_000; }; const defaultDelay = minutesToMilliSeconds(1); const delaysPerEvent = { - render: minutesToMilliSeconds(5), - panZoom: minutesToMilliSeconds(10), - copyClipboard: defaultDelay, - download: defaultDelay, - copyMarkdown: defaultDelay, - loadGist: defaultDelay, - loadSampleDiagram: defaultDelay, - renderDiagram: defaultDelay, - history: defaultDelay, - migration: defaultDelay, - themeChange: defaultDelay + render: minutesToMilliSeconds(5), + panZoom: minutesToMilliSeconds(10), + copyClipboard: defaultDelay, + download: defaultDelay, + copyMarkdown: defaultDelay, + loadGist: defaultDelay, + loadSampleDiagram: defaultDelay, + renderDiagram: defaultDelay, + history: defaultDelay, + migration: defaultDelay, + themeChange: defaultDelay }; export type AnalyticsEvent = keyof typeof delaysPerEvent; const timeouts: Record = {}; // manual debounce to reduce the number of events sent to analytics export const logEvent = (name: AnalyticsEvent, data?: unknown): void => { - if (!analytics) { - return; - } - const key = data ? JSON.stringify({ name, data }) : name; - if (timeouts[key] === undefined) { - void analytics.track(name, data); - } else { - clearTimeout(timeouts[key]); - } - timeouts[key] = window.setTimeout(() => { - delete timeouts[key]; - }, delaysPerEvent[name]); + if (!analytics) { + return; + } + const key = data ? JSON.stringify({ name, data }) : name; + if (timeouts[key] === undefined) { + void analytics.track(name, data); + } else { + clearTimeout(timeouts[key]); + } + timeouts[key] = window.setTimeout(() => { + delete timeouts[key]; + }, delaysPerEvent[name]); }; diff --git a/src/lib/util/theme.ts b/src/lib/util/theme.ts index 4221ee06..0da7f3ad 100644 --- a/src/lib/util/theme.ts +++ b/src/lib/util/theme.ts @@ -4,35 +4,35 @@ import { persist, localStorage } from '$lib/util/persist'; import { logEvent } from './stats'; export interface ThemeConfig { - isDark: boolean; - theme?: string; + isDark: boolean; + theme?: string; } export const themeStore: Writable = persist( - writable({ - isDark: false - }), - localStorage(), - 'themeStore' + writable({ + isDark: false + }), + localStorage(), + 'themeStore' ); const darkThemes = [ - 'dark', - 'synthwave', - 'halloween', - 'aqua', - 'forest', - 'luxury', - 'black', - 'dracula' + '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 }); - logEvent('themeChange', { theme, isDark }); + if (theme.includes(' ')) { + theme = theme.split(' ')[1].trim(); + } + const isDark = darkThemes.includes(theme); + console.log('Setting theme', theme); + themeStore.set({ theme, isDark }); + logEvent('themeChange', { theme, isDark }); }; diff --git a/src/lib/util/util.ts b/src/lib/util/util.ts index 96f72889..7eeb64c9 100644 --- a/src/lib/util/util.ts +++ b/src/lib/util/util.ts @@ -6,24 +6,24 @@ import { applyMigrations } from './migrations'; import { init } from './mermaid'; export const loadStateFromURL = (): void => { - loadState(window.location.hash.slice(1)); + loadState(window.location.hash.slice(1)); }; export const syncDiagram = (): void => { - updateCodeStore({ - updateDiagram: true - }); + updateCodeStore({ + updateDiagram: true + }); }; export const initHandler = async (): Promise => { - applyMigrations(); - await initLoading('Loading Mermaid...', init()); - loadStateFromURL(); - await initLoading('Loading Gist...', loadDataFromUrl().catch(console.error)); - syncDiagram(); - initURLSubscription(); - await initAnalytics(); - analytics?.page(); + applyMigrations(); + await initLoading('Loading Mermaid...', init()); + loadStateFromURL(); + await initLoading('Loading Gist...', loadDataFromUrl().catch(console.error)); + syncDiagram(); + initURLSubscription(); + await initAnalytics(); + analytics?.page(); }; export const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; @@ -31,31 +31,31 @@ export const cmdKey = isMac ? 'Cmd' : 'Ctrl'; let count = 0; export const errorDebug = (limit = 100) => { - count += 1; - if (count > limit) { - console.log(count, limit); - // eslint-disable-next-line no-debugger - debugger; - } + count += 1; + if (count > limit) { + console.log(count, limit); + // eslint-disable-next-line no-debugger + debugger; + } }; // To queue async tasks so they are executed one after the other, not concurrently. export class AsyncQueue { - private readonly queue: T[] = []; - private running = false; - constructor(private processor: (item: T) => Promise) {} - public async process(item: T): Promise { - this.queue.push(item); - if (this.running) { - return; - } - this.running = true; - while (this.queue.length > 0) { - const item = this.queue.shift(); - if (item) { - await this.processor(item); - } - } - this.running = false; - } + private readonly queue: T[] = []; + private running = false; + constructor(private processor: (item: T) => Promise) {} + public async process(item: T): Promise { + this.queue.push(item); + if (this.running) { + return; + } + this.running = true; + while (this.queue.length > 0) { + const item = this.queue.shift(); + if (item) { + await this.processor(item); + } + } + this.running = false; + } } diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 26aa808c..4c14fe93 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,87 +1,87 @@
- +
{#if $loadingStateStore.loading} -
-
-
-
{$loadingStateStore.message}
-
-
+
+
+
+
{$loadingStateStore.message}
+
+
{/if} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index ca9daa13..4a12a8b8 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,17 +1,17 @@ diff --git a/src/routes/edit/+page.svelte b/src/routes/edit/+page.svelte index 19293e86..e3977afa 100644 --- a/src/routes/edit/+page.svelte +++ b/src/routes/edit/+page.svelte @@ -1,202 +1,202 @@
- -
- diff --git a/src/routes/view/+page.svelte b/src/routes/view/+page.svelte index 216251f0..8de80023 100644 --- a/src/routes/view/+page.svelte +++ b/src/routes/view/+page.svelte @@ -1,8 +1,8 @@ diff --git a/src/tests/setup.ts b/src/tests/setup.ts index f0c016c1..51d4037f 100644 --- a/src/tests/setup.ts +++ b/src/tests/setup.ts @@ -5,7 +5,7 @@ expect.extend(matchers); // TODO: Remove once https://github.com/sveltejs/kit/issues/6259 is closed. beforeAll(() => { - vi.mock('$app/environment', () => ({ - browser: 'window' in globalThis - })); + vi.mock('$app/environment', () => ({ + browser: 'window' in globalThis + })); }); diff --git a/svelte.config.js b/svelte.config.js index c5c508a6..bbefdfde 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -3,24 +3,24 @@ import adapter from '@sveltejs/adapter-static'; /** @type {import('@sveltejs/kit').Config} */ const config = { - // Consult https://github.com/sveltejs/svelte-preprocess - // for more information about preprocessors - preprocess: [ - preprocess({ - postcss: true - }) - ], - kit: { - adapter: adapter({ - pages: `docs` - }), - paths: process.env['DEPLOY'] - ? { - base: `/mermaid-live-editor` - } - : {}, - trailingSlash: 'ignore' - } + // Consult https://github.com/sveltejs/svelte-preprocess + // for more information about preprocessors + preprocess: [ + preprocess({ + postcss: true + }) + ], + kit: { + adapter: adapter({ + pages: `docs` + }), + paths: process.env['DEPLOY'] + ? { + base: `/mermaid-live-editor` + } + : {}, + trailingSlash: 'ignore' + } }; export default config; diff --git a/tailwind.config.cjs b/tailwind.config.cjs index dc72ba6e..18cb32c1 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -1,10 +1,10 @@ module.exports = { - plugins: [require('daisyui')], - content: ['./src/**/*.{html,js,svelte,ts}'], - theme: { - extend: {} - }, - variants: { - extend: {} - } + plugins: [require('daisyui')], + content: ['./src/**/*.{html,js,svelte,ts}'], + theme: { + extend: {} + }, + variants: { + extend: {} + } }; diff --git a/tsconfig.json b/tsconfig.json index 87bfd16b..38b29b7f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,18 @@ { - "include": [ - "src/**/*.d.ts", - "src/**/*.ts", - "src/**/*.svelte", - "cypress/**/*.ts", - "cypress/**/*.js", - "static/**/*.js", - "static/**/*.json" - ], - "compilerOptions": { - "resolveJsonModule": true, - "allowSyntheticDefaultImports": true, - "types": ["vitest/importMeta"] - }, - "extends": "./.svelte-kit/tsconfig.json" + "include": [ + "src/**/*.d.ts", + "src/**/*.ts", + "src/**/*.svelte", + "cypress/**/*.ts", + "cypress/**/*.js", + "cypress/**/*.json", + "static/**/*.js", + "static/**/*.json" + ], + "compilerOptions": { + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "types": ["vitest/importMeta"] + }, + "extends": "./.svelte-kit/tsconfig.json" } diff --git a/vite.config.js b/vite.config.js index 4aee28c2..ecf6163b 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,26 +1,26 @@ import { sveltekit } from '@sveltejs/kit/vite'; /** @type {import('vite').UserConfig} */ const config = { - plugins: [sveltekit()], - envPrefix: 'MERMAID_', - optimizeDeps: { include: ['mermaid'] }, - server: { - port: 3000, - host: true - }, - preview: { - port: 3000, - host: true - }, - test: { - environment: 'jsdom', - // in-source testing - includeSource: ['src/**/*.{js,ts,svelte}'], - setupFiles: ['./src/tests/setup.ts'], - coverage: { - exclude: ['src/mocks', '.svelte-kit', 'src/**/*.test.ts'], - reporter: ['text', 'json', 'html', 'lcov'] - } - } + plugins: [sveltekit()], + envPrefix: 'MERMAID_', + optimizeDeps: { include: ['mermaid'] }, + server: { + port: 3000, + host: true + }, + preview: { + port: 3000, + host: true + }, + test: { + environment: 'jsdom', + // in-source testing + includeSource: ['src/**/*.{js,ts,svelte}'], + setupFiles: ['./src/tests/setup.ts'], + coverage: { + exclude: ['src/mocks', '.svelte-kit', 'src/**/*.test.ts'], + reporter: ['text', 'json', 'html', 'lcov'] + } + } }; export default config;