From 4628a670c842296a43ee7814a6dafc931ced0545 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Thu, 20 Jan 2022 14:19:36 +0530 Subject: [PATCH 1/4] Compressed URL --- cypress/integration/loadSite.spec.ts | 23 +++++++++++++ cypress/snapshots.js | 2 +- package.json | 3 +- src/lib/components/actions.svelte | 51 ++++++---------------------- src/lib/util/state.ts | 34 ++++++++++++++++--- src/routes/edit.svelte | 6 ++-- tsconfig.json | 5 +-- yarn.lock | 13 ++++--- 8 files changed, 81 insertions(+), 56 deletions(-) diff --git a/cypress/integration/loadSite.spec.ts b/cypress/integration/loadSite.spec.ts index c8962b2d1d..6043f8e3c5 100644 --- a/cypress/integration/loadSite.spec.ts +++ b/cypress/integration/loadSite.spec.ts @@ -93,4 +93,27 @@ describe('Site Loads', () => { cy.visit('/#/edit/eyJjb2RlIjoiZ3JhcGggVERcbiAg'); cy.contains('Please Click here to Raise an issue in github.'); }); + + it('should load uncompressed URL', () => { + cy.visit( + '/edit/#eyJjb2RlIjoiZ3JhcGggVERcbiAgICBBW05ldyBZZWFyXSAtLT58R2V0IG1vbmV5fCBCKEdvIHNob3BwaW5nKVxuICAgIEIgLS0-IEN7TGV0IG1lIHRoaW5rfVxuICAgIEMgLS0-fE9uZXwgRFtMYXB0b3BdXG4gICAgQyAtLT58VHdvfCBFW2lQaG9uZV1cbiAgICBDIC0tPnxUaHJlZXwgRltmYTpmYS1jYXIgQ2FyXSIsIm1lcm1haWQiOiJ7XG4gIFwidGhlbWVcIjogXCJkZWZhdWx0XCJcbn0iLCJ1cGRhdGVFZGl0b3IiOmZhbHNlLCJhdXRvU3luYyI6dHJ1ZSwidXBkYXRlRGlhZ3JhbSI6ZmFsc2V9' + ); + cy.contains('New Year'); + cy.visit( + '/edit#eyJjb2RlIjoiY2xhc3NEaWFncmFtXG4gICAgQW5pbWFsIDx8LS0gRHVja1xuICAgIEFuaW1hbCA8fC0tIEZpc2hcbiAgICBBbmltYWwgPHwtLSBaZWJyYVxuICAgIEFuaW1hbCA6ICtpbnQgYWdlXG4gICAgQW5pbWFsIDogK1N0cmluZyBnZW5kZXJcbiAgICBBbmltYWw6ICtpc01hbW1hbCgpXG4gICAgQW5pbWFsOiArbWF0ZSgpXG4gICAgY2xhc3MgRHVja3tcbiAgICAgICtTdHJpbmcgYmVha0NvbG9yXG4gICAgICArc3dpbSgpXG4gICAgICArcXVhY2soKVxuICAgIH1cbiAgICBjbGFzcyBGaXNoe1xuICAgICAgLWludCBzaXplSW5GZWV0XG4gICAgICAtY2FuRWF0KClcbiAgICB9XG4gICAgY2xhc3MgWmVicmF7XG4gICAgICArYm9vbCBpc193aWxkXG4gICAgICArcnVuKClcbiAgICB9XG4gICAgICAgICAgICAiLCJtZXJtYWlkIjoie1xuICBcInRoZW1lXCI6IFwiZGFya1wiXG59IiwidXBkYXRlRWRpdG9yIjpmYWxzZSwiYXV0b1N5bmMiOnRydWUsInVwZGF0ZURpYWdyYW0iOmZhbHNlfQ' + ); + cy.contains('Animal'); + }); + + it('should load compressed URL', () => { + cy.visit( + '/edit#eNpVkM2KwkAQhF-l6dMK5gVyEDRxvYi7sF6WjIcm0zqDzg_jBJEk725Hd2G3Tw31VVFUj23QjCWeEkUD-1p5kFs2O77BN1M6QFEshg1ncMHzfYDV2ybA1YQYrT_NXvhqgqDqtxPGkI315_ElVU__h-cB6mZLMYd4-Kvsb2GAdWM_jcT_V0xicb03RyqPVLSUoJI-OEfHyZHV0rqfDAqzYccKS3k1pbNC5Ufhuqgp81rbHBJKxuXKc6Quh6-7b7HMqeNfqLYkC7gfanwAlW1ZvQ' + ); + cy.contains('New Year'); + cy.visit( + '/edit#eNptkU1PwzAMhv9K5BOI9Q9EXBDbJA477YYqITcxndV8QD40weh_Jy1rGR0-OY_tV2_sEyivCSQogzGuGduAtnaixINji0bcf1WVWGfVXdMtx8M1faYm4B8sxR27JLClJd6nwK4VLTlN4bI4jMQd2pLe3C4KFhNNcLQ92jv9ADGLNoTdozc-zIV4ZDsNlud7RtVN7_5Sb_jYrFcN3iN_0pPbEqUZK3QbTP_Ojyv4NdR4bwTHlyMbPcOQ3WJ2CliBpWCRdbnLqFJDOpClGmRJNYauhtr1pS-_6bKMjebkA8hXNJFWgDn5_YdTIFPINDWdb3vu6r8BaWOZRQ' + ); + cy.reload(); + cy.contains('Animal'); + }); }); diff --git a/cypress/snapshots.js b/cypress/snapshots.js index 1abcb91544..d023001fe4 100644 --- a/cypress/snapshots.js +++ b/cypress/snapshots.js @@ -16,7 +16,7 @@ module.exports = { "1": "{\"code\":\"graph TD\\n A[Party] -->|Get money| B(Go shopping!!)\\n \",\"mermaid\":\"{\\n \\\"theme\\\": \\\"forest\\\",\\n \\\"test\\\": \\\"hello world\\\"\\n}\",\"updateEditor\":false,\"autoSync\":true,\"updateDiagram\":true,\"loader\":{\"type\":\"files\",\"config\":{\"codeURL\":\"https://gist.githubusercontent.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/raw/4eb03887e6a41397e80bdcdbf94017c498f8f1e2/code.mmd\",\"configURL\":\"https://gist.githubusercontent.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/raw/4eb03887e6a41397e80bdcdbf94017c498f8f1e2/config.json\"}}}" } }, - "__version": "9.1.1", + "__version": "9.2.0", "Auto sync tests": { "should dim diagram when code is edited": { "1": "{\"code\":\"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)\\n B --> C{Let me think}\\n C -->|One| D[Laptop]\\n C -->|Two| E[iPhone]\\n C -->|Three| F[fa:fa-car Car]\\n C --> Test\",\"mermaid\":\"{\\n \\\"theme\\\": \\\"default\\\"\\n}\",\"updateEditor\":false,\"autoSync\":false,\"updateDiagram\":false}" diff --git a/package.json b/package.json index 82c74c022e..d8421632b4 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@sveltejs/adapter-static": "1.0.0-next.26", "@sveltejs/kit": "1.0.0-next.232", "@types/mermaid": "^8.2.7", + "@types/pako": "^1.0.3", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", "autoprefixer": "^10.4.0", @@ -57,7 +58,7 @@ "mermaid": "8.13.8", "moment": "^2.29.1", "monaco-editor": "^0.31.1", - "pako": "1.0.10", + "pako": "^2.0.4", "random-word-slugs": "^0.1.6" }, "lint-staged": { diff --git a/src/lib/components/actions.svelte b/src/lib/components/actions.svelte index b1b915eaaf..13d13ec491 100644 --- a/src/lib/components/actions.svelte +++ b/src/lib/components/actions.svelte @@ -2,11 +2,10 @@ import { browser } from '$app/env'; import Card from '$lib/components/card/card.svelte'; - import { rendererUrl, krokiRendererUrl } from '$lib/util/env'; - import { base64State, codeStore } from '$lib/util/state'; - import { toBase64, btoa as jsbtoa } from 'js-base64'; + import { krokiRendererUrl, rendererUrl } from '$lib/util/env'; + import { compressedState, codeStore, compressString } from '$lib/util/state'; + import { toBase64 } from 'js-base64'; import moment from 'moment'; - import pako from 'pako'; type Exporter = (context: CanvasRenderingContext2D, image: HTMLImageElement) => () => void; @@ -109,7 +108,7 @@ }; const onCopyMarkdown = () => { - document.getElementById('markdown').select(); + (document.getElementById('markdown') as HTMLInputElement).select(); document.execCommand('Copy'); }; @@ -128,33 +127,9 @@ window.location.href = `${window.location.pathname}?gist=${gistURL}`; }; - const textEncode = (str) => { - if (window.TextEncoder) { - return new TextEncoder('utf-8').encode(str); - } - let utf8 = unescape(encodeURIComponent(str)); - let result = new Uint8Array(utf8.length); - for (let i = 0; i < utf8.length; i++) { - result[i] = utf8.charCodeAt(i); - } - return result; - }; - - const onKrokiClick = () => { - const krokiCode = getKrokiCode($codeStore.code); - const krokiUrl = `${krokiRendererUrl}/mermaid/svg/${krokiCode}`; - return window.open(krokiUrl, '_blank'); - }; - - const getKrokiCode = (source) => { - const data = textEncode(source); - const compressed = pako.deflate(data, { level: 9, to: 'string' }); - let result = jsbtoa(compressed).replace(/\+/g, '-').replace(/\//g, '_'); - return result; - }; - let iUrl: string; let svgUrl: string; + let krokiUrl: string; let mdCode: string; let imagemodeselected = 'auto'; let userimagesize = 1080; @@ -163,14 +138,10 @@ if (browser && ['mermaid.live', 'netlify'].some((path) => window.location.host.includes(path))) { isNetlify = true; } - base64State.subscribe((encodedState: string) => { - const stateCopy = JSON.parse(JSON.stringify($codeStore)); - if (typeof stateCopy.mermaid === 'string') { - stateCopy.mermaid = JSON.parse(stateCopy.mermaid); - } - const b64Code = toBase64(JSON.stringify(stateCopy), true); - iUrl = `${rendererUrl}/img/${b64Code}`; - svgUrl = `${rendererUrl}/svg/${b64Code}`; + compressedState.subscribe((encodedState: string) => { + iUrl = `${rendererUrl}/img/${encodedState}`; + svgUrl = `${rendererUrl}/svg/${encodedState}`; + krokiUrl = `${krokiRendererUrl}/mermaid/svg/${compressString($codeStore.code)}`; mdCode = `[![](${iUrl})](${window.location.protocol}//${window.location.host}${window.location.pathname}#${encodedState})`; }); @@ -194,8 +165,8 @@ -
diff --git a/src/lib/util/state.ts b/src/lib/util/state.ts index 7d35990826..a124eb2e0f 100644 --- a/src/lib/util/state.ts +++ b/src/lib/util/state.ts @@ -1,9 +1,10 @@ import { writable, get, derived } from 'svelte/store'; import type { Readable } from 'svelte/store'; -import { toBase64, fromBase64 } from 'js-base64'; +import { fromBase64, toUint8Array, fromUint8Array } from 'js-base64'; import { persist, localStorage } from '@macfja/svelte-persistent-store'; import type { State } from '$lib/types'; import { saveStatistics } from './stats'; +import pako from 'pako'; export const defaultState: State = { code: `graph TD @@ -36,15 +37,38 @@ const urlParseFailedState = `graph TD G --> |"No :("| H(Try using the Timeline tab in History
from same browser you used to create the diagram.) click D href "https://github.com/mermaid-js/mermaid-live-editor/issues/new?assignees=&labels=bug&template=bug_report.md&title=Broken%20link" "Raise issue"`; +const textEncode = (str: string) => { + return new TextEncoder().encode(str); +}; + +export const compressString = (source: string): string => { + const data = textEncode(source); + const compressed = pako.deflate(data, { level: 9 }); + const result = fromUint8Array(compressed, true); + return result; +}; + +const decompressString = (source: string): string => { + const data = toUint8Array(source); + const decompressed = pako.inflate(data, { to: 'string' }); + return decompressed; +}; + export const codeStore = persist(writable(defaultState), localStorage(), 'codeStore'); -export const base64State: Readable = derived([codeStore], ([code], set) => { - set(toBase64(JSON.stringify(code), true)); +export const compressedState: Readable = derived([codeStore], ([code], set) => { + set(compressString(JSON.stringify(code))); }); export const loadState = (data: string): void => { let state: State; + console.log('Loading', data); try { - const stateStr = fromBase64(data); + let stateStr: string; + if (data.startsWith('eyJ')) { + stateStr = fromBase64(data); + } else { + stateStr = decompressString(data); + } console.log(`Trying to load state: ${stateStr}`); state = JSON.parse(stateStr); const mermaidConfig = @@ -116,7 +140,7 @@ export const toggleDarkTheme = (dark: boolean): void => { }; export const initURLSubscription = (): void => { - base64State.subscribe((state: string) => { + compressedState.subscribe((state: string) => { history.replaceState(undefined, undefined, `#${state}`); }); }; diff --git a/src/routes/edit.svelte b/src/routes/edit.svelte index 341a75e9aa..5e6e3f0e99 100644 --- a/src/routes/edit.svelte +++ b/src/routes/edit.svelte @@ -6,7 +6,7 @@ import View from '$lib/components/view.svelte'; import Card from '$lib/components/card/card.svelte'; import History from '$lib/components/history/history.svelte'; - import { updateCode, updateConfig, codeStore, base64State } from '$lib/util/state'; + import { updateCode, updateConfig, codeStore, compressedState } from '$lib/util/state'; import { initHandler, syncDiagram } from '$lib/util/util'; import { errorStore } from '$lib/util/error'; import { onMount } from 'svelte'; @@ -15,7 +15,7 @@ import type { EditorUpdateEvent, State, Tab } from '$lib/types'; import { base } from '$app/paths'; - base64State; // Weird fix for error > base64State is not defined. Treeshaking? + compressedState; // Weird fix for error > compressedState is not defined. Treeshaking? let selectedMode = 'code'; const languageMap = { code: 'mermaid', @@ -96,7 +96,7 @@ }; const viewDiagram = () => { - window.open(`${base}/view#${$base64State}`, '_blank').focus(); + window.open(`${base}/view#${$compressedState}`, '_blank').focus(); }; onMount(async () => { diff --git a/tsconfig.json b/tsconfig.json index e82fd2974c..0f97b2a3e9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "moduleResolution": "node", "module": "es2020", - "lib": ["es2020", "ESNext", "DOM.Iterable"], + "lib": ["es2020", "ESNext", "DOM.Iterable", "DOM"], "target": "es2019", /** svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript @@ -33,6 +33,7 @@ "src/**/*.svelte", "cypress/**/*.ts", "cypress/**/*.js", - "static/**/*.js" + "static/**/*.js", + "src/pako.mts" ] } diff --git a/yarn.lock b/yarn.lock index 6bf5fd81d6..5e201dc31f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -250,6 +250,11 @@ resolved "https://registry.npmjs.org/@types/node/-/node-14.17.0.tgz" integrity sha512-w8VZUN/f7SSbvVReb9SWp6cJFevxb4/nkG65yLAya//98WgocKm5PLDAtSs5CtJJJM+kHmJjO/6mmYW4MHShZA== +"@types/pako@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/pako/-/pako-1.0.3.tgz#2e61c2b02020b5f44e2e5e946dfac74f4ec33c58" + integrity sha512-EDxOsHAD5dqjbjEUM1xwa7rpKPFb8ECBE5irONTQU7/OsO3thI5YrNEWSPNMvYmvFM0l/OLQJ6Mgw7PEdXSjhg== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz" @@ -3411,10 +3416,10 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" -pako@1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732" - integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw== +pako@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.4.tgz#6cebc4bbb0b6c73b0d5b8d7e8476e2b2fbea576d" + integrity sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg== parent-module@^1.0.0: version "1.0.1" From b52f2c42205751893f8279d7c4bd623b82dec76e Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Thu, 20 Jan 2022 18:08:53 +0530 Subject: [PATCH 2/4] Serdes --- .vscode/settings.json | 3 ++ cypress/integration/loadSite.spec.ts | 8 +++- src/lib/components/actions.svelte | 7 ++-- src/lib/util/serde.ts | 57 ++++++++++++++++++++++++++++ src/lib/util/state.ts | 35 +++-------------- src/routes/edit.svelte | 6 +-- 6 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/lib/util/serde.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..1295ee63d0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": true +} diff --git a/cypress/integration/loadSite.spec.ts b/cypress/integration/loadSite.spec.ts index 6043f8e3c5..aa939cc4b9 100644 --- a/cypress/integration/loadSite.spec.ts +++ b/cypress/integration/loadSite.spec.ts @@ -103,15 +103,19 @@ describe('Site Loads', () => { '/edit#eyJjb2RlIjoiY2xhc3NEaWFncmFtXG4gICAgQW5pbWFsIDx8LS0gRHVja1xuICAgIEFuaW1hbCA8fC0tIEZpc2hcbiAgICBBbmltYWwgPHwtLSBaZWJyYVxuICAgIEFuaW1hbCA6ICtpbnQgYWdlXG4gICAgQW5pbWFsIDogK1N0cmluZyBnZW5kZXJcbiAgICBBbmltYWw6ICtpc01hbW1hbCgpXG4gICAgQW5pbWFsOiArbWF0ZSgpXG4gICAgY2xhc3MgRHVja3tcbiAgICAgICtTdHJpbmcgYmVha0NvbG9yXG4gICAgICArc3dpbSgpXG4gICAgICArcXVhY2soKVxuICAgIH1cbiAgICBjbGFzcyBGaXNoe1xuICAgICAgLWludCBzaXplSW5GZWV0XG4gICAgICAtY2FuRWF0KClcbiAgICB9XG4gICAgY2xhc3MgWmVicmF7XG4gICAgICArYm9vbCBpc193aWxkXG4gICAgICArcnVuKClcbiAgICB9XG4gICAgICAgICAgICAiLCJtZXJtYWlkIjoie1xuICBcInRoZW1lXCI6IFwiZGFya1wiXG59IiwidXBkYXRlRWRpdG9yIjpmYWxzZSwiYXV0b1N5bmMiOnRydWUsInVwZGF0ZURpYWdyYW0iOmZhbHNlfQ' ); cy.contains('Animal'); + cy.visit( + '/edit/#base64:eyJjb2RlIjoiZ3JhcGggVERcbiAgICBBW05ldyBZZWFyXSAtLT58R2V0IG1vbmV5fCBCKEdvIHNob3BwaW5nKVxuICAgIEIgLS0-IEN7TGV0IG1lIHRoaW5rfVxuICAgIEMgLS0-fE9uZXwgRFtMYXB0b3BdXG4gICAgQyAtLT58VHdvfCBFW2lQaG9uZV1cbiAgICBDIC0tPnxUaHJlZXwgRltmYTpmYS1jYXIgQ2FyXSIsIm1lcm1haWQiOiJ7XG4gIFwidGhlbWVcIjogXCJkZWZhdWx0XCJcbn0iLCJ1cGRhdGVFZGl0b3IiOmZhbHNlLCJhdXRvU3luYyI6dHJ1ZSwidXBkYXRlRGlhZ3JhbSI6ZmFsc2V9' + ); + cy.contains('New Year'); }); it('should load compressed URL', () => { cy.visit( - '/edit#eNpVkM2KwkAQhF-l6dMK5gVyEDRxvYi7sF6WjIcm0zqDzg_jBJEk725Hd2G3Tw31VVFUj23QjCWeEkUD-1p5kFs2O77BN1M6QFEshg1ncMHzfYDV2ybA1YQYrT_NXvhqgqDqtxPGkI315_ElVU__h-cB6mZLMYd4-Kvsb2GAdWM_jcT_V0xicb03RyqPVLSUoJI-OEfHyZHV0rqfDAqzYccKS3k1pbNC5Ufhuqgp81rbHBJKxuXKc6Quh6-7b7HMqeNfqLYkC7gfanwAlW1ZvQ' + '/edit#pako:eNpVkM2KwkAQhF-l6dMK5gVyEDRxvYi7sF6WjIcm0zqDzg_jBJEk725Hd2G3Tw31VVFUj23QjCWeEkUD-1p5kFs2O77BN1M6QFEshg1ncMHzfYDV2ybA1YQYrT_NXvhqgqDqtxPGkI315_ElVU__h-cB6mZLMYd4-Kvsb2GAdWM_jcT_V0xicb03RyqPVLSUoJI-OEfHyZHV0rqfDAqzYccKS3k1pbNC5Ufhuqgp81rbHBJKxuXKc6Quh6-7b7HMqeNfqLYkC7gfanwAlW1ZvQ' ); cy.contains('New Year'); cy.visit( - '/edit#eNptkU1PwzAMhv9K5BOI9Q9EXBDbJA477YYqITcxndV8QD40weh_Jy1rGR0-OY_tV2_sEyivCSQogzGuGduAtnaixINji0bcf1WVWGfVXdMtx8M1faYm4B8sxR27JLClJd6nwK4VLTlN4bI4jMQd2pLe3C4KFhNNcLQ92jv9ADGLNoTdozc-zIV4ZDsNlud7RtVN7_5Sb_jYrFcN3iN_0pPbEqUZK3QbTP_Ojyv4NdR4bwTHlyMbPcOQ3WJ2CliBpWCRdbnLqFJDOpClGmRJNYauhtr1pS-_6bKMjebkA8hXNJFWgDn5_YdTIFPINDWdb3vu6r8BaWOZRQ' + '/edit#pako:eNptkU1PwzAMhv9K5BOI9Q9EXBDbJA477YYqITcxndV8QD40weh_Jy1rGR0-OY_tV2_sEyivCSQogzGuGduAtnaixINji0bcf1WVWGfVXdMtx8M1faYm4B8sxR27JLClJd6nwK4VLTlN4bI4jMQd2pLe3C4KFhNNcLQ92jv9ADGLNoTdozc-zIV4ZDsNlud7RtVN7_5Sb_jYrFcN3iN_0pPbEqUZK3QbTP_Ojyv4NdR4bwTHlyMbPcOQ3WJ2CliBpWCRdbnLqFJDOpClGmRJNYauhtr1pS-_6bKMjebkA8hXNJFWgDn5_YdTIFPINDWdb3vu6r8BaWOZRQ' ); cy.reload(); cy.contains('Animal'); diff --git a/src/lib/components/actions.svelte b/src/lib/components/actions.svelte index 13d13ec491..4b527d855e 100644 --- a/src/lib/components/actions.svelte +++ b/src/lib/components/actions.svelte @@ -3,7 +3,8 @@ import Card from '$lib/components/card/card.svelte'; import { krokiRendererUrl, rendererUrl } from '$lib/util/env'; - import { compressedState, codeStore, compressString } from '$lib/util/state'; + import { pakoSerde } from '$lib/util/serde'; + import { serializedState, codeStore } from '$lib/util/state'; import { toBase64 } from 'js-base64'; import moment from 'moment'; @@ -138,10 +139,10 @@ if (browser && ['mermaid.live', 'netlify'].some((path) => window.location.host.includes(path))) { isNetlify = true; } - compressedState.subscribe((encodedState: string) => { + serializedState.subscribe((encodedState: string) => { iUrl = `${rendererUrl}/img/${encodedState}`; svgUrl = `${rendererUrl}/svg/${encodedState}`; - krokiUrl = `${krokiRendererUrl}/mermaid/svg/${compressString($codeStore.code)}`; + krokiUrl = `${krokiRendererUrl}/mermaid/svg/${pakoSerde.serialize($codeStore.code)}`; mdCode = `[![](${iUrl})](${window.location.protocol}//${window.location.host}${window.location.pathname}#${encodedState})`; }); diff --git a/src/lib/util/serde.ts b/src/lib/util/serde.ts new file mode 100644 index 0000000000..d70ea3c1a4 --- /dev/null +++ b/src/lib/util/serde.ts @@ -0,0 +1,57 @@ +import { deflate, inflate } from 'pako'; +import { toUint8Array, fromUint8Array, toBase64, fromBase64 } from 'js-base64'; +import type { State } from '$lib/types'; + +interface Serde { + serialize: (state: string) => string; + deserialize: (state: string) => string; +} + +enum SerdeType { + Base64 = 'base64', + Pako = 'pako' +} + +const base64Serde: Serde = { + serialize: (state: string): string => { + return toBase64(state, true); + }, + deserialize: (state: string): string => { + return fromBase64(state); + } +}; + +export const pakoSerde: Serde = { + serialize: (state: string): string => { + const data = new TextEncoder().encode(state); + const compressed = deflate(data, { level: 9 }); + return fromUint8Array(compressed, true); + }, + deserialize: (state: string): string => { + const data = toUint8Array(state); + return inflate(data, { to: 'string' }); + } +}; +const serdes: Record = { + [SerdeType.Base64]: base64Serde, + [SerdeType.Pako]: pakoSerde +}; + +export const serializeState = (state: State): string => { + const json = JSON.stringify(state); + const defaultSerde = SerdeType.Pako; + const serialized = serdes[defaultSerde].serialize(json); + return `${defaultSerde}:${serialized}`; +}; + +export const deserializeState = (state: string): State => { + let type: string, serialized: string; + if (state.includes(':')) { + [type, serialized] = state.split(':'); + } else { + type = SerdeType.Base64; + serialized = state; + } + const json = serdes[type].deserialize(serialized); + return JSON.parse(json); +}; diff --git a/src/lib/util/state.ts b/src/lib/util/state.ts index a124eb2e0f..fb1900492d 100644 --- a/src/lib/util/state.ts +++ b/src/lib/util/state.ts @@ -1,10 +1,9 @@ import { writable, get, derived } from 'svelte/store'; import type { Readable } from 'svelte/store'; -import { fromBase64, toUint8Array, fromUint8Array } from 'js-base64'; import { persist, localStorage } from '@macfja/svelte-persistent-store'; import type { State } from '$lib/types'; import { saveStatistics } from './stats'; -import pako from 'pako'; +import { serializeState, deserializeState } from './serde'; export const defaultState: State = { code: `graph TD @@ -37,40 +36,16 @@ const urlParseFailedState = `graph TD G --> |"No :("| H(Try using the Timeline tab in History
from same browser you used to create the diagram.) click D href "https://github.com/mermaid-js/mermaid-live-editor/issues/new?assignees=&labels=bug&template=bug_report.md&title=Broken%20link" "Raise issue"`; -const textEncode = (str: string) => { - return new TextEncoder().encode(str); -}; - -export const compressString = (source: string): string => { - const data = textEncode(source); - const compressed = pako.deflate(data, { level: 9 }); - const result = fromUint8Array(compressed, true); - return result; -}; - -const decompressString = (source: string): string => { - const data = toUint8Array(source); - const decompressed = pako.inflate(data, { to: 'string' }); - return decompressed; -}; - export const codeStore = persist(writable(defaultState), localStorage(), 'codeStore'); -export const compressedState: Readable = derived([codeStore], ([code], set) => { - set(compressString(JSON.stringify(code))); +export const serializedState: Readable = derived([codeStore], ([code], set) => { + set(serializeState(code)); }); export const loadState = (data: string): void => { let state: State; console.log('Loading', data); try { - let stateStr: string; - if (data.startsWith('eyJ')) { - stateStr = fromBase64(data); - } else { - stateStr = decompressString(data); - } - console.log(`Trying to load state: ${stateStr}`); - state = JSON.parse(stateStr); + state = deserializeState(data); const mermaidConfig = typeof state.mermaid === 'string' ? JSON.parse(state.mermaid) : state.mermaid; if ( @@ -140,7 +115,7 @@ export const toggleDarkTheme = (dark: boolean): void => { }; export const initURLSubscription = (): void => { - compressedState.subscribe((state: string) => { + serializedState.subscribe((state: string) => { history.replaceState(undefined, undefined, `#${state}`); }); }; diff --git a/src/routes/edit.svelte b/src/routes/edit.svelte index 5e6e3f0e99..aa21f82ae6 100644 --- a/src/routes/edit.svelte +++ b/src/routes/edit.svelte @@ -6,7 +6,7 @@ import View from '$lib/components/view.svelte'; import Card from '$lib/components/card/card.svelte'; import History from '$lib/components/history/history.svelte'; - import { updateCode, updateConfig, codeStore, compressedState } from '$lib/util/state'; + import { updateCode, updateConfig, codeStore, serializedState } from '$lib/util/state'; import { initHandler, syncDiagram } from '$lib/util/util'; import { errorStore } from '$lib/util/error'; import { onMount } from 'svelte'; @@ -15,7 +15,7 @@ import type { EditorUpdateEvent, State, Tab } from '$lib/types'; import { base } from '$app/paths'; - compressedState; // Weird fix for error > compressedState is not defined. Treeshaking? + serializedState; // Weird fix for error > serializedState is not defined. Treeshaking? let selectedMode = 'code'; const languageMap = { code: 'mermaid', @@ -96,7 +96,7 @@ }; const viewDiagram = () => { - window.open(`${base}/view#${$compressedState}`, '_blank').focus(); + window.open(`${base}/view#${$serializedState}`, '_blank').focus(); }; onMount(async () => { From aee44604585500241a6def9c65280f183aa3d37d Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Thu, 20 Jan 2022 18:46:00 +0530 Subject: [PATCH 3/4] Fix types --- src/lib/util/serde.ts | 14 +++++++------- src/lib/util/state.ts | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/util/serde.ts b/src/lib/util/serde.ts index d70ea3c1a4..7f56ab2508 100644 --- a/src/lib/util/serde.ts +++ b/src/lib/util/serde.ts @@ -32,15 +32,15 @@ export const pakoSerde: Serde = { return inflate(data, { to: 'string' }); } }; -const serdes: Record = { - [SerdeType.Base64]: base64Serde, - [SerdeType.Pako]: pakoSerde -}; +const serdes: Map = new Map([ + [SerdeType.Base64, base64Serde], + [SerdeType.Pako, pakoSerde] +]); export const serializeState = (state: State): string => { const json = JSON.stringify(state); const defaultSerde = SerdeType.Pako; - const serialized = serdes[defaultSerde].serialize(json); + const serialized = serdes.get(defaultSerde).serialize(json); return `${defaultSerde}:${serialized}`; }; @@ -52,6 +52,6 @@ export const deserializeState = (state: string): State => { type = SerdeType.Base64; serialized = state; } - const json = serdes[type].deserialize(serialized); - return JSON.parse(json); + const json = serdes.get(type).deserialize(serialized); + return JSON.parse(json) as State; }; diff --git a/src/lib/util/state.ts b/src/lib/util/state.ts index fb1900492d..73157a543c 100644 --- a/src/lib/util/state.ts +++ b/src/lib/util/state.ts @@ -46,7 +46,7 @@ export const loadState = (data: string): void => { console.log('Loading', data); try { state = deserializeState(data); - const mermaidConfig = + const mermaidConfig: { [key: string]: string } = typeof state.mermaid === 'string' ? JSON.parse(state.mermaid) : state.mermaid; if ( mermaidConfig.securityLevel && From 9fb7c4a17618fb80cadbea53ae37d59cd77ca1c0 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 21 Jan 2022 00:41:24 +0530 Subject: [PATCH 4/4] fix: Types --- .vscode/settings.json | 3 ++- src/lib/components/actions.svelte | 4 ++-- src/lib/util/env.ts | 6 ++++-- src/lib/util/serde.ts | 34 +++++++++++++++++-------------- src/routes/edit.svelte | 2 +- 5 files changed, 28 insertions(+), 21 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1295ee63d0..cd3023f663 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "editor.formatOnSave": true + "editor.formatOnSave": true, + "cSpell.words": ["pako", "Serde", "serdes"] } diff --git a/src/lib/components/actions.svelte b/src/lib/components/actions.svelte index 4b527d855e..b3c411fa73 100644 --- a/src/lib/components/actions.svelte +++ b/src/lib/components/actions.svelte @@ -18,7 +18,7 @@ } const svgString = svg.outerHTML .replaceAll('
', '
') - .replaceAll(/]*)>/g, (m, g) => ``); + .replaceAll(/]*)>/g, (m, g: string) => ``); return toBase64(svgString); }; @@ -79,7 +79,7 @@ canvas.toBlob((blob) => { try { // @ts-ignore: https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1004/files - navigator.clipboard.write([ + void navigator.clipboard.write([ /* eslint-disable no-undef */ // @ts-ignore: https://github.com/microsoft/TypeScript/issues/43821 new ClipboardItem({ diff --git a/src/lib/util/env.ts b/src/lib/util/env.ts index f5e22f3086..128761f2a6 100644 --- a/src/lib/util/env.ts +++ b/src/lib/util/env.ts @@ -1,2 +1,4 @@ -export const rendererUrl = import.meta.env.MERMAID_RENDERER_URL ?? 'https://mermaid.ink'; -export const krokiRendererUrl = import.meta.env.MERMAID_KROKI_RENDERER_URL ?? 'https://kroki.io'; +export const rendererUrl: string = + (import.meta.env.MERMAID_RENDERER_URL as string) ?? 'https://mermaid.ink'; +export const krokiRendererUrl: string = + (import.meta.env.MERMAID_KROKI_RENDERER_URL as string) ?? 'https://kroki.io'; diff --git a/src/lib/util/serde.ts b/src/lib/util/serde.ts index 7f56ab2508..1f7540fc8e 100644 --- a/src/lib/util/serde.ts +++ b/src/lib/util/serde.ts @@ -7,11 +7,6 @@ interface Serde { deserialize: (state: string) => string; } -enum SerdeType { - Base64 = 'base64', - Pako = 'pako' -} - const base64Serde: Serde = { serialize: (state: string): string => { return toBase64(state, true); @@ -32,26 +27,35 @@ export const pakoSerde: Serde = { return inflate(data, { to: 'string' }); } }; -const serdes: Map = new Map([ - [SerdeType.Base64, base64Serde], - [SerdeType.Pako, pakoSerde] -]); + +const serdes: { [key: string]: Serde } = { + base64: base64Serde, + pako: pakoSerde +}; + +type SerdeType = keyof typeof serdes; export const serializeState = (state: State): string => { const json = JSON.stringify(state); - const defaultSerde = SerdeType.Pako; - const serialized = serdes.get(defaultSerde).serialize(json); + const defaultSerde: SerdeType = 'pako'; + const serialized = serdes[defaultSerde].serialize(json); return `${defaultSerde}:${serialized}`; }; export const deserializeState = (state: string): State => { - let type: string, serialized: string; + let type: SerdeType, serialized: string; if (state.includes(':')) { - [type, serialized] = state.split(':'); + let tempType: string; + [tempType, serialized] = state.split(':'); + if (tempType in serdes) { + type = tempType; + } else { + throw new Error(`Unknown serde type: ${tempType}`); + } } else { - type = SerdeType.Base64; + type = 'base64'; serialized = state; } - const json = serdes.get(type).deserialize(serialized); + const json = serdes[type].deserialize(serialized); return JSON.parse(json) as State; }; diff --git a/src/routes/edit.svelte b/src/routes/edit.svelte index aa21f82ae6..afdd0f9a03 100644 --- a/src/routes/edit.svelte +++ b/src/routes/edit.svelte @@ -82,7 +82,7 @@ startLineNumber: e.hash.loc.first_line, startColumn: e.hash.loc.first_column, endLineNumber: e.hash.loc.last_line, - endColumn: e.hash.loc.last_column + 1, + endColumn: (e.hash.loc.last_column as number) + 1, message: e.str }; errorMarkers.push(marker);