Compare commits

..

3 Commits

Author SHA1 Message Date
darshanr0107
5e74f76758 Merge branch 'develop' of https://github.com/mermaid-js/mermaid into fix-edge-direction-id-parsing 2025-11-10 13:36:53 +05:30
darshanr0107
b2159e4d8a Merge branch 'develop' into fix-edge-direction-id-parsing 2025-11-07 13:00:34 +05:30
darshanr0107
f7d7fe42aa fix: prevent edge direction tokens L,R,T,B from breaking ID parsing
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-10-27 13:33:48 +05:30
111 changed files with 1396 additions and 5606 deletions

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: Prevent HTML tags from being escaped in sandbox label rendering

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix(treemap): Fixed treemap classDef style application to properly apply user-defined styles

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
feat: add alias support for new participant syntax of sequence diagrams

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: Allow IDs starting with L, R, T, or B in parser

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: Support ComponentQueue_Ext to prevent parsing error

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: validate dates and tick interval to prevent UI freeze/crash in gantt diagramtype

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: Mindmap rendering issue when the number of Level 2 nodes exceeds 11

View File

@@ -1,53 +0,0 @@
# Dev Explorer frontend: architecture + debugging notes
## Root cause of the “CSS changes do nothing” problem
The page loads `/dev/styles.css`, but **document-level CSS does not apply through a shadow DOM boundary**.
Historically, `dev-explorer-app` was a `LitElement` using Lits default `shadowRoot`, while the rest of the UI used light DOM. That meant:
- The browser showed the _right classes_ (`card`, `card-folder`, `card-file`) in Elements panel.
- `/dev/styles.css` was clearly being served/updated.
- Yet computed styles for `.card` looked like UA defaults because the selector never matched across the shadow root.
Fix: make `dev-explorer-app` light DOM too (`createRenderRoot() { return this; }`), so `/dev/styles.css` reliably styles the whole UI.
## Debugging traps (and fast detection)
- **Shadow DOM trap**
- Symptom: “CSS is loaded but doesnt apply”, especially for simple class selectors.
- Fast check:
- DevTools console: `document.querySelector('dev-explorer-app')?.shadowRoot`
- If non-null, global CSS wont style inside it.
- Or: right-click an element you expect styled → “Reveal in Elements” → see if its under `#shadow-root`.
- **“Light DOM child inside shadow DOM parent” trap**
- Even if a child component uses `createRenderRoot() { return this; }`,
if its _rendered inside the parents shadow root_, its still effectively in shadow for document styles.
- **Dev loop trap (CSS-only changes dont trigger reload)**
- The server watches TypeScript bundle inputs + `.mmd` files; static `/dev/styles.css` previously didnt emit SSE reload events.
- That makes CSS changes look flaky unless you manually refresh.
- Fix: watch `.esbuild/dev-explorer/public/**/*` and emit SSE on changes.
- **Caching trap (less common here, but real)**
- If a query param is constant (`?v=3`) and you dont reload, the browser can keep a cached stylesheet.
- Fast check: DevTools → Network → disable cache + hard reload; or check “(from disk cache)” on the CSS request.
## Styling strategy recommendation (pragmatic)
For a dev-only explorer, keep it simple:
- **Light DOM everywhere**
- **One stylesheet**: `.esbuild/dev-explorer/public/styles.css` served as `/dev/styles.css`
- **Scoped selectors** under `dev-explorer-app` to avoid generic class collisions (`.header`, `.content`, etc.)
If you later _want_ Shadow DOM isolation, do it deliberately:
- Put UI styles in Lit `static styles` or adopt a `CSSStyleSheet` into `this.renderRoot.adoptedStyleSheets`.
- Avoid relying on document CSS selectors for component internals.
## Shoelace integration notes
- Current setup is correct for dev: `setBasePath('/dev/vendor/shoelace')` and `registerIconLibrary(...)`.
- Prefer theming via CSS variables (Shoelace tokens) rather than overriding internal parts everywhere.

View File

@@ -1,187 +0,0 @@
import { LitElement, html } from 'lit';
import '@shoelace-style/shoelace/dist/components/input/input.js';
export type LogLevel = 'info' | 'warn' | 'error';
export type LogEntry = {
ts: number;
level: LogLevel;
message: string;
};
function formatTs(ts: number) {
const d = new Date(ts);
return (
d.toLocaleTimeString(undefined, { hour12: false }) +
'.' +
String(d.getMilliseconds()).padStart(3, '0')
);
}
function levelVariant(level: LogLevel) {
switch (level) {
case 'error':
return 'danger';
case 'warn':
return 'warning';
default:
return 'neutral';
}
}
type DisplayLevel = 'debug' | LogLevel;
function displayLevel(entry: LogEntry): DisplayLevel {
// Mermaid often emits debug lines through console.log/info with a marker.
if (entry.message.includes(': DEBUG :')) return 'debug';
return entry.level;
}
function displayVariant(level: DisplayLevel) {
switch (level) {
case 'error':
return 'danger';
case 'warn':
return 'warning';
case 'debug':
return 'success';
default:
return 'neutral';
}
}
export class DevConsolePanel extends LitElement {
static properties = {
logs: { state: true },
showInfo: { state: true },
showWarn: { state: true },
showError: { state: true },
showDebug: { state: true },
filterText: { state: true },
};
declare logs: LogEntry[];
declare showInfo: boolean;
declare showWarn: boolean;
declare showError: boolean;
declare showDebug: boolean;
declare filterText: string;
constructor() {
super();
this.logs = [];
this.showInfo = true;
this.showWarn = true;
this.showError = true;
this.showDebug = true;
this.filterText = '';
}
createRenderRoot() {
return this;
}
clear() {
this.logs = [];
}
append(entry: LogEntry) {
this.logs = [...this.logs, entry];
}
async copyVisible() {
const visible = this.filteredLogs();
const text = visible
.map((l) => `[${formatTs(l.ts)}] ${l.level.toUpperCase()} ${l.message}`)
.join('\n');
await navigator.clipboard.writeText(text);
}
filteredLogs() {
const q = this.filterText.trim().toLowerCase();
return this.logs.filter((l) => {
const isDebugLine = l.message.includes(': DEBUG :');
// Treat debug-marked lines as their own independent toggle, since Mermaid often routes them through
// console.log/info with a marker rather than a distinct "debug" level.
if (isDebugLine && !this.showDebug) return false;
if (!isDebugLine) {
const levelOk =
l.level === 'info' ? this.showInfo : l.level === 'warn' ? this.showWarn : this.showError;
if (!levelOk) return false;
}
if (!q) return true;
return l.message.toLowerCase().includes(q);
});
}
render() {
const visible = this.filteredLogs();
return html`
<div class="console">
<div class="console-toolbar">
<div class="spacer"></div>
<sl-input
size="small"
placeholder="filter…"
clearable
value=${this.filterText}
@sl-input=${(e: any) => (this.filterText = e.target.value ?? '')}
></sl-input>
<sl-checkbox
size="small"
?checked=${this.showDebug}
@sl-change=${(e: any) => (this.showDebug = e.target.checked)}
>debug</sl-checkbox
>
<sl-checkbox
size="small"
?checked=${this.showInfo}
@sl-change=${(e: any) => (this.showInfo = e.target.checked)}
>info</sl-checkbox
>
<sl-checkbox
size="small"
?checked=${this.showWarn}
@sl-change=${(e: any) => (this.showWarn = e.target.checked)}
>warn</sl-checkbox
>
<sl-checkbox
size="small"
?checked=${this.showError}
@sl-change=${(e: any) => (this.showError = e.target.checked)}
>error</sl-checkbox
>
<sl-button size="small" variant="default" @click=${() => void this.copyVisible()}>
<sl-icon slot="prefix" name="clipboard"></sl-icon>
Copy
</sl-button>
<sl-button size="small" variant="default" @click=${() => this.clear()}>
<sl-icon slot="prefix" name="trash"></sl-icon>
Clear
</sl-button>
</div>
<div class="console-body">
${visible.length === 0
? html`<div class="empty">No logs yet.</div>`
: visible.map((l) => {
const lvl = displayLevel(l);
return html`
<div class="logline">
<div class="logmeta">
<sl-badge variant=${displayVariant(lvl)}>${lvl}</sl-badge>
<span class="path">${formatTs(l.ts)}</span>
</div>
<div>${l.message}</div>
</div>
`;
})}
</div>
</div>
`;
}
}
customElements.define('dev-console-panel', DevConsolePanel);

View File

@@ -1,551 +0,0 @@
import { LitElement, html, nothing } from 'lit';
import '@shoelace-style/shoelace/dist/components/button/button.js';
import '@shoelace-style/shoelace/dist/components/icon/icon.js';
import '@shoelace-style/shoelace/dist/components/select/select.js';
import '@shoelace-style/shoelace/dist/components/option/option.js';
import '@shoelace-style/shoelace/dist/components/checkbox/checkbox.js';
import '@shoelace-style/shoelace/dist/components/split-panel/split-panel.js';
import './console-panel';
import type { LogEntry, LogLevel } from './console-panel';
type MermaidIife = {
initialize: (config: Record<string, unknown>) => void | Promise<void>;
render: (
id: string,
text: string,
container?: Element
) => Promise<{ svg: string; bindFunctions?: (el: Element) => void }>;
};
declare global {
interface Window {
mermaid?: MermaidIife;
mermaidReady?: Promise<MermaidIife>;
}
}
function stringifyArgs(args: unknown[]) {
// Mermaid's internal logger frequently uses console formatting like:
// console.log('%c...message...', 'color: lightgreen', ...)
// For the log panel we want the human text, not the formatting tokens/styles.
let normalized = [...args];
if (typeof normalized[0] === 'string') {
const fmt = normalized[0];
const cssCount = (fmt.match(/%c/g) ?? []).length;
if (cssCount > 0) {
normalized[0] = fmt.replaceAll('%c', '');
// Drop the corresponding CSS args that follow the format string.
normalized.splice(1, cssCount);
}
}
return normalized
.map((a) => {
if (typeof a === 'string') return a;
if (a instanceof Error) return a.stack ?? a.message;
try {
return JSON.stringify(a);
} catch {
return String(a);
}
})
.join(' ')
.replace(/\s+/g, ' ')
.trim();
}
type MermaidTheme = 'default' | 'dark' | 'forest' | 'neutral' | 'base';
type MermaidLayout = 'dagre' | 'elk';
type MermaidLogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
const DEFAULT_THEME: MermaidTheme = 'default';
const DEFAULT_LAYOUT: MermaidLayout = 'dagre';
const DEFAULT_MERMAID_LOG_LEVEL: MermaidLogLevel = 'warn';
function readUrlParam(name: string) {
try {
return new URL(window.location.href).searchParams.get(name);
} catch {
return null;
}
}
function setUrlParams(pairs: Record<string, string | null | undefined>) {
const url = new URL(window.location.href);
for (const [k, v] of Object.entries(pairs)) {
if (!v) url.searchParams.delete(k);
else url.searchParams.set(k, v);
}
history.replaceState(null, '', url);
}
function readStorage(key: string) {
try {
return localStorage.getItem(key);
} catch {
return null;
}
}
function writeStorage(key: string, value: string) {
try {
localStorage.setItem(key, value);
} catch {
// ignore
}
}
function isTheme(v: unknown): v is MermaidTheme {
return v === 'default' || v === 'dark' || v === 'forest' || v === 'neutral' || v === 'base';
}
function isLayout(v: unknown): v is MermaidLayout {
return v === 'dagre' || v === 'elk';
}
function isMermaidLogLevel(v: unknown): v is MermaidLogLevel {
return (
v === 'trace' || v === 'debug' || v === 'info' || v === 'warn' || v === 'error' || v === 'fatal'
);
}
function normalizeLayout(v: unknown): MermaidLayout | null {
// Back-compat:
// - older UI used `renderer=dagre-d3|dagre-wrapper|elk`
// - new UI uses `layout=dagre|elk`
if (v === 'dagre' || v === 'elk') return v;
if (v === 'dagre-d3' || v === 'dagre-wrapper') return 'dagre';
return null;
}
function parseBoolean(v: unknown): boolean | null {
if (typeof v !== 'string') return null;
const s = v.trim().toLowerCase();
if (['1', 'true', 'yes', 'on'].includes(s)) return true;
if (['0', 'false', 'no', 'off'].includes(s)) return false;
return null;
}
export class DevDiagramViewer extends LitElement {
static properties = {
filePath: { type: String },
sseToken: { type: Number },
theme: { state: true },
layout: { state: true },
mermaidLogLevel: { state: true },
useMaxWidth: { state: true },
loading: { state: true },
error: { state: true },
source: { state: true },
svg: { state: true },
};
declare filePath: string;
declare sseToken: number;
declare theme: MermaidTheme;
declare layout: MermaidLayout;
declare mermaidLogLevel: MermaidLogLevel;
declare useMaxWidth: boolean;
declare loading: boolean;
declare error: string;
declare source: string;
declare svg: string;
#renderSeq = 0;
#consolePatched = false;
#originalConsole?: {
log: typeof console.log;
info: typeof console.info;
debug: typeof console.debug;
warn: typeof console.warn;
error: typeof console.error;
};
constructor() {
super();
const themeParam = readUrlParam('theme');
const layoutParam = readUrlParam('layout');
const rendererParam = readUrlParam('renderer'); // legacy
const logParam = readUrlParam('logLevel');
const useMaxWidthParam = readUrlParam('useMaxWidth');
const storedTheme = readStorage('devExplorer.viewer.theme');
const storedLayout = readStorage('devExplorer.viewer.layout');
const storedRenderer = readStorage('devExplorer.viewer.renderer'); // legacy
const storedLog = readStorage('devExplorer.viewer.logLevel');
const storedUseMaxWidth = readStorage('devExplorer.viewer.useMaxWidth');
this.theme = isTheme(themeParam)
? themeParam
: isTheme(storedTheme)
? storedTheme
: DEFAULT_THEME;
this.layout =
normalizeLayout(layoutParam) ??
normalizeLayout(rendererParam) ??
normalizeLayout(storedLayout) ??
normalizeLayout(storedRenderer) ??
DEFAULT_LAYOUT;
this.mermaidLogLevel = isMermaidLogLevel(logParam)
? logParam
: isMermaidLogLevel(storedLog)
? storedLog
: DEFAULT_MERMAID_LOG_LEVEL;
this.useMaxWidth = parseBoolean(useMaxWidthParam) ?? parseBoolean(storedUseMaxWidth) ?? true;
this.filePath = '';
this.sseToken = 0;
this.loading = true;
this.error = '';
this.source = '';
this.svg = '';
}
createRenderRoot() {
return this;
}
connectedCallback() {
super.connectedCallback();
this.#installConsoleCapture();
}
disconnectedCallback() {
super.disconnectedCallback();
this.#restoreConsoleCapture();
}
updated(changed: Map<string, unknown>) {
if (changed.has('filePath')) {
void this.#loadAndRender();
} else if (changed.has('sseToken')) {
// On rebuild events, re-fetch + re-render the currently open diagram.
if (this.filePath) void this.#loadAndRender();
} else if (
changed.has('theme') ||
changed.has('layout') ||
changed.has('mermaidLogLevel') ||
changed.has('useMaxWidth')
) {
// Re-render the currently loaded diagram with the new config without refetching.
if (this.source) void this.#renderCurrentSource();
}
}
#back() {
this.dispatchEvent(new CustomEvent('back', { bubbles: true, composed: true }));
}
#persistSettings() {
writeStorage('devExplorer.viewer.theme', this.theme);
writeStorage('devExplorer.viewer.layout', this.layout);
writeStorage('devExplorer.viewer.logLevel', this.mermaidLogLevel);
writeStorage('devExplorer.viewer.useMaxWidth', String(this.useMaxWidth));
setUrlParams({
theme: this.theme,
layout: this.layout,
renderer: null, // drop legacy param
logLevel: this.mermaidLogLevel,
useMaxWidth: this.useMaxWidth ? '1' : '0',
});
}
#syncConsolePanelFilters() {
const panel = this.querySelector('dev-console-panel') as any;
if (!panel) return;
// This is intentionally opinionated: less noise by default as logLevel increases.
if (
this.mermaidLogLevel === 'trace' ||
this.mermaidLogLevel === 'debug' ||
this.mermaidLogLevel === 'info'
) {
panel.showInfo = true;
panel.showWarn = true;
panel.showError = true;
return;
}
if (this.mermaidLogLevel === 'warn') {
panel.showInfo = false;
panel.showWarn = true;
panel.showError = true;
return;
}
// error / fatal
panel.showInfo = false;
panel.showWarn = false;
panel.showError = true;
}
#appendLog(entry: LogEntry) {
const panel = this.querySelector('dev-console-panel') as any;
panel?.append?.(entry);
}
#installConsoleCapture() {
if (this.#consolePatched) return;
this.#consolePatched = true;
this.#originalConsole = {
log: console.log,
info: console.info,
debug: console.debug,
warn: console.warn,
error: console.error,
};
const capture = (level: LogLevel, args: unknown[]) => {
this.#appendLog({
ts: Date.now(),
level,
message: stringifyArgs(args),
});
};
// Mermaid uses its own logger which routes to console.info/debug/warn/error.
// Capture those too (map debug/info/log -> panel "info").
console.log = (...args) => {
capture('info', args);
this.#originalConsole!.log.apply(console, args as any);
};
console.info = (...args) => {
capture('info', args);
this.#originalConsole!.info.apply(console, args as any);
};
console.debug = (...args) => {
capture('info', args);
this.#originalConsole!.debug.apply(console, args as any);
};
console.warn = (...args) => {
capture('warn', args);
this.#originalConsole!.warn.apply(console, args as any);
};
console.error = (...args) => {
capture('error', args);
this.#originalConsole!.error.apply(console, args as any);
};
}
#restoreConsoleCapture() {
if (!this.#consolePatched) return;
this.#consolePatched = false;
if (!this.#originalConsole) return;
console.log = this.#originalConsole.log;
console.info = this.#originalConsole.info;
console.debug = this.#originalConsole.debug;
console.warn = this.#originalConsole.warn;
console.error = this.#originalConsole.error;
this.#originalConsole = undefined;
}
#clearLogs() {
const panel = this.querySelector('dev-console-panel') as any;
panel?.clear?.();
}
async #fetchSource() {
const url = new URL('/dev/api/file', window.location.origin);
url.searchParams.set('path', this.filePath);
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.text();
}
async #loadAndRender() {
const seq = ++this.#renderSeq;
this.loading = true;
this.error = '';
this.svg = '';
this.#clearLogs();
this.#syncConsolePanelFilters();
try {
const source = await this.#fetchSource();
if (seq !== this.#renderSeq) return;
this.source = source;
await this.#renderMermaid(source);
} catch (e) {
this.error = e instanceof Error ? e.message : String(e);
} finally {
this.loading = false;
}
}
async #renderCurrentSource() {
const seq = ++this.#renderSeq;
this.loading = true;
this.error = '';
this.svg = '';
this.#clearLogs();
this.#syncConsolePanelFilters();
try {
const source = this.source;
if (!source) return;
if (seq !== this.#renderSeq) return;
await this.#renderMermaid(source);
} catch (e) {
this.error = e instanceof Error ? e.message : String(e);
} finally {
this.loading = false;
}
}
async #renderMermaid(text: string) {
const m = (await window.mermaidReady?.catch(() => undefined)) ?? window.mermaid;
if (!m) {
throw new Error(
'window.mermaid is not available (did /mermaid.esm.mjs load and did the bootstrap set window.mermaid?)'
);
}
const initConfig = {
startOnLoad: false,
securityLevel: 'strict',
theme: this.theme,
layout: this.layout,
logLevel: this.mermaidLogLevel,
flowchart: {
useMaxWidth: this.useMaxWidth,
},
};
// Debugging aid: log exactly what we are about to initialize/render with.
// Do it *before* initialize so detector issues can be correlated.
const previewLimit = 4000;
const preview =
text.length > previewLimit
? `${text.slice(0, previewLimit)}\n… (${text.length - previewLimit} more chars)`
: text;
console.log('[dev-explorer] mermaid.initialize config:', initConfig);
console.log('[dev-explorer] diagram source preview:\n' + preview);
// Keep it deterministic-ish between reloads.
await m.initialize(initConfig);
const id = `dev-explorer-${Date.now()}-${Math.random().toString(16).slice(2)}`;
const { svg, bindFunctions } = await m.render(id, text);
this.svg = svg;
// Allow mermaid to attach event handlers (e.g. links).
await this.updateComplete;
// If the page ever ended up scrolled down due to a previous oversized render, snap back to top.
// (We intentionally removed vertical scrollbars in the viewer.)
try {
window.scrollTo(0, 0);
} catch {
// ignore
}
const container = this.querySelector('.diagram-inner');
if (container && bindFunctions) bindFunctions(container);
}
render() {
return html`
<div class="header">
<sl-button size="small" variant="default" @click=${() => this.#back()}>
<sl-icon slot="prefix" name="arrow-left"></sl-icon>
Back
</sl-button>
<div style="min-width: 0;">
<div class="title">Diagram</div>
<div class="path">${this.filePath}</div>
</div>
<div class="spacer"></div>
<div class="viewer-controls">
<div class="control">
<span class="label">Theme</span>
<sl-select
size="small"
value=${this.theme}
@sl-change=${(e: any) => {
const v = e.target?.value;
if (isTheme(v)) {
this.theme = v;
this.#persistSettings();
}
}}
>
<sl-option value="default">default</sl-option>
<sl-option value="dark">dark</sl-option>
<sl-option value="forest">forest</sl-option>
<sl-option value="neutral">neutral</sl-option>
<sl-option value="base">base</sl-option>
</sl-select>
</div>
<div class="control">
<span class="label">Layout</span>
<sl-select
size="small"
value=${this.layout}
@sl-change=${(e: any) => {
const v = e.target?.value;
if (isLayout(v)) {
this.layout = v;
this.#persistSettings();
}
}}
>
<sl-option value="dagre">dagre</sl-option>
<sl-option value="elk">elk</sl-option>
</sl-select>
</div>
<div class="control">
<span class="label">Log</span>
<sl-select
size="small"
value=${this.mermaidLogLevel}
@sl-change=${(e: any) => {
const v = e.target?.value;
if (isMermaidLogLevel(v)) {
this.mermaidLogLevel = v;
this.#persistSettings();
this.#syncConsolePanelFilters();
}
}}
>
<sl-option value="trace">trace</sl-option>
<sl-option value="debug">debug</sl-option>
<sl-option value="info">info</sl-option>
<sl-option value="warn">warn</sl-option>
<sl-option value="error">error</sl-option>
<sl-option value="fatal">fatal</sl-option>
</sl-select>
</div>
<div class="control">
<sl-checkbox
size="small"
?checked=${this.useMaxWidth}
@sl-change=${(e: any) => {
this.useMaxWidth = Boolean(e.target?.checked);
this.#persistSettings();
}}
>useMaxWidth</sl-checkbox
>
</div>
</div>
${this.loading ? html`<div class="subtle">rendering…</div>` : nothing}
</div>
${this.error
? html`<div class="empty">Error: <span class="path">${this.error}</span></div>`
: nothing}
<div class="content">
<sl-split-panel position="75" style="height: 100%;">
<div slot="start" class="diagram">
<div class="diagram-inner" data-theme=${this.theme} .innerHTML=${this.svg}></div>
</div>
<div slot="end" style="height: 100%;">
<dev-console-panel></dev-console-panel>
</div>
</sl-split-panel>
</div>
`;
}
}
customElements.define('dev-diagram-viewer', DevDiagramViewer);

View File

@@ -1,143 +0,0 @@
import { LitElement, html, nothing } from 'lit';
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js';
import { registerIconLibrary } from '@shoelace-style/shoelace/dist/utilities/icon-library.js';
import '@shoelace-style/shoelace/dist/components/breadcrumb/breadcrumb.js';
import '@shoelace-style/shoelace/dist/components/breadcrumb-item/breadcrumb-item.js';
import '@shoelace-style/shoelace/dist/components/button/button.js';
import '@shoelace-style/shoelace/dist/components/icon/icon.js';
import '@shoelace-style/shoelace/dist/components/split-panel/split-panel.js';
import '@shoelace-style/shoelace/dist/components/badge/badge.js';
import '@shoelace-style/shoelace/dist/components/checkbox/checkbox.js';
import './file-explorer';
import './diagram-viewer';
type ViewMode = 'explorer' | 'viewer';
function getInitialStateFromUrl() {
const url = new URL(window.location.href);
const dir = url.searchParams.get('path') ?? '';
const file = url.searchParams.get('file') ?? '';
return { dir, file };
}
function setUrlState({ dir, file }: { dir: string; file: string }) {
const url = new URL(window.location.href);
url.searchParams.delete('path');
url.searchParams.delete('file');
if (dir) url.searchParams.set('path', dir);
if (file) url.searchParams.set('file', file);
history.replaceState(null, '', url);
}
export class DevExplorerApp extends LitElement {
static properties = {
mode: { state: true },
dirPath: { state: true },
lastDirPath: { state: true },
filePath: { state: true },
sseToken: { state: true },
};
declare mode: ViewMode;
declare dirPath: string;
declare lastDirPath: string;
declare filePath: string;
declare sseToken: number;
#events?: EventSource;
constructor() {
super();
const { dir, file } = getInitialStateFromUrl();
this.dirPath = dir;
this.lastDirPath = dir;
this.filePath = file;
this.mode = file ? 'viewer' : 'explorer';
this.sseToken = 0;
setBasePath('/dev/vendor/shoelace');
registerIconLibrary('default', {
resolver: (name) => `/dev/vendor/shoelace/assets/icons/${name}.svg`,
});
}
createRenderRoot() {
// Use light DOM so document-level CSS (public/styles.css => /dev/styles.css) applies to the UI.
// Without this, Lit's default shadow root will block global selectors like `.list` / `button.card`.
return this;
}
connectedCallback() {
super.connectedCallback();
this.#events = new EventSource('/events');
this.#events.onmessage = () => {
this.sseToken++;
};
window.addEventListener('popstate', this.#onPopState);
}
disconnectedCallback() {
super.disconnectedCallback();
this.#events?.close();
window.removeEventListener('popstate', this.#onPopState);
}
#onPopState = () => {
const { dir, file } = getInitialStateFromUrl();
this.dirPath = dir;
this.lastDirPath = dir;
this.filePath = file;
this.mode = file ? 'viewer' : 'explorer';
};
#goToDir = (dir: string) => {
this.dirPath = dir;
this.lastDirPath = dir;
this.mode = 'explorer';
this.filePath = '';
setUrlState({ dir, file: '' });
};
#openFile = (filePath: string) => {
this.filePath = filePath;
this.mode = 'viewer';
setUrlState({ dir: this.lastDirPath, file: filePath });
};
#backToExplorer = () => {
this.mode = 'explorer';
this.filePath = '';
setUrlState({ dir: this.lastDirPath, file: '' });
};
render() {
return html`
<div class="app">
${this.mode === 'explorer'
? html`
<dev-file-explorer
.path=${this.dirPath}
.sseToken=${this.sseToken}
@navigate=${(e: CustomEvent<{ path: string }>) => this.#goToDir(e.detail.path)}
@open-file=${(e: CustomEvent<{ path: string }>) => this.#openFile(e.detail.path)}
></dev-file-explorer>
`
: nothing}
${this.mode === 'viewer'
? html`
<dev-diagram-viewer
.filePath=${this.filePath}
.sseToken=${this.sseToken}
@back=${this.#backToExplorer}
></dev-diagram-viewer>
`
: nothing}
</div>
`;
}
}
customElements.define('dev-explorer-app', DevExplorerApp);

View File

@@ -1,182 +0,0 @@
import { LitElement, html, nothing } from 'lit';
type Entry = {
name: string;
kind: 'dir' | 'file';
path: string;
};
type FilesResponse = {
root: string;
path: string;
entries: Entry[];
};
function dirname(posixPath: string) {
const p = posixPath.replaceAll('\\', '/').replace(/\/+$/, '');
if (!p) return '';
const idx = p.lastIndexOf('/');
if (idx <= 0) return '';
return p.slice(0, idx);
}
function pathSegments(posixPath: string) {
const p = posixPath.replaceAll('\\', '/').replace(/^\/+/, '').replace(/\/+$/, '');
if (!p) return [];
return p.split('/').filter(Boolean);
}
export class DevFileExplorer extends LitElement {
static properties = {
path: { type: String },
sseToken: { type: Number },
loading: { state: true },
error: { state: true },
root: { state: true },
entries: { state: true },
};
declare path: string;
declare sseToken: number;
declare loading: boolean;
declare error: string;
declare root: string;
declare entries: Entry[];
constructor() {
super();
this.path = '';
this.sseToken = 0;
this.loading = true;
this.error = '';
this.root = '';
this.entries = [];
}
createRenderRoot() {
// Use light DOM so global CSS in public/styles.css applies.
return this;
}
updated(changed: Map<string, unknown>) {
if (changed.has('path') || changed.has('sseToken')) {
void this.#load();
}
}
async #load() {
this.loading = true;
this.error = '';
try {
const url = new URL('/dev/api/files', window.location.origin);
if (this.path) url.searchParams.set('path', this.path);
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = (await res.json()) as FilesResponse;
this.root = json.root ?? '';
this.entries = json.entries ?? [];
} catch (e) {
this.error = e instanceof Error ? e.message : String(e);
this.entries = [];
} finally {
this.loading = false;
}
}
#emitNavigate(nextPath: string) {
this.dispatchEvent(
new CustomEvent('navigate', { detail: { path: nextPath }, bubbles: true, composed: true })
);
}
#emitOpenFile(filePath: string) {
this.dispatchEvent(
new CustomEvent('open-file', { detail: { path: filePath }, bubbles: true, composed: true })
);
}
#onActivate(kind: Entry['kind'], entryPath: string) {
if (kind === 'dir') {
this.#emitNavigate(entryPath);
} else {
this.#emitOpenFile(entryPath);
}
}
render() {
const segments = pathSegments(this.path);
const itemLabel = this.entries.length === 1 ? 'item' : 'items';
return html`
<div class="header">
<div style="min-width: 0;">
<div class="title">Dev Explorer</div>
<div class="subtle">
root:
<span class="path">${this.root || 'cypress/platform/dev-diagrams'}</span>
</div>
<div style="margin-top: 6px;">
<sl-breadcrumb>
<sl-breadcrumb-item @click=${() => this.#emitNavigate('')}>root</sl-breadcrumb-item>
${segments.map((seg, idx) => {
const to = segments.slice(0, idx + 1).join('/');
return html`<sl-breadcrumb-item @click=${() => this.#emitNavigate(to)}
>${seg}</sl-breadcrumb-item
>`;
})}
</sl-breadcrumb>
</div>
</div>
<div class="spacer"></div>
<div class="subtle">
${this.loading ? 'loading…' : html`<span>${this.entries.length} ${itemLabel}</span>`}
</div>
<sl-button
size="small"
variant="default"
?disabled=${!this.path}
@click=${() => this.#emitNavigate(dirname(this.path))}
>
<sl-icon slot="prefix" name="arrow-left"></sl-icon>
Up
</sl-button>
</div>
<div class="content">
${this.error
? html`<div class="empty">Error: <span class="path">${this.error}</span></div>`
: nothing}
${!this.error && !this.loading && this.entries.length === 0
? html`<div class="empty">No folders or <span class="path">.mmd</span> files here.</div>`
: nothing}
<div class="list">
${this.entries.map((e) => {
const icon = e.kind === 'dir' ? 'folder-fill' : 'file-earmark-code';
const cardClass = e.kind === 'dir' ? 'card card-folder' : 'card card-file';
const click =
e.kind === 'dir'
? () => this.#emitNavigate(e.path)
: () => this.#emitOpenFile(e.path);
const onKeyDown = (ev: KeyboardEvent) => {
if (ev.key === 'Enter' || ev.key === ' ') {
ev.preventDefault();
this.#onActivate(e.kind, e.path);
}
};
return html`
<button class=${cardClass} type="button" @click=${click} @keydown=${onKeyDown}>
<div class="card-inner">
<sl-icon class="card-icon" name=${icon}></sl-icon>
<div class="card-title">${e.name}</div>
</div>
</button>
`;
})}
</div>
</div>
`;
}
}
customElements.define('dev-file-explorer', DevFileExplorer);

View File

@@ -1,39 +0,0 @@
<!doctype html>
<html lang="en" class="sl-theme-dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Mermaid Dev Explorer</title>
<link rel="stylesheet" href="/dev/vendor/shoelace/themes/dark.css" />
<link rel="stylesheet" href="/dev/styles.css?v=6" />
</head>
<body>
<!-- Mermaid ESM + ELK layout loaders (required for `layout: 'elk'`) -->
<script type="module">
// Expose a single async hook so the app can reliably await Mermaid "activation".
// This avoids races where the UI calls initialize/render before layouts/diagrams are ready.
window.mermaidReady = (async () => {
try {
const [{ default: mermaid }, { default: layouts }] = await Promise.all([
import('/mermaid.esm.mjs'),
import('/mermaid-layout-elk.esm.mjs'),
]);
mermaid.registerLayoutLoaders(layouts);
// Keep the rest of the dev explorer simple: expose mermaid on window for the Lit components.
window.mermaid = mermaid;
return mermaid;
} catch (err) {
console.error('[dev-explorer] Failed to initialize mermaid (ESM + elk loaders).', err);
throw err;
}
})();
</script>
<dev-explorer-app></dev-explorer-app>
<script type="module" src="/dev/assets/explorer-app.js?v=6"></script>
</body>
</html>

View File

@@ -1,359 +0,0 @@
/* Dev Explorer tokens. Keep on :root so the page background picks them up too. */
:root {
--app-bg: #0b1020;
--app-fg: #e8eefc;
--panel-bg: #0f1733;
--muted: rgba(232, 238, 252, 0.75);
--border: rgba(232, 238, 252, 0.12);
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
monospace;
}
html,
body {
height: 100%;
}
body {
margin: 0;
background: var(--app-bg);
color: var(--app-fg);
font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
/* Keep the page from becoming scrollable when components accidentally overflow. */
overflow: hidden;
}
a {
color: inherit;
}
/* Scope app-level layout rules to avoid generic class collisions. */
dev-explorer-app {
display: block;
height: 100%;
}
dev-explorer-app .app {
height: 100vh;
display: flex;
flex-direction: column;
}
/* Let the top-level view components actually fill the available vertical space. */
dev-explorer-app dev-file-explorer,
dev-explorer-app dev-diagram-viewer {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
}
dev-explorer-app .header {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
border-bottom: 1px solid var(--border);
background: var(--panel-bg);
}
dev-explorer-app .viewer-controls {
display: inline-flex;
align-items: center;
gap: 10px;
margin-right: 8px;
}
dev-explorer-app .viewer-controls .control {
display: inline-flex;
align-items: center;
gap: 6px;
}
dev-explorer-app .viewer-controls .label {
font-size: 12px;
color: var(--muted);
user-select: none;
}
dev-explorer-app .viewer-controls sl-select {
width: 140px;
}
dev-explorer-app .header .spacer {
flex: 1;
}
dev-explorer-app .title {
font-weight: 600;
letter-spacing: 0.2px;
}
dev-explorer-app .subtle {
color: var(--muted);
font-size: 12px;
}
dev-explorer-app .content {
flex: 1;
min-height: 0;
overflow: auto;
}
/* Diagram view should behave like a full-height canvas; avoid nested scrollbars. */
dev-explorer-app dev-diagram-viewer .content {
overflow: hidden;
}
/* Shoelace split panel: ensure slot content can't overflow and push itself off-screen. */
dev-explorer-app sl-split-panel {
height: 100%;
overflow: hidden;
}
dev-explorer-app sl-split-panel::part(base) {
height: 100%;
overflow: hidden;
}
dev-explorer-app sl-split-panel::part(start),
dev-explorer-app sl-split-panel::part(end) {
overflow: hidden;
min-height: 0;
}
dev-explorer-app .list {
padding: 24px;
display: grid;
gap: 20px;
grid-template-columns: repeat(auto-fill, 260px);
}
/* Card base - use button element */
dev-explorer-app button.card {
all: unset;
box-sizing: border-box;
width: 260px;
height: 200px;
border-radius: 20px;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
/* Folder card style */
dev-explorer-app button.card.card-folder {
background: linear-gradient(160deg, #1a3052 0%, #0d1a2d 100%);
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.4),
0 2px 8px rgba(0, 0, 0, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
dev-explorer-app button.card.card-folder:hover {
transform: translateY(-6px) scale(1.02);
box-shadow:
0 16px 48px rgba(0, 0, 0, 0.5),
0 4px 12px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.12);
}
/* File card style */
dev-explorer-app button.card.card-file {
background: linear-gradient(160deg, #1e2d4a 0%, #111827 100%);
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.4),
0 2px 8px rgba(0, 0, 0, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
dev-explorer-app button.card.card-file:hover {
transform: translateY(-6px) scale(1.02);
box-shadow:
0 16px 48px rgba(0, 0, 0, 0.5),
0 4px 12px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
dev-explorer-app button.card:focus-visible {
outline: 3px solid rgba(96, 165, 250, 0.7);
outline-offset: 4px;
}
/* Subtle border overlay */
dev-explorer-app button.card::before {
content: '';
position: absolute;
inset: 0;
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.08);
pointer-events: none;
}
dev-explorer-app .card-inner {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20px;
padding: 24px;
width: 100%;
height: 100%;
box-sizing: border-box;
}
/* Icon styling */
dev-explorer-app .card-icon {
font-size: 56px !important;
width: 56px;
height: 56px;
}
dev-explorer-app button.card.card-folder .card-icon {
color: #f59e0b;
filter: drop-shadow(0 4px 12px rgba(245, 158, 11, 0.4));
}
dev-explorer-app button.card.card-file .card-icon {
color: #3b82f6;
filter: drop-shadow(0 4px 12px rgba(59, 130, 246, 0.35));
}
/* Title styling */
dev-explorer-app .card-title {
font-size: 12px;
font-family: var(--mono);
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 230px;
color: rgba(255, 255, 255, 0.85);
line-height: 1.4;
text-align: center;
}
dev-explorer-app .row {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 8px;
border: 1px solid var(--border);
border-radius: 8px;
background: rgba(255, 255, 255, 0.02);
}
dev-explorer-app .row:hover {
background: rgba(255, 255, 255, 0.04);
}
dev-explorer-app .row sl-icon {
font-size: 16px;
}
dev-explorer-app .path {
font-family: var(--mono);
font-size: 12px;
color: var(--muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
dev-explorer-app .diagram {
height: 100%;
overflow: hidden;
padding: 12px;
box-sizing: border-box;
}
dev-explorer-app .diagram-inner {
/* Canvas background behind the rendered SVG (theme-dependent). */
background: #fff;
color: #111;
border-radius: 10px;
padding: 12px;
display: flex;
align-items: flex-start;
justify-content: flex-start;
width: 100%;
height: 100%;
box-sizing: border-box;
}
/* Fit the rendered SVG within the available pane (no scrollbars). */
dev-explorer-app .diagram-inner > svg {
width: auto;
height: auto;
max-width: 100% !important;
max-height: 100% !important;
}
dev-explorer-app .diagram-inner[data-theme='dark'] {
background: #0b1020;
color: #e8eefc;
}
dev-explorer-app .console {
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
border-left: 1px solid var(--border);
background: var(--panel-bg);
}
dev-explorer-app .spacer {
flex: 1;
}
dev-explorer-app .console-toolbar {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
border-bottom: 1px solid var(--border);
}
dev-explorer-app .console-toolbar sl-input {
width: 260px;
}
dev-explorer-app .console-body {
flex: 1;
min-height: 0;
overflow: auto;
padding: 10px 12px;
}
dev-explorer-app .logline {
font-family: var(--mono);
font-size: 12px;
white-space: pre-wrap;
word-break: break-word;
margin: 0 0 8px 0;
line-height: 1.35;
}
dev-explorer-app .logmeta {
display: inline-flex;
gap: 8px;
align-items: center;
margin-bottom: 4px;
}
dev-explorer-app .empty {
padding: 18px 12px;
color: var(--muted);
font-size: 13px;
}
dev-explorer-app sl-breadcrumb::part(base) {
font-size: 12px;
}

View File

@@ -2,17 +2,12 @@
import chokidar from 'chokidar';
import cors from 'cors';
import { context } from 'esbuild';
import { promises as fs } from 'fs';
import type { Request, Response } from 'express';
import express from 'express';
import path, { resolve } from 'path';
import { fileURLToPath } from 'url';
import { packageOptions } from '../.build/common.js';
import { generateLangium } from '../.build/generateLangium.js';
import { defaultOptions, getBuildConfig } from './util.js';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const configs = Object.values(packageOptions).map(({ packageName }) =>
getBuildConfig({
...defaultOptions,
@@ -23,7 +18,7 @@ const configs = Object.values(packageOptions).map(({ packageName }) =>
);
const mermaidIIFEConfig = getBuildConfig({
...defaultOptions,
minify: true,
minify: false,
core: false,
options: packageOptions.mermaid,
format: 'iife',
@@ -89,81 +84,6 @@ function sendEventsToAll() {
clients.forEach(({ response }) => response.write(`data: ${Date.now()}\n\n`));
}
interface DevExplorerEntry {
name: string;
kind: 'dir' | 'file';
path: string; // posix-style, relative to root
}
const devExplorerRootAbs = resolve(
process.cwd(),
process.env.MERMAID_DEV_EXPLORER_ROOT ?? 'cypress/platform/dev-diagrams'
);
function toPosixPath(p: string) {
return p.split(path.sep).join('/');
}
function resolveWithinDevExplorerRoot(requestedPath: unknown) {
const requested = typeof requestedPath === 'string' ? requestedPath : '';
if (requested.includes('\0')) {
throw new Error('Invalid path');
}
// Normalize slashes and avoid weird absolute-path cases.
const cleaned = requested.replaceAll('\\', '/').replace(/^\/+/, '');
const absPath = resolve(devExplorerRootAbs, cleaned);
const rel = path.relative(devExplorerRootAbs, absPath);
// Prevent traversal above root.
if (rel.startsWith('..') || path.isAbsolute(rel)) {
throw new Error('Path escapes configured root');
}
return { absPath, relPath: toPosixPath(rel) };
}
async function createDevExplorerBundle() {
const devExplorerDir = resolve(__dirname, 'dev-explorer');
const entryPoint = resolve(devExplorerDir, 'explorer-app.ts');
const outDir = resolve(devExplorerDir, 'dist');
try {
const devExplorerCtx = await context({
absWorkingDir: process.cwd(),
entryPoints: [entryPoint],
bundle: true,
format: 'esm',
target: 'es2020',
sourcemap: true,
outdir: outDir,
logLevel: 'info',
plugins: [
{
name: 'dev-explorer-reload',
setup(build) {
build.onEnd(() => {
sendEventsToAll();
});
},
},
],
});
await devExplorerCtx.watch();
await devExplorerCtx.rebuild();
} catch (err) {
console.error(
[
'Dev Explorer bundle build failed.',
'Install dependencies: pnpm add -D lit @shoelace-style/shoelace',
'Then restart the dev server.',
].join('\n')
);
console.error(err);
}
}
async function createServer() {
await generateLangium();
handleFileChange();
@@ -186,117 +106,8 @@ async function createServer() {
handleFileChange();
});
// Rebuild the dev-explorer client bundle on changes (and emit SSE so the browser reloads).
await createDevExplorerBundle();
// Emit SSE when Dev Explorer static assets change (e.g. public/styles.css),
// otherwise CSS-only changes can look "ignored" unless the user manually refreshes.
chokidar
.watch(['.esbuild/dev-explorer/public/**/*'], {
ignoreInitial: true,
ignored: [/node_modules/, /dist/, /docs/, /coverage/],
})
.on('all', (event, changedPath) => {
if (!['add', 'change', 'unlink'].includes(event)) {
return;
}
console.log(`[dev-explorer] ${event}: ${changedPath}`);
sendEventsToAll();
});
// Emit SSE when .mmd files inside the configured explorer root change.
chokidar
.watch('**/*.mmd', {
cwd: devExplorerRootAbs,
ignoreInitial: true,
})
.on('all', (event, changedPath) => {
if (!['add', 'change', 'unlink'].includes(event)) {
return;
}
console.log(`[dev-explorer] ${event}: ${changedPath}`);
sendEventsToAll();
});
app.use(cors());
app.get('/events', eventsHandler);
// --- Dev Explorer API + UI -------------------------------------------------
const devExplorerDir = resolve(__dirname, 'dev-explorer');
const devExplorerPublicDir = resolve(devExplorerDir, 'public');
const devExplorerDistDir = resolve(devExplorerDir, 'dist');
// Shoelace assets (theme css + icons). Safe: only mounted in dev server.
app.use(
'/dev/vendor/shoelace',
express.static(resolve(process.cwd(), 'node_modules/@shoelace-style/shoelace/dist'))
);
app.get('/dev/api/files', async (req, res) => {
try {
const { absPath, relPath } = resolveWithinDevExplorerRoot(req.query.path);
const stats = await fs.stat(absPath);
if (!stats.isDirectory()) {
res.status(400).json({ error: 'Not a directory' });
return;
}
const dirEntries = await fs.readdir(absPath, { withFileTypes: true });
const entries = dirEntries
.filter((d) => d.isDirectory() || (d.isFile() && d.name.endsWith('.mmd')))
.map<DevExplorerEntry>((d) => ({
name: d.name,
kind: d.isDirectory() ? 'dir' : 'file',
path: toPosixPath(path.join(relPath, d.name)),
}))
.sort((a, b) => {
if (a.kind !== b.kind) {
return a.kind === 'dir' ? -1 : 1;
}
return a.name.localeCompare(b.name, undefined, { sensitivity: 'base' });
});
res.json({
root: toPosixPath(path.relative(process.cwd(), devExplorerRootAbs)),
path: relPath === '' ? '' : relPath,
entries,
});
} catch (_e) {
res.status(400).json({ error: 'Invalid path' });
}
});
app.get('/dev/api/file', async (req, res) => {
try {
const { absPath, relPath } = resolveWithinDevExplorerRoot(req.query.path);
if (!absPath.endsWith('.mmd')) {
res.status(400).send('Only .mmd files are allowed');
return;
}
const stats = await fs.stat(absPath);
if (!stats.isFile()) {
res.status(400).send('Not a file');
return;
}
const content = await fs.readFile(absPath, 'utf-8');
res.type('text/plain').send(content);
// Optional: include relPath for debugging.
void relPath;
} catch (_e) {
res.status(400).send('Invalid path');
}
});
// Static assets for the dev-explorer UI.
app.use('/dev/assets', express.static(devExplorerDistDir));
// Serve `/dev/` (and `/dev`) from public/, including index.html.
app.use(
'/dev',
express.static(devExplorerPublicDir, {
index: ['index.html'],
})
);
for (const { packageName } of Object.values(packageOptions)) {
app.use(express.static(`./packages/${packageName}/dist`));
}

View File

@@ -58,7 +58,7 @@ jobs:
echo "EOF" >> $GITHUB_OUTPUT
- name: Commit and create pull request
uses: peter-evans/create-pull-request@0979079bc20c05bbbb590a56c21c4e2b1d1f1bbe
uses: peter-evans/create-pull-request@0edc001d28a2959cd7a6b505629f1d82f0a6e67d
with:
add-paths: |
cypress/timings.json

View File

@@ -19,7 +19,7 @@ jobs:
message: 'chore: update browsers list'
push: false
- name: Create Pull Request
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with:
branch: update-browserslist
title: Update Browserslist

View File

@@ -98,21 +98,12 @@ export const openURLAndVerifyRendering = (
cy.visit(url);
cy.window().should('have.property', 'rendered', true);
cy.get('svg').should('be.visible');
// cspell:ignore viewbox
cy.get('svg').should('not.have.attr', 'viewbox');
// Handle sandbox mode where SVG is inside an iframe
if (options.securityLevel === 'sandbox') {
cy.get('iframe').should('be.visible');
if (validation) {
cy.get('iframe').should(validation);
}
} else {
cy.get('svg').should('be.visible');
// cspell:ignore viewbox
cy.get('svg').should('not.have.attr', 'viewbox');
if (validation) {
cy.get('svg').should(validation);
}
if (validation) {
cy.get('svg').should(validation);
}
if (screenshot) {

View File

@@ -114,28 +114,4 @@ describe('C4 diagram', () => {
{}
);
});
it('C4.6 should render C4Context diagram with ComponentQueue_Ext', () => {
imgSnapshotTest(
`
C4Context
title System Context diagram with ComponentQueue_Ext
Enterprise_Boundary(b0, "BankBoundary0") {
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")
System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")
Enterprise_Boundary(b1, "BankBoundary") {
ComponentQueue_Ext(msgQueue, "Message Queue", "RabbitMQ", "External message queue system for processing banking transactions")
System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.")
}
}
BiRel(customerA, SystemAA, "Uses")
Rel(SystemAA, msgQueue, "Sends messages to")
Rel(SystemAA, SystemC, "Sends e-mails", "SMTP")
`,
{}
);
});
});

View File

@@ -79,18 +79,6 @@ describe('Flowchart v2', () => {
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('6a: should render complex HTML in labels with sandbox security', () => {
imgSnapshotTest(
`flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{ securityLevel: 'sandbox', flowchart: { htmlLabels: true } }
);
});
it('7: should render a flowchart when useMaxWidth is true (default)', () => {
renderGraph(
`flowchart TD

View File

@@ -833,34 +833,4 @@ describe('Gantt diagram', () => {
{}
);
});
it('should handle seconds-only format with tickInterval (issue #5496)', () => {
imgSnapshotTest(
`
gantt
tickInterval 1second
dateFormat ss
axisFormat %s
section Network Request
RTT : rtt, 0, 20
`,
{}
);
});
it('should handle dates with year typo like 202 instead of 2024 (issue #5496)', () => {
imgSnapshotTest(
`
gantt
title Schedule
dateFormat YYYY-MM-DD
tickInterval 1week
axisFormat %m-%d
section Vacation
London : 2024-12-01, 7d
London : 202-12-01, 7d
`,
{}
);
});
});

View File

@@ -247,31 +247,5 @@ root
);
});
});
describe('Level 2 nodes exceeding 11', () => {
it('should render all Level 2 nodes correctly when there are more than 11', () => {
imgSnapshotTest(
`mindmap
root
Node1
Node2
Node3
Node4
Node5
Node6
Node7
Node8
Node9
Node10
Node11
Node12
Node13
Node14
Node15`,
{},
undefined,
shouldHaveRoot
);
});
});
/* The end */
});

View File

@@ -776,194 +776,5 @@ describe('Sequence Diagram Special Cases', () => {
);
});
});
describe('Participant Stereotypes with Aliases', () => {
it('should render participants with stereotypes and aliases', () => {
imgSnapshotTest(
`sequenceDiagram
participant API@{ "type" : "boundary" } as Public API
participant Auth@{ "type" : "control" } as Auth Controller
participant DB@{ "type" : "database" } as User Database
participant Cache@{ "type" : "entity" } as Cache Layer
API ->> Auth: Authenticate request
Auth ->> DB: Query user
DB -->> Auth: User data
Auth ->> Cache: Store session
Cache -->> Auth: Confirmed
Auth -->> API: Token`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
it('should render actors with stereotypes and aliases', () => {
imgSnapshotTest(
`sequenceDiagram
actor U@{ "type" : "actor" } as End User
actor A@{ "type" : "boundary" } as API Gateway
actor S@{ "type" : "control" } as Service Layer
actor D@{ "type" : "database" } as Data Store
U ->> A: Send request
A ->> S: Process
S ->> D: Persist
D -->> S: Success
S -->> A: Response
A -->> U: Result`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
it('should render mixed participants and actors with stereotypes and aliases', () => {
imgSnapshotTest(
`sequenceDiagram
actor Client@{ "type" : "actor" } AS Mobile Client
participant Gateway@{ "type" : "boundary" } as API Gateway
participant OrderSvc@{ "type" : "control" } as Order Service
participant Queue@{ "type" : "queue" } as Message Queue
participant DB@{ "type" : "database" } as Order Database
participant Logs@{ "type" : "collections" } as Audit Logs
Client ->> Gateway: Place order
Gateway ->> OrderSvc: Validate order
OrderSvc ->> Queue: Queue for processing as well
OrderSvc ->> DB: Save order
OrderSvc ->> Logs: Log transaction
Queue -->> OrderSvc: Processing started AS Well
DB -->> OrderSvc: Order saved
Logs -->> OrderSvc: Logged
OrderSvc -->> Gateway: Order confirmed
Gateway -->> Client: Confirmation`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
it('should render stereotypes with aliases in boxes', () => {
imgSnapshotTest(
`sequenceDiagram
box rgb(200,220,255) Frontend Layer
actor User@{ "type" : "actor" } as End User
participant UI@{ "type" : "boundary" } as User Interface
end
box rgb(255,220,200) Backend Layer
participant API@{ "type" : "boundary" } as REST API
participant Svc@{ "type" : "control" } as Business Logic
end
box rgb(220,255,200) Data Layer
participant DB@{ "type" : "database" } as Primary DB
participant Cache@{ "type" : "entity" } as Cache Store
end
User ->> UI: Click button
UI ->> API: HTTP request
API ->> Svc: Process
Svc ->> Cache: Check cache
Cache -->> Svc: Cache miss
Svc ->> DB: Query data
DB -->> Svc: Data
Svc ->> Cache: Update cache
Svc -->> API: Response
API -->> UI: Data
UI -->> User: Display`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
it('should render stereotypes with aliases and complex interactions', () => {
imgSnapshotTest(
`sequenceDiagram
participant Web@{ "type" : "boundary" } as Web Portal
participant Auth@{ "type" : "control" } as Auth Service
participant UserDB@{ "type" : "database" } as User DB
participant Queue@{ "type" : "queue" } as Event Queue
participant Audit@{ "type" : "collections" } as Audit Trail
Web ->> Auth: Login request
activate Auth
Auth ->> UserDB: Verify credentials
activate UserDB
UserDB -->> Auth: User found
deactivate UserDB
alt Valid credentials
Auth ->> Queue: Publish login event
Auth ->> Audit: Log success
par Parallel processing
Queue -->> Auth: Event queued
and
Audit -->> Auth: Logged
end
Auth -->> Web: Success token
else Invalid credentials
Auth ->> Audit: Log failure
Audit -->> Auth: Logged
Auth --x Web: Access denied
end
deactivate Auth
Note over Web,Audit: All interactions logged`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
});
describe('Participant Inline Alias in Config', () => {
it('should render participants with inline alias in config object', () => {
imgSnapshotTest(
`sequenceDiagram
participant API@{ "type" : "boundary", "alias": "Public API" }
participant Auth@{ "type" : "control", "alias": "Auth Service" }
participant DB@{ "type" : "database", "alias": "User DB" }
API ->> Auth: Login request
Auth ->> DB: Query user
DB -->> Auth: User data
Auth -->> API: Token`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
it('should render actors with inline alias in config object', () => {
imgSnapshotTest(
`sequenceDiagram
actor U@{ "type" : "actor", "alias": "End User" }
actor G@{ "type" : "boundary", "alias": "Gateway" }
actor S@{ "type" : "control", "alias": "Service" }
U ->> G: Request
G ->> S: Process
S -->> G: Response
G -->> U: Result`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
it('should handle mixed inline and external alias syntax', () => {
imgSnapshotTest(
`sequenceDiagram
participant A@{ "type" : "boundary", "alias": "Service A" }
participant B@{ "type" : "control" } as Service B
participant C@{ "type" : "database" }
A ->> B: Request
B ->> C: Query
C -->> B: Data
B -->> A: Response`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
it('should prioritize external alias over inline alias', () => {
imgSnapshotTest(
`sequenceDiagram
participant API@{ "type" : "boundary", "alias": "Internal Name" } as External Name
participant DB@{ "type" : "database", "alias": "Internal DB" } AS External DB
API ->> DB: Query
DB -->> API: Result`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
it('should render inline alias with only alias field (no type)', () => {
imgSnapshotTest(
`sequenceDiagram
participant API@{ "alias": "Public API" }
participant Auth@{ "alias": "Auth Service" }
API ->> Auth: Request
Auth -->> API: Response`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
});
});
});

View File

@@ -327,97 +327,8 @@ classDef sales fill:#c3a66b,stroke:#333;
{}
);
});
it('12: should apply classDef fill color to leaf nodes', () => {
imgSnapshotTest(
`treemap-beta
"Root"
"Item A": 30:::redClass
"Item B": 20
"Item C": 25:::blueClass
classDef redClass fill:#ff0000;
classDef blueClass fill:#0000ff;
`,
{}
);
});
it('13: should apply classDef stroke styles to sections', () => {
imgSnapshotTest(
`treemap-beta
%% This is a comment
"Category A":::thickBorder
"Item A1": 10
"Item A2": 20
%% Another comment
"Category B":::dashedBorder
"Item B1": 15
"Item B2": 25
classDef thickBorder stroke:red,stroke-width:8px;
classDef dashedBorder stroke:black,stroke-dasharray:5,stroke-width:8px;
`,
{}
);
});
it('14: should apply classDef color to text labels', () => {
imgSnapshotTest(
`treemap-beta
"Products"
"Electronics":::whiteText
"Phones": 40
"Laptops": 30
"Furniture":::darkText
"Chairs": 25
"Tables": 20
classDef whiteText fill:#2c3e50,color:#ffffff;
classDef darkText fill:#ecf0f1,color:#000000;
`,
{}
);
});
it('15: should apply multiple classDef properties simultaneously', () => {
imgSnapshotTest(
`treemap-beta
"Budget"
"Critical":::critical
"Server Costs": 50000
"Salaries": 80000
"Normal":::normal
"Office Supplies": 5000
"Marketing": 15000
classDef critical fill:#e74c3c,color:#fff,stroke:#c0392b,stroke-width:3px;
classDef normal fill:#3498db,color:#fff,stroke:#2980b9,stroke-width:1px;
`,
{}
);
});
it('16: should handle classDef on nested sections and leaves', () => {
imgSnapshotTest(
`treemap-beta
"Company"
"Engineering":::engSection
"Frontend": 30:::highlight
"Backend": 40
"DevOps": 20:::highlight
"Sales"
"Direct": 35
"Channel": 25:::highlight
classDef engSection fill:#9b59b6,stroke:#8e44ad,stroke-width:2px;
classDef highlight fill:#f39c12,color:#000,stroke:#e67e22,stroke-width:2px;
`,
{}
);
});
/*
it.skip('17: should render a treemap with title', () => {
it.skip('12: should render a treemap with title', () => {
imgSnapshotTest(
`
treemap-beta

View File

@@ -1,16 +0,0 @@
sequenceDiagram
actor Alice
participant John@{ "type": "boundary" }
participant P1@{ "type": "control" } as New Control
participant P2@{ "type": "entity" } as New Entity
participant P3@{ "type": "database" } as New Database
Alice->>+John: Hello John, how are you?
John->>P1: new msg
P1->>P2: new msg
P2->>P3: new msg
Alice->>+John: John, can you hear me?
P3->>P2: new msg
P2->>P1: new msg
John-->>-Alice: Hi Alice, I can hear you!
P1->>John: new msg
John-->>-Alice: I feel great!

View File

@@ -1,9 +0,0 @@
treemap
"Section 1"
"Leaf 1.1": 12
"Section 1.2":::class1
"Leaf 1.2.1": 12
"Section 2"
"Leaf 2.1": 20:::class1
"Leaf 2.2": 25
"Leaf 2.3": 12

View File

@@ -1,11 +0,0 @@
---
title: "Grades"
---
radar-beta
axis m["Math"], s["Science"], e["English"]
axis h["History"], g["Geography"], a["Art"]
curve a["Alice"]{85, 90, 80, 70, 75, 90}
curve b["Bob"]{70, 75, 85, 80, 90, 85}
max 100
min 0

View File

@@ -1,11 +0,0 @@
flowchart LR
A["A"] e1@==> B["B"]
A L_A_n1_0@--> n1["C"]
B L_B_n2_0@==> n2["D"]
n1 L_n1_n2_0@--> n2
e1@{ animate: true }
L_A_n1_0@{ animation: slow }
L_B_n2_0@{ animation: slow }
L_n1_n2_0@{ animation: fast }

View File

@@ -1,3 +0,0 @@
flowchart LR
A e1@==> B
e1@{ animate: true }

View File

@@ -1,18 +0,0 @@
---
config:
layout: ogdc
---
flowchart-elk TB
c1-->a2
subgraph one
a1-->a2
end
subgraph two
b1-->b2
end
subgraph three
c1-->c2
end
one --> two
three --> two
two --> c2

View File

@@ -1,17 +0,0 @@
---
config:
layout: elk
---
flowchart TB
process_C
subgraph container_Alpha
subgraph process_B
pppB
end
subgraph process_A
pppA
end
process_B-->|via_AWSBatch|container_Beta
process_A-->|messages|container_Beta
end

View File

@@ -1,18 +0,0 @@
---
config:
layout: elk
---
flowchart TB
subgraph container_Beta
process_C
end
subgraph container_Alpha
subgraph process_B
pppB
end
subgraph process_A
pppA
end
process_B-->|via_AWSBatch|container_Beta
process_A-->|messages|container_Beta
end

View File

@@ -1,10 +0,0 @@
---
config:
layout: elk
---
flowchart TB
subgraph container_Beta
process_C
end
process_B-->|via_AWSBatch|container_Beta

View File

@@ -1,31 +0,0 @@
---
config:
layout: elk
---
classDiagram
note "I love this diagram!\nDo you love it?"
Class01 <|-- AveryLongClass : Cool
<<interface>> Class01
Class03 "1" *-- "*" Class04
Class05 "1" o-- "many" Class06
Class07 "1" .. "*" Class08
Class09 "1" --> "*" C2 : Where am i?
Class09 "*" --* "*" C3
Class09 "1" --|> "1" Class07
Class12 <|.. Class08
Class11 ..>Class12
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
Class01 : int chimp
Class01 : int gorilla
Class01 : -int privateChimp
Class01 : +int publicGorilla
Class01 : #int protectedMarmoset
Class08 <--> C2: Cool label
class Class10 {
<<service>>
int id
test()
}
note for Class10 "Cool class\nI said it's very cool class!"

View File

@@ -1,17 +0,0 @@
---
config:
layout: elk
---
requirementDiagram
requirement test_req {
id: 1
text: the test text.
risk: high
verifymethod: test
}
element test_entity {
type: simulation
}
test_entity - satisfies -> test_req

View File

@@ -1,21 +0,0 @@
---
config:
layout: elk
---
flowchart-elk TB
internet
nat
router
compute1
subgraph project
router
nat
subgraph subnet1
compute1
end
end
%% router --> subnet1
subnet1 --> nat
%% nat --> internet

View File

@@ -1,27 +0,0 @@
---
config:
layout: elk
---
flowchart-elk TB
internet
nat
router
lb1
lb2
compute1
compute2
subgraph project
router
nat
subgraph subnet1
compute1
lb1
end
subgraph subnet2
compute2
lb2
end
end
internet --> router
router --> subnet1 & subnet2
subnet1 & subnet2 --> nat --> internet

View File

@@ -1,14 +0,0 @@
---
config:
layout: elk
elk:
mergeEdges: false
forceNodeModelOrder: false
considerModelOrder: NONE
---
flowchart TB
a --> a1 & a2 & a3 & a4
b --> b1 & b2
b2 --> b3
b1 --> b4

View File

@@ -1,11 +0,0 @@
---
config:
layout: elk
flowchart:
curve: rounded
---
flowchart LR
I["fa:fa-code Text"] -- Mermaid js --> D["Use<br/>the<br/>editor!"]
I --> D & D
D@{ shape: question}
I@{ shape: question}

View File

@@ -1,21 +0,0 @@
---
config:
layout: tidy-tree
---
mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectiveness<br/>and features
On Automatic creation
Uses
Creative techniques
Strategic planning
Argument mapping
Tools
Pen and paper
Mermaid

View File

@@ -1,11 +0,0 @@
---
config:
layout: elk
flowchart:
curve: linear
---
flowchart LR
A[A] --> B[B]
A[A] --- B([C])
A@{ shape: diamond}
%%B@{ shape: diamond}

View File

@@ -1,11 +0,0 @@
---
config:
layout: elk
flowchart:
curve: linear
---
flowchart LR
A[A] -- Mermaid js --> B[B]
A[A] -- Mermaid js --- B[B]
A@{ shape: diamond}
B@{ shape: diamond}

View File

@@ -1,11 +0,0 @@
---
config:
layout: elk
flowchart:
curve: rounded
---
flowchart LR
D["Use the editor"] -- Mermaid js --> I["fa:fa-code Text"]
I --> D & D
D@{ shape: question}
I@{ shape: question}

View File

@@ -1,14 +0,0 @@
---
config:
layout: elk
flowchart:
curve: rounded
elk:
nodePlacementStrategy: NETWORK_SIMPLEX
---
flowchart LR
D["Use the editor"] -- Mermaid js --> I["fa:fa-code Text"]
D --> I & I
a["a"]
D@{ shape: trap-b}
I@{ shape: lean-l}

View File

@@ -1,11 +0,0 @@
---
config:
layout: elk
---
flowchart LR
%% subgraph s1["Untitled subgraph"]
C["Evaluate"]
%% end
B --> C

View File

@@ -1,10 +0,0 @@
---
config:
layout: elk
flowchart:
//curve: linear
---
flowchart LR
%% A ==> B
%% A2 --> B2
A{A} --> B((Bo boo)) & B & B & B

View File

@@ -1,18 +0,0 @@
---
config:
layout: elk
theme: default
look: classic
---
flowchart LR
subgraph s1["APA"]
D{"Use the editor"}
end
subgraph S2["S2"]
s1
I>"fa:fa-code Text"]
E["E"]
end
D -- Mermaid js --> I
D --> I & E
E --> I

View File

@@ -1,23 +0,0 @@
---
config:
layout: elk
---
flowchart LR
a
subgraph s0["APA"]
subgraph s8["APA"]
subgraph s1["APA"]
D{"X"}
E[Q]
end
subgraph s3["BAPA"]
F[Q]
I
end
D --> I
D --> I
D --> I
I{"X"}
end
end

View File

@@ -1,11 +0,0 @@
---
config:
layout: elk
---
flowchart LR
a
D{"Use the editor"}
D -- Mermaid js --> I{"fa:fa-code Text"}
D-->I
D-->I

View File

@@ -1,36 +0,0 @@
---
config:
layout: elk
---
flowchart LR
subgraph s1["Untitled subgraph"]
n1["Evaluate"]
n2["Option 1"]
n3["Option 2"]
n4["fa:fa-car Option 3"]
end
subgraph s2["Untitled subgraph"]
n5["Evaluate"]
n6["Option 1"]
n7["Option 2"]
n8["fa:fa-car Option 3"]
end
A["Start"] -- Some text --> B("Continue")
B --> C{"Evaluate"}
C -- One --> D["Option 1"]
C -- Two --> E["Option 2"]
C -- Three --> F["fa:fa-car Option 3"]
n1 -- One --> n2
n1 -- Two --> n3
n1 -- Three --> n4
n5 -- One --> n6
n5 -- Two --> n7
n5 -- Three --> n8
n1@{ shape: diam}
n2@{ shape: rect}
n3@{ shape: rect}
n4@{ shape: rect}
n5@{ shape: diam}
n6@{ shape: rect}
n7@{ shape: rect}
n8@{ shape: rect}

View File

@@ -1,10 +0,0 @@
---
config:
layout: elk
---
flowchart LR
subgraph s1["Untitled subgraph"]
n1["Evaluate"]
n2["Option 1"]
end
n1 -- One --> n2

View File

@@ -1,6 +0,0 @@
---
config:
layout: elk
---
flowchart LR
A{A} --> B & C

View File

@@ -1,9 +0,0 @@
---
config:
layout: elk
---
flowchart LR
A{A} --> B & C
subgraph "subbe"
A
end

View File

@@ -1,14 +0,0 @@
---
config:
layout: elk
---
flowchart LR
n2@{ shape: rect}
n3@{ shape: rect}
n4@{ shape: rect}
A["Start"] -- Some text --> B("Continue")
B --> C{"Evaluate"}
C -- One --> D["Option 1"]
C -- Two --> E["Option 2"]
C -- Three --> F["fa:fa-car Option 3"]
%% C@{ shape: hexagon}

View File

@@ -1,16 +0,0 @@
---
config:
kanban:
ticketBaseUrl: 'https://github.com/your-repo/issues/#TICKET#'
---
kanban
Backlog
task1[📝 Define project requirements]@{ ticket: a101 }
To Do
task2[🔍 Research technologies]@{ ticket: a102 }
Review
task4[🔍 Code review for login feature]@{ ticket: a104 }
Done
task5[✅ Deploy initial version]@{ ticket: a105 }
In Progress
task3[💻 Develop login feature]@{ ticket: 103 }

View File

@@ -1,2 +0,0 @@
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'rounded' }

View File

@@ -1,3 +0,0 @@
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'rounded' }
style A fill:#f9f,stroke:#333,stroke-width:4px

View File

@@ -1,4 +0,0 @@
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'rounded' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px

View File

@@ -1,2 +0,0 @@
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'rounded' }

View File

@@ -1,2 +0,0 @@
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'square' }

View File

@@ -1,3 +0,0 @@
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'square' }
style A fill:#f9f,stroke:#333,stroke-width:4px

View File

@@ -1,4 +0,0 @@
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'square' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px

View File

@@ -1,2 +0,0 @@
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'square' }

View File

@@ -1,2 +0,0 @@
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'circle' }

View File

@@ -1,3 +0,0 @@
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'circle' }
style A fill:#f9f,stroke:#333,stroke-width:4px

View File

@@ -1,4 +0,0 @@
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'circle' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px

View File

@@ -1,4 +0,0 @@
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'circle' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px

View File

@@ -1,3 +0,0 @@
flowchart LR
nA[Style] --> A@{ icon: 'logos:aws', form: 'circle' }
style A fill:#f9f,stroke:#333,stroke-width:4px

View File

@@ -1,3 +0,0 @@
kanban
id2[In progress]
docs[Create Blog about the new diagram]@{ priority: 'Very Low', ticket: MC-2037, assigned: 'knsv' }

View File

@@ -1,24 +0,0 @@
---
config:
kanban:
ticketBaseUrl: 'https://mermaidchart.atlassian.net/browse/#TICKET#'
# sectionWidth: 300
---
kanban
Todo
[Create Documentation]
docs[Create Blog about the new diagram]
id7[In progress]
id6[Create renderer so that it works in all cases. We also add some extra text here for testing purposes. And some more just for the extra flare.]
id9[Ready for deploy]
id8[Design grammar]@{ assigned: 'knsv' }
id10[Ready for test]
id4[Create parsing tests]@{ ticket: MC-2038, assigned: 'K.Sveidqvist', priority: 'High' }
id66[last item]@{ priority: 'Very Low', assigned: 'knsv' }
id11[Done]
id5[define getData]
id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char]@{ ticket: MC-2036, priority: 'Very High'}
id3[Update DB function]@{ ticket: MC-2037, assigned: knsv, priority: 'High' }
id12[Can't reproduce]
id3[Weird flickering in Firefox]

View File

@@ -1,9 +0,0 @@
flowchart TD
B("B: Investigate how SOX2 is implicated in endothelial to mesenchymal transition and how expression affects the migration of GBM cells")
B --> D["D: Investigate and isolate migratory glioblastoma with regards to phenotype"] & E["E: Investigate the effect of SOX2 on EMT in GBM"] & F["F: Investigate the effect of SOX2 downregulation on migratory/invasive phenotype in the in vitro model and the effect on metastasis formation in the in vivo model"]
L("L: Expression of markers for stemness, EMT and invasion pattern")
D -- RNA Sequencing --> L
E -- "Lentiviral-mediated RNA interference" --> J("J:Sox2 Knock-Down")
J --> O("O: Up/downregulation of stemness markers") & P("P: Up/downregulation of EMT markers")
F -- Transwell/Wound Healing Assay --> K("K: In Vitro m")

View File

@@ -105,77 +105,49 @@
</head>
<body>
<div class="grid grid-cols-2 gap-4">
<pre id="diagram4" class="mermaid">
sequenceDiagram
actor Alice
participant John@{ "type": "boundary" }
participant P1@{ "type": "control" } as New Control
participant P2@{ "type": "entity" } as New Entity
participant P3@{ "type": "database" } as New Database
Alice->>+John: Hello John, how are you?
John->>P1: new msg
P1->>P2: new msg
P2->>P3: new msg
Alice->>+John: John, can you hear me?
P3->>P2: new msg
P2->>P1: new msg
John-->>-Alice: Hi Alice, I can hear you!
P1->>John: new msg
John-->>-Alice: I feel great!
</pre
>
<pre id="diagram4" class="mermaid">
treemap
"Section 1"
"Leaf 1.1": 12
"Section 1.2":::class1
"Leaf 1.2.1": 12
"Section 2"
"Leaf 2.1": 20:::class1
"Leaf 2.2": 25
"Leaf 2.3": 12
</pre
>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid">
---
title: "Grades"
---
radar-beta
axis m["Math"], s["Science"], e["English"]
axis h["History"], g["Geography"], a["Art"]
curve a["Alice"]{85, 90, 80, 70, 75, 90}
curve b["Bob"]{70, 75, 85, 80, 90, 85}
max 100
min 0
</pre
>
<pre id="diagram4" class="mermaid">
flowchart LR
A["A"] e1@==> B["B"]
A L_A_n1_0@--> n1["C"]
B L_B_n2_0@==> n2["D"]
n1 L_n1_n2_0@--> n2
e1@{ animate: true }
L_A_n1_0@{ animation: slow }
L_B_n2_0@{ animation: slow }
L_n1_n2_0@{ animation: fast }
</pre
>
</div>
<pre id="diagram4" class="mermaid2">
flowchart LR
A e1@==> B
e1@{ animate: true }
config:
layout: elk
---
mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectiveness&lt;br/>and features
On Automatic creation
Uses
Creative techniques
Strategic planning
Argument mapping
Tools
id)I am a cloud(
id))I am a bang((
Tools
</pre>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
---
flowchart
aid0
</pre
>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
---
mindmap
aid0
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: ogdc
@@ -197,7 +169,7 @@ radar-beta
</pre
>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -218,7 +190,7 @@ radar-beta
</pre
>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -240,7 +212,7 @@ radar-beta
</pre
>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -255,7 +227,7 @@ radar-beta
</pre
>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -289,7 +261,7 @@ radar-beta
note for Class10 "Cool class\nI said it's very cool class!"
</pre
>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -309,7 +281,7 @@ radar-beta
test_entity - satisfies -> test_req
</pre
>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -333,7 +305,7 @@ radar-beta
%% nat --> internet
</pre
>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -363,7 +335,7 @@ radar-beta
subnet1 & subnet2 --> nat --> internet
</pre
>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -379,7 +351,19 @@ config:
b2 --> b3
b1 --> b4</pre
>
<pre id="diagram5" class="mermaid2">
<pre id="diagram4" class="mermaid">
treemap
"Section 1"
"Leaf 1.1": 12
"Section 1.2":::class1
"Leaf 1.2.1": 12
"Section 2"
"Leaf 2.1": 20:::class1
"Leaf 2.2": 25
"Leaf 2.3": 12
</pre>
<pre id="diagram5" class="mermaid">
---
config:
layout: elk
@@ -393,7 +377,7 @@ config:
I@{ shape: question}
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: tidy-tree
@@ -417,7 +401,7 @@ config:
Mermaid
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -431,7 +415,7 @@ config:
%%B@{ shape: diamond}
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -445,7 +429,7 @@ config:
B@{ shape: diamond}
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -458,7 +442,7 @@ config:
D@{ shape: question}
I@{ shape: question}
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -474,7 +458,7 @@ config:
D@{ shape: trap-b}
I@{ shape: lean-l}
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -487,7 +471,7 @@ flowchart LR
B --> C
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -500,7 +484,7 @@ flowchart LR
A{A} --> B((Bo boo)) & B & B & B
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -520,7 +504,7 @@ A{A} --> B((Bo boo)) & B & B & B
D --> I & E
E --> I
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -545,7 +529,7 @@ config:
end
end
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -558,7 +542,7 @@ config:
D-->I
D-->I
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -597,7 +581,7 @@ flowchart LR
n8@{ shape: rect}
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -613,7 +597,7 @@ flowchart LR
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -622,7 +606,7 @@ flowchart LR
A{A} --> B & C
</pre
>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -634,7 +618,7 @@ flowchart LR
end
</pre
>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -652,7 +636,7 @@ flowchart LR
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
kanban:
@@ -671,81 +655,81 @@ kanban
task3[💻 Develop login feature]@{ ticket: 103 }
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'rounded' }
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'rounded' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'rounded' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'rounded' }
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'square' }
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'square' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'square' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'square' }
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'circle' }
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'circle' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'circle' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'circle' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Style] --> A@{ icon: 'logos:aws', form: 'circle' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
kanban
id2[In progress]
docs[Create Blog about the new diagram]@{ priority: 'Very Low', ticket: MC-2037, assigned: 'knsv' }
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
kanban:
@@ -816,7 +800,7 @@ kanban
// look: 'handDrawn',
// 'elk.nodePlacement.strategy': 'NETWORK_SIMPLEX',
// layout: 'dagre',
layout: 'dagre',
layout: 'elk',
// layout: 'fixed',
// htmlLabels: false,
flowchart: { titleTopMargin: 10 },

View File

@@ -1,818 +0,0 @@
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/font-awesome.min.css"
/>
<link
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
rel="stylesheet"
/>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
rel="stylesheet"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Caveat:wght@400..700&family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
rel="stylesheet"
/>
<style>
body {
/* background: rgb(221, 208, 208); */
/* background: #333; */
font-family: 'Arial';
/* color: white; */
/* font-size: 18px !important; */
}
h1 {
color: grey;
}
.mermaid2 {
display: none;
}
.mermaid svg {
/* font-size: 18px !important; */
/* background-color: #efefef;
background-image: radial-gradient(#fff 51%, transparent 91%),
radial-gradient(#fff 51%, transparent 91%);
background-size: 20px 20px;
background-position:
0 0,
10px 10px;
background-repeat: repeat; */
}
.malware {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 150px;
background: red;
color: black;
display: flex;
display: flex;
justify-content: center;
align-items: center;
font-family: monospace;
font-size: 72px;
}
pre {
width: 100%;
}
/* tspan {
font-size: 6px !important;
} */
/* .flowchart-link {
stroke-dasharray: 4, 4 !important;
animation: flow 1s linear infinite;
animation: dashdraw 4.93282s linear infinite;
stroke-width: 2px !important;
} */
@keyframes dashdraw {
from {
stroke-dashoffset: 0;
}
}
/*stroke-width:2;stroke-dasharray:10.000000,9.865639;stroke-dashoffset:-198.656393;animation: 4.932820s linear infinite;*/
/* stroke-width:2;stroke-dasharray:10.000000,9.865639;stroke-dashoffset:-198.656393;animation: dashdraw 4.932820s linear infinite;*/
</style>
</head>
<body>
<pre id="diagram4" class="mermaid">
---
config:
layout: tidy-tree
---
mindmap
[My User Manual ]
)What motivates me?(
a("Impact: Create real change, see outcomes")
People Energy: Connection, fun, joy
)What drives me nuts?(
Inefficiency: Poor execution, no follow-through
Indecision: Limbo > Action, prefer decisive moves
)How can people really impress me?(
Bring energy, ideas, enthusiasm
Execute + follow through!
Make things better, not just maintain
)What qualities do I value?(
Approachable & collaborative
Direct, transparent communication
Ownership with healthy boundaries
Willingness to learn and share
New perspectives & expertise
)What people might misunderstand(
Passion ≠ upset; high energy!
Many ideas ≠ all top priorities
I move fast - let me know if pace is overwhelming
)Real talk: My quirks & weaknesses(
Thrive on momentum, find it hard to slow down
Prefer "real" over "polished"
"Why" is my default question
Action > reflection sometimes
My energy can overshadow quieter voices
Help me pause & amplify all voices!
)Working & Growing Together(
How I like to be coached: Help me see blind spots, push/anchor me
Communication: Slack for quick; docs for updates; calls for nuance
Feedback: Real-time & caring, structured for growth
Receiving feedback: Direct, soon, with impact; ideally 1:1 or positive+constructive together
How I coach: Focus on outcomes, stretch/celebrate, creative growth opportunities, consultative problem-solving
Mistakes: Be quick, transparent, own it, share learnings
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart
aid0
</pre
>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
mindmap
aid0
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: ogdc
---
flowchart-elk TB
c1-->a2
subgraph one
a1-->a2
end
subgraph two
b1-->b2
end
subgraph three
c1-->c2
end
one --> two
three --> two
two --> c2
</pre
>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart TB
process_C
subgraph container_Alpha
subgraph process_B
pppB
end
subgraph process_A
pppA
end
process_B-->|via_AWSBatch|container_Beta
process_A-->|messages|container_Beta
end
</pre
>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart TB
subgraph container_Beta
process_C
end
subgraph container_Alpha
subgraph process_B
pppB
end
subgraph process_A
pppA
end
process_B-->|via_AWSBatch|container_Beta
process_A-->|messages|container_Beta
end
</pre
>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart TB
subgraph container_Beta
process_C
end
process_B-->|via_AWSBatch|container_Beta
</pre
>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
classDiagram
note "I love this diagram!\nDo you love it?"
Class01 <|-- AveryLongClass : Cool
&lt;&lt;interface&gt;&gt; Class01
Class03 "1" *-- "*" Class04
Class05 "1" o-- "many" Class06
Class07 "1" .. "*" Class08
Class09 "1" --> "*" C2 : Where am i?
Class09 "*" --* "*" C3
Class09 "1" --|> "1" Class07
Class12 <|.. Class08
Class11 ..>Class12
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
Class01 : int chimp
Class01 : int gorilla
Class01 : -int privateChimp
Class01 : +int publicGorilla
Class01 : #int protectedMarmoset
Class08 <--> C2: Cool label
class Class10 {
&lt;&lt;service&gt;&gt;
int id
test()
}
note for Class10 "Cool class\nI said it's very cool class!"
</pre
>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
requirementDiagram
requirement test_req {
id: 1
text: the test text.
risk: high
verifymethod: test
}
element test_entity {
type: simulation
}
test_entity - satisfies -> test_req
</pre
>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart-elk TB
internet
nat
router
compute1
subgraph project
router
nat
subgraph subnet1
compute1
end
end
%% router --> subnet1
subnet1 --> nat
%% nat --> internet
</pre
>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart-elk TB
internet
nat
router
lb1
lb2
compute1
compute2
subgraph project
router
nat
subgraph subnet1
compute1
lb1
end
subgraph subnet2
compute2
lb2
end
end
internet --> router
router --> subnet1 & subnet2
subnet1 & subnet2 --> nat --> internet
</pre
>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
elk:
mergeEdges: false
forceNodeModelOrder: false
considerModelOrder: NONE
---
flowchart TB
a --> a1 & a2 & a3 & a4
b --> b1 & b2
b2 --> b3
b1 --> b4</pre
>
<pre id="diagram4" class="mermaid2">
treemap
"Section 1"
"Leaf 1.1": 12
"Section 1.2":::class1
"Leaf 1.2.1": 12
"Section 2"
"Leaf 2.1": 20:::class1
"Leaf 2.2": 25
"Leaf 2.3": 12
</pre>
<pre id="diagram5" class="mermaid2">
---
config:
layout: elk
flowchart:
curve: rounded
---
flowchart LR
I["fa:fa-code Text"] -- Mermaid js --> D["Use<br/>the<br/>editor!"]
I --> D & D
D@{ shape: question}
I@{ shape: question}
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: tidy-tree
---
mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectiveness<br/>and features
On Automatic creation
Uses
Creative techniques
Strategic planning
Argument mapping
Tools
Pen and paper
Mermaid
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
flowchart:
curve: linear
---
flowchart LR
A[A] --> B[B]
A[A] --- B([C])
A@{ shape: diamond}
%%B@{ shape: diamond}
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
flowchart:
curve: linear
---
flowchart LR
A[A] -- Mermaid js --> B[B]
A[A] -- Mermaid js --- B[B]
A@{ shape: diamond}
B@{ shape: diamond}
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
flowchart:
curve: rounded
---
flowchart LR
D["Use the editor"] -- Mermaid js --> I["fa:fa-code Text"]
I --> D & D
D@{ shape: question}
I@{ shape: question}
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
flowchart:
curve: rounded
elk:
nodePlacementStrategy: NETWORK_SIMPLEX
---
flowchart LR
D["Use the editor"] -- Mermaid js --> I["fa:fa-code Text"]
D --> I & I
a["a"]
D@{ shape: trap-b}
I@{ shape: lean-l}
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart LR
%% subgraph s1["Untitled subgraph"]
C["Evaluate"]
%% end
B --> C
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
flowchart:
//curve: linear
---
flowchart LR
%% A ==> B
%% A2 --> B2
A{A} --> B((Bo boo)) & B & B & B
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
theme: default
look: classic
---
flowchart LR
subgraph s1["APA"]
D{"Use the editor"}
end
subgraph S2["S2"]
s1
I>"fa:fa-code Text"]
E["E"]
end
D -- Mermaid js --> I
D --> I & E
E --> I
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart LR
a
subgraph s0["APA"]
subgraph s8["APA"]
subgraph s1["APA"]
D{"X"}
E[Q]
end
subgraph s3["BAPA"]
F[Q]
I
end
D --> I
D --> I
D --> I
I{"X"}
end
end
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart LR
a
D{"Use the editor"}
D -- Mermaid js --> I{"fa:fa-code Text"}
D-->I
D-->I
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart LR
subgraph s1["Untitled subgraph"]
n1["Evaluate"]
n2["Option 1"]
n3["Option 2"]
n4["fa:fa-car Option 3"]
end
subgraph s2["Untitled subgraph"]
n5["Evaluate"]
n6["Option 1"]
n7["Option 2"]
n8["fa:fa-car Option 3"]
end
A["Start"] -- Some text --> B("Continue")
B --> C{"Evaluate"}
C -- One --> D["Option 1"]
C -- Two --> E["Option 2"]
C -- Three --> F["fa:fa-car Option 3"]
n1 -- One --> n2
n1 -- Two --> n3
n1 -- Three --> n4
n5 -- One --> n6
n5 -- Two --> n7
n5 -- Three --> n8
n1@{ shape: diam}
n2@{ shape: rect}
n3@{ shape: rect}
n4@{ shape: rect}
n5@{ shape: diam}
n6@{ shape: rect}
n7@{ shape: rect}
n8@{ shape: rect}
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart LR
subgraph s1["Untitled subgraph"]
n1["Evaluate"]
n2["Option 1"]
end
n1 -- One --> n2
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart LR
A{A} --> B & C
</pre
>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart LR
A{A} --> B & C
subgraph "subbe"
A
end
</pre
>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart LR
n2@{ shape: rect}
n3@{ shape: rect}
n4@{ shape: rect}
A["Start"] -- Some text --> B("Continue")
B --> C{"Evaluate"}
C -- One --> D["Option 1"]
C -- Two --> E["Option 2"]
C -- Three --> F["fa:fa-car Option 3"]
%% C@{ shape: hexagon}
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
kanban:
ticketBaseUrl: 'https://github.com/your-repo/issues/#TICKET#'
---
kanban
Backlog
task1[📝 Define project requirements]@{ ticket: a101 }
To Do
task2[🔍 Research technologies]@{ ticket: a102 }
Review
task4[🔍 Code review for login feature]@{ ticket: a104 }
Done
task5[✅ Deploy initial version]@{ ticket: a105 }
In Progress
task3[💻 Develop login feature]@{ ticket: 103 }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'rounded' }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'rounded' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'rounded' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'rounded' }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'square' }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'square' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'square' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'square' }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'circle' }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'circle' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'circle' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'circle' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Style] --> A@{ icon: 'logos:aws', form: 'circle' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
kanban
id2[In progress]
docs[Create Blog about the new diagram]@{ priority: 'Very Low', ticket: MC-2037, assigned: 'knsv' }
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
kanban:
ticketBaseUrl: 'https://mermaidchart.atlassian.net/browse/#TICKET#'
# sectionWidth: 300
---
kanban
Todo
[Create Documentation]
docs[Create Blog about the new diagram]
id7[In progress]
id6[Create renderer so that it works in all cases. We also add some extra text here for testing purposes. And some more just for the extra flare.]
id9[Ready for deploy]
id8[Design grammar]@{ assigned: 'knsv' }
id10[Ready for test]
id4[Create parsing tests]@{ ticket: MC-2038, assigned: 'K.Sveidqvist', priority: 'High' }
id66[last item]@{ priority: 'Very Low', assigned: 'knsv' }
id11[Done]
id5[define getData]
id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char]@{ ticket: MC-2036, priority: 'Very High'}
id3[Update DB function]@{ ticket: MC-2037, assigned: knsv, priority: 'High' }
id12[Can't reproduce]
id3[Weird flickering in Firefox]
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
import layoutElk from './mermaid-layout-elk.esm.mjs';
import layouts from './mermaid-layout-tidy-tree.esm.mjs';
const staticBellIconPack = {
prefix: 'fa6-regular',
icons: {
bell: {
body: '<path fill="currentColor" d="M224 0c-17.7 0-32 14.3-32 32v19.2C119 66 64 130.6 64 208v25.4c0 45.4-15.5 89.5-43.8 124.9L5.3 377c-5.8 7.2-6.9 17.1-2.9 25.4S14.8 416 24 416h400c9.2 0 17.6-5.3 21.6-13.6s2.9-18.2-2.9-25.4l-14.9-18.6c-28.3-35.5-43.8-79.6-43.8-125V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32m0 96c61.9 0 112 50.1 112 112v25.4c0 47.9 13.9 94.6 39.7 134.6H72.3c25.8-40 39.7-86.7 39.7-134.6V208c0-61.9 50.1-112 112-112m64 352H160c0 17 6.7 33.3 18.7 45.3S207 512 224 512s33.3-6.7 45.3-18.7S288 465 288 448"/>',
width: 448,
},
},
width: 512,
height: 512,
};
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos@1/icons.json').then((res) => res.json()),
},
{
name: 'fa',
loader: () => staticBellIconPack,
},
]);
mermaid.registerLayoutLoaders(layouts);
await mermaid.initialize({
logLevel: 0,
securityLevel: 'loose',
callback,
});
mermaid.parseError = function (err, hash) {
console.error('In parse error:');
console.error(err);
};
</script>
</body>
</html>

View File

@@ -2,35 +2,35 @@
"durations": [
{
"spec": "cypress/integration/other/configuration.spec.js",
"duration": 5944
"duration": 6099
},
{
"spec": "cypress/integration/other/external-diagrams.spec.js",
"duration": 2180
"duration": 2236
},
{
"spec": "cypress/integration/other/ghsa.spec.js",
"duration": 3282
"duration": 3405
},
{
"spec": "cypress/integration/other/iife.spec.js",
"duration": 2137
"duration": 2176
},
{
"spec": "cypress/integration/other/interaction.spec.js",
"duration": 11926
"duration": 12300
},
{
"spec": "cypress/integration/other/rerender.spec.js",
"duration": 2021
"duration": 2089
},
{
"spec": "cypress/integration/other/xss.spec.js",
"duration": 31377
"duration": 32033
},
{
"spec": "cypress/integration/rendering/appli.spec.js",
"duration": 3442
"duration": 3672
},
{
"spec": "cypress/integration/rendering/architecture.spec.ts",
@@ -38,191 +38,191 @@
},
{
"spec": "cypress/integration/rendering/block.spec.js",
"duration": 18390
"duration": 18135
},
{
"spec": "cypress/integration/rendering/c4.spec.js",
"duration": 6468
"duration": 5661
},
{
"spec": "cypress/integration/rendering/classDiagram-elk-v3.spec.js",
"duration": 41282
"duration": 41456
},
{
"spec": "cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js",
"duration": 39226
"duration": 38910
},
{
"spec": "cypress/integration/rendering/classDiagram-v2.spec.js",
"duration": 25028
"duration": 24120
},
{
"spec": "cypress/integration/rendering/classDiagram-v3.spec.js",
"duration": 38458
"duration": 38454
},
{
"spec": "cypress/integration/rendering/classDiagram.spec.js",
"duration": 17305
"duration": 17099
},
{
"spec": "cypress/integration/rendering/conf-and-directives.spec.js",
"duration": 9762
"duration": 9844
},
{
"spec": "cypress/integration/rendering/current.spec.js",
"duration": 2923
"duration": 2951
},
{
"spec": "cypress/integration/rendering/erDiagram-unified.spec.js",
"duration": 89135
"duration": 90081
},
{
"spec": "cypress/integration/rendering/erDiagram.spec.js",
"duration": 18976
"duration": 19496
},
{
"spec": "cypress/integration/rendering/errorDiagram.spec.js",
"duration": 3643
"duration": 3829
},
{
"spec": "cypress/integration/rendering/flowchart-elk.spec.js",
"duration": 43103
"duration": 42517
},
{
"spec": "cypress/integration/rendering/flowchart-handDrawn.spec.js",
"duration": 31637
"duration": 31541
},
{
"spec": "cypress/integration/rendering/flowchart-icon.spec.js",
"duration": 7630
"duration": 7749
},
{
"spec": "cypress/integration/rendering/flowchart-shape-alias.spec.ts",
"duration": 25642
"duration": 25230
},
{
"spec": "cypress/integration/rendering/flowchart-v2.spec.js",
"duration": 50365
"duration": 49359
},
{
"spec": "cypress/integration/rendering/flowchart.spec.js",
"duration": 32790
"duration": 33028
},
{
"spec": "cypress/integration/rendering/gantt.spec.js",
"duration": 23065
"duration": 22271
},
{
"spec": "cypress/integration/rendering/gitGraph.spec.js",
"duration": 52238
"duration": 51837
},
{
"spec": "cypress/integration/rendering/iconShape.spec.ts",
"duration": 289380
"duration": 285060
},
{
"spec": "cypress/integration/rendering/imageShape.spec.ts",
"duration": 59265
"duration": 59517
},
{
"spec": "cypress/integration/rendering/info.spec.ts",
"duration": 3269
"duration": 3501
},
{
"spec": "cypress/integration/rendering/journey.spec.js",
"duration": 7470
"duration": 7405
},
{
"spec": "cypress/integration/rendering/kanban.spec.ts",
"duration": 7980
"duration": 7975
},
{
"spec": "cypress/integration/rendering/katex.spec.js",
"duration": 3896
"duration": 4312
},
{
"spec": "cypress/integration/rendering/marker_unique_id.spec.js",
"duration": 2640
"duration": 2630
},
{
"spec": "cypress/integration/rendering/mindmap-tidy-tree.spec.js",
"duration": 4327
"duration": 4541
},
{
"spec": "cypress/integration/rendering/mindmap.spec.ts",
"duration": 12588
"duration": 12134
},
{
"spec": "cypress/integration/rendering/newShapes.spec.ts",
"duration": 153490
"duration": 151160
},
{
"spec": "cypress/integration/rendering/oldShapes.spec.ts",
"duration": 117833
"duration": 118044
},
{
"spec": "cypress/integration/rendering/packet.spec.ts",
"duration": 4975
"duration": 5166
},
{
"spec": "cypress/integration/rendering/pie.spec.ts",
"duration": 6682
"duration": 7074
},
{
"spec": "cypress/integration/rendering/quadrantChart.spec.js",
"duration": 8972
"duration": 9518
},
{
"spec": "cypress/integration/rendering/radar.spec.js",
"duration": 5631
"duration": 5846
},
{
"spec": "cypress/integration/rendering/requirement.spec.js",
"duration": 2776
"duration": 3089
},
{
"spec": "cypress/integration/rendering/requirementDiagram-unified.spec.js",
"duration": 54373
"duration": 55361
},
{
"spec": "cypress/integration/rendering/sankey.spec.ts",
"duration": 7203
"duration": 7236
},
{
"spec": "cypress/integration/rendering/sequencediagram-v2.spec.js",
"duration": 31707
"duration": 26057
},
{
"spec": "cypress/integration/rendering/sequencediagram.spec.js",
"duration": 48327
"duration": 48401
},
{
"spec": "cypress/integration/rendering/stateDiagram-v2.spec.js",
"duration": 30728
"duration": 30364
},
{
"spec": "cypress/integration/rendering/stateDiagram.spec.js",
"duration": 16881
"duration": 16862
},
{
"spec": "cypress/integration/rendering/theme.spec.js",
"duration": 30715
"duration": 30553
},
{
"spec": "cypress/integration/rendering/timeline.spec.ts",
"duration": 8586
"duration": 8962
},
{
"spec": "cypress/integration/rendering/treemap.spec.ts",
"duration": 15184
"duration": 12486
},
{
"spec": "cypress/integration/rendering/xyChart.spec.js",
"duration": 21282
"duration": 21718
},
{
"spec": "cypress/integration/rendering/zenuml.spec.js",
"duration": 3576
"duration": 3882
}
]
}

View File

@@ -10,7 +10,7 @@
# Interface: ParseOptions
Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L89)
Defined in: [packages/mermaid/src/types.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L88)
## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mer
> `optional` **suppressErrors**: `boolean`
Defined in: [packages/mermaid/src/types.ts:94](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L94)
Defined in: [packages/mermaid/src/types.ts:93](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L93)
If `true`, parse will return `false` instead of throwing error when the diagram is invalid.
The `parseError` function will not be called.

View File

@@ -10,7 +10,7 @@
# Interface: ParseResult
Defined in: [packages/mermaid/src/types.ts:97](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L97)
Defined in: [packages/mermaid/src/types.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L96)
## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:97](https://github.com/mermaid-js/mer
> **config**: [`MermaidConfig`](MermaidConfig.md)
Defined in: [packages/mermaid/src/types.ts:105](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L105)
Defined in: [packages/mermaid/src/types.ts:104](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L104)
The config passed as YAML frontmatter or directives
@@ -28,6 +28,6 @@ The config passed as YAML frontmatter or directives
> **diagramType**: `string`
Defined in: [packages/mermaid/src/types.ts:101](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L101)
Defined in: [packages/mermaid/src/types.ts:100](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L100)
The diagram type, e.g. 'flowchart', 'sequence', etc.

View File

@@ -10,7 +10,7 @@
# Interface: RenderResult
Defined in: [packages/mermaid/src/types.ts:115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L115)
Defined in: [packages/mermaid/src/types.ts:114](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L114)
## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:115](https://github.com/mermaid-js/me
> `optional` **bindFunctions**: (`element`) => `void`
Defined in: [packages/mermaid/src/types.ts:133](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L133)
Defined in: [packages/mermaid/src/types.ts:132](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L132)
Bind function to be called after the svg has been inserted into the DOM.
This is necessary for adding event listeners to the elements in the svg.
@@ -45,7 +45,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
> **diagramType**: `string`
Defined in: [packages/mermaid/src/types.ts:123](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L123)
Defined in: [packages/mermaid/src/types.ts:122](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L122)
The diagram type, e.g. 'flowchart', 'sequence', etc.
@@ -55,6 +55,6 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
> **svg**: `string`
Defined in: [packages/mermaid/src/types.ts:119](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L119)
Defined in: [packages/mermaid/src/types.ts:118](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L118)
The svg code for the rendered graph.

View File

@@ -59,7 +59,6 @@ To add an integration to this list, see the [Integrations - create page](./integ
- [GitLab](https://docs.gitlab.com/ee/user/markdown.html#diagrams-and-flowcharts) ✅
- [GNU Octave](https://octave.org/) ✅
- [octave_mermaid_js](https://github.com/CNOCTAVE/octave_mermaid_js) ✅
- [HackMD](https://hackmd.io/c/tutorials/%2F%40docs%2Fflowchart-en#Create-more-complex-flowcharts) ✅
- [Mermaid Plugin for JetBrains IDEs](https://plugins.jetbrains.com/plugin/20146-mermaid)
- [MonsterWriter](https://www.monsterwriter.com/) ✅
- [Joplin](https://joplinapp.org) ✅

View File

@@ -402,7 +402,7 @@ block
blockArrowId4<["Label"]>(down)
blockArrowId5<["Label"]>(x)
blockArrowId6<["Label"]>(y)
blockArrowId7<["Label"]>(x, down)
blockArrowId6<["Label"]>(x, down)
```
```mermaid
@@ -413,7 +413,7 @@ block
blockArrowId4<["Label"]>(down)
blockArrowId5<["Label"]>(x)
blockArrowId6<["Label"]>(y)
blockArrowId7<["Label"]>(x, down)
blockArrowId6<["Label"]>(x, down)
```
#### Example - Space Blocks

View File

@@ -62,7 +62,7 @@ radar-beta
radar-beta
title Restaurant Comparison
axis food["Food Quality"], service["Service"], price["Price"]
axis ambiance["Ambiance"]
axis ambiance["Ambiance"],
curve a["Restaurant A"]{4, 3, 2, 4}
curve b["Restaurant B"]{3, 4, 3, 3}
@@ -78,7 +78,7 @@ radar-beta
radar-beta
title Restaurant Comparison
axis food["Food Quality"], service["Service"], price["Price"]
axis ambiance["Ambiance"]
axis ambiance["Ambiance"],
curve a["Restaurant A"]{4, 3, 2, 4}
curve b["Restaurant B"]{3, 4, 3, 3}

View File

@@ -196,11 +196,7 @@ sequenceDiagram
### Aliases
The actor can have a convenient identifier and a descriptive label. Aliases can be defined in two ways: using external syntax with the `as` keyword, or inline within the configuration object.
#### External Alias Syntax
You can define an alias using the `as` keyword after the participant declaration:
The actor can have a convenient identifier and a descriptive label.
```mermaid-example
sequenceDiagram
@@ -218,78 +214,6 @@ sequenceDiagram
J->>A: Great!
```
The external alias syntax also works with participant stereotype configurations, allowing you to combine type specification with aliases:
```mermaid-example
sequenceDiagram
participant API@{ "type": "boundary" } as Public API
actor DB@{ "type": "database" } as User Database
participant Svc@{ "type": "control" } as Auth Service
API->>Svc: Authenticate
Svc->>DB: Query user
DB-->>Svc: User data
Svc-->>API: Token
```
```mermaid
sequenceDiagram
participant API@{ "type": "boundary" } as Public API
actor DB@{ "type": "database" } as User Database
participant Svc@{ "type": "control" } as Auth Service
API->>Svc: Authenticate
Svc->>DB: Query user
DB-->>Svc: User data
Svc-->>API: Token
```
#### Inline Alias Syntax
Alternatively, you can define an alias directly inside the configuration object using the `"alias"` field. This works with both `participant` and `actor` keywords:
```mermaid-example
sequenceDiagram
participant API@{ "type": "boundary", "alias": "Public API" }
participant Auth@{ "type": "control", "alias": "Auth Service" }
participant DB@{ "type": "database", "alias": "User Database" }
API->>Auth: Login request
Auth->>DB: Query user
DB-->>Auth: User data
Auth-->>API: Access token
```
```mermaid
sequenceDiagram
participant API@{ "type": "boundary", "alias": "Public API" }
participant Auth@{ "type": "control", "alias": "Auth Service" }
participant DB@{ "type": "database", "alias": "User Database" }
API->>Auth: Login request
Auth->>DB: Query user
DB-->>Auth: User data
Auth-->>API: Access token
```
#### Alias Precedence
When both inline alias (in the configuration object) and external alias (using `as` keyword) are provided, the **external alias takes precedence**:
```mermaid-example
sequenceDiagram
participant API@{ "type": "boundary", "alias": "Internal Name" } as External Name
participant DB@{ "type": "database", "alias": "Internal DB" } as External DB
API->>DB: Query
DB-->>API: Result
```
```mermaid
sequenceDiagram
participant API@{ "type": "boundary", "alias": "Internal Name" } as External Name
participant DB@{ "type": "database", "alias": "Internal DB" } as External DB
API->>DB: Query
DB-->>API: Result
```
In the example above, "External Name" and "External DB" will be displayed, not "Internal Name" and "Internal DB".
### Actor Creation and Destruction (v10.3.0+)
It is possible to create and destroy actors by messages. To do so, add a create or destroy directive before the message.

View File

@@ -11,8 +11,7 @@ import unicorn from 'eslint-plugin-unicorn';
import globals from 'globals';
import tseslint from 'typescript-eslint';
/** @type {import('eslint').Linter.FlatConfig[]} */
const config = tseslint.config(
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.stylisticTypeChecked,
@@ -24,7 +23,6 @@ const config = tseslint.config(
'.git/',
'**/generated/',
'**/coverage/',
'.esbuild/dev-explorer/**',
'packages/mermaid/src/config.type.ts',
'packages/mermaid/src/docs/.vitepress/components.d.ts',
],
@@ -223,5 +221,3 @@ const config = tseslint.config(
processor: 'markdown/markdown',
}
);
export default config;

View File

@@ -63,22 +63,21 @@
]
},
"devDependencies": {
"@applitools/eyes-cypress": "^3.56.5",
"@argos-ci/cypress": "^6.2.2",
"@changesets/changelog-github": "^0.5.2",
"@changesets/cli": "^2.29.8",
"@cspell/eslint-plugin": "^9.3.2",
"@applitools/eyes-cypress": "^3.55.4",
"@argos-ci/cypress": "^6.1.5",
"@changesets/changelog-github": "^0.5.1",
"@changesets/cli": "^2.29.7",
"@cspell/eslint-plugin": "^9.3.0",
"@cypress/code-coverage": "^3.14.7",
"@eslint/js": "^9.26.0",
"@rollup/plugin-typescript": "^12.1.4",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/express": "^5.0.5",
"@types/js-yaml": "^4.0.9",
"@types/jsdom": "^21.1.7",
"@types/lodash": "^4.17.21",
"@types/lodash": "^4.17.20",
"@types/mdast": "^4.0.4",
"@types/node": "^22.19.1",
"@shoelace-style/shoelace": "^2.20.1",
"@types/node": "^22.18.13",
"@types/rollup-plugin-visualizer": "^5.0.3",
"@vitest/coverage-v8": "^3.2.4",
"@vitest/spy": "^3.2.4",
@@ -89,7 +88,7 @@
"cors": "^2.8.5",
"cpy-cli": "^5.0.0",
"cross-env": "^7.0.3",
"cspell": "^9.3.2",
"cspell": "^9.2.2",
"cypress": "^14.5.4",
"cypress-image-snapshot": "^4.0.1",
"cypress-split": "^1.24.25",
@@ -106,14 +105,13 @@
"eslint-plugin-no-only-tests": "^3.3.0",
"eslint-plugin-tsdoc": "^0.4.0",
"eslint-plugin-unicorn": "^62.0.0",
"lit": "^3.2.1",
"express": "^5.2.1",
"express": "^5.1.0",
"globals": "^16.4.0",
"globby": "^14.1.0",
"husky": "^9.1.7",
"jest": "^30.1.3",
"jison": "^0.4.18",
"js-yaml": "^4.1.1",
"js-yaml": "^4.1.0",
"jsdom": "^26.1.0",
"langium-cli": "3.3.0",
"lint-staged": "^16.1.6",
@@ -124,12 +122,12 @@
"prettier-plugin-jsdoc": "^1.3.3",
"rimraf": "^6.0.1",
"rollup-plugin-visualizer": "^6.0.5",
"start-server-and-test": "^2.1.3",
"start-server-and-test": "^2.1.2",
"tslib": "^2.8.1",
"tsx": "^4.20.6",
"typescript": "~5.7.3",
"typescript-eslint": "^8.38.0",
"vite": "^7.0.8",
"vite": "^7.0.7",
"vite-plugin-istanbul": "^7.0.0",
"vitest": "^3.2.4"
},

View File

@@ -78,7 +78,7 @@
"d3-sankey": "^0.12.3",
"dagre-d3-es": "7.0.13",
"dayjs": "^1.11.19",
"dompurify": "^3.3.1",
"dompurify": "^3.2.7",
"katex": "^0.16.25",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",
@@ -89,11 +89,11 @@
"uuid": "^11.1.0"
},
"devDependencies": {
"@adobe/jsonschema2md": "^8.0.8",
"@adobe/jsonschema2md": "^8.0.7",
"@iconify/types": "^2.0.0",
"@types/cytoscape": "^3.21.9",
"@types/cytoscape-fcose": "^2.2.5",
"@types/d3-sankey": "^0.12.5",
"@types/d3-sankey": "^0.12.4",
"@types/d3-scale": "^4.0.9",
"@types/d3-scale-chromatic": "^3.1.0",
"@types/d3-selection": "^3.0.11",
@@ -121,9 +121,9 @@
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"rimraf": "^6.0.1",
"start-server-and-test": "^2.1.3",
"start-server-and-test": "^2.1.2",
"type-fest": "^4.41.0",
"typedoc": "^0.28.15",
"typedoc": "^0.28.14",
"typedoc-plugin-markdown": "^4.8.1",
"typescript": "~5.7.3",
"unist-util-flatmap": "^1.0.0",

View File

@@ -1,58 +0,0 @@
import c4Db from '../c4Db.js';
import c4 from './c4Diagram.jison';
import { setConfig } from '../../../config.js';
setConfig({
securityLevel: 'strict',
});
describe.each([
['Component', 'component'],
['ComponentDb', 'component_db'],
['ComponentQueue', 'component_queue'],
['Component_Ext', 'external_component'],
['ComponentDb_Ext', 'external_component_db'],
['ComponentQueue_Ext', 'external_component_queue'],
])('parsing a C4 %s', function (macroName, elementName) {
beforeEach(function () {
c4.parser.yy = c4Db;
c4.parser.yy.clear();
});
it('should parse a C4 diagram with one Component correctly', function () {
c4.parser.parse(`C4Component
title Component diagram for Internet Banking Component
${macroName}(ComponentAA, "Internet Banking Component", "Technology", "Allows customers to view information about their bank accounts, and make payments.")`);
const yy = c4.parser.yy;
const shapes = yy.getC4ShapeArray();
expect(shapes.length).toBe(1);
const onlyShape = shapes[0];
expect(onlyShape).toMatchObject({
alias: 'ComponentAA',
descr: {
text: 'Allows customers to view information about their bank accounts, and make payments.',
},
label: {
text: 'Internet Banking Component',
},
techn: {
text: 'Technology',
},
typeC4Shape: {
text: elementName,
},
});
});
it('should handle a trailing whitespaces after Component', function () {
const whitespace = ' ';
const rendered = c4.parser.parse(`C4Component${whitespace}
title Component diagram for Internet Banking Component${whitespace}
${macroName}(ComponentAA, "Internet Banking Component", "Technology", "Allows customers to view information about their bank accounts, and make payments.")${whitespace}`);
expect(rendered).toBe(true);
});
});

View File

@@ -158,10 +158,10 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
"UpdateRelStyle" { this.begin("update_rel_style"); return 'UPDATE_REL_STYLE';}
"UpdateLayoutConfig" { this.begin("update_layout_config"); return 'UPDATE_LAYOUT_CONFIG';}
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext_queue,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config><<EOF>> return "EOF_IN_STRUCT";
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext_queue,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(][ ]*[,] { this.begin("attribute"); return "ATTRIBUTE_EMPTY";}
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext_queue,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(] { this.begin("attribute"); }
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext_queue,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config,attribute>[)] { this.popState();this.popState();}
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config><<EOF>> return "EOF_IN_STRUCT";
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(][ ]*[,] { this.begin("attribute"); return "ATTRIBUTE_EMPTY";}
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(] { this.begin("attribute"); }
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config,attribute>[)] { this.popState();this.popState();}
<attribute>",," { return 'ATTRIBUTE_EMPTY';}
<attribute>"," { }

View File

@@ -70,31 +70,6 @@ describe('Sanitize text', () => {
});
expect(result).not.toContain('javascript:alert(1)');
});
it('should allow HTML tags in sandbox mode', () => {
const htmlStr = '<p>This is a <strong>bold</strong> text</p>';
const result = sanitizeText(htmlStr, {
securityLevel: 'sandbox',
flowchart: { htmlLabels: true },
});
expect(result).toContain('<p>');
expect(result).toContain('<strong>');
expect(result).toContain('</strong>');
expect(result).toContain('</p>');
});
it('should remove script tags in sandbox mode', () => {
const maliciousStr = '<p>Hello <script>alert(1)</script> world</p>';
const result = sanitizeText(maliciousStr, {
securityLevel: 'sandbox',
flowchart: { htmlLabels: true },
});
expect(result).not.toContain('<script>');
expect(result).not.toContain('alert(1)');
expect(result).toContain('<p>');
expect(result).toContain('Hello');
expect(result).toContain('world');
});
});
describe('generic parser', () => {

View File

@@ -66,7 +66,7 @@ export const removeScript = (txt: string): string => {
const sanitizeMore = (text: string, config: MermaidConfig) => {
if (config.flowchart?.htmlLabels !== false) {
const level = config.securityLevel;
if (level === 'antiscript' || level === 'strict' || level === 'sandbox') {
if (level === 'antiscript' || level === 'strict') {
text = removeScript(text);
} else if (level !== 'loose') {
text = breakToPlaceholder(text);

View File

@@ -268,15 +268,7 @@ const fixTaskDates = function (startTime, endTime, dateFormat, excludes, include
const getStartDate = function (prevTime, dateFormat, str) {
str = str.trim();
// Helper function to check if format is a timestamp format (x or X)
const isTimestampFormat = (format) => {
const trimmedFormat = format.trim();
return trimmedFormat === 'x' || trimmedFormat === 'X';
};
// Handle timestamp formats (x, X) with numeric strings
if (isTimestampFormat(dateFormat) && /^\d+$/.test(str)) {
if ((dateFormat.trim() === 'x' || dateFormat.trim() === 'X') && /^\d+$/.test(str)) {
return new Date(Number(str));
}
// Test for after
@@ -301,15 +293,13 @@ const getStartDate = function (prevTime, dateFormat, str) {
return today;
}
// Check for actual date set using dayjs strict parsing
// Check for actual date set
let mDate = dayjs(str, dateFormat.trim(), true);
if (mDate.isValid()) {
return mDate.toDate();
} else {
log.debug('Invalid date:' + str);
log.debug('With date format:' + dateFormat.trim());
// Timestamp formats can fall back to new Date()
const d = new Date(str);
if (
d === undefined ||

View File

@@ -505,27 +505,4 @@ describe('when using the ganttDb', function () {
ganttDb.addTask('test1', 'id1,202304,1d');
expect(() => ganttDb.getTasks()).toThrowError('Invalid date:202304');
});
it('should handle seconds-only format with valid numeric values (issue #5496)', function () {
ganttDb.setDateFormat('ss');
ganttDb.addSection('Network Request');
ganttDb.addTask('RTT', 'rtt, 0, 20');
const tasks = ganttDb.getTasks();
expect(tasks).toHaveLength(1);
expect(tasks[0].task).toBe('RTT');
expect(tasks[0].id).toBe('rtt');
});
it('should handle dates with year typo like 202 instead of 2024 (issue #5496)', function () {
ganttDb.setDateFormat('YYYY-MM-DD');
ganttDb.addSection('Vacation');
ganttDb.addTask('London Trip 1', '2024-12-01, 7d');
ganttDb.addTask('London Trip 2', '202-12-01, 7d');
const tasks = ganttDb.getTasks();
expect(tasks).toHaveLength(2);
// First task should be in year 2024
expect(tasks[0].startTime.getFullYear()).toBe(2024);
// Second task will be parsed as year 202 (fallback to new Date())
expect(tasks[1].startTime.getFullYear()).toBe(202);
});
});

View File

@@ -1,5 +1,4 @@
import dayjs from 'dayjs';
import dayjsDuration from 'dayjs/plugin/duration.js';
import { log } from '../../logger.js';
import {
select,
@@ -29,8 +28,6 @@ import common from '../common/common.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { configureSvgSize } from '../../setupGraphViewbox.js';
dayjs.extend(dayjsDuration);
export const setConf = function () {
log.debug('Something is calling, setConf, remove the call');
};
@@ -81,7 +78,6 @@ const getMaxIntersections = (tasks, orderOffset) => {
};
let w;
const MAX_TICK_COUNT = 10000;
export const draw = function (text, id, version, diagObj) {
const conf = getConfig().gantt;
@@ -606,27 +602,6 @@ export const draw = function (text, id, version, diagObj) {
.attr('class', 'exclude-range');
}
/**
* Calculates the estimated number of ticks based on the time domain and tick interval.
* Returns the estimated number of ticks as a number.
* @param {Date} minTime - The minimum time in the domain
* @param {Date} maxTime - The maximum time in the domain
* @param {number} every - The interval count (e.g., 1 for "1second")
* @param {string} interval - The interval unit (e.g., "second", "day")
* @returns {number} The estimated number of ticks
*/
function getEstimatedTickCount(minTime, maxTime, every, interval) {
if (every <= 0 || minTime > maxTime) {
return Infinity;
}
const timeDiffMs = maxTime - minTime;
const intervalMs = dayjs.duration({ [interval ?? 'day']: every }).asMilliseconds();
if (intervalMs <= 0) {
return Infinity;
}
return Math.ceil(timeDiffMs / intervalMs);
}
/**
* @param theSidePad
* @param theTopPad
@@ -655,54 +630,32 @@ export const draw = function (text, id, version, diagObj) {
);
if (resultTickInterval !== null) {
const every = parseInt(resultTickInterval[1], 10);
if (isNaN(every) || every <= 0) {
log.warn(
`Invalid tick interval value: "${resultTickInterval[1]}". Skipping custom tick interval.`
);
// Skip applying custom ticks
} else {
const interval = resultTickInterval[2];
const weekday = diagObj.db.getWeekday() || conf.weekday;
const every = resultTickInterval[1];
const interval = resultTickInterval[2];
const weekday = diagObj.db.getWeekday() || conf.weekday;
// Get the time domain to check tick count
const domain = timeScale.domain();
const minTime = domain[0];
const maxTime = domain[1];
const estimatedTicks = getEstimatedTickCount(minTime, maxTime, every, interval);
if (estimatedTicks > MAX_TICK_COUNT) {
log.warn(
`The tick interval "${every}${interval}" would generate ${estimatedTicks} ticks, ` +
`which exceeds the maximum allowed (${MAX_TICK_COUNT}). ` +
`This may indicate an invalid date or time range. Skipping custom tick interval.`
);
// D3 will use its default automatic tick generation
} else {
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;
case 'hour':
bottomXAxis.ticks(timeHour.every(every));
break;
case 'day':
bottomXAxis.ticks(timeDay.every(every));
break;
case 'week':
bottomXAxis.ticks(mapWeekdayToTimeFunction[weekday].every(every));
break;
case 'month':
bottomXAxis.ticks(timeMonth.every(every));
break;
}
}
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;
case 'hour':
bottomXAxis.ticks(timeHour.every(every));
break;
case 'day':
bottomXAxis.ticks(timeDay.every(every));
break;
case 'week':
bottomXAxis.ticks(mapWeekdayToTimeFunction[weekday].every(every));
break;
case 'month':
bottomXAxis.ticks(timeMonth.every(every));
break;
}
}
@@ -724,48 +677,32 @@ export const draw = function (text, id, version, diagObj) {
.tickFormat(timeFormat(axisFormat));
if (resultTickInterval !== null) {
const every = parseInt(resultTickInterval[1], 10);
if (isNaN(every) || every <= 0) {
log.warn(
`Invalid tick interval value: "${resultTickInterval[1]}". Skipping custom tick interval.`
);
// Skip applying custom ticks
} else {
const interval = resultTickInterval[2];
const weekday = diagObj.db.getWeekday() || conf.weekday;
const every = resultTickInterval[1];
const interval = resultTickInterval[2];
const weekday = diagObj.db.getWeekday() || conf.weekday;
// Get the time domain to check tick count
const domain = timeScale.domain();
const minTime = domain[0];
const maxTime = domain[1];
const estimatedTicks = getEstimatedTickCount(minTime, maxTime, every, interval);
// Only apply custom ticks if the count is reasonable
if (estimatedTicks <= MAX_TICK_COUNT) {
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;
case 'hour':
topXAxis.ticks(timeHour.every(every));
break;
case 'day':
topXAxis.ticks(timeDay.every(every));
break;
case 'week':
topXAxis.ticks(mapWeekdayToTimeFunction[weekday].every(every));
break;
case 'month':
topXAxis.ticks(timeMonth.every(every));
break;
}
}
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;
case 'hour':
topXAxis.ticks(timeHour.every(every));
break;
case 'day':
topXAxis.ticks(timeDay.every(every));
break;
case 'week':
topXAxis.ticks(mapWeekdayToTimeFunction[weekday].every(every));
break;
case 'month':
topXAxis.ticks(timeMonth.every(every));
break;
}
}

View File

@@ -293,37 +293,5 @@ describe('MindmapDb getData function', () => {
expect(edgeA1_aaa.section).toBe(1);
expect(edgeA_a2.section).toBe(2);
});
it('should wrap section numbers when there are more than 11 level 2 nodes', () => {
db.addNode(0, 'root', 'Example', 0);
for (let i = 1; i <= 15; i++) {
db.addNode(1, `child${i}`, `${i}`, 0);
}
const result = db.getData();
expect(result.nodes).toHaveLength(16);
const child1 = result.nodes.find((n) => n.label === '1') as MindmapLayoutNode;
const child11 = result.nodes.find((n) => n.label === '11') as MindmapLayoutNode;
const child12 = result.nodes.find((n) => n.label === '12') as MindmapLayoutNode;
const child13 = result.nodes.find((n) => n.label === '13') as MindmapLayoutNode;
const child14 = result.nodes.find((n) => n.label === '14') as MindmapLayoutNode;
const child15 = result.nodes.find((n) => n.label === '15') as MindmapLayoutNode;
expect(child1.section).toBe(0);
expect(child11.section).toBe(10);
expect(child12.section).toBe(0);
expect(child13.section).toBe(1);
expect(child14.section).toBe(2);
expect(child15.section).toBe(3);
expect(child12.cssClasses).toBe('mindmap-node section-0');
expect(child13.cssClasses).toBe('mindmap-node section-1');
expect(child14.cssClasses).toBe('mindmap-node section-2');
expect(child15.cssClasses).toBe('mindmap-node section-3');
});
});
});

View File

@@ -7,7 +7,6 @@ import type { MindmapNode } from './mindmapTypes.js';
import defaultConfig from '../../defaultConfig.js';
import type { LayoutData, Node, Edge } from '../../rendering-util/types.js';
import { getUserDefinedConfig } from '../../config.js';
import { MAX_SECTIONS } from './svgDraw.js';
// Extend Node type for mindmap-specific properties
export type MindmapLayoutNode = Node & {
@@ -204,7 +203,7 @@ export class MindmapDB {
// For other nodes, inherit parent's section number
if (node.children) {
for (const [index, child] of node.children.entries()) {
const childSectionNumber = node.level === 0 ? index % (MAX_SECTIONS - 1) : sectionNumber;
const childSectionNumber = node.level === 0 ? index : sectionNumber;
this.assignSections(child, childSectionNumber);
}
}

View File

@@ -5,7 +5,7 @@ import { parseFontSize } from '../../utils.js';
import type { MermaidConfig } from '../../config.type.js';
import type { MindmapDB } from './mindmapDb.js';
export const MAX_SECTIONS = 12;
const MAX_SECTIONS = 12;
type ShapeFunction = (
db: MindmapDB,

View File

@@ -30,7 +30,6 @@
[0-9]+(?=[ \n]+) return 'NUM';
<ID>\@\{ { this.begin('CONFIG'); return 'CONFIG_START'; }
<CONFIG>[^\}]+ { return 'CONFIG_CONTENT'; }
<CONFIG>\}(?=\s+as\s) { this.popState(); this.begin('ALIAS'); return 'CONFIG_END'; }
<CONFIG>\} { this.popState(); this.popState(); return 'CONFIG_END'; }
<ID>[^\<->\->:\n,;@\s]+(?=\@\{) { yytext = yytext.trim(); return 'ACTOR'; }
<ID>[^<>:\n,;@\s]+(?=\s+as\s) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
@@ -265,10 +264,7 @@ participant_statement
| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;}
| 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;}
| 'participant' actor_with_config 'AS' restOfLine 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant'; $2.description=yy.parseMessage($4); $$=$2;}
| 'participant' actor_with_config 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant'; $$=$2;}
| 'participant_actor' actor_with_config 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $2.description=yy.parseMessage($4); $$=$2;}
| 'participant_actor' actor_with_config 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;}
;

View File

@@ -172,12 +172,6 @@ export class SequenceDB implements DiagramDB {
doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as ParticipantMetaData;
}
type = doc?.type ?? type;
// If alias is provided in metadata and description is not already set, use the alias
if (doc?.alias && (!description || description.text === name)) {
description = { text: doc.alias, wrap: description?.wrap, type };
}
const old = this.state.records.actors.get(id);
if (old) {
// If already set and trying to set to a new one throw error

View File

@@ -2621,114 +2621,5 @@ Bob->>Alice:Got it!
}
expect(error).toBe(true);
});
it('should parse participant with stereotype and alias', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Alice@{ "type" : "boundary" } as Public API
participant Bob@{ "type" : "control" } as Controller
Alice->>Bob: Request
Bob-->>Alice: Response
`);
const actors = diagram.db.getActors();
expect(actors.get('Alice').type).toBe('boundary');
expect(actors.get('Alice').description).toBe('Public API');
expect(actors.get('Bob').type).toBe('control');
expect(actors.get('Bob').description).toBe('Controller');
});
it('should parse actor with stereotype and alias', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
actor A@{ "type" : "database" } AS Database Server
actor B@{ "type" : "queue" } as Message Queue
A->>B: Send message
`);
const actors = diagram.db.getActors();
expect(actors.get('A').type).toBe('database');
expect(actors.get('A').description).toBe('Database Server');
expect(actors.get('B').type).toBe('queue');
expect(actors.get('B').description).toBe('Message Queue');
});
it('should parse participant with stereotype and simple alias', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant API@{ "type" : "boundary" } AS Public API
API->>API: test
`);
const actors = diagram.db.getActors();
expect(actors.get('API').type).toBe('boundary');
expect(actors.get('API').description).toBe('Public API');
});
it('should parse participant with inline alias in config object', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant API@{ "type" : "boundary", "alias": "Public API" }
participant Auth@{ "type" : "control", "alias": "Auth Controller" }
API->>Auth: Request
Auth-->>API: Response
`);
const actors = diagram.db.getActors();
expect(actors.get('API').type).toBe('boundary');
expect(actors.get('API').description).toBe('Public API');
expect(actors.get('Auth').type).toBe('control');
expect(actors.get('Auth').description).toBe('Auth Controller');
});
it('should parse actor with inline alias in config object', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
actor U@{ "type" : "actor", "alias": "End User" }
actor DB@{ "type" : "database", "alias": "User Database" }
U->>DB: Query
DB-->>U: Result
`);
const actors = diagram.db.getActors();
expect(actors.get('U').type).toBe('actor');
expect(actors.get('U').description).toBe('End User');
expect(actors.get('DB').type).toBe('database');
expect(actors.get('DB').description).toBe('User Database');
});
it('should prioritize external alias over inline alias', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant API@{ "type" : "boundary", "alias": "Internal Name" } as External Name
API->>API: test
`);
const actors = diagram.db.getActors();
expect(actors.get('API').type).toBe('boundary');
expect(actors.get('API').description).toBe('External Name');
});
it('should handle participant with only inline alias (no type)', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant API@{ "alias": "Public API" }
API->>API: test
`);
const actors = diagram.db.getActors();
expect(actors.get('API').description).toBe('Public API');
});
it('should handle mixed inline and external alias syntax', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant A@{ "type" : "boundary", "alias": "Service A" }
participant B@{ "type" : "control" } as Service B
participant C@{ "type" : "database" }
A->>B: Request
B->>C: Query
`);
const actors = diagram.db.getActors();
expect(actors.get('A').type).toBe('boundary');
expect(actors.get('A').description).toBe('Service A');
expect(actors.get('B').type).toBe('control');
expect(actors.get('B').description).toBe('Service B');
expect(actors.get('C').type).toBe('database');
expect(actors.get('C').description).toBe('C');
});
});
});

View File

@@ -21,7 +21,7 @@ const populate = (ast: TreemapAst, db: TreemapDB) => {
type: string;
value?: number;
classSelector?: string;
cssCompiledStyles?: string[];
cssCompiledStyles?: string;
}[] = [];
// Extract classes and styles from the treemap
@@ -44,7 +44,7 @@ const populate = (ast: TreemapAst, db: TreemapDB) => {
// Get styles as a string if they exist
const styles = item.classSelector ? db.getStylesForClass(item.classSelector) : [];
const cssCompiledStyles = styles.length > 0 ? styles : undefined;
const cssCompiledStyles = styles.length > 0 ? styles.join(';') : undefined;
const itemData = {
level,

View File

@@ -12,7 +12,7 @@ export function buildHierarchy(
type: string;
value?: number;
classSelector?: string;
cssCompiledStyles?: string[];
cssCompiledStyles?: string;
}[]
): TreemapNode[] {
if (!items.length) {
@@ -29,7 +29,7 @@ export function buildHierarchy(
};
node.classSelector = item?.classSelector;
if (item?.cssCompiledStyles) {
node.cssCompiledStyles = item.cssCompiledStyles;
node.cssCompiledStyles = [item.cssCompiledStyles];
}
if (item.type === 'Leaf' && item.value !== undefined) {

View File

@@ -8,7 +8,7 @@ import type { CanonicalUrlConfig } from './canonical-urls.js';
*/
export const canonicalConfig: CanonicalUrlConfig = {
// Base URL for the Mermaid documentation site
baseUrl: 'https://mermaid.ai/open-source',
baseUrl: 'https://docs.mermaidchart.com',
// Disable automatic generation - only use specificCanonicalUrls
autoGenerate: false,
@@ -57,6 +57,93 @@ export const canonicalConfig: CanonicalUrlConfig = {
},
};
/**
* Pages that should have specific canonical URLs
*
* Since autoGenerate is set to false, ONLY pages listed here will get canonical URLs.
*
* Usage: Add entries to this object where the key is the relative path
* of the markdown file and the value is the desired canonical URL.
*
* Examples:
* - 'intro/index.md': 'https://docs.mermaidchart.com/intro/index.html'
* - 'syntax/flowchart.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/flowchart.html'
* - 'config/configuration.md': 'https://docs.mermaidchart.com/mermaid-oss/config/configuration.html'
*/
export const specificCanonicalUrls: Record<string, string> = {
// Add your specific canonical URLs here
// Example:
// 'syntax/flowchart.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/flowchart.html',
// Intro section
'intro/index.md': 'https://docs.mermaidchart.com/intro/index.html',
'intro/getting-started.md':
'https://docs.mermaidchart.com/mermaid-oss/intro/getting-started.html',
'intro/syntax-reference.md':
'https://docs.mermaidchart.com/mermaid-oss/intro/syntax-reference.html',
// Syntax section
'syntax/flowchart.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/flowchart.html',
'syntax/sequenceDiagram.md':
'https://docs.mermaidchart.com/mermaid-oss/syntax/sequenceDiagram.html',
'syntax/classDiagram.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/classDiagram.html',
'syntax/stateDiagram.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/stateDiagram.html',
'syntax/entityRelationshipDiagram.md':
'https://docs.mermaidchart.com/mermaid-oss/syntax/entityRelationshipDiagram.html',
'syntax/userJourney.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/userJourney.html',
'syntax/gantt.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/gantt.html',
'syntax/pie.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/pie.html',
'syntax/quadrantChart.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/quadrantChart.html',
'syntax/requirementDiagram.md':
'https://docs.mermaidchart.com/mermaid-oss/syntax/requirementDiagram.html',
'syntax/mindmap.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/mindmap.html',
'syntax/timeline.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/timeline.html',
'syntax/gitgraph.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/gitgraph.html',
'syntax/c4.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/c4.html',
'syntax/sankey.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/sankey.html',
'syntax/xyChart.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/xyChart.html',
'syntax/block.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/block.html',
'syntax/packet.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/packet.html',
'syntax/kanban.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/kanban.html',
'syntax/architecture.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/architecture.html',
'syntax/radar.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/radar.html',
'syntax/examples.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/examples.html',
// Config section
'config/configuration.md': 'https://docs.mermaidchart.com/mermaid-oss/config/configuration.html',
'config/usage.md': 'https://docs.mermaidchart.com/mermaid-oss/config/usage.html',
'config/icons.md': 'https://docs.mermaidchart.com/mermaid-oss/config/icons.html',
'config/directives.md': 'https://docs.mermaidchart.com/mermaid-oss/config/directives.html',
'config/theming.md': 'https://docs.mermaidchart.com/mermaid-oss/config/theming.html',
'config/math.md': 'https://docs.mermaidchart.com/mermaid-oss/config/math.html',
'config/accessibility.md': 'https://docs.mermaidchart.com/mermaid-oss/config/accessibility.html',
'config/mermaidCLI.md': 'https://docs.mermaidchart.com/mermaid-oss/config/mermaidCLI.html',
'config/faq.md': 'https://docs.mermaidchart.com/mermaid-oss/config/faq.html',
// Ecosystem section
'ecosystem/mermaid-chart.md':
'https://docs.mermaidchart.com/mermaid-oss/ecosystem/mermaid-chart.html',
'ecosystem/tutorials.md': 'https://docs.mermaidchart.com/mermaid-oss/ecosystem/tutorials.html',
'ecosystem/integrations-community.md':
'https://docs.mermaidchart.com/mermaid-oss/ecosystem/integrations-community.html',
'ecosystem/integrations-create.md':
'https://docs.mermaidchart.com/mermaid-oss/ecosystem/integrations-create.html',
// Community section
'community/intro.md': 'https://docs.mermaidchart.com/mermaid-oss/community/intro.html',
'community/contributing.md':
'https://docs.mermaidchart.com/mermaid-oss/community/contributing.html',
'community/new-diagram.md':
'https://docs.mermaidchart.com/mermaid-oss/community/new-diagram.html',
'community/questions-and-suggestions.md':
'https://docs.mermaidchart.com/mermaid-oss/community/questions-and-suggestions.html',
'community/security.md': 'https://docs.mermaidchart.com/mermaid-oss/community/security.html',
};
/**
* Helper function to get canonical URL for a specific page
* This can be used in frontmatter or for manual overrides
*/
export function getCanonicalUrl(relativePath: string): string | undefined {
return `https://mermaid.ai/open-source/${relativePath}`;
return specificCanonicalUrls[relativePath];
}

Some files were not shown because too many files have changed in this diff Show More