first commit
This commit is contained in:
Vendored
Executable
+20
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
Executable
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"mocha.files.glob": "tests/**/*.test.js"
|
||||
}
|
||||
Vendored
Executable
+46
@@ -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
|
||||
Vendored
Executable
+21
@@ -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.
|
||||
Vendored
Executable
+157
@@ -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
|
||||
```
|
||||
|
||||

|
||||
|
||||
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` |
|
||||
Vendored
Executable
+8
@@ -0,0 +1,8 @@
|
||||
|
||||
module.exports = {
|
||||
inputDir: './',
|
||||
engine: ({ marp }) => marp.use(require('../index'), {
|
||||
entrypoint: "https://kroki.io",
|
||||
marpAutoScaling: true
|
||||
})
|
||||
}
|
||||
Vendored
Executable
+99
@@ -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;
|
||||
}
|
||||
```
|
||||
Vendored
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
Vendored
Executable
+6
@@ -0,0 +1,6 @@
|
||||
const { MarkdownItKrokiCore } = require('./lib/plugin-core');
|
||||
|
||||
module.exports = (md, opt) => {
|
||||
const plugin = new MarkdownItKrokiCore(md);
|
||||
plugin.setOptions(opt).use();
|
||||
};
|
||||
Vendored
Executable
+37
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
.obsidian/plugins/marp-slides/lib3/markdown-it/@kazumatu981/markdown-it-kroki/lib/diagram-encoder.js
Vendored
Executable
+27
@@ -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
|
||||
};
|
||||
Vendored
Executable
+52
@@ -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
|
||||
Vendored
Executable
+85
@@ -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
|
||||
}
|
||||
Vendored
Executable
+15
@@ -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 };
|
||||
Vendored
Executable
+56
@@ -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)
|
||||
};
|
||||
Vendored
Executable
+79
@@ -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`.
|
||||
Vendored
Executable
+37
@@ -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"
|
||||
}
|
||||
}
|
||||
Vendored
Executable
+88
@@ -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/');
|
||||
})
|
||||
})
|
||||
});
|
||||
Vendored
Executable
+154
@@ -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);
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
Vendored
Executable
+35
@@ -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();
|
||||
})
|
||||
});
|
||||
});
|
||||
})
|
||||
Vendored
Executable
+75
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Vendored
Executable
+146
@@ -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);
|
||||
})
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
Vendored
Executable
+272
@@ -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();
|
||||
});
|
||||
|
||||
})
|
||||
});
|
||||
});
|
||||
Vendored
Executable
+49
@@ -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);
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
Vendored
Executable
+120
@@ -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);
|
||||
});
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user