Skip to content
This repository was archived by the owner on Dec 7, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
dist/
node_modules/
.vscode/
coverage/
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The include code plugin is normally used in the context of a site generator usin
1. __lang__: The image transformations is applied for all codeblocks with this language.
1. __imgRefDir__: The prefix that will be used for a generated image to create the link.
1. __imgDir__: The directory where the generated image files are stored.
1. __langAliases__: an array of markdown code languages that get treated as a kroki document ([See blow](#automatic-use-of-existing-language-tags))

### Docusaurus 2

Expand Down Expand Up @@ -62,6 +63,8 @@ The user can decide to put generated images under version control so that images

### Examples

#### Explicit kroki "language"

__Generate a mermaid image__

<pre>
Expand All @@ -85,6 +88,57 @@ graph TD
```
</pre>

#### Automatic use of existing language tags

In some cases, it would make a lot of sense to preserve the original language tag, for example `mermaid` documents are a first-class citizen in many markdown editors and is even explicitly supported inside popular code hosting services such as GitHub and GitLab.

In such cases, in would make sense to supply the optional `langAliases` option as part of the kroki plugin setup like so, to avoid rewriting the language tag for existing code-blocks that work well in other environments not related to remark/kroki:

```json
presets: [[
'@docusaurus/preset-classic',
{
docs: {
sidebarPath: require.resolve('./sidebars.js'),
remarkPlugins: [
[
require('remark-kroki-plugin'),
{
krokiBase: 'https://kroki.io',
lang: "kroki",
langAliases: ["mermaid"],
imgRefDir: "../img/kroki",
imgDir: "static/img/kroki"
}
]
],
}}
]]
```

This in turn allows for direct inclusing of mermaid blocks in a more straight-forward manner:

<pre>
```mermaid
graph TD
subgraph Shop X
Bx(Shop X) --> FX1((Fs X1)) --> Bx
Bx --> FX2((Fs X2)) --> Bx
end
subgraph Shop Y
By(Shop Y) --> FY1((Fs Y1)) --> By
By --> FY2((Fs Y2)) --> By
end
subgraph Shop Z
Bz(Shop Z) --> FZ1((Fs Z1)) --> Bz
Bz --> FZ2((Fs Z2)) --> Bz
end
A(Data Center) --> Bx --> A
A --> By --> A
A --> Bz --> A
```
</pre>

## Development

It might be good to allow the generated filename to be overwritten with a parameter.
Expand Down
3 changes: 3 additions & 0 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { KrokiOptions } from './transform';
declare const plugin: (options: KrokiOptions) => (tree: any, vfile: any) => Promise<void>;
export = plugin;
6 changes: 6 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"use strict";
const transform_1 = require("./transform");
const plugin = (options) => {
return transform_1.transform(options);
};
module.exports = plugin;
12 changes: 12 additions & 0 deletions dist/transform.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
declare type OptionString = string | undefined;
export interface KrokiOptions {
krokiBase: string;
imgDir: string;
imgRefDir: string;
lang: string;
langAliases: string[];
verbose: boolean;
}
export declare function extractParam(name: string, input: string): OptionString;
export declare const transform: (options: KrokiOptions) => (tree: any, vfile: any) => Promise<void>;
export {};
124 changes: 124 additions & 0 deletions dist/transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.transform = exports.extractParam = void 0;
const md5_1 = require("ts-md5/dist/md5");
const unist_util_visit_1 = __importDefault(require("unist-util-visit"));
const node_fetch_1 = __importDefault(require("node-fetch"));
const fs_1 = __importDefault(require("fs"));
function get(v) {
if (v === undefined) {
throw new Error("Mandatory variable is not defined");
}
else {
return v;
}
}
class ImageBlock {
constructor(node, options, imgType, imgAlt, imgTitle, imageCode, position) {
this.node = node;
this.options = options;
this.imgType = imgType;
this.imgAlt = imgAlt;
this.imgTitle = imgTitle;
this.imageCode = imageCode;
this.position = position;
this.md5 = md5_1.Md5.hashStr(this.imageCode);
this.imgFile = (this.options.imgDir.startsWith("/")) ?
this.options.imgDir + "/" + this.md5 + ".svg" :
process.cwd() + "/" + this.options.imgDir + "/" + this.md5 + ".svg";
this.krokiUrl = this.options.krokiBase + "/" + this.imgType + "/svg";
this.getImage = () => __awaiter(this, void 0, void 0, function* () {
if (this.options.verbose)
console.log("Fetching image from kroki @ " + this.krokiUrl + " ...");
const response = yield node_fetch_1.default(this.krokiUrl, {
method: 'POST',
body: this.imageCode,
headers: { 'Content-Type': 'text/plain' }
});
if (this.options.verbose)
console.log(`Got response from kroki: ${response.status} ${response.statusText}`);
return response;
});
this.createNode = (vfile) => __awaiter(this, void 0, void 0, function* () {
if (fs_1.default.existsSync(this.imgFile)) {
if (this.options.verbose)
console.log("Reusing image file [" + this.imgFile + "].");
}
else {
const imgText = yield this.getImage();
if (!imgText.ok) {
throw new Error(`Unable to get image text from kroki @ ${vfile.path}:${this.position.start.line}:${this.position.start.column}: ${this.imageCode}`);
}
else {
const svg = yield imgText.text();
if (this.options.verbose)
console.log(`Writing image file [${this.imgFile}] with ${svg.length} bytes.`);
;
fs_1.default.writeFileSync(this.imgFile, svg, "utf-8");
}
}
const imgNode = {
type: "image",
url: this.options.imgRefDir + "/" + this.md5 + ".svg",
title: this.imgTitle,
alt: this.imgAlt === undefined ? this.md5 : this.imgAlt
};
this.node.type = 'paragraph';
this.node.children = [imgNode];
});
}
}
function extractParam(name, input) {
const regExp = /([a-zA-Z]+)=\"([^\"]+)\"/g;
var result = undefined;
var elem;
while ((result == undefined) && (elem = regExp.exec(input)) !== null) {
if (elem[1] == name)
result = elem[2];
}
return result;
}
exports.extractParam = extractParam;
const applyCodeBlock = (options, node) => {
const { lang, meta, value, position } = node;
let kb = undefined;
let isAliasLang = options.langAliases != undefined && options.langAliases.includes(lang);
if (lang === options.lang || isAliasLang) {
const imgAlt = extractParam("imgAlt", meta);
const imgTitle = extractParam("imgTitle", meta);
const imgType = isAliasLang ? lang : get(extractParam("imgType", meta));
kb = new ImageBlock(node, options, imgType, imgAlt, imgTitle, value, position);
}
return kb;
};
const transform = (options) => (tree, vfile) => new Promise((resolve) => __awaiter(void 0, void 0, void 0, function* () {
const nodesToChange = [];
// First, collect all the node that need to be changed, so that
// we can iterate over them later on and fetch the file contents
// asynchronously
const visitor = (node) => {
const kb = applyCodeBlock(options, node);
if (kb !== undefined) {
nodesToChange.push(kb);
}
};
unist_util_visit_1.default(tree, 'code', visitor);
// Now go over the collected nodes and change them
for (const kb of nodesToChange) {
yield kb.createNode(vfile);
}
resolve();
}));
exports.transform = transform;
Loading