mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-15 06:19:24 +02:00
Compare commits
3 Commits
mermaid@11
...
sidv/prior
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5dc3850c52 | ||
![]() |
927217d77c | ||
![]() |
b5f3cdc0b0 |
@@ -1,7 +1,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 { detectType, getDiagramLoaderAndPriority } from './diagram-api/detectType.js';
|
||||
import { UnknownDiagramError } from './errors.js';
|
||||
import { encodeEntities } from './utils.js';
|
||||
|
||||
@@ -92,14 +92,14 @@ export const getDiagramFromText = async (
|
||||
// Trying to find the diagram
|
||||
getDiagram(type);
|
||||
} catch (error) {
|
||||
const loader = getDiagramLoader(type);
|
||||
const { loader, priority } = getDiagramLoaderAndPriority(type);
|
||||
if (!loader) {
|
||||
throw new UnknownDiagramError(`Diagram ${type} not found.`);
|
||||
}
|
||||
// Diagram not available, loading it.
|
||||
// new diagram will try getDiagram again and if fails then it is a valid throw
|
||||
const { id, diagram } = await loader();
|
||||
registerDiagram(id, diagram);
|
||||
registerDiagram(id, diagram, priority);
|
||||
}
|
||||
return new Diagram(text, metadata);
|
||||
};
|
||||
|
@@ -61,23 +61,37 @@ export const detectType = function (text: string, config?: MermaidConfig): strin
|
||||
* The first detector to return `true` is the diagram that will be loaded
|
||||
* and used, so put more specific detectors at the beginning!
|
||||
*
|
||||
* If two diagrams are registered with the same id,
|
||||
* the one with higher `priority` property will be used.
|
||||
*
|
||||
* @param diagrams - Diagrams to lazy load, and their detectors, in order of importance.
|
||||
*/
|
||||
export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinition[]) => {
|
||||
for (const { id, detector, loader } of diagrams) {
|
||||
addDetector(id, detector, loader);
|
||||
for (const { id, detector, priority, loader } of diagrams) {
|
||||
addDetector(id, detector, priority ?? 0, loader);
|
||||
}
|
||||
};
|
||||
|
||||
export const addDetector = (key: string, detector: DiagramDetector, loader?: DiagramLoader) => {
|
||||
if (detectors[key]) {
|
||||
log.error(`Detector with key ${key} already exists`);
|
||||
} else {
|
||||
detectors[key] = { detector, loader };
|
||||
export const addDetector = (
|
||||
key: string,
|
||||
detector: DiagramDetector,
|
||||
priority: number,
|
||||
loader?: DiagramLoader
|
||||
) => {
|
||||
if (detectors[key] && priority <= detectors[key].priority) {
|
||||
log.error(
|
||||
`Detector with key ${key} already exists with priority ${detectors[key].priority}. Cannot add new detector with priority ${priority}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
log.debug(`Detector with key ${key} added${loader ? ' with loader' : ''}`);
|
||||
|
||||
detectors[key] = { detector, loader, priority };
|
||||
log.debug(
|
||||
`Detector with key ${key} added with priority ${priority} ${loader ? 'and loader' : ''}`
|
||||
);
|
||||
};
|
||||
|
||||
export const getDiagramLoader = (key: string) => {
|
||||
return detectors[key].loader;
|
||||
export const getDiagramLoaderAndPriority = (key: string) => {
|
||||
const { loader, priority } = detectors[key];
|
||||
return { loader, priority };
|
||||
};
|
||||
|
@@ -31,7 +31,7 @@ export const addDiagrams = () => {
|
||||
// This is added here to avoid race-conditions.
|
||||
// We could optimize the loading logic somehow.
|
||||
hasLoadedDiagrams = true;
|
||||
registerDiagram('error', errorDiagram, (text) => {
|
||||
registerDiagram('error', errorDiagram, 0, (text) => {
|
||||
return text.toLowerCase().trim() === 'error';
|
||||
});
|
||||
registerDiagram(
|
||||
@@ -60,6 +60,7 @@ export const addDiagrams = () => {
|
||||
},
|
||||
init: () => null, // no op
|
||||
},
|
||||
0,
|
||||
(text) => {
|
||||
return text.toLowerCase().trimStart().startsWith('---');
|
||||
}
|
||||
|
@@ -47,6 +47,7 @@ describe('DiagramAPI', () => {
|
||||
},
|
||||
styles: {},
|
||||
},
|
||||
0,
|
||||
detector
|
||||
);
|
||||
expect(getDiagram('loki')).not.toBeNull();
|
||||
|
@@ -29,7 +29,7 @@ export const getCommonDb = () => {
|
||||
return _commonDb;
|
||||
};
|
||||
|
||||
const diagrams: Record<string, DiagramDefinition> = {};
|
||||
const diagrams: Record<string, DiagramDefinition & { priority: number }> = {};
|
||||
export interface Detectors {
|
||||
[key: string]: DiagramDetector;
|
||||
}
|
||||
@@ -37,7 +37,8 @@ export interface Detectors {
|
||||
/**
|
||||
* Registers the given diagram with Mermaid.
|
||||
*
|
||||
* Can be used for third-party custom diagrams.
|
||||
* To be used internally by Mermaid.
|
||||
* Use `mermaid.registerExternalDiagrams` to register external diagrams.
|
||||
*
|
||||
* @param id - A unique ID for the given diagram.
|
||||
* @param diagram - The diagram definition.
|
||||
@@ -46,14 +47,17 @@ export interface Detectors {
|
||||
export const registerDiagram = (
|
||||
id: string,
|
||||
diagram: DiagramDefinition,
|
||||
priority: number,
|
||||
detector?: DiagramDetector
|
||||
) => {
|
||||
if (diagrams[id]) {
|
||||
throw new Error(`Diagram ${id} already registered.`);
|
||||
if (diagrams[id] && priority <= diagrams[id].priority) {
|
||||
throw new Error(
|
||||
`Diagram ${id} already registered with priority ${diagrams[id].priority}. Cannot add new diagram with priority ${priority}`
|
||||
);
|
||||
}
|
||||
diagrams[id] = diagram;
|
||||
diagrams[id] = { ...diagram, priority };
|
||||
if (detector) {
|
||||
addDetector(id, detector);
|
||||
addDetector(id, detector, priority);
|
||||
}
|
||||
addStylesForDiagram(id, diagram.styles);
|
||||
|
||||
|
@@ -6,7 +6,7 @@ export const loadRegisteredDiagrams = async () => {
|
||||
log.debug(`Loading registered diagrams`);
|
||||
// Load all lazy loaded diagrams in parallel
|
||||
const results = await Promise.allSettled(
|
||||
Object.entries(detectors).map(async ([key, { detector, loader }]) => {
|
||||
Object.entries(detectors).map(async ([key, { detector, loader, priority }]) => {
|
||||
if (loader) {
|
||||
try {
|
||||
getDiagram(key);
|
||||
@@ -14,7 +14,7 @@ export const loadRegisteredDiagrams = async () => {
|
||||
try {
|
||||
// Register diagram if it is not already registered
|
||||
const { diagram, id } = await loader();
|
||||
registerDiagram(id, diagram, detector);
|
||||
registerDiagram(id, diagram, priority, detector);
|
||||
} catch (err) {
|
||||
// Remove failed diagram from detectors
|
||||
log.error(`Failed to load external diagram with key ${key}. Removing from detectors.`);
|
||||
|
@@ -76,13 +76,23 @@ export interface DiagramDefinition {
|
||||
|
||||
export interface DetectorRecord {
|
||||
detector: DiagramDetector;
|
||||
priority: number;
|
||||
loader?: DiagramLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* External diagrams, which are not bundled with mermaid should expose the following to be registered using the `mermaid.registerExternalDiagrams` function.
|
||||
*
|
||||
* @param id - An ID for the given diagram. If two diagrams are registered with the same ID, the one with the higher priority will be used.
|
||||
* @param detector - Function that returns `true` if a given mermaid text satisfies with this diagram definition.
|
||||
* @param loader - Function that returns a promise of the diagram definition.
|
||||
* @param priority - The priority of the diagram. Optional, defaults to 0.
|
||||
*/
|
||||
export interface ExternalDiagramDefinition {
|
||||
id: string;
|
||||
detector: DiagramDetector;
|
||||
loader: DiagramLoader;
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
export type DiagramDetector = (text: string, config?: MermaidConfig) => boolean;
|
||||
|
@@ -21,6 +21,7 @@ describe('diagram detection', () => {
|
||||
addDetector(
|
||||
'loki',
|
||||
(str) => str.startsWith('loki'),
|
||||
0,
|
||||
() =>
|
||||
Promise.resolve({
|
||||
id: 'loki',
|
||||
@@ -45,6 +46,37 @@ describe('diagram detection', () => {
|
||||
expect(diagram.type).toBe('loki');
|
||||
});
|
||||
|
||||
test('should allow external diagrams to override internal ones with same ID', async () => {
|
||||
addDetector(
|
||||
'flowchart-elk',
|
||||
(str) => str.startsWith('flowchart-elk'),
|
||||
1,
|
||||
() =>
|
||||
Promise.resolve({
|
||||
id: 'flowchart-elk',
|
||||
diagram: {
|
||||
db: {
|
||||
getDiagramTitle: () => 'overridden',
|
||||
},
|
||||
parser: {
|
||||
parse: () => {
|
||||
// no-op
|
||||
},
|
||||
},
|
||||
renderer: {
|
||||
draw: () => {
|
||||
// no-op
|
||||
},
|
||||
},
|
||||
styles: {},
|
||||
},
|
||||
})
|
||||
);
|
||||
const diagram = (await getDiagramFromText('flowchart-elk TD; A-->B')) as Diagram;
|
||||
expect(diagram).toBeInstanceOf(Diagram);
|
||||
expect(diagram.db.getDiagramTitle?.()).toBe('overridden');
|
||||
});
|
||||
|
||||
test('should throw the right error for incorrect diagram', async () => {
|
||||
await expect(getDiagramFromText('graph TD; A-->')).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Parse error on line 2:
|
||||
|
@@ -15,6 +15,7 @@ import { isDetailedError } from './utils.js';
|
||||
import type { DetailedError } from './utils.js';
|
||||
import type { ExternalDiagramDefinition } from './diagram-api/types.js';
|
||||
import type { UnknownDiagramError } from './errors.js';
|
||||
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
|
||||
|
||||
export type {
|
||||
MermaidConfig,
|
||||
@@ -243,6 +244,7 @@ const registerExternalDiagrams = async (
|
||||
lazyLoad?: boolean;
|
||||
} = {}
|
||||
) => {
|
||||
addDiagrams();
|
||||
registerLazyLoadedDiagrams(...diagrams);
|
||||
if (lazyLoad === false) {
|
||||
await loadRegisteredDiagrams();
|
||||
|
Reference in New Issue
Block a user