diff --git a/cypress/integration/loadSite.spec.ts b/cypress/integration/loadSite.spec.ts index c8962b2d..6043f8e3 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 1abcb915..d023001f 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 82c74c02..d8421632 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 b1b915ea..13d13ec4 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 7d359908..a124eb2e 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 341a75e9..5e6e3f0e 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 e82fd297..0f97b2a3 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 6bf5fd81..5e201dc3 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"