first commit

This commit is contained in:
CrazyJasonwell
2026-07-23 20:36:13 +08:00
commit 513b68be8c
7915 changed files with 2642530 additions and 0 deletions
@@ -0,0 +1,20 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"command": "npm run demo",
"name": "Run npm demo",
"request": "launch",
"type": "node-terminal"
},
{
"command": "npm run test",
"name": "Run npm test",
"request": "launch",
"type": "node-terminal"
}
]
}
@@ -0,0 +1,3 @@
{
"mocha.files.glob": "tests/**/*.test.js"
}
@@ -0,0 +1,46 @@
# CHANGELOG
## v1.3.2
* refactor url parser.
* **remark**: throw error on calling `use()` with invalid option (invalid `entrypoint` parameter).
## v1.3.1
* update development packages
* `@marp-team/marp-cli`, `jsdom`, `markdown-it`, `mocha`
## v1.3.0
* support `render` option see [this issue](https://github.com/kazumatu981/markdown-it-kroki/issues/4) and [this memo](memo/howToWriteRender.md)
* *for developers* support coverage test script `test:coverage`.
## v1.2.3
* support `d2`, see [this issue](https://github.com/kazumatu981/markdown-it-kroki/issues/8)
## v1.2.2
* fix readme, see [this pull-request](https://github.com/kazumatu981/markdown-it-kroki/pull/6)
* support `dbml`, see [this issue](https://github.com/kazumatu981/markdown-it-kroki/issues/5)
## v1.2.1
* Embed `<embed>` tag instead of `<img>` tag, see [this issue](https://github.com/kazumatu981/markdown-it-kroki/issues/2).
* `<img>` will be used for **compatibility**, so set option `useImg` to `true` (default `false`).
## v1.1.1
* fix readme, see [this issue](https://github.com/kazumatu981/markdown-it-kroki/issues/1)
## v1.1.0
* Obsolated Option `marpAutoScaling` and detect automatically wether it is nessesury or not.
## v1.0.1
release on npm
## v1.0.0
create new
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Kazuyoshi Matsumoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,157 @@
# markdown-it-kroki
> This library was designed to embed [Kroki.io](https://kroki.io/) diagram into [Marp](https://marp.app/) slide decks!!
This library is a plugin for markdown-it to embed figure created by textual syntax.
To use this package, you can embed **Software Diagram** (like UML) written by **code** in Marp slide decks.
See marp sample code.
## Sample
---
marp: true
---
## plantuml
```plantuml[platuml image]
@startuml
left to right direction
actor Guest as g
package Professional {
actor Chef as c
actor "Food Critic" as fc
}
package Restaurant {
usecase "Eat Food" as UC1
usecase "Pay for Food" as UC2
usecase "Drink" as UC3
usecase "Review" as UC4
}
fc --> UC4
g --> UC1
g --> UC2
g --> UC3
@enduml
```
![plantuml-sample](img/plantuml-sample.png)
If you want to write diagrams, you can use various syntaxes (such as [PlantUML](https://plantuml.com/) or [Mermaid](https://mermaid-js.github.io/mermaid/#/)) within fenced code blocks.
## How to install
You can install `npm install` command like below.
```bash
npm install -D @kazumatu981/markdown-it-kroki
```
## How to use
Here is the how to use `markdow-it-kroki`.
This section introduces how to create Marp slides deck project,
and introduces how to create Marp slides deck server.
You can find detail [here](https://marp.app/),
and you can learn about marp plugin ecosystem [here](https://marpit.marp.app/usage?id=extend-marpit-by-plugins).
### **[1st step]** Create slides deck project
First, to create a slides deck, you have to create a **Marp Project** directory and initialize a npm project.
```bash
mkdir myslides
cd myslides
npm init -y
```
Then, build a Marp environment.
Install [@marp-team/marp-cli](https://github.com/marp-team/marp-cli).
```bash
npm install -D @marp-team/marp-cli
```
> Off-course you can install as **global package** (like `npm install -g @marp-team/marp-cli`), or **run at-once** (like `npx`).
### **[2nd step]** Download this project and install
```bash
git clone https://github.com/kazumatu981/markdown-it-kroki.git
cd myslides
npm install -D path/to/markdown-it-kroki
```
### **[3rd step]** Create `marp.config.js`.
Here is the configuration file for **Marp**.
```javascript
module.exports = {
inputDir: './slides',
engine: ({ marp }) => marp.use(require('@kazumatu981/markdown-it-kroki'), {
entrypoint: "https://kroki.io",
})
}
```
### **[4th step]** Create your slides
On `slies` directory. you create slides-deck. like this.
---
marp: true
---
## mermaid
```mermaid[mermaid image]
flowchart TD
Start --> Stop
```
### **[5th step]** run server
Run marp server.
```bash
marp -s -c marp.config.js
```
## Detail
### Syntax of Markdown
#### Diagram Language
You have to write diagram language by [fenced code block](https://spec.commonmark.org/0.30/#fenced-code-blocks) syntax, start with **triple back quot** and after that the language.
```plantuml
> [!NOTE]
> This package depends on [Kroki.io](https://kroki.io).
> If you want to know which **diagram languages are supported**,
> please check the official website at [https://kroki.io/](https://kroki.io/).
#### Alt Text
You can write Alt-text attribute to IMG tag in HTML.
Write in `square blacket` after **Diagram Language**.
```mermaid [check your network config..]
### Options of `constructor`
| property-name | type | mean | defaul value |
| ---------------- | -------- | ------------------------------------------------------ | ------------------------- |
| `entrypoint` | `string` | The entry point for Kroki server. | `'https://kroki.io'` |
| `containerClass` | `string` | class name of container (`DIV`-tag `class` attribute). | `'kroki-image-container'` |
| `imageFormat` | `string` | image format of diagram. see [here](https://kroki.io/) | `'svg'` |
| `useImg` | `boolean` | use `<img>` tag for embed diagram. ( *for compatibility for past version* ) | `false` |
| `render` | `function` | if you render `html` yourself use this option. see [How To Write `render()` call back](memo/howToWriteRender.md) | `undefined` |
@@ -0,0 +1,8 @@
module.exports = {
inputDir: './',
engine: ({ marp }) => marp.use(require('../index'), {
entrypoint: "https://kroki.io",
marpAutoScaling: true
})
}
@@ -0,0 +1,99 @@
---
marp: true
---
# @kazumatu981/markdown-it-kroki
## Marp Sample
---
## plantuml
```plantuml[platuml image]
@startuml
left to right direction
actor Guest as g
package Professional {
actor Chef as c
actor "Food Critic" as fc
}
package Restaurant {
usecase "Eat Food" as UC1
usecase "Pay for Food" as UC2
usecase "Drink" as UC3
usecase "Review" as UC4
}
fc --> UC4
g --> UC1
g --> UC2
g --> UC3
@enduml
```
---
## plantuml with hyperlink
```plantuml
@startuml
left to right direction
object "[[https://github.com/markdown-it/markdown-it{markdown-it} markdown-it]]" as MarkdownIt
object "[[https://github.com/kazumatu981/markdown-it-kroki{markdown-it-krokii} markdown-it-krokii]]" as MarkdownItKrokii
MarkdownIt --> MarkdownItKrokii
@enduml
```
---
## dbml
```dbml
Table users {
id integer
username varchar
role varchar
created_at timestamp
}
Table posts {
id integer [primary key]
title varchar
body text [note: 'Content of the post']
user_id integer
created_at timestamp
}
Ref: posts.user_id > users.id
```
---
## mermaid
```mermaid[mermaid image]
graph TD
A[ Anyone ] -->|Can help | B( Go to github.com/yuzutech/kroki )
B --> C{ How to contribute? }
C --> D[ Reporting bugs ]
C --> E[ Sharing ideas ]
C --> F[ Advocating ]
```
---
## normal code
```JavaScript
function testFunc(test) {
let sum = 0;
for(let x = 1; x<=test; x++) {
sum += x;
}
return sum;
}
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

@@ -0,0 +1,6 @@
const { MarkdownItKrokiCore } = require('./lib/plugin-core');
module.exports = (md, opt) => {
const plugin = new MarkdownItKrokiCore(md);
plugin.setOptions(opt).use();
};
@@ -0,0 +1,37 @@
'use strict';
module.exports = {
/**
* contract `test` to be non-empty string.
* @param {string} test test string
* @param {string} msg message on exception
*/
toNonEmptyString: function (test, msg) {
if (typeof test !== 'string') throw new Error(msg);
if (test === ''
|| test === null
|| test === undefined) throw new Error(msg);
},
/**
* contract `test` to be true.
* @param {boolean} test test boolean.
* @param {sting} msg massage on excetion.
*/
toTrue: function (test, msg) {
if (typeof test !== 'boolean') throw new Error(msg);
if (!test) throw new Error(msg);
},
toBeUrlString: function (test, msg) {
this.toNonEmptyString(test, msg);
try {
new URL(test);
} catch {
throw new Error(msg);
}
},
toBeClassName: function (test, msg) {
if (!/^[A-Za-z0-9]+(-?[A-Za-z0-9]+)*$/.exec(test)) {
throw new Error(msg);
}
}
};
@@ -0,0 +1,27 @@
'use strict';
const { deflateSync } = require('zlib');
const contract = require('./contract');
const support = require('./support');
function encode(diagram) {
return deflateSync(diagram, { level: 9 }).toString('base64url');
}
function generateUrl(entrypoint, lang, imgType, diagram) {
contract.toNonEmptyString(entrypoint, '\'entrypoint\' must be non-empty string.');
contract.toNonEmptyString(lang, '\'lang\' must be non-empty string.');
contract.toNonEmptyString(imgType, '\'imgType\' must be non-empty string.');
contract.toNonEmptyString(diagram, '\'diagram\' must be non-empty string.');
contract.toTrue(support.languageSupports(lang), 'Not Supported Diagram Language.');
contract.toTrue(support.imageFormatSupports(imgType), 'Not Supported Image Type.');
const api = `${lang}/${imgType}/${encode(diagram)}`;
return entrypoint.endsWith('/') ?
`${entrypoint}${api}` : `${entrypoint}/${api}`;
}
module.exports = {
encode, generateUrl
};
@@ -0,0 +1,52 @@
const { deflateSync } = require('zlib')
const krokiLangs = [
'actdiag',
'blockdiag',
'bpmn',
'bytefield',
'c4plantuml',
'ditaa',
'dot',
'erd',
'excalidraw',
'graphviz',
'mermaid',
'nomnoml',
'nwdiag',
'packetdiag',
'pikchr',
'plantuml',
'rackdiag',
'seqdiag',
'svgbob',
'umlet',
'vega',
'vegalite',
'wavedrom',
]
const entrypoint = 'https://kroki.io/'
const marpKrokiPlugin = (md) => {
const { fence } = md.renderer.rules
md.renderer.rules.fence = (tokens, idx, options, env, self) => {
const info = md.utils.unescapeAll(tokens[idx].info).trim()
if (info) {
const [lang] = info.split(/(\s+)/g)
if (krokiLangs.includes(lang)) {
const data = deflateSync(tokens[idx].content).toString('base64url')
// <marp-auto-scaling> is working only with Marp Core v3
return `<p><marp-auto-scaling data-downscale-only><img src="${entrypoint}${lang}/svg/${data}"/></marp-auto-scaling></p>`
}
}
return fence.call(self, tokens, idx, options, env, self)
}
}
module.exports = marpKrokiPlugin
@@ -0,0 +1,85 @@
'use strict';
const support = require('./support');
const contract = require('./contract');
const { safeProperty, safeChoice } = require('./safe-property');
const diagramEncoder = require('./diagram-encoder');
class MarkdownItKrokiCore {
constructor(md) {
this._md = md;
}
setOptions(opt) {
this._entrypoint = safeProperty(opt, "entrypoint", "string", 'https://kroki.io');
this._containerClass = safeProperty(opt, "containerClass", "string", "kroki-image-container");
this._imageFormat = safeProperty(opt, "imageFormat", "string", "svg");
this._useImg = safeProperty(opt, "useImg", "boolean", false);
this._render = safeProperty(opt, "render", "function", undefined);
this._imageFormat = safeChoice(this._imageFormat, support.imageFormats, "svg");
contract.toBeUrlString(this._entrypoint, "entrypoint must be url string.");
contract.toBeClassName(this._containerClass, "containerClass must be className.");
return this;
}
use() {
// if _md has `marpit` property then use <marp-auto-scaling> tag
this._marpAutoScaling = this._md['marpit'] !== undefined;
this._defaultFence = this._md.renderer.rules.fence;
this._md.renderer.rules.fence
= (tokens, idx, options, env, self) => this.krokiFencePlugin(tokens, idx, options, env, self);
}
static readLanguageAndAltText(info) {
if (!info) return { language: '', alt: '' };
const trimed = info.trim();
const langFound = /[\s|\[]/.exec(trimed);
const altFound = /\[.*?\]/.exec(trimed);
return {
language: langFound ?
trimed.substring(0, langFound.index) : trimed,
alt: altFound ?
altFound[0].replace('[', '').replace(']', '') : ''
};
}
buildEmbedHTML(langAndAlt, diagramCode) {
// alt build url
const url = diagramEncoder.generateUrl(
this._entrypoint, langAndAlt.language, this._imageFormat, diagramCode);
// sanitize alt
const alt = langAndAlt.alt ?
this._md.utils.escapeHtml(langAndAlt.alt) : undefined;
// build img tag
let imgTag;
if(this._render) {
imgTag = this._render(url, alt);
} else if (this._useImg) {
imgTag = langAndAlt.alt ?
`<img alt="${alt}" src="${url}" />` : `<img src="${url}" />`;
} else {
imgTag = langAndAlt.alt ?
`<embed title="${alt}" src="${url}" />` : `<embed src="${url}" />`;
}
// build container contents
const containerContents = this._marpAutoScaling ?
`<marp-auto-scaling data-downscale-only>${imgTag}</marp-auto-scaling>` : imgTag;
// build embed HTML
return `<p class="${this._containerClass}">${containerContents}</p>`;
}
krokiFencePlugin(tokens, idx, options, env, self) {
const info = this._md.utils.unescapeAll(tokens[idx].info)
const langAndAlt = MarkdownItKrokiCore.readLanguageAndAltText(info);
return support.languageSupports(langAndAlt.language) ?
this.buildEmbedHTML(langAndAlt, tokens[idx].content) :
this._defaultFence.call(self, tokens, idx, options, env, self);
}
}
module.exports = {
MarkdownItKrokiCore
}
@@ -0,0 +1,15 @@
'use strict';
function safeProperty(test, name, type, defaultValue) {
if (test == null || test == undefined) return defaultValue;
if (typeof test[name] !== type) return defaultValue;
if (typeof test[name] === "string" && test[name] === '') return defaultValue;
return test[name];
}
function safeChoice(test, candidates, defaultValue) {
return candidates.includes(test) ?
test : defaultValue;
}
module.exports = { safeProperty, safeChoice };
@@ -0,0 +1,56 @@
'use strict';
/**
* Diagram Languages are supported by kroki.io
*/
const LANGUAGES = [
'actdiag',
'blockdiag',
'bpmn',
'bytefield',
'c4plantuml',
'dbml',
'ditaa',
'dot',
'd2',
'erd',
'excalidraw',
'graphviz',
'mermaid',
'nomnoml',
'nwdiag',
'packetdiag',
'pikchr',
'plantuml',
'rackdiag',
'seqdiag',
'svgbob',
'umlet',
'vega',
'vegalite',
'wavedrom',
];
/**
* Image formats are supported by kroki.io
*/
const IMG_FORMATS = [
'png', 'svg', 'jpeg', 'pdf', 'base64'
];
module.exports = {
lnaguages: LANGUAGES,
imageFormats: IMG_FORMATS,
/**
* test whether `lang` is supported diagram language by kroki.io
* @param {string} lang target language
* @returns is supported
*/
languageSupports: (lang) => LANGUAGES.includes(lang),
/**
* test whether `format` is supported image format by kroki.io
* @param {string} format name of image format like 'png', 'svg', ... etc
* @returns is supported
*/
imageFormatSupports: (format) => IMG_FORMATS.includes(format)
};
@@ -0,0 +1,79 @@
# How To Write `render()` call back
If you need to write code to embed into your own `HTML`,
We provide this option.
When you write call back function as `markdown-it`'s option argument object's `render` property,
this plugin call the call-back funtion with argument (encoded url for kroki and alt-text).
## example
For example, if you write call-back and `markdown-it`'s `use()` method, like bellow.
```javascript
const md = require('markdown-it');
const markdownItKroki = require('kazumatu981/markdown-it-kroki');
const urlMap = [];
function myRender(encodedUrl, altText) {
console.log(`URL: ${encodedUrl}`);
console.log(`param: ${altText}`);
urlMap.push({id: altText, url:encodedUrl});
return `<div id=${altText}></div>`
}
md.use(markdownItKroki, {
render: myRender
});
```
and, write markdown like this.
## This is test figure
```plantuml [figure-001]
skinparam ranksep 20
skinparam dpi 125
skinparam packageTitleAlignment left
rectangle "Main" {
(main.view)
(singleton)
}
rectangle "Base" {
(base.component)
(component)
(model)
}
rectangle "<b>main.ts</b>" as main_ts
(component) ..> (base.component)
main_ts ==> (main.view)
(main.view) --> (component)
(main.view) ...> (singleton)
(singleton) ---> (model)
```
then console results are like this.
```text
URL: https://kroki.io/plantuml/svg/eNpljzEPgjAQhff-iguTDFQlcYMmuru5mwNO0tCWhjY6GP-7LRJTdHvv7r67d26QxuKEGiY0gyML5Y65b7GzEvblIalYbAfs6SK9oqOSvdFkPCi6ecYmaj2aXhFkZ5QmgycD2Ogg-V3SI4_OyTjgR5OzVwqc0NECNEHydtR2NGH3TK2dHjtSP3zViPmQd9W2ERmgg-iv3jGW4MC5-L-wTEJdi1XeRENRiFWOtMfnrclriQ5gJD-Z3x9beAM=
param: figure-001
```
## api
```TypeScript
function render(encodedUrl: string, altText: string): string;
```
### arguments
| name | type| value |
| ------------ | -------- | ----- |
| `encodedUrl` | `string` | encoded url (include encoded and compressed textual diagram) for Kroki web service. |
| `altText` | `string` | The text you have written in markdown as alt-text |
### return
The fragment of `html` you want to embed into render result as `string`.
@@ -0,0 +1,37 @@
{
"name": "@kazumatu981/markdown-it-kroki",
"version": "1.3.2",
"description": "markdown-it kroki plugin.",
"main": "index.js",
"scripts": {
"test": "mocha tests/**/*.test.js",
"test:coverage": "nyc --reporter=lcov --reporter=html mocha tests/**/*.test.js",
"demo": "marp -s -c demo/marp.config.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/kazumatu981/markdown-it-kroki.git"
},
"keywords": [
"markdown-it",
"markdown-it-plugin",
"kroki",
"marp",
"markdown"
],
"author": "kazumatu981",
"license": "MIT",
"bugs": {
"url": "https://github.com/kazumatu981/markdown-it-kroki/issues"
},
"homepage": "https://github.com/kazumatu981/markdown-it-kroki#readme",
"devDependencies": {
"@marp-team/marp-cli": "^3.4.0",
"chai": "^4.4.1",
"jsdom": "^24.0.0",
"markdown-it": "^14.0.0",
"mocha": "^10.3.0",
"nyc": "^15.1.0",
"sinon": "^17.0.1"
}
}
@@ -0,0 +1,88 @@
const MarkdownIt = require('markdown-it');
const { expect } = require('chai');
const { JSDOM } = require('jsdom');
const MarkdownItKroki = require('../../index');
describe('# [Security-test] anti-injecttion for syntax.', () => {
describe("## for alt", () => {
it('* escape double quote', () => {
const expected = 'this is a "test comment" test';
const md = new MarkdownIt();
md.use(MarkdownItKroki, {
entrypoint: "https://kroki.io",
marpAutoScaling: true,
containerClass: "the-container"
});
const result = md.render(
'```graphviz [this is a "test comment" test]\r\n' +
'digraph G {Hello->World}\r\n' +
'```\r\n'
);
const dom = new JSDOM(result);
const imgTag = dom.window.document.getElementsByTagName("embed")[0];
const actual = imgTag.getAttribute('title');
expect(actual).to.be.equal(expected);
})
})
});
describe('# [Security-test] anti-injecttion for option.', () => {
describe("## for entrypoint", () => {
it('* deny invalid URL', () => {
const testFunction = () => {
const md = new MarkdownIt();
md.use(MarkdownItKroki, {
entrypoint: "https://kroki.io\"> <script src=\"xxxx.js",
marpAutoScaling: true,
containerClass: "the-container"
});
}
expect(testFunction).to.throw();
})
})
describe("## for containerClass", () => {
it('* throw when containerClass are not alpha, digit, or \"-\"', () => {
const testFunction = () => {
const md = new MarkdownIt();
md.use(MarkdownItKroki, {
entrypoint: "https://kroki.io",
marpAutoScaling: true,
containerClass: "<container>"
});
const result = md.render(
'```graphviz [this is a test]\r\n' +
'digraph G {Hello->World}\r\n' +
'```\r\n'
);
};
expect(testFunction).to.throw();
})
})
describe("## for imageFormat", () => {
it('* ignore invalid imageFormat', () => {
const md = new MarkdownIt();
md.use(MarkdownItKroki, {
entrypoint: "https://kroki.io",
marpAutoScaling: true,
containerClass: "the-container",
imageFormat: "<injected>"
});
const html = md.render(
'```graphviz [this is a test]\r\n' +
'digraph G {Hello->World}\r\n' +
'```\r\n'
);
const dom = new JSDOM(html);
const imgTag = dom.window.document.getElementsByTagName("embed")[0];
const actual = imgTag.getAttribute('src');
expect(actual).to.includes('/svg/');
})
})
});
@@ -0,0 +1,154 @@
const { expect } = require('chai');
const sinon = require('sinon');
const MarkdownIt = require('markdown-it');
const MarkdownItKroki = require('../../index');
const { JSDOM } = require('jsdom');
const testData = [
// graphviz
{
langname: 'graphviz',
data: '```graphviz svg\r\n' +
'digraph G {Hello->World}\r\n' +
'```\r\n'
}
];
describe('# [total test] Test pulugin can Render DOM', () => {
for (const test of testData) {
describe(`## Test for ${test.langname}`, () => {
describe('### no option call test', () => {
it('* Not to Throw on no options', () => {
const testFunction = () => {
// render !
const md = new MarkdownIt();
md.use(MarkdownItKroki);
const _ = md.render(test.data);
};
expect(testFunction).to.not.throw();
});
it('* root DOM item is \'p\' on no option', () => {
// render !
const md = new MarkdownIt();
md.use(MarkdownItKroki);
const result = md.render(test.data);
// find p-tag
const dom = new JSDOM(result);
const document = dom.window.document;
const ptags = document.getElementsByTagName("p");
// test p-tag is only one
expect(ptags.length).to.be.equal(1);
// test root item is p
const thePtag = ptags[0];
expect(thePtag.isSameNode(document.body.firstChild)).to.true;
// test embeded default container class
expect(thePtag.getAttribute('class')).to.be.equal('kroki-image-container');
});
it('* has embed tag and source is created by this library.', () => {
// render !
const md = new MarkdownIt();
md.use(MarkdownItKroki);
const result = md.render(test.data);
// find embed tag
const dom = new JSDOM(result);
const document = dom.window.document;
const imgTags = document.getElementsByTagName("embed");
// test embed is only one
expect(imgTags.length).to.be.equal(1);
const imgTag = imgTags[0];
expect(imgTag.getAttribute('src')).not.to.empty;
});
});
describe('### option call test', () => {
it('* entrypoint is embeded in image tag.', () => {
const entrypoint = 'https://127.0.0.1';
// render !
const md = new MarkdownIt();
md.use(MarkdownItKroki, {entrypoint: entrypoint});
const result = md.render(test.data);
// find embed tag
const dom = new JSDOM(result);
const document = dom.window.document;
const imgTags = document.getElementsByTagName("embed");
const imgTag = imgTags[0];
expect(imgTag.getAttribute('src')).includes(entrypoint);
});
it('* containerClass is embeded.', () => {
const containerClass = 'my-container';
// render !
const md = new MarkdownIt();
md.use(MarkdownItKroki, {containerClass: containerClass});
const result = md.render(test.data);
// find p-tag
const dom = new JSDOM(result);
const document = dom.window.document;
const ptags = document.getElementsByTagName("p");
// test p-tag is only one
expect(ptags.length).to.be.equal(1);
const thePtag = ptags[0];
expect(thePtag.getAttribute('class')).to.be.equal(containerClass);
});
it('* imageFormat is embeded.', () => {
const imageFormat = 'png';
// render !
const md = new MarkdownIt();
md.use(MarkdownItKroki, {imageFormat: imageFormat});
const result = md.render(test.data);
// find embed tag
const dom = new JSDOM(result);
const document = dom.window.document;
const imgTags = document.getElementsByTagName("embed");
const imgTag = imgTags[0];
expect(imgTag.getAttribute('src')).includes(imageFormat);
});
it('* useImg is enable.', () => {
const useImg = true;
// render !
const md = new MarkdownIt();
md.use(MarkdownItKroki, {useImg: useImg});
const result = md.render(test.data);
// img embed tag
const dom = new JSDOM(result);
const document = dom.window.document;
const imgTags = document.getElementsByTagName("img");
expect(imgTags.length).to.be.equal(1);
});
it('* render is embeded.', () => {
const fakeTag = 'testXXXXXXXXXXXXXXX';
const render = sinon.fake.returns(fakeTag);
// render !
const md = new MarkdownIt();
md.use(MarkdownItKroki, {render: render});
const result = md.render(test.data);
// render must be called
expect(render.called).to.be.true;
// render must be called with correct argument
expect(render.getCall(0).args[0]).to.contains('https://kroki.io');
// endered embeded fakeTag
expect(result).to.contains(fakeTag);
})
});
});
}
})
@@ -0,0 +1,35 @@
const expect = require('chai').expect;
const contract = require('../../lib/contract');
describe('# [unit test]: contract.js', () => {
describe('## toNonEmptyString()', () => {
['abc', ' ', ' abc '].forEach((test) => {
it(`* normal cases. { testcase: ${test} }`, () => {
expect(() => {
contract.toNonEmptyString(test);
}).not.to.throw();
})
});
['', null, undefined, 123, 0.123, true, { test: 123 }].forEach((test) => {
it(`* abnormal cases. { testcase: ${test} }`, () => {
expect(() => {
contract.toNonEmptyString(test);
}).to.throw();
})
});
});
describe('## toTrue', () => {
it('* normal case. {test case: true}', () => {
expect(() => {
contract.toTrue(true);
}).not.to.throw();
});
['', null, undefined, 123, 0.123, false, { test: 123 }].forEach((test) => {
it(`* abnormal cases. { testcase: ${test} }`, () => {
expect(() => {
contract.toTrue(test);
}).to.throw();
})
});
});
})
@@ -0,0 +1,75 @@
const expect = require('chai').expect;
const { inflateSync } = require('zlib');
const { encode, generateUrl } = require('../../lib/diagram-encoder');
describe('# [unit-test]: diagram-encoder.js', () => {
describe('## [function]: encode()', () => {
it('* encoded data is able to decode.', () => {
const testFunc = () => {
const expected = '@startuml\nBob -> Alice : hello\n@enduml';
const encoded = encode(expected);
const deflated = Buffer.from(encoded, "base64url");
const actual = inflateSync(deflated).toString();
expect(actual).to.be.equal(expected);
}
expect(testFunc).not.to.Throw();
});
});
describe('## [function]: generateUrl()', () => {
it('* must start format like <entry-point>/<lang>/<format>/', () => {
const actual = generateUrl('https://kroki.io', 'graphviz', 'svg', 'digraph G {Hello->World}');
const expected = 'https://kroki.io/graphviz/svg/';
expect(actual).to.be.a('string');
expect(actual.startsWith(expected)).to.be.true;
});
it('* must endwith <encoded>', () => {
const actual = generateUrl('https://kroki.io', 'graphviz', 'svg', 'digraph G {Hello->World}');
const expected = encode('digraph G {Hello->World}');
expect(actual).to.be.a('string');
expect(actual.endsWith(expected)).to.be.true;
});
[1, '', null, undefined].forEach(test => {
it(`* [exception] throws when entry-point is, non-string object, empty string, null or undefined. Test: ${test}`, () => {
const testFunction = () => {
let _ = generateUrl(test, 'graphviz', 'svg', 'digraph G {Hello->World}');
}
expect(testFunction).throw();
});
it(`* [exception] throws when lang is, non-string object, empty string, null or undefined. Test: ${test}`, () => {
const testFunction = () => {
let _ = generateUrl('https://kroki.io', test, 'svg', 'digraph G {Hello->World}');
}
expect(testFunction).throw();
});
it(`* [exception] throws when imgType is, non-string object, empty string, null or undefined. Test: ${test}`, () => {
const testFunction = () => {
let _ = generateUrl('https://kroki.io', 'graphviz', test, 'digraph G {Hello->World}');
}
expect(testFunction).throw();
});
it(`* [exception] throws when diagram is, non-string object, empty string, null or undefined. Test: ${test}`, () => {
const testFunction = () => {
let _ = generateUrl('https://kroki.io', 'graphviz', 'svg', diagram);
}
expect(testFunction).throw();
});
});
it('* [exception] throws when lang is unsupported lang', () => {
const testFunction = () => {
let _ = generateUrl('https://kroki.io', 'graphviz123', 'svg', 'digraph G {Hello->World}');
}
expect(testFunction).throw();
});
it('* [exception] throws when imgType is unsupported imgType', () => {
const testFunction = () => {
let _ = generateUrl('https://kroki.io', 'graphviz', 'svg123', 'digraph G {Hello->World}');
}
expect(testFunction).throw();
});
});
});
@@ -0,0 +1,146 @@
const md = require('markdown-it');
const expect = require('chai').expect;
const { JSDOM } = require('jsdom');
const { MarkdownItKrokiCore } = require('../../lib/plugin-core');
const { encode } = require('../../lib/diagram-encoder');
describe('# [unit-test] plugin-core.js', () => {
describe('## method: buuildEmbedHTML', () => {
describe('### langAndAlt.language', () => {
[null, undefined, ''].forEach((test) => {
it(`* when langAndAlt.language is null or empty, throws error. testcase:${test}`, () => {
const diagramCode = '@startuml\nBob -> Alice : hello\n @enduml';
const plugin = new MarkdownItKrokiCore(new md()).setOptions();
const testFunc = () => {
plugin.use();
const _ = plugin.buildEmbedHTML(
{ language: test, alt: '' }, diagramCode);
};
expect(testFunc).to.throw();
});
});
it('* language embeded in to url', () => {
const test = 'plantuml';
const diagramCode = '@startuml\nBob -> Alice : hello\n @enduml';
// build embed HTML
const plugin = new MarkdownItKrokiCore(new md()).setOptions();
plugin.use();
const html = plugin.buildEmbedHTML({ language: test, alt: '' }, diagramCode);
// parse dom
const dom = new JSDOM(html);
const imgTag = dom.window.document.getElementsByTagName("embed")[0];
// get url attribute
const url = imgTag.getAttribute('src');
expect(/\/plantuml\//.test(url)).to.true;
});
});
describe('### langAndAlt.alt', () => {
[null, undefined, ''].forEach((test) => {
it(`* when langAndAlt.alt is null or empty, no alt attribute. testcase:${test}`, () => {
const diagramCode = '@startuml\nBob -> Alice : hello\n @enduml';
// prepair
const plugin = new MarkdownItKrokiCore(new md()).setOptions();
plugin.use();
// render
const html = plugin.buildEmbedHTML(
{ language: 'plantuml', alt: test }, diagramCode);
// parse dom
const dom = new JSDOM(html);
const imgTag = dom.window.document.getElementsByTagName("embed")[0];
expect(imgTag.hasAttribute('alt')).to.false;
});
});
it('* embeded altText', () => {
const expected = "this is test Text";
const diagramCode = '@startuml\nBob -> Alice : hello\n @enduml';
// prepair
const plugin = new MarkdownItKrokiCore(new md()).setOptions();
plugin.use();
// render
const html = plugin.buildEmbedHTML(
{ language: 'plantuml', alt: expected }, diagramCode);
// parse dom
const dom = new JSDOM(html);
const imgTag = dom.window.document.getElementsByTagName("embed")[0];
expect(imgTag.getAttribute('title')).to.equal(expected);
});
});
describe('### diagramCode', () => {
[null, undefined, ''].forEach((test) => {
it(`* when diagramCode is null or empty, throws error. testcase:${test}`, () => {
const plugin = new MarkdownItKrokiCore(new md()).setOptions();
const testFunc = () => {
plugin.use();
const _ = plugin.buildEmbedHTML(
{ language: 'plantuml', alt: '' }, test);
};
expect(testFunc).to.throw();
});
});
it('* encoded diagram must be embed to url on <img src=\'....\' ', () => {
const test = '@startuml\nBob -> Alice : hello\n @enduml';
const expected = encode(test);
// build embed HTML
const plugin = new MarkdownItKrokiCore(new md()).setOptions();
plugin.use();
const html = plugin.buildEmbedHTML({ language: 'plantuml', alt: '' }, test);
// parse dom
const dom = new JSDOM(html);
const imgTag = dom.window.document.getElementsByTagName("embed")[0];
// get url attribute
const url = imgTag.getAttribute('src');
expect(url.endsWith(expected)).to.true;
});
it('* <img> is surounded by <marp-auto-scaling> on to be used form marp-it', () => {
const test = '@startuml\nBob -> Alice : hello\n @enduml';
const markdownIt = new md()
markdownIt['marpit'] = { someObject: 'is implemented' };
// build embed HTML
const plugin = new MarkdownItKrokiCore(markdownIt).setOptions();
plugin.use();
const html = plugin.buildEmbedHTML({ language: 'plantuml', alt: '' }, test);
// parse dom
const dom = new JSDOM(html);
const imgTag = dom.window.document.getElementsByTagName("embed")[0];
const marpAutoScaling = dom.window.document.getElementsByTagName("marp-auto-scaling")[0];
expect(imgTag.isSameNode(marpAutoScaling.firstChild)).to.be.true;
});
it('* <img> is surounded by <marp-auto-scaling> on not to be used form marp-it', () => {
const test = '@startuml\nBob -> Alice : hello\n @enduml';
const markdownIt = new md()
// build embed HTML
const plugin = new MarkdownItKrokiCore(markdownIt).setOptions();
plugin.use();
const html = plugin.buildEmbedHTML({ language: 'plantuml', alt: '' }, test);
// parse dom
const dom = new JSDOM(html);
const marpAutoScaling = dom.window.document.getElementsByTagName("marp-auto-scaling");
expect(marpAutoScaling.length).to.equal(0);
})
});
});
});
@@ -0,0 +1,272 @@
const md = require('markdown-it');
const expect = require('chai').expect;
const sinon = require('sinon');
const { JSDOM } = require('jsdom');
const { MarkdownItKrokiCore } = require('../../lib/plugin-core');
describe('# [unit-test] plugin-core.js', () => {
describe('## method: setOptions() must be work', () => {
function buildHtmlForTest(options) {
const test = 'plantuml';
const alt = 'test alt text';
const diagramCode = '@startuml\nBob -> Alice : hello\n @enduml';
// build embed HTML
const plugin = new MarkdownItKrokiCore(new md()).setOptions(options);
plugin.use();
return plugin.buildEmbedHTML({ language: test, alt: alt }, diagramCode);
}
describe('### entrypoint', () => {
function expectEntryPointToEmbed(htmlString, expected) {
if (!expected) expected = 'https://kroki.io';
// parse dom
const dom = new JSDOM(htmlString);
const imgTag = dom.window.document.getElementsByTagName("embed")[0];
// get url attribute
const url = imgTag.getAttribute('src');
expect(url.startsWith(expected)).to.true;
}
it('* no options', () => {
const html = buildHtmlForTest();
expectEntryPointToEmbed(html);
});
it('* option is null', () => {
const html = buildHtmlForTest({ entrypoint: null });
expectEntryPointToEmbed(html);
});
it('* option is undefined', () => {
const html = buildHtmlForTest({ entrypoint: undefined });
expectEntryPointToEmbed(html);
});
it('* option is \'\'', () => {
const html = buildHtmlForTest({ entrypoint: '' });
expectEntryPointToEmbed(html);
});
it('* option is 1', () => {
const html = buildHtmlForTest({ entrypoint: 1 });
expectEntryPointToEmbed(html);
});
it('* option is true', () => {
const html = buildHtmlForTest({ entrypoint: true });
expectEntryPointToEmbed(html);
});
it('* option is \'https://localhost:8080\'', () => {
const html = buildHtmlForTest({
entrypoint: 'https://localhost:8080'
});
expectEntryPointToEmbed(html, 'https://localhost:8080');
});
});
describe.skip('### marpAutoScaling', () => {
function expectMarpAutoScalingToEmbed(htmlString, expected) {
// parse dom
const dom = new JSDOM(htmlString);
const tags = dom.window.document.getElementsByTagName("marp-auto-scaling");
if (expected) {
expect(tags).not.to.empty;
} else {
expect(tags).to.empty;
}
}
it('* no options', () => {
const html = buildHtmlForTest();
expectMarpAutoScalingToEmbed(html, true);
});
it('* option is null', () => {
const html = buildHtmlForTest({ marpAutoScaling: null });
expectMarpAutoScalingToEmbed(html, true);
});
it('* option is undefined', () => {
const html = buildHtmlForTest({ marpAutoScaling: undefined });
expectMarpAutoScalingToEmbed(html, true);
});
it('* option is \'\'', () => {
const html = buildHtmlForTest({ marpAutoScaling: '' });
expectMarpAutoScalingToEmbed(html, true);
});
it('* option is 1', () => {
const html = buildHtmlForTest({ marpAutoScaling: 1 });
expectMarpAutoScalingToEmbed(html, true);
});
it('* option is \'test\'', () => {
const html = buildHtmlForTest({ marpAutoScaling: 'test' });
expectMarpAutoScalingToEmbed(html, true);
});
it('* option is false', () => {
const html = buildHtmlForTest({ marpAutoScaling: false });
expectMarpAutoScalingToEmbed(html, false);
});
})
describe('### containerClass', () => {
function expectContainerClassToEmbed(htmlString, className) {
// parse dom
const dom = new JSDOM(htmlString);
const pTag = dom.window.document.getElementsByTagName("p")[0];
const actualClassName = pTag.getAttribute('class');
expect(actualClassName).to.equal(className);
}
it('* no options', () => {
const html = buildHtmlForTest();
expectContainerClassToEmbed(html, 'kroki-image-container');
});
it('* option is null', () => {
const html = buildHtmlForTest({ containerClass: null });
expectContainerClassToEmbed(html, 'kroki-image-container');
});
it('* option is undefined', () => {
const html = buildHtmlForTest({ containerClass: undefined });
expectContainerClassToEmbed(html, 'kroki-image-container');
});
it('* option is \'\'', () => {
const html = buildHtmlForTest({ containerClass: '' });
expectContainerClassToEmbed(html, 'kroki-image-container');
});
it('* option is 1', () => {
const html = buildHtmlForTest({ containerClass: 1 });
expectContainerClassToEmbed(html, 'kroki-image-container');
});
it('* option is \'containerClass\'', () => {
const html = buildHtmlForTest({ containerClass: 'containerClass' });
expectContainerClassToEmbed(html, 'containerClass');
});
});
describe('### imageFormat', () => {
function expectImageFormatToEmbed(htmlString, expected) {
// parse dom
const dom = new JSDOM(htmlString);
const imgTag = dom.window.document.getElementsByTagName("embed")[0];
// get url attribute
const url = imgTag.getAttribute('src');
expect(url).to.includes('/' + expected + '/');
}
it('* no options', () => {
const html = buildHtmlForTest();
expectImageFormatToEmbed(html, 'svg');
});
it('* option is null', () => {
const html = buildHtmlForTest({ imageFormat: null });
expectImageFormatToEmbed(html, 'svg');
});
it('* option is undefined', () => {
const html = buildHtmlForTest({ imageFormat: undefined });
expectImageFormatToEmbed(html, 'svg');
});
it('* option is \'\'', () => {
const html = buildHtmlForTest({ imageFormat: '' });
expectImageFormatToEmbed(html, 'svg');
});
it('* option is 1', () => {
const html = buildHtmlForTest({ imageFormat: 1 });
expectImageFormatToEmbed(html, 'svg');
});
it('* option is \'test\'', () => {
const html = buildHtmlForTest({ imageFormat: 'test' });
expectImageFormatToEmbed(html, 'svg');
});
it('* option is \'png\'', () => {
const html = buildHtmlForTest({ imageFormat: 'png' });
expectImageFormatToEmbed(html, 'png');
});
});
describe('### useImg', () => {
function expectUseImgTag(html, expectToUseImg) {
// parse dom
const dom = new JSDOM(html);
const imgTag = dom.window.document.getElementsByTagName("img")[0];
if(!expectToUseImg) {
expect(imgTag).to.be.undefined;
return;
} else {
expect(imgTag).not.to.be.undefined;
}
}
it('* no options', () => {
const html = buildHtmlForTest();
expectUseImgTag(html, false);
});
it('* option is true', () => {
const html = buildHtmlForTest({ useImg: true });
expectUseImgTag(html, true);
});
it('* option is null', () => {
const html = buildHtmlForTest({ useImg: null });
expectUseImgTag(html, false);
});
it('* option is undefined', () => {
const html = buildHtmlForTest({ useImg: undefined });
expectUseImgTag(html, false);
});
it('* option is \'\'', () => {
const html = buildHtmlForTest({ useImg: '' });
expectUseImgTag(html, false);
});
it('* option is 1', () => {
const html = buildHtmlForTest({ useImg: 1 });
expectUseImgTag(html, false);
});
});
describe('### render', () => {
it('* render was called', ()=>{
const renderCallback = sinon.fake();
buildHtmlForTest({ render: renderCallback });
expect(renderCallback.calledOnce).to.be.true;
});
it('* render was called with correct arg', ()=>{
const renderCallback = sinon.fake();
buildHtmlForTest({ render: renderCallback });
expect(renderCallback.firstCall.args[0]).to.be.an('string');
expect(renderCallback.firstCall.args[0]).to.includes('https://kroki.io');
expect(renderCallback.firstCall.args[1]).to.be.an('string');
expect(renderCallback.firstCall.args[1]).to.includes('test alt text');
});
it('* render the return value', ()=>{
const testReturnValue = '%%%%%test return value%%%%%';
const renderCallback = sinon.fake.returns(testReturnValue);
const html = buildHtmlForTest({ render: renderCallback });
expect(html).to.includes(testReturnValue);
})
it('* no options', () => {
expect(()=>{
buildHtmlForTest();
}).not.to.throw();
});
it('* option is true', () => {
expect(()=>{
buildHtmlForTest({ render: true });
}).not.to.throw();
});
it('* option is null', () => {
expect(()=>{
buildHtmlForTest({ render: false });
}).not.to.throw();
});
it('* option is undefined', () => {
expect(()=>{
buildHtmlForTest({ render: false });
}).not.to.throw();
});
it('* option is \'\'', () => {
expect(()=>{
buildHtmlForTest({ useImg: '' });
}).not.to.throw();
});
it('* option is 1', () => {
expect(()=>{
buildHtmlForTest({ useImg: 1 });
}).not.to.throw();
});
})
});
});
@@ -0,0 +1,49 @@
const expect = require('chai').expect;
const { MarkdownItKrokiCore } = require('../../lib/plugin-core');
describe('# [unit-test] plugin-core.js', () => {
describe('## static method: readLanguageAndAltText() - language', () => {
[
{ test: null, expected: '' },
{ test: undefined, expected: '' },
{ test: '', expected: '' },
{ test: ' ', expected: '' },
{ test: 'plantuml', expected: 'plantuml' },
{ test: ' plantuml', expected: 'plantuml' },
{ test: 'plantuml ', expected: 'plantuml' },
{ test: 'plantuml +++', expected: 'plantuml' },
{ test: 'html+md', expected: 'html+md' },
{ test: 'graphviz[]', expected: 'graphviz' },
{ test: 'graphviz[test]', expected: 'graphviz' },
{ test: 'graphviz [test test]', expected: 'graphviz' },
].forEach(testCase => {
it(`### Can read diagramLanguage. in case \'${testCase.test}\'`, () => {
const actual = MarkdownItKrokiCore.readLanguageAndAltText(testCase.test);
const expected = testCase.expected;
expect(actual.language).to.be.equal(expected);
})
});
});
describe('## static method: readLanguageAndAltText() - alt', () => {
[
{ test: null, expected: '' },
{ test: undefined, expected: '' },
{ test: '', expected: '' },
{ test: ' ', expected: '' },
{ test: 'plantuml', expected: '' },
{ test: ' plantuml', expected: '' },
{ test: 'plantuml ', expected: '' },
{ test: 'plantuml +++', expected: '' },
{ test: 'html+md', expected: '' },
{ test: 'graphviz[]', expected: '' },
{ test: 'graphviz[test]', expected: 'test' },
{ test: 'graphviz [test test]', expected: 'test test' },
].forEach(testCase => {
it(`### Can read diagramLanguage. in case \'${testCase.test}\'`, () => {
const actual = MarkdownItKrokiCore.readLanguageAndAltText(testCase.test);
const expected = testCase.expected;
expect(actual.alt).to.be.equal(expected);
})
});
});
});
@@ -0,0 +1,120 @@
const { expect } = require('chai');
const { safeProperty } = require('../../lib/safe-property');
describe('# [unit-test]: safe-property.js', () => {
[
{
testCaseDescription: "standard test - string",
testCase: {
test: {
property1: "hello"
},
name: "property1",
type: "string",
defaultValue: undefined
},
expected: "hello"
},
{
testCaseDescription: "standard test - boolean",
testCase: {
test: {
property1: true
},
name: "property1",
type: "boolean",
defaultValue: undefined
},
expected: true
},
{
testCaseDescription: "standard test - boolean on null",
testCase: {
test: {
property1: null
},
name: "property1",
type: "boolean",
defaultValue: false
},
expected: false
},
{
testCaseDescription: "on null",
testCase: {
test: {
property1: null
},
name: "property1",
type: "string",
defaultValue: "hello"
},
expected: "hello"
},
{
testCaseDescription: "on empty string",
testCase: {
test: {
property1: ''
},
name: "property1",
type: "string",
defaultValue: "hello"
},
expected: "hello"
},
{
testCaseDescription: "on undefined",
testCase: {
test: {
property1: undefined
},
name: "property1",
type: "string",
defaultValue: "hello"
},
expected: "hello"
},
{
testCaseDescription: "on not mutch type",
testCase: {
test: {
property1: 1
},
name: "property1",
type: "string",
defaultValue: "hello"
},
expected: "hello"
},
{
testCaseDescription: "on object is null",
testCase: {
test: null,
name: "property1",
type: "string",
defaultValue: "hello"
},
expected: "hello"
},
{
testCaseDescription: "on object is undefined",
testCase: {
test: undefined,
name: "property1",
type: "string",
defaultValue: "hello"
},
expected: "hello"
},
].forEach((testItem) => {
it(`* ${testItem.testCaseDescription}`, () => {
const actual = safeProperty(
testItem.testCase.test,
testItem.testCase.name,
testItem.testCase.type,
testItem.testCase.defaultValue);
expect(actual).to.equal(testItem.expected);
});
})
})
@@ -0,0 +1,2 @@
/*! markdown-it-container 4.0.0 https://github.com/markdown-it/markdown-it-container @license MIT */
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e="undefined"!=typeof globalThis?globalThis:e||self).markdownitContainer=n()}(this,(function(){"use strict";return function(e,n,r){const t=(r=r||{}).marker||":",o=t.charCodeAt(0),i=t.length,c=r.validate||function(e){return e.trim().split(" ",2)[0]===n},s=r.render||function(e,r,t,o,i){return 1===e[r].nesting&&e[r].attrJoin("class",n),i.renderToken(e,r,t,o,i)};e.block.ruler.before("fence","container_"+n,(function(e,r,s,a){let l,f=!1,u=e.bMarks[r]+e.tShift[r],d=e.eMarks[r];if(o!==e.src.charCodeAt(u))return!1;for(l=u+1;l<=d&&t[(l-u)%i]===e.src[l];l++);const p=Math.floor((l-u)/i);if(p<3)return!1;l-=(l-u)%i;const k=e.src.slice(u,l),b=e.src.slice(l,d);if(!c(b,k))return!1;if(a)return!0;let h=r;for(;(h++,!(h>=s))&&(u=e.bMarks[h]+e.tShift[h],d=e.eMarks[h],!(u<d&&e.sCount[h]<e.blkIndent));)if(o===e.src.charCodeAt(u)&&!(e.sCount[h]-e.blkIndent>=4)){for(l=u+1;l<=d&&t[(l-u)%i]===e.src[l];l++);if(!(Math.floor((l-u)/i)<p||(l-=(l-u)%i,l=e.skipSpaces(l),l<d))){f=!0;break}}const m=e.parentType,M=e.lineMax;e.parentType="container",e.lineMax=h;const _=e.push("container_"+n+"_open","div",1);_.markup=k,_.block=!0,_.info=b,_.map=[r,h],e.md.block.tokenize(e,r+1,h);const y=e.push("container_"+n+"_close","div",-1);return y.markup=e.src.slice(u,l),y.block=!0,e.parentType=m,e.lineMax=M,e.line=h+(f?1:0),!0}),{alt:["paragraph","reference","blockquote","list"]}),e.renderer.rules["container_"+n+"_open"]=s,e.renderer.rules["container_"+n+"_close"]=s}}));
@@ -0,0 +1,2 @@
/*! markdown-it-mark 4.0.0 https://github.com/markdown-it/markdown-it-mark @license MIT */
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e="undefined"!=typeof globalThis?globalThis:e||self).markdownitMark=n()}(this,(function(){"use strict";return function(e){function n(e,n){const t=[],o=n.length;for(let s=0;s<o;s++){const o=n[s];if(61!==o.marker)continue;if(-1===o.end)continue;const r=n[o.end],i=e.tokens[o.token];i.type="mark_open",i.tag="mark",i.nesting=1,i.markup="==",i.content="";const c=e.tokens[r.token];c.type="mark_close",c.tag="mark",c.nesting=-1,c.markup="==",c.content="","text"===e.tokens[r.token-1].type&&"="===e.tokens[r.token-1].content&&t.push(r.token-1)}for(;t.length;){const n=t.pop();let o=n+1;for(;o<e.tokens.length&&"mark_close"===e.tokens[o].type;)o++;if(o--,n!==o){const t=e.tokens[o];e.tokens[o]=e.tokens[n],e.tokens[n]=t}}}e.inline.ruler.before("emphasis","mark",(function(e,n){const t=e.pos,o=e.src.charCodeAt(t);if(n)return!1;if(61!==o)return!1;const s=e.scanDelims(e.pos,!0);let r=s.length;const i=String.fromCharCode(o);if(r<2)return!1;if(r%2){e.push("text","",0).content=i,r--}for(let n=0;n<r;n+=2){e.push("text","",0).content=i+i,(s.can_open||s.can_close)&&e.delimiters.push({marker:o,length:0,jump:n/2,token:e.tokens.length-1,end:-1,open:s.can_open,close:s.can_close})}return e.pos+=s.length,!0})),e.inline.ruler2.before("emphasis","mark",(function(e){let t;const o=e.tokens_meta,s=(e.tokens_meta||[]).length;for(n(e,e.delimiters),t=0;t<s;t++)o[t]&&o[t].delimiters&&n(e,o[t].delimiters)}))}}));