Skip to content

Commit

Permalink
feat: render HTML svg for markdown progress bar
Browse files Browse the repository at this point in the history
Converts an array in markdown into corresponding HTML code that can display one (or more) progress
bar.

BREAKING CHANGE: change the output from ascii to html svg
  • Loading branch information
tlylt committed Jan 6, 2022
1 parent 778f48e commit 6a57abb
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 103 deletions.
36 changes: 28 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,44 @@ Progress bar plugin for markdown-it markdown parser
[![npm version](https://img.shields.io/npm/v/markdown-it-progress)](https://npmjs.org/package/markdown-it-progress)
[![Coverage Status](https://coveralls.io/repos/github/tlylt/markdown-it-progress/badge.svg?branch=main)](https://coveralls.io/github/tlylt/markdown-it-progress?branch=main)

Example input:
Example Input:
<pre><code>
```progressBar
[["a", 2], ["b", 3], ["c", 40]]
```
</code></pre>

Output:
```html
<p>
a ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2%
Rendered Output:

b ███░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 3%
<svg class="ProgressBar" style="height:25px;width: 300px;display: inline;"><g><defs><clipPath id="ProgressBar-clipPath"><rect width="100%" height="100%" rx="15px" /></clipPath></defs><rect class="ProgressBar-background" width="100%" height="100%" rx="15px" style="fill-opacity: 0.2;fill: cadetblue;"/><rect class="ProgressBar-percentage" width="2%" height="100%" clip-path="url(#ProgressBar-clipPath)" style="fill-opacity: 0.6;fill: #4c90cf;"/><text x="100" y="20" font-size="20" fill="black">a: 2%</text></g></svg><br><svg class="ProgressBar" style="height:25px;width: 300px;display: inline;"><g><defs><clipPath id="ProgressBar-clipPath"><rect width="100%" height="100%" rx="15px" /></clipPath></defs><rect class="ProgressBar-background" width="100%" height="100%" rx="15px" style="fill-opacity: 0.2;fill: cadetblue;"/><rect class="ProgressBar-percentage" width="3%" height="100%" clip-path="url(#ProgressBar-clipPath)" style="fill-opacity: 0.6;fill: #4c90cf;"/><text x="100" y="20" font-size="20" fill="black">b: 3%</text></g></svg><br><svg class="ProgressBar" style="height:25px;width: 300px;display: inline;"><g><defs><clipPath id="ProgressBar-clipPath"><rect width="100%" height="100%" rx="15px" /></clipPath></defs><rect class="ProgressBar-background" width="100%" height="100%" rx="15px" style="fill-opacity: 0.2;fill: cadetblue;"/><rect class="ProgressBar-percentage" width="40%" height="100%" clip-path="url(#ProgressBar-clipPath)" style="fill-opacity: 0.6;fill: #4c90cf;"/><text x="100" y="20" font-size="20" fill="black">c: 40%</text></g></svg>

c ████████████████████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 40%
</p>
Raw html output:
```html
<svg class="ProgressBar" style="height:25px;width: 300px;display: inline;"><g><defs><clipPath id="ProgressBar-clipPath"><rect width="100%" height="100%" rx="15px" /></clipPath></defs><rect class="ProgressBar-background" width="100%" height="100%" rx="15px" style="fill-opacity: 0.2;fill: cadetblue;"/><rect class="ProgressBar-percentage" width="2%" height="100%" clip-path="url(#ProgressBar-clipPath)" style="fill-opacity: 0.6;fill: #4c90cf;"/><text x="100" y="20" font-size="20" fill="black">a: 2%</text></g></svg><br><svg class="ProgressBar" style="height:25px;width: 300px;display: inline;"><g><defs><clipPath id="ProgressBar-clipPath"><rect width="100%" height="100%" rx="15px" /></clipPath></defs><rect class="ProgressBar-background" width="100%" height="100%" rx="15px" style="fill-opacity: 0.2;fill: cadetblue;"/><rect class="ProgressBar-percentage" width="3%" height="100%" clip-path="url(#ProgressBar-clipPath)" style="fill-opacity: 0.6;fill: #4c90cf;"/><text x="100" y="20" font-size="20" fill="black">b: 3%</text></g></svg><br><svg class="ProgressBar" style="height:25px;width: 300px;display: inline;"><g><defs><clipPath id="ProgressBar-clipPath"><rect width="100%" height="100%" rx="15px" /></clipPath></defs><rect class="ProgressBar-background" width="100%" height="100%" rx="15px" style="fill-opacity: 0.2;fill: cadetblue;"/><rect class="ProgressBar-percentage" width="40%" height="100%" clip-path="url(#ProgressBar-clipPath)" style="fill-opacity: 0.6;fill: #4c90cf;"/><text x="100" y="20" font-size="20" fill="black">c: 40%</text></g></svg>
```

## Install

```
$ npm install --save markdown-it-progress
```
Or
```
$ yarn add markdown-it-progress
```

## Usage
```
var md = require('markdown-it')();
var markdownItProgress = require('markdown-it-progress');
md.use(markdownItProgress, {
// optional, these are default options
render: 'svg'
});
var src = '```progressBar\n[["a", 2]]\n```';
var res = md.render(src);
console.log(res);
```
36 changes: 20 additions & 16 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
'use strict';

const renderToPara = require("./render");
const { renderToSvg } = require("./render");

function generateProgressBar(arr) {
const doneBlock = '\u2588' //'█'
const emptyBlock = '\u2591'//'░'
const reducer = (prev, curr) => prev + '\n' + `${curr[0]} ${doneBlock.repeat(curr[1])}${emptyBlock.repeat(100 - curr[1])} ${curr[1]}%\n`
return arr.reduce(reducer, "")
const defaultOptions = {
render: 'svg'
};

const renderMap = {
'svg': renderToSvg
}

module.exports = function progressBarPlugin(md, pluginOptions) {
function progressBar(state) {
const tokens = state.tokens;
for (var i = 0; i < tokens.length; ++i) {
let token = tokens[i];
if (token.block && token.type === 'fence' && token.info.match(/^progressbar/)) {
token.content = generateProgressBar(JSON.parse(token.content));
renderToPara(tokens, token, i)
}
module.exports = function progressBarPlugin(md, _pluginOptions) {
let pluginOptions = Object.assign({}, defaultOptions);
pluginOptions = Object.assign(pluginOptions, _pluginOptions);
const proxy = (tokens, idx, options, env, self) => self.renderToken(tokens, idx, options);
const defaultFenceRenderer = md.renderer.rules.fence || proxy;
md.renderer.rules.fence = function (tokens, idx, options, env, slf) {
const token = tokens[idx];
if (token.info.match(/^progressBar/)) {
const data = JSON.parse(token.content);
return data.map(([key, percentage]) => {
return renderMap[pluginOptions.render](key, percentage);
}).join('<br>')
}
return defaultFenceRenderer(tokens, idx, options, env, slf);
}
md.core.ruler.before('linkify', 'progress_bar', progressBar);
}
10 changes: 5 additions & 5 deletions src/index.test.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

91 changes: 17 additions & 74 deletions src/render.js
Original file line number Diff line number Diff line change
@@ -1,77 +1,20 @@
const pOpen = {
"type": "paragraph_open",
"tag": "p",
"attrs": null,
"map": [
1,
2
],
"nesting": 1,
"level": 0,
"children": null,
"content": "",
"markup": "",
"info": "",
"meta": null,
"block": true,
"hidden": false
// `<svg class="ProgressBar" style="height:25px;width: 300px;display: inline;">
// <g>
// <defs>
// <clipPath id="ProgressBar-clipPath">
// <rect width="100%" height="100%" rx="15px" />
// </clipPath>
// </defs>
// <rect class="ProgressBar-background" width="100%" height="100%" rx="15px" style="fill-opacity: 0.2;fill: cadetblue;"/>
// <rect class="ProgressBar-percentage" width="${percentage}%" height="100%" clip-path="url(#ProgressBar-clipPath)" style="fill-opacity: 0.6;fill: #4c90cf;"/>
// <text x="100" y="20" font-size="20" fill="black">${key}: ${percentage}%</text>
// </g>
// </svg>`
// if more than one, each svg is joined by <br
function renderToSvg(key, percentage) {
return `<svg class="ProgressBar" style="height:25px;width: 300px;display: inline;"><g><defs><clipPath id="ProgressBar-clipPath"><rect width="100%" height="100%" rx="15px" /></clipPath></defs><rect class="ProgressBar-background" width="100%" height="100%" rx="15px" style="fill-opacity: 0.2;fill: cadetblue;"/><rect class="ProgressBar-percentage" width="${percentage}%" height="100%" clip-path="url(#ProgressBar-clipPath)" style="fill-opacity: 0.6;fill: #4c90cf;"/><text x="100" y="20" font-size="20" fill="black">${key}: ${percentage}%</text></g></svg>`;
}

const paraClose = {
"type": "paragraph_close",
"tag": "p",
"attrs": null,
"map": null,
"nesting": -1,
"level": 0,
"children": null,
"content": "",
"markup": "",
"info": "",
"meta": null,
"block": true,
"hidden": false
}

const paraInline = {
"type": "inline",
"tag": "",
"attrs": null,
"map": [
1,
2
],
"nesting": 0,
"level": 1,
"children": [
{
"type": "text",
"tag": "",
"attrs": null,
"map": null,
"nesting": 0,
"level": 0,
"children": null,
"content": "",
"markup": "",
"info": "",
"meta": null,
"block": false,
"hidden": false
}
],
"content": "",
"markup": "",
"info": "",
"meta": null,
"block": true,
"hidden": false
}

module.exports = function renderToPara(tokens, currToken, idx) {
paraInline.content = currToken.content;
paraInline.children[0].content = currToken.content;
currToken = paraInline
// insert paraOpen and paraClose tokens
tokens.splice(idx, 1, pOpen, currToken, paraClose);
module.exports = {
renderToSvg
}

0 comments on commit 6a57abb

Please sign in to comment.