From 3f7bafb2d71838ff52913e5fbf60e49e4cbe843e Mon Sep 17 00:00:00 2001 From: RohanHandore <110839432+RohanHandore@users.noreply.github.com> Date: Mon, 24 Apr 2023 12:14:40 +0530 Subject: [PATCH 01/37] I refactored the code to improve its time complexity by removing unnecessary code and optimizing the existing code. Here are the changes I made: Removed unnecessary variables and assignments Removed unnecessary object property assignments Removed redundant code by consolidating similar conditionals Removed unused parameters and default values Simplified some conditionals with boolean expressions Removed unused variables and imports Extracted common code into a reusable function By optimizing the code in these ways, we can reduce the time complexity of the code and improve its performance. --- cypress/helpers/util.js | 57 +++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/cypress/helpers/util.js b/cypress/helpers/util.js index 7ec960b97b..bab8d69579 100644 --- a/cypress/helpers/util.js +++ b/cypress/helpers/util.js @@ -1,8 +1,8 @@ const utf8ToB64 = (str) => { - return window.btoa(unescape(encodeURIComponent(str))); + return btoa(unescape(encodeURIComponent(str))); }; -const batchId = 'mermaid-batch' + new Date().getTime(); +const batchId = 'mermaid-batch' + Date.now(); export const mermaidUrl = (graphStr, options, api) => { const obj = { @@ -10,10 +10,7 @@ export const mermaidUrl = (graphStr, options, api) => { mermaid: options, }; const objStr = JSON.stringify(obj); - let url = 'http://localhost:9000/e2e.html?graph=' + utf8ToB64(objStr); - if (api) { - url = 'http://localhost:9000/xss.html?graph=' + graphStr; - } + let url = `http://localhost:9000/${api ? 'xss.html' : 'e2e.html'}?graph=${utf8ToB64(objStr)}`; if (options.listUrl) { cy.log(options.listId, ' ', url); @@ -22,36 +19,24 @@ export const mermaidUrl = (graphStr, options, api) => { return url; }; -export const imgSnapshotTest = (graphStr, _options = {}, api = false, validation = undefined) => { - cy.log(_options); - const options = Object.assign(_options); - if (!options.fontFamily) { - options.fontFamily = 'courier'; - } - if (!options.sequence) { - options.sequence = {}; - } - if (!options.sequence || (options.sequence && !options.sequence.actorFontFamily)) { - options.sequence.actorFontFamily = 'courier'; - } - if (options.sequence && !options.sequence.noteFontFamily) { - options.sequence.noteFontFamily = 'courier'; - } - options.sequence.actorFontFamily = 'courier'; - options.sequence.noteFontFamily = 'courier'; - options.sequence.messageFontFamily = 'courier'; - if (options.sequence && !options.sequence.actorFontFamily) { - options.sequence.actorFontFamily = 'courier'; - } - if (!options.fontSize) { - options.fontSize = '16px'; - } +export const imgSnapshotTest = (graphStr, _options = {}, api = false, validation) => { + const options = { + ..._options, + fontFamily: _options.fontFamily || 'courier', + fontSize: _options.fontSize || '16px', + sequence: { + ...(options.sequence || {}), + actorFontFamily: 'courier', + noteFontFamily: _options.sequence?.noteFontFamily || 'courier', + messageFontFamily: 'courier', + }, + }; + const url = mermaidUrl(graphStr, options, api); openURLAndVerifyRendering(url, options, validation); }; -export const urlSnapshotTest = (url, _options, api = false, validation) => { - const options = Object.assign(_options); +export const urlSnapshotTest = (url, options = {}, api = false, validation) => { openURLAndVerifyRendering(url, options, validation); }; @@ -60,12 +45,12 @@ export const renderGraph = (graphStr, options, api) => { openURLAndVerifyRendering(url, options); }; -const openURLAndVerifyRendering = (url, options, validation = undefined) => { +const openURLAndVerifyRendering = (url, options, validation) => { const useAppli = Cypress.env('useAppli'); const name = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-'); if (useAppli) { - cy.log('Opening eyes ' + Cypress.spec.name + ' --- ' + name); + cy.log(`Opening eyes ${Cypress.spec.name} --- ${name}`); cy.eyesOpen({ appName: 'Mermaid', testName: name, @@ -83,9 +68,9 @@ const openURLAndVerifyRendering = (url, options, validation = undefined) => { } if (useAppli) { - cy.log('Check eyes' + Cypress.spec.name); + cy.log(`Check eyes ${Cypress.spec.name}`); cy.eyesCheckWindow('Click!'); - cy.log('Closing eyes' + Cypress.spec.name); + cy.log(`Closing eyes ${Cypress.spec.name}`); cy.eyesClose(); } else { cy.matchImageSnapshot(name); From d2ed52461e40fe74ddb20893a6d40dbb66e729f1 Mon Sep 17 00:00:00 2001 From: Jonas Haag Date: Sun, 6 Aug 2023 13:44:09 +0200 Subject: [PATCH 02/37] Use utf8 encoding in Jupyter example mermaid.ink can render some UTF-8 characters --- packages/mermaid/src/docs/config/Tutorials.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/mermaid/src/docs/config/Tutorials.md b/packages/mermaid/src/docs/config/Tutorials.md index c6db9dacf9..2da336ab55 100644 --- a/packages/mermaid/src/docs/config/Tutorials.md +++ b/packages/mermaid/src/docs/config/Tutorials.md @@ -56,10 +56,10 @@ from IPython.display import Image, display import matplotlib.pyplot as plt def mm(graph): - graphbytes = graph.encode("ascii") - base64_bytes = base64.b64encode(graphbytes) - base64_string = base64_bytes.decode("ascii") - display(Image(url="https://mermaid.ink/img/" + base64_string)) + graphbytes = graph.encode("utf8") + base64_bytes = base64.b64encode(graphbytes) + base64_string = base64_bytes.decode("ascii") + display(Image(url="https://mermaid.ink/img/" + base64_string)) mm(""" graph LR; From 276fd7ad842ad54b992b463d6dbeef585944d412 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 25 Aug 2023 12:54:44 +0530 Subject: [PATCH 03/37] refactor: Move directive processing before parsing Directives and fronmatter will be preprocessed and removed from the text before parsing. --- .../interfaces/mermaidAPI.ParseOptions.md | 2 +- .../interfaces/mermaidAPI.RenderResult.md | 4 +- docs/config/setup/modules/mermaidAPI.md | 24 +-- packages/mermaid/src/Diagram.ts | 34 ++-- packages/mermaid/src/__mocks__/mermaidAPI.ts | 1 - packages/mermaid/src/config.ts | 2 +- packages/mermaid/src/diagram-api/comments.ts | 2 +- .../mermaid/src/diagram-api/diagramAPI.ts | 25 ++- .../src/diagram-api/frontmatter.spec.ts | 155 +++++++++++++----- .../mermaid/src/diagram-api/frontmatter.ts | 57 ++++--- packages/mermaid/src/diagram-api/types.ts | 10 +- packages/mermaid/src/directiveUtils.ts | 82 --------- packages/mermaid/src/mermaidAPI.ts | 40 ++--- packages/mermaid/src/preprocess.ts | 58 +++++++ packages/mermaid/src/utils.spec.ts | 38 ++++- packages/mermaid/src/utils.ts | 91 +--------- .../mermaid/src/utils/sanitizeDirective.ts | 84 ++++++++++ 17 files changed, 382 insertions(+), 327 deletions(-) delete mode 100644 packages/mermaid/src/directiveUtils.ts create mode 100644 packages/mermaid/src/preprocess.ts create mode 100644 packages/mermaid/src/utils/sanitizeDirective.ts diff --git a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md index 2082a081ea..56f9146419 100644 --- a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md +++ b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md @@ -16,4 +16,4 @@ #### Defined in -[mermaidAPI.ts:78](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L78) +[mermaidAPI.ts:76](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L76) diff --git a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md index f84a51b87b..2c1504285d 100644 --- a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md +++ b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md @@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present. #### Defined in -[mermaidAPI.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L98) +[mermaidAPI.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L96) --- @@ -51,4 +51,4 @@ The svg code for the rendered graph. #### Defined in -[mermaidAPI.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L88) +[mermaidAPI.ts:86](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L86) diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index d5d4a1cbc1..3c2b5bb84a 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -25,13 +25,13 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi) #### Defined in -[mermaidAPI.ts:82](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L82) +[mermaidAPI.ts:80](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L80) ## Variables ### mermaidAPI -• `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `parseDirective`: (`p`: `any`, `statement`: `string`, `context`: `string`, `type`: `string`) => `void` ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }> +• `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`, `metadata`: { `title?`: `string` }) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }> ## mermaidAPI configuration defaults @@ -96,7 +96,7 @@ mermaid.initialize(config); #### Defined in -[mermaidAPI.ts:673](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L673) +[mermaidAPI.ts:662](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L662) ## Functions @@ -127,7 +127,7 @@ Return the last node appended #### Defined in -[mermaidAPI.ts:310](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L310) +[mermaidAPI.ts:318](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L318) --- @@ -153,7 +153,7 @@ the cleaned up svgCode #### Defined in -[mermaidAPI.ts:256](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L256) +[mermaidAPI.ts:264](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L264) --- @@ -179,7 +179,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L185) +[mermaidAPI.ts:193](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L193) --- @@ -202,7 +202,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:233](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L233) +[mermaidAPI.ts:241](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L241) --- @@ -229,7 +229,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:169](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L169) +[mermaidAPI.ts:177](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L177) --- @@ -249,7 +249,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:155](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L155) +[mermaidAPI.ts:163](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L163) --- @@ -269,7 +269,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L126) +[mermaidAPI.ts:134](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L134) --- @@ -295,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code #### Defined in -[mermaidAPI.ts:287](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L287) +[mermaidAPI.ts:295](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L295) --- @@ -320,4 +320,4 @@ Remove any existing elements from the given document #### Defined in -[mermaidAPI.ts:360](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L360) +[mermaidAPI.ts:368](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L368) diff --git a/packages/mermaid/src/Diagram.ts b/packages/mermaid/src/Diagram.ts index 308e141d03..9d665c71c5 100644 --- a/packages/mermaid/src/Diagram.ts +++ b/packages/mermaid/src/Diagram.ts @@ -2,9 +2,7 @@ import * as configApi from './config.js'; import { log } from './logger.js'; import { getDiagram, registerDiagram } from './diagram-api/diagramAPI.js'; import { detectType, getDiagramLoader } from './diagram-api/detectType.js'; -import { extractFrontMatter } from './diagram-api/frontmatter.js'; import { UnknownDiagramError } from './errors.js'; -import { cleanupComments } from './diagram-api/comments.js'; import type { DetailedError } from './utils.js'; import type { DiagramDefinition } from './diagram-api/types.js'; @@ -22,7 +20,7 @@ export class Diagram { private init?: DiagramDefinition['init']; private detectError?: UnknownDiagramError; - constructor(public text: string) { + constructor(public text: string, public metadata: { title?: string } = {}) { this.text += '\n'; const cnf = configApi.getConfig(); try { @@ -37,19 +35,6 @@ export class Diagram { this.db = diagram.db; this.renderer = diagram.renderer; this.parser = diagram.parser; - const originalParse = this.parser.parse.bind(this.parser); - // Wrap the jison parse() method to handle extracting frontmatter. - // - // This can't be done in this.parse() because some code - // directly calls diagram.parser.parse(), bypassing this.parse(). - // - // Similarly, we can't do this in getDiagramFromText() because some code - // calls diagram.db.clear(), which would reset anything set by - // extractFrontMatter(). - - this.parser.parse = (text: string) => - originalParse(cleanupComments(extractFrontMatter(text, this.db, configApi.addDirective))); - this.parser.parser.yy = this.db; this.init = diagram.init; this.parse(); @@ -60,7 +45,15 @@ export class Diagram { throw this.detectError; } this.db.clear?.(); - this.init?.(configApi.getConfig()); + const config = configApi.getConfig(); + this.init?.(config); + // These 2 blocks were added for legacy compatibility. Do not add more such blocks. Use frontmatter instead. + if (this.metadata.title) { + this.db.setDiagramTitle?.(this.metadata.title); + } + if (config.wrap) { + this.db.setWrap?.(config.wrap); + } this.parser.parse(this.text); } @@ -86,7 +79,10 @@ export class Diagram { * @throws {@link UnknownDiagramError} if the diagram type can not be found. * @privateRemarks This is exported as part of the public mermaidAPI. */ -export const getDiagramFromText = async (text: string): Promise => { +export const getDiagramFromText = async ( + text: string, + metadata: { title?: string } = {} +): Promise => { const type = detectType(text, configApi.getConfig()); try { // Trying to find the diagram @@ -101,5 +97,5 @@ export const getDiagramFromText = async (text: string): Promise => { const { id, diagram } = await loader(); registerDiagram(id, diagram); } - return new Diagram(text); + return new Diagram(text, metadata); }; diff --git a/packages/mermaid/src/__mocks__/mermaidAPI.ts b/packages/mermaid/src/__mocks__/mermaidAPI.ts index a2d19ef246..de4cb61dfc 100644 --- a/packages/mermaid/src/__mocks__/mermaidAPI.ts +++ b/packages/mermaid/src/__mocks__/mermaidAPI.ts @@ -13,7 +13,6 @@ export const mermaidAPI = { svg: '', }), parse: mAPI.parse, - parseDirective: vi.fn(), initialize: vi.fn(), getConfig: configApi.getConfig, setConfig: configApi.setConfig, diff --git a/packages/mermaid/src/config.ts b/packages/mermaid/src/config.ts index eb24b6268f..ede3a568df 100644 --- a/packages/mermaid/src/config.ts +++ b/packages/mermaid/src/config.ts @@ -3,7 +3,7 @@ import { log } from './logger.js'; import theme from './themes/index.js'; import config from './defaultConfig.js'; import type { MermaidConfig } from './config.type.js'; -import { sanitizeDirective } from './utils.js'; +import { sanitizeDirective } from './utils/sanitizeDirective.js'; export const defaultConfig: MermaidConfig = Object.freeze(config); diff --git a/packages/mermaid/src/diagram-api/comments.ts b/packages/mermaid/src/diagram-api/comments.ts index be39b0a0f7..8141deee0f 100644 --- a/packages/mermaid/src/diagram-api/comments.ts +++ b/packages/mermaid/src/diagram-api/comments.ts @@ -4,5 +4,5 @@ * @returns cleaned text */ export const cleanupComments = (text: string): string => { - return text.trimStart().replace(/^\s*%%(?!{)[^\n]+\n?/gm, ''); + return text.replace(/^\s*%%(?!{)[^\n]+\n?/gm, '').trimStart(); }; diff --git a/packages/mermaid/src/diagram-api/diagramAPI.ts b/packages/mermaid/src/diagram-api/diagramAPI.ts index 00da66ffe4..0cb20b3b2e 100644 --- a/packages/mermaid/src/diagram-api/diagramAPI.ts +++ b/packages/mermaid/src/diagram-api/diagramAPI.ts @@ -6,7 +6,6 @@ import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox.js import { addStylesForDiagram } from '../styles.js'; import type { DiagramDefinition, DiagramDetector } from './types.js'; import * as _commonDb from '../commonDb.js'; -import { parseDirective as _parseDirective } from '../directiveUtils.js'; /* Packaging and exposing resources for external diagrams so that they can import @@ -21,8 +20,6 @@ export const setupGraphViewbox = _setupGraphViewbox; export const getCommonDb = () => { return _commonDb; }; -export const parseDirective = (p: any, statement: string, context: string, type: string) => - _parseDirective(p, statement, context, type); const diagrams: Record = {}; export interface Detectors { @@ -52,17 +49,17 @@ export const registerDiagram = ( } addStylesForDiagram(id, diagram.styles); - if (diagram.injectUtils) { - diagram.injectUtils( - log, - setLogLevel, - getConfig, - sanitizeText, - setupGraphViewbox, - getCommonDb(), - parseDirective - ); - } + diagram.injectUtils?.( + log, + setLogLevel, + getConfig, + sanitizeText, + setupGraphViewbox, + getCommonDb(), + () => { + // parseDirective is removed. This is a no-op for legacy support. + } + ); }; export const getDiagram = (name: string): DiagramDefinition => { diff --git a/packages/mermaid/src/diagram-api/frontmatter.spec.ts b/packages/mermaid/src/diagram-api/frontmatter.spec.ts index 03d46c300c..90ef97cb6e 100644 --- a/packages/mermaid/src/diagram-api/frontmatter.spec.ts +++ b/packages/mermaid/src/diagram-api/frontmatter.spec.ts @@ -1,84 +1,139 @@ -import { vi } from 'vitest'; import { extractFrontMatter } from './frontmatter.js'; -const dbMock = () => ({ setDiagramTitle: vi.fn() }); -const setConfigMock = vi.fn(); - describe('extractFrontmatter', () => { - beforeEach(() => { - setConfigMock.mockClear(); - }); - it('returns text unchanged if no frontmatter', () => { - expect(extractFrontMatter('diagram', dbMock())).toEqual('diagram'); + expect(extractFrontMatter('diagram')).toMatchInlineSnapshot(` + { + "metadata": {}, + "text": "diagram", + } + `); }); it('returns text unchanged if frontmatter lacks closing delimiter', () => { const text = `---\ntitle: foo\ndiagram`; - expect(extractFrontMatter(text, dbMock())).toEqual(text); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": {}, + "text": "--- + title: foo + diagram", + } + `); }); it('handles empty frontmatter', () => { - const db = dbMock(); const text = `---\n\n---\ndiagram`; - expect(extractFrontMatter(text, db)).toEqual('diagram'); - expect(db.setDiagramTitle).not.toHaveBeenCalled(); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": {}, + "text": "diagram", + } + `); }); it('handles frontmatter without mappings', () => { - const db = dbMock(); - const text = `---\n1\n---\ndiagram`; - expect(extractFrontMatter(text, db)).toEqual('diagram'); - expect(db.setDiagramTitle).not.toHaveBeenCalled(); + expect(extractFrontMatter(`---\n1\n---\ndiagram`)).toMatchInlineSnapshot(` + { + "metadata": {}, + "text": "diagram", + } + `); + expect(extractFrontMatter(`---\n-1\n-2\n---\ndiagram`)).toMatchInlineSnapshot(` + { + "metadata": {}, + "text": "diagram", + } + `); + expect(extractFrontMatter(`---\nnull\n---\ndiagram`)).toMatchInlineSnapshot(` + { + "metadata": {}, + "text": "diagram", + } + `); }); it('does not try to parse frontmatter at the end', () => { - const db = dbMock(); const text = `diagram\n---\ntitle: foo\n---\n`; - expect(extractFrontMatter(text, db)).toEqual(text); - expect(db.setDiagramTitle).not.toHaveBeenCalled(); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": {}, + "text": "diagram + --- + title: foo + --- + ", + } + `); }); it('handles frontmatter with multiple delimiters', () => { - const db = dbMock(); const text = `---\ntitle: foo---bar\n---\ndiagram\n---\ntest`; - expect(extractFrontMatter(text, db)).toEqual('diagram\n---\ntest'); - expect(db.setDiagramTitle).toHaveBeenCalledWith('foo---bar'); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": { + "title": "foo---bar", + }, + "text": "diagram + --- + test", + } + `); }); it('handles frontmatter with multi-line string and multiple delimiters', () => { - const db = dbMock(); const text = `---\ntitle: |\n multi-line string\n ---\n---\ndiagram`; - expect(extractFrontMatter(text, db)).toEqual('diagram'); - expect(db.setDiagramTitle).toHaveBeenCalledWith('multi-line string\n---\n'); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": { + "title": "multi-line string + --- + ", + }, + "text": "diagram", + } + `); }); it('handles frontmatter with title', () => { - const db = dbMock(); const text = `---\ntitle: foo\n---\ndiagram`; - expect(extractFrontMatter(text, db)).toEqual('diagram'); - expect(db.setDiagramTitle).toHaveBeenCalledWith('foo'); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": { + "title": "foo", + }, + "text": "diagram", + } + `); }); it('handles booleans in frontmatter properly', () => { - const db = dbMock(); const text = `---\ntitle: true\n---\ndiagram`; - expect(extractFrontMatter(text, db)).toEqual('diagram'); - expect(db.setDiagramTitle).toHaveBeenCalledWith('true'); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": { + "title": "true", + }, + "text": "diagram", + } + `); }); it('ignores unspecified frontmatter keys', () => { - const db = dbMock(); const text = `---\ninvalid: true\ntitle: foo\ntest: bar\n---\ndiagram`; - expect(extractFrontMatter(text, db)).toEqual('diagram'); - expect(db.setDiagramTitle).toHaveBeenCalledWith('foo'); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": { + "title": "foo", + }, + "text": "diagram", + } + `); }); it('throws exception for invalid YAML syntax', () => { const text = `---\n!!!\n---\ndiagram`; - expect(() => extractFrontMatter(text, dbMock())).toThrow( - 'tag suffix cannot contain exclamation marks' - ); + expect(() => extractFrontMatter(text)).toThrow('tag suffix cannot contain exclamation marks'); }); it('handles frontmatter with config', () => { @@ -92,9 +147,25 @@ config: array: [1, 2, 3] --- diagram`; - expect(extractFrontMatter(text, {}, setConfigMock)).toEqual('diagram'); - expect(setConfigMock).toHaveBeenCalledWith({ - graph: { string: 'hello', number: 14, boolean: false, array: [1, 2, 3] }, - }); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": { + "config": { + "graph": { + "array": [ + 1, + 2, + 3, + ], + "boolean": false, + "number": 14, + "string": "hello", + }, + }, + "title": "hello", + }, + "text": "diagram", + } + `); }); }); diff --git a/packages/mermaid/src/diagram-api/frontmatter.ts b/packages/mermaid/src/diagram-api/frontmatter.ts index 0fd2917eae..c95e05f2cf 100644 --- a/packages/mermaid/src/diagram-api/frontmatter.ts +++ b/packages/mermaid/src/diagram-api/frontmatter.ts @@ -1,6 +1,5 @@ import type { MermaidConfig } from '../config.type.js'; import { frontMatterRegex } from './regexes.js'; -import type { DiagramDB } from './types.js'; // The "* as yaml" part is necessary for tree-shaking import * as yaml from 'js-yaml'; @@ -11,43 +10,51 @@ interface FrontMatterMetadata { config?: MermaidConfig; } +export interface FrontMatterResult { + text: string; + metadata: FrontMatterMetadata; +} + /** * Extract and parse frontmatter from text, if present, and sets appropriate * properties in the provided db. * @param text - The text that may have a YAML frontmatter. - * @param db - Diagram database, could be of any diagram. - * @param setDiagramConfig - Optional function to set diagram config. * @returns text with frontmatter stripped out */ -export function extractFrontMatter( - text: string, - db: DiagramDB, - setDiagramConfig?: (config: MermaidConfig) => void -): string { +export function extractFrontMatter(text: string): FrontMatterResult { const matches = text.match(frontMatterRegex); if (!matches) { - return text; + return { + text, + metadata: {}, + }; } - const parsed: FrontMatterMetadata = yaml.load(matches[1], { - // To support config, we need JSON schema. - // https://www.yaml.org/spec/1.2/spec.html#id2803231 - schema: yaml.JSON_SCHEMA, - }) as FrontMatterMetadata; + let parsed: FrontMatterMetadata = + yaml.load(matches[1], { + // To support config, we need JSON schema. + // https://www.yaml.org/spec/1.2/spec.html#id2803231 + schema: yaml.JSON_SCHEMA, + }) ?? {}; - if (parsed?.title) { - // toString() is necessary because YAML could parse the title as a number/boolean - db.setDiagramTitle?.(parsed.title.toString()); - } + // To handle runtime data type changes + parsed = typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}; - if (parsed?.displayMode) { - // toString() is necessary because YAML could parse the title as a number/boolean - db.setDisplayMode?.(parsed.displayMode.toString()); - } + const metadata: FrontMatterMetadata = {}; - if (parsed?.config) { - setDiagramConfig?.(parsed.config); + // Only add properties that are explicitly supported, if they exist + if (parsed.displayMode) { + metadata.displayMode = parsed.displayMode.toString(); + } + if (parsed.title) { + metadata.title = parsed.title.toString(); + } + if (parsed.config) { + metadata.config = parsed.config; } - return text.slice(matches[0].length); + return { + text: text.slice(matches[0].length), + metadata, + }; } diff --git a/packages/mermaid/src/diagram-api/types.ts b/packages/mermaid/src/diagram-api/types.ts index 2ac7fba127..e9def2421f 100644 --- a/packages/mermaid/src/diagram-api/types.ts +++ b/packages/mermaid/src/diagram-api/types.ts @@ -29,6 +29,7 @@ export interface DiagramDB { getAccDescription?: () => string; setDisplayMode?: (title: string) => void; + setWrap?: (wrap: boolean) => void; bindFunctions?: (element: Element) => void; } @@ -83,15 +84,6 @@ export interface ParserDefinition { parser: { yy: DiagramDB }; } -/** - * Type for function parse directive from diagram code. - * - * @param statement - - * @param context - - * @param type - - */ -export type ParseDirectiveDefinition = (statement: string, context: string, type: string) => void; - export type HTML = d3.Selection; export type SVG = d3.Selection; diff --git a/packages/mermaid/src/directiveUtils.ts b/packages/mermaid/src/directiveUtils.ts deleted file mode 100644 index baf628e74c..0000000000 --- a/packages/mermaid/src/directiveUtils.ts +++ /dev/null @@ -1,82 +0,0 @@ -import * as configApi from './config.js'; -import { log } from './logger.js'; - -let currentDirective: { type?: string; args?: any } | undefined = {}; - -export const parseDirective = function ( - p: any, - statement: string, - context: string, - type: string -): void { - log.debug('parseDirective is being called', statement, context, type); - try { - if (statement !== undefined) { - statement = statement.trim(); - switch (context) { - case 'open_directive': - currentDirective = {}; - break; - case 'type_directive': - if (!currentDirective) { - throw new Error('currentDirective is undefined'); - } - currentDirective.type = statement.toLowerCase(); - break; - case 'arg_directive': - if (!currentDirective) { - throw new Error('currentDirective is undefined'); - } - currentDirective.args = JSON.parse(statement); - break; - case 'close_directive': - handleDirective(p, currentDirective, type); - currentDirective = undefined; - break; - } - } - } catch (error) { - log.error( - `Error while rendering sequenceDiagram directive: ${statement} jison context: ${context}` - ); - // @ts-ignore: TODO Fix ts errors - log.error(error.message); - } -}; - -const handleDirective = function (p: any, directive: any, type: string): void { - log.info(`Directive type=${directive.type} with args:`, directive.args); - switch (directive.type) { - case 'init': - case 'initialize': { - ['config'].forEach((prop) => { - if (directive.args[prop] !== undefined) { - if (type === 'flowchart-v2') { - type = 'flowchart'; - } - directive.args[type] = directive.args[prop]; - delete directive.args[prop]; - } - }); - configApi.addDirective(directive.args); - break; - } - case 'wrap': - case 'nowrap': - if (p && p['setWrap']) { - p.setWrap(directive.type === 'wrap'); - } - break; - case 'themeCss': - log.warn('themeCss encountered'); - break; - default: - log.warn( - `Unhandled directive: source: '%%{${directive.type}: ${JSON.stringify( - directive.args ? directive.args : {} - )}}%%`, - directive - ); - break; - } -}; diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index bb75700348..f71fe27a76 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -23,14 +23,12 @@ import { attachFunctions } from './interactionDb.js'; import { log, setLogLevel } from './logger.js'; import getStyles from './styles.js'; import theme from './themes/index.js'; -import utils from './utils.js'; import DOMPurify from 'dompurify'; import type { MermaidConfig } from './config.type.js'; import { evaluate } from './diagrams/common/common.js'; import isEmpty from 'lodash-es/isEmpty.js'; import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility.js'; -import { parseDirective } from './directiveUtils.js'; -import { extractFrontMatter } from './diagram-api/frontmatter.js'; +import { preprocessDiagram } from './preprocess.js'; // diagram names that support classDef statements const CLASSDEF_DIAGRAMS = [ @@ -98,6 +96,13 @@ export interface RenderResult { bindFunctions?: (element: Element) => void; } +function processAndSetConfigs(text: string) { + const processed = preprocessDiagram(text); + configApi.reset(); + configApi.addDirective(processed.config); + return processed; +} + /** * Parse the text and validate the syntax. * @param text - The mermaid diagram definition. @@ -108,6 +113,9 @@ export interface RenderResult { async function parse(text: string, parseOptions?: ParseOptions): Promise { addDiagrams(); + + text = processAndSetConfigs(text).code; + try { await getDiagramFromText(text); } catch (error) { @@ -384,18 +392,8 @@ const render = async function ( ): Promise { addDiagrams(); - configApi.reset(); - - // We need to add the directives before creating the diagram. - // So extractFrontMatter is called twice. Once here and once in the diagram parser. - // This can be fixed in a future refactor. - extractFrontMatter(text, {}, configApi.addDirective); - - // Add Directives. - const graphInit = utils.detectInit(text); - if (graphInit) { - configApi.addDirective(graphInit); - } + const processed = processAndSetConfigs(text); + text = processed.code; const config = configApi.getConfig(); log.debug(config); @@ -405,15 +403,6 @@ const render = async function ( text = MAX_TEXTLENGTH_EXCEEDED_MSG; } - // clean up text CRLFs - text = text.replace(/\r\n?/g, '\n'); // parser problems on CRLF ignore all CR and leave LF;; - - // clean up html tags so that all attributes use single quotes, parser throws error on double quotes - text = text.replace( - /<(\w+)([^>]*)>/g, - (match, tag, attributes) => '<' + tag + attributes.replace(/="([^"]*)"/g, "='$1'") + '>' - ); - const idSelector = '#' + id; const iFrameID = 'i' + id; const iFrameID_selector = '#' + iFrameID; @@ -476,7 +465,7 @@ const render = async function ( let parseEncounteredException; try { - diag = await getDiagramFromText(text); + diag = await getDiagramFromText(text, { title: processed.title }); } catch (error) { diag = new Diagram('error'); parseEncounteredException = error; @@ -673,7 +662,6 @@ function addA11yInfo( export const mermaidAPI = Object.freeze({ render, parse, - parseDirective, getDiagramFromText, initialize, getConfig: configApi.getConfig, diff --git a/packages/mermaid/src/preprocess.ts b/packages/mermaid/src/preprocess.ts new file mode 100644 index 0000000000..3c33ce30f8 --- /dev/null +++ b/packages/mermaid/src/preprocess.ts @@ -0,0 +1,58 @@ +import { cleanupComments } from './diagram-api/comments.js'; +import { extractFrontMatter } from './diagram-api/frontmatter.js'; +import utils, { cleanAndMerge, removeDirectives } from './utils.js'; + +const cleanupText = (code: string) => { + return ( + code + // parser problems on CRLF ignore all CR and leave LF;; + .replace(/\r\n?/g, '\n') + // clean up html tags so that all attributes use single quotes, parser throws error on double quotes + .replace( + /<(\w+)([^>]*)>/g, + (match, tag, attributes) => '<' + tag + attributes.replace(/="([^"]*)"/g, "='$1'") + '>' + ) + ); +}; + +const processFrontmatter = (code: string) => { + const { text, metadata } = extractFrontMatter(code); + const { displayMode, title, config = {} } = metadata; + if (displayMode) { + // Needs to be supported for legacy reasons + if (!config.gantt) { + config.gantt = {}; + } + config.gantt.displayMode = displayMode; + } + return { title, config, text }; +}; + +const processDirectives = (code: string) => { + const initDirective = utils.detectInit(code) ?? {}; + const wrapDirectives = utils.detectDirective(code, 'wrap'); + if (Array.isArray(wrapDirectives)) { + initDirective.wrap = wrapDirectives.some(({ type }) => { + type === 'wrap'; + }); + } else if (wrapDirectives?.type === 'wrap') { + initDirective.wrap = true; + } + return { + text: removeDirectives(code), + directive: initDirective, + }; +}; + +export const preprocessDiagram = (code: string) => { + const cleanedCode = cleanupText(code); + const frontMatterResult = processFrontmatter(cleanedCode); + const directiveResult = processDirectives(frontMatterResult.text); + const config = cleanAndMerge(frontMatterResult.config, directiveResult.directive); + code = cleanupComments(directiveResult.text); + return { + code, + title: frontMatterResult.title, + config, + }; +}; diff --git a/packages/mermaid/src/utils.spec.ts b/packages/mermaid/src/utils.spec.ts index 271dc588c6..e1398efc73 100644 --- a/packages/mermaid/src/utils.spec.ts +++ b/packages/mermaid/src/utils.spec.ts @@ -1,10 +1,11 @@ import { vi } from 'vitest'; -import utils, { cleanAndMerge } from './utils.js'; +import utils, { cleanAndMerge, detectDirective } from './utils.js'; import assignWithDepth from './assignWithDepth.js'; import { detectType } from './diagram-api/detectType.js'; import { addDiagrams } from './diagram-api/diagram-orchestration.js'; import memoize from 'lodash-es/memoize.js'; import { MockedD3 } from './tests/MockedD3.js'; +import { preprocessDiagram } from './preprocess.js'; addDiagrams(); @@ -158,13 +159,38 @@ describe('when detecting chart type ', function () { const type = detectType(str); expect(type).toBe('flowchart'); }); + it('should handle a wrap directive', () => { + const wrap = { type: 'wrap', args: null }; + expect(detectDirective('%%{wrap}%%', 'wrap')).toEqual(wrap); + expect( + detectDirective( + `%%{ + wrap + }%%`, + 'wrap' + ) + ).toEqual(wrap); + expect( + detectDirective( + `%%{ + + wrap + + }%%`, + 'wrap' + ) + ).toEqual(wrap); + expect(detectDirective('%%{wrap:}%%', 'wrap')).toEqual(wrap); + expect(detectDirective('%%{wrap: }%%', 'wrap')).toEqual(wrap); + expect(detectDirective('graph', 'wrap')).not.toEqual(wrap); + }); it('should handle an initialize definition', function () { const str = ` %%{initialize: { 'logLevel': 0, 'theme': 'dark' }}%% sequenceDiagram Alice->Bob: hi`; const type = detectType(str); - const init = utils.detectInit(str); + const init = preprocessDiagram(str).config; expect(type).toBe('sequence'); expect(init).toEqual({ logLevel: 0, theme: 'dark' }); }); @@ -174,7 +200,7 @@ Alice->Bob: hi`; sequenceDiagram Alice->Bob: hi`; const type = detectType(str); - const init = utils.detectInit(str); + const init = preprocessDiagram(str).config; expect(type).toBe('sequence'); expect(init).toEqual({ logLevel: 0, theme: 'dark' }); }); @@ -184,7 +210,7 @@ Alice->Bob: hi`; sequenceDiagram Alice->Bob: hi`; const type = detectType(str); - const init = utils.detectInit(str); + const init = preprocessDiagram(str).config; expect(type).toBe('sequence'); expect(init).toEqual({ logLevel: 0, theme: 'dark', sequence: { wrap: true } }); }); @@ -199,7 +225,7 @@ Alice->Bob: hi`; sequenceDiagram Alice->Bob: hi`; const type = detectType(str); - const init = utils.detectInit(str); + const init = preprocessDiagram(str).config; expect(type).toBe('sequence'); expect(init).toEqual({ logLevel: 0, theme: 'dark' }); }); @@ -214,7 +240,7 @@ Alice->Bob: hi`; sequenceDiagram Alice->Bob: hi`; const type = detectType(str); - const init = utils.detectInit(str); + const init = preprocessDiagram(str).config; expect(type).toBe('sequence'); expect(init).toEqual({ logLevel: 0, theme: 'dark' }); }); diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts index 42b4ee67e8..70de197dad 100644 --- a/packages/mermaid/src/utils.ts +++ b/packages/mermaid/src/utils.ts @@ -25,7 +25,7 @@ import { select, } from 'd3'; import common from './diagrams/common/common.js'; -import { configKeys } from './defaultConfig.js'; +import { sanitizeDirective } from './utils/sanitizeDirective.js'; import { log } from './logger.js'; import { detectType } from './diagram-api/detectType.js'; import assignWithDepth from './assignWithDepth.js'; @@ -62,7 +62,6 @@ const d3CurveTypes = { const directiveWithoutOpen = /\s*(?:(\w+)(?=:):|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi; - /** * Detects the init config object from the text * @@ -197,6 +196,10 @@ export const detectDirective = function ( } }; +export const removeDirectives = function (text: string): string { + return text.replace(directiveRegex, ''); +}; + /** * Detects whether a substring in present in a given array * @@ -842,88 +845,6 @@ export const entityDecode = function (html: string): string { return unescape(decoder.textContent); }; -/** - * Sanitizes directive objects - * - * @param args - Directive's JSON - */ -export const sanitizeDirective = (args: unknown): void => { - log.debug('sanitizeDirective called with', args); - - // Return if not an object - if (typeof args !== 'object' || args == null) { - return; - } - - // Sanitize each element if an array - if (Array.isArray(args)) { - args.forEach((arg) => sanitizeDirective(arg)); - return; - } - - // Sanitize each key if an object - for (const key of Object.keys(args)) { - log.debug('Checking key', key); - if ( - key.startsWith('__') || - key.includes('proto') || - key.includes('constr') || - !configKeys.has(key) || - args[key] == null - ) { - log.debug('sanitize deleting key: ', key); - delete args[key]; - continue; - } - - // Recurse if an object - if (typeof args[key] === 'object') { - log.debug('sanitizing object', key); - sanitizeDirective(args[key]); - continue; - } - - const cssMatchers = ['themeCSS', 'fontFamily', 'altFontFamily']; - for (const cssKey of cssMatchers) { - if (key.includes(cssKey)) { - log.debug('sanitizing css option', key); - args[key] = sanitizeCss(args[key]); - } - } - } - - if (args.themeVariables) { - for (const k of Object.keys(args.themeVariables)) { - const val = args.themeVariables[k]; - if (val?.match && !val.match(/^[\d "#%(),.;A-Za-z]+$/)) { - args.themeVariables[k] = ''; - } - } - } - log.debug('After sanitization', args); -}; - -export const sanitizeCss = (str: string): string => { - let startCnt = 0; - let endCnt = 0; - - for (const element of str) { - if (startCnt < endCnt) { - return '{ /* ERROR: Unbalanced CSS */ }'; - } - if (element === '{') { - startCnt++; - } else if (element === '}') { - endCnt++; - } - } - if (startCnt !== endCnt) { - return '{ /* ERROR: Unbalanced CSS */ }'; - } - // Todo add more checks here - return str; -}; - export interface DetailedError { str: string; hash: any; @@ -1021,8 +942,6 @@ export default { runFunc, entityDecode, initIdGenerator, - sanitizeDirective, - sanitizeCss, insertTitle, parseFontSize, }; diff --git a/packages/mermaid/src/utils/sanitizeDirective.ts b/packages/mermaid/src/utils/sanitizeDirective.ts new file mode 100644 index 0000000000..9b7e7da5c0 --- /dev/null +++ b/packages/mermaid/src/utils/sanitizeDirective.ts @@ -0,0 +1,84 @@ +import { configKeys } from '../defaultConfig.js'; +import { log } from '../logger.js'; + +/** + * Sanitizes directive objects + * + * @param args - Directive's JSON + */ +export const sanitizeDirective = (args: any): void => { + log.debug('sanitizeDirective called with', args); + + // Return if not an object + if (typeof args !== 'object' || args == null) { + return; + } + + // Sanitize each element if an array + if (Array.isArray(args)) { + args.forEach((arg) => sanitizeDirective(arg)); + return; + } + + // Sanitize each key if an object + for (const key of Object.keys(args)) { + log.debug('Checking key', key); + if ( + key.startsWith('__') || + key.includes('proto') || + key.includes('constr') || + !configKeys.has(key) || + args[key] == null + ) { + log.debug('sanitize deleting key: ', key); + delete args[key]; + continue; + } + + // Recurse if an object + if (typeof args[key] === 'object') { + log.debug('sanitizing object', key); + sanitizeDirective(args[key]); + continue; + } + + const cssMatchers = ['themeCSS', 'fontFamily', 'altFontFamily']; + for (const cssKey of cssMatchers) { + if (key.includes(cssKey)) { + log.debug('sanitizing css option', key); + args[key] = sanitizeCss(args[key]); + } + } + } + + if (args.themeVariables) { + for (const k of Object.keys(args.themeVariables)) { + const val = args.themeVariables[k]; + if (val?.match && !val.match(/^[\d "#%(),.;A-Za-z]+$/)) { + args.themeVariables[k] = ''; + } + } + } + log.debug('After sanitization', args); +}; + +export const sanitizeCss = (str: string): string => { + let startCnt = 0; + let endCnt = 0; + + for (const element of str) { + if (startCnt < endCnt) { + return '{ /* ERROR: Unbalanced CSS */ }'; + } + if (element === '{') { + startCnt++; + } else if (element === '}') { + endCnt++; + } + } + if (startCnt !== endCnt) { + return '{ /* ERROR: Unbalanced CSS */ }'; + } + // Todo add more checks here + return str; +}; From f0883be0e3e481afbb9d21abf8f81c420b99227b Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 25 Aug 2023 12:55:35 +0530 Subject: [PATCH 04/37] refactor: Update DBs to remove directive handling --- packages/mermaid/src/diagrams/c4/c4Db.js | 6 ---- .../mermaid/src/diagrams/class/classDb.ts | 7 ---- packages/mermaid/src/diagrams/er/erDb.js | 6 ---- .../mermaid/src/diagrams/flowchart/flowDb.js | 6 ---- .../mermaid/src/diagrams/gantt/ganttDb.js | 6 ---- .../mermaid/src/diagrams/git/gitGraphAst.js | 6 ---- packages/mermaid/src/diagrams/pie/pie.spec.ts | 11 ------- packages/mermaid/src/diagrams/pie/pieDb.ts | 7 ---- packages/mermaid/src/diagrams/pie/pieTypes.ts | 3 +- .../parser/quadrant.jison.spec.ts | 32 +------------------ .../src/diagrams/quadrant-chart/quadrantDb.ts | 8 ----- .../src/diagrams/requirement/requirementDb.js | 6 ---- .../src/diagrams/sequence/sequenceDb.js | 6 ---- .../diagrams/sequence/sequenceDiagram.spec.js | 6 ---- .../mermaid/src/diagrams/state/stateDb.js | 6 ---- .../diagrams/state/stateDiagram-v2.spec.js | 10 ------ .../src/diagrams/state/stateDiagram.spec.js | 10 ------ .../src/diagrams/timeline/timeline.spec.js | 20 +----------- .../src/diagrams/timeline/timelineDb.js | 6 ---- .../src/diagrams/user-journey/journeyDb.js | 6 ---- 20 files changed, 3 insertions(+), 171 deletions(-) diff --git a/packages/mermaid/src/diagrams/c4/c4Db.js b/packages/mermaid/src/diagrams/c4/c4Db.js index 09dcc13cda..2dc014d9ed 100644 --- a/packages/mermaid/src/diagrams/c4/c4Db.js +++ b/packages/mermaid/src/diagrams/c4/c4Db.js @@ -1,4 +1,3 @@ -import mermaidAPI from '../../mermaidAPI.js'; import * as configApi from '../../config.js'; import { sanitizeText } from '../common/common.js'; import { setAccTitle, getAccTitle, getAccDescription, setAccDescription } from '../../commonDb.js'; @@ -33,10 +32,6 @@ export const setC4Type = function (c4TypeParam) { c4Type = sanitizedText; }; -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - //type, from, to, label, ?techn, ?descr, ?sprite, ?tags, $link export const addRel = function (type, from, to, label, techn, descr, sprite, tags, link) { // Don't allow label nulling @@ -816,7 +811,6 @@ export default { getAccTitle, getAccDescription, setAccDescription, - parseDirective, getConfig: () => configApi.getConfig().c4, clear, LINETYPE, diff --git a/packages/mermaid/src/diagrams/class/classDb.ts b/packages/mermaid/src/diagrams/class/classDb.ts index b14b1d07a1..11e80ffc88 100644 --- a/packages/mermaid/src/diagrams/class/classDb.ts +++ b/packages/mermaid/src/diagrams/class/classDb.ts @@ -5,7 +5,6 @@ import { log } from '../../logger.js'; import * as configApi from '../../config.js'; import common from '../common/common.js'; import utils from '../../utils.js'; -import mermaidAPI from '../../mermaidAPI.js'; import { setAccTitle, getAccTitle, @@ -37,11 +36,6 @@ let functions: any[] = []; const sanitizeText = (txt: string) => common.sanitizeText(txt, configApi.getConfig()); -export const parseDirective = function (statement: string, context: string, type: string) { - // @ts-ignore Don't wanna mess it up - mermaidAPI.parseDirective(this, statement, context, type); -}; - const splitClassNameAndType = function (id: string) { let genericType = ''; let className = id; @@ -456,7 +450,6 @@ export const addClassesToNamespace = function (id: string, classNames: string[]) }; export default { - parseDirective, setAccTitle, getAccTitle, getAccDescription, diff --git a/packages/mermaid/src/diagrams/er/erDb.js b/packages/mermaid/src/diagrams/er/erDb.js index 2f5116cbfe..559337ba90 100644 --- a/packages/mermaid/src/diagrams/er/erDb.js +++ b/packages/mermaid/src/diagrams/er/erDb.js @@ -1,5 +1,4 @@ import { log } from '../../logger.js'; -import mermaidAPI from '../../mermaidAPI.js'; import * as configApi from '../../config.js'; import { @@ -28,10 +27,6 @@ const Identification = { IDENTIFYING: 'IDENTIFYING', }; -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - const addEntity = function (name) { if (entities[name] === undefined) { entities[name] = { attributes: [] }; @@ -85,7 +80,6 @@ const clear = function () { export default { Cardinality, Identification, - parseDirective, getConfig: () => configApi.getConfig().er, addEntity, addAttributes, diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.js b/packages/mermaid/src/diagrams/flowchart/flowDb.js index ea8fa71d2b..6e161a33a7 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDb.js +++ b/packages/mermaid/src/diagrams/flowchart/flowDb.js @@ -2,7 +2,6 @@ import { select } from 'd3'; import utils from '../../utils.js'; import * as configApi from '../../config.js'; import common from '../common/common.js'; -import mermaidAPI from '../../mermaidAPI.js'; import { log } from '../../logger.js'; import { setAccTitle, @@ -34,10 +33,6 @@ let funs = []; const sanitizeText = (txt) => common.sanitizeText(txt, config); -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - /** * Function to lookup domId from id in the graph definition. * @@ -771,7 +766,6 @@ export const lex = { firstGraph, }; export default { - parseDirective, defaultConfig: () => configApi.defaultConfig.flowchart, setAccTitle, getAccTitle, diff --git a/packages/mermaid/src/diagrams/gantt/ganttDb.js b/packages/mermaid/src/diagrams/gantt/ganttDb.js index da838f8392..20e1ad70a0 100644 --- a/packages/mermaid/src/diagrams/gantt/ganttDb.js +++ b/packages/mermaid/src/diagrams/gantt/ganttDb.js @@ -6,7 +6,6 @@ import dayjsAdvancedFormat from 'dayjs/plugin/advancedFormat.js'; import { log } from '../../logger.js'; import * as configApi from '../../config.js'; import utils from '../../utils.js'; -import mermaidAPI from '../../mermaidAPI.js'; import { setAccTitle, @@ -42,10 +41,6 @@ let weekday = 'sunday'; // The serial order of the task in the script let lastOrder = 0; -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - export const clear = function () { sections = []; tasks = []; @@ -730,7 +725,6 @@ export const bindFunctions = function (element) { }; export default { - parseDirective, getConfig: () => configApi.getConfig().gantt, clear, setDateFormat, diff --git a/packages/mermaid/src/diagrams/git/gitGraphAst.js b/packages/mermaid/src/diagrams/git/gitGraphAst.js index 416479d151..5b34c6b656 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphAst.js +++ b/packages/mermaid/src/diagrams/git/gitGraphAst.js @@ -1,6 +1,5 @@ import { log } from '../../logger.js'; import { random } from '../../utils.js'; -import mermaidAPI from '../../mermaidAPI.js'; import * as configApi from '../../config.js'; import { getConfig } from '../../config.js'; import common from '../common/common.js'; @@ -33,10 +32,6 @@ function getId() { return random({ length: 7 }); } -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - // /** // * @param currentCommit // * @param otherCommit @@ -507,7 +502,6 @@ export const commitType = { }; export default { - parseDirective, getConfig: () => configApi.getConfig().gitGraph, setDirection, setOptions, diff --git a/packages/mermaid/src/diagrams/pie/pie.spec.ts b/packages/mermaid/src/diagrams/pie/pie.spec.ts index 7c8e0809a3..564e12f0f1 100644 --- a/packages/mermaid/src/diagrams/pie/pie.spec.ts +++ b/packages/mermaid/src/diagrams/pie/pie.spec.ts @@ -62,17 +62,6 @@ describe('pie', () => { expect(sections['bat']).toBe(40); }); - it('should handle simple pie with a directive', () => { - parser.parse(`%%{init: {'logLevel':0}}%% - pie - "ash" : 60 - "bat" : 40 - `); - const sections = db.getSections(); - expect(sections['ash']).toBe(60); - expect(sections['bat']).toBe(40); - }); - it('should handle simple pie with a title', () => { parser.parse(`pie title a 60/40 pie "ash" : 60 diff --git a/packages/mermaid/src/diagrams/pie/pieDb.ts b/packages/mermaid/src/diagrams/pie/pieDb.ts index dbe50f08a0..2a0dc16f31 100644 --- a/packages/mermaid/src/diagrams/pie/pieDb.ts +++ b/packages/mermaid/src/diagrams/pie/pieDb.ts @@ -1,5 +1,4 @@ import { log } from '../../logger.js'; -import { parseDirective as _parseDirective } from '../../directiveUtils.js'; import { getConfig as commonGetConfig } from '../../config.js'; import { sanitizeText } from '../common/common.js'; import { @@ -11,7 +10,6 @@ import { setAccDescription, clear as commonClear, } from '../../commonDb.js'; -import type { ParseDirectiveDefinition } from '../../diagram-api/types.js'; import type { PieFields, PieDB, Sections } from './pieTypes.js'; import type { RequiredDeep } from 'type-fest'; import type { PieDiagramConfig } from '../../config.type.js'; @@ -31,10 +29,6 @@ const config: Required = structuredClone(DEFAULT_PIE_CONFIG); const getConfig = (): Required => structuredClone(config); -const parseDirective: ParseDirectiveDefinition = (statement, context, type) => { - _parseDirective(this, statement, context, type); -}; - const clear = (): void => { sections = structuredClone(DEFAULT_PIE_DB.sections); showData = DEFAULT_PIE_DB.showData; @@ -67,7 +61,6 @@ const getShowData = (): boolean => showData; export const db: PieDB = { getConfig, - parseDirective, clear, setDiagramTitle, getDiagramTitle, diff --git a/packages/mermaid/src/diagrams/pie/pieTypes.ts b/packages/mermaid/src/diagrams/pie/pieTypes.ts index 67fb1dca25..6ba3ab92e5 100644 --- a/packages/mermaid/src/diagrams/pie/pieTypes.ts +++ b/packages/mermaid/src/diagrams/pie/pieTypes.ts @@ -1,5 +1,5 @@ import type { PieDiagramConfig } from '../../config.type.js'; -import type { DiagramDB, ParseDirectiveDefinition } from '../../diagram-api/types.js'; +import type { DiagramDB } from '../../diagram-api/types.js'; export interface PieFields { sections: Sections; @@ -46,7 +46,6 @@ export interface PieDB extends DiagramDB { getConfig: () => Required; // common db - parseDirective: ParseDirectiveDefinition; clear: () => void; setDiagramTitle: (title: string) => void; getDiagramTitle: () => string; diff --git a/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison.spec.ts b/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison.spec.ts index faa9281f0d..d10cb21340 100644 --- a/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison.spec.ts +++ b/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison.spec.ts @@ -19,7 +19,6 @@ const mockDB: Record> = { setYAxisTopText: vi.fn(), setYAxisBottomText: vi.fn(), setDiagramTitle: vi.fn(), - parseDirective: vi.fn(), addPoint: vi.fn(), }; @@ -45,23 +44,6 @@ describe('Testing quadrantChart jison file', () => { expect(parserFnConstructor(str)).not.toThrow(); }); - it('should be able to parse directive', () => { - const str = - '%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600} } }%% \n quadrantChart'; - expect(parserFnConstructor(str)).not.toThrow(); - expect(mockDB.parseDirective.mock.calls[0]).toEqual(['%%{', 'open_directive']); - expect(mockDB.parseDirective.mock.calls[1]).toEqual(['init', 'type_directive']); - expect(mockDB.parseDirective.mock.calls[2]).toEqual([ - '{"quadrantChart": {"chartWidth": 600, "chartHeight": 600} }', - 'arg_directive', - ]); - expect(mockDB.parseDirective.mock.calls[3]).toEqual([ - '}%%', - 'close_directive', - 'quadrantChart', - ]); - }); - it('should be able to parse xAxis text', () => { let str = 'quadrantChart\nx-axis urgent --> not urgent'; expect(parserFnConstructor(str)).not.toThrow(); @@ -243,8 +225,7 @@ describe('Testing quadrantChart jison file', () => { }); it('should be able to parse the whole chart', () => { - const str = `%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600} } }%% - quadrantChart + const str = `quadrantChart title Analytics and Business Intelligence Platforms x-axis "Completeness of Vision ❤" --> "x-axis-2" y-axis Ability to Execute --> "y-axis-2" @@ -258,17 +239,6 @@ describe('Testing quadrantChart jison file', () => { Incorta: [0.20, 0.30]`; expect(parserFnConstructor(str)).not.toThrow(); - expect(mockDB.parseDirective.mock.calls[0]).toEqual(['%%{', 'open_directive']); - expect(mockDB.parseDirective.mock.calls[1]).toEqual(['init', 'type_directive']); - expect(mockDB.parseDirective.mock.calls[2]).toEqual([ - '{"quadrantChart": {"chartWidth": 600, "chartHeight": 600} }', - 'arg_directive', - ]); - expect(mockDB.parseDirective.mock.calls[3]).toEqual([ - '}%%', - 'close_directive', - 'quadrantChart', - ]); expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({ text: 'Completeness of Vision ❤', type: 'text', diff --git a/packages/mermaid/src/diagrams/quadrant-chart/quadrantDb.ts b/packages/mermaid/src/diagrams/quadrant-chart/quadrantDb.ts index c0c0f4c8ac..4466f9fa4b 100644 --- a/packages/mermaid/src/diagrams/quadrant-chart/quadrantDb.ts +++ b/packages/mermaid/src/diagrams/quadrant-chart/quadrantDb.ts @@ -1,5 +1,3 @@ -import { log } from '../../logger.js'; -import mermaidAPI from '../../mermaidAPI.js'; import * as configApi from '../../config.js'; import { sanitizeText } from '../common/common.js'; import { @@ -94,11 +92,6 @@ function getQuadrantData() { return quadrantBuilder.build(); } -export const parseDirective = function (statement: string, context: string, type: string) { - // @ts-ignore: TODO Fix ts errors - mermaidAPI.parseDirective(this, statement, context, type); -}; - const clear = function () { quadrantBuilder.clear(); commonClear(); @@ -117,7 +110,6 @@ export default { setYAxisBottomText, addPoint, getQuadrantData, - parseDirective, clear, setAccTitle, getAccTitle, diff --git a/packages/mermaid/src/diagrams/requirement/requirementDb.js b/packages/mermaid/src/diagrams/requirement/requirementDb.js index 1d0a3e2e12..03629aeac8 100644 --- a/packages/mermaid/src/diagrams/requirement/requirementDb.js +++ b/packages/mermaid/src/diagrams/requirement/requirementDb.js @@ -1,6 +1,5 @@ import * as configApi from '../../config.js'; import { log } from '../../logger.js'; -import mermaidAPI from '../../mermaidAPI.js'; import { setAccTitle, @@ -48,10 +47,6 @@ const Relationships = { TRACES: 'traces', }; -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - const addRequirement = (name, type) => { if (requirements[name] === undefined) { requirements[name] = { @@ -149,7 +144,6 @@ export default { VerifyType, Relationships, - parseDirective, getConfig: () => configApi.getConfig().req, addRequirement, diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.js b/packages/mermaid/src/diagrams/sequence/sequenceDb.js index b5d68fb9f5..ff8a7c0ae8 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.js @@ -1,4 +1,3 @@ -import mermaidAPI from '../../mermaidAPI.js'; import * as configApi from '../../config.js'; import { log } from '../../logger.js'; import { sanitizeText } from '../common/common.js'; @@ -25,10 +24,6 @@ let currentBox = undefined; let lastCreated = undefined; let lastDestroyed = undefined; -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - export const addBox = function (data) { boxes.push({ name: data.text, @@ -619,7 +614,6 @@ export default { getBoxes, getDiagramTitle, setDiagramTitle, - parseDirective, getConfig: () => configApi.getConfig().sequence, clear, parseMessage, diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index 0b84fbe356..5e0943699c 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -2051,9 +2051,7 @@ describe('when rendering a sequenceDiagram with directives', () => { it('should handle one actor, when theme is dark and logLevel is 1 DX1 (dfg1)', async () => { const str = ` -%%{init: { "theme": "dark", "logLevel": 1 } }%% sequenceDiagram -%%{wrap}%% participant Alice `; diagram = new Diagram(str); @@ -2062,8 +2060,6 @@ participant Alice const { bounds, models } = diagram.renderer.bounds.getBounds(); const mermaid = mermaidAPI.getConfig(); - expect(mermaid.theme).toBe('dark'); - expect(mermaid.logLevel).toBe(1); expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0); expect(bounds.starty).toBe(0); @@ -2073,7 +2069,6 @@ participant Alice }); it('should handle one actor, when logLevel is 3 (dfg0)', async () => { const str = ` -%%{initialize: { "logLevel": 3 }}%% sequenceDiagram participant Alice `; @@ -2083,7 +2078,6 @@ participant Alice const { bounds, models } = diagram.renderer.bounds.getBounds(); const mermaid = mermaidAPI.getConfig(); - expect(mermaid.logLevel).toBe(3); expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0); expect(bounds.starty).toBe(0); diff --git a/packages/mermaid/src/diagrams/state/stateDb.js b/packages/mermaid/src/diagrams/state/stateDb.js index d9c789a99b..56a0a4cc6d 100644 --- a/packages/mermaid/src/diagrams/state/stateDb.js +++ b/packages/mermaid/src/diagrams/state/stateDb.js @@ -1,6 +1,5 @@ import { log } from '../../logger.js'; import { generateId } from '../../utils.js'; -import mermaidAPI from '../../mermaidAPI.js'; import common from '../common/common.js'; import * as configApi from '../../config.js'; import { @@ -77,10 +76,6 @@ export const relationType = { const clone = (o) => JSON.parse(JSON.stringify(o)); -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - const setRootDoc = (o) => { log.info('Setting root doc', o); // rootDoc = { id: 'root', doc: o }; @@ -547,7 +542,6 @@ const setDirection = (dir) => { const trimColon = (str) => (str && str[0] === ':' ? str.substr(1).trim() : str.trim()); export default { - parseDirective, getConfig: () => configApi.getConfig().state, addState, clear, diff --git a/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js b/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js index e64ecfdf89..c387ff7b3d 100644 --- a/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js +++ b/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js @@ -55,16 +55,6 @@ describe('state diagram V2, ', function () { const title = stateDb.getAccTitle(); expect(title).toBe('a simple title of the diagram'); }); - it('simple with directive', function () { - const str = `%%{init: {'logLevel': 0 }}%% - stateDiagram-v2\n - State1 : this is another string - [*] --> State1 - State1 --> [*] - `; - - parser.parse(str); - }); it('should handle relation definitions', function () { const str = `stateDiagram-v2\n [*] --> State1 diff --git a/packages/mermaid/src/diagrams/state/stateDiagram.spec.js b/packages/mermaid/src/diagrams/state/stateDiagram.spec.js index e6e470140c..536031c815 100644 --- a/packages/mermaid/src/diagrams/state/stateDiagram.spec.js +++ b/packages/mermaid/src/diagrams/state/stateDiagram.spec.js @@ -66,16 +66,6 @@ describe('state diagram, ', function () { const title = stateDb.getAccTitle(); expect(title).toBe('a simple title of the diagram'); }); - it('simple with directive', function () { - const str = `%%{init: {'logLevel': 0 }}%% - stateDiagram\n - State1 : this is another string - [*] --> State1 - State1 --> [*] - `; - - parser.parse(str); - }); it('should handle relation definitions', function () { const str = `stateDiagram\n [*] --> State1 diff --git a/packages/mermaid/src/diagrams/timeline/timeline.spec.js b/packages/mermaid/src/diagrams/timeline/timeline.spec.js index 1f6a960244..a66a7f2c12 100644 --- a/packages/mermaid/src/diagrams/timeline/timeline.spec.js +++ b/packages/mermaid/src/diagrams/timeline/timeline.spec.js @@ -1,26 +1,8 @@ import { parser as timeline } from './parser/timeline.jison'; import * as timelineDB from './timelineDb.js'; -// import { injectUtils } from './mermaidUtils.js'; import * as _commonDb from '../../commonDb.js'; -import { parseDirective as _parseDirective } from '../../directiveUtils.js'; -import { - log, - setLogLevel, - getConfig, - sanitizeText, - setupGraphViewBox, -} from '../../diagram-api/diagramAPI.js'; - -// injectUtils( -// log, -// setLogLevel, -// getConfig, -// sanitizeText, -// setupGraphViewBox, -// _commonDb, -// _parseDirective -// ); +import { setLogLevel } from '../../diagram-api/diagramAPI.js'; describe('when parsing a timeline ', function () { beforeEach(function () { diff --git a/packages/mermaid/src/diagrams/timeline/timelineDb.js b/packages/mermaid/src/diagrams/timeline/timelineDb.js index 337cfe4416..91cee51759 100644 --- a/packages/mermaid/src/diagrams/timeline/timelineDb.js +++ b/packages/mermaid/src/diagrams/timeline/timelineDb.js @@ -1,4 +1,3 @@ -import { parseDirective as _parseDirective } from '../../directiveUtils.js'; import * as commonDb from '../../commonDb.js'; let currentSection = ''; let currentTaskId = 0; @@ -9,10 +8,6 @@ const rawTasks = []; export const getCommonDb = () => commonDb; -export const parseDirective = (statement, context, type) => { - _parseDirective(this, statement, context, type); -}; - export const clear = function () { sections.length = 0; tasks.length = 0; @@ -104,5 +99,4 @@ export default { addTask, addTaskOrg, addEvent, - parseDirective, }; diff --git a/packages/mermaid/src/diagrams/user-journey/journeyDb.js b/packages/mermaid/src/diagrams/user-journey/journeyDb.js index d4f34e9422..5812acffe1 100644 --- a/packages/mermaid/src/diagrams/user-journey/journeyDb.js +++ b/packages/mermaid/src/diagrams/user-journey/journeyDb.js @@ -1,4 +1,3 @@ -import mermaidAPI from '../../mermaidAPI.js'; import * as configApi from '../../config.js'; import { setAccTitle, @@ -16,10 +15,6 @@ const sections = []; const tasks = []; const rawTasks = []; -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - export const clear = function () { sections.length = 0; tasks.length = 0; @@ -118,7 +113,6 @@ const getActors = function () { }; export default { - parseDirective, getConfig: () => configApi.getConfig().journey, clear, setDiagramTitle, From 1e0918c2ffbb213fb60bcd010f922d7ebae9553c Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 25 Aug 2023 12:55:58 +0530 Subject: [PATCH 05/37] refactor: Remove directives from grammar --- .../src/diagrams/c4/parser/c4Diagram.jison | 30 -- .../diagrams/class/parser/classDiagram.jison | 30 -- .../src/diagrams/er/parser/erDiagram.jison | 31 +- .../src/diagrams/flowchart/parser/flow.jison | 36 +-- .../src/diagrams/gantt/parser/gantt.jison | 32 +- .../src/diagrams/git/gitGraphParser.spec | 288 ------------------ .../src/diagrams/git/parser/gitGraph.jison | 31 -- .../mermaid/src/diagrams/pie/parser/pie.jison | 32 -- .../quadrant-chart/parser/quadrant.jison | 32 -- .../parser/requirementDiagram.jison | 24 +- .../sequence/parser/sequenceDiagram.jison | 32 +- .../diagrams/state/parser/stateDiagram.jison | 35 +-- .../diagrams/timeline/parser/timeline.jison | 33 -- .../user-journey/parser/journey.jison | 32 -- 14 files changed, 8 insertions(+), 690 deletions(-) delete mode 100644 packages/mermaid/src/diagrams/git/gitGraphParser.spec diff --git a/packages/mermaid/src/diagrams/c4/parser/c4Diagram.jison b/packages/mermaid/src/diagrams/c4/parser/c4Diagram.jison index 1dfa69ef1f..a6bd8a6ec2 100644 --- a/packages/mermaid/src/diagrams/c4/parser/c4Diagram.jison +++ b/packages/mermaid/src/diagrams/c4/parser/c4Diagram.jison @@ -72,25 +72,16 @@ %x string_kv_key %x string_kv_value -%x open_directive -%x type_directive -%x arg_directive -%x close_directive %x acc_title %x acc_descr %x acc_descr_multiline %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } .*direction\s+TB[^\n]* return 'direction_tb'; .*direction\s+BT[^\n]* return 'direction_bt'; .*direction\s+RL[^\n]* return 'direction_rl'; .*direction\s+LR[^\n]* return 'direction_lr'; -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; "title"\s[^#\n;]+ return 'title'; @@ -207,7 +198,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} start : mermaidDoc | direction - | directive start ; direction @@ -225,26 +215,6 @@ mermaidDoc : graphConfig ; -directive - : openDirective typeDirective closeDirective NEWLINE - | openDirective typeDirective ':' argDirective closeDirective NEWLINE - ; - -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'c4Context'); } - ; graphConfig : C4_CONTEXT NEWLINE statements EOF {yy.setC4Type($1)} diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison index 8fdfced75b..6066fdc352 100644 --- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison +++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison @@ -13,9 +13,6 @@ %x href %x callback_name %x callback_args -%x open_directive -%x type_directive -%x arg_directive %x acc_title %x acc_descr %x acc_descr_multiline @@ -24,15 +21,10 @@ %x namespace %x namespace-body %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } .*direction\s+TB[^\n]* return 'direction_tb'; .*direction\s+BT[^\n]* return 'direction_bt'; .*direction\s+RL[^\n]* return 'direction_rl'; .*direction\s+LR[^\n]* return 'direction_lr'; -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; \%\%(?!\{)*[^\n]*(\r?\n?)+ /* skip comments */ \%\%[^\n]*(\r?\n)* /* skip comments */ accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } @@ -220,7 +212,6 @@ line was introduced with 'click'. start : mermaidDoc - | directive start | statements ; @@ -239,27 +230,6 @@ mermaidDoc : graphConfig ; -directive - : openDirective typeDirective closeDirective NEWLINE - | openDirective typeDirective ':' argDirective closeDirective NEWLINE - ; - -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'class'); } - ; - graphConfig : CLASS_DIAGRAM NEWLINE statements EOF ; diff --git a/packages/mermaid/src/diagrams/er/parser/erDiagram.jison b/packages/mermaid/src/diagrams/er/parser/erDiagram.jison index 0a2549268d..4338983ec1 100644 --- a/packages/mermaid/src/diagrams/er/parser/erDiagram.jison +++ b/packages/mermaid/src/diagrams/er/parser/erDiagram.jison @@ -1,7 +1,7 @@ %lex %options case-insensitive -%x open_directive type_directive arg_directive block +%x block %x acc_title %x acc_descr %x acc_descr_multiline @@ -14,11 +14,6 @@ accDescr\s*":"\s* { this.begin("ac accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} [\}] { this.popState(); } [^\}]* return "acc_descr_multiline_value"; -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; [\n]+ return 'NEWLINE'; \s+ /* skip whitespace */ [\s]+ return 'SPACE'; @@ -75,7 +70,6 @@ o\{ return 'ZERO_OR_MORE'; start : 'ER_DIAGRAM' document 'EOF' { /*console.log('finished parsing');*/ } - | directive start ; document @@ -90,14 +84,9 @@ line | EOF { $$=[];} ; -directive - : openDirective typeDirective closeDirective 'NEWLINE' - | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE' - ; statement - : directive - | entityName relSpec entityName ':' role + : entityName relSpec entityName ':' role { yy.addEntity($1); yy.addEntity($3); @@ -185,20 +174,4 @@ role | 'ALPHANUM' { $$ = $1; } ; -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'er'); } - ; - %% diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison index 8d746f8082..6dad36d25f 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison +++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison @@ -23,17 +23,8 @@ %x href %x callbackname %x callbackargs -%x open_directive -%x type_directive -%x arg_directive -%x close_directive %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } (?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } @@ -272,35 +263,10 @@ that id. %% /* language grammar */ start - : mermaidDoc - | directive start - ; - -directive - : openDirective typeDirective closeDirective separator - | openDirective typeDirective ':' argDirective closeDirective separator - ; - -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($type_directive, 'type_directive'); } - ; - -argDirective - : arg_directive { $arg_directive = $arg_directive.trim().replace(/'/g, '"'); yy.parseDirective($arg_directive, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'flowchart'); } - ; - -mermaidDoc : graphConfig document ; + document : /* empty */ { $$ = [];} diff --git a/packages/mermaid/src/diagrams/gantt/parser/gantt.jison b/packages/mermaid/src/diagrams/gantt/parser/gantt.jison index f7fd40c1bc..b4daab5dca 100644 --- a/packages/mermaid/src/diagrams/gantt/parser/gantt.jison +++ b/packages/mermaid/src/diagrams/gantt/parser/gantt.jison @@ -11,19 +11,11 @@ %x href %x callbackname %x callbackargs -%x open_directive -%x type_directive -%x arg_directive -%x close_directive %x acc_title %x acc_descr %x acc_descr_multiline %% \%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } (?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } @@ -112,8 +104,7 @@ weekday\s+sunday return 'weekday_sunday' %% /* language grammar */ start - : directive start - | gantt document 'EOF' { return $2; } + : gantt document 'EOF' { return $2; } ; document @@ -155,13 +146,8 @@ statement | section { yy.addSection($1.substr(8));$$=$1.substr(8); } | clickStatement | taskTxt taskData {yy.addTask($1,$2);$$='task';} - | directive ; -directive - : openDirective typeDirective closeDirective 'NL' - | openDirective typeDirective ':' argDirective closeDirective 'NL' - ; /* click allows any combination of href and call. @@ -192,20 +178,4 @@ clickStatementDebug | click href {$$=$1 + ' ' + $2;} ; -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'gantt'); } - ; - %% diff --git a/packages/mermaid/src/diagrams/git/gitGraphParser.spec b/packages/mermaid/src/diagrams/git/gitGraphParser.spec deleted file mode 100644 index 0a8256c5b7..0000000000 --- a/packages/mermaid/src/diagrams/git/gitGraphParser.spec +++ /dev/null @@ -1,288 +0,0 @@ - -// Todo reintroduce without cryptoRandomString -import gitGraphAst from './gitGraphAst'; -import { parser } from './parser/gitGraph'; -import randomString from 'crypto-random-string'; -import cryptoRandomString from 'crypto-random-string'; - -jest.mock('crypto-random-string'); - -describe('when parsing a gitGraph', function() { - let randomNumber; - beforeEach(function() { - parser.yy = gitGraphAst; - parser.yy.clear(); - randomNumber = 0; - cryptoRandomString.mockImplementation(() => { - randomNumber = randomNumber + 1; - return String(randomNumber); - }); - }); - afterEach(function() { - cryptoRandomString.mockReset(); - }); - it('should handle a gitGraph definition', function() { - const str = 'gitGraph:\n' + 'commit\n'; - - parser.parse(str); - const commits = parser.yy.getCommits(); - - expect(Object.keys(commits).length).toBe(1); - expect(parser.yy.getCurrentBranch()).toBe('master'); - expect(parser.yy.getDirection()).toBe('LR'); - expect(Object.keys(parser.yy.getBranches()).length).toBe(1); - }); - - it('should handle a gitGraph definition with empty options', function() { - const str = 'gitGraph:\n' + 'options\n' + 'end\n' + 'commit\n'; - - parser.parse(str); - const commits = parser.yy.getCommits(); - - expect(parser.yy.getOptions()).toEqual({}); - expect(Object.keys(commits).length).toBe(1); - expect(parser.yy.getCurrentBranch()).toBe('master'); - expect(parser.yy.getDirection()).toBe('LR'); - expect(Object.keys(parser.yy.getBranches()).length).toBe(1); - }); - - it('should handle a gitGraph definition with valid options', function() { - const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"}\n' + 'end\n' + 'commit\n'; - - parser.parse(str); - const commits = parser.yy.getCommits(); - expect(parser.yy.getOptions()['key']).toBe('value'); - expect(Object.keys(commits).length).toBe(1); - expect(parser.yy.getCurrentBranch()).toBe('master'); - expect(parser.yy.getDirection()).toBe('LR'); - expect(Object.keys(parser.yy.getBranches()).length).toBe(1); - }); - - it('should not fail on a gitGraph with malformed json', function() { - const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"\n' + 'end\n' + 'commit\n'; - - parser.parse(str); - const commits = parser.yy.getCommits(); - expect(Object.keys(commits).length).toBe(1); - expect(parser.yy.getCurrentBranch()).toBe('master'); - expect(parser.yy.getDirection()).toBe('LR'); - expect(Object.keys(parser.yy.getBranches()).length).toBe(1); - }); - - it('should handle set direction', function() { - const str = 'gitGraph BT:\n' + 'commit\n'; - - parser.parse(str); - const commits = parser.yy.getCommits(); - - expect(Object.keys(commits).length).toBe(1); - expect(parser.yy.getCurrentBranch()).toBe('master'); - expect(parser.yy.getDirection()).toBe('BT'); - expect(Object.keys(parser.yy.getBranches()).length).toBe(1); - }); - - it('should checkout a branch', function() { - const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n'; - - parser.parse(str); - const commits = parser.yy.getCommits(); - - expect(Object.keys(commits).length).toBe(0); - expect(parser.yy.getCurrentBranch()).toBe('new'); - }); - - it('should add commits to checked out branch', function() { - const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n' + 'commit\n' + 'commit\n'; - - parser.parse(str); - const commits = parser.yy.getCommits(); - - expect(Object.keys(commits).length).toBe(2); - expect(parser.yy.getCurrentBranch()).toBe('new'); - const branchCommit = parser.yy.getBranches()['new']; - expect(branchCommit).not.toBeNull(); - expect(commits[branchCommit].parent).not.toBeNull(); - }); - it('should handle commit with args', function() { - const str = 'gitGraph:\n' + 'commit "a commit"\n'; - - parser.parse(str); - const commits = parser.yy.getCommits(); - - expect(Object.keys(commits).length).toBe(1); - const key = Object.keys(commits)[0]; - expect(commits[key].message).toBe('a commit'); - expect(parser.yy.getCurrentBranch()).toBe('master'); - }); - - it('should reset a branch', function() { - const str = - 'gitGraph:\n' + - 'commit\n' + - 'commit\n' + - 'branch newbranch\n' + - 'checkout newbranch\n' + - 'commit\n' + - 'reset master\n'; - - parser.parse(str); - - const commits = parser.yy.getCommits(); - expect(Object.keys(commits).length).toBe(3); - expect(parser.yy.getCurrentBranch()).toBe('newbranch'); - expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']); - expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']); - }); - - it('reset can take an argument', function() { - const str = - 'gitGraph:\n' + - 'commit\n' + - 'commit\n' + - 'branch newbranch\n' + - 'checkout newbranch\n' + - 'commit\n' + - 'reset master^\n'; - - parser.parse(str); - - const commits = parser.yy.getCommits(); - expect(Object.keys(commits).length).toBe(3); - expect(parser.yy.getCurrentBranch()).toBe('newbranch'); - const master = commits[parser.yy.getBranches()['master']]; - expect(parser.yy.getHead().id).toEqual(master.parent); - }); - - it('should handle fast forwardable merges', function() { - const str = - 'gitGraph:\n' + - 'commit\n' + - 'branch newbranch\n' + - 'checkout newbranch\n' + - 'commit\n' + - 'commit\n' + - 'checkout master\n' + - 'merge newbranch\n'; - - parser.parse(str); - - const commits = parser.yy.getCommits(); - expect(Object.keys(commits).length).toBe(3); - expect(parser.yy.getCurrentBranch()).toBe('master'); - expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']); - expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']); - }); - - it('should handle cases when merge is a noop', function() { - const str = - 'gitGraph:\n' + - 'commit\n' + - 'branch newbranch\n' + - 'checkout newbranch\n' + - 'commit\n' + - 'commit\n' + - 'merge master\n'; - - parser.parse(str); - - const commits = parser.yy.getCommits(); - expect(Object.keys(commits).length).toBe(3); - expect(parser.yy.getCurrentBranch()).toBe('newbranch'); - expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['master']); - expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']); - }); - - it('should handle merge with 2 parents', function() { - const str = - 'gitGraph:\n' + - 'commit\n' + - 'branch newbranch\n' + - 'checkout newbranch\n' + - 'commit\n' + - 'commit\n' + - 'checkout master\n' + - 'commit\n' + - 'merge newbranch\n'; - - parser.parse(str); - - const commits = parser.yy.getCommits(); - expect(Object.keys(commits).length).toBe(5); - expect(parser.yy.getCurrentBranch()).toBe('master'); - expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['master']); - expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['master']); - }); - - it('should handle ff merge when history walk has two parents (merge commit)', function() { - const str = - 'gitGraph:\n' + - 'commit\n' + - 'branch newbranch\n' + - 'checkout newbranch\n' + - 'commit\n' + - 'commit\n' + - 'checkout master\n' + - 'commit\n' + - 'merge newbranch\n' + - 'commit\n' + - 'checkout newbranch\n' + - 'merge master\n'; - - parser.parse(str); - - const commits = parser.yy.getCommits(); - expect(Object.keys(commits).length).toBe(6); - expect(parser.yy.getCurrentBranch()).toBe('newbranch'); - expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']); - expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['master']); - - parser.yy.prettyPrint(); - }); - - it('should generate a secure random ID for commits', function() { - const str = 'gitGraph:\n' + 'commit\n' + 'commit\n'; - const EXPECTED_LENGTH = 7; - const EXPECTED_CHARACTERS = '0123456789abcdef'; - - let idCount = 0; - randomString.mockImplementation(options => { - if ( - options.length === EXPECTED_LENGTH && - options.characters === EXPECTED_CHARACTERS && - Object.keys(options).length === 2 - ) { - const id = `abcdef${idCount}`; - idCount += 1; - return id; - } - return 'unexpected-ID'; - }); - - parser.parse(str); - const commits = parser.yy.getCommits(); - - expect(Object.keys(commits)).toEqual(['abcdef0', 'abcdef1']); - Object.keys(commits).forEach(key => { - expect(commits[key].id).toEqual(key); - }); - }); - - it('should generate an array of known branches', function() { - const str = - 'gitGraph:\n' + - 'commit\n' + - 'branch b1\n' + - 'checkout b1\n' + - 'commit\n' + - 'commit\n' + - 'branch b2\n'; - - parser.parse(str); - const branches = gitGraphAst.getBranchesAsObjArray(); - - expect(branches).toHaveLength(3); - expect(branches[0]).toHaveProperty('name', 'master'); - expect(branches[1]).toHaveProperty('name', 'b1'); - expect(branches[2]).toHaveProperty('name', 'b2'); - }); -}); diff --git a/packages/mermaid/src/diagrams/git/parser/gitGraph.jison b/packages/mermaid/src/diagrams/git/parser/gitGraph.jison index 9ff5623f83..2297db17cd 100644 --- a/packages/mermaid/src/diagrams/git/parser/gitGraph.jison +++ b/packages/mermaid/src/diagrams/git/parser/gitGraph.jison @@ -9,10 +9,6 @@ %x string %x options -%x open_directive -%x type_directive -%x arg_directive -%x close_directive %x acc_title %x acc_descr %x acc_descr_multiline @@ -20,11 +16,6 @@ %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } (?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } @@ -76,7 +67,6 @@ checkout(?=\s|$) return 'CHECKOUT'; start : eol start - | directive start | GG document EOF{ return $3; } | GG ':' document EOF{ return $3; } | GG DIR ':' document EOF {yy.setDirection($2); return $4;} @@ -240,27 +230,6 @@ commitType | HIGHLIGHT { $$=yy.commitType.HIGHLIGHT;} ; -directive - : openDirective typeDirective closeDirective - | openDirective typeDirective ':' argDirective closeDirective - ; - -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'gitGraph'); } - ; - ref : ID | STR diff --git a/packages/mermaid/src/diagrams/pie/parser/pie.jison b/packages/mermaid/src/diagrams/pie/parser/pie.jison index e98638aa8b..d1f516e755 100644 --- a/packages/mermaid/src/diagrams/pie/parser/pie.jison +++ b/packages/mermaid/src/diagrams/pie/parser/pie.jison @@ -8,19 +8,10 @@ %x string %x title -%x open_directive -%x type_directive -%x arg_directive -%x close_directive %x acc_title %x acc_descr %x acc_descr_multiline %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; \%\%(?!\{)[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */{ /*console.log('');*/ } [\n\r]+ return 'NEWLINE'; @@ -52,7 +43,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili start : eol start - | directive start | PIE document | PIE showData document {yy.setShowData(true);} ; @@ -73,34 +63,12 @@ statement | acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); } | acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); } | acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } | section {yy.addSection($1.substr(8));$$=$1.substr(8);} - | directive ; -directive - : openDirective typeDirective closeDirective - | openDirective typeDirective ':' argDirective closeDirective - ; - eol : NEWLINE | ';' | EOF ; -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'pie'); } - ; - %% diff --git a/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison b/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison index 00c1252944..255b30a035 100644 --- a/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison +++ b/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison @@ -5,10 +5,6 @@ %x string %x md_string %x title -%x open_directive -%x type_directive -%x arg_directive -%x close_directive %x acc_title %x acc_descr %x acc_descr_multiline @@ -16,11 +12,6 @@ %x point_x %x point_y %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; \%\%(?!\{)[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */ [\n\r]+ return 'NEWLINE'; @@ -87,7 +78,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} start : eol start | SPACE start - | directive start | QUADRANT document ; @@ -110,7 +100,6 @@ statement | acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); } | acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); } | acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } | section {yy.addSection($1.substr(8));$$=$1.substr(8);} - | directive ; points @@ -133,33 +122,12 @@ quadrantDetails | QUADRANT_4 text {yy.setQuadrant4Text($2)} ; -directive - : openDirective typeDirective closeDirective - | openDirective typeDirective ':' argDirective closeDirective - ; - eol : NEWLINE | SEMI | EOF ; -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'quadrantChart'); } - ; - text: alphaNumToken { $$={text:$1, type: 'text'};} | text textNoTagsToken diff --git a/packages/mermaid/src/diagrams/requirement/parser/requirementDiagram.jison b/packages/mermaid/src/diagrams/requirement/parser/requirementDiagram.jison index 3313102835..6d0f7b1222 100644 --- a/packages/mermaid/src/diagrams/requirement/parser/requirementDiagram.jison +++ b/packages/mermaid/src/diagrams/requirement/parser/requirementDiagram.jison @@ -9,19 +9,10 @@ %x string %x token %x unqString -%x open_directive -%x type_directive -%x arg_directive -%x close_directive %x acc_title %x acc_descr %x acc_descr_multiline %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; "title"\s[^#\n;]+ return 'title'; accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } @@ -99,23 +90,10 @@ start | RD NEWLINE diagram EOF; directive - : openDirective typeDirective closeDirective - | openDirective typeDirective ':' argDirective closeDirective - | acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); } + : acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); } | acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); } | acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } ; -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); }; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); }; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'pie'); }; diagram : /* empty */ { $$ = [] } diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 04f0de20e7..7e2d45419b 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -16,22 +16,15 @@ // A special state for grabbing text up to the first comment/newline %x ID ALIAS LINE -// Directive states -%x open_directive type_directive arg_directive %x acc_title %x acc_descr %x acc_descr_multiline %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; [\n]+ return 'NEWLINE'; \s+ /* skip all whitespace */ ((?!\n)\s)+ /* skip same-line whitespace */ -\#[^\n]* /* skip comments */ +\#[^\n]* /* skip comments */ \%%(?!\{)[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */ [0-9]+(?=[ \n]+) return 'NUM'; @@ -106,7 +99,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili start : SPACE start | NEWLINE start - | directive start | SD document { yy.apply($2);return $2; } ; @@ -133,11 +125,6 @@ box_line ; -directive - : openDirective typeDirective closeDirective 'NEWLINE' - | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE' - ; - statement : participant_statement | 'create' participant_statement {$2.type='createParticipant'; $$=$2;} @@ -215,7 +202,6 @@ statement $3.unshift({type: 'breakStart', breakText:yy.parseMessage($2), signalType: yy.LINETYPE.BREAK_START}); $3.push({type: 'breakEnd', optText:yy.parseMessage($2), signalType: yy.LINETYPE.BREAK_END}); $$=$3;} - | directive ; option_sections @@ -335,20 +321,4 @@ text2 : TXT {$$ = yy.parseMessage($1.trim().substring(1)) } ; -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'sequence'); } - ; - %% diff --git a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison index dc050b2ffa..44235ecd40 100644 --- a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison +++ b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison @@ -33,10 +33,6 @@ %x FLOATING_NOTE %x FLOATING_NOTE_ID %x struct -%x open_directive -%x type_directive -%x arg_directive -%x close_directive // A special state for grabbing text up to the first comment/newline %x LINE @@ -50,18 +46,13 @@ .*direction\s+RL[^\n]* return 'direction_rl'; .*direction\s+LR[^\n]* return 'direction_lr'; -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; \%\%(?!\{)[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */{ /*console.log('Crap after close');*/ } [\n]+ return 'NL'; [\s]+ /* skip all whitespace */ -((?!\n)\s)+ /* skip same-line whitespace */ -\#[^\n]* /* skip comments */ +((?!\n)\s)+ /* skip same-line whitespace */ +\#[^\n]* /* skip comments */ \%%[^\n]* /* skip comments */ "scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; } \d+ return 'WIDTH'; @@ -155,7 +146,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili start : SPACE start | NL start - | directive start | SD document { /* console.log('--> Root document', $2); */ yy.setRootDoc($2); return $2; } ; @@ -241,7 +231,6 @@ statement $$={ stmt: 'state', id: $3.trim(), note:{position: $2.trim(), text: $4.trim()}}; } | note NOTE_TEXT AS ID - | directive | direction | acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); } | acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); } @@ -264,10 +253,6 @@ cssClassStatement } ; -directive - : openDirective typeDirective closeDirective - | openDirective typeDirective ':' argDirective closeDirective - ; direction : direction_tb { yy.setDirection('TB');$$={stmt:'dir', value:'TB'};} @@ -308,20 +293,4 @@ notePosition | right_of ; -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'state'); } - ; - %% diff --git a/packages/mermaid/src/diagrams/timeline/parser/timeline.jison b/packages/mermaid/src/diagrams/timeline/parser/timeline.jison index 59b96516a4..348c31fad8 100644 --- a/packages/mermaid/src/diagrams/timeline/parser/timeline.jison +++ b/packages/mermaid/src/diagrams/timeline/parser/timeline.jison @@ -9,17 +9,8 @@ %x acc_descr %x acc_descr_multiline -// Directive states -%x open_directive type_directive arg_directive - - %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; \%%(?!\{)[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */ [\n]+ return 'NEWLINE'; @@ -55,7 +46,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili start : timeline document 'EOF' { return $2; } - | directive start ; document @@ -70,11 +60,6 @@ line | EOF { $$=[];} ; -directive - : openDirective typeDirective closeDirective 'NEWLINE' - | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE' - ; - statement : title {yy.getCommonDb().setDiagramTitle($1.substr(6));$$=$1.substr(6);} | acc_title acc_title_value { $$=$2.trim();yy.getCommonDb().setAccTitle($$); } @@ -83,7 +68,6 @@ statement | section {yy.addSection($1.substr(8));$$=$1.substr(8);} | period_statement | event_statement - | directive ; period_statement : period {yy.addTask($1,0,'');$$=$1;} @@ -92,21 +76,4 @@ event_statement : event {yy.addEvent($1.substr(2));$$=$1;} ; - -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'timeline'); } - ; - %% diff --git a/packages/mermaid/src/diagrams/user-journey/parser/journey.jison b/packages/mermaid/src/diagrams/user-journey/parser/journey.jison index 4c28d53dc9..5567f14172 100644 --- a/packages/mermaid/src/diagrams/user-journey/parser/journey.jison +++ b/packages/mermaid/src/diagrams/user-journey/parser/journey.jison @@ -9,17 +9,8 @@ %x acc_descr %x acc_descr_multiline -// Directive states -%x open_directive type_directive arg_directive - - %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; \%%(?!\{)[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */ [\n]+ return 'NEWLINE'; @@ -52,7 +43,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili start : journey document 'EOF' { return $2; } - | directive start ; document @@ -67,11 +57,6 @@ line | EOF { $$=[];} ; -directive - : openDirective typeDirective closeDirective 'NEWLINE' - | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE' - ; - statement : title {yy.setDiagramTitle($1.substr(6));$$=$1.substr(6);} | acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); } @@ -79,23 +64,6 @@ statement | acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } | section {yy.addSection($1.substr(8));$$=$1.substr(8);} | taskName taskData {yy.addTask($1, $2);$$='task';} - | directive - ; - -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'journey'); } ; %% From 066e0967de7e296febc93d17774c8c102d7ac84b Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Sat, 26 Aug 2023 23:27:25 +0530 Subject: [PATCH 06/37] refactor: Move setWrap to individual diagrams as necessary. --- packages/mermaid/src/Diagram.ts | 5 +---- packages/mermaid/src/diagram-api/types.ts | 2 +- packages/mermaid/src/diagrams/c4/c4Diagram.ts | 21 ++++++++++--------- .../src/diagrams/sequence/sequenceDiagram.ts | 3 +++ 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/mermaid/src/Diagram.ts b/packages/mermaid/src/Diagram.ts index 9d665c71c5..3dc9738049 100644 --- a/packages/mermaid/src/Diagram.ts +++ b/packages/mermaid/src/Diagram.ts @@ -47,13 +47,10 @@ export class Diagram { this.db.clear?.(); const config = configApi.getConfig(); this.init?.(config); - // These 2 blocks were added for legacy compatibility. Do not add more such blocks. Use frontmatter instead. + // This block was added for legacy compatibility. Use frontmatter instead of adding more special cases. if (this.metadata.title) { this.db.setDiagramTitle?.(this.metadata.title); } - if (config.wrap) { - this.db.setWrap?.(config.wrap); - } this.parser.parse(this.text); } diff --git a/packages/mermaid/src/diagram-api/types.ts b/packages/mermaid/src/diagram-api/types.ts index e9def2421f..4e73d109f0 100644 --- a/packages/mermaid/src/diagram-api/types.ts +++ b/packages/mermaid/src/diagram-api/types.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import type { Diagram } from '../Diagram.js'; import type { BaseDiagramConfig, MermaidConfig } from '../config.type.js'; import type * as d3 from 'd3'; @@ -29,7 +30,6 @@ export interface DiagramDB { getAccDescription?: () => string; setDisplayMode?: (title: string) => void; - setWrap?: (wrap: boolean) => void; bindFunctions?: (element: Element) => void; } diff --git a/packages/mermaid/src/diagrams/c4/c4Diagram.ts b/packages/mermaid/src/diagrams/c4/c4Diagram.ts index 4c578b6246..9557a0f708 100644 --- a/packages/mermaid/src/diagrams/c4/c4Diagram.ts +++ b/packages/mermaid/src/diagrams/c4/c4Diagram.ts @@ -1,17 +1,18 @@ // @ts-ignore: JISON doesn't support types -import c4Parser from './parser/c4Diagram.jison'; -import c4Db from './c4Db.js'; -import c4Renderer from './c4Renderer.js'; -import c4Styles from './styles.js'; +import parser from './parser/c4Diagram.jison'; +import db from './c4Db.js'; +import renderer from './c4Renderer.js'; +import styles from './styles.js'; import type { MermaidConfig } from '../../config.type.js'; import type { DiagramDefinition } from '../../diagram-api/types.js'; export const diagram: DiagramDefinition = { - parser: c4Parser, - db: c4Db, - renderer: c4Renderer, - styles: c4Styles, - init: (cnf: MermaidConfig) => { - c4Renderer.setConf(cnf.c4); + parser, + db, + renderer, + styles, + init: ({ c4, wrap }: MermaidConfig) => { + renderer.setConf(c4); + db.setWrap(wrap); }, }; diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts index 8779b9cc43..f8d71c95e6 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts @@ -10,4 +10,7 @@ export const diagram: DiagramDefinition = { db, renderer, styles, + init: ({ wrap }) => { + db.setWrap(wrap); + }, }; From 8f340094d9913a01637f5ccd40484cccb1bdfebc Mon Sep 17 00:00:00 2001 From: Thomas Ingram Date: Sun, 27 Aug 2023 15:57:17 +1000 Subject: [PATCH 07/37] Added support for millisecond and second to gantt tickInterval --- packages/mermaid/src/config.type.ts | 2 +- .../mermaid/src/diagrams/gantt/ganttRenderer.js | 16 +++++++++++++++- packages/mermaid/src/docs/syntax/gantt.md | 2 +- packages/mermaid/src/schemas/config.schema.yaml | 4 ++-- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index a4bf6cca8c..ca55d9af47 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -1048,7 +1048,7 @@ export interface GanttDiagramConfig extends BaseDiagramConfig { * Pattern is: * * ```javascript - * /^([1-9][0-9]*)(minute|hour|day|week|month)$/ + * /^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$/ * ``` * */ diff --git a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js index 522f59e2c9..935ecc9285 100644 --- a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js +++ b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js @@ -10,6 +10,8 @@ import { axisBottom, axisTop, timeFormat, + timeMillisecond, + timeSecond, timeMinute, timeHour, timeDay, @@ -573,7 +575,7 @@ export const draw = function (text, id, version, diagObj) { .tickSize(-h + theTopPad + conf.gridLineStartPadding) .tickFormat(timeFormat(diagObj.db.getAxisFormat() || conf.axisFormat || '%Y-%m-%d')); - const reTickInterval = /^([1-9]\d*)(minute|hour|day|week|month)$/; + const reTickInterval = /^([1-9]\d*)(millisecond|second|minute|hour|day|week|month)$/; const resultTickInterval = reTickInterval.exec( diagObj.db.getTickInterval() || conf.tickInterval ); @@ -584,6 +586,12 @@ export const draw = function (text, id, version, diagObj) { const weekday = diagObj.db.getWeekday() || conf.weekday; switch (interval) { + case 'millisecond': + bottomXAxis.ticks(timeMillisecond.every(every)); + break; + case 'second': + bottomXAxis.ticks(timeSecond.every(every)); + break; case 'minute': bottomXAxis.ticks(timeMinute.every(every)); break; @@ -625,6 +633,12 @@ export const draw = function (text, id, version, diagObj) { const weekday = diagObj.db.getWeekday() || conf.weekday; switch (interval) { + case 'millisecond': + topXAxis.ticks(timeMillisecond.every(every)); + break; + case 'second': + topXAxis.ticks(timeSecond.every(every)); + break; case 'minute': topXAxis.ticks(timeMinute.every(every)); break; diff --git a/packages/mermaid/src/docs/syntax/gantt.md b/packages/mermaid/src/docs/syntax/gantt.md index 6ddd011f65..2d7af6a26c 100644 --- a/packages/mermaid/src/docs/syntax/gantt.md +++ b/packages/mermaid/src/docs/syntax/gantt.md @@ -184,7 +184,7 @@ tickInterval 1day The pattern is: ```javascript -/^([1-9][0-9]*)(minute|hour|day|week|month)$/; +/^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$/; ``` More info in: [https://github.com/d3/d3-time#interval_every](https://github.com/d3/d3-time#interval_every) diff --git a/packages/mermaid/src/schemas/config.schema.yaml b/packages/mermaid/src/schemas/config.schema.yaml index c0d239fb60..6f01d57154 100644 --- a/packages/mermaid/src/schemas/config.schema.yaml +++ b/packages/mermaid/src/schemas/config.schema.yaml @@ -1524,10 +1524,10 @@ $defs: # JSON Schema definition (maybe we should move these to a seperate file) Pattern is: ```javascript - /^([1-9][0-9]*)(minute|hour|day|week|month)$/ + /^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$/ ``` type: string - pattern: ^([1-9][0-9]*)(minute|hour|day|week|month)$ + pattern: ^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$ topAxis: description: | When this flag is set, date labels will be added to the top of the chart From b6d5497f51efdf14d22f7bd8fd7a6b2e10efc617 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Sun, 27 Aug 2023 15:23:59 -0700 Subject: [PATCH 08/37] Update README.md Quick fix to remove namespace from readme class definition since github has not been update to the version that uses namespaces --- README.md | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 98741a6894..f844e2cb92 100644 --- a/README.md +++ b/README.md @@ -165,13 +165,7 @@ class Class10 { int id size() } -namespace Namespace01 { - class Class11 - class Class12 { - int id - size() - } -} + ``` ```mermaid @@ -191,13 +185,7 @@ class Class10 { int id size() } -namespace Namespace01 { - class Class11 - class Class12 { - int id - size() - } -} + ``` ### State diagram [docs - live editor] From f6325f69067cc6c1c127b69e21075ff1eb1bbc08 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Mon, 28 Aug 2023 12:30:39 +0530 Subject: [PATCH 09/37] docs: Disable showValues in sankey example --- docs/syntax/sankey.md | 10 ++++++++++ packages/mermaid/src/docs/syntax/sankey.md | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/docs/syntax/sankey.md b/docs/syntax/sankey.md index 6f9e14d828..9b9fe2766a 100644 --- a/docs/syntax/sankey.md +++ b/docs/syntax/sankey.md @@ -19,6 +19,11 @@ The things being connected are called nodes and the connections are called links This example taken from [observable](https://observablehq.com/@d3/sankey/2?collection=@d3/d3-sankey). It may be rendered a little bit differently, though, in terms of size and colors. ```mermaid-example +--- +config: + sankey: + showValues: false +--- sankey-beta Agricultural 'waste',Bio-conversion,124.729 @@ -92,6 +97,11 @@ Wind,Electricity grid,289.366 ``` ```mermaid +--- +config: + sankey: + showValues: false +--- sankey-beta Agricultural 'waste',Bio-conversion,124.729 diff --git a/packages/mermaid/src/docs/syntax/sankey.md b/packages/mermaid/src/docs/syntax/sankey.md index c5e7244c61..c4db0646f1 100644 --- a/packages/mermaid/src/docs/syntax/sankey.md +++ b/packages/mermaid/src/docs/syntax/sankey.md @@ -13,6 +13,11 @@ The things being connected are called nodes and the connections are called links This example taken from [observable](https://observablehq.com/@d3/sankey/2?collection=@d3/d3-sankey). It may be rendered a little bit differently, though, in terms of size and colors. ```mermaid-example +--- +config: + sankey: + showValues: false +--- sankey-beta Agricultural 'waste',Bio-conversion,124.729 From 02517e8a7d5bf527289f5db64fd13252344aaf49 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Mon, 28 Aug 2023 12:31:40 +0530 Subject: [PATCH 10/37] chore: Fix warning formatting --- docs/syntax/sankey.md | 5 ++--- packages/mermaid/src/docs/syntax/sankey.md | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/syntax/sankey.md b/docs/syntax/sankey.md index 9b9fe2766a..bcbc22795f 100644 --- a/docs/syntax/sankey.md +++ b/docs/syntax/sankey.md @@ -8,9 +8,8 @@ > A sankey diagram is a visualization used to depict a flow from one set of values to another. -::: warning -This is an experimental diagram. Its syntax are very close to plain CSV, but it is to be extended in the nearest future. -::: +> **Warning** +> This is an experimental diagram. Its syntax are very close to plain CSV, but it is to be extended in the nearest future. The things being connected are called nodes and the connections are called links. diff --git a/packages/mermaid/src/docs/syntax/sankey.md b/packages/mermaid/src/docs/syntax/sankey.md index c4db0646f1..93cc020a86 100644 --- a/packages/mermaid/src/docs/syntax/sankey.md +++ b/packages/mermaid/src/docs/syntax/sankey.md @@ -2,9 +2,9 @@ > A sankey diagram is a visualization used to depict a flow from one set of values to another. -::: warning +```warning This is an experimental diagram. Its syntax are very close to plain CSV, but it is to be extended in the nearest future. -::: +``` The things being connected are called nodes and the connections are called links. From 4efac6721dc3660878df997811a3a3ff6064ba66 Mon Sep 17 00:00:00 2001 From: Thomas Ingram Date: Mon, 28 Aug 2023 20:17:15 +1000 Subject: [PATCH 11/37] Added missing integration tests and release version in docs. --- cypress/integration/rendering/gantt.spec.js | 42 +++++++++++++++++++++ docs/syntax/gantt.md | 6 +-- packages/mermaid/src/docs/syntax/gantt.md | 6 ++- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js index 33d3ac9e14..7c16909522 100644 --- a/cypress/integration/rendering/gantt.spec.js +++ b/cypress/integration/rendering/gantt.spec.js @@ -330,6 +330,48 @@ describe('Gantt diagram', () => { ); }); + it('should render a gantt diagram with tick is 2 milliseconds', () => { + imgSnapshotTest( + ` + gantt + title A Gantt Diagram + dateFormat SSS + axisFormat %Lms + tickInterval 2millisecond + excludes weekends + + section Section + A task : a1, 000, 6ms + Another task : after a1, 6ms + section Another + Task in sec : a2, 006, 3ms + another task : 3ms + `, + {} + ); + }); + + it('should render a gantt diagram with tick is 2 seconds', () => { + imgSnapshotTest( + ` + gantt + title A Gantt Diagram + dateFormat ss + axisFormat %Ss + tickInterval 2second + excludes weekends + + section Section + A task : a1, 00, 6s + Another task : after a1, 6s + section Another + Task in sec : 06, 3s + another task : 3s + `, + {} + ); + }); + it('should render a gantt diagram with tick is 15 minutes', () => { imgSnapshotTest( ` diff --git a/docs/syntax/gantt.md b/docs/syntax/gantt.md index 8ad438fb1b..33c2740e51 100644 --- a/docs/syntax/gantt.md +++ b/docs/syntax/gantt.md @@ -241,7 +241,7 @@ The following formatting strings are supported: More info in: -### Axis ticks +### Axis ticks (v10.3.0+) The default output ticks are auto. You can custom your `tickInterval`, like `1day` or `1week`. @@ -252,7 +252,7 @@ tickInterval 1day The pattern is: ```javascript -/^([1-9][0-9]*)(minute|hour|day|week|month)$/; +/^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$/; ``` More info in: @@ -271,7 +271,7 @@ gantt weekday monday ``` -Support: v10.3.0+ +> **Warning** > `millisecond` and `second` support was added in vMERMAID_RELEASE_VERSION ## Output in compact mode diff --git a/packages/mermaid/src/docs/syntax/gantt.md b/packages/mermaid/src/docs/syntax/gantt.md index 2d7af6a26c..a0cebc560d 100644 --- a/packages/mermaid/src/docs/syntax/gantt.md +++ b/packages/mermaid/src/docs/syntax/gantt.md @@ -173,7 +173,7 @@ The following formatting strings are supported: More info in: [https://github.com/d3/d3-time-format/tree/v4.0.0#locale_format](https://github.com/d3/d3-time-format/tree/v4.0.0#locale_format) -### Axis ticks +### Axis ticks (v10.3.0+) The default output ticks are auto. You can custom your `tickInterval`, like `1day` or `1week`. @@ -197,7 +197,9 @@ gantt weekday monday ``` -Support: v10.3.0+ +```warning +`millisecond` and `second` support was added in vMERMAID_RELEASE_VERSION +``` ## Output in compact mode From a0e33bee71000b411584e91483f61121920e4cd6 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Tue, 5 Sep 2023 21:57:05 +0530 Subject: [PATCH 12/37] chore: Cleanup gitGraph tests --- .../src/diagrams/git/gitGraphParser.spec.js | 246 ++++++++++++++++++ .../src/diagrams/git/gitGraphParserV2.spec.js | 11 - 2 files changed, 246 insertions(+), 11 deletions(-) create mode 100644 packages/mermaid/src/diagrams/git/gitGraphParser.spec.js diff --git a/packages/mermaid/src/diagrams/git/gitGraphParser.spec.js b/packages/mermaid/src/diagrams/git/gitGraphParser.spec.js new file mode 100644 index 0000000000..446f067393 --- /dev/null +++ b/packages/mermaid/src/diagrams/git/gitGraphParser.spec.js @@ -0,0 +1,246 @@ +import gitGraphAst from './gitGraphAst.js'; +import { parser } from './parser/gitGraph.jison'; + +describe('when parsing a gitGraph', function () { + beforeEach(function () { + parser.yy = gitGraphAst; + parser.yy.clear(); + }); + it('should handle a gitGraph definition', function () { + const str = 'gitGraph:\n' + 'commit\n'; + + parser.parse(str); + const commits = parser.yy.getCommits(); + + expect(Object.keys(commits).length).toBe(1); + expect(parser.yy.getCurrentBranch()).toBe('main'); + expect(parser.yy.getDirection()).toBe('LR'); + expect(Object.keys(parser.yy.getBranches()).length).toBe(1); + }); + + it('should handle a gitGraph definition with empty options', function () { + const str = 'gitGraph:\n' + 'options\n' + ' end\n' + 'commit\n'; + + parser.parse(str); + const commits = parser.yy.getCommits(); + + expect(parser.yy.getOptions()).toEqual({}); + expect(Object.keys(commits).length).toBe(1); + expect(parser.yy.getCurrentBranch()).toBe('main'); + expect(parser.yy.getDirection()).toBe('LR'); + expect(Object.keys(parser.yy.getBranches()).length).toBe(1); + }); + + it('should handle a gitGraph definition with valid options', function () { + const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"}\n' + 'end\n' + 'commit\n'; + + parser.parse(str); + const commits = parser.yy.getCommits(); + expect(parser.yy.getOptions()['key']).toBe('value'); + expect(Object.keys(commits).length).toBe(1); + expect(parser.yy.getCurrentBranch()).toBe('main'); + expect(parser.yy.getDirection()).toBe('LR'); + expect(Object.keys(parser.yy.getBranches()).length).toBe(1); + }); + + it('should not fail on a gitGraph with malformed json', function () { + const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"\n' + 'end\n' + 'commit\n'; + + parser.parse(str); + const commits = parser.yy.getCommits(); + expect(Object.keys(commits).length).toBe(1); + expect(parser.yy.getCurrentBranch()).toBe('main'); + expect(parser.yy.getDirection()).toBe('LR'); + expect(Object.keys(parser.yy.getBranches()).length).toBe(1); + }); + + it('should handle set direction', function () { + const str = 'gitGraph TB:\n' + 'commit\n'; + + parser.parse(str); + const commits = parser.yy.getCommits(); + + expect(Object.keys(commits).length).toBe(1); + expect(parser.yy.getCurrentBranch()).toBe('main'); + expect(parser.yy.getDirection()).toBe('TB'); + expect(Object.keys(parser.yy.getBranches()).length).toBe(1); + }); + + it('should checkout a branch', function () { + const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n'; + + parser.parse(str); + const commits = parser.yy.getCommits(); + + expect(Object.keys(commits).length).toBe(0); + expect(parser.yy.getCurrentBranch()).toBe('new'); + }); + + it('should add commits to checked out branch', function () { + const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n' + 'commit\n' + 'commit\n'; + + parser.parse(str); + const commits = parser.yy.getCommits(); + + expect(Object.keys(commits).length).toBe(2); + expect(parser.yy.getCurrentBranch()).toBe('new'); + const branchCommit = parser.yy.getBranches()['new']; + expect(branchCommit).not.toBeNull(); + expect(commits[branchCommit].parent).not.toBeNull(); + }); + it('should handle commit with args', function () { + const str = 'gitGraph:\n' + 'commit "a commit"\n'; + + parser.parse(str); + const commits = parser.yy.getCommits(); + + expect(Object.keys(commits).length).toBe(1); + const key = Object.keys(commits)[0]; + expect(commits[key].message).toBe('a commit'); + expect(parser.yy.getCurrentBranch()).toBe('main'); + }); + + // Reset has been commented out in JISON + it.skip('should reset a branch', function () { + const str = + 'gitGraph:\n' + + 'commit\n' + + 'commit\n' + + 'branch newbranch\n' + + 'checkout newbranch\n' + + 'commit\n' + + 'reset main\n'; + + parser.parse(str); + + const commits = parser.yy.getCommits(); + expect(Object.keys(commits).length).toBe(3); + expect(parser.yy.getCurrentBranch()).toBe('newbranch'); + expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['main']); + expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']); + }); + + it.skip('reset can take an argument', function () { + const str = + 'gitGraph:\n' + + 'commit\n' + + 'commit\n' + + 'branch newbranch\n' + + 'checkout newbranch\n' + + 'commit\n' + + 'reset main^\n'; + + parser.parse(str); + + const commits = parser.yy.getCommits(); + expect(Object.keys(commits).length).toBe(3); + expect(parser.yy.getCurrentBranch()).toBe('newbranch'); + const main = commits[parser.yy.getBranches()['main']]; + expect(parser.yy.getHead().id).toEqual(main.parent); + }); + + it.skip('should handle fast forwardable merges', function () { + const str = + 'gitGraph:\n' + + 'commit\n' + + 'branch newbranch\n' + + 'checkout newbranch\n' + + 'commit\n' + + 'commit\n' + + 'checkout main\n' + + 'merge newbranch\n'; + + parser.parse(str); + + const commits = parser.yy.getCommits(); + expect(Object.keys(commits).length).toBe(4); + expect(parser.yy.getCurrentBranch()).toBe('main'); + expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['main']); + expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']); + }); + + it('should handle cases when merge is a noop', function () { + const str = + 'gitGraph:\n' + + 'commit\n' + + 'branch newbranch\n' + + 'checkout newbranch\n' + + 'commit\n' + + 'commit\n' + + 'merge main\n'; + + parser.parse(str); + + const commits = parser.yy.getCommits(); + expect(Object.keys(commits).length).toBe(4); + expect(parser.yy.getCurrentBranch()).toBe('newbranch'); + expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['main']); + expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']); + }); + + it('should handle merge with 2 parents', function () { + const str = + 'gitGraph:\n' + + 'commit\n' + + 'branch newbranch\n' + + 'checkout newbranch\n' + + 'commit\n' + + 'commit\n' + + 'checkout main\n' + + 'commit\n' + + 'merge newbranch\n'; + + parser.parse(str); + + const commits = parser.yy.getCommits(); + expect(Object.keys(commits).length).toBe(5); + expect(parser.yy.getCurrentBranch()).toBe('main'); + expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['main']); + expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['main']); + }); + + it.skip('should handle ff merge when history walk has two parents (merge commit)', function () { + const str = + 'gitGraph:\n' + + 'commit\n' + + 'branch newbranch\n' + + 'checkout newbranch\n' + + 'commit\n' + + 'commit\n' + + 'checkout main\n' + + 'commit\n' + + 'merge newbranch\n' + + 'commit\n' + + 'checkout newbranch\n' + + 'merge main\n'; + + parser.parse(str); + + const commits = parser.yy.getCommits(); + expect(Object.keys(commits).length).toBe(7); + expect(parser.yy.getCurrentBranch()).toBe('newbranch'); + expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['main']); + expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['main']); + + parser.yy.prettyPrint(); + }); + + it('should generate an array of known branches', function () { + const str = + 'gitGraph:\n' + + 'commit\n' + + 'branch b1\n' + + 'checkout b1\n' + + 'commit\n' + + 'commit\n' + + 'branch b2\n'; + + parser.parse(str); + const branches = gitGraphAst.getBranchesAsObjArray(); + + expect(branches).toHaveLength(3); + expect(branches[0]).toHaveProperty('name', 'main'); + expect(branches[1]).toHaveProperty('name', 'b1'); + expect(branches[2]).toHaveProperty('name', 'b2'); + }); +}); diff --git a/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js b/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js index 764fbb214c..df20a5eb5a 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js +++ b/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js @@ -1,22 +1,11 @@ -/* eslint-env jasmine */ -// Todo reintroduce without cryptoRandomString import gitGraphAst from './gitGraphAst.js'; import { parser } from './parser/gitGraph.jison'; -//import randomString from 'crypto-random-string'; -//import cryptoRandomString from 'crypto-random-string'; - -//jest.mock('crypto-random-string'); describe('when parsing a gitGraph', function () { - let randomNumber; beforeEach(function () { parser.yy = gitGraphAst; parser.yy.clear(); - randomNumber = 0; }); - // afterEach(function() { - // cryptoRandomString.mockReset(); - // }); it('should handle a gitGraph commit with NO pararms, get auto-genrated reandom ID', function () { const str = `gitGraph: commit From 18079a44bedb09eda7831aaacc5c3b263c97c227 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Wed, 6 Sep 2023 22:06:05 +0530 Subject: [PATCH 13/37] chore: Add deprecation notices, improve types Co-authored-by: Alois Klink --- docs/config/setup/modules/mermaidAPI.md | 2 +- packages/mermaid/src/Diagram.ts | 7 ++++--- packages/mermaid/src/diagram-api/diagramAPI.ts | 3 ++- packages/mermaid/src/diagram-api/types.ts | 7 +++++++ packages/mermaid/src/mermaidAPI.ts | 2 +- packages/mermaid/src/preprocess.ts | 11 +++++++++-- 6 files changed, 24 insertions(+), 8 deletions(-) diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index 3c2b5bb84a..1ea19fac4e 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -31,7 +31,7 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi) ### mermaidAPI -• `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`, `metadata`: { `title?`: `string` }) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }> +• `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`, `metadata`: `Pick`<`DiagramMetadata`, `"title"`>) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }> ## mermaidAPI configuration defaults diff --git a/packages/mermaid/src/Diagram.ts b/packages/mermaid/src/Diagram.ts index 3dc9738049..b77091f28c 100644 --- a/packages/mermaid/src/Diagram.ts +++ b/packages/mermaid/src/Diagram.ts @@ -4,7 +4,7 @@ import { getDiagram, registerDiagram } from './diagram-api/diagramAPI.js'; import { detectType, getDiagramLoader } from './diagram-api/detectType.js'; import { UnknownDiagramError } from './errors.js'; import type { DetailedError } from './utils.js'; -import type { DiagramDefinition } from './diagram-api/types.js'; +import type { DiagramDefinition, DiagramMetadata } from './diagram-api/types.js'; export type ParseErrorFunction = (err: string | DetailedError | unknown, hash?: any) => void; @@ -20,7 +20,7 @@ export class Diagram { private init?: DiagramDefinition['init']; private detectError?: UnknownDiagramError; - constructor(public text: string, public metadata: { title?: string } = {}) { + constructor(public text: string, public metadata: Pick = {}) { this.text += '\n'; const cnf = configApi.getConfig(); try { @@ -72,13 +72,14 @@ export class Diagram { * **Warning:** This function may be changed in the future. * @alpha * @param text - The mermaid diagram definition. + * @param metadata - Diagram metadata, defined in YAML. * @returns A the Promise of a Diagram object. * @throws {@link UnknownDiagramError} if the diagram type can not be found. * @privateRemarks This is exported as part of the public mermaidAPI. */ export const getDiagramFromText = async ( text: string, - metadata: { title?: string } = {} + metadata: Pick = {} ): Promise => { const type = detectType(text, configApi.getConfig()); try { diff --git a/packages/mermaid/src/diagram-api/diagramAPI.ts b/packages/mermaid/src/diagram-api/diagramAPI.ts index 9a4af2585d..ea3c10159f 100644 --- a/packages/mermaid/src/diagram-api/diagramAPI.ts +++ b/packages/mermaid/src/diagram-api/diagramAPI.ts @@ -57,7 +57,8 @@ export const registerDiagram = ( setupGraphViewbox, getCommonDb(), () => { - // parseDirective is removed. This is a no-op for legacy support. + // parseDirective is removed in https://github.com/mermaid-js/mermaid/pull/4759. + // This is a no-op for legacy support. } ); }; diff --git a/packages/mermaid/src/diagram-api/types.ts b/packages/mermaid/src/diagram-api/types.ts index 4e73d109f0..15aa4b0336 100644 --- a/packages/mermaid/src/diagram-api/types.ts +++ b/packages/mermaid/src/diagram-api/types.ts @@ -3,6 +3,11 @@ import type { Diagram } from '../Diagram.js'; import type { BaseDiagramConfig, MermaidConfig } from '../config.type.js'; import type * as d3 from 'd3'; +export interface DiagramMetadata { + title?: string; + config?: MermaidConfig; +} + export interface InjectUtils { _log: any; _setLogLevel: any; @@ -10,6 +15,7 @@ export interface InjectUtils { _sanitizeText: any; _setupGraphViewbox: any; _commonDb: any; + /** @deprecated as directives will be pre-processed since https://github.com/mermaid-js/mermaid/pull/4759 */ _parseDirective: any; } @@ -46,6 +52,7 @@ export interface DiagramDefinition { _sanitizeText: InjectUtils['_sanitizeText'], _setupGraphViewbox: InjectUtils['_setupGraphViewbox'], _commonDb: InjectUtils['_commonDb'], + /** @deprecated as directives will be pre-processed since https://github.com/mermaid-js/mermaid/pull/4759 */ _parseDirective: InjectUtils['_parseDirective'] ) => void; } diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index f71fe27a76..3649b50f53 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -99,7 +99,7 @@ export interface RenderResult { function processAndSetConfigs(text: string) { const processed = preprocessDiagram(text); configApi.reset(); - configApi.addDirective(processed.config); + configApi.addDirective(processed.config ?? {}); return processed; } diff --git a/packages/mermaid/src/preprocess.ts b/packages/mermaid/src/preprocess.ts index 3c33ce30f8..6e386e744a 100644 --- a/packages/mermaid/src/preprocess.ts +++ b/packages/mermaid/src/preprocess.ts @@ -1,5 +1,6 @@ import { cleanupComments } from './diagram-api/comments.js'; import { extractFrontMatter } from './diagram-api/frontmatter.js'; +import type { DiagramMetadata } from './diagram-api/types.js'; import utils, { cleanAndMerge, removeDirectives } from './utils.js'; const cleanupText = (code: string) => { @@ -44,7 +45,13 @@ const processDirectives = (code: string) => { }; }; -export const preprocessDiagram = (code: string) => { +/** + * Preprocess the given code by cleaning it up, extracting front matter and directives, + * cleaning and merging configuration, and removing comments. + * @param code - The code to preprocess. + * @returns The object containing the preprocessed code, title, and configuration. + */ +export function preprocessDiagram(code: string): DiagramMetadata & { code: string } { const cleanedCode = cleanupText(code); const frontMatterResult = processFrontmatter(cleanedCode); const directiveResult = processDirectives(frontMatterResult.text); @@ -55,4 +62,4 @@ export const preprocessDiagram = (code: string) => { title: frontMatterResult.title, config, }; -}; +} From bcd03151e8e4832a045d240daed398c7418b1481 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Wed, 6 Sep 2023 22:09:15 +0530 Subject: [PATCH 14/37] chore: Update docs --- docs/config/Tutorials.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/config/Tutorials.md b/docs/config/Tutorials.md index 8f6d7e1ab7..cdfb74d920 100644 --- a/docs/config/Tutorials.md +++ b/docs/config/Tutorials.md @@ -62,10 +62,10 @@ from IPython.display import Image, display import matplotlib.pyplot as plt def mm(graph): - graphbytes = graph.encode("ascii") - base64_bytes = base64.b64encode(graphbytes) - base64_string = base64_bytes.decode("ascii") - display(Image(url="https://mermaid.ink/img/" + base64_string)) + graphbytes = graph.encode("utf8") + base64_bytes = base64.b64encode(graphbytes) + base64_string = base64_bytes.decode("ascii") + display(Image(url="https://mermaid.ink/img/" + base64_string)) mm(""" graph LR; From fd39ea80494e46db86852e996afe220d5fb898cc Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Wed, 6 Sep 2023 17:07:08 +0000 Subject: [PATCH 15/37] Update cypress/helpers/util.js --- cypress/helpers/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/helpers/util.js b/cypress/helpers/util.js index bab8d69579..a05e489221 100644 --- a/cypress/helpers/util.js +++ b/cypress/helpers/util.js @@ -25,7 +25,7 @@ export const imgSnapshotTest = (graphStr, _options = {}, api = false, validation fontFamily: _options.fontFamily || 'courier', fontSize: _options.fontSize || '16px', sequence: { - ...(options.sequence || {}), + ...(_options.sequence || {}), actorFontFamily: 'courier', noteFontFamily: _options.sequence?.noteFontFamily || 'courier', messageFontFamily: 'courier', From 8662b33de96e372f38bceb29b541f98823066d3c Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Wed, 6 Sep 2023 22:50:32 +0530 Subject: [PATCH 16/37] core: Adapt changes from 3f7bafb2d7 Co-authored-by: RohanHandore <110839432+RohanHandore@users.noreply.github.com> --- cypress/helpers/util.ts | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/cypress/helpers/util.ts b/cypress/helpers/util.ts index 4160f4cbdc..a9028cd166 100644 --- a/cypress/helpers/util.ts +++ b/cypress/helpers/util.ts @@ -52,29 +52,18 @@ export const imgSnapshotTest = ( api = false, validation?: any ): void => { - cy.log(JSON.stringify(_options)); - const options: CypressMermaidConfig = Object.assign(_options); - if (!options.fontFamily) { - options.fontFamily = 'courier'; - } - if (!options.sequence) { - options.sequence = {}; - } - if (!options.sequence || (options.sequence && !options.sequence.actorFontFamily)) { - options.sequence.actorFontFamily = 'courier'; - } - if (options.sequence && !options.sequence.noteFontFamily) { - options.sequence.noteFontFamily = 'courier'; - } - options.sequence.actorFontFamily = 'courier'; - options.sequence.noteFontFamily = 'courier'; - options.sequence.messageFontFamily = 'courier'; - if (options.sequence && !options.sequence.actorFontFamily) { - options.sequence.actorFontFamily = 'courier'; - } - if (!options.fontSize) { - options.fontSize = 16; - } + const options: CypressMermaidConfig = { + ..._options, + fontFamily: _options.fontFamily ?? 'courier', + // @ts-ignore TODO: Fix type of fontSize + fontSize: _options.fontSize ?? '16px', + sequence: { + ...(_options.sequence ?? {}), + actorFontFamily: 'courier', + noteFontFamily: _options.sequence?.noteFontFamily ?? 'courier', + messageFontFamily: 'courier', + }, + }; const url: string = mermaidUrl(graphStr, options, api); openURLAndVerifyRendering(url, options, validation); @@ -82,11 +71,10 @@ export const imgSnapshotTest = ( export const urlSnapshotTest = ( url: string, - _options: CypressMermaidConfig, + options: CypressMermaidConfig, _api = false, validation?: any ): void => { - const options: CypressMermaidConfig = Object.assign(_options); openURLAndVerifyRendering(url, options, validation); }; From 53dee702929ed4f82f70804b48c5a321ea7b9762 Mon Sep 17 00:00:00 2001 From: Oscar Andersson Date: Wed, 6 Sep 2023 19:34:20 +0200 Subject: [PATCH 17/37] Update flowchart.md (#4792) * Update flowchart.md Update from mermaid 8.4.8 to 10.4.0 for jsfiddle * Update flowchart.md * Update flowchart.md --- packages/mermaid/src/docs/syntax/flowchart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/docs/syntax/flowchart.md b/packages/mermaid/src/docs/syntax/flowchart.md index a49e974ab7..b0f1b9f0a2 100644 --- a/packages/mermaid/src/docs/syntax/flowchart.md +++ b/packages/mermaid/src/docs/syntax/flowchart.md @@ -560,7 +560,7 @@ flowchart LR > **Success** The tooltip functionality and the ability to link to urls are available from version 0.5.2. -?> Due to limitations with how Docsify handles JavaScript callback functions, an alternate working demo for the above code can be viewed at [this jsfiddle](https://jsfiddle.net/s37cjoau/3/). +?> Due to limitations with how Docsify handles JavaScript callback functions, an alternate working demo for the above code can be viewed at [this jsfiddle](https://jsfiddle.net/Ogglas/2o73vdez/7). Links are opened in the same browser tab/window by default. It is possible to change this by adding a link target to the click definition (`_self`, `_blank`, `_parent` and `_top` are supported): From 2eb9afa2f2b3fd490300ae46074023ab4a47bfc7 Mon Sep 17 00:00:00 2001 From: Douglas Blank Date: Wed, 6 Sep 2023 13:44:35 -0400 Subject: [PATCH 18/37] Update flowchart.md (#4798) * Update flowchart.md Fixed typos * docs: Fix proper doc * docs: Fix npmjs link --------- Co-authored-by: Sidharth Vinod --- docs/intro/index.md | 2 +- docs/syntax/flowchart.md | 8 ++++---- packages/mermaid/src/docs/intro/index.md | 2 +- packages/mermaid/src/docs/syntax/flowchart.md | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/intro/index.md b/docs/intro/index.md index 0197d7950f..974003fd6c 100644 --- a/docs/intro/index.md +++ b/docs/intro/index.md @@ -402,7 +402,7 @@ Update version number in `package.json`. npm publish ``` -The above command generates files into the `dist` folder and publishes them to [npmjs.org](npmjs.org). +The above command generates files into the `dist` folder and publishes them to [npmjs.com](https://www.npmjs.com/). ## Security and safe diagrams diff --git a/docs/syntax/flowchart.md b/docs/syntax/flowchart.md index ee635d451a..4e68c53a97 100644 --- a/docs/syntax/flowchart.md +++ b/docs/syntax/flowchart.md @@ -860,8 +860,8 @@ flowchart LR C-->D click A callback "Tooltip for a callback" click B "https://www.github.com" "This is a tooltip for a link" - click A call callback() "Tooltip for a callback" - click B href "https://www.github.com" "This is a tooltip for a link" + click C call callback() "Tooltip for a callback" + click D href "https://www.github.com" "This is a tooltip for a link" ``` ```mermaid @@ -871,8 +871,8 @@ flowchart LR C-->D click A callback "Tooltip for a callback" click B "https://www.github.com" "This is a tooltip for a link" - click A call callback() "Tooltip for a callback" - click B href "https://www.github.com" "This is a tooltip for a link" + click C call callback() "Tooltip for a callback" + click D href "https://www.github.com" "This is a tooltip for a link" ``` > **Success** The tooltip functionality and the ability to link to urls are available from version 0.5.2. diff --git a/packages/mermaid/src/docs/intro/index.md b/packages/mermaid/src/docs/intro/index.md index 8410ef1cb6..84fec41f86 100644 --- a/packages/mermaid/src/docs/intro/index.md +++ b/packages/mermaid/src/docs/intro/index.md @@ -179,7 +179,7 @@ Update version number in `package.json`. npm publish ``` -The above command generates files into the `dist` folder and publishes them to [npmjs.org](npmjs.org). +The above command generates files into the `dist` folder and publishes them to [npmjs.com](https://www.npmjs.com/). ## Security and safe diagrams diff --git a/packages/mermaid/src/docs/syntax/flowchart.md b/packages/mermaid/src/docs/syntax/flowchart.md index 01a33cf401..00aa677a65 100644 --- a/packages/mermaid/src/docs/syntax/flowchart.md +++ b/packages/mermaid/src/docs/syntax/flowchart.md @@ -554,8 +554,8 @@ flowchart LR C-->D click A callback "Tooltip for a callback" click B "https://www.github.com" "This is a tooltip for a link" - click A call callback() "Tooltip for a callback" - click B href "https://www.github.com" "This is a tooltip for a link" + click C call callback() "Tooltip for a callback" + click D href "https://www.github.com" "This is a tooltip for a link" ``` > **Success** The tooltip functionality and the ability to link to urls are available from version 0.5.2. From 7d2b39f69f1ecb8f6b59bb6d728927e67630b906 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Wed, 6 Sep 2023 23:21:51 +0530 Subject: [PATCH 19/37] refactor: Use `||` instead of `??` --- cypress/helpers/util.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cypress/helpers/util.ts b/cypress/helpers/util.ts index a9028cd166..60f3ad7a90 100644 --- a/cypress/helpers/util.ts +++ b/cypress/helpers/util.ts @@ -54,13 +54,13 @@ export const imgSnapshotTest = ( ): void => { const options: CypressMermaidConfig = { ..._options, - fontFamily: _options.fontFamily ?? 'courier', + fontFamily: _options.fontFamily || 'courier', // @ts-ignore TODO: Fix type of fontSize - fontSize: _options.fontSize ?? '16px', + fontSize: _options.fontSize || '16px', sequence: { - ...(_options.sequence ?? {}), + ...(_options.sequence || {}), actorFontFamily: 'courier', - noteFontFamily: _options.sequence?.noteFontFamily ?? 'courier', + noteFontFamily: _options.sequence?.noteFontFamily || 'courier', messageFontFamily: 'courier', }, }; From ba66b219e2fdfce583cdd2a260a6879032b13d23 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Wed, 6 Sep 2023 23:23:48 +0530 Subject: [PATCH 20/37] chore: Update docs --- docs/syntax/flowchart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/syntax/flowchart.md b/docs/syntax/flowchart.md index 8d16141a09..23b05af7f4 100644 --- a/docs/syntax/flowchart.md +++ b/docs/syntax/flowchart.md @@ -877,7 +877,7 @@ flowchart LR > **Success** The tooltip functionality and the ability to link to urls are available from version 0.5.2. -?> Due to limitations with how Docsify handles JavaScript callback functions, an alternate working demo for the above code can be viewed at [this jsfiddle](https://jsfiddle.net/s37cjoau/3/). +?> Due to limitations with how Docsify handles JavaScript callback functions, an alternate working demo for the above code can be viewed at [this jsfiddle](https://jsfiddle.net/Ogglas/2o73vdez/7). Links are opened in the same browser tab/window by default. It is possible to change this by adding a link target to the click definition (`_self`, `_blank`, `_parent` and `_top` are supported): From 56aa503ded16f27cb4e8a1ab99d6289af0c40317 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Thu, 7 Sep 2023 08:31:05 +0530 Subject: [PATCH 21/37] Remove optional chaining --- cypress/helpers/util.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cypress/helpers/util.ts b/cypress/helpers/util.ts index 60f3ad7a90..c656f638da 100644 --- a/cypress/helpers/util.ts +++ b/cypress/helpers/util.ts @@ -60,7 +60,10 @@ export const imgSnapshotTest = ( sequence: { ...(_options.sequence || {}), actorFontFamily: 'courier', - noteFontFamily: _options.sequence?.noteFontFamily || 'courier', + noteFontFamily: + _options.sequence && _options.sequence.noteFontFamily + ? _options.sequence.noteFontFamily + : 'courier', messageFontFamily: 'courier', }, }; From 667b17bf66ad13fe822a92f43d62265ba4f9fccb Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Thu, 7 Sep 2023 10:15:58 +0530 Subject: [PATCH 22/37] Remove unnecessary tests --- .../diagrams/sequence/sequenceDiagram.spec.js | 83 +------------------ 1 file changed, 1 insertion(+), 82 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index c46c195412..77ac7c45cd 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -1,5 +1,4 @@ import { vi } from 'vitest'; - import * as configApi from '../../config.js'; import mermaidAPI from '../../mermaidAPI.js'; import { Diagram, getDiagramFromText } from '../../Diagram.js'; @@ -225,6 +224,7 @@ Bob-->Alice: I am good thanks!`; diagram.renderer.draw(str, 'tst', '1.2.3', diagram); // needs to be rendered for the correct value of visibility auto numbers expect(diagram.db.showSequenceNumbers()).toBe(true); }); + it('should handle a sequenceDiagram definition with a title:', async () => { const str = ` sequenceDiagram @@ -2034,84 +2034,3 @@ participant Alice`; }); }); }); - -describe('when rendering a sequenceDiagram with directives', () => { - beforeAll(function () { - let conf = { - diagramMarginX: 50, - diagramMarginY: 10, - actorMargin: 50, - width: 150, - height: 65, - boxMargin: 10, - messageMargin: 40, - boxTextMargin: 15, - noteMargin: 25, - }; - mermaidAPI.initialize({ sequence: conf }); - }); - - beforeEach(function () { - mermaidAPI.reset(); - diagram.renderer.bounds.init(); - }); - - it('should handle one actor, when theme is dark and logLevel is 1 DX1 (dfg1)', async () => { - const str = ` -sequenceDiagram -participant Alice -`; - diagram = new Diagram(str); - diagram.renderer.bounds.init(); - diagram.renderer.draw(str, 'tst', '1.2.3', diagram); - - const { bounds, models } = diagram.renderer.bounds.getBounds(); - const mermaid = mermaidAPI.getConfig(); - expect(bounds.startx).toBe(0); - expect(bounds.startx).toBe(0); - expect(bounds.starty).toBe(0); - expect(bounds.stopy).toBe( - models.lastActor().stopy + models.lastActor().height + mermaid.sequence.boxMargin - ); - }); - it('should handle one actor, when logLevel is 3 (dfg0)', async () => { - const str = ` -sequenceDiagram -participant Alice -`; - - diagram = new Diagram(str); - diagram.renderer.draw(str, 'tst', '1.2.3', diagram); - - const { bounds, models } = diagram.renderer.bounds.getBounds(); - const mermaid = mermaidAPI.getConfig(); - expect(bounds.startx).toBe(0); - expect(bounds.startx).toBe(0); - expect(bounds.starty).toBe(0); - expect(bounds.stopy).toBe( - models.lastActor().stopy + models.lastActor().height + mermaid.sequence.boxMargin - ); - }); - it('should hide sequence numbers when autonumber is removed when autonumber is enabled', async () => { - const str1 = ` -sequenceDiagram -autonumber -Alice->Bob:Hello Bob, how are you? -Note right of Bob: Bob thinks -Bob-->Alice: I am good thanks!`; - - diagram = new Diagram(str1); - diagram.renderer.draw(str1, 'tst', '1.2.3', diagram); // needs to be rendered for the correct value of visibility auto numbers - expect(diagram.db.showSequenceNumbers()).toBe(true); - - const str2 = ` -sequenceDiagram -Alice->Bob:Hello Bob, how are you? -Note right of Bob: Bob thinks -Bob-->Alice: I am good thanks!`; - - diagram = new Diagram(str2); - diagram.renderer.draw(str2, 'tst', '1.2.3', diagram); - expect(diagram.db.showSequenceNumbers()).toBe(false); - }); -}); From 38123d2f2175eed1403b47560e9fcd2498cd81ec Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Thu, 7 Sep 2023 12:01:22 +0530 Subject: [PATCH 23/37] fix: #4818 support `getClasses` in external diagrams. --- .../interfaces/mermaidAPI.ParseOptions.md | 2 +- .../interfaces/mermaidAPI.RenderResult.md | 4 +-- docs/config/setup/modules/mermaidAPI.md | 34 +++++++++---------- .../src/diagram-api/diagram-orchestration.ts | 6 +++- .../src/diagram-api/diagramAPI.spec.ts | 6 +++- packages/mermaid/src/diagram-api/types.ts | 21 ++++++++++-- packages/mermaid/src/diagram.spec.ts | 6 +++- .../flowchart/elk/flowRenderer-elk.js | 5 ++- .../src/diagrams/flowchart/flowRenderer-v2.js | 2 +- .../src/diagrams/flowchart/flowRenderer.js | 2 +- .../src/diagrams/state/stateRenderer-v2.js | 2 +- packages/mermaid/src/mermaid.spec.ts | 6 ++-- packages/mermaid/src/mermaidAPI.ts | 27 +++------------ 13 files changed, 67 insertions(+), 56 deletions(-) diff --git a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md index 2082a081ea..f7e2c2ed7c 100644 --- a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md +++ b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md @@ -16,4 +16,4 @@ #### Defined in -[mermaidAPI.ts:78](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L78) +[mermaidAPI.ts:61](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L61) diff --git a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md index f84a51b87b..2f039b2eb9 100644 --- a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md +++ b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md @@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present. #### Defined in -[mermaidAPI.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L98) +[mermaidAPI.ts:81](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L81) --- @@ -51,4 +51,4 @@ The svg code for the rendered graph. #### Defined in -[mermaidAPI.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L88) +[mermaidAPI.ts:71](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L71) diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index d5d4a1cbc1..0d3501576f 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -25,7 +25,7 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi) #### Defined in -[mermaidAPI.ts:82](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L82) +[mermaidAPI.ts:65](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L65) ## Variables @@ -96,7 +96,7 @@ mermaid.initialize(config); #### Defined in -[mermaidAPI.ts:673](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L673) +[mermaidAPI.ts:654](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L654) ## Functions @@ -127,7 +127,7 @@ Return the last node appended #### Defined in -[mermaidAPI.ts:310](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L310) +[mermaidAPI.ts:293](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L293) --- @@ -153,7 +153,7 @@ the cleaned up svgCode #### Defined in -[mermaidAPI.ts:256](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L256) +[mermaidAPI.ts:239](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L239) --- @@ -179,7 +179,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L185) +[mermaidAPI.ts:168](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L168) --- @@ -189,12 +189,12 @@ the string with all the user styles #### Parameters -| Name | Type | -| :---------- | :----------------------------------------- | -| `config` | `MermaidConfig` | -| `graphType` | `string` | -| `classDefs` | `Record`<`string`, `DiagramStyleClassDef`> | -| `svgId` | `string` | +| Name | Type | +| :---------- | :-------------------------------------------------------- | +| `config` | `MermaidConfig` | +| `graphType` | `string` | +| `classDefs` | `undefined` \| `Record`<`string`, `DiagramStyleClassDef`> | +| `svgId` | `string` | #### Returns @@ -202,7 +202,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:233](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L233) +[mermaidAPI.ts:216](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L216) --- @@ -229,7 +229,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:169](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L169) +[mermaidAPI.ts:152](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L152) --- @@ -249,7 +249,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:155](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L155) +[mermaidAPI.ts:138](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L138) --- @@ -269,7 +269,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L126) +[mermaidAPI.ts:109](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L109) --- @@ -295,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code #### Defined in -[mermaidAPI.ts:287](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L287) +[mermaidAPI.ts:270](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L270) --- @@ -320,4 +320,4 @@ Remove any existing elements from the given document #### Defined in -[mermaidAPI.ts:360](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L360) +[mermaidAPI.ts:343](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L343) diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts index 80665cfa2e..0357ad7feb 100644 --- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts +++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts @@ -43,7 +43,11 @@ export const addDiagrams = () => { }, }, styles: {}, // should never be used - renderer: {}, // should never be used + renderer: { + draw: () => { + // should never be used + }, + }, parser: { parser: { yy: {} }, parse: () => { diff --git a/packages/mermaid/src/diagram-api/diagramAPI.spec.ts b/packages/mermaid/src/diagram-api/diagramAPI.spec.ts index b82011f8de..2cafd695ba 100644 --- a/packages/mermaid/src/diagram-api/diagramAPI.spec.ts +++ b/packages/mermaid/src/diagram-api/diagramAPI.spec.ts @@ -41,7 +41,11 @@ describe('DiagramAPI', () => { }, parser: { yy: {} }, }, - renderer: {}, + renderer: { + draw: () => { + // no-op + }, + }, styles: {}, }, detector diff --git a/packages/mermaid/src/diagram-api/types.ts b/packages/mermaid/src/diagram-api/types.ts index 2ac7fba127..45f75fb08e 100644 --- a/packages/mermaid/src/diagram-api/types.ts +++ b/packages/mermaid/src/diagram-api/types.ts @@ -32,9 +32,26 @@ export interface DiagramDB { bindFunctions?: (element: Element) => void; } +// This is what is returned from getClasses(...) methods. +// It is slightly renamed to ..StyleClassDef instead of just ClassDef because "class" is a greatly ambiguous and overloaded word. +// It makes it clear we're working with a style class definition, even though defining the type is currently difficult. +export interface DiagramStyleClassDef { + id: string; + styles?: string[]; + textStyles?: string[]; +} + +export interface DiagramRenderer { + draw: DrawDefinition; + getClasses?: ( + text: string, + diagram: Pick + ) => Record; +} + export interface DiagramDefinition { db: DiagramDB; - renderer: any; + renderer: DiagramRenderer; parser: ParserDefinition; styles?: any; init?: (config: MermaidConfig) => void; @@ -76,7 +93,7 @@ export type DrawDefinition = ( id: string, version: string, diagramObject: Diagram -) => void; +) => void | Promise; export interface ParserDefinition { parse: (text: string) => void; diff --git a/packages/mermaid/src/diagram.spec.ts b/packages/mermaid/src/diagram.spec.ts index 99ce4e2c66..19a65b716b 100644 --- a/packages/mermaid/src/diagram.spec.ts +++ b/packages/mermaid/src/diagram.spec.ts @@ -34,7 +34,11 @@ describe('diagram detection', () => { yy: {}, }, }, - renderer: {}, + renderer: { + draw: () => { + // no-op + }, + }, styles: {}, }, }) diff --git a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js b/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js index c7bfdf5246..f1ad22f675 100644 --- a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js +++ b/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js @@ -4,11 +4,10 @@ import insertMarkers from '../../../dagre-wrapper/markers.js'; import { insertEdgeLabel } from '../../../dagre-wrapper/edges.js'; import { findCommonAncestor } from './render-utils.js'; import { labelHelper } from '../../../dagre-wrapper/shapes/util.js'; -import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js'; import { getConfig } from '../../../config.js'; import { log } from '../../../logger.js'; import { setupGraphViewbox } from '../../../setupGraphViewbox.js'; -import common, { evaluate } from '../../common/common.js'; +import common from '../../common/common.js'; import { interpolateToCurve, getStylesFromArray } from '../../../utils.js'; import ELK from 'elkjs/lib/elk.bundled.js'; const elk = new ELK(); @@ -651,7 +650,7 @@ const addMarkersToEdge = function (svgPath, edgeData, diagramType, arrowMarkerAb * * @param text * @param diagObj - * @returns {object} ClassDef styles + * @returns {Record} ClassDef styles */ export const getClasses = function (text, diagObj) { log.info('Extracting classes'); diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js index 4a3b7a8ce5..576ee6b34d 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js @@ -338,7 +338,7 @@ export const addEdges = function (edges, g, diagObj) { * * @param text * @param diagObj - * @returns {object} ClassDef styles + * @returns {Record} ClassDef styles */ export const getClasses = function (text, diagObj) { return diagObj.db.getClasses(); diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js index fc06cacd4d..8394b41e88 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js @@ -269,7 +269,7 @@ export const addEdges = function (edges, g, diagObj) { * * @param text * @param diagObj - * @returns {object} ClassDef styles + * @returns {Record} ClassDef styles */ export const getClasses = function (text, diagObj) { log.info('Extracting classes'); diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js index 1c9b2d1d3c..0d3117b206 100644 --- a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js +++ b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js @@ -81,7 +81,7 @@ export const setConf = function (cnf) { * * @param {string} text - the diagram text to be parsed * @param diagramObj - * @returns {object} ClassDef styles (a Map with keys = strings, values = ) + * @returns {Record} ClassDef styles (a Map with keys = strings, values = ) */ export const getClasses = function (text, diagramObj) { diagramObj.db.extract(diagramObj.db.getRootDocV2()); diff --git a/packages/mermaid/src/mermaid.spec.ts b/packages/mermaid/src/mermaid.spec.ts index 0b4437d742..645b5b39cb 100644 --- a/packages/mermaid/src/mermaid.spec.ts +++ b/packages/mermaid/src/mermaid.spec.ts @@ -95,8 +95,10 @@ describe('when using mermaid and ', () => { let loaded = false; const dummyDiagram: DiagramDefinition = { db: {}, - renderer: () => { - // do nothing + renderer: { + draw: () => { + // no-op + }, }, parser: { parse: (_text) => { diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index bb75700348..6ea27a809a 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -31,16 +31,8 @@ import isEmpty from 'lodash-es/isEmpty.js'; import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility.js'; import { parseDirective } from './directiveUtils.js'; import { extractFrontMatter } from './diagram-api/frontmatter.js'; +import type { DiagramStyleClassDef } from './diagram-api/types.js'; -// diagram names that support classDef statements -const CLASSDEF_DIAGRAMS = [ - 'graph', - 'flowchart', - 'flowchart-v2', - 'flowchart-elk', - 'stateDiagram', - 'stateDiagram-v2', -]; const MAX_TEXTLENGTH = 50_000; const MAX_TEXTLENGTH_EXCEEDED_MSG = 'graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa'; @@ -65,15 +57,6 @@ const IFRAME_NOT_SUPPORTED_MSG = 'The "iframe" tag is not supported by your brow const DOMPURIFY_TAGS = ['foreignobject']; const DOMPURIFY_ATTR = ['dominant-baseline']; -// This is what is returned from getClasses(...) methods. -// It is slightly renamed to ..StyleClassDef instead of just ClassDef because "class" is a greatly ambiguous and overloaded word. -// It makes it clear we're working with a style class definition, even though defining the type is currently difficult. -interface DiagramStyleClassDef { - id: string; - styles?: string[]; - textStyles?: string[]; -} - export interface ParseOptions { suppressErrors?: boolean; } @@ -204,7 +187,7 @@ export const createCssStyles = ( } // classDefs defined in the diagram text - if (!isEmpty(classDefs) && CLASSDEF_DIAGRAMS.includes(graphType)) { + if (!isEmpty(classDefs)) { const htmlLabels = config.htmlLabels || config.flowchart?.htmlLabels; // TODO why specifically check the Flowchart diagram config? const cssHtmlElements = ['> *', 'span']; // TODO make a constant @@ -233,7 +216,7 @@ export const createCssStyles = ( export const createUserStyles = ( config: MermaidConfig, graphType: string, - classDefs: Record, + classDefs: Record | undefined, svgId: string ): string => { const userCSSstyles = createCssStyles(config, graphType, classDefs); @@ -492,9 +475,7 @@ const render = async function ( // Insert an element into svg. This is where we put the styles const svg = element.firstChild; const firstChild = svg.firstChild; - const diagramClassDefs = CLASSDEF_DIAGRAMS.includes(diagramType) - ? diag.renderer.getClasses(text, diag) - : {}; + const diagramClassDefs = diag.renderer.getClasses?.(text, diag); const rules = createUserStyles(config, diagramType, diagramClassDefs, idSelector); From ef959196919da256662774111e8f81c8b526cca7 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Thu, 7 Sep 2023 12:40:56 +0530 Subject: [PATCH 24/37] refactor: Remove unused variables --- docs/config/setup/modules/mermaidAPI.md | 17 ++++++++--------- packages/mermaid/src/mermaidAPI.spec.ts | 12 +++++------- packages/mermaid/src/mermaidAPI.ts | 4 +--- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index 0d3501576f..b24a849bd7 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -96,7 +96,7 @@ mermaid.initialize(config); #### Defined in -[mermaidAPI.ts:654](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L654) +[mermaidAPI.ts:652](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L652) ## Functions @@ -127,7 +127,7 @@ Return the last node appended #### Defined in -[mermaidAPI.ts:293](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L293) +[mermaidAPI.ts:291](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L291) --- @@ -153,13 +153,13 @@ the cleaned up svgCode #### Defined in -[mermaidAPI.ts:239](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L239) +[mermaidAPI.ts:237](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L237) --- ### createCssStyles -▸ **createCssStyles**(`config`, `graphType`, `classDefs?`): `string` +▸ **createCssStyles**(`config`, `classDefs?`): `string` Create the user styles @@ -168,7 +168,6 @@ Create the user styles | Name | Type | Description | | :---------- | :------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------ | | `config` | `MermaidConfig` | configuration that has style and theme settings to use | -| `graphType` | `string` | used for checking if classDefs should be applied | | `classDefs` | `undefined` \| `null` \| `Record`<`string`, `DiagramStyleClassDef`> | the classDefs in the diagram text. Might be null if none were defined. Usually is the result of a call to getClasses(...) | #### Returns @@ -179,7 +178,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:168](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L168) +[mermaidAPI.ts:167](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L167) --- @@ -202,7 +201,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:216](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L216) +[mermaidAPI.ts:214](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L214) --- @@ -295,7 +294,7 @@ Put the svgCode into an iFrame. Return the iFrame code #### Defined in -[mermaidAPI.ts:270](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L270) +[mermaidAPI.ts:268](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L268) --- @@ -320,4 +319,4 @@ Remove any existing elements from the given document #### Defined in -[mermaidAPI.ts:343](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L343) +[mermaidAPI.ts:341](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L341) diff --git a/packages/mermaid/src/mermaidAPI.spec.ts b/packages/mermaid/src/mermaidAPI.spec.ts index d7c16a1cfb..a79fd44c4a 100644 --- a/packages/mermaid/src/mermaidAPI.spec.ts +++ b/packages/mermaid/src/mermaidAPI.spec.ts @@ -287,15 +287,15 @@ describe('mermaidAPI', () => { }; it('gets the cssStyles from the theme', () => { - const styles = createCssStyles(mocked_config_with_htmlLabels, 'graphType', null); + const styles = createCssStyles(mocked_config_with_htmlLabels, null); expect(styles).toMatch(/^\ndefault(.*)/); }); it('gets the fontFamily from the config', () => { - const styles = createCssStyles(mocked_config_with_htmlLabels, 'graphType', {}); + const styles = createCssStyles(mocked_config_with_htmlLabels, {}); expect(styles).toMatch(/(.*)\n:root { --mermaid-font-family: serif(.*)/); }); it('gets the alt fontFamily from the config', () => { - const styles = createCssStyles(mocked_config_with_htmlLabels, 'graphType', undefined); + const styles = createCssStyles(mocked_config_with_htmlLabels, undefined); expect(styles).toMatch(/(.*)\n:root { --mermaid-alt-font-family: sans-serif(.*)/); }); @@ -306,8 +306,6 @@ describe('mermaidAPI', () => { const classDefs = { classDef1, classDef2, classDef3 }; describe('the graph supports classDefs', () => { - const graphType = 'flowchart-v2'; - const REGEXP_SPECIALS = ['^', '$', '?', '(', '{', '[', '.', '*', '!']; // prefix any special RegExp characters in the given string with a \ so we can use the literal character in a RegExp @@ -373,7 +371,7 @@ describe('mermaidAPI', () => { // @todo TODO Can't figure out how to spy on the cssImportantStyles method. // That would be a much better approach than manually checking the result - const styles = createCssStyles(mocked_config, graphType, classDefs); + const styles = createCssStyles(mocked_config, classDefs); htmlElements.forEach((htmlElement) => { expect_styles_matchesHtmlElements(styles, htmlElement); }); @@ -411,7 +409,7 @@ describe('mermaidAPI', () => { it('creates CSS styles for every style and textStyle in every classDef', () => { // TODO Can't figure out how to spy on the cssImportantStyles method. That would be a much better approach than manually checking the result. - const styles = createCssStyles(mocked_config_no_htmlLabels, graphType, classDefs); + const styles = createCssStyles(mocked_config_no_htmlLabels, classDefs); htmlElements.forEach((htmlElement) => { expect_styles_matchesHtmlElements(styles, htmlElement); }); diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index 6ea27a809a..da884fde73 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -161,13 +161,11 @@ export const cssImportantStyles = ( * Create the user styles * * @param config - configuration that has style and theme settings to use - * @param graphType - used for checking if classDefs should be applied * @param classDefs - the classDefs in the diagram text. Might be null if none were defined. Usually is the result of a call to getClasses(...) * @returns the string with all the user styles */ export const createCssStyles = ( config: MermaidConfig, - graphType: string, classDefs: Record | null | undefined = {} ): string => { let cssStyles = ''; @@ -219,7 +217,7 @@ export const createUserStyles = ( classDefs: Record | undefined, svgId: string ): string => { - const userCSSstyles = createCssStyles(config, graphType, classDefs); + const userCSSstyles = createCssStyles(config, classDefs); const allStyles = getStyles(graphType, userCSSstyles, config.themeVariables); // Now turn all of the styles into a (compiled) string that starts with the id From e75af86ef25c3af7ee54ba04f39d38be5c7beea1 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Thu, 7 Sep 2023 12:46:58 +0530 Subject: [PATCH 25/37] chore: Bump version --- packages/mermaid/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 7b4de70a98..e208d561e5 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -1,6 +1,6 @@ { "name": "mermaid", - "version": "10.4.0", + "version": "10.5.0-alpha.1", "description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "type": "module", "module": "./dist/mermaid.core.mjs", From 6eb3337d20d3f1d91d1167cb74d10dd782a2b040 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 8 Sep 2023 08:20:03 +0530 Subject: [PATCH 26/37] chore: Add @internal to createCSSStyles --- packages/mermaid/src/mermaidAPI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index 12dea484b7..5250f0b190 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -167,7 +167,7 @@ export const cssImportantStyles = ( /** * Create the user styles - * + * @internal * @param config - configuration that has style and theme settings to use * @param classDefs - the classDefs in the diagram text. Might be null if none were defined. Usually is the result of a call to getClasses(...) * @returns the string with all the user styles From 552d3ec2e1198f41fa8701ff2cc0a7d4dbe939bf Mon Sep 17 00:00:00 2001 From: Chad Fawcett Date: Fri, 8 Sep 2023 15:28:41 -0700 Subject: [PATCH 27/37] Give markers unique id's per graph --- packages/mermaid/src/dagre-wrapper/edges.js | 80 ++++++++++++++----- packages/mermaid/src/dagre-wrapper/index.js | 8 +- packages/mermaid/src/dagre-wrapper/markers.js | 50 ++++++------ 3 files changed, 90 insertions(+), 48 deletions(-) diff --git a/packages/mermaid/src/dagre-wrapper/edges.js b/packages/mermaid/src/dagre-wrapper/edges.js index f89b4422be..babb0ea6a9 100644 --- a/packages/mermaid/src/dagre-wrapper/edges.js +++ b/packages/mermaid/src/dagre-wrapper/edges.js @@ -382,7 +382,7 @@ function calculateDeltaAndAngle(point1, point2) { return { angle: Math.atan(deltaY / deltaX), deltaX, deltaY }; } -export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph) { +export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph, id) { let points = edge.points; let pointsHasChanged = false; const tail = graph.node(e.v); @@ -569,61 +569,103 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph switch (edge.arrowTypeStart) { case 'arrow_cross': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-crossStart' + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + id + '_' + diagramType + '-crossStart' + ')' + ); break; case 'arrow_point': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-pointStart' + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + id + '_' + diagramType + '-pointStart' + ')' + ); break; case 'arrow_barb': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-barbStart' + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + id + '_' + diagramType + '-barbStart' + ')' + ); break; case 'arrow_circle': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-circleStart' + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + id + '_' + diagramType + '-circleStart' + ')' + ); break; case 'aggregation': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-aggregationStart' + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + id + '_' + diagramType + '-aggregationStart' + ')' + ); break; case 'extension': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-extensionStart' + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + id + '_' + diagramType + '-extensionStart' + ')' + ); break; case 'composition': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-compositionStart' + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + id + '_' + diagramType + '-compositionStart' + ')' + ); break; case 'dependency': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-dependencyStart' + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + id + '_' + diagramType + '-dependencyStart' + ')' + ); break; case 'lollipop': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-lollipopStart' + ')'); + svgPath.attr( + 'marker-start', + 'url(' + url + '#' + id + '_' + diagramType + '-lollipopStart' + ')' + ); break; default: } switch (edge.arrowTypeEnd) { case 'arrow_cross': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-crossEnd' + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-crossEnd' + ')'); break; case 'arrow_point': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-pointEnd' + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-pointEnd' + ')'); break; case 'arrow_barb': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-barbEnd' + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-barbEnd' + ')'); break; case 'arrow_circle': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-circleEnd' + ')'); + svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-circleEnd' + ')'); break; case 'aggregation': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-aggregationEnd' + ')'); + svgPath.attr( + 'marker-end', + 'url(' + url + '#' + id + '_' + diagramType + '-aggregationEnd' + ')' + ); break; case 'extension': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-extensionEnd' + ')'); + svgPath.attr( + 'marker-end', + 'url(' + url + '#' + id + '_' + diagramType + '-extensionEnd' + ')' + ); break; case 'composition': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-compositionEnd' + ')'); + svgPath.attr( + 'marker-end', + 'url(' + url + '#' + id + '_' + diagramType + '-compositionEnd' + ')' + ); break; case 'dependency': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-dependencyEnd' + ')'); + svgPath.attr( + 'marker-end', + 'url(' + url + '#' + id + '_' + diagramType + '-dependencyEnd' + ')' + ); break; case 'lollipop': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-lollipopEnd' + ')'); + svgPath.attr( + 'marker-end', + 'url(' + url + '#' + id + '_' + diagramType + '-lollipopEnd' + ')' + ); break; default: } diff --git a/packages/mermaid/src/dagre-wrapper/index.js b/packages/mermaid/src/dagre-wrapper/index.js index 279c5d9ddd..9843adb8b0 100644 --- a/packages/mermaid/src/dagre-wrapper/index.js +++ b/packages/mermaid/src/dagre-wrapper/index.js @@ -14,7 +14,7 @@ import { insertCluster, clear as clearClusters } from './clusters.js'; import { insertEdgeLabel, positionEdgeLabel, insertEdge, clear as clearEdges } from './edges.js'; import { log } from '../logger.js'; -const recursiveRender = async (_elem, graph, diagramtype, parentCluster) => { +const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster) => { log.info('Graph in recursive render: XXX', graphlibJson.write(graph), parentCluster); const dir = graph.graph().rankdir; log.trace('Dir in recursive render - dir:', dir); @@ -52,7 +52,7 @@ const recursiveRender = async (_elem, graph, diagramtype, parentCluster) => { if (node && node.clusterNode) { // const children = graph.children(v); log.info('Cluster identified', v, node.width, graph.node(v)); - const o = await recursiveRender(nodes, node.graph, diagramtype, graph.node(v)); + const o = await recursiveRender(nodes, node.graph, diagramtype, id, graph.node(v)); const newEl = o.elem; updateNodeBounds(node, newEl); node.diff = o.diff || 0; @@ -134,7 +134,7 @@ const recursiveRender = async (_elem, graph, diagramtype, parentCluster) => { const edge = graph.edge(e); log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge); - const paths = insertEdge(edgePaths, e, edge, clusterDb, diagramtype, graph); + const paths = insertEdge(edgePaths, e, edge, clusterDb, diagramtype, graph, id); positionEdgeLabel(edge, paths); }); @@ -159,7 +159,7 @@ export const render = async (elem, graph, markers, diagramtype, id) => { adjustClustersAndEdges(graph); log.warn('Graph after:', JSON.stringify(graphlibJson.write(graph))); // log.warn('Graph ever after:', graphlibJson.write(graph.node('A').graph)); - await recursiveRender(elem, graph, diagramtype); + await recursiveRender(elem, graph, diagramtype, id); }; // const shapeDefinitions = {}; diff --git a/packages/mermaid/src/dagre-wrapper/markers.js b/packages/mermaid/src/dagre-wrapper/markers.js index 051c987f62..a499b8f263 100644 --- a/packages/mermaid/src/dagre-wrapper/markers.js +++ b/packages/mermaid/src/dagre-wrapper/markers.js @@ -14,7 +14,7 @@ const extension = (elem, type, id) => { elem .append('defs') .append('marker') - .attr('id', type + '-extensionStart') + .attr('id', id + '_' + type + '-extensionStart') .attr('class', 'marker extension ' + type) .attr('refX', 18) .attr('refY', 7) @@ -27,7 +27,7 @@ const extension = (elem, type, id) => { elem .append('defs') .append('marker') - .attr('id', type + '-extensionEnd') + .attr('id', id + '_' + type + '-extensionEnd') .attr('class', 'marker extension ' + type) .attr('refX', 1) .attr('refY', 7) @@ -38,11 +38,11 @@ const extension = (elem, type, id) => { .attr('d', 'M 1,1 V 13 L18,7 Z'); // this is actual shape for arrowhead }; -const composition = (elem, type) => { +const composition = (elem, type, id) => { elem .append('defs') .append('marker') - .attr('id', type + '-compositionStart') + .attr('id', id + '_' + type + '-compositionStart') .attr('class', 'marker composition ' + type) .attr('refX', 18) .attr('refY', 7) @@ -55,7 +55,7 @@ const composition = (elem, type) => { elem .append('defs') .append('marker') - .attr('id', type + '-compositionEnd') + .attr('id', id + '_' + type + '-compositionEnd') .attr('class', 'marker composition ' + type) .attr('refX', 1) .attr('refY', 7) @@ -65,11 +65,11 @@ const composition = (elem, type) => { .append('path') .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z'); }; -const aggregation = (elem, type) => { +const aggregation = (elem, type, id) => { elem .append('defs') .append('marker') - .attr('id', type + '-aggregationStart') + .attr('id', id + '_' + type + '-aggregationStart') .attr('class', 'marker aggregation ' + type) .attr('refX', 18) .attr('refY', 7) @@ -82,7 +82,7 @@ const aggregation = (elem, type) => { elem .append('defs') .append('marker') - .attr('id', type + '-aggregationEnd') + .attr('id', id + '_' + type + '-aggregationEnd') .attr('class', 'marker aggregation ' + type) .attr('refX', 1) .attr('refY', 7) @@ -92,11 +92,11 @@ const aggregation = (elem, type) => { .append('path') .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z'); }; -const dependency = (elem, type) => { +const dependency = (elem, type, id) => { elem .append('defs') .append('marker') - .attr('id', type + '-dependencyStart') + .attr('id', id + '_' + type + '-dependencyStart') .attr('class', 'marker dependency ' + type) .attr('refX', 6) .attr('refY', 7) @@ -109,7 +109,7 @@ const dependency = (elem, type) => { elem .append('defs') .append('marker') - .attr('id', type + '-dependencyEnd') + .attr('id', id + '_' + type + '-dependencyEnd') .attr('class', 'marker dependency ' + type) .attr('refX', 13) .attr('refY', 7) @@ -119,11 +119,11 @@ const dependency = (elem, type) => { .append('path') .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z'); }; -const lollipop = (elem, type) => { +const lollipop = (elem, type, id) => { elem .append('defs') .append('marker') - .attr('id', type + '-lollipopStart') + .attr('id', id + '_' + type + '-lollipopStart') .attr('class', 'marker lollipop ' + type) .attr('refX', 13) .attr('refY', 7) @@ -140,7 +140,7 @@ const lollipop = (elem, type) => { elem .append('defs') .append('marker') - .attr('id', type + '-lollipopEnd') + .attr('id', id + '_' + type + '-lollipopEnd') .attr('class', 'marker lollipop ' + type) .attr('refX', 1) .attr('refY', 7) @@ -154,10 +154,10 @@ const lollipop = (elem, type) => { .attr('cy', 7) .attr('r', 6); }; -const point = (elem, type) => { +const point = (elem, type, id) => { elem .append('marker') - .attr('id', type + '-pointEnd') + .attr('id', id + '_' + type + '-pointEnd') .attr('class', 'marker ' + type) .attr('viewBox', '0 0 10 10') .attr('refX', 6) @@ -173,7 +173,7 @@ const point = (elem, type) => { .style('stroke-dasharray', '1,0'); elem .append('marker') - .attr('id', type + '-pointStart') + .attr('id', id + '_' + type + '-pointStart') .attr('class', 'marker ' + type) .attr('viewBox', '0 0 10 10') .attr('refX', 0) @@ -188,10 +188,10 @@ const point = (elem, type) => { .style('stroke-width', 1) .style('stroke-dasharray', '1,0'); }; -const circle = (elem, type) => { +const circle = (elem, type, id) => { elem .append('marker') - .attr('id', type + '-circleEnd') + .attr('id', id + '_' + type + '-circleEnd') .attr('class', 'marker ' + type) .attr('viewBox', '0 0 10 10') .attr('refX', 11) @@ -210,7 +210,7 @@ const circle = (elem, type) => { elem .append('marker') - .attr('id', type + '-circleStart') + .attr('id', id + '_' + type + '-circleStart') .attr('class', 'marker ' + type) .attr('viewBox', '0 0 10 10') .attr('refX', -1) @@ -227,10 +227,10 @@ const circle = (elem, type) => { .style('stroke-width', 1) .style('stroke-dasharray', '1,0'); }; -const cross = (elem, type) => { +const cross = (elem, type, id) => { elem .append('marker') - .attr('id', type + '-crossEnd') + .attr('id', id + '_' + type + '-crossEnd') .attr('class', 'marker cross ' + type) .attr('viewBox', '0 0 11 11') .attr('refX', 12) @@ -248,7 +248,7 @@ const cross = (elem, type) => { elem .append('marker') - .attr('id', type + '-crossStart') + .attr('id', id + '_' + type + '-crossStart') .attr('class', 'marker cross ' + type) .attr('viewBox', '0 0 11 11') .attr('refX', -1) @@ -264,11 +264,11 @@ const cross = (elem, type) => { .style('stroke-width', 2) .style('stroke-dasharray', '1,0'); }; -const barb = (elem, type) => { +const barb = (elem, type, id) => { elem .append('defs') .append('marker') - .attr('id', type + '-barbEnd') + .attr('id', id + '_' + type + '-barbEnd') .attr('refX', 19) .attr('refY', 7) .attr('markerWidth', 20) From f3e0d5a20ab98eccab1751b9f9c8a5724b65ba0c Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Sat, 9 Sep 2023 11:15:55 +0530 Subject: [PATCH 28/37] refactor: Fix typings in utils.ts --- cSpell.json | 1 + package.json | 1 + .../quadrant-chart/quadrantBuilder.ts | 6 +- packages/mermaid/src/mermaid.ts | 2 +- packages/mermaid/src/types.ts | 16 + packages/mermaid/src/utils.spec.ts | 112 ++++++- packages/mermaid/src/utils.ts | 317 +++++++----------- 7 files changed, 258 insertions(+), 197 deletions(-) create mode 100644 packages/mermaid/src/types.ts diff --git a/cSpell.json b/cSpell.json index e8d718316c..e67c7d48e6 100644 --- a/cSpell.json +++ b/cSpell.json @@ -22,6 +22,7 @@ "brkt", "brolin", "brotli", + "catmull", "città", "classdef", "codedoc", diff --git a/package.json b/package.json index 232f23be1d..bc081f36ed 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "build:mermaid": "pnpm build:vite --mermaid", "build:viz": "pnpm build:mermaid --visualize", "build:types": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-zenuml/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-example-diagram/tsconfig.json --emitDeclarationOnly", + "build:types:watch": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly --watch", "build:watch": "pnpm build:vite --watch", "build": "pnpm run -r clean && pnpm build:types && pnpm build:vite", "dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server.ts\"", diff --git a/packages/mermaid/src/diagrams/quadrant-chart/quadrantBuilder.ts b/packages/mermaid/src/diagrams/quadrant-chart/quadrantBuilder.ts index 9c11627620..5b740b0e0e 100644 --- a/packages/mermaid/src/diagrams/quadrant-chart/quadrantBuilder.ts +++ b/packages/mermaid/src/diagrams/quadrant-chart/quadrantBuilder.ts @@ -4,17 +4,13 @@ import { log } from '../../logger.js'; import type { BaseDiagramConfig, QuadrantChartConfig } from '../../config.type.js'; import defaultConfig from '../../defaultConfig.js'; import { getThemeVariables } from '../../themes/theme-default.js'; +import type { Point } from '../../types.js'; const defaultThemeVariables = getThemeVariables(); export type TextVerticalPos = 'left' | 'center' | 'right'; export type TextHorizontalPos = 'top' | 'middle' | 'bottom'; -export interface Point { - x: number; - y: number; -} - export interface QuadrantPointInputType extends Point { text: string; } diff --git a/packages/mermaid/src/mermaid.ts b/packages/mermaid/src/mermaid.ts index caf4a2b9b3..a6d4954714 100644 --- a/packages/mermaid/src/mermaid.ts +++ b/packages/mermaid/src/mermaid.ts @@ -136,7 +136,7 @@ const runThrowsErrors = async function ( } // generate the id of the diagram - const idGenerator = new utils.initIdGenerator(conf.deterministicIds, conf.deterministicIDSeed); + const idGenerator = new utils.InitIDGenerator(conf.deterministicIds, conf.deterministicIDSeed); let txt: string; const errors: DetailedError[] = []; diff --git a/packages/mermaid/src/types.ts b/packages/mermaid/src/types.ts new file mode 100644 index 0000000000..4b9eedad6c --- /dev/null +++ b/packages/mermaid/src/types.ts @@ -0,0 +1,16 @@ +export interface Point { + x: number; + y: number; +} + +export interface TextDimensionConfig { + fontSize?: number; + fontWeight?: number; + fontFamily?: string; +} + +export interface TextDimensions { + width: number; + height: number; + lineHeight?: number; +} diff --git a/packages/mermaid/src/utils.spec.ts b/packages/mermaid/src/utils.spec.ts index e1398efc73..3be3bc2141 100644 --- a/packages/mermaid/src/utils.spec.ts +++ b/packages/mermaid/src/utils.spec.ts @@ -1,5 +1,5 @@ import { vi } from 'vitest'; -import utils, { cleanAndMerge, detectDirective } from './utils.js'; +import utils, { calculatePoint, cleanAndMerge, detectDirective } from './utils.js'; import assignWithDepth from './assignWithDepth.js'; import { detectType } from './diagram-api/detectType.js'; import { addDiagrams } from './diagram-api/diagram-orchestration.js'; @@ -352,7 +352,7 @@ describe('when initializing the id generator', function () { }); it('should return a random number generator based on Date', function () { - const idGenerator = new utils.initIdGenerator(false); + const idGenerator = new utils.InitIDGenerator(false); expect(typeof idGenerator.next).toEqual('function'); const lastId = idGenerator.next(); vi.advanceTimersByTime(1000); @@ -360,7 +360,7 @@ describe('when initializing the id generator', function () { }); it('should return a non random number generator', function () { - const idGenerator = new utils.initIdGenerator(true); + const idGenerator = new utils.InitIDGenerator(true); expect(typeof idGenerator.next).toEqual('function'); const start = 0; const lastId = idGenerator.next(); @@ -369,7 +369,7 @@ describe('when initializing the id generator', function () { }); it('should return a non random number generator based on seed', function () { - const idGenerator = new utils.initIdGenerator(true, 'thisIsASeed'); + const idGenerator = new utils.InitIDGenerator(true, 'thisIsASeed'); expect(typeof idGenerator.next).toEqual('function'); const start = 11; const lastId = idGenerator.next(); @@ -490,3 +490,107 @@ describe('cleanAndMerge', () => { expect(inputDeep).toEqual({ a: { b: 1 } }); }); }); + +describe('calculatePoint', () => { + it('should calculate a point on a straight line', () => { + const points = [ + { x: 0, y: 0 }, + { x: 0, y: 10 }, + { x: 0, y: 20 }, + ]; + expect(calculatePoint(points, 0)).toEqual({ x: 0, y: 0 }); + expect(calculatePoint(points, 5)).toEqual({ x: 0, y: 5 }); + expect(calculatePoint(points, 10)).toEqual({ x: 0, y: 10 }); + }); + + it('should calculate a point on a straight line with slope', () => { + const points = [ + { x: 0, y: 0 }, + { x: 10, y: 10 }, + { x: 20, y: 20 }, + ]; + expect(calculatePoint(points, 0)).toMatchInlineSnapshot(` + { + "x": 0, + "y": 0, + } + `); + expect(calculatePoint(points, 5)).toMatchInlineSnapshot(` + { + "x": 3.53553, + "y": 3.53553, + } + `); + expect(calculatePoint(points, 10)).toMatchInlineSnapshot(` + { + "x": 7.07107, + "y": 7.07107, + } + `); + }); + + it('should calculate a point on a straight line with negative slope', () => { + const points = [ + { x: 20, y: 20 }, + { x: 10, y: 10 }, + { x: 15, y: 15 }, + { x: 0, y: 0 }, + ]; + expect(calculatePoint(points, 0)).toMatchInlineSnapshot(` + { + "x": 20, + "y": 20, + } + `); + expect(calculatePoint(points, 5)).toMatchInlineSnapshot(` + { + "x": 16.46447, + "y": 16.46447, + } + `); + expect(calculatePoint(points, 10)).toMatchInlineSnapshot(` + { + "x": 12.92893, + "y": 12.92893, + } + `); + }); + + it('should calculate a point on a curved line', () => { + const points = [ + { x: 0, y: 0 }, + { x: 10, y: 10 }, + { x: 20, y: 0 }, + ]; + expect(calculatePoint(points, 0)).toMatchInlineSnapshot(` + { + "x": 0, + "y": 0, + } + `); + expect(calculatePoint(points, 15)).toMatchInlineSnapshot(` + { + "x": 10.6066, + "y": 9.3934, + } + `); + expect(calculatePoint(points, 20)).toMatchInlineSnapshot(` + { + "x": 14.14214, + "y": 5.85786, + } + `); + }); + + it('should throw an error if the new point cannot be found', () => { + const points = [ + { x: 0, y: 0 }, + { x: 10, y: 10 }, + { x: 20, y: 20 }, + ]; + const distanceToTraverse = 30; + expect(() => calculatePoint(points, distanceToTraverse)).toThrow( + 'Could not find a suitable point for the given distance' + ); + }); +}); diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts index 70de197dad..e706ef1227 100644 --- a/packages/mermaid/src/utils.ts +++ b/packages/mermaid/src/utils.ts @@ -1,4 +1,3 @@ -// @ts-nocheck : TODO Fix ts errors import { sanitizeUrl } from '@braintree/sanitize-url'; import type { CurveFactory } from 'd3'; import { @@ -33,6 +32,8 @@ import type { MermaidConfig } from './config.type.js'; import memoize from 'lodash-es/memoize.js'; import merge from 'lodash-es/merge.js'; import { directiveRegex } from './diagram-api/regexes.js'; +import type { D3Element } from './mermaidAPI.js'; +import type { Point, TextDimensionConfig, TextDimensions } from './types.js'; export const ZERO_WIDTH_SPACE = '\u200b'; @@ -58,7 +59,7 @@ const d3CurveTypes = { curveStep: curveStep, curveStepAfter: curveStepAfter, curveStepBefore: curveStepBefore, -}; +} as const; const directiveWithoutOpen = /\s*(?:(\w+)(?=:):|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi; @@ -101,14 +102,14 @@ export const detectInit = function ( config?: MermaidConfig ): MermaidConfig | undefined { const inits = detectDirective(text, /(?:init\b)|(?:initialize\b)/); - let results = {}; + let results: MermaidConfig & { config?: unknown } = {}; if (Array.isArray(inits)) { const args = inits.map((init) => init.args); sanitizeDirective(args); results = assignWithDepth(results, [...args]); } else { - results = inits.args; + results = inits.args as MermaidConfig; } if (!results) { @@ -116,19 +117,24 @@ export const detectInit = function ( } let type = detectType(text, config); - ['config'].forEach((prop) => { - if (results[prop] !== undefined) { - if (type === 'flowchart-v2') { - type = 'flowchart'; - } - results[type] = results[prop]; - delete results[prop]; + + // Move the `config` value to appropriate diagram type value + const prop = 'config'; + if (results[prop] !== undefined) { + if (type === 'flowchart-v2') { + type = 'flowchart'; } - }); + results[type as keyof MermaidConfig] = results[prop]; + delete results[prop]; + } return results; }; +interface Directive { + type?: string; + args?: unknown; +} /** * Detects the directive from the text. * @@ -154,8 +160,8 @@ export const detectInit = function ( */ export const detectDirective = function ( text: string, - type: string | RegExp = null -): { type?: string; args?: any } | { type?: string; args?: any }[] { + type: string | RegExp | null = null +): Directive | Directive[] { try { const commentWithoutDirectives = new RegExp( `[%]{2}(?![{]${directiveWithoutOpen.source})(?=[}][%]{2}).*\n`, @@ -165,8 +171,8 @@ export const detectDirective = function ( log.debug( `Detecting diagram directive${type !== null ? ' type:' + type : ''} based on the text:${text}` ); - let match; - const result = []; + let match: RegExpExecArray | null; + const result: Directive[] = []; while ((match = directiveRegex.exec(text)) !== null) { // This is necessary to avoid infinite loops with zero-width matches if (match.index === directiveRegex.lastIndex) { @@ -183,16 +189,17 @@ export const detectDirective = function ( } } if (result.length === 0) { - result.push({ type: text, args: null }); + return { type: text, args: null }; } return result.length === 1 ? result[0] : result; } catch (error) { log.error( - `ERROR: ${error.message} - Unable to parse directive - ${type !== null ? ' type:' + type : ''} based on the text:${text}` + `ERROR: ${ + (error as Error).message + } - Unable to parse directive type: '${type}' based on the text: '${text}'` ); - return { type: null, args: null }; + return { type: undefined, args: null }; } }; @@ -231,7 +238,9 @@ export function interpolateToCurve( return defaultCurve; } const curveName = `curve${interpolate.charAt(0).toUpperCase() + interpolate.slice(1)}`; - return d3CurveTypes[curveName] || defaultCurve; + + // @ts-ignore TODO: Fix issue with curve type + return d3CurveTypes[curveName as keyof typeof d3CurveTypes] ?? defaultCurve; } /** @@ -244,13 +253,15 @@ export function interpolateToCurve( export function formatUrl(linkStr: string, config: MermaidConfig): string | undefined { const url = linkStr.trim(); - if (url) { - if (config.securityLevel !== 'loose') { - return sanitizeUrl(url); - } + if (!url) { + return undefined; + } - return url; + if (config.securityLevel !== 'loose') { + return sanitizeUrl(url); } + + return url; } /** @@ -259,7 +270,7 @@ export function formatUrl(linkStr: string, config: MermaidConfig): string | unde * @param functionName - A dot separated path to the function relative to the `window` * @param params - Parameters to pass to the function */ -export const runFunc = (functionName: string, ...params) => { +export const runFunc = (functionName: string, ...params: unknown[]) => { const arrPaths = functionName.split('.'); const len = arrPaths.length - 1; @@ -267,23 +278,16 @@ export const runFunc = (functionName: string, ...params) => { let obj = window; for (let i = 0; i < len; i++) { - obj = obj[arrPaths[i]]; + obj = obj[arrPaths[i] as keyof typeof obj]; if (!obj) { + log.error(`Function name: ${functionName} not found in window`); return; } } - obj[fnName](...params); + obj[fnName as keyof typeof obj](...params); }; -/** A (x, y) point */ -interface Point { - /** The x value */ - x: number; - /** The y value */ - y: number; -} - /** * Finds the distance between two points using the Distance Formula * @@ -291,8 +295,11 @@ interface Point { * @param p2 - The second point * @returns The distance between the two points. */ -function distance(p1: Point, p2: Point): number { - return p1 && p2 ? Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)) : 0; +function distance(p1?: Point, p2?: Point): number { + if (!p1 || !p2) { + return 0; + } + return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); } /** @@ -301,7 +308,7 @@ function distance(p1: Point, p2: Point): number { * @param points - List of points */ function traverseEdge(points: Point[]): Point { - let prevPoint; + let prevPoint: Point | undefined; let totalDistance = 0; points.forEach((point) => { @@ -310,35 +317,8 @@ function traverseEdge(points: Point[]): Point { }); // Traverse half of total distance along points - let remainingDistance = totalDistance / 2; - let center = undefined; - prevPoint = undefined; - points.forEach((point) => { - if (prevPoint && !center) { - const vectorDistance = distance(point, prevPoint); - if (vectorDistance < remainingDistance) { - remainingDistance -= vectorDistance; - } else { - // The point is remainingDistance from prevPoint in the vector between prevPoint and point - // Calculate the coordinates - const distanceRatio = remainingDistance / vectorDistance; - if (distanceRatio <= 0) { - center = prevPoint; - } - if (distanceRatio >= 1) { - center = { x: point.x, y: point.y }; - } - if (distanceRatio > 0 && distanceRatio < 1) { - center = { - x: (1 - distanceRatio) * prevPoint.x + distanceRatio * point.x, - y: (1 - distanceRatio) * prevPoint.y + distanceRatio * point.y, - }; - } - } - } - prevPoint = point; - }); - return center; + const remainingDistance = totalDistance / 2; + return calculatePoint(points, remainingDistance); } /** @@ -351,20 +331,16 @@ function calcLabelPosition(points: Point[]): Point { return traverseEdge(points); } -const calcCardinalityPosition = (isRelationTypePresent, points, initialPosition) => { - let prevPoint; - log.info(`our points ${JSON.stringify(points)}`); - if (points[0] !== initialPosition) { - points = points.reverse(); - } - // Traverse only 25 total distance along points to find cardinality point - const distanceToCardinalityPoint = 25; +export const roundNumber = (num: number, precision = 2) => { + const factor = Math.pow(10, precision); + return Math.round(num * factor) / factor; +}; - let remainingDistance = distanceToCardinalityPoint; - let center; - prevPoint = undefined; - points.forEach((point) => { - if (prevPoint && !center) { +export const calculatePoint = (points: Point[], distanceToTraverse: number): Point => { + let prevPoint: Point | undefined = undefined; + let remainingDistance = distanceToTraverse; + for (const point of points) { + if (prevPoint) { const vectorDistance = distance(point, prevPoint); if (vectorDistance < remainingDistance) { remainingDistance -= vectorDistance; @@ -373,27 +349,42 @@ const calcCardinalityPosition = (isRelationTypePresent, points, initialPosition) // Calculate the coordinates const distanceRatio = remainingDistance / vectorDistance; if (distanceRatio <= 0) { - center = prevPoint; + return prevPoint; } if (distanceRatio >= 1) { - center = { x: point.x, y: point.y }; + return { x: point.x, y: point.y }; } if (distanceRatio > 0 && distanceRatio < 1) { - center = { - x: (1 - distanceRatio) * prevPoint.x + distanceRatio * point.x, - y: (1 - distanceRatio) * prevPoint.y + distanceRatio * point.y, + return { + x: roundNumber((1 - distanceRatio) * prevPoint.x + distanceRatio * point.x, 5), + y: roundNumber((1 - distanceRatio) * prevPoint.y + distanceRatio * point.y, 5), }; } } } prevPoint = point; - }); + } + throw new Error('Could not find a suitable point for the given distance'); +}; + +const calcCardinalityPosition = ( + isRelationTypePresent: boolean, + points: Point[], + initialPosition: Point +) => { + log.info(`our points ${JSON.stringify(points)}`); + if (points[0] !== initialPosition) { + points = points.reverse(); + } + // Traverse only 25 total distance along points to find cardinality point + const distanceToCardinalityPoint = 25; + const center = calculatePoint(points, distanceToCardinalityPoint); // if relation is present (Arrows will be added), change cardinality point off-set distance (d) const d = isRelationTypePresent ? 10 : 5; //Calculate Angle for x and y axis const angle = Math.atan2(points[0].y - center.y, points[0].x - center.x); const cardinalityPosition = { x: 0, y: 0 }; - //Calculation cardinality position using angle, center point on the line/curve but pendicular and with offset-distance + //Calculation cardinality position using angle, center point on the line/curve but perpendicular and with offset-distance cardinalityPosition.x = Math.sin(angle) * d + (points[0].x + center.x) / 2; cardinalityPosition.y = -Math.cos(angle) * d + (points[0].y + center.y) / 2; return cardinalityPosition; @@ -412,71 +403,36 @@ function calcTerminalLabelPosition( position: 'start_left' | 'start_right' | 'end_left' | 'end_right', _points: Point[] ): Point { - // Todo looking to faster cloning method - let points = JSON.parse(JSON.stringify(_points)); - let prevPoint; + const points = structuredClone(_points); log.info('our points', points); if (position !== 'start_left' && position !== 'start_right') { - points = points.reverse(); + points.reverse(); } - points.forEach((point) => { - prevPoint = point; - }); - // Traverse only 25 total distance along points to find cardinality point const distanceToCardinalityPoint = 25 + terminalMarkerSize; + const center = calculatePoint(points, distanceToCardinalityPoint); - let remainingDistance = distanceToCardinalityPoint; - let center; - prevPoint = undefined; - points.forEach((point) => { - if (prevPoint && !center) { - const vectorDistance = distance(point, prevPoint); - if (vectorDistance < remainingDistance) { - remainingDistance -= vectorDistance; - } else { - // The point is remainingDistance from prevPoint in the vector between prevPoint and point - // Calculate the coordinates - const distanceRatio = remainingDistance / vectorDistance; - if (distanceRatio <= 0) { - center = prevPoint; - } - if (distanceRatio >= 1) { - center = { x: point.x, y: point.y }; - } - if (distanceRatio > 0 && distanceRatio < 1) { - center = { - x: (1 - distanceRatio) * prevPoint.x + distanceRatio * point.x, - y: (1 - distanceRatio) * prevPoint.y + distanceRatio * point.y, - }; - } - } - } - prevPoint = point; - }); // if relation is present (Arrows will be added), change cardinality point off-set distance (d) const d = 10 + terminalMarkerSize * 0.5; //Calculate Angle for x and y axis const angle = Math.atan2(points[0].y - center.y, points[0].x - center.x); - const cardinalityPosition = { x: 0, y: 0 }; - - //Calculation cardinality position using angle, center point on the line/curve but pendicular and with offset-distance + const cardinalityPosition: Point = { x: 0, y: 0 }; + //Calculation cardinality position using angle, center point on the line/curve but perpendicular and with offset-distance - cardinalityPosition.x = Math.sin(angle) * d + (points[0].x + center.x) / 2; - cardinalityPosition.y = -Math.cos(angle) * d + (points[0].y + center.y) / 2; if (position === 'start_left') { cardinalityPosition.x = Math.sin(angle + Math.PI) * d + (points[0].x + center.x) / 2; cardinalityPosition.y = -Math.cos(angle + Math.PI) * d + (points[0].y + center.y) / 2; - } - if (position === 'end_right') { + } else if (position === 'end_right') { cardinalityPosition.x = Math.sin(angle - Math.PI) * d + (points[0].x + center.x) / 2 - 5; cardinalityPosition.y = -Math.cos(angle - Math.PI) * d + (points[0].y + center.y) / 2 - 5; - } - if (position === 'end_left') { + } else if (position === 'end_left') { cardinalityPosition.x = Math.sin(angle) * d + (points[0].x + center.x) / 2 - 5; cardinalityPosition.y = -Math.cos(angle) * d + (points[0].y + center.y) / 2 - 5; + } else { + cardinalityPosition.x = Math.sin(angle) * d + (points[0].x + center.x) / 2; + cardinalityPosition.y = -Math.cos(angle) * d + (points[0].y + center.y) / 2; } return cardinalityPosition; } @@ -502,7 +458,7 @@ export function getStylesFromArray(arr: string[]): { style: string; labelStyle: } } - return { style: style, labelStyle: labelStyle }; + return { style, labelStyle }; } let cnt = 0; @@ -514,10 +470,10 @@ export const generateId = () => { /** * Generates a random hexadecimal id of the given length. * - * @param length - Length of ID. - * @returns The generated ID. + * @param length - Length of string. + * @returns The generated string. */ -function makeid(length: number): string { +function makeRandomHex(length: number): string { let result = ''; const characters = '0123456789abcdef'; const charactersLength = characters.length; @@ -527,8 +483,8 @@ function makeid(length: number): string { return result; } -export const random = (options) => { - return makeid(options.length); +export const random = (options: { length: number }) => { + return makeRandomHex(options.length); }; export const getTextObj = function () { @@ -544,6 +500,7 @@ export const getTextObj = function () { rx: 0, ry: 0, valign: undefined, + text: '', }; }; @@ -574,7 +531,7 @@ export const drawSimpleText = function ( const [, _fontSizePx] = parseFontSize(textData.fontSize); - const textElem = elem.append('text'); + const textElem = elem.append('text') as any; textElem.attr('x', textData.x); textElem.attr('y', textData.y); textElem.style('text-anchor', textData.anchor); @@ -582,6 +539,7 @@ export const drawSimpleText = function ( textElem.style('font-size', _fontSizePx); textElem.style('font-weight', textData.fontWeight); textElem.attr('fill', textData.fill); + if (textData.class !== undefined) { textElem.attr('class', textData.class); } @@ -601,9 +559,9 @@ interface WrapLabelConfig { joinWith: string; } -export const wrapLabel: (label: string, maxWidth: string, config: WrapLabelConfig) => string = +export const wrapLabel: (label: string, maxWidth: number, config: WrapLabelConfig) => string = memoize( - (label: string, maxWidth: string, config: WrapLabelConfig): string => { + (label: string, maxWidth: number, config: WrapLabelConfig): string => { if (!label) { return label; } @@ -615,7 +573,7 @@ export const wrapLabel: (label: string, maxWidth: string, config: WrapLabelConfi return label; } const words = label.split(' '); - const completedLines = []; + const completedLines: string[] = []; let nextLine = ''; words.forEach((word, index) => { const wordLength = calculateTextWidth(`${word} `, config); @@ -700,10 +658,6 @@ export function calculateTextHeight( text: Parameters[0], config: Parameters[1] ): ReturnType['height'] { - config = Object.assign( - { fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 15 }, - config - ); return calculateTextDimensions(text, config).height; } @@ -719,20 +673,9 @@ export function calculateTextWidth( text: Parameters[0], config: Parameters[1] ): ReturnType['width'] { - config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config); return calculateTextDimensions(text, config).width; } -interface TextDimensionConfig { - fontSize?: number; - fontWeight?: number; - fontFamily?: string; -} -interface TextDimensions { - width: number; - height: number; - lineHeight?: number; -} /** * This calculates the dimensions of the given text, font size, font family, font weight, and * margins. @@ -747,8 +690,7 @@ export const calculateTextDimensions: ( config: TextDimensionConfig ) => TextDimensions = memoize( (text: string, config: TextDimensionConfig): TextDimensions => { - config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config); - const { fontSize, fontFamily, fontWeight } = config; + const { fontSize = 12, fontFamily = 'Arial', fontWeight = 400 } = config; if (!text) { return { width: 0, height: 0 }; } @@ -772,12 +714,14 @@ export const calculateTextDimensions: ( const g = body.append('svg'); for (const fontFamily of fontFamilies) { - let cheight = 0; + let cHeight = 0; const dim = { width: 0, height: 0, lineHeight: 0 }; for (const line of lines) { const textObj = getTextObj(); textObj.text = line || ZERO_WIDTH_SPACE; + // @ts-ignore TODO: Fix D3 types const textElem = drawSimpleText(g, textObj) + // @ts-ignore TODO: Fix D3 types .style('font-size', _fontSizePx) .style('font-weight', fontWeight) .style('font-family', fontFamily); @@ -787,9 +731,9 @@ export const calculateTextDimensions: ( throw new Error('svg element not in render tree'); } dim.width = Math.round(Math.max(dim.width, bBox.width)); - cheight = Math.round(bBox.height); - dim.height += cheight; - dim.lineHeight = Math.round(Math.max(dim.lineHeight, cheight)); + cHeight = Math.round(bBox.height); + dim.height += cHeight; + dim.lineHeight = Math.round(Math.max(dim.lineHeight, cHeight)); } dims.push(dim); } @@ -810,25 +754,18 @@ export const calculateTextDimensions: ( (text, config) => `${text}${config.fontSize}${config.fontWeight}${config.fontFamily}` ); -export const initIdGenerator = class iterator { - constructor(deterministic, seed?: any) { - this.deterministic = deterministic; +export class InitIDGenerator { + private count = 0; + public next: () => number; + constructor(deterministic = false, seed?: string) { // TODO: Seed is only used for length? - this.seed = seed; - + // v11: Use the actual value of seed string to generate an initial value for count. this.count = seed ? seed.length : 0; + this.next = deterministic ? () => this.count++ : () => Date.now(); } +} - next() { - if (!this.deterministic) { - return Date.now(); - } - - return this.count++; - } -}; - -let decoder; +let decoder: HTMLDivElement; /** * Decodes HTML, source: {@link https://github.com/shrpne/entity-decode/blob/v2.0.1/browser.js} @@ -840,20 +777,23 @@ export const entityDecode = function (html: string): string { decoder = decoder || document.createElement('div'); // Escape HTML before decoding for HTML Entities html = escape(html).replace(/%26/g, '&').replace(/%23/g, '#').replace(/%3B/g, ';'); - // decoding decoder.innerHTML = html; - return unescape(decoder.textContent); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return unescape(decoder.textContent!); }; export interface DetailedError { str: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any hash: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any error?: any; message?: string; } /** @param error - The error to check */ -export function isDetailedError(error: unknown): error is DetailedError { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function isDetailedError(error: any): error is DetailedError { return 'str' in error; } @@ -874,7 +814,7 @@ export function getErrorMessage(error: unknown): string { * @param title - The title. If empty, returns immediately. */ export const insertTitle = ( - parent, + parent: D3Element, cssClass: string, titleTopMargin: number, title?: string @@ -882,7 +822,10 @@ export const insertTitle = ( if (!title) { return; } - const bounds = parent.node().getBBox(); + const bounds = parent.node()?.getBBox(); + if (!bounds) { + return; + } parent .append('text') .text(title) @@ -905,7 +848,7 @@ export const parseFontSize = (fontSize: string | number | undefined): [number?, return [fontSize, fontSize + 'px']; } - const fontSizeNumber = parseInt(fontSize, 10); + const fontSizeNumber = parseInt(fontSize ?? '', 10); if (Number.isNaN(fontSizeNumber)) { // if a number value can't be parsed, return null for both values return [undefined, undefined]; @@ -941,7 +884,7 @@ export default { random, runFunc, entityDecode, - initIdGenerator, insertTitle, parseFontSize, + InitIDGenerator, }; From 11aaee043fea35dcfb1703c006dcfde7e310ff10 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 13:44:53 +0000 Subject: [PATCH 29/37] chore(deps): update all patch dependencies --- package.json | 2 +- packages/mermaid/src/docs/package.json | 2 +- pnpm-lock.yaml | 19 ++++++++++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 232f23be1d..b0031f4daf 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "10.2.4", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "type": "module", - "packageManager": "pnpm@8.7.1", + "packageManager": "pnpm@8.7.5", "keywords": [ "diagram", "markdown", diff --git a/packages/mermaid/src/docs/package.json b/packages/mermaid/src/docs/package.json index 742a28c0a5..759d1ffb17 100644 --- a/packages/mermaid/src/docs/package.json +++ b/packages/mermaid/src/docs/package.json @@ -32,7 +32,7 @@ "unplugin-vue-components": "^0.25.0", "vite": "^4.3.9", "vite-plugin-pwa": "^0.16.0", - "vitepress": "1.0.0-rc.10", + "vitepress": "1.0.0-rc.12", "workbox-window": "^7.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b86be8ff17..5a04bb353b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -475,8 +475,8 @@ importers: specifier: ^0.16.0 version: 0.16.0(vite@4.3.9)(workbox-build@7.0.0)(workbox-window@7.0.0) vitepress: - specifier: 1.0.0-rc.10 - version: 1.0.0-rc.10(@algolia/client-search@4.19.1)(@types/node@18.16.0)(search-insights@2.6.0) + specifier: 1.0.0-rc.12 + version: 1.0.0-rc.12(@algolia/client-search@4.19.1)(@types/node@18.16.0)(search-insights@2.6.0) workbox-window: specifier: ^7.0.0 version: 7.0.0 @@ -13913,6 +13913,15 @@ packages: vscode-textmate: 8.0.0 dev: true + /shiki@0.14.4: + resolution: {integrity: sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ==} + dependencies: + ansi-sequence-parser: 1.1.0 + jsonc-parser: 3.2.0 + vscode-oniguruma: 1.7.0 + vscode-textmate: 8.0.0 + dev: true + /side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: @@ -15456,8 +15465,8 @@ packages: - terser dev: true - /vitepress@1.0.0-rc.10(@algolia/client-search@4.19.1)(@types/node@18.16.0)(search-insights@2.6.0): - resolution: {integrity: sha512-+MsahIWqq5WUEmj6MR4obcKYbT7im07jZPCQPdNJExkeOSbOAJ4xypSLx88x7rvtzWHhHc5aXbOhCRvGEGjFrw==} + /vitepress@1.0.0-rc.12(@algolia/client-search@4.19.1)(@types/node@18.16.0)(search-insights@2.6.0): + resolution: {integrity: sha512-mZknN5l9lgbBjXwumwdOQQDM+gPivswFEykEQeenY0tv7eocS+bb801IpFZT3mFV6YRhSddmbutHlFgPPADjEg==} hasBin: true dependencies: '@docsearch/css': 3.5.2 @@ -15468,7 +15477,7 @@ packages: focus-trap: 7.5.2 mark.js: 8.11.1 minisearch: 6.1.0 - shiki: 0.14.3 + shiki: 0.14.4 vite: 4.4.9(@types/node@18.16.0) vue: 3.3.4 transitivePeerDependencies: From 924c9e913b665f22f9a3401dbf31857579833ca4 Mon Sep 17 00:00:00 2001 From: Chad Fawcett Date: Mon, 11 Sep 2023 11:59:28 -0700 Subject: [PATCH 30/37] Added cypress test --- .../rendering/marker_unique_id.spec.js | 10 +++++++ cypress/platform/marker_unique_id.html | 28 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 cypress/integration/rendering/marker_unique_id.spec.js create mode 100644 cypress/platform/marker_unique_id.html diff --git a/cypress/integration/rendering/marker_unique_id.spec.js b/cypress/integration/rendering/marker_unique_id.spec.js new file mode 100644 index 0000000000..617189db05 --- /dev/null +++ b/cypress/integration/rendering/marker_unique_id.spec.js @@ -0,0 +1,10 @@ +import { urlSnapshotTest } from '../../helpers/util.ts'; + +describe('Marker Unique IDs Per Diagram', () => { + it('should render a blue arrow tip in second digram', () => { + urlSnapshotTest('http://localhost:9000/marker_unique_id.html', { + logLevel: 1, + flowchart: { htmlLabels: false }, + }); + }); +}); diff --git a/cypress/platform/marker_unique_id.html b/cypress/platform/marker_unique_id.html new file mode 100644 index 0000000000..eff04dbb69 --- /dev/null +++ b/cypress/platform/marker_unique_id.html @@ -0,0 +1,28 @@ + + + +

Example

+
+      %%{init:{"theme":"base", "themeVariables": {"lineColor":"red"}}}%%
+      flowchart LR
+      subgraph red
+      A --> B
+      end
+    
+
+      %%{init:{"theme":"base", "themeVariables": {"lineColor":"blue"}}}%%
+      flowchart LR
+      subgraph black
+      A --> B
+      end
+    
+ + + From 6b7a0e1d8e0e34f9628f223ccc4967cb9cf43931 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Tue, 12 Sep 2023 14:04:54 +0530 Subject: [PATCH 31/37] fix: PointStart marker refX --- packages/mermaid/src/dagre-wrapper/markers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/dagre-wrapper/markers.js b/packages/mermaid/src/dagre-wrapper/markers.js index 051c987f62..223e682540 100644 --- a/packages/mermaid/src/dagre-wrapper/markers.js +++ b/packages/mermaid/src/dagre-wrapper/markers.js @@ -176,7 +176,7 @@ const point = (elem, type) => { .attr('id', type + '-pointStart') .attr('class', 'marker ' + type) .attr('viewBox', '0 0 10 10') - .attr('refX', 0) + .attr('refX', 4.5) .attr('refY', 5) .attr('markerUnits', 'userSpaceOnUse') .attr('markerWidth', 12) From 78346943a2abc88b37071ecc64afa5c274169f52 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Tue, 12 Sep 2023 14:06:53 +0530 Subject: [PATCH 32/37] refactor: Move EdgeData to types --- .../src/diagrams/class/classRenderer-v2.ts | 3 ++- .../mermaid/src/diagrams/class/classTypes.ts | 18 ------------------ packages/mermaid/src/types.ts | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts index b581252bfa..5abfd769a2 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts @@ -8,7 +8,8 @@ import utils from '../../utils.js'; import { interpolateToCurve, getStylesFromArray } from '../../utils.js'; import { setupGraphViewbox } from '../../setupGraphViewbox.js'; import common from '../common/common.js'; -import type { ClassRelation, ClassNote, ClassMap, EdgeData, NamespaceMap } from './classTypes.js'; +import type { ClassRelation, ClassNote, ClassMap, NamespaceMap } from './classTypes.js'; +import type { EdgeData } from '../../types.js'; const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig()); diff --git a/packages/mermaid/src/diagrams/class/classTypes.ts b/packages/mermaid/src/diagrams/class/classTypes.ts index aa5ec7b70d..d372feebad 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.ts @@ -137,24 +137,6 @@ export interface ClassNote { text: string; } -export interface EdgeData { - arrowheadStyle?: string; - labelpos?: string; - labelType?: string; - label?: string; - classes: string; - pattern: string; - id: string; - arrowhead: string; - startLabelRight: string; - endLabelLeft: string; - arrowTypeStart: string; - arrowTypeEnd: string; - style: string; - labelStyle: string; - curve: any; -} - export type ClassRelation = { id1: string; id2: string; diff --git a/packages/mermaid/src/types.ts b/packages/mermaid/src/types.ts index 4b9eedad6c..13da885033 100644 --- a/packages/mermaid/src/types.ts +++ b/packages/mermaid/src/types.ts @@ -14,3 +14,21 @@ export interface TextDimensions { height: number; lineHeight?: number; } + +export interface EdgeData { + arrowheadStyle?: string; + labelpos?: string; + labelType?: string; + label?: string; + classes: string; + pattern: string; + id: string; + arrowhead: string; + startLabelRight: string; + endLabelLeft: string; + arrowTypeStart: string; + arrowTypeEnd: string; + style: string; + labelStyle: string; + curve: any; +} From 3c34fbaacdc0bd5bc09c21378d46c0962d496ecf Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Tue, 12 Sep 2023 14:08:02 +0530 Subject: [PATCH 33/37] refactor: Add getLineFunctionsWithOffset function --- packages/mermaid/src/dagre-wrapper/edges.js | 67 +------------- .../flowchart/elk/flowRenderer-elk.js | 9 +- packages/mermaid/src/utils/lineWithOffset.ts | 90 +++++++++++++++++++ 3 files changed, 98 insertions(+), 68 deletions(-) create mode 100644 packages/mermaid/src/utils/lineWithOffset.ts diff --git a/packages/mermaid/src/dagre-wrapper/edges.js b/packages/mermaid/src/dagre-wrapper/edges.js index f89b4422be..a47d5f4741 100644 --- a/packages/mermaid/src/dagre-wrapper/edges.js +++ b/packages/mermaid/src/dagre-wrapper/edges.js @@ -5,6 +5,7 @@ import { line, curveBasis, select } from 'd3'; import { getConfig } from '../config.js'; import utils from '../utils.js'; import { evaluate } from '../diagrams/common/common.js'; +import { getLineFunctionsWithOffset } from '../utils/lineWithOffset.js'; let edgeLabels = {}; let terminalLabels = {}; @@ -368,20 +369,6 @@ const cutPathAtIntersect = (_points, boundryNode) => { return points; }; -/** - * Calculate the deltas and angle between two points - * @param {{x: number, y:number}} point1 - * @param {{x: number, y:number}} point2 - * @returns {{angle: number, deltaX: number, deltaY: number}} - */ -function calculateDeltaAndAngle(point1, point2) { - const [x1, y1] = [point1.x, point1.y]; - const [x2, y2] = [point2.x, point2.y]; - const deltaX = x2 - x1; - const deltaY = y2 - y1; - return { angle: Math.atan(deltaY / deltaX), deltaX, deltaY }; -} - export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph) { let points = edge.points; let pointsHasChanged = false; @@ -456,56 +443,8 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph curve = edge.curve; } - // We need to draw the lines a bit shorter to avoid drawing - // under any transparent markers. - // The offsets are calculated from the markers' dimensions. - const markerOffsets = { - aggregation: 18, - extension: 18, - composition: 18, - dependency: 6, - lollipop: 13.5, - arrow_point: 5.3, - }; - - const lineFunction = line() - .x(function (d, i, data) { - let offset = 0; - if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) { - // Handle first point - // Calculate the angle and delta between the first two points - const { angle, deltaX } = calculateDeltaAndAngle(data[0], data[1]); - // Calculate the offset based on the angle and the marker's dimensions - offset = markerOffsets[edge.arrowTypeStart] * Math.cos(angle) * (deltaX >= 0 ? 1 : -1) || 0; - } else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) { - // Handle last point - // Calculate the angle and delta between the last two points - const { angle, deltaX } = calculateDeltaAndAngle( - data[data.length - 1], - data[data.length - 2] - ); - offset = markerOffsets[edge.arrowTypeEnd] * Math.cos(angle) * (deltaX >= 0 ? 1 : -1) || 0; - } - return d.x + offset; - }) - .y(function (d, i, data) { - // Same handling as X above - let offset = 0; - if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) { - const { angle, deltaY } = calculateDeltaAndAngle(data[0], data[1]); - offset = - markerOffsets[edge.arrowTypeStart] * Math.abs(Math.sin(angle)) * (deltaY >= 0 ? 1 : -1); - } else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) { - const { angle, deltaY } = calculateDeltaAndAngle( - data[data.length - 1], - data[data.length - 2] - ); - offset = - markerOffsets[edge.arrowTypeEnd] * Math.abs(Math.sin(angle)) * (deltaY >= 0 ? 1 : -1); - } - return d.y + offset; - }) - .curve(curve); + const { x, y } = getLineFunctionsWithOffset(edge); + const lineFunction = line().x(x).y(y).curve(curve); // Construct stroke classes based on properties let strokeClasses; diff --git a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js b/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js index c7bfdf5246..2f576025f6 100644 --- a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js +++ b/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js @@ -4,13 +4,14 @@ import insertMarkers from '../../../dagre-wrapper/markers.js'; import { insertEdgeLabel } from '../../../dagre-wrapper/edges.js'; import { findCommonAncestor } from './render-utils.js'; import { labelHelper } from '../../../dagre-wrapper/shapes/util.js'; -import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js'; import { getConfig } from '../../../config.js'; import { log } from '../../../logger.js'; import { setupGraphViewbox } from '../../../setupGraphViewbox.js'; -import common, { evaluate } from '../../common/common.js'; +import common from '../../common/common.js'; import { interpolateToCurve, getStylesFromArray } from '../../../utils.js'; import ELK from 'elkjs/lib/elk.bundled.js'; +import { getLineFunctionsWithOffset } from '../../../utils/lineWithOffset.js'; + const elk = new ELK(); let portPos = {}; @@ -705,8 +706,8 @@ const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb) { [dest.x + offset.x, dest.y + offset.y], ]; - // const curve = line().curve(curveBasis); - const curve = line().curve(curveLinear); + const { x, y } = getLineFunctionsWithOffset(edge.edgeData); + const curve = line().x(x).y(y).curve(curveLinear); const edgePath = edgesEl .insert('path') .attr('d', curve(points)) diff --git a/packages/mermaid/src/utils/lineWithOffset.ts b/packages/mermaid/src/utils/lineWithOffset.ts new file mode 100644 index 0000000000..9b31767835 --- /dev/null +++ b/packages/mermaid/src/utils/lineWithOffset.ts @@ -0,0 +1,90 @@ +import type { EdgeData, Point } from '../types.js'; + +// We need to draw the lines a bit shorter to avoid drawing +// under any transparent markers. +// The offsets are calculated from the markers' dimensions. +const markerOffsets = { + aggregation: 18, + extension: 18, + composition: 18, + dependency: 6, + lollipop: 13.5, + arrow_point: 5.3, +} as const; + +/** + * Calculate the deltas and angle between two points + * @param point1 - First point + * @param point2 - Second point + * @returns The angle, deltaX and deltaY + */ +function calculateDeltaAndAngle( + point1: Point | [number, number], + point2: Point | [number, number] +): { angle: number; deltaX: number; deltaY: number } { + point1 = pointTransformer(point1); + point2 = pointTransformer(point2); + const [x1, y1] = [point1.x, point1.y]; + const [x2, y2] = [point2.x, point2.y]; + const deltaX = x2 - x1; + const deltaY = y2 - y1; + return { angle: Math.atan(deltaY / deltaX), deltaX, deltaY }; +} + +const pointTransformer = (data: Point | [number, number]) => { + if (Array.isArray(data)) { + return { x: data[0], y: data[1] }; + } + return data; +}; + +export const getLineFunctionsWithOffset = (edge: EdgeData) => { + return { + x: function (d: Point | [number, number], i: number, data: (Point | [number, number])[]) { + let offset = 0; + if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) { + // Handle first point + // Calculate the angle and delta between the first two points + const { angle, deltaX } = calculateDeltaAndAngle(data[0], data[1]); + // Calculate the offset based on the angle and the marker's dimensions + offset = + markerOffsets[edge.arrowTypeStart as keyof typeof markerOffsets] * + Math.cos(angle) * + (deltaX >= 0 ? 1 : -1); + } else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) { + // Handle last point + // Calculate the angle and delta between the last two points + const { angle, deltaX } = calculateDeltaAndAngle( + data[data.length - 1], + data[data.length - 2] + ); + offset = + markerOffsets[edge.arrowTypeEnd as keyof typeof markerOffsets] * + Math.cos(angle) * + (deltaX >= 0 ? 1 : -1); + } + return pointTransformer(d).x + offset; + }, + y: function (d: Point | [number, number], i: number, data: (Point | [number, number])[]) { + // Same handling as X above + let offset = 0; + if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) { + const { angle, deltaY } = calculateDeltaAndAngle(data[0], data[1]); + offset = + markerOffsets[edge.arrowTypeStart as keyof typeof markerOffsets] * + Math.abs(Math.sin(angle)) * + (deltaY >= 0 ? 1 : -1); + } else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) { + const { angle, deltaY } = calculateDeltaAndAngle( + data[data.length - 1], + data[data.length - 2] + ); + offset = + markerOffsets[edge.arrowTypeEnd as keyof typeof markerOffsets] * + Math.abs(Math.sin(angle)) * + (deltaY >= 0 ? 1 : -1); + } + return pointTransformer(d).y + offset; + }, + }; +}; From bceae92d3021498543f6eb90aff0c5755b0f3117 Mon Sep 17 00:00:00 2001 From: Chad Fawcett Date: Tue, 12 Sep 2023 10:29:39 -0700 Subject: [PATCH 34/37] Update cypress/platform/marker_unique_id.html Co-authored-by: Sidharth Vinod --- cypress/platform/marker_unique_id.html | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/cypress/platform/marker_unique_id.html b/cypress/platform/marker_unique_id.html index eff04dbb69..e49169c556 100644 --- a/cypress/platform/marker_unique_id.html +++ b/cypress/platform/marker_unique_id.html @@ -16,6 +16,30 @@

Example

A --> B end +
+      ---
+      config:
+        theme: base
+        themeVariables:
+          lineColor: yellow
+      ---
+      flowchart LR
+      subgraph red
+      A --> B
+      end
+    
+
+      ---
+      config:
+        theme: base
+        themeVariables:
+          lineColor: green
+      ---
+      flowchart LR
+      subgraph black
+      A --> B
+      end
+