diff --git a/packages/example/articles/example.md b/packages/example/articles/example.md
index de9b4bc1..7722231d 100644
--- a/packages/example/articles/example.md
+++ b/packages/example/articles/example.md
@@ -135,4 +135,193 @@ $a^*b$ with $a^*$
$\sum_{i=1}^n$
fafa
-a
\ No newline at end of file
+a
+
+
+
+## mermaid.js
+
+```mermaid
+%%{init: { 'theme': 'forest' } }%%
+graph LR;
+ A-->B & C-->D & E-->F & Z-->X;
+ F-->G
+ G-->H
+ H-->I
+ I-->J
+ J-->K
+ K-->L
+ L-->M
+ M-->N
+ N-->O
+ O-->P
+ P-->ID1[ノード1
+ mermaidをレンダリングできません。
+
ノード2]
+```
+
+### flowchart
+
+```mermaid
+flowchart TB
+ c1-->a2
+ subgraph one
+ a1-->a2
+ end
+ subgraph two
+ b1-->b2
+ end
+ subgraph three
+ c1-->c2
+ end
+ one --> two
+ three --> two
+ two --> c2
+```
+
+### sequence diagram
+
+```mermaid
+sequenceDiagram
+ autonumber
+ アリス->>光輝: Hello John, how are you?
+ loop Healthcheck
+ 光輝->>光輝: Fight against hypochondria
+ end
+ Note right of 光輝: Rational thoughts!
+ 光輝-->>アリス: Great!
+ 光輝->>Bob: How about you?
+ Bob-->>光輝: Jolly good!
+```
+
+### class diagram
+
+```mermaid
+ classDiagram
+ Animal <|-- Duck
+ Animal <|-- Fish
+ Animal <|-- Zebra
+ Animal : +int age
+ Animal : +String gender
+ Animal: +isMammal()
+ Animal: +mate()
+ class Duck{
+ +String beakColor
+ +swim()
+ +quack()
+ }
+ class Fish{
+ -int sizeInFeet
+ -canEat()
+ }
+ class Zebra{
+ +bool is_wild
+ +run()
+ }
+```
+
+
+### state diagram
+
+```mermaid
+stateDiagram-v2
+ [*] --> Active
+
+ state Active {
+ [*] --> NumLockOff
+ NumLockOff --> NumLockOn : EvNumLockPressed
+ NumLockOn --> NumLockOff : EvNumLockPressed
+ --
+ [*] --> CapsLockOff
+ CapsLockOff --> CapsLockOn : EvCapsLockPressed
+ CapsLockOn --> CapsLockOff : EvCapsLockPressed
+ --
+ [*] --> ScrollLockOff
+ ScrollLockOff --> ScrollLockOn : EvScrollLockPressed
+ ScrollLockOn --> ScrollLockOff : EvScrollLockPressed
+
+ }
+```
+
+
+
+```mermaid
+graph LR
+A:::someclass B
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+classDef someclass fill:#f96;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+ A-->B & C-->D & E-->F & Z-->X;
+```
+
+
+```mermaid
+graph LR;
+ A[""] --> B;
+ alert`md5_salt`-->B;
+ click alert`md5_salt` eval "Tooltip for a callback"
+ click B "javascript:alert('XSS')" "This is a tooltip for a link"
+```
+
+```mermaid
+graph LR;
+ alert`md5_salt`-->B;
+ click alert`md5_salt` eval "Tooltip for a callback"
+ click B "javascript:alert('XSS')" "This is a tooltip for a link"
+ link Zebra "http://www.github.com" "This is a link"
+```
diff --git a/packages/zenn-embed-elements/src/classes/mermaid.ts b/packages/zenn-embed-elements/src/classes/mermaid.ts
new file mode 100644
index 00000000..3703252b
--- /dev/null
+++ b/packages/zenn-embed-elements/src/classes/mermaid.ts
@@ -0,0 +1,164 @@
+/**
+ * original: https://github.com/gitlabhq/gitlabhq/blob/master/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+ */
+
+import { loadScript } from '../utils/load-script';
+
+// レンダリングする図ごとの最大文字数
+const MAX_CHAR_LIMIT = 2000;
+
+// https://mermaid-js.github.io/mermaid/#/flowchart?id=chaining-of-links
+// 新しい仕様で
+// graph LR
+// a --> b & c--> d
+// に対応するが、少ない記述でノード接続が爆発する可能性があるため最大数を制限する
+const MAX_CHAINING_OF_LINKS_LIMIT = 10;
+
+// Page values
+declare let mermaid: any;
+const containerId = 'mermaid-container';
+
+async function initMermaid(): Promise
+ ${risk.syntaxError.yes ? risk.syntaxError.message : ''}
+ ${risk.charLimitOver.yes ? risk.charLimitOver.message : ''}
+ ${risk.chainingOfLinksOver.yes ? risk.chainingOfLinksOver.message : ''}
+
+
'
);
});
+ test('should escape img tag around mermaid syntax', () => {
+ const html = markdownToHtml(
+ `\`\`\`mermaid\ngraph TD\nA[""] --> B\`\`\``
+ );
+ expect(html).toContain(
+ '
'
+ );
+ });
});
diff --git a/packages/zenn-markdown-html/jest.config.js b/packages/zenn-markdown-html/jest.config.js
index 4805cba6..c42bb78c 100644
--- a/packages/zenn-markdown-html/jest.config.js
+++ b/packages/zenn-markdown-html/jest.config.js
@@ -2,7 +2,7 @@ module.exports = {
globals: {
'ts-jest': {
// avoid "jsx" treated as "preserved"
- tsConfig: 'tsconfig.json',
+ tsconfig: 'tsconfig.json',
},
},
moduleFileExtensions: ['js', 'json', 'ts'],
diff --git a/packages/zenn-markdown-html/src/index.ts b/packages/zenn-markdown-html/src/index.ts
index 4c91eb16..ffd1ac13 100644
--- a/packages/zenn-markdown-html/src/index.ts
+++ b/packages/zenn-markdown-html/src/index.ts
@@ -14,6 +14,7 @@ import { mdBr } from './utils/md-br';
import { mdCustomBlock } from './utils/md-custom-block';
import markdownItImSize from '@steelydylan/markdown-it-imsize';
import markdownItAnchor from 'markdown-it-anchor';
+import { mdMermaid } from './utils/md-mermaid';
const mdContainer = require('markdown-it-container');
const mdFootnote = require('markdown-it-footnote');
@@ -52,7 +53,8 @@ md.use(mdBr)
},
})
.use(mdKatex)
- .use(mdLinkifyToCard);
+ .use(mdLinkifyToCard)
+ .use(mdMermaid);
// custom footnote => TODO: ファイルを分ける
md.renderer.rules.footnote_block_open = () =>
diff --git a/packages/zenn-markdown-html/src/utils/md-mermaid.ts b/packages/zenn-markdown-html/src/utils/md-mermaid.ts
new file mode 100644
index 00000000..242d0d0e
--- /dev/null
+++ b/packages/zenn-markdown-html/src/utils/md-mermaid.ts
@@ -0,0 +1,19 @@
+import MarkdownIt from 'markdown-it';
+
+export function mdMermaid(md: MarkdownIt) {
+ const defaultRender =
+ md.renderer.rules.fence ||
+ function (tokens, idx, options, env, self) {
+ return self.renderToken(tokens, idx, options);
+ };
+ md.renderer.rules.fence = (tokens, idx, options, env, slf) => {
+ const langInfo = tokens[idx];
+ if (langInfo.info === 'mermaid') {
+ const code = langInfo.content.trim();
+ return ` `;
+ }
+ return defaultRender(tokens, idx, options, env, slf);
+ };
+}