mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-28 07:03:17 +08:00
fix: Type of DiagramStyleClassDef, general cleanup
This commit is contained in:
parent
ea86ef3995
commit
377b22e82b
@ -22,7 +22,7 @@ import { MermaidConfig } from './config.type';
|
||||
*
|
||||
* @name Configuration
|
||||
*/
|
||||
const config: Partial<MermaidConfig> = {
|
||||
const config: MermaidConfig = {
|
||||
/**
|
||||
* Theme , the CSS style sheet
|
||||
*
|
||||
@ -1069,6 +1069,7 @@ const config: Partial<MermaidConfig> = {
|
||||
showCommitLabel: true,
|
||||
showBranches: true,
|
||||
rotateCommitLabel: true,
|
||||
arrowMarkerAbsolute: false,
|
||||
},
|
||||
|
||||
/** The object containing configurations specific for c4 diagrams */
|
||||
@ -1833,9 +1834,6 @@ const config: Partial<MermaidConfig> = {
|
||||
fontSize: 16,
|
||||
};
|
||||
|
||||
if (config.class) config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
if (config.gitGraph) config.gitGraph.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
|
||||
const keyify = (obj: any, prefix = ''): string[] =>
|
||||
Object.keys(obj).reduce((res: string[], el): string[] => {
|
||||
if (Array.isArray(obj[el])) {
|
||||
|
@ -17,7 +17,7 @@ let vertexCounter = 0;
|
||||
let config = configApi.getConfig();
|
||||
let vertices = {};
|
||||
let edges = [];
|
||||
let classes = [];
|
||||
let classes = {};
|
||||
let subGraphs = [];
|
||||
let subGraphLookup = {};
|
||||
let tooltips = {};
|
||||
|
@ -279,7 +279,8 @@ export const getClasses = function (text, diagObj) {
|
||||
diagObj.parse(text);
|
||||
return diagObj.db.getClasses();
|
||||
} catch (e) {
|
||||
return;
|
||||
log.error(e);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -256,11 +256,11 @@ describe('mermaidAPI', function () {
|
||||
expect(styles).toMatch(/^\ndefault(.*)/);
|
||||
});
|
||||
it('gets the fontFamily from the config', () => {
|
||||
const styles = createCssStyles(mocked_config_with_htmlLabels, 'graphType', null);
|
||||
const styles = createCssStyles(mocked_config_with_htmlLabels, 'graphType', {});
|
||||
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', null);
|
||||
const styles = createCssStyles(mocked_config_with_htmlLabels, 'graphType', undefined);
|
||||
expect(styles).toMatch(/(.*)\n:root \{ --mermaid-alt-font-family: sans-serif(.*)/);
|
||||
});
|
||||
|
||||
@ -268,7 +268,7 @@ describe('mermaidAPI', function () {
|
||||
const classDef1 = { id: 'classDef1', styles: ['style1-1', 'style1-2'], textStyles: [] };
|
||||
const classDef2 = { id: 'classDef2', styles: [], textStyles: ['textStyle2-1'] };
|
||||
const classDef3 = { id: 'classDef3', textStyles: ['textStyle3-1', 'textStyle3-2'] };
|
||||
const classDefs = [classDef1, classDef2, classDef3];
|
||||
const classDefs = { classDef1, classDef2, classDef3 };
|
||||
|
||||
describe('the graph supports classDefs', () => {
|
||||
const graphType = 'flowchart-v2';
|
||||
@ -431,7 +431,7 @@ describe('mermaidAPI', function () {
|
||||
it('gets the css styles created', () => {
|
||||
// @todo TODO if a single function in the module can be mocked, do it for createCssStyles and mock the results.
|
||||
|
||||
createUserStyles(mockConfig, 'flowchart-v2', [classDef1], 'someId');
|
||||
createUserStyles(mockConfig, 'flowchart-v2', { classDef1 }, 'someId');
|
||||
const expectedStyles =
|
||||
'\ndefault' +
|
||||
'\n.classDef1 > * { style1-1 !important; }' +
|
||||
@ -442,12 +442,12 @@ describe('mermaidAPI', function () {
|
||||
});
|
||||
|
||||
it('calls getStyles to get css for all graph, user css styles, and config theme variables', () => {
|
||||
createUserStyles(mockConfig, 'someDiagram', null, 'someId');
|
||||
createUserStyles(mockConfig, 'someDiagram', {}, 'someId');
|
||||
expect(getStyles).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns the result of compiling, stringifying, and serializing the css code with stylis', () => {
|
||||
const result = createUserStyles(mockConfig, 'someDiagram', null, 'someId');
|
||||
const result = createUserStyles(mockConfig, 'someDiagram', {}, 'someId');
|
||||
expect(compile).toHaveBeenCalled();
|
||||
expect(serialize).toHaveBeenCalled();
|
||||
expect(result).toEqual('stylis serialized css');
|
||||
|
@ -28,7 +28,7 @@ import { attachFunctions } from './interactionDb';
|
||||
import { log, setLogLevel } from './logger';
|
||||
import getStyles from './styles';
|
||||
import theme from './themes';
|
||||
import utils, { directiveSanitizer } from './utils';
|
||||
import utils, { directiveSanitizer, isNonEmptyArray } from './utils';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { MermaidConfig } from './config.type';
|
||||
import { evaluate } from './diagrams/common/common';
|
||||
@ -59,8 +59,11 @@ const DOMPURE_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.
|
||||
// @ts-ignore This is an alias for a js construct used in diagrams.
|
||||
type DiagramStyleClassDef = any;
|
||||
interface DiagramStyleClassDef {
|
||||
id: string;
|
||||
styles?: string[];
|
||||
textStyles?: string[];
|
||||
}
|
||||
|
||||
// This makes it clear that we're working with a d3 selected element of some kind, even though it's hard to specify the exact type.
|
||||
// @ts-ignore Could replicate the type definition in d3. This also makes it possible to use the untyped info from the js diagram files.
|
||||
@ -151,29 +154,32 @@ export const cssImportantStyles = (
|
||||
*
|
||||
* @param {MermaidConfig} config
|
||||
* @param {string} graphType
|
||||
* @param {null | DiagramStyleClassDef[]} classDefs - the classDefs in the diagram text. Might be null if none were defined. Usually is the result of a call to getClasses(...)
|
||||
* @param {Record<string, DiagramStyleClassDef> | null | undefined} classDefs - the classDefs in the diagram text. Might be null if none were defined. Usually is the result of a call to getClasses(...)
|
||||
* @returns {string} the string with all the user styles
|
||||
*/
|
||||
export const createCssStyles = (
|
||||
config: MermaidConfig,
|
||||
graphType: string,
|
||||
classDefs: DiagramStyleClassDef[] | null | undefined
|
||||
classDefs: Record<string, DiagramStyleClassDef> | null | undefined = {}
|
||||
): string => {
|
||||
let cssStyles = '';
|
||||
|
||||
// user provided theme CSS info
|
||||
// If you add more configuration driven data into the user styles make sure that the value is
|
||||
// sanitized by the santizeCSS function @todo TODO where is this method? what should be used to replace it? refactor so that it's always sanitized
|
||||
if (config.themeCSS !== undefined) cssStyles += `\n${config.themeCSS}`;
|
||||
if (config.themeCSS !== undefined) {
|
||||
cssStyles += `\n${config.themeCSS}`;
|
||||
}
|
||||
|
||||
if (config.fontFamily !== undefined)
|
||||
if (config.fontFamily !== undefined) {
|
||||
cssStyles += `\n:root { --mermaid-font-family: ${config.fontFamily}}`;
|
||||
|
||||
if (config.altFontFamily !== undefined)
|
||||
}
|
||||
if (config.altFontFamily !== undefined) {
|
||||
cssStyles += `\n:root { --mermaid-alt-font-family: ${config.altFontFamily}}`;
|
||||
}
|
||||
|
||||
// classDefs defined in the diagram text
|
||||
if (classDefs !== undefined && classDefs !== null && classDefs.length > 0) {
|
||||
if (classDefs && Object.keys(classDefs).length > 0) {
|
||||
if (graphType === 'flowchart' || graphType === 'flowchart-v2' || graphType === 'graph') {
|
||||
const htmlLabels = config.htmlLabels || config.flowchart?.htmlLabels;
|
||||
|
||||
@ -186,22 +192,14 @@ export const createCssStyles = (
|
||||
for (const classId in classDefs) {
|
||||
const styleClassDef = classDefs[classId];
|
||||
// create the css styles for each cssElement and the styles (only if there are styles)
|
||||
if (styleClassDef['styles'] && styleClassDef['styles'].length > 0) {
|
||||
if (isNonEmptyArray(styleClassDef.styles)) {
|
||||
cssElements.forEach((cssElement) => {
|
||||
cssStyles += cssImportantStyles(
|
||||
styleClassDef['id'],
|
||||
cssElement,
|
||||
styleClassDef['styles']
|
||||
);
|
||||
cssStyles += cssImportantStyles(styleClassDef.id, cssElement, styleClassDef.styles);
|
||||
});
|
||||
}
|
||||
// create the css styles for the tspan element and the text styles (only if there are textStyles)
|
||||
if (styleClassDef['textStyles'] && styleClassDef['textStyles'].length > 0) {
|
||||
cssStyles += cssImportantStyles(
|
||||
styleClassDef['id'],
|
||||
'tspan',
|
||||
styleClassDef['textStyles']
|
||||
);
|
||||
if (isNonEmptyArray(styleClassDef.textStyles)) {
|
||||
cssStyles += cssImportantStyles(styleClassDef.id, 'tspan', styleClassDef.textStyles);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -212,7 +210,7 @@ export const createCssStyles = (
|
||||
export const createUserStyles = (
|
||||
config: MermaidConfig,
|
||||
graphType: string,
|
||||
classDefs: null | DiagramStyleClassDef,
|
||||
classDefs: Record<string, DiagramStyleClassDef>,
|
||||
svgId: string
|
||||
): string => {
|
||||
const userCSSstyles = createCssStyles(config, graphType, classDefs);
|
||||
@ -261,8 +259,7 @@ export const cleanUpSvgCode = (
|
||||
* @todo TODO replace btoa(). Replace with buf.toString('base64')?
|
||||
*/
|
||||
export const putIntoIFrame = (svgCode = '', svgElement?: D3Element): string => {
|
||||
let height = IFRAME_HEIGHT; // default iFrame height
|
||||
if (svgElement) height = svgElement.viewBox.baseVal.height + 'px';
|
||||
const height = svgElement ? svgElement.viewBox.baseVal.height + 'px' : IFRAME_HEIGHT;
|
||||
const base64encodedSrc = btoa('<body style="' + IFRAME_BODY_STYLE + '">' + svgCode + '</body>');
|
||||
return `<iframe style="width:${IFRAME_WIDTH};height:${height};${IFRAME_STYLES}" src="data:text/html;base64,${base64encodedSrc}" sandbox="${IFRAME_SANDBOX_OPTS}">
|
||||
${IFRAME_NOT_SUPPORTED_MSG}
|
||||
@ -291,14 +288,18 @@ export const appendDivSvgG = (
|
||||
): D3Element => {
|
||||
const enclosingDiv = parentRoot.append('div');
|
||||
enclosingDiv.attr('id', enclosingDivId);
|
||||
if (divStyle) enclosingDiv.attr('style', divStyle);
|
||||
if (divStyle) {
|
||||
enclosingDiv.attr('style', divStyle);
|
||||
}
|
||||
|
||||
const svgNode = enclosingDiv
|
||||
.append('svg')
|
||||
.attr('id', id)
|
||||
.attr('width', '100%')
|
||||
.attr('xmlns', XMLNS_SVG_STD);
|
||||
if (svgXlink) svgNode.attr('xmlns:xlink', svgXlink);
|
||||
if (svgXlink) {
|
||||
svgNode.attr('xmlns:xlink', svgXlink);
|
||||
}
|
||||
|
||||
svgNode.append('g');
|
||||
return parentRoot;
|
||||
@ -337,11 +338,15 @@ export const removeExistingElements = (
|
||||
) => {
|
||||
// Remove existing SVG element if it exists
|
||||
const existingSvg = doc.getElementById(id);
|
||||
if (existingSvg) existingSvg.remove();
|
||||
if (existingSvg) {
|
||||
existingSvg.remove();
|
||||
}
|
||||
|
||||
// Remove previous temporary element if it exists
|
||||
const element = isSandboxed ? doc.querySelector(iFrameSelector) : doc.querySelector(divSelector);
|
||||
if (element) element.remove();
|
||||
if (element) {
|
||||
element.remove();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -389,7 +394,10 @@ const render = async function (
|
||||
log.debug(config);
|
||||
|
||||
// Check the maximum allowed text size
|
||||
if (text.length > config.maxTextSize!) text = MAX_TEXTLENGTH_EXCEEDED_MSG;
|
||||
// TODO: Remove magic number
|
||||
if (text.length > (config?.maxTextSize ?? 50000)) {
|
||||
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;;
|
||||
@ -472,6 +480,7 @@ const render = async function (
|
||||
const rules = createUserStyles(
|
||||
config,
|
||||
graphType,
|
||||
// @ts-ignore convert flowRender to TS.
|
||||
flowRenderer.getClasses(text, diag),
|
||||
idSelector
|
||||
);
|
||||
@ -485,7 +494,7 @@ const render = async function (
|
||||
try {
|
||||
await diag.renderer.draw(text, id, pkg.version, diag);
|
||||
} catch (e) {
|
||||
await errorRenderer.draw(text, id, pkg.version);
|
||||
errorRenderer.draw(text, id, pkg.version);
|
||||
throw e;
|
||||
}
|
||||
|
||||
@ -502,14 +511,12 @@ const render = async function (
|
||||
if (isSandboxed) {
|
||||
const svgEl = root.select(enclosingDivID_selector + ' svg').node();
|
||||
svgCode = putIntoIFrame(svgCode, svgEl);
|
||||
} else {
|
||||
if (isLooseSecurityLevel) {
|
||||
// Sanitize the svgCode using DOMPurify
|
||||
svgCode = DOMPurify.sanitize(svgCode, {
|
||||
ADD_TAGS: DOMPURE_TAGS,
|
||||
ADD_ATTR: DOMPURE_ATTR,
|
||||
});
|
||||
}
|
||||
} else if (isLooseSecurityLevel) {
|
||||
// Sanitize the svgCode using DOMPurify
|
||||
svgCode = DOMPurify.sanitize(svgCode, {
|
||||
ADD_TAGS: DOMPURE_TAGS,
|
||||
ADD_ATTR: DOMPURE_ATTR,
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
@ -530,7 +537,9 @@ const render = async function (
|
||||
default:
|
||||
cb(svgCode);
|
||||
}
|
||||
} else log.debug('CB = undefined!');
|
||||
} else {
|
||||
log.debug('CB = undefined!');
|
||||
}
|
||||
|
||||
attachFunctions();
|
||||
|
||||
@ -538,9 +547,13 @@ const render = async function (
|
||||
// Remove the temporary element if appropriate
|
||||
const tmpElementSelector = isSandboxed ? iFrameID_selector : enclosingDivID_selector;
|
||||
const node = select(tmpElementSelector).node();
|
||||
if (node && 'remove' in node) node.remove();
|
||||
if (node && 'remove' in node) {
|
||||
node.remove();
|
||||
}
|
||||
|
||||
if (parseEncounteredException) throw parseEncounteredException;
|
||||
if (parseEncounteredException) {
|
||||
throw parseEncounteredException;
|
||||
}
|
||||
|
||||
return svgCode;
|
||||
};
|
||||
|
@ -838,6 +838,10 @@ export function getErrorMessage(error: unknown): string {
|
||||
return String(error);
|
||||
}
|
||||
|
||||
export const isNonEmptyArray = (array: unknown[] | undefined): array is unknown[] => {
|
||||
return array && array.length > 0;
|
||||
};
|
||||
|
||||
export default {
|
||||
assignWithDepth,
|
||||
wrapLabel,
|
||||
|
Loading…
x
Reference in New Issue
Block a user