mirror of
				https://github.com/mermaid-js/mermaid.git
				synced 2025-10-26 16:34:08 +01: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