chore: Cleanup potentially
problematic code.
This commit is contained in:
+5
-1
@@ -5,6 +5,7 @@ module.exports = {
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
'plugin:@typescript-eslint/strict',
|
||||
'prettier'
|
||||
],
|
||||
plugins: ['svelte3', 'tailwindcss', '@typescript-eslint', 'es', 'vitest'],
|
||||
@@ -19,7 +20,10 @@ module.exports = {
|
||||
'package.json',
|
||||
'tsconfig.json'
|
||||
],
|
||||
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
||||
overrides: [
|
||||
{ files: ['*.svelte'], processor: 'svelte3/svelte3' }
|
||||
// { files: ['*.ts'], extends: ['plugin:@typescript-eslint/recommended-requiring-type-checking'] }
|
||||
],
|
||||
settings: {
|
||||
'svelte3/typescript': () => require('typescript')
|
||||
},
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"cSpell.words": ["asyncable", "mindmap", "pako", "Serde", "serdes"],
|
||||
"cSpell.words": ["asyncable", "mindmap", "pako", "Serde", "serdes", "tailwindcss"],
|
||||
"vitest.commandLine": "yarn test:unit",
|
||||
"vitest.enable": true,
|
||||
"testing.autoRun.mode": "rerun",
|
||||
|
||||
Vendored
+2
-1
@@ -9,7 +9,8 @@ declare global {
|
||||
namespace jest {
|
||||
interface Matchers<R = void>
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
// eslint-disable-next-line no-undef
|
||||
extends TestingLibraryMatchers<typeof expect.stringContaining, R> {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
`mermaid-diagram-${moment().format('YYYY-MM-DD-HHmmss')}.${ext}`;
|
||||
|
||||
const getBase64SVG = (svg?: HTMLElement, width?: number, height?: number): string => {
|
||||
svg?.setAttribute('height', `${height}px`);
|
||||
svg?.setAttribute('width', `${width}px`); // Workaround https://stackoverflow.com/questions/28690643/firefox-error-rendering-an-svg-image-to-html5-canvas-with-drawimage
|
||||
height && svg?.setAttribute('height', `${height}px`);
|
||||
width && svg?.setAttribute('width', `${width}px`); // Workaround https://stackoverflow.com/questions/28690643/firefox-error-rendering-an-svg-image-to-html5-canvas-with-drawimage
|
||||
if (!svg) {
|
||||
svg = getSvgEl();
|
||||
}
|
||||
@@ -28,7 +28,10 @@
|
||||
|
||||
const exportImage = (event: Event, exporter: Exporter) => {
|
||||
const canvas: HTMLCanvasElement = document.createElement('canvas');
|
||||
const svg: HTMLElement = document.querySelector('#container svg');
|
||||
const svg: HTMLElement | null = document.querySelector('#container svg');
|
||||
if (!svg) {
|
||||
throw new Error('svg not found');
|
||||
}
|
||||
const box: DOMRect = svg.getBoundingClientRect();
|
||||
canvas.width = box.width;
|
||||
canvas.height = box.height;
|
||||
@@ -43,6 +46,9 @@
|
||||
}
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
if (!context) {
|
||||
throw new Error('context not found');
|
||||
}
|
||||
context.fillStyle = 'white';
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
@@ -56,12 +62,12 @@
|
||||
|
||||
const getSvgEl = () => {
|
||||
const svgEl: HTMLElement = document
|
||||
.querySelector('#container svg')
|
||||
.querySelector('#container svg')!
|
||||
.cloneNode(true) as HTMLElement;
|
||||
svgEl.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
|
||||
const fontAwesomeCdnUrl = Array.from(document.head.getElementsByTagName('link'))
|
||||
.map((l) => l.href)
|
||||
.find((h) => h && h.includes('font-awesome'));
|
||||
.find((h) => h.includes('font-awesome'));
|
||||
if (fontAwesomeCdnUrl == null) {
|
||||
return svgEl;
|
||||
}
|
||||
@@ -99,10 +105,10 @@
|
||||
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||
canvas.toBlob((blob) => {
|
||||
try {
|
||||
// @ts-ignore: https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1004/files
|
||||
if (!blob) {
|
||||
throw new Error('blob is empty');
|
||||
}
|
||||
void navigator.clipboard.write([
|
||||
/* eslint-disable no-undef */
|
||||
// @ts-ignore: https://github.com/microsoft/TypeScript/issues/43821
|
||||
new ClipboardItem({
|
||||
[blob.type]: blob
|
||||
})
|
||||
@@ -142,7 +148,7 @@
|
||||
let gistURL = '';
|
||||
stateStore.subscribe(({ loader }) => {
|
||||
if (loader?.type === 'gist') {
|
||||
// @ts-ignore Gist will have url
|
||||
// @ts-expect-error Gist will have url
|
||||
gistURL = loader.config.url;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
import initEditor from 'monaco-mermaid';
|
||||
import { logEvent } from '$lib/util/stats';
|
||||
|
||||
let divEl: HTMLDivElement = null;
|
||||
let editor: monaco.editor.IStandaloneCodeEditor;
|
||||
let Monaco: typeof monaco;
|
||||
let divEl: HTMLDivElement | undefined = undefined;
|
||||
let editor: monaco.editor.IStandaloneCodeEditor | undefined;
|
||||
let Monaco: typeof monaco | undefined;
|
||||
let editorOptions: monaco.editor.IStandaloneEditorConstructionOptions = {
|
||||
minimap: {
|
||||
enabled: false
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
stateStore.subscribe(({ errorMarkers, editorMode, code, mermaid }) => {
|
||||
console.log('editor store subscription', { code, mermaid });
|
||||
if (!editor) {
|
||||
if (!editor || !Monaco) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -36,12 +36,17 @@
|
||||
|
||||
// Update editor mode if it's different
|
||||
const language = editorMode === 'code' ? 'mermaid' : 'json';
|
||||
if (editor.getModel().getLanguageId() !== language) {
|
||||
Monaco?.editor.setModelLanguage(editor.getModel(), language);
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
console.error("editor model doesn't exist");
|
||||
return;
|
||||
}
|
||||
if (model.getLanguageId() !== language) {
|
||||
Monaco.editor.setModelLanguage(model, language);
|
||||
}
|
||||
|
||||
// Display/clear errors
|
||||
Monaco?.editor.setModelMarkers(editor.getModel(), 'mermaid', errorMarkers);
|
||||
Monaco.editor.setModelMarkers(model, 'mermaid', errorMarkers);
|
||||
});
|
||||
|
||||
themeStore.subscribe(({ isDark }) => {
|
||||
@@ -62,7 +67,7 @@
|
||||
// errorDebug();
|
||||
let i = 0;
|
||||
while (i++ < 500) {
|
||||
// @ts-ignore : This is a hack to handle a svelte-kit error when importing monaco.
|
||||
// @ts-expect-error : This is a hack to handle a svelte-kit error when importing monaco.
|
||||
Monaco = window.monaco;
|
||||
if (Monaco !== undefined) {
|
||||
return;
|
||||
@@ -74,14 +79,20 @@
|
||||
|
||||
onMount(async () => {
|
||||
await loadMonaco(); // Fix https://github.com/mermaid-js/mermaid-live-editor/issues/175
|
||||
if (!Monaco) {
|
||||
throw new Error('Monaco failed to load');
|
||||
}
|
||||
if (!divEl) {
|
||||
throw new Error('divEl is undefined');
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
initEditor(Monaco);
|
||||
errorDebug(100);
|
||||
editor = Monaco.editor.create(divEl, editorOptions);
|
||||
editor.onDidChangeModelContent(({ isFlush, changes }) => {
|
||||
const newText = editor.getValue();
|
||||
const newText = editor?.getValue();
|
||||
console.log('editor onDidChangeModelContent', { text, newText, isFlush, changes });
|
||||
if (text === newText || isFlush) {
|
||||
if (!newText || text === newText || isFlush) {
|
||||
return;
|
||||
}
|
||||
text = newText;
|
||||
@@ -98,19 +109,21 @@
|
||||
});
|
||||
}
|
||||
});
|
||||
Monaco?.editor.setTheme($themeStore.isDark ? 'mermaid-dark' : 'mermaid');
|
||||
Monaco.editor.setTheme($themeStore.isDark ? 'mermaid-dark' : 'mermaid');
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
editor.layout({
|
||||
editor!.layout({
|
||||
height: entries[0].contentRect.height,
|
||||
width: entries[0].contentRect.width
|
||||
});
|
||||
});
|
||||
|
||||
resizeObserver.observe(divEl.parentElement);
|
||||
if (divEl.parentElement) {
|
||||
resizeObserver.observe(divEl.parentElement);
|
||||
}
|
||||
console.log(`editor mounted`);
|
||||
return () => {
|
||||
console.log(`editor disposed`);
|
||||
editor.dispose();
|
||||
editor?.dispose();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -114,8 +114,8 @@
|
||||
};
|
||||
|
||||
// Adding in this array will add an icon to the preset menu
|
||||
const newDiagrams: Array<SampleTypes> = ['Mindmap'];
|
||||
const diagramOrder: Array<SampleTypes> = [
|
||||
const newDiagrams: SampleTypes[] = ['Mindmap'];
|
||||
const diagramOrder: SampleTypes[] = [
|
||||
'Sequence',
|
||||
'Flow',
|
||||
'Class',
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
'🧛♂️ dracula'
|
||||
];
|
||||
|
||||
function checkTheme(theme: string) {
|
||||
function checkTheme(theme: string): boolean {
|
||||
return theme.includes($themeStore.theme);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -17,9 +17,12 @@
|
||||
let hide = false;
|
||||
let manualUpdate = true;
|
||||
let panZoomEnabled = $stateStore.panZoom;
|
||||
let pzoom: typeof panzoom;
|
||||
let pzoom: typeof panzoom | undefined;
|
||||
|
||||
const handlePanZoomChange = () => {
|
||||
if (!pzoom) {
|
||||
return;
|
||||
}
|
||||
const pan = pzoom.getPan();
|
||||
const zoom = pzoom.getZoom();
|
||||
updateCodeStore({ pan, zoom });
|
||||
@@ -35,6 +38,9 @@
|
||||
pzoom = undefined;
|
||||
void Promise.resolve().then(() => {
|
||||
const graphDiv = document.getElementById('graph-div');
|
||||
if (!graphDiv) {
|
||||
return;
|
||||
}
|
||||
pzoom = panzoom(graphDiv, {
|
||||
onPan: handlePanZoomChange,
|
||||
onZoom: handlePanZoomChange,
|
||||
@@ -71,7 +77,7 @@
|
||||
code = state.code;
|
||||
config = state.mermaid;
|
||||
panZoomEnabled = state.panZoom;
|
||||
const scroll = view.parentElement.scrollTop;
|
||||
const scroll = view.parentElement!.scrollTop;
|
||||
delete container.dataset.processed;
|
||||
await renderDiagram(
|
||||
Object.assign({}, JSON.parse(state.mermaid)) as MermaidConfig,
|
||||
@@ -83,6 +89,9 @@
|
||||
container.innerHTML = svgCode;
|
||||
// console.log(container.innerHTML);
|
||||
const graphDiv = document.getElementById('graph-div');
|
||||
if (!graphDiv) {
|
||||
throw new Error('graph-div not found');
|
||||
}
|
||||
graphDiv.setAttribute('height', '100%');
|
||||
graphDiv.style.maxWidth = '100%';
|
||||
if (bindFunctions) {
|
||||
@@ -91,7 +100,7 @@
|
||||
}
|
||||
}
|
||||
);
|
||||
view.parentElement.scrollTop = scroll;
|
||||
view.parentElement!.scrollTop = scroll;
|
||||
error = false;
|
||||
} else if (manualUpdate) {
|
||||
manualUpdate = false;
|
||||
|
||||
Vendored
+5
-4
@@ -73,12 +73,13 @@ export type HistoryEntry = { id: string; state: State; time: number; url?: strin
|
||||
}
|
||||
);
|
||||
|
||||
export interface DocConfig {
|
||||
[key: string]: {
|
||||
export type DocConfig = Record<
|
||||
string,
|
||||
{
|
||||
code: string;
|
||||
config?: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
>;
|
||||
|
||||
export type EditorMode = 'code' | 'config';
|
||||
|
||||
|
||||
@@ -8,10 +8,11 @@ const loaders: Record<string, Loader> = {
|
||||
export const loadDataFromUrl = async (): Promise<void> => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
let state: Partial<State> = defaultState;
|
||||
let code: string, config: string;
|
||||
let code: string | undefined = undefined;
|
||||
let config: string | undefined = undefined;
|
||||
let loaded = false;
|
||||
const codeURL: string = searchParams.get('code');
|
||||
const configURL: string = searchParams.get('config');
|
||||
const codeURL: string | undefined = searchParams.get('code') ?? undefined;
|
||||
const configURL: string | undefined = searchParams.get('config') ?? undefined;
|
||||
|
||||
if (codeURL) {
|
||||
code = await (await fetch(codeURL)).text();
|
||||
@@ -23,9 +24,6 @@ export const loadDataFromUrl = async (): Promise<void> => {
|
||||
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 {
|
||||
@@ -38,6 +36,9 @@ export const loadDataFromUrl = async (): Promise<void> => {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!codeURL) {
|
||||
throw new Error('Code URL is not defined');
|
||||
}
|
||||
state = {
|
||||
code,
|
||||
mermaid: config,
|
||||
|
||||
@@ -7,7 +7,7 @@ interface MigrationState {
|
||||
version: number;
|
||||
}
|
||||
|
||||
const migrations: { [key: string]: () => void } = {
|
||||
const migrations: Record<string, () => void> = {
|
||||
injectHistoryIDs
|
||||
};
|
||||
|
||||
|
||||
+12
-18
@@ -39,16 +39,16 @@ let noWarnings = false;
|
||||
/**
|
||||
* List of storages where the warning have already been displayed.
|
||||
*/
|
||||
const alreadyWarnFor: Array<string> = [];
|
||||
const alreadyWarnFor: string[] = [];
|
||||
|
||||
/**
|
||||
* Add a log to indicate that the requested Storage have not been found.
|
||||
* @param {string} storageName
|
||||
*/
|
||||
const warnStorageNotFound = (storageName: string) => {
|
||||
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) {
|
||||
if (!noWarnings && !alreadyWarnFor.includes(storageName) && !isProduction) {
|
||||
let message = `Unable to find the ${storageName}. No data will be persisted.`;
|
||||
if (typeof window === 'undefined') {
|
||||
message +=
|
||||
@@ -60,17 +60,8 @@ const warnStorageNotFound = (storageName: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const allowedClasses = [];
|
||||
/**
|
||||
* Add a class to the allowed list of classes to be serialized
|
||||
* @param classDef The class to add to the list
|
||||
*/
|
||||
export const addSerializableClass = (classDef: () => unknown): void => {
|
||||
allowedClasses.push(classDef);
|
||||
};
|
||||
|
||||
const serialize = (value: unknown): string => ESSerializer.serialize(value);
|
||||
const deserialize = (value: string): unknown => {
|
||||
const deserialize = (value?: string | null): unknown => {
|
||||
// @TODO: to remove in the next major
|
||||
if (value === 'undefined') {
|
||||
return undefined;
|
||||
@@ -78,7 +69,7 @@ const deserialize = (value: string): unknown => {
|
||||
|
||||
if (value !== null && value !== undefined) {
|
||||
try {
|
||||
return ESSerializer.deserialize(value, allowedClasses);
|
||||
return ESSerializer.deserialize(value);
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
// use the value "as is"
|
||||
@@ -184,7 +175,7 @@ function getBrowserStorage(
|
||||
browserStorage: Storage,
|
||||
listenExternalChanges = false
|
||||
): SelfUpdateStorageInterface<any> {
|
||||
const listeners: Array<{ key: string; listener: (newValue: any) => void }> = [];
|
||||
const listeners: { key: string; listener: (newValue: any) => void }[] = [];
|
||||
const listenerFunction = (event: StorageEvent) => {
|
||||
const eventKey = event.key;
|
||||
if (event.storageArea === browserStorage) {
|
||||
@@ -196,12 +187,14 @@ function getBrowserStorage(
|
||||
}
|
||||
};
|
||||
const connect = () => {
|
||||
if (listenExternalChanges && typeof window !== 'undefined' && window?.addEventListener) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (listenExternalChanges && typeof window !== 'undefined' && window.addEventListener) {
|
||||
window.addEventListener('storage', listenerFunction);
|
||||
}
|
||||
};
|
||||
const disconnect = () => {
|
||||
if (listenExternalChanges && typeof window !== 'undefined' && window?.removeEventListener) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (listenExternalChanges && typeof window !== 'undefined' && window.removeEventListener) {
|
||||
window.removeEventListener('storage', listenerFunction);
|
||||
}
|
||||
};
|
||||
@@ -240,7 +233,8 @@ function getBrowserStorage(
|
||||
* @param listenExternalChanges - Update the store if the localStorage is updated from another page
|
||||
*/
|
||||
export function localStorage<T>(listenExternalChanges = false): StorageInterface<T> {
|
||||
if (typeof window !== 'undefined' && window?.localStorage) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (typeof window !== 'undefined' && window.localStorage) {
|
||||
return getBrowserStorage(window.localStorage, listenExternalChanges);
|
||||
}
|
||||
warnStorageNotFound('window.localStorage');
|
||||
|
||||
@@ -111,7 +111,7 @@ export const loadState = (data: string): void => {
|
||||
console.log(`Loading '${data}'`);
|
||||
try {
|
||||
state = deserializeState(data);
|
||||
const mermaidConfig: { [key: string]: string } =
|
||||
const mermaidConfig: Record<string, string> =
|
||||
typeof state.mermaid === 'string' ? JSON.parse(state.mermaid) : state.mermaid;
|
||||
if (
|
||||
mermaidConfig.securityLevel &&
|
||||
|
||||
+10
-9
@@ -1,6 +1,6 @@
|
||||
import { browser } from '$app/environment';
|
||||
import type { AnalyticsInstance } from 'analytics';
|
||||
export let analytics: AnalyticsInstance;
|
||||
export let analytics: AnalyticsInstance | undefined;
|
||||
|
||||
export const initAnalytics = async (): Promise<void> => {
|
||||
if (browser && !analytics) {
|
||||
@@ -27,7 +27,7 @@ export const initAnalytics = async (): Promise<void> => {
|
||||
}
|
||||
};
|
||||
|
||||
export const detectType = (text: string): string => {
|
||||
export const detectType = (text: string): string | undefined => {
|
||||
const possibleDiagramTypes = [
|
||||
'classDiagram',
|
||||
'erDiagram',
|
||||
@@ -49,7 +49,7 @@ export const detectType = (text: string): string => {
|
||||
};
|
||||
|
||||
export const countLines = (code: string): number => {
|
||||
return (code.match(/\n/g) || '').length + 1;
|
||||
return (code.match(/\n/g)?.length ?? 0) + 1;
|
||||
};
|
||||
|
||||
export const saveStatistics = (graph: string): void => {
|
||||
@@ -80,19 +80,20 @@ const delaysPerEvent = {
|
||||
themeChange: defaultDelay
|
||||
};
|
||||
export type AnalyticsEvent = keyof typeof delaysPerEvent;
|
||||
const timeouts: Record<string, number> = {};
|
||||
const timeouts: Map<string, number> = new Map<string, number>();
|
||||
// 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) {
|
||||
if (!timeouts.has(key)) {
|
||||
void analytics.track(name, data);
|
||||
} else {
|
||||
clearTimeout(timeouts[key]);
|
||||
clearTimeout(timeouts.get(key));
|
||||
}
|
||||
timeouts[key] = window.setTimeout(() => {
|
||||
delete timeouts[key];
|
||||
}, delaysPerEvent[name]);
|
||||
timeouts.set(
|
||||
key,
|
||||
window.setTimeout(() => timeouts.delete(key), delaysPerEvent[name])
|
||||
);
|
||||
};
|
||||
|
||||
@@ -23,10 +23,10 @@ export const initHandler = async (): Promise<void> => {
|
||||
syncDiagram();
|
||||
initURLSubscription();
|
||||
await initAnalytics();
|
||||
await analytics?.page();
|
||||
await analytics.page();
|
||||
};
|
||||
|
||||
export const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||
export const isMac = navigator.platform.toUpperCase().includes('MAC');
|
||||
export const cmdKey = isMac ? 'Cmd' : 'Ctrl';
|
||||
|
||||
let count = 0;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strictNullChecks": true,
|
||||
"types": ["vitest/importMeta"]
|
||||
},
|
||||
"extends": "./.svelte-kit/tsconfig.json"
|
||||
|
||||
Reference in New Issue
Block a user