Compare commits

...

9 Commits

Author SHA1 Message Date
Sidharth Vinod
ccfd40ec79 Merge branch 'master' into sidv/pricingTiers
* master:
  fix: Upgrade npm to v11 to support trusted publishing
  Version Packages
  Merge pull request #7197 from mermaid-js/fix/5496-gantt-tickinterval-app-crash
2025-12-04 16:57:22 +05:30
Sidharth Vinod
0b1ee233f9 chore: Update plan in editor selection modal 2025-12-04 16:56:57 +05:30
Ashish Jain
019ee818db Merge pull request #7181 from mermaid-js/sidv/pricingTiers
docs: Update to new pricing tiers
2025-12-03 14:28:18 +01:00
Sidharth Vinod
bd85b51e24 fix: Upgrade npm to v11 to support trusted publishing 2025-12-02 21:56:59 +05:30
Sidharth Vinod
939aff91d6 Merge pull request #7201 from mermaid-js/changeset-release/master
Version Packages
2025-12-01 11:41:14 +05:30
github-actions[bot]
e72b26c16f Version Packages 2025-11-27 20:15:37 +00:00
Sidharth Vinod
4bcb8f0313 Merge pull request #7200 from mermaid-js/pre-release
Pre Release
2025-11-28 05:12:55 +09:00
Shubham P
de7ed10339 Merge pull request #7197 from mermaid-js/fix/5496-gantt-tickinterval-app-crash
5496 : fixed UI crash from excessive tick generation with invalid dates/intervals
2025-11-27 22:56:12 +05:30
Sidharth Vinod
c0b17f2a10 docs: Update to new pricing tiers 2025-11-21 12:31:15 +05:30
12 changed files with 218 additions and 80 deletions

View File

@@ -32,7 +32,9 @@ jobs:
node-version-file: '.node-version' node-version-file: '.node-version'
- name: Install Packages - name: Install Packages
run: pnpm install --frozen-lockfile run: |
pnpm install --frozen-lockfile
npm install -g npm@11
- name: Create Release Pull Request or Publish to npm - name: Create Release Pull Request or Publish to npm
id: changesets id: changesets

View File

@@ -833,4 +833,34 @@ 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

@@ -47,16 +47,18 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
## Plans ## Plans
- **Free** - A free plan that includes six diagrams. - **Basic** - A free plan that includes three diagrams and limited AI usage.
- **Pro** - A paid plan that includes unlimited diagrams, access to the collaboration feature, and more. - **Plus** - A paid plan that includes unlimited diagrams, scalable AI usage, and centralized billing for small teams.
- **Enterprise** - A paid plan for enterprise use that includes all Pro features, and more. - **Premium** - A paid plan for larger teams that need massive AI or diagram usage along with team collaboration, knowledge management, and security features like SSO.
To learn more, visit our [Pricing](https://mermaidchart.com/pricing) page. - **Enterprise** - A paid plan for enterprise use that includes all Premium features, custom contracts, support, and more.
Mermaid Chart is currently offering a 7-day free trial on our Pro and Enterprise tiers. Sign up for a free account at [Mermaid Chart](https://www.mermaidchart.com/app/sign-up). To learn more, visit our [Pricing](https://www.mermaidchart.com/pricing) page.
Sign up for a free account on the [Mermaid](https://www.mermaidchart.com/app/sign-up) website and trial paid features at any time for 7 days free.
## Mermaid JS contributions ## Mermaid JS contributions
First time contributors are eligible for a free Pro tier account for 1 year. First time contributors are eligible for a free Premium tier account for 1 year.

View File

@@ -1,5 +1,11 @@
# mermaid # mermaid
## 11.12.2
### Patch Changes
- [#7200](https://github.com/mermaid-js/mermaid/pull/7200) [`de7ed10`](https://github.com/mermaid-js/mermaid/commit/de7ed1033996d702e3983dcf8114f33faea89577) Thanks [@shubhamparikh2704](https://github.com/shubhamparikh2704)! - fix: validate dates and tick interval to prevent UI freeze/crash in gantt diagramtype
## 11.12.1 ## 11.12.1
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "mermaid", "name": "mermaid",
"version": "11.12.1", "version": "11.12.2",
"description": "Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.", "description": "Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.",
"type": "module", "type": "module",
"module": "./dist/mermaid.core.mjs", "module": "./dist/mermaid.core.mjs",

View File

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

View File

@@ -505,4 +505,27 @@ describe('when using the ganttDb', function () {
ganttDb.addTask('test1', 'id1,202304,1d'); ganttDb.addTask('test1', 'id1,202304,1d');
expect(() => ganttDb.getTasks()).toThrowError('Invalid date:202304'); 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,4 +1,5 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import dayjsDuration from 'dayjs/plugin/duration.js';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import { import {
select, select,
@@ -28,6 +29,8 @@ import common from '../common/common.js';
import { getConfig } from '../../diagram-api/diagramAPI.js'; import { getConfig } from '../../diagram-api/diagramAPI.js';
import { configureSvgSize } from '../../setupGraphViewbox.js'; import { configureSvgSize } from '../../setupGraphViewbox.js';
dayjs.extend(dayjsDuration);
export const setConf = function () { export const setConf = function () {
log.debug('Something is calling, setConf, remove the call'); log.debug('Something is calling, setConf, remove the call');
}; };
@@ -78,6 +81,7 @@ const getMaxIntersections = (tasks, orderOffset) => {
}; };
let w; let w;
const MAX_TICK_COUNT = 10000;
export const draw = function (text, id, version, diagObj) { export const draw = function (text, id, version, diagObj) {
const conf = getConfig().gantt; const conf = getConfig().gantt;
@@ -602,6 +606,27 @@ export const draw = function (text, id, version, diagObj) {
.attr('class', 'exclude-range'); .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 theSidePad
* @param theTopPad * @param theTopPad
@@ -630,10 +655,30 @@ export const draw = function (text, id, version, diagObj) {
); );
if (resultTickInterval !== null) { if (resultTickInterval !== null) {
const every = resultTickInterval[1]; 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 interval = resultTickInterval[2];
const weekday = diagObj.db.getWeekday() || conf.weekday; 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) { switch (interval) {
case 'millisecond': case 'millisecond':
bottomXAxis.ticks(timeMillisecond.every(every)); bottomXAxis.ticks(timeMillisecond.every(every));
@@ -658,6 +703,8 @@ export const draw = function (text, id, version, diagObj) {
break; break;
} }
} }
}
}
svg svg
.append('g') .append('g')
@@ -677,10 +724,24 @@ export const draw = function (text, id, version, diagObj) {
.tickFormat(timeFormat(axisFormat)); .tickFormat(timeFormat(axisFormat));
if (resultTickInterval !== null) { if (resultTickInterval !== null) {
const every = resultTickInterval[1]; 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 interval = resultTickInterval[2];
const weekday = diagObj.db.getWeekday() || conf.weekday; 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) { switch (interval) {
case 'millisecond': case 'millisecond':
topXAxis.ticks(timeMillisecond.every(every)); topXAxis.ticks(timeMillisecond.every(every));
@@ -705,6 +766,8 @@ export const draw = function (text, id, version, diagObj) {
break; break;
} }
} }
}
}
svg svg
.append('g') .append('g')

View File

@@ -20,17 +20,11 @@ interface EditorColumn {
} }
const mermaidChartFeatures: Feature[] = [ const mermaidChartFeatures: Feature[] = [
{ iconUrl: '/icons/folder.svg', featureName: 'Storage' }, { iconUrl: '/icons/whiteboard.svg', featureName: 'Visual editor' },
{ iconUrl: '/icons/terminal.svg', featureName: 'Code editor' }, { iconUrl: '/icons/ai-diagram.svg', featureName: '300 AI credits' },
{ iconUrl: '/icons/ai-diagram.svg', featureName: 'AI diagram generator' }, { iconUrl: '/icons/folder.svg', featureName: 'Unlimited diagram storage' },
{ iconUrl: '/icons/whiteboard.svg', featureName: 'Whiteboard' }, { iconUrl: '/icons/presentation.svg', featureName: 'Limitless diagram size' },
{ iconUrl: '/icons/group.svg', featureName: 'Teams' }, { iconUrl: '/icons/comment.svg', featureName: 'View & comment collaboration' },
{ iconUrl: '/icons/groups.svg', featureName: 'Multi-user editing' },
{ iconUrl: '/icons/ai-repair.svg', featureName: 'AI diagram repair' },
{ iconUrl: '/icons/version-history.svg', featureName: 'Version history' },
{ iconUrl: '/icons/comment.svg', featureName: 'Comments' },
{ iconUrl: '/icons/presentation.svg', featureName: 'Presentations' },
{ iconUrl: '/icons/plugins.svg', featureName: 'Advanced plugins' },
]; ];
const openSourceFeatures: Feature[] = [ const openSourceFeatures: Feature[] = [
@@ -42,8 +36,8 @@ const openSourceFeatures: Feature[] = [
const editorColumns: EditorColumn[] = [ const editorColumns: EditorColumn[] = [
{ {
title: 'Mermaid Pro', title: 'Mermaid Plus',
description: 'Unlock AI and real-time collaboration', description: 'Unlock AI, storage and collaboration',
highlighted: true, highlighted: true,
redBarText: 'Recommended', redBarText: 'Recommended',
proTrialButtonText: 'Start free trial', proTrialButtonText: 'Start free trial',

View File

@@ -41,16 +41,18 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
## Plans ## Plans
- **Free** - A free plan that includes six diagrams. - **Basic** - A free plan that includes three diagrams and limited AI usage.
- **Pro** - A paid plan that includes unlimited diagrams, access to the collaboration feature, and more. - **Plus** - A paid plan that includes unlimited diagrams, scalable AI usage, and centralized billing for small teams.
- **Enterprise** - A paid plan for enterprise use that includes all Pro features, and more. - **Premium** - A paid plan for larger teams that need massive AI or diagram usage along with team collaboration, knowledge management, and security features like SSO.
To learn more, visit our [Pricing](https://mermaidchart.com/pricing) page. - **Enterprise** - A paid plan for enterprise use that includes all Premium features, custom contracts, support, and more.
Mermaid Chart is currently offering a 7-day free trial on our Pro and Enterprise tiers. Sign up for a free account at [Mermaid Chart](https://www.mermaidchart.com/app/sign-up). To learn more, visit our [Pricing](https://www.mermaidchart.com/pricing) page.
Sign up for a free account on the [Mermaid](https://www.mermaidchart.com/app/sign-up) website and trial paid features at any time for 7 days free.
## Mermaid JS contributions ## Mermaid JS contributions
First time contributors are eligible for a free Pro tier account for 1 year. First time contributors are eligible for a free Premium tier account for 1 year.

View File

@@ -1,5 +1,11 @@
# mermaid # mermaid
## 11.12.2
### Patch Changes
- [#7200](https://github.com/mermaid-js/mermaid/pull/7200) [`de7ed10`](https://github.com/mermaid-js/mermaid/commit/de7ed1033996d702e3983dcf8114f33faea89577) Thanks [@shubhamparikh2704](https://github.com/shubhamparikh2704)! - fix: validate dates and tick interval to prevent UI freeze/crash in gantt diagramtype
## 11.12.1 ## 11.12.1
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@mermaid-js/tiny", "name": "@mermaid-js/tiny",
"version": "11.12.1", "version": "11.12.2",
"description": "Tiny version of mermaid", "description": "Tiny version of mermaid",
"type": "commonjs", "type": "commonjs",
"main": "./dist/mermaid.tiny.js", "main": "./dist/mermaid.tiny.js",