mikel-markdown 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +60 -0
  2. package/index.js +152 -0
  3. package/package.json +30 -0
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # mikel-markdown
2
+
3
+ ![npm version](https://badgen.net/npm/v/mikel-markdown?labelColor=1d2734&color=21bf81)
4
+ ![license](https://badgen.net/github/license/jmjuanes/mikel?labelColor=1d2734&color=21bf81)
5
+
6
+ A plugin for **mikel** that registers a helper for parsing markdown content.
7
+
8
+ ## Limitations
9
+
10
+ **Important**: **This plugin DOES NOT sanitize the output HTML**. The package focuses solely on converting Markdown content to HTML markup. It is crucial to note that the converted HTML may contain potentially unsafe content, such as scripts or malicious code, if the input Markdown includes such elements.
11
+
12
+ If you plan to render user-generated Markdown content as HTML in a web application, it is strongly recommended to use a separate HTML sanitization library, such as [DOMPurify](https://github.com/cure53/DOMPurify), to ensure that the resulting HTML is safe for rendering and doesn't pose a security risk.
13
+
14
+ Always exercise caution when incorporating user-generated content into your web application and take appropriate measures to prevent cross-site scripting (XSS) attacks and other security vulnerabilities.
15
+
16
+ Please be mindful of this limitation and take the necessary precautions to protect your application's security when using this package.
17
+
18
+ ## Installation
19
+
20
+ You can install this plugin using npm or yarn:
21
+
22
+ ```bash
23
+ ## install using YARN
24
+ $ yarn add mikel-markdown
25
+
26
+ ## install using NPM
27
+ $ npm install --save mikel-markdown
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ Import the `mikel-markdown` package:
33
+
34
+ ```javascript
35
+ import mikel from "mikel";
36
+ import mikelMarkdown from "mikel-markdown";
37
+ ```
38
+
39
+ In your template, use the `{{#markdown}}` helper in the markdown code you want to convert:
40
+
41
+ ```javascript
42
+ const template = `{{#markdown}}Hello **world**{{/markdown}}`;
43
+ ```
44
+
45
+ Include the plugin using the `use` method of mikel:
46
+
47
+ ```javascript
48
+ const m = mikel.create(template);
49
+
50
+ m.use(mikelMarkdown());
51
+ ```
52
+
53
+ And finally, compile your template:
54
+ ```javascript
55
+ const result = m({}); // --> 'Hello <strong>world</strong>'
56
+ ```
57
+
58
+ ## License
59
+
60
+ Licensed under the [MIT License](../../LICENSE).
package/index.js ADDED
@@ -0,0 +1,152 @@
1
+ const escape = str => encodeURIComponent(str);
2
+
3
+ // @description custom method to render the provided tag and content
4
+ const render = (tag, props = {}, content = "") => {
5
+ const attrs = Object.keys(props).filter(k => !!props[k]).map(k => `${k}="${props[k]}"`);
6
+ if (tag === "hr" || tag === "img") {
7
+ return `<${[tag, ...attrs].join(" ")} />`;
8
+ }
9
+ return `<${[tag, ...attrs].join(" ")}>${content}</${tag}>`;
10
+ };
11
+
12
+ const expressions = {
13
+ pre: {
14
+ regex: /(?:^```(?:[^\n]*)\n([\s\S]*?)\n``` *$)/gm,
15
+ replace: (args, cn) => render("pre", {class: cn.pre}, escape(args[1])),
16
+ },
17
+ heading: {
18
+ regex: /^(#+)\s+(.*)/gm,
19
+ replace: (args, cn) => render("h" + args[1].length, {class: cn.heading}, args[2]),
20
+ },
21
+ blockquote: {
22
+ regex: /^[\s]*>\s(.*)/gm,
23
+ replace: (args, cn) => render("blockquote", {class: cn.blockquote}, args[1]),
24
+ },
25
+ code: {
26
+ regex: /`([^`]*?)`/g,
27
+ replace: (args, cn) => render("code", {class: cn.code}, escape(args[1])),
28
+ },
29
+ image: {
30
+ regex: /!\[([^\]]*?)\]\(([^)]*?)\)/g,
31
+ replace: (args, cn) => render("img", {class: cn.image, alt: args[1], src: args[2]}),
32
+ },
33
+ table: {
34
+ regex: /^\|((?: +[^\n|]+ +\|?)+)\| *\n\|((?: *[:]?[-]+[:]? *\|?)+)\| *\n((?:^\|(?: +[^\n|]+ +\|?)+\| *\n)+)\n/gm,
35
+ replace: (args, cn) => {
36
+ // args[1] --> table header
37
+ // args[3] --> table body
38
+ const head = args[1].trim().split("|").map(c => {
39
+ return render("td", {class: cn.tableColumn}, c.trim());
40
+ });
41
+ const body = args[3].replace(/\r/g, "").split("\n").map(line => {
42
+ line = line.trim().replace(/^\|/m, "").replace(/\|$/m, "").trim();
43
+ if (line.length === 0) {
44
+ return "";
45
+ }
46
+ const cols = line.split("|").map(c => {
47
+ return render("td", {class: cn.tableColumn}, c.trim());
48
+ });
49
+ return render("tr", {class: cn.tableRow}, cols.join(""));
50
+ });
51
+ const thead = render("thead", {class: cn.tableHead}, "<tr>" + head.join("") + "</tr>");
52
+ const tbody = render("tbody", {class: cn.tableBody}, body.join(""));
53
+ return render("table", {class: cn.table}, thead + tbody);
54
+ },
55
+ },
56
+ link: {
57
+ regex: /\[(.*?)\]\(([^\t\n\s]*?)\)/gm,
58
+ replace: (args, cn) => render("a", {class: cn.link, href: args[2]}, args[1]),
59
+ },
60
+ rule: {
61
+ regex: /^.*?(?:---|-\s-\s-|\*\s\*\s\*)/gm,
62
+ replacement: (args, cn) => render("hr", {class: cn.rule}),
63
+ },
64
+ list: {
65
+ regex: /^[\t\s]*?(?:-|\+|\*)\s(.*)/gm,
66
+ replace: (args, cn) => {
67
+ return render("ul", {class: cn.list}, render("li", {class: cn.listItem}, args[1]));
68
+ },
69
+ afterRegex: /(<\/ul>\n(?:.*)<ul ?(?:class=".*")?>*)+/g
70
+ },
71
+ orderedList: {
72
+ regex: /^[\t\s]*?(?:\d(?:\)|\.))\s(.*)/gm,
73
+ replace: (args, cn) => {
74
+ return render("ol", {class: cn.list}, render("li", {class: cn.listItem}, args[1]));
75
+ },
76
+ afterRegex: /(<\/ol>\n(?:.*)<ol ?(?:class=".*")?>*)+/g
77
+ },
78
+ strong: {
79
+ regex: /(?:\*\*|__)([^\n]+?)(?:\*\*|__)/g,
80
+ replace: (args, cn) => render("strong", {class: cn.strong}, args[1]),
81
+ },
82
+ emphasis: {
83
+ regex: /(?:\*|_)([^\n]+?)(?:\*|_)/g,
84
+ replace: (args, cn) => render("em", {class: cn.emphasis}, args[1]),
85
+ },
86
+ paragraph: {
87
+ regex: /^((?:.+(?:\n|$))+)/gm,
88
+ replace: (args, cn) => {
89
+ const line = args[0].trim();
90
+ // Check if the line starts with a block tag
91
+ if (/^\<\/?(ul|ol|bl|h\d|p|div|sty|scr).*/.test(line.slice(0, 4)) === true) {
92
+ return line;
93
+ }
94
+ return render("p", {class: cn.paragraph}, line.replace(/\n/g, ""));
95
+ }
96
+ }
97
+ };
98
+
99
+ // @description markdown parser
100
+ const parser = (str, options = {}) => {
101
+ const classNames = options?.classNames || {}; // custom classNames
102
+ const ignoredBlocks = []; // chunks to ignore
103
+ str = str.replace(/\r\n/g, "\n");
104
+ // replace all <script> tags
105
+ // str = str.replace(/<script[^\0]*?>([^\0]*?)<\/script>/gmi, function (match, content) {
106
+ // return "&lt;script&gt;" + content + "&lt;/script&gt;";
107
+ // });
108
+ // replace all expressions
109
+ Object.keys(expressions).forEach(key => {
110
+ str = str.replace(expressions[key].regex, (...args) => {
111
+ const value = expressions[key].replace(args, classNames);
112
+ if (key === "pre") {
113
+ ignoredBlocks.push(value);
114
+ return `<pre>{IGNORED%${(ignoredBlocks.length - 1)}}</pre>\n`;
115
+ }
116
+ // other value --> return the value provided by the renderer
117
+ return value;
118
+ });
119
+ // check for regex to apply after the main refex
120
+ if (typeof expressions[key].afterRegex !== "undefined") {
121
+ str = str.replace(expressions[key].afterRegex, "");
122
+ }
123
+ });
124
+ // Replace all line breaks expressions
125
+ // str = str.replace(/^\n\n+/gm, function () {
126
+ // return renderer("br", {});
127
+ // });
128
+ // Replace all the ignored blocks
129
+ for (let i = ignoredBlocks.length - 1; i >= 0; i--) {
130
+ str = str.replace(`<pre>{IGNORED%${i}}</pre>`, ignoredBlocks[i]);
131
+ }
132
+ return str;
133
+ };
134
+
135
+ // @description markdown plugin
136
+ // @param options {object} options for this plugin
137
+ const markdownPlugin = (options = {}) => {
138
+ return {
139
+ helpers: {
140
+ markdown: params => {
141
+ return parser(params.fn(params.data) || "", options);
142
+ },
143
+ },
144
+ };
145
+ };
146
+
147
+ // assign additional options for this plugin
148
+ markdownPlugin.parser = parser;
149
+ markdownPlugin.expressions = expressions;
150
+
151
+ // export the plugin
152
+ export default markdownPlugin;
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "mikel-markdown",
3
+ "description": "A mikel helper for parsing markdown content.",
4
+ "version": "0.19.0",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": {
8
+ "name": "Josemi Juanes",
9
+ "email": "hello@josemi.xyz"
10
+ },
11
+ "repository": "https://github.com/jmjuanes/mikel",
12
+ "bugs": "https://github.com/jmjuanes/mikel/issues",
13
+ "exports": {
14
+ ".": "./index.js",
15
+ "./index.js": "./index.js",
16
+ "./package.json": "./package.json"
17
+ },
18
+ "scripts": {
19
+ "test": "node ./test.js"
20
+ },
21
+ "files": [
22
+ "README.md",
23
+ "index.js"
24
+ ],
25
+ "keywords": [
26
+ "mikel",
27
+ "plugin",
28
+ "markdown"
29
+ ]
30
+ }