diff --git a/package.json b/package.json
index 1b77452d1c..2477232ed9 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,8 @@
"pnpm": {
"patchedDependencies": {
"readable-stream@2.3.8": "patches/readable-stream@2.3.8.patch",
- "comark@0.6.2": "patches/comark@0.6.2.patch"
+ "comark@0.6.2": "patches/comark@0.6.2.patch",
+ "beautiful-mermaid@1.1.3": "patches/beautiful-mermaid@1.1.3.patch"
},
"peerDependencyRules": {
"allowedVersions": {
diff --git a/packages/assets/styles/classes.scss b/packages/assets/styles/classes.scss
index 69c4615458..ab32774ee1 100644
--- a/packages/assets/styles/classes.scss
+++ b/packages/assets/styles/classes.scss
@@ -953,6 +953,18 @@ a:not(.no-click-animation),
}
}
+ .mermaid {
+ @apply bg-surface-2 rounded-xl p-4 border border-solid border-surface-5;
+ margin-top: 1rem;
+ overflow-x: auto;
+
+ svg {
+ width: 100%;
+ max-width: 32rem;
+ height: auto;
+ }
+ }
+
code {
@apply bg-surface-3 rounded-md p-4 border border-solid border-surface-5;
padding: 0.2em 0.4em;
diff --git a/packages/ui/package.json b/packages/ui/package.json
index a1b22d3675..809ad59018 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -74,6 +74,7 @@
"@xterm/xterm": "^6.0.0",
"ace-builds": "^1.43.5",
"apexcharts": "^4.0.0",
+ "beautiful-mermaid": "^1.1.3",
"comark": "^0.6.2",
"dayjs": "^1.11.10",
"dompurify": "^3.1.7",
diff --git a/packages/ui/src/components/base/MarkdownBody.vue b/packages/ui/src/components/base/MarkdownBody.vue
index 18cecec1fa..669a2ba36d 100644
--- a/packages/ui/src/components/base/MarkdownBody.vue
+++ b/packages/ui/src/components/base/MarkdownBody.vue
@@ -35,6 +35,7 @@ import { computed } from 'vue'
import MarkdownAlert, { alertMarkerTypes } from './markdown/MarkdownAlert.vue'
import MarkdownCollectionEmbed from './markdown/MarkdownCollectionEmbed.vue'
import MarkdownHighlightedPre from './markdown/MarkdownHighlightedPre.vue'
+import MarkdownMermaid from './markdown/MarkdownMermaid.vue'
import MarkdownOrganizationEmbed from './markdown/MarkdownOrganizationEmbed.vue'
import MarkdownProjectEmbed from './markdown/MarkdownProjectEmbed.vue'
import MarkdownTaskCheckbox from './markdown/MarkdownTaskCheckbox.vue'
@@ -84,6 +85,7 @@ const components = computed(() =>
organization: MarkdownOrganizationEmbed,
collection: MarkdownCollectionEmbed,
input: MarkdownTaskCheckbox,
+ mermaid: MarkdownMermaid,
...(props.highlight ? { pre: MarkdownHighlightedPre } : {}),
},
)
diff --git a/packages/ui/src/components/base/markdown/MarkdownMermaid.vue b/packages/ui/src/components/base/markdown/MarkdownMermaid.vue
new file mode 100644
index 0000000000..a348499408
--- /dev/null
+++ b/packages/ui/src/components/base/markdown/MarkdownMermaid.vue
@@ -0,0 +1,29 @@
+
+
+
+
+
diff --git a/packages/utils/markdown/parse.ts b/packages/utils/markdown/parse.ts
index 6e8693a11f..c307504252 100644
--- a/packages/utils/markdown/parse.ts
+++ b/packages/utils/markdown/parse.ts
@@ -11,6 +11,7 @@ import components from 'comark/plugins/components'
import emoji from 'comark/plugins/emoji'
import footnotes from 'comark/plugins/footnotes'
import html from 'comark/plugins/html'
+import mermaid from 'comark/plugins/mermaid'
import punctuation from 'comark/plugins/punctuation'
import taskList from 'comark/plugins/task-list'
import { visitAsync } from 'comark/utils'
@@ -48,7 +49,7 @@ export const modrinthPlugins: ComarkPlugin[] = [
modrinthHtmlBlock(),
// json-render()
// math(), // needs dep
- // mermaid(), // needs dep
+ mermaid(),
punctuation(),
// rangi() // needs dep + we have highlight.js + if we do want to switch I'd say shiki is better
modrinthEmbedSyntax(),
diff --git a/packages/utils/markdown/security.ts b/packages/utils/markdown/security.ts
index f757399c22..748a947fbd 100644
--- a/packages/utils/markdown/security.ts
+++ b/packages/utils/markdown/security.ts
@@ -93,6 +93,7 @@ export const attributeAllowlist: Record = {
li: ['class'],
map: ['name'],
mark: [],
+ mermaid: ['content', 'theme', 'theme-dark', 'class'],
nav: [],
ol: [],
organization: ['id'],
diff --git a/patches/beautiful-mermaid@1.1.3.patch b/patches/beautiful-mermaid@1.1.3.patch
new file mode 100644
index 0000000000..8f464a4444
--- /dev/null
+++ b/patches/beautiful-mermaid@1.1.3.patch
@@ -0,0 +1,56 @@
+diff --git a/dist/index.js b/dist/index.js
+index e7e087bcb6f29d0d8f36cd558e0183bc0f162fcb..500c550c2723be4e84775afbb680dc1e06351451 100644
+--- a/dist/index.js
++++ b/dist/index.js
+@@ -6985,14 +6985,18 @@ function markerSuffix(color) {
+ function renderGroup(group, font) {
+ const headerHeight = FONT_SIZES.groupHeader + 16;
+ const parts = [];
++ const clipId = `group-clip-${group.id.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
++ parts.push(
++ ``
++ );
+ parts.push(
+ ``
+ );
+ parts.push(
+- ` `
++ ` `
+ );
+ parts.push(
+- ` `
++ ` `
+ );
+ parts.push(
+ " " + renderMultilineText(
+@@ -7134,10 +7138,10 @@ function renderNodeShape(node) {
+ }
+ }
+ function renderRect(x, y, w, h, fill, stroke, sw) {
+- return ``;
++ return ``;
+ }
+ function renderRoundedRect(x, y, w, h, fill, stroke, sw) {
+- return ``;
++ return ``;
+ }
+ function renderStadium(x, y, w, h, fill, stroke, sw) {
+ const r2 = h / 2;
+@@ -7168,7 +7172,7 @@ function renderDiamond(x, y, w, h, fill, stroke, sw) {
+ }
+ function renderSubroutine(x, y, w, h, fill, stroke, sw) {
+ const inset = 8;
+- return `
++ return `
+
+ `;
+ }
+@@ -7645,7 +7649,7 @@ function renderActor(actor) {
+ } else {
+ const boxX = x - width / 2;
+ parts.push(
+- ` `
++ ` `
+ );
+ parts.push(
+ " " + renderMultilineText(
diff --git a/patches/comark@0.6.2.patch b/patches/comark@0.6.2.patch
index f8053d6cb6..4cc3ed7bb8 100644
--- a/patches/comark@0.6.2.patch
+++ b/patches/comark@0.6.2.patch
@@ -1,8 +1,8 @@
diff --git a/dist/internal/parse/token-processor.js b/dist/internal/parse/token-processor.js
-index 440841f8d9252067681f09df935dd0287fff2cf0..71c44f24a932a1f900329800f28029206aa5a13f 100644
+index 440841f8d9252067681f09df935dd0287fff2cf0..e71c6f763d8520c53d7f7d8a1e9bcdc5e29f0526 100644
--- a/dist/internal/parse/token-processor.js
+++ b/dist/internal/parse/token-processor.js
-@@ -483,6 +483,20 @@ function processBlockChildren(tokens, startIndex, closeType, inlineOnly, inHeadi
+@@ -483,6 +483,16 @@ function processBlockChildren(tokens, startIndex, closeType, inlineOnly, inHeadi
}
i++;
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7ce89af57f..3137a64ff9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -5,8 +5,11 @@ settings:
excludeLinksFromLockfile: false
patchedDependencies:
+ beautiful-mermaid@1.1.3:
+ hash: 51834efb99f2945be29ce3a3cbc45a3b9ff4cde6ca80ba6d2c22e85d9b9cbd47
+ path: patches/beautiful-mermaid@1.1.3.patch
comark@0.6.2:
- hash: 4e73fba3ed127a788219ec2e7a413966f48fc7695802a1b0f2694ef2686d475c
+ hash: c8af805d46dd2a152fda357d551ed56a0a876f61520b78ad6fe250b82afd17f8
path: patches/comark@0.6.2.patch
readable-stream@2.3.8:
hash: 3045f7adf989b4e668a4a13819b7285b0de186b79bbf22408acaf2a41c6eaa86
@@ -654,7 +657,7 @@ importers:
version: 6.39.12
'@comark/vue':
specifier: ^0.6.2
- version: 0.6.2(vue@3.5.27(typescript@5.9.3))
+ version: 0.6.2(beautiful-mermaid@1.1.3(patch_hash=51834efb99f2945be29ce3a3cbc45a3b9ff4cde6ca80ba6d2c22e85d9b9cbd47))(vue@3.5.27(typescript@5.9.3))
'@intercom/messenger-js-sdk':
specifier: ^0.0.14
version: 0.0.14
@@ -709,9 +712,12 @@ importers:
apexcharts:
specifier: ^4.0.0
version: 4.7.0
+ beautiful-mermaid:
+ specifier: ^1.1.3
+ version: 1.1.3(patch_hash=51834efb99f2945be29ce3a3cbc45a3b9ff4cde6ca80ba6d2c22e85d9b9cbd47)
comark:
specifier: ^0.6.2
- version: 0.6.2(patch_hash=4e73fba3ed127a788219ec2e7a413966f48fc7695802a1b0f2694ef2686d475c)
+ version: 0.6.2(patch_hash=c8af805d46dd2a152fda357d551ed56a0a876f61520b78ad6fe250b82afd17f8)(beautiful-mermaid@1.1.3(patch_hash=51834efb99f2945be29ce3a3cbc45a3b9ff4cde6ca80ba6d2c22e85d9b9cbd47))
dayjs:
specifier: ^1.11.10
version: 1.11.19
@@ -856,13 +862,13 @@ importers:
version: 6.39.12
'@comark/html':
specifier: ^0.6.2
- version: 0.6.2
+ version: 0.6.2(beautiful-mermaid@1.1.3(patch_hash=51834efb99f2945be29ce3a3cbc45a3b9ff4cde6ca80ba6d2c22e85d9b9cbd47))
'@types/three':
specifier: ^0.172.0
version: 0.172.0
comark:
specifier: ^0.6.2
- version: 0.6.2(patch_hash=4e73fba3ed127a788219ec2e7a413966f48fc7695802a1b0f2694ef2686d475c)
+ version: 0.6.2(patch_hash=c8af805d46dd2a152fda357d551ed56a0a876f61520b78ad6fe250b82afd17f8)(beautiful-mermaid@1.1.3(patch_hash=51834efb99f2945be29ce3a3cbc45a3b9ff4cde6ca80ba6d2c22e85d9b9cbd47))
dayjs:
specifier: ^1.11.10
version: 1.11.19
@@ -5571,6 +5577,9 @@ packages:
bcp-47@2.1.0:
resolution: {integrity: sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==}
+ beautiful-mermaid@1.1.3:
+ resolution: {integrity: sha512-TItrtrAyHp1vwFfFVYauWGrquouk/6SS21Aq3RsxindSYZODcN4xYrPZD6BiZRU+o5mKJzDPz9MUSMvELdylyg==}
+
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
@@ -6242,6 +6251,9 @@ packages:
electron-to-chromium@1.5.286:
resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==}
+ elkjs@0.11.1:
+ resolution: {integrity: sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==}
+
emmet@2.4.11:
resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==}
@@ -11308,16 +11320,20 @@ snapshots:
style-mod: 4.1.3
w3c-keyname: 2.2.8
- '@comark/html@0.6.2':
+ '@comark/html@0.6.2(beautiful-mermaid@1.1.3(patch_hash=51834efb99f2945be29ce3a3cbc45a3b9ff4cde6ca80ba6d2c22e85d9b9cbd47))':
dependencies:
- comark: 0.6.2(patch_hash=4e73fba3ed127a788219ec2e7a413966f48fc7695802a1b0f2694ef2686d475c)
+ comark: 0.6.2(patch_hash=c8af805d46dd2a152fda357d551ed56a0a876f61520b78ad6fe250b82afd17f8)(beautiful-mermaid@1.1.3(patch_hash=51834efb99f2945be29ce3a3cbc45a3b9ff4cde6ca80ba6d2c22e85d9b9cbd47))
+ optionalDependencies:
+ beautiful-mermaid: 1.1.3(patch_hash=51834efb99f2945be29ce3a3cbc45a3b9ff4cde6ca80ba6d2c22e85d9b9cbd47)
transitivePeerDependencies:
- rangi
- '@comark/vue@0.6.2(vue@3.5.27(typescript@5.9.3))':
+ '@comark/vue@0.6.2(beautiful-mermaid@1.1.3(patch_hash=51834efb99f2945be29ce3a3cbc45a3b9ff4cde6ca80ba6d2c22e85d9b9cbd47))(vue@3.5.27(typescript@5.9.3))':
dependencies:
- comark: 0.6.2(patch_hash=4e73fba3ed127a788219ec2e7a413966f48fc7695802a1b0f2694ef2686d475c)
+ comark: 0.6.2(patch_hash=c8af805d46dd2a152fda357d551ed56a0a876f61520b78ad6fe250b82afd17f8)(beautiful-mermaid@1.1.3(patch_hash=51834efb99f2945be29ce3a3cbc45a3b9ff4cde6ca80ba6d2c22e85d9b9cbd47))
vue: 3.5.27(typescript@5.9.3)
+ optionalDependencies:
+ beautiful-mermaid: 1.1.3(patch_hash=51834efb99f2945be29ce3a3cbc45a3b9ff4cde6ca80ba6d2c22e85d9b9cbd47)
transitivePeerDependencies:
- rangi
@@ -15692,6 +15708,11 @@ snapshots:
is-alphanumerical: 2.0.1
is-decimal: 2.0.1
+ beautiful-mermaid@1.1.3(patch_hash=51834efb99f2945be29ce3a3cbc45a3b9ff4cde6ca80ba6d2c22e85d9b9cbd47):
+ dependencies:
+ elkjs: 0.11.1
+ entities: 7.0.1
+
binary-extensions@2.3.0: {}
bindings@1.5.0:
@@ -15951,12 +15972,14 @@ snapshots:
colord@2.9.3: {}
- comark@0.6.2(patch_hash=4e73fba3ed127a788219ec2e7a413966f48fc7695802a1b0f2694ef2686d475c):
+ comark@0.6.2(patch_hash=c8af805d46dd2a152fda357d551ed56a0a876f61520b78ad6fe250b82afd17f8)(beautiful-mermaid@1.1.3(patch_hash=51834efb99f2945be29ce3a3cbc45a3b9ff4cde6ca80ba6d2c22e85d9b9cbd47)):
dependencies:
entities: 8.0.0
htmlparser2: 12.0.0
js-yaml: 5.3.0
markdown-exit: 1.1.0-beta.2
+ optionalDependencies:
+ beautiful-mermaid: 1.1.3(patch_hash=51834efb99f2945be29ce3a3cbc45a3b9ff4cde6ca80ba6d2c22e85d9b9cbd47)
combined-stream@1.0.8:
dependencies:
@@ -16323,6 +16346,8 @@ snapshots:
electron-to-chromium@1.5.286: {}
+ elkjs@0.11.1: {}
+
emmet@2.4.11:
dependencies:
'@emmetio/abbreviation': 2.3.3