diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..71d07c514 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +demos/*.html \ No newline at end of file diff --git a/.webpack/webpack.config.base.js b/.webpack/webpack.config.base.js index 24db2a4f3..be56ce0ba 100644 --- a/.webpack/webpack.config.base.js +++ b/.webpack/webpack.config.base.js @@ -6,7 +6,7 @@ export default { amd: false, // https://github.com/lodash/lodash/issues/3052 target: 'web', entry: { - mermaid: './src/mermaid.ts', + mermaid: './src/mermaid', }, resolve: { extensions: ['.wasm', '.mjs', '.js', '.ts', '.json', '.jison'], diff --git a/.webpack/webpack.config.e2e.babel.js b/.webpack/webpack.config.e2e.babel.js index b9c5b7733..801460a71 100644 --- a/.webpack/webpack.config.e2e.babel.js +++ b/.webpack/webpack.config.e2e.babel.js @@ -4,7 +4,7 @@ import { merge } from 'webpack-merge'; export default merge(baseConfig, { mode: 'development', entry: { - mermaid: './src/mermaid.ts', + mermaid: './src/mermaid', e2e: './cypress/platform/viewer.js', 'bundle-test': './cypress/platform/bundle-test.js', }, diff --git a/package.json b/package.json index 9aceb6568..899a07bcc 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "lint:fix": "yarn lint --fix", "e2e:depr": "yarn lint && jest e2e --config e2e/jest.config.js", "cypress": "cypress run", + "cypress:open": "cypress open", "e2e": "start-server-and-test dev http://localhost:9000/ cypress", "e2e-upd": "yarn lint && jest e2e -u --config e2e/jest.config.js", "dev": "webpack serve --config ./.webpack/webpack.config.e2e.babel.js", @@ -129,4 +130,4 @@ "**/*.css", "**/*.scss" ] -} +} \ No newline at end of file diff --git a/src/@types/config.d.ts b/src/@types/config.d.ts index 2e947273d..9c63be1ac 100644 --- a/src/@types/config.d.ts +++ b/src/@types/config.d.ts @@ -8,6 +8,7 @@ export interface MermaidConfig { themeCSS?: string; maxTextSize?: number; darkMode?: boolean; + htmlLabels?: boolean; fontFamily?: string; logLevel?: number; securityLevel?: string; diff --git a/src/config.js b/src/config.ts similarity index 84% rename from src/config.js rename to src/config.ts index 59c6c18cb..22804f988 100644 --- a/src/config.js +++ b/src/config.ts @@ -2,16 +2,17 @@ import assignWithDepth from './assignWithDepth'; import { log } from './logger'; import theme from './themes'; import config from './defaultConfig'; +import { MermaidConfig } from 'types/config'; -export const defaultConfig = Object.freeze(config); +export const defaultConfig: MermaidConfig = Object.freeze(config); -let siteConfig = assignWithDepth({}, defaultConfig); -let configFromInitialize; -let directives = []; -let currentConfig = assignWithDepth({}, defaultConfig); +let siteConfig: MermaidConfig = assignWithDepth({}, defaultConfig); +let configFromInitialize: MermaidConfig; +let directives: any[] = []; +let currentConfig: MermaidConfig = assignWithDepth({}, defaultConfig); -export const updateCurrentConfig = (siteCfg, _directives) => { - // start with config beeing the siteConfig +export const updateCurrentConfig = (siteCfg: MermaidConfig, _directives: any[]) => { + // start with config being the siteConfig let cfg = assignWithDepth({}, siteCfg); // let sCfg = assignWithDepth(defaultConfig, siteConfigDelta); @@ -27,12 +28,15 @@ export const updateCurrentConfig = (siteCfg, _directives) => { cfg = assignWithDepth(cfg, sumOfDirectives); + // @ts-ignore if (sumOfDirectives.theme && theme[sumOfDirectives.theme]) { const tmpConfigFromInitialize = assignWithDepth({}, configFromInitialize); const themeVariables = assignWithDepth( tmpConfigFromInitialize.themeVariables || {}, + // @ts-ignore sumOfDirectives.themeVariables ); + // @ts-ignore cfg.themeVariables = theme[cfg.theme].getThemeVariables(themeVariables); } @@ -55,11 +59,13 @@ export const updateCurrentConfig = (siteCfg, _directives) => { * @param conf - The base currentConfig to use as siteConfig * @returns {object} - The siteConfig */ -export const setSiteConfig = (conf) => { +export const setSiteConfig = (conf: MermaidConfig): MermaidConfig => { siteConfig = assignWithDepth({}, defaultConfig); siteConfig = assignWithDepth(siteConfig, conf); + // @ts-ignore if (conf.theme && theme[conf.theme]) { + // @ts-ignore siteConfig.themeVariables = theme[conf.theme].getThemeVariables(conf.themeVariables); } @@ -67,11 +73,11 @@ export const setSiteConfig = (conf) => { return siteConfig; }; -export const saveConfigFromInitialize = (conf) => { +export const saveConfigFromInitialize = (conf: MermaidConfig): void => { configFromInitialize = assignWithDepth({}, conf); }; -export const updateSiteConfig = (conf) => { +export const updateSiteConfig = (conf: MermaidConfig): MermaidConfig => { siteConfig = assignWithDepth(siteConfig, conf); updateCurrentConfig(siteConfig, directives); @@ -88,7 +94,7 @@ export const updateSiteConfig = (conf) => { * * @returns {object} - The siteConfig */ -export const getSiteConfig = () => { +export const getSiteConfig = (): MermaidConfig => { return assignWithDepth({}, siteConfig); }; /** @@ -105,7 +111,7 @@ export const getSiteConfig = () => { * @param {any} conf - The potential currentConfig * @returns {any} - The currentConfig merged with the sanitized conf */ -export const setConfig = (conf) => { +export const setConfig = (conf: MermaidConfig): MermaidConfig => { // sanitize(conf); // Object.keys(conf).forEach(key => { // const manipulator = manipulators[key]; @@ -143,17 +149,14 @@ export const getConfig = () => { * * @param {any} options - The potential setConfig parameter */ -export const sanitize = (options) => { +export const sanitize = (options: any) => { // Checking that options are not in the list of excluded options - Object.keys(siteConfig.secure).forEach((key) => { - if (typeof options[siteConfig.secure[key]] !== 'undefined') { - // DO NOT attempt to print options[siteConfig.secure[key]] within `${}` as a malicious script + siteConfig.secure?.forEach((key) => { + if (typeof options[key] !== 'undefined') { + // DO NOT attempt to print options[key] within `${}` as a malicious script // can exploit the logger's attempt to stringify the value and execute arbitrary code - log.debug( - `Denied attempt to modify a secure key ${siteConfig.secure[key]}`, - options[siteConfig.secure[key]] - ); - delete options[siteConfig.secure[key]]; + log.debug(`Denied attempt to modify a secure key ${key}`, options[key]); + delete options[key]; } }); @@ -186,7 +189,7 @@ export const sanitize = (options) => { * * @param {object} directive The directive to push in */ -export const addDirective = (directive) => { +export const addDirective = (directive: any) => { if (directive.fontFamily) { if (!directive.themeVariables) { directive.themeVariables = { fontFamily: directive.fontFamily }; @@ -215,7 +218,7 @@ export const addDirective = (directive) => { * * **Notes**: (default: current siteConfig ) (optional, default `getSiteConfig()`) */ -export const reset = () => { +export const reset = (): void => { // Replace current config with siteConfig directives = []; updateCurrentConfig(siteConfig, directives); diff --git a/src/diagram-api/detectType.ts b/src/diagram-api/detectType.ts index 3a3250aa0..3066c55c1 100644 --- a/src/diagram-api/detectType.ts +++ b/src/diagram-api/detectType.ts @@ -40,10 +40,10 @@ const diagramMatchers: Record = { * class: { defaultRenderer: string } | undefined; * state: { defaultRenderer: string } | undefined; * flowchart: { defaultRenderer: string } | undefined; - * }} [cnf] + * }} [config] * @returns {string} A graph definition key */ -export const detectType = function (text: string, cnf: MermaidConfig): string { +export const detectType = function (text: string, config?: MermaidConfig): string { text = text.replace(directive, '').replace(anyComment, '\n'); for (const [diagram, matcher] of Object.entries(diagramMatchers)) { if (text.match(matcher)) { @@ -52,16 +52,16 @@ export const detectType = function (text: string, cnf: MermaidConfig): string { } if (text.match(/^\s*classDiagram/)) { - if (cnf?.class?.defaultRenderer === 'dagre-wrapper') return 'classDiagram'; + if (config?.class?.defaultRenderer === 'dagre-wrapper') return 'classDiagram'; return 'class'; } if (text.match(/^\s*stateDiagram/)) { - if (cnf?.state?.defaultRenderer === 'dagre-wrapper') return 'stateDiagram'; + if (config?.state?.defaultRenderer === 'dagre-wrapper') return 'stateDiagram'; return 'state'; } - if (cnf?.flowchart?.defaultRenderer === 'dagre-wrapper') { + if (config?.flowchart?.defaultRenderer === 'dagre-wrapper') { return 'flowchart-v2'; } diff --git a/src/mermaid.ts b/src/mermaid.ts index 13b693b8c..8272a037a 100644 --- a/src/mermaid.ts +++ b/src/mermaid.ts @@ -1,9 +1,8 @@ -// TODO: Remove -// @ts-nocheck /** * Web page integration module for the mermaid framework. It uses the mermaidAPI for mermaid * functionality and to render the diagrams to svg code. */ +import { MermaidConfig } from 'types/config'; import { log } from './logger'; import mermaidAPI from './mermaidAPI'; import utils from './utils'; @@ -31,73 +30,73 @@ import utils from './utils'; * * Renders the mermaid diagrams */ -const init = function (config: any, ...nodes: any[]) { +const init = function ( + config?: MermaidConfig, + nodes?: string | HTMLElement | NodeListOf, + callback?: Function +) { try { - initThrowsErrors(config, nodes); + initThrowsErrors(config, nodes, callback); } catch (e) { log.warn('Syntax Error rendering'); + // @ts-ignore log.warn(e.str); - if (this.parseError) { - this.parseError(e); + if (mermaid.parseError) { + // @ts-ignore + mermaid.parseError(e); } } }; -const initThrowsErrors = function (config: any, nodes: any[]) { +const initThrowsErrors = function ( + config?: MermaidConfig, + nodes?: string | HTMLElement | NodeListOf, + callback?: Function +) { const conf = mermaidAPI.getConfig(); // console.log('Starting rendering diagrams (init) - mermaid.init', conf); if (config) { + // @ts-ignore mermaid.sequenceConfig = config; } // if last argument is a function this is the callback function - let callback: (id: string) => void; - if (typeof nodes[nodes.length - 1] === 'function') { - callback = nodes[nodes.length - 1]; - log.debug('Callback function found'); + + if (!callback && typeof conf?.mermaid?.callback === 'function') { + callback = conf.mermaid.callback; + } + log.debug(`${!callback ? 'No ' : ''}Callback function found`); + let nodesToProcess: NodeListOf; + if (typeof nodes === 'undefined') { + nodesToProcess = document.querySelectorAll('.mermaid'); + } else if (typeof nodes === 'string') { + nodesToProcess = document.querySelectorAll(nodes); + } else if (nodes instanceof HTMLElement) { + nodesToProcess = new NodeList() as NodeListOf; + nodesToProcess[0] = nodes; + } else if (nodes instanceof NodeList) { + nodesToProcess = nodes; } else { - if (typeof conf.mermaid !== 'undefined') { - if (typeof conf.mermaid.callback === 'function') { - callback = conf.mermaid.callback; - log.debug('Callback function found'); - } else { - log.debug('No Callback function found'); - } - } - } - nodes = - nodes === undefined - ? document.querySelectorAll('.mermaid') - : typeof nodes === 'string' - ? document.querySelectorAll(nodes) - : nodes instanceof window.Node - ? [nodes] - : nodes; // Last case - sequence config was passed pick next - - log.debug('Start On Load before: ' + mermaid.startOnLoad); - if (typeof mermaid.startOnLoad !== 'undefined') { - log.debug('Start On Load inner: ' + mermaid.startOnLoad); - mermaidAPI.updateSiteConfig({ startOnLoad: mermaid.startOnLoad }); + throw new Error('Invalid argument nodes for mermaid.init'); } - if (typeof mermaid.ganttConfig !== 'undefined') { - mermaidAPI.updateSiteConfig({ gantt: mermaid.ganttConfig }); + log.debug(`Found ${nodesToProcess.length} diagrams`); + if (typeof config?.startOnLoad !== 'undefined') { + log.debug('Start On Load: ' + config?.startOnLoad); + mermaidAPI.updateSiteConfig({ startOnLoad: config?.startOnLoad }); } const idGenerator = new utils.initIdGenerator(conf.deterministicIds, conf.deterministicIDSeed); let txt; - for (let i = 0; i < nodes.length; i++) { - // element is the current div with mermaid class - const element = nodes[i]; - + // element is the current div with mermaid class + for (const element of Array.from(nodesToProcess)) { /*! Check if previously processed */ - if (!element.getAttribute('data-processed')) { - element.setAttribute('data-processed', true); - } else { + if (element.getAttribute('data-processed')) { continue; } + element.setAttribute('data-processed', 'true'); const id = `mermaid-${idGenerator.next()}`; @@ -118,7 +117,7 @@ const initThrowsErrors = function (config: any, nodes: any[]) { mermaidAPI.render( id, txt, - (svgCode, bindFunctions) => { + (svgCode: string, bindFunctions: (el: HTMLElement) => void) => { element.innerHTML = svgCode; if (typeof callback !== 'undefined') { callback(id); @@ -129,24 +128,15 @@ const initThrowsErrors = function (config: any, nodes: any[]) { ); } catch (error) { log.warn('Catching Error (bootstrap)'); + // @ts-ignore + // TODO: We should be throwing an error object. throw { error, message: error.str }; } } }; -const initialize = function (config: any) { - // mermaidAPI.reset(); - if (typeof config.mermaid !== 'undefined') { - if (typeof config.mermaid.startOnLoad !== 'undefined') { - mermaid.startOnLoad = config.mermaid.startOnLoad; - } - if (typeof config.mermaid.htmlLabels !== 'undefined') { - mermaid.htmlLabels = - config.mermaid.htmlLabels === 'false' || config.mermaid.htmlLabels === false ? false : true; - } - } +const initialize = function (config: MermaidConfig) { mermaidAPI.initialize(config); - // mermaidAPI.reset(); }; /** @@ -154,22 +144,9 @@ const initialize = function (config: any) { * configuration for mermaid rendering and calls init for rendering the mermaid diagrams on the page. */ const contentLoaded = function () { - let config; - - if (mermaid.startOnLoad) { - // No config found, do check API config - config = mermaidAPI.getConfig(); - if (config.startOnLoad) { - mermaid.init(); - } - } else { - if (typeof mermaid.startOnLoad === 'undefined') { - log.debug('In start, no config'); - config = mermaidAPI.getConfig(); - if (config.startOnLoad) { - mermaid.init(); - } - } + const { startOnLoad } = mermaidAPI.getConfig(); + if (startOnLoad) { + mermaid.init(); } }; @@ -200,13 +177,12 @@ if (typeof document !== 'undefined') { * * @param {function (err, hash)} newParseErrorHandler New parseError() callback. */ -const setParseErrorHandler = function (newParseErrorHandler) { +const setParseErrorHandler = function (newParseErrorHandler: (err: any, hash: any) => void) { + // @ts-ignore mermaid.parseError = newParseErrorHandler; }; const mermaid = { - startOnLoad: true, - htmlLabels: true, diagrams: {}, mermaidAPI, parse: mermaidAPI != undefined ? mermaidAPI.parse : null, @@ -215,7 +191,7 @@ const mermaid = { init, initThrowsErrors, initialize, - + parseError: undefined, contentLoaded, setParseErrorHandler, diff --git a/src/utils.js b/src/utils.ts similarity index 99% rename from src/utils.js rename to src/utils.ts index 24fb965b5..d5f3b31a9 100644 --- a/src/utils.js +++ b/src/utils.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { sanitizeUrl } from '@braintree/sanitize-url'; import { curveBasis, @@ -18,6 +19,7 @@ import { configKeys } from './defaultConfig'; import { log } from './logger'; import { detectType } from './diagram-api/detectType'; import assignWithDepth from './assignWithDepth'; +import { MermaidConfig } from 'types/config'; // Effectively an enum of the supported curve types, accessible by name const d3CurveTypes = { @@ -71,7 +73,7 @@ const anyComment = /\s*%%.*\n/gm; * @param {any} cnf * @returns {object} The json object representing the init passed to mermaid.initialize() */ -export const detectInit = function (text, cnf) { +export const detectInit = function (text: string, config?: MermaidConfig): MermaidConfig { let inits = detectDirective(text, /(?:init\b)|(?:initialize\b)/); let results = {}; @@ -84,7 +86,7 @@ export const detectInit = function (text, cnf) { results = inits.args; } if (results) { - let type = detectType(text, cnf); + let type = detectType(text, config); ['config'].forEach((prop) => { if (typeof results[prop] !== 'undefined') { if (type === 'flowchart-v2') {