Merge pull request #3591 from mermaid-js/sidv/fixDetectDiagram

fix Detect diagram fallback
This commit is contained in:
Knut Sveidqvist
2022-10-10 13:43:41 +02:00
committed by GitHub
30 changed files with 164 additions and 556 deletions

View File

@@ -0,0 +1,14 @@
<html>
<body>
<pre class="mermaid">
none
hello world
</pre>
<script src="./mermaid.js"></script>
<script>
mermaid.initialize({
logLevel: 1,
});
</script>
</body>
</html>

View File

@@ -47,7 +47,8 @@
"e2e": "start-server-and-test dev http://localhost:9000/ cypress",
"ci": "vitest run",
"test": "pnpm lint && vitest run",
"test:watch": "vitest --coverage --watch",
"test:watch": "vitest --watch",
"test:coverage": "vitest --coverage",
"prepublishOnly": "pnpm build && pnpm test",
"prepare": "concurrently \"husky install\" \"pnpm build\"",
"pre-commit": "lint-staged"

View File

@@ -8,11 +8,18 @@ export class Diagram {
parser;
renderer;
db;
private detectTypeFailed = false;
// eslint-disable-next-line @typescript-eslint/ban-types
constructor(public txt: string, parseError?: Function) {
const cnf = configApi.getConfig();
this.txt = txt;
try {
this.type = detectType(txt, cnf);
} catch (e) {
this.handleError(e, parseError);
this.type = 'error';
this.detectTypeFailed = true;
}
const diagram = getDiagram(this.type);
log.debug('Type ' + this.type);
// Setup diagram
@@ -32,12 +39,22 @@ export class Diagram {
// eslint-disable-next-line @typescript-eslint/ban-types
parse(text: string, parseError?: Function): boolean {
if (this.detectTypeFailed) {
return false;
}
try {
text = text + '\n';
this.db.clear();
this.parser.parse(text);
return true;
} catch (error) {
this.handleError(error, parseError);
}
return false;
}
// eslint-disable-next-line @typescript-eslint/ban-types
handleError(error: unknown, parseError?: Function) {
// Is this the correct way to access mermiad's parseError()
// method ? (or global.mermaid.parseError()) ?
if (parseError) {
@@ -54,8 +71,6 @@ export class Diagram {
throw error;
}
}
return false;
}
getParser() {
return this.parser;
@@ -66,10 +81,8 @@ export class Diagram {
}
}
export default Diagram;
// eslint-disable-next-line @typescript-eslint/ban-types
export const getDiagramFromText = async (txt: string, parseError?: Function) => {
export const getDiagramFromText = async (txt: string, parseError?: Function): Promise<Diagram> => {
const type = detectType(txt, configApi.getConfig());
try {
// Trying to find the diagram
@@ -79,24 +92,14 @@ export const getDiagramFromText = async (txt: string, parseError?: Function) =>
if (!loader) {
throw new Error(`Diagram ${type} not found.`);
}
// Diagram not avaiable, loading it
// const path = getPathForDiagram(type);
const { diagram } = await loader(); // eslint-disable-line @typescript-eslint/no-explicit-any
registerDiagram(
type,
{
db: diagram.db,
renderer: diagram.renderer,
parser: diagram.parser,
styles: diagram.styles,
},
diagram.injectUtils
);
// await loadDiagram('./packages/mermaid-mindmap/dist/mermaid-mindmap.js');
// await loadDiagram(path + 'mermaid-' + type + '.js');
// Diagram not available, loading it
const { diagram } = await loader();
registerDiagram(type, diagram, undefined, diagram.injectUtils);
// new diagram will try getDiagram again and if fails then it is a valid throw
}
// If either of the above worked, we have the diagram
// logic and can continue
return new Diagram(txt, parseError);
};
export default Diagram;

View File

@@ -11,17 +11,13 @@ import Diagram from '../Diagram';
// Normally, we could just do the following to get the original `parse()`
// implementation, however, requireActual returns a promise and it's not documented how to use withing mock file.
let hasLoadedDiagrams = false;
/**
* @param text
* @param parseError
*/
// eslint-disable-next-line @typescript-eslint/ban-types
function parse(text: string, parseError?: Function): boolean {
if (!hasLoadedDiagrams) {
addDiagrams();
hasLoadedDiagrams = true;
}
const diagram = new Diagram(text, parseError);
return diagram.parse(text, parseError);
}

View File

@@ -3,7 +3,7 @@
import DOMPurify from 'dompurify';
export interface MermaidConfig {
lazyLoadedDiagrams?: any;
lazyLoadedDiagrams?: string[];
theme?: string;
themeVariables?: any;
themeCSS?: string;

View File

@@ -1,8 +1,7 @@
import { MermaidConfig } from '../config.type';
import { log } from '../logger';
import { DetectorRecord, DiagramDetector, DiagramLoader } from './types';
export type DiagramDetector = (text: string, config?: MermaidConfig) => boolean;
export type DiagramLoader = (() => any) | null;
export type DetectorRecord = { detector: DiagramDetector; loader: DiagramLoader };
const directive =
/[%]{2}[{]\s*(?:(?:(\w+)\s*:|(\w+))\s*(?:(?:(\w+))|((?:(?![}][%]{2}).|\r?\n)*))?\s*)(?:[}][%]{2})?/gi;
const anyComment = /\s*%%.*\n/gm;
@@ -34,26 +33,22 @@ const detectors: Record<string, DetectorRecord> = {};
*/
export const detectType = function (text: string, config?: MermaidConfig): string {
text = text.replace(directive, '').replace(anyComment, '\n');
// console.log(detectors);
for (const [key, { detector }] of Object.entries(detectors)) {
const diagram = detector(text, config);
if (diagram) {
return key;
}
}
// TODO: #3391
// throw new Error(`No diagram type detected for text: ${text}`);
return 'flowchart';
throw new Error(`No diagram type detected for text: ${text}`);
};
export const addDetector = (
key: string,
detector: DiagramDetector,
loader: DiagramLoader | null
) => {
export const addDetector = (key: string, detector: DiagramDetector, loader?: DiagramLoader) => {
if (detectors[key]) {
throw new Error(`Detector with key ${key} already exists`);
}
detectors[key] = { detector, loader };
log.debug(`Detector with key ${key} added${loader ? ' with loader' : ''}`);
};
export const getDiagramLoader = (key: string) => detectors[key].loader;

View File

@@ -1,16 +1,4 @@
import {
registerDiagram,
registerDetector,
DiagramDefinition,
DiagramDetector,
} from './diagramAPI';
// // @ts-ignore: TODO Fix ts errors
// import mindmapParser from '../diagrams/mindmap/parser/mindmap';
// import * as mindmapDb from '../diagrams/mindmap/mindmapDb';
// import { mindmapDetector } from '../diagrams/mindmap/mindmapDetector';
// import mindmapRenderer from '../diagrams/mindmap/mindmapRenderer';
// import mindmapStyles from '../diagrams/mindmap/styles';
import { registerDiagram } from './diagramAPI';
// @ts-ignore: TODO Fix ts errors
import gitGraphParser from '../diagrams/git/parser/gitGraph';
@@ -106,17 +94,15 @@ import { setConfig } from '../config';
import errorRenderer from '../diagrams/error/errorRenderer';
import errorStyles from '../diagrams/error/styles';
const registerDiagramAndDetector = (
id: string,
diagram: DiagramDefinition,
detector: DiagramDetector
) => {
registerDiagram(id, diagram);
registerDetector(id, detector);
};
let hasLoadedDiagrams = false;
export const addDiagrams = () => {
registerDiagramAndDetector(
if (hasLoadedDiagrams) {
return;
}
// This is added here to avoid race-conditions.
// We could optimize the loading logic somehow.
hasLoadedDiagrams = true;
registerDiagram(
'error',
// Special diagram with error messages but setup as a regular diagram
{
@@ -140,7 +126,7 @@ export const addDiagrams = () => {
(text) => text.toLowerCase().trim() === 'error'
);
registerDiagramAndDetector(
registerDiagram(
'c4',
{
parser: c4Parser,
@@ -153,7 +139,7 @@ export const addDiagrams = () => {
},
c4Detector
);
registerDiagramAndDetector(
registerDiagram(
'class',
{
parser: classParser,
@@ -170,7 +156,7 @@ export const addDiagrams = () => {
},
classDetector
);
registerDiagramAndDetector(
registerDiagram(
'classDiagram',
{
parser: classParser,
@@ -187,7 +173,7 @@ export const addDiagrams = () => {
},
classDetectorV2
);
registerDiagramAndDetector(
registerDiagram(
'er',
{
parser: erParser,
@@ -197,7 +183,7 @@ export const addDiagrams = () => {
},
erDetector
);
registerDiagramAndDetector(
registerDiagram(
'gantt',
{
parser: ganttParser,
@@ -207,7 +193,7 @@ export const addDiagrams = () => {
},
ganttDetector
);
registerDiagramAndDetector(
registerDiagram(
'info',
{
parser: infoParser,
@@ -217,7 +203,7 @@ export const addDiagrams = () => {
},
infoDetector
);
registerDiagramAndDetector(
registerDiagram(
'pie',
{
parser: pieParser,
@@ -227,7 +213,7 @@ export const addDiagrams = () => {
},
pieDetector
);
registerDiagramAndDetector(
registerDiagram(
'requirement',
{
parser: requirementParser,
@@ -237,7 +223,7 @@ export const addDiagrams = () => {
},
requirementDetector
);
registerDiagramAndDetector(
registerDiagram(
'sequence',
{
parser: sequenceParser,
@@ -260,7 +246,7 @@ export const addDiagrams = () => {
},
sequenceDetector
);
registerDiagramAndDetector(
registerDiagram(
'state',
{
parser: stateParser,
@@ -277,7 +263,7 @@ export const addDiagrams = () => {
},
stateDetector
);
registerDiagramAndDetector(
registerDiagram(
'stateDiagram',
{
parser: stateParser,
@@ -294,7 +280,7 @@ export const addDiagrams = () => {
},
stateDetectorV2
);
registerDiagramAndDetector(
registerDiagram(
'journey',
{
parser: journeyParser,
@@ -309,7 +295,7 @@ export const addDiagrams = () => {
journeyDetector
);
registerDiagramAndDetector(
registerDiagram(
'flowchart',
{
parser: flowParser,
@@ -329,7 +315,7 @@ export const addDiagrams = () => {
},
flowDetector
);
registerDiagramAndDetector(
registerDiagram(
'flowchart-v2',
{
parser: flowParser,
@@ -350,14 +336,9 @@ export const addDiagrams = () => {
},
flowDetectorV2
);
registerDiagramAndDetector(
registerDiagram(
'gitGraph',
{ parser: gitGraphParser, db: gitGraphDb, renderer: gitGraphRenderer, styles: gitGraphStyles },
gitGraphDetector
);
// registerDiagram(
// 'mindmap',
// { parser: mindmapParser, db: mindmapDb, renderer: mindmapRenderer, styles: mindmapStyles },
// mindmapDetector
// );
};

View File

@@ -1,6 +1,7 @@
import { detectType, DiagramDetector } from './detectType';
import { getDiagram, registerDiagram, registerDetector } from './diagramAPI';
import { detectType } from './detectType';
import { getDiagram, registerDiagram } from './diagramAPI';
import { addDiagrams } from './diagram-orchestration';
import { DiagramDetector } from './types';
addDiagrams();
@@ -15,17 +16,22 @@ describe('DiagramAPI', () => {
it('should handle diagram registrations', () => {
expect(() => getDiagram('loki')).toThrow();
expect(() => detectType('loki diagram')).not.toThrow(); // TODO: #3391
expect(() => detectType('loki diagram')).toThrow(
'No diagram type detected for text: loki diagram'
);
const detector: DiagramDetector = (str: string) => {
return str.match('loki') !== null;
};
registerDetector('loki', detector);
registerDiagram('loki', {
registerDiagram(
'loki',
{
db: {},
parser: {},
renderer: {},
styles: {},
});
},
detector
);
expect(getDiagram('loki')).not.toBeNull();
expect(detectType('loki diagram')).toBe('loki');
});

View File

@@ -1,10 +1,10 @@
import { addDetector, DiagramDetector as _DiagramDetector } from './detectType';
import { addDetector } from './detectType';
import { log as _log, setLogLevel as _setLogLevel } from '../logger';
import { getConfig as _getConfig } from '../config';
import { sanitizeText as _sanitizeText } from '../diagrams/common/common';
import { MermaidConfig } from '../config.type';
import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox';
import { addStylesForDiagram } from '../styles';
import { DiagramDefinition, DiagramDetector } from './types';
/*
Packaging and exposing resources for externa diagrams so that they can import
@@ -13,41 +13,19 @@ import { addStylesForDiagram } from '../styles';
*/
export const log = _log;
export const setLogLevel = _setLogLevel;
export type DiagramDetector = _DiagramDetector;
export const getConfig = _getConfig;
export const sanitizeText = (text: string) => _sanitizeText(text, getConfig());
export const setupGraphViewbox = _setupGraphViewbox;
export interface InjectUtils {
_log: any;
_setLogLevel: any;
_getConfig: any;
_sanitizeText: any;
_setupGraphViewbox: any;
}
export interface DiagramDefinition {
db: any;
renderer: any;
parser: any;
styles: any;
init?: (config: MermaidConfig) => void;
injectUtils?: (utils: InjectUtils) => void;
}
const diagrams: Record<string, DiagramDefinition> = {};
const connectCallbacks: Record<string, any> = {}; // TODO fix, eslint-disable-line @typescript-eslint/no-explicit-any
export interface Detectors {
[key: string]: DiagramDetector;
}
export const registerDetector = (id: string, detector: DiagramDetector) => {
addDetector(id, detector, null);
};
export const registerDiagram = (
id: string,
diagram: DiagramDefinition,
detector?: DiagramDetector,
callback?: (
_log: any,
_setLogLevel: any,
@@ -57,9 +35,12 @@ export const registerDiagram = (
) => void
) => {
if (diagrams[id]) {
log.warn(`Diagram ${id} already registered.`);
throw new Error(`Diagram ${id} already registered.`);
}
diagrams[id] = diagram;
if (detector) {
addDetector(id, detector);
}
addStylesForDiagram(id, diagram.styles);
if (typeof callback !== 'undefined') {
callback(log, setLogLevel, getConfig, sanitizeText, setupGraphViewbox);
@@ -72,19 +53,3 @@ export const getDiagram = (name: string): DiagramDefinition => {
}
throw new Error(`Diagram ${name} not found.`);
};
/**
*
* @param sScriptSrc
*/
export const loadDiagram = (sScriptSrc: string) =>
new Promise((resolve) => {
const oHead = document.getElementsByTagName('HEAD')[0];
const oScript = document.createElement('script');
oScript.type = 'text/javascript';
oScript.src = sScriptSrc;
oHead.appendChild(oScript);
oScript.onload = () => {
resolve(true);
};
});

View File

@@ -1,227 +0,0 @@
export const lineBreakRegex = /<br\s*\/?>/gi;
/**
* Caches results of functions based on input
*
* @param {Function} fn Function to run
* @param {Function} resolver Function that resolves to an ID given arguments the `fn` takes
* @returns {Function} An optimized caching function
*/
const memoize = (fn, resolver) => {
let cache = {};
return (...args) => {
let n = resolver ? resolver.apply(this, args) : args[0];
if (n in cache) {
return cache[n];
} else {
let result = fn(...args);
cache[n] = result;
return result;
}
};
};
/**
* This calculates the width of the given text, font size and family.
*
* @param {any} text - The text to calculate the width of
* @param {any} config - The config for fontSize, fontFamily, and fontWeight all impacting the resulting size
* @returns {any} - The width for the given text
*/
export const calculateTextWidth = function (text, config) {
config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config);
return calculateTextDimensions(text, config).width;
};
export const getTextObj = function () {
return {
x: 0,
y: 0,
fill: undefined,
anchor: 'start',
style: '#666',
width: 100,
height: 100,
textMargin: 0,
rx: 0,
ry: 0,
valign: undefined,
};
};
/**
* Adds text to an element
*
* @param {SVGElement} elem Element to add text to
* @param {{
* text: string;
* x: number;
* y: number;
* anchor: 'start' | 'middle' | 'end';
* fontFamily: string;
* fontSize: string | number;
* fontWeight: string | number;
* fill: string;
* class: string | undefined;
* textMargin: number;
* }} textData
* @returns {SVGTextElement} Text element with given styling and content
*/
export const drawSimpleText = function (elem, textData) {
// Remove and ignore br:s
const nText = textData.text.replace(lineBreakRegex, ' ');
const textElem = elem.append('text');
textElem.attr('x', textData.x);
textElem.attr('y', textData.y);
textElem.style('text-anchor', textData.anchor);
textElem.style('font-family', textData.fontFamily);
textElem.style('font-size', textData.fontSize);
textElem.style('font-weight', textData.fontWeight);
textElem.attr('fill', textData.fill);
if (typeof textData.class !== 'undefined') {
textElem.attr('class', textData.class);
}
const span = textElem.append('tspan');
span.attr('x', textData.x + textData.textMargin * 2);
span.attr('fill', textData.fill);
span.text(nText);
return textElem;
};
/**
* This calculates the dimensions of the given text, font size, font family, font weight, and margins.
*
* @param {any} text - The text to calculate the width of
* @param {any} config - The config for fontSize, fontFamily, fontWeight, and margin all impacting
* the resulting size
* @returns - The width for the given text
*/
export const calculateTextDimensions = memoize(
function (text, config) {
config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config);
const { fontSize, fontFamily, fontWeight } = config;
if (!text) {
return { width: 0, height: 0 };
}
// We can't really know if the user supplied font family will render on the user agent;
// thus, we'll take the max width between the user supplied font family, and a default
// of sans-serif.
const fontFamilies = ['sans-serif', fontFamily];
const lines = text.split(common.lineBreakRegex);
let dims = [];
const body = select('body');
// We don't want to leak DOM elements - if a removal operation isn't available
// for any reason, do not continue.
if (!body.remove) {
return { width: 0, height: 0, lineHeight: 0 };
}
const g = body.append('svg');
for (let fontFamily of fontFamilies) {
let cheight = 0;
let dim = { width: 0, height: 0, lineHeight: 0 };
for (let line of lines) {
const textObj = getTextObj();
textObj.text = line;
const textElem = drawSimpleText(g, textObj)
.style('font-size', fontSize)
.style('font-weight', fontWeight)
.style('font-family', fontFamily);
let bBox = (textElem._groups || textElem)[0][0].getBBox();
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));
}
dims.push(dim);
}
g.remove();
let index =
isNaN(dims[1].height) ||
isNaN(dims[1].width) ||
isNaN(dims[1].lineHeight) ||
(dims[0].height > dims[1].height &&
dims[0].width > dims[1].width &&
dims[0].lineHeight > dims[1].lineHeight)
? 0
: 1;
return dims[index];
},
(text, config) => `${text}-${config.fontSize}-${config.fontWeight}-${config.fontFamily}`
);
const breakString = memoize(
(word, maxWidth, hyphenCharacter = '-', config) => {
config = Object.assign(
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 0 },
config
);
const characters = word.split('');
const lines = [];
let currentLine = '';
characters.forEach((character, index) => {
const nextLine = `${currentLine}${character}`;
const lineWidth = calculateTextWidth(nextLine, config);
if (lineWidth >= maxWidth) {
const currentCharacter = index + 1;
const isLastLine = characters.length === currentCharacter;
const hyphenatedNextLine = `${nextLine}${hyphenCharacter}`;
lines.push(isLastLine ? nextLine : hyphenatedNextLine);
currentLine = '';
} else {
currentLine = nextLine;
}
});
return { hyphenatedStrings: lines, remainingWord: currentLine };
},
(word, maxWidth, hyphenCharacter = '-', config) =>
`${word}-${maxWidth}-${hyphenCharacter}-${config.fontSize}-${config.fontWeight}-${config.fontFamily}`
);
export const wrapLabel = memoize(
(label, maxWidth, config) => {
if (!label) {
return label;
}
config = Object.assign(
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', joinWith: '<br/>' },
config
);
if (lineBreakRegex.test(label)) {
return label;
}
const words = label.split(' ');
const completedLines = [];
let nextLine = '';
words.forEach((word, index) => {
const wordLength = calculateTextWidth(`${word} `, config);
const nextLineLength = calculateTextWidth(nextLine, config);
if (wordLength > maxWidth) {
const { hyphenatedStrings, remainingWord } = breakString(word, maxWidth, '-', config);
completedLines.push(nextLine, ...hyphenatedStrings);
nextLine = remainingWord;
} else if (nextLineLength + wordLength >= maxWidth) {
completedLines.push(nextLine);
nextLine = word;
} else {
nextLine = [nextLine, word].filter(Boolean).join(' ');
}
const currentWord = index + 1;
const isLastWord = currentWord === words.length;
if (isLastWord) {
completedLines.push(nextLine);
}
});
return completedLines.filter((line) => line !== '').join(config.joinWith);
},
(label, maxWidth, config) =>
`${label}-${maxWidth}-${config.fontSize}-${config.fontWeight}-${config.fontFamily}-${config.joinWith}`
);

View File

@@ -0,0 +1,26 @@
import { MermaidConfig } from '../config.type';
export interface InjectUtils {
_log: any;
_setLogLevel: any;
_getConfig: any;
_sanitizeText: any;
_setupGraphViewbox: any;
}
export interface DiagramDefinition {
db: any;
renderer: any;
parser: any;
styles: any;
init?: (config: MermaidConfig) => void;
injectUtils?: (utils: InjectUtils) => void;
}
export interface DetectorRecord {
detector: DiagramDetector;
loader?: DiagramLoader;
}
export type DiagramDetector = (text: string, config?: MermaidConfig) => boolean;
export type DiagramLoader = (() => Promise<{ id: string; diagram: DiagramDefinition }>) | null;

View File

@@ -1,4 +1,4 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
import type { DiagramDetector } from '../../diagram-api/types';
export const c4Detector: DiagramDetector = (txt) => {
return txt.match(/^\s*C4Context|C4Container|C4Component|C4Dynamic|C4Deployment/) !== null;

View File

@@ -1,4 +1,4 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
import type { DiagramDetector } from '../../diagram-api/types';
export const classDetectorV2: DiagramDetector = (txt, config) => {
// If we have confgured to use dagre-wrapper then we should return true in this function for classDiagram code thus making it use the new class diagram

View File

@@ -1,4 +1,4 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
import type { DiagramDetector } from '../../diagram-api/types';
export const classDetector: DiagramDetector = (txt, config) => {
// If we have confgured to use dagre-wrapper then we should never return true in this function

View File

@@ -1,4 +1,4 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
import type { DiagramDetector } from '../../diagram-api/types';
export const erDetector: DiagramDetector = (txt) => {
return txt.match(/^\s*erDiagram/) !== null;

View File

@@ -429,8 +429,7 @@ export const clear = function (ver = 'gen-1') {
vertices = {};
classes = {};
edges = [];
funs = [];
funs.push(setupToolTips);
funs = [setupToolTips];
subGraphs = [];
subGraphLookup = {};
subCount = 0;

View File

@@ -1,4 +1,4 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
import type { DiagramDetector } from '../../diagram-api/types';
export const flowDetectorV2: DiagramDetector = (txt, config) => {
// If we have confgured to use dagre-wrapper then we should return true in this function for graph code thus making it use the new flowchart diagram

View File

@@ -1,4 +1,4 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
import type { DiagramDetector } from '../../diagram-api/types';
export const flowDetector: DiagramDetector = (txt, config) => {
// If we have confired to only use new flow charts this function shohuld always return false

View File

@@ -1,4 +1,4 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
import type { DiagramDetector } from '../../diagram-api/types';
export const ganttDetector: DiagramDetector = (txt) => {
return txt.match(/^\s*gantt/) !== null;

View File

@@ -1,4 +1,4 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
import type { DiagramDetector } from '../../diagram-api/types';
export const gitGraphDetector: DiagramDetector = (txt) => {
return txt.match(/^\s*gitGraph/) !== null;

View File

@@ -1,4 +1,4 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
import type { DiagramDetector } from '../../diagram-api/types';
export const infoDetector: DiagramDetector = (txt) => {
return txt.match(/^\s*info/) !== null;

View File

@@ -1,4 +1,4 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
import type { DiagramDetector } from '../../diagram-api/types';
export const pieDetector: DiagramDetector = (txt) => {
return txt.match(/^\s*pie/) !== null;

View File

@@ -1,4 +1,4 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
import type { DiagramDetector } from '../../diagram-api/types';
export const requirementDetector: DiagramDetector = (txt) => {
return txt.match(/^\s*requirement(Diagram)?/) !== null;

View File

@@ -1,4 +1,4 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
import type { DiagramDetector } from '../../diagram-api/types';
export const sequenceDetector: DiagramDetector = (txt) => {
return txt.match(/^\s*sequenceDiagram/) !== null;

View File

@@ -1,4 +1,4 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
import type { DiagramDetector } from '../../diagram-api/types';
export const stateDetectorV2: DiagramDetector = (text, config) => {
if (text.match(/^\s*stateDiagram-v2/) !== null) return true;

View File

@@ -1,4 +1,4 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
import type { DiagramDetector } from '../../diagram-api/types';
export const stateDetector: DiagramDetector = (txt, config) => {
// If we have confired to only use new state diagrams this function should always return false

View File

@@ -1,4 +1,4 @@
import type { DiagramDetector } from '../../diagram-api/detectType';
import type { DiagramDetector } from '../../diagram-api/types';
export const journeyDetector: DiagramDetector = (txt) => {
return txt.match(/^\s*journey/) !== null;

View File

@@ -7,14 +7,6 @@ import { log } from './logger';
import utils from './utils';
import { mermaidAPI } from './mermaidAPI';
import { addDetector } from './diagram-api/detectType';
import {
registerDiagram,
DiagramDefinition,
setLogLevel,
getConfig,
setupGraphViewbox,
sanitizeText,
} from './diagram-api/diagramAPI';
import { isDetailedError } from './utils';
/**
@@ -53,17 +45,16 @@ const init = async function (
callback?: Function
) {
try {
log.info('Detectors in init', mermaid.detectors); // eslint-disable-line
const conf = mermaidAPI.getConfig();
if (typeof conf.lazyLoadedDiagrams !== 'undefined' && conf.lazyLoadedDiagrams.length > 0) {
for (let i = 0; i < conf.lazyLoadedDiagrams.length; i++) {
const { id, detector, loadDiagram } = await import(conf.lazyLoadedDiagrams[i]);
if (conf?.lazyLoadedDiagrams && conf.lazyLoadedDiagrams.length > 0) {
// Load all lazy loaded diagrams in parallel
await Promise.allSettled(
conf.lazyLoadedDiagrams.map(async (diagram: string) => {
const { id, detector, loadDiagram } = await import(diagram);
addDetector(id, detector, loadDiagram);
})
);
}
}
mermaid.detectors.forEach(({ id, detector, path }) => {
addDetector(id, detector, path);
});
await initThrowsErrors(config, nodes, callback);
} catch (e) {
log.warn('Syntax Error rendering');
@@ -218,22 +209,6 @@ const parse = (txt: string) => {
return mermaidAPI.parse(txt, mermaid.parseError);
};
const connectDiagram = (
id: string,
diagram: DiagramDefinition,
callback: (
_log: any,
_setLogLevel: any,
_getConfig: any,
_sanitizeText: any,
_setupGraphViewbox: any
) => void
) => {
registerDiagram(id, diagram, callback);
// Todo move this connect call to after the diagram is actually loaded.
callback(log, setLogLevel, getConfig, sanitizeText, setupGraphViewbox);
};
const mermaid: {
startOnLoad: boolean;
diagrams: any;
@@ -247,9 +222,6 @@ const mermaid: {
initialize: typeof initialize;
contentLoaded: typeof contentLoaded;
setParseErrorHandler: typeof setParseErrorHandler;
// Array of functions to use for detecting diagram types
detectors: Array<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
connectDiagram: (id: string, diagram: DiagramDefinition, callback: (id: string) => void) => void;
} = {
startOnLoad: true,
diagrams: {},
@@ -262,8 +234,6 @@ const mermaid: {
parseError: undefined,
contentLoaded,
setParseErrorHandler,
detectors: [],
connectDiagram: connectDiagram,
};
export default mermaid;

View File

@@ -18,7 +18,6 @@ import { compile, serialize, stringify } from 'stylis';
import pkg from '../package.json';
import * as configApi from './config';
import { addDiagrams } from './diagram-api/diagram-orchestration';
import { addDetector } from './diagram-api/detectType';
import classDb from './diagrams/class/classDb';
import flowDb from './diagrams/flowchart/flowDb';
import flowRenderer from './diagrams/flowchart/flowRenderer';
@@ -34,18 +33,13 @@ import DOMPurify from 'dompurify';
import { MermaidConfig } from './config.type';
import { evaluate } from './diagrams/common/common';
let hasLoadedDiagrams = false;
/**
* @param text
* @param parseError
*/
// eslint-disable-next-line @typescript-eslint/ban-types
function parse(text: string, parseError?: Function): boolean {
if (!hasLoadedDiagrams) {
addDiagrams();
hasLoadedDiagrams = true;
}
const diagram = new Diagram(text, parseError);
return diagram.parse(text, parseError);
}
@@ -122,10 +116,7 @@ const render = async function (
cb: (svgCode: string, bindFunctions?: (element: Element) => void) => void,
container?: Element
): Promise<void> {
if (!hasLoadedDiagrams) {
addDiagrams();
hasLoadedDiagrams = true;
}
configApi.reset();
text = text.replace(/\r\n?/g, '\n'); // parser problems on CRLF ignore all CR and leave LF;;
const graphInit = utils.detectInit(text);
@@ -486,11 +477,7 @@ async function initialize(options: MermaidConfig) {
typeof options === 'object' ? configApi.setSiteConfig(options) : configApi.getSiteConfig();
setLogLevel(config.logLevel);
if (!hasLoadedDiagrams) {
addDiagrams();
hasLoadedDiagrams = true;
}
}
export const mermaidAPI = Object.freeze({

View File

@@ -1,108 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true /* Enable incremental compilation */,
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "ES6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"lib": [
"DOM",
"ES2021"
] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
"module": "ES6" /* Specify what module code is generated. */,
"rootDir": "./src" /* Specify the root folder within your source files. */,
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./src" /* Specify the base directory to resolve non-relative module names. */,
// "paths": {} /* Specify a set of entries that re-map imports to additional lookup locations. */,
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [] /* Specify multiple folders that act like `./node_modules/@types`. */,
"types": [
"vitest/globals"
] /* Specify type package names to be included without being referenced in a source file. */,
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
"resolveJsonModule": true /* Enable importing .json files */,
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
"allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */,
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
"declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": ["./src/**/*.ts", "./package.json"]
}