aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugene Sokolov <eug-vs@keemail.me>2021-01-14 19:27:47 +0200
committerGitHub <noreply@github.com>2021-01-14 19:27:47 +0200
commite1d1bb966cca5e1541d5703505cfa83e6ae12ec0 (patch)
treed803e0e891fc6c14ff2a390beeabbe59d0ad8485
parentd23df805ed5d0e78c604d5c9f81f21bbc6a06288 (diff)
parentef9dfdacf17c62254331140a40a508549aa725b4 (diff)
downloadreact-benzin-master.tar.gz
Merge pull request #22 from eug-vs/develop4.1.0master
Release 4.1.0
-rw-r--r--README.md41
-rw-r--r--package-lock.json338
-rw-r--r--package.json4
-rw-r--r--src/demo/content.md13
-rw-r--r--src/index.tsx131
-rw-r--r--src/lib/ContentSection/ContentSection.tsx51
-rw-r--r--src/lib/Markdown/CodeBlock.tsx13
-rw-r--r--src/lib/Markdown/Content.tsx90
-rw-r--r--src/lib/Markdown/Heading.tsx33
-rw-r--r--src/lib/Markdown/Image.tsx12
-rw-r--r--src/lib/Markdown/InlineCode.tsx18
-rw-r--r--src/lib/Markdown/Markdown.tsx69
-rw-r--r--src/lib/Markdown/Section.tsx61
-rw-r--r--src/lib/Markdown/SyntacticSpan.tsx96
-rw-r--r--src/lib/Markdown/Text.tsx13
-rw-r--r--src/lib/Markdown/emojilib.d.ts2
-rw-r--r--src/lib/Markdown/types.d.ts7
-rw-r--r--src/lib/Markdown/types.ts4
-rw-r--r--src/lib/index.ts4
19 files changed, 580 insertions, 420 deletions
diff --git a/README.md b/README.md
index f2473c4..08cefa6 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
<h1 align="center"> BENZIN </h1>
<p align="center">
- <a href="https://reactjs.org/">React</a> <a href="https://material-ui.com/">Material</a> library for content-display featuring Markdown rendering.
+ <a href="https://reactjs.org/">React</a> <a href="https://material-ui.com/">Material</a> library for content-display featuring context-aware Markdown rendering.
</p>
<p align="center">
<img src="https://img.shields.io/npm/v/react-benzin?logo=npm" />
@@ -18,14 +18,41 @@ You can easily add **BENZIN** to your project with `npm`:
```bash
$ npm install react-benzin
```
-**TIP:** **BENZIN** is designed to work in Material-UI environment, so it's best to use them together:
+**BENZIN** is designed to work in Material-UI environment, so it's best to use them together:
```bash
-$ npm @material-ui/core
+$ npm install @material-ui/core
```
-![Preview screenshot](https://user-images.githubusercontent.com/51545008/95653266-209d0900-0b00-11eb-9a0a-f3aa81c878e5.png)
+## Usage
+One can use `$variableName` syntax (with backticks around!) to access `context.variableName` from within markdown.
-You can find a minimal usage example [here](src/index.tsx).
+Consider following markdown **source.md:**
+```markdown
+# Hello, world!
+ - I can render markdown
+ - My name is `$name`
+
+I can also display button below:
+
+`$button`
+```
+
+We can render it using `<Markdown>` component:
+```typescript
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Markdown } from 'react-benzin';
+import { Button } from '@material-ui/core';
+import source from './source.md'; // This import resolves into file url
+
+const name = 'John Doe';
+const button = <Button variant="contained" color="primary">Click me</Button>;
+
+ReactDOM.render(<Markdown url={source} context={{ name, button }}/>, document.getElementById('root'));
+```
+
+
+![Preview screenshot](https://user-images.githubusercontent.com/51545008/104624821-182b1000-56a5-11eb-8d25-728f85206b26.png)
# Development
## Running live demo
@@ -53,3 +80,7 @@ This command will generate `dist/` folder ready for distribution, which you of c
Publishing to `npm` is fully automated through **CircleCI** - package is deployed on every push into `master`. Therefore only release *PR*'s should be merged into `master` branch.
Deploying to `gh-pages` is automatically performed on every commit into `develop` branch.
+
+# See also
+ - [remark](https://github.com/remarkjs/remark)
+ - [MDX](https://github.com/mdx-js/mdx)
diff --git a/package-lock.json b/package-lock.json
index a147a70..fced57b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "react-benzin",
- "version": "4.0.2",
+ "version": "4.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -2374,6 +2374,14 @@
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
"dev": true
},
+ "@types/mdast": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.3.tgz",
+ "integrity": "sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw==",
+ "requires": {
+ "@types/unist": "*"
+ }
+ },
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@@ -2492,6 +2500,11 @@
}
}
},
+ "@types/unist": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz",
+ "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ=="
+ },
"@types/webpack": {
"version": "4.41.25",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.25.tgz",
@@ -3605,6 +3618,11 @@
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
"dev": true
},
+ "bail": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz",
+ "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ=="
+ },
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -4191,6 +4209,21 @@
"integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
"dev": true
},
+ "character-entities": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
+ "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw=="
+ },
+ "character-entities-legacy": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
+ "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA=="
+ },
+ "character-reference-invalid": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
+ "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg=="
+ },
"chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
@@ -5215,7 +5248,6 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
- "dev": true,
"requires": {
"ms": "2.1.2"
}
@@ -5850,8 +5882,7 @@
"entities": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
- "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==",
- "dev": true
+ "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w=="
},
"errno": {
"version": "0.1.7",
@@ -6844,8 +6875,7 @@
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
- "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
- "dev": true
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"extend-shallow": {
"version": "3.0.2",
@@ -7608,6 +7638,11 @@
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
"dev": true
},
+ "gemoji": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/gemoji/-/gemoji-6.1.0.tgz",
+ "integrity": "sha512-MOlX3doQ1fsfzxQX8Y+u6bC5Ssc1pBUBIPVyrS69EzKt+5LIZAOm0G5XGVNhwXFgkBF3r+Yk88ONyrFHo8iNFA=="
+ },
"gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -8093,6 +8128,83 @@
}
}
},
+ "html-to-react": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.4.5.tgz",
+ "integrity": "sha512-KONZUDFPg5OodWaQu2ymfkDmU0JA7zB1iPfvyHehTmMUZnk0DS7/TyCMTzsLH6b4BvxX15g88qZCXFhJWktsmA==",
+ "requires": {
+ "domhandler": "^3.3.0",
+ "htmlparser2": "^5.0",
+ "lodash.camelcase": "^4.3.0",
+ "ramda": "^0.27.1"
+ },
+ "dependencies": {
+ "dom-serializer": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz",
+ "integrity": "sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==",
+ "requires": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^4.0.0",
+ "entities": "^2.0.0"
+ },
+ "dependencies": {
+ "domhandler": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz",
+ "integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==",
+ "requires": {
+ "domelementtype": "^2.1.0"
+ }
+ }
+ }
+ },
+ "domelementtype": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz",
+ "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w=="
+ },
+ "domhandler": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz",
+ "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==",
+ "requires": {
+ "domelementtype": "^2.0.1"
+ }
+ },
+ "domutils": {
+ "version": "2.4.4",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.4.4.tgz",
+ "integrity": "sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==",
+ "requires": {
+ "dom-serializer": "^1.0.1",
+ "domelementtype": "^2.0.1",
+ "domhandler": "^4.0.0"
+ },
+ "dependencies": {
+ "domhandler": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz",
+ "integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==",
+ "requires": {
+ "domelementtype": "^2.1.0"
+ }
+ }
+ }
+ },
+ "htmlparser2": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-5.0.1.tgz",
+ "integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==",
+ "requires": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^3.3.0",
+ "domutils": "^2.4.2",
+ "entities": "^2.0.0"
+ }
+ }
+ }
+ },
"html-webpack-plugin": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz",
@@ -8657,6 +8769,20 @@
"kind-of": "^3.0.2"
}
},
+ "is-alphabetical": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
+ "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg=="
+ },
+ "is-alphanumerical": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
+ "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
+ "requires": {
+ "is-alphabetical": "^1.0.0",
+ "is-decimal": "^1.0.0"
+ }
+ },
"is-arguments": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz",
@@ -8738,6 +8864,11 @@
"integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==",
"dev": true
},
+ "is-decimal": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
+ "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw=="
+ },
"is-descriptor": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
@@ -8802,6 +8933,11 @@
"is-extglob": "^2.1.1"
}
},
+ "is-hexadecimal": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
+ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw=="
+ },
"is-in-browser": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
@@ -10517,6 +10653,11 @@
"integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=",
"dev": true
},
+ "lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
+ },
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -10647,6 +10788,31 @@
"safe-buffer": "^5.1.2"
}
},
+ "mdast-add-list-metadata": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz",
+ "integrity": "sha512-fB/VP4MJ0LaRsog7hGPxgOrSL3gE/2uEdZyDuSEnKCv/8IkYHiDkIQSbChiJoHyxZZXZ9bzckyRk+vNxFzh8rA==",
+ "requires": {
+ "unist-util-visit-parents": "1.1.2"
+ }
+ },
+ "mdast-util-from-markdown": {
+ "version": "0.8.4",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.4.tgz",
+ "integrity": "sha512-jj891B5pV2r63n2kBTFh8cRI2uR9LQHsXG1zSDqfhXkIlDzrTcIlbB5+5aaYEkl8vOPIOPLf8VT7Ere1wWTMdw==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-string": "^2.0.0",
+ "micromark": "~2.11.0",
+ "parse-entities": "^2.0.0",
+ "unist-util-stringify-position": "^2.0.0"
+ }
+ },
+ "mdast-util-to-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz",
+ "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w=="
+ },
"mdn-data": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz",
@@ -10736,6 +10902,15 @@
"integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==",
"dev": true
},
+ "micromark": {
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.2.tgz",
+ "integrity": "sha512-IXuP76p2uj8uMg4FQc1cRE7lPCLsfAXuEfdjtdO55VRiFO1asrCSQ5g43NmPqFtRwzEnEhafRVzn2jg0UiKArQ==",
+ "requires": {
+ "debug": "^4.0.0",
+ "parse-entities": "^2.0.0"
+ }
+ },
"micromatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
@@ -11667,6 +11842,19 @@
"safe-buffer": "^5.1.1"
}
},
+ "parse-entities": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz",
+ "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
+ "requires": {
+ "character-entities": "^1.0.0",
+ "character-entities-legacy": "^1.0.0",
+ "character-reference-invalid": "^1.0.0",
+ "is-alphanumerical": "^1.0.0",
+ "is-decimal": "^1.0.0",
+ "is-hexadecimal": "^1.0.0"
+ }
+ },
"parse-json": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
@@ -13294,6 +13482,11 @@
"performance-now": "^2.1.0"
}
},
+ "ramda": {
+ "version": "0.27.1",
+ "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz",
+ "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw=="
+ },
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -13503,6 +13696,23 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "react-markdown": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-5.0.3.tgz",
+ "integrity": "sha512-jDWOc1AvWn0WahpjW6NK64mtx6cwjM4iSsLHJPNBqoAgGOVoIdJMqaKX4++plhOtdd4JksdqzlDibgPx6B/M2w==",
+ "requires": {
+ "@types/mdast": "^3.0.3",
+ "@types/unist": "^2.0.3",
+ "html-to-react": "^1.3.4",
+ "mdast-add-list-metadata": "1.0.1",
+ "prop-types": "^15.7.2",
+ "react-is": "^16.8.6",
+ "remark-parse": "^9.0.0",
+ "unified": "^9.0.0",
+ "unist-util-visit": "^2.0.0",
+ "xtend": "^4.0.1"
+ }
+ },
"react-refresh": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
@@ -13800,6 +14010,23 @@
"integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=",
"dev": true
},
+ "remark-gemoji": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/remark-gemoji/-/remark-gemoji-6.0.0.tgz",
+ "integrity": "sha512-LDW2h6QqNzAbAcOjscgfkJW9/8TGBasBe/ji+3mCxHlJdhF2IEXFSmm/3tdEPP1JJDZ4y+Ea+xlFQ4tOIU9WvA==",
+ "requires": {
+ "gemoji": "^6.0.0",
+ "unist-util-visit": "^2.0.0"
+ }
+ },
+ "remark-parse": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz",
+ "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==",
+ "requires": {
+ "mdast-util-from-markdown": "^0.8.0"
+ }
+ },
"remove-trailing-separator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
@@ -16071,6 +16298,11 @@
"escape-string-regexp": "^1.0.2"
}
},
+ "trough": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz",
+ "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA=="
+ },
"tryer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz",
@@ -16222,6 +16454,31 @@
"integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==",
"dev": true
},
+ "unified": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz",
+ "integrity": "sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==",
+ "requires": {
+ "bail": "^1.0.0",
+ "extend": "^3.0.0",
+ "is-buffer": "^2.0.0",
+ "is-plain-obj": "^2.0.0",
+ "trough": "^1.0.0",
+ "vfile": "^4.0.0"
+ },
+ "dependencies": {
+ "is-buffer": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
+ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="
+ },
+ "is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA=="
+ }
+ }
+ },
"union-value": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
@@ -16273,6 +16530,45 @@
"crypto-random-string": "^1.0.0"
}
},
+ "unist-util-is": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.4.tgz",
+ "integrity": "sha512-3dF39j/u423v4BBQrk1AQ2Ve1FxY5W3JKwXxVFzBODQ6WEvccguhgp802qQLKSnxPODE6WuRZtV+ohlUg4meBA=="
+ },
+ "unist-util-stringify-position": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz",
+ "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==",
+ "requires": {
+ "@types/unist": "^2.0.2"
+ }
+ },
+ "unist-util-visit": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz",
+ "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==",
+ "requires": {
+ "@types/unist": "^2.0.0",
+ "unist-util-is": "^4.0.0",
+ "unist-util-visit-parents": "^3.0.0"
+ },
+ "dependencies": {
+ "unist-util-visit-parents": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz",
+ "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==",
+ "requires": {
+ "@types/unist": "^2.0.0",
+ "unist-util-is": "^4.0.0"
+ }
+ }
+ }
+ },
+ "unist-util-visit-parents": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz",
+ "integrity": "sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q=="
+ },
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
@@ -16522,6 +16818,33 @@
"extsprintf": "^1.2.0"
}
},
+ "vfile": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz",
+ "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==",
+ "requires": {
+ "@types/unist": "^2.0.0",
+ "is-buffer": "^2.0.0",
+ "unist-util-stringify-position": "^2.0.0",
+ "vfile-message": "^2.0.0"
+ },
+ "dependencies": {
+ "is-buffer": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
+ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="
+ }
+ }
+ },
+ "vfile-message": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz",
+ "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==",
+ "requires": {
+ "@types/unist": "^2.0.0",
+ "unist-util-stringify-position": "^2.0.0"
+ }
+ },
"vm-browserify": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
@@ -18164,8 +18487,7 @@
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
- "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
- "dev": true
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
"y18n": {
"version": "4.0.0",
diff --git a/package.json b/package.json
index 05fa52e..dae9647 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-benzin",
- "version": "4.0.2",
+ "version": "4.1.0",
"description": "A powerful React Material components library.",
"homepage": "https://eug-vs.github.io/react-benzin",
"main": "dist/index.js",
@@ -24,6 +24,8 @@
"emojilib": "^2.4.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
+ "react-markdown": "^5.0.3",
+ "remark-gemoji": "^6.0.0",
"typeface-roboto": "1.1.13"
},
"devDependencies": {
diff --git a/src/demo/content.md b/src/demo/content.md
new file mode 100644
index 0000000..01d848f
--- /dev/null
+++ b/src/demo/content.md
@@ -0,0 +1,13 @@
+## Markdown
+[Markdown file](${url}) *(...`$fileName`)* that you can see on the left was parsed and rendered by **BENZIN**! :rocket:
+Switch between tabs on the header to explore other markdown templates. :recycle:
+Templates on the left are being loaded from the [GitHub](https://github.com), though this pane is generated from plaintext. :pen:
+## How do I use this feature?
+```
+import Markdown from 'react-benzin';
+const data = '# Header\nHello, *world!*';
+ReactDOM.render(<Markdown data={data}/>, document.getElementById('root'));
+```
+
+`$tryButton`
+
diff --git a/src/index.tsx b/src/index.tsx
index 63f7938..e37e7aa 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -9,11 +9,12 @@ import {
import {
Benzin,
Markdown,
- ContentSection,
+ Heading,
} from './lib';
import Header from './demo/Header/Header';
import Window from './demo/Window/Window';
+import content from './demo/content.md';
import icon from './assets/icon.svg';
const useStyles = makeStyles(theme => ({
@@ -32,7 +33,7 @@ const Icon = <img src={icon} width="32px" height="37px" alt="logo" />;
const headerContents = {
home: null,
- spacevim: null,
+ dotfiles: null,
'material-ui': null,
custom: null,
'live preview': null,
@@ -40,8 +41,8 @@ const headerContents = {
const pageMap: Record<string, string> = {
home: 'https://raw.githubusercontent.com/eug-vs/react-benzin/develop/README.md',
- spacevim: 'https://raw.githubusercontent.com/spacevim/spacevim/master/README.md',
'material-ui': 'https://raw.githubusercontent.com/mui-org/material-ui/master/README.md',
+ dotfiles: 'https://raw.githubusercontent.com/eug-vs/dotfiles/master/.github/README.md',
};
@@ -55,24 +56,23 @@ const CustomPage: React.FC = () => {
return (
<>
- <ContentSection sectionName="Render custom markdown document" level={2}>
- <p>
- This should be a link to a valid markdown file. Response should give the file contents.
- If you copy README file from GitHub, make sure you provide link to raw view.
- </p>
- <p>
- <TextField
- fullWidth
- inputRef={inputEl}
- variant="outlined"
- color="secondary"
- label="Markdown url"
- />
- </p>
- <Button variant="contained" color="secondary" onClick={handleParseUrl}>
- Render!
- </Button>
- </ContentSection>
+ <Heading level={2}>Render custom markdown document</Heading>
+ <p>
+ This should be a link to a valid markdown file. Response should give the file contents.
+ If you copy README file from GitHub, make sure you provide link to raw view.
+ </p>
+ <p>
+ <TextField
+ fullWidth
+ inputRef={inputEl}
+ variant="outlined"
+ color="secondary"
+ label="Markdown url"
+ />
+ </p>
+ <Button variant="contained" color="secondary" onClick={handleParseUrl}>
+ Render!
+ </Button>
<Markdown url={url} />
</>
);
@@ -91,29 +91,28 @@ const LivePreviewPage: React.FC<LivePropTypes> = ({ setLivePreviewData }) => {
return (
<>
- <ContentSection sectionName="Markdown live preview" level={2}>
- <p>
- Start typing and see your text rendered on the left window!
- You can find the list of all Markdown features
- {' '}
- <Link href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet">
- here
- </Link>
- . (some of them are yet in progress).
- We recommend starting with # Header.
- </p>
- <p>
- <TextField
- fullWidth
- multiline
- inputRef={inputEl}
- variant="outlined"
- color="primary"
- label="Markdown"
- onChange={handleRender}
- />
- </p>
- </ContentSection>
+ <Heading level={2}>Markdown live preview</Heading>
+ <p>
+ Start typing and see your text rendered on the left window!
+ You can find the list of all Markdown features
+ {' '}
+ <Link href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet">
+ here
+ </Link>
+ . (some of them are yet in progress).
+ We recommend starting with # Header.
+ </p>
+ <p>
+ <TextField
+ fullWidth
+ multiline
+ inputRef={inputEl}
+ variant="outlined"
+ color="primary"
+ label="Markdown"
+ onChange={handleRender}
+ />
+ </p>
</>
);
};
@@ -130,26 +129,26 @@ const App: React.FC = () => {
const url = pageMap[page];
const fileName = url?.slice(url.lastIndexOf('/') + 1);
- const info = [
- /* eslint-disable max-len */
- `## Markdown\n [Markdown file](${url}) *(...${fileName})* that you can see on the left was parsed and rendered by **BENZIN**! :rocket:`,
- 'Switch between tabs on the header to explore other markdown templates. :recycle: ',
- 'Templates on the left are being loaded from the [GitHub](https://github.com), though this pane is generated from plaintext. :pen:',
- '## How do I use this feature?',
- '```',
- 'import Markdown from \'react-benzin\';',
- 'const data = \'# Header\\nHello, *world!*\';',
- 'ReactDOM.render(<Markdown data={data}/>, document.getElementById(\'root\'));',
- '```',
- /* eslint-enable max-len */
- ].join('\n');
let primaryWindowContent = <Markdown url={url} />;
if (page === 'custom') primaryWindowContent = <CustomPage />;
else if (page === 'live preview') {
- primaryWindowContent = <Markdown data={livePreviewData || '# Start typing in the right window!'} />;
+ primaryWindowContent = <Markdown source={livePreviewData || '# Start typing in the right window!'} />;
}
+ const tryButton = (
+ <p className={classes.promoButton}>
+ <Button
+ variant="contained"
+ color="primary"
+ size="large"
+ onClick={handleGoLivePreview}
+ >
+ Try it yourself!
+ </Button>
+ </p>
+ );
+
return (
<Benzin>
<Header
@@ -169,21 +168,7 @@ const App: React.FC = () => {
{
(page === 'live preview')
? <LivePreviewPage setLivePreviewData={setLivePreviewData} />
- : (
- <>
- <Markdown data={info} />
- <p className={classes.promoButton}>
- <Button
- variant="contained"
- color="primary"
- size="large"
- onClick={handleGoLivePreview}
- >
- Try it yourself!
- </Button>
- </p>
- </>
- )
+ : <Markdown url={content} context={{ tryButton, fileName }} />
}
</div>
</Window>
diff --git a/src/lib/ContentSection/ContentSection.tsx b/src/lib/ContentSection/ContentSection.tsx
deleted file mode 100644
index f18684c..0000000
--- a/src/lib/ContentSection/ContentSection.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import React from 'react';
-
-import {
- Typography,
- Divider,
- makeStyles,
- useMediaQuery,
- Theme,
-} from '@material-ui/core';
-
-
-interface PropTypes {
- sectionName: string;
- level?: number;
-}
-
-const useStyles = makeStyles(theme => ({
- content: {
- [theme.breakpoints.up('md')]: {
- padding: theme.spacing(2, 2, 1, 3),
- },
- [theme.breakpoints.down('sm')]: {
- padding: theme.spacing(2, 0),
- },
- marginBottom: theme.spacing(1),
- },
-}));
-
-const ContentSection: React.FC<PropTypes> = ({ sectionName, children, level = 0 }) => {
- const classes = useStyles();
- const isMobile = useMediaQuery((theme: Theme) => theme.breakpoints.down('sm'));
-
- let adjustedLevel = level + 2; // Make everything smaller
- if (adjustedLevel > 6) adjustedLevel = 6;
-
- type Variant = 'h3' | 'h4' | 'h5' | 'h6';
- const variant: Variant = `h${adjustedLevel}` as Variant;
-
- return (
- <>
- <Typography variant={variant}>{sectionName}</Typography>
- <Divider variant={isMobile ? 'fullWidth' : 'middle'} />
- <Typography component="div" className={classes.content}>
- {children}
- </Typography>
- </>
- );
-};
-
-
-export default ContentSection;
diff --git a/src/lib/Markdown/CodeBlock.tsx b/src/lib/Markdown/CodeBlock.tsx
index 394458e..7431881 100644
--- a/src/lib/Markdown/CodeBlock.tsx
+++ b/src/lib/Markdown/CodeBlock.tsx
@@ -1,8 +1,9 @@
import React from 'react';
-import { Paper } from '@material-ui/core';
+import { Paper, makeStyles } from '@material-ui/core';
-import { makeStyles } from '@material-ui/core/styles';
-import { ParserPropTypes } from './types';
+interface PropTypes {
+ value: string;
+}
const useStyles = makeStyles(theme => ({
root: {
@@ -14,11 +15,13 @@ const useStyles = makeStyles(theme => ({
},
}));
-const CodeBlock: React.FC<ParserPropTypes> = ({ rawLines }) => {
+const CodeBlock: React.FC<PropTypes> = ({ value }) => {
const classes = useStyles();
return (
<Paper variant="outlined" className={classes.root}>
- {rawLines.map(line => <pre>{line}</pre>)}
+ <pre>
+ {value}
+ </pre>
</Paper>
);
};
diff --git a/src/lib/Markdown/Content.tsx b/src/lib/Markdown/Content.tsx
deleted file mode 100644
index 88409fa..0000000
--- a/src/lib/Markdown/Content.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import React from 'react';
-
-import CodeBlock from './CodeBlock';
-import Text from './Text';
-import { ParserPropTypes } from './types';
-
-
-const denotesCodeBlock = (line: string): boolean => {
- return line.match(/^\s*```.*$/) !== null;
-};
-
-const denotesDottedList = (line: string): boolean => {
- return line.match(/^ ?[-*] .*$/) !== null;
-};
-
-const denotesOpenHtml = (line: string): string => {
- const regex = /<([^/\s]*)[^<]*[^/]>/g;
- const match = regex.exec(line);
- return match ? match[1] : '';
-};
-
-const denotesClosingHtml = (line: string, tag: string): boolean => {
- const regex = new RegExp(`</${tag}[^<]*>`);
- return line.match(regex) !== null;
-};
-
-const denotesSelfClosingHtml = (line: string): string[] | null => {
- const regex = /(<[^/\s]*[^<]*\/>)/g;
- return line.match(regex);
-};
-
-const declaresNoLineBreak = (line: string): boolean => {
- return line.match(/\\\|$/) !== null;
-};
-
-const Content: React.FC<ParserPropTypes> = ({ rawLines }) => {
- if (!rawLines.length) return null;
-
- const line = rawLines.splice(0, 1)[0];
-
- let buffer;
- if (denotesCodeBlock(line)) {
- const closeIndex = rawLines.findIndex(rawLine => denotesCodeBlock(rawLine));
- const codeBlockLines = rawLines.splice(0, closeIndex + 1).slice(0, closeIndex);
- buffer = <CodeBlock rawLines={codeBlockLines} />;
- } else if (denotesDottedList(line)) {
- const closeIndex = rawLines.findIndex(rawLine => !denotesDottedList(rawLine));
- const dottedListLines = rawLines.splice(0, closeIndex).slice(0, closeIndex);
- dottedListLines.unshift(line);
- buffer = <ul>{dottedListLines.map(li => <li><Text line={li.slice(2)} /></li>)}</ul>;
- } else if ((buffer = denotesOpenHtml(line))) {
- const tag = buffer;
- const closeIndex = denotesClosingHtml(line, tag) ? -1 : rawLines.findIndex(
- rawLine => denotesClosingHtml(rawLine, tag),
- );
- const htmlLines = rawLines.splice(0, closeIndex + 1);
- htmlLines.unshift(line);
- buffer = <div dangerouslySetInnerHTML={{ __html: htmlLines.join('\n') }} />;
- } else if ((buffer = denotesSelfClosingHtml(line)) !== null) {
- const match = buffer[0];
- const [before, after] = line.split(match);
- buffer = (
- <>
- <Text line={before} />
- <div dangerouslySetInnerHTML={{ __html: match }} />
- <Text line={after} />
- </>
- );
- } else if (declaresNoLineBreak(line)) {
- const closeIndex = rawLines.findIndex(rawLine => !declaresNoLineBreak(rawLine));
- const lineBreakLines = rawLines.splice(0, closeIndex).map(rawLine => rawLine.slice(0, -2));
- lineBreakLines.unshift(line.slice(0, -2));
- lineBreakLines.push(rawLines.splice(0, 1)[0]);
- buffer = <p>{lineBreakLines.map(lineBreakLine => <Text line={lineBreakLine} />)}</p>;
- } else if (denotesClosingHtml(line, '')) {
- buffer = null;
- } else {
- buffer = <p><Text line={line} /></p>;
- }
-
- return (
- <>
- { buffer }
- <Content rawLines={rawLines} />
- </>
- );
-};
-
-export default Content;
-
diff --git a/src/lib/Markdown/Heading.tsx b/src/lib/Markdown/Heading.tsx
new file mode 100644
index 0000000..cc0b709
--- /dev/null
+++ b/src/lib/Markdown/Heading.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { Typography, Divider, makeStyles } from '@material-ui/core';
+
+interface PropTypes {
+ level: number;
+}
+
+type Variant = 'h3' | 'h4' | 'h5' | 'h6';
+
+const useStyles = makeStyles(theme => ({
+ root: {
+ padding: theme.spacing(2, 0, 1, 0),
+ },
+}));
+
+const Heading: React.FC<PropTypes> = ({ children, level }) => {
+ const classes = useStyles();
+
+ let adjustedLevel = level + 2; // Make everything smaller
+ if (adjustedLevel > 6) adjustedLevel = 6;
+
+ const variant: Variant = `h${adjustedLevel}` as Variant;
+
+ return (
+ <div className={classes.root}>
+ <Typography variant={variant}>{children}</Typography>
+ <Divider variant="middle" />
+ </div>
+ );
+};
+
+export default Heading;
+
diff --git a/src/lib/Markdown/Image.tsx b/src/lib/Markdown/Image.tsx
new file mode 100644
index 0000000..472d6f0
--- /dev/null
+++ b/src/lib/Markdown/Image.tsx
@@ -0,0 +1,12 @@
+import React from 'react';
+
+interface PropTypes {
+ src: string;
+ alt: string;
+}
+
+const Image: React.FC<PropTypes> = ({ src, alt }) => {
+ return <img src={src} alt={alt} style={{ maxWidth: '100%', maxHeight: '100%' }} />;
+};
+
+export default Image;
diff --git a/src/lib/Markdown/InlineCode.tsx b/src/lib/Markdown/InlineCode.tsx
new file mode 100644
index 0000000..c51f924
--- /dev/null
+++ b/src/lib/Markdown/InlineCode.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import { makeStyles } from '@material-ui/core';
+
+const useStyles = makeStyles(theme => ({
+ root: {
+ background: theme.palette.background.default,
+ borderRadius: theme.spacing(0.5),
+ padding: theme.spacing(0.5),
+ fontFamily: 'Monospace',
+ },
+}));
+
+const InlineCode: React.FC = ({ children }) => {
+ const classes = useStyles();
+ return <span className={classes.root}>{children}</span>;
+};
+
+export default InlineCode;
diff --git a/src/lib/Markdown/Markdown.tsx b/src/lib/Markdown/Markdown.tsx
index cfcf117..c0389dc 100644
--- a/src/lib/Markdown/Markdown.tsx
+++ b/src/lib/Markdown/Markdown.tsx
@@ -1,33 +1,82 @@
import React, { useState, useEffect } from 'react';
+import { Link, Typography } from '@material-ui/core';
import axios from 'axios';
+import ReactMarkdown from 'react-markdown';
+import emoji from 'remark-gemoji';
-import Section from './Section';
+import CodeBlock from './CodeBlock';
+import InlineCode from './InlineCode';
+import Heading from './Heading';
+import Image from './Image';
interface PropTypes {
- data?: string;
+ source?: string;
url?: string;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ context?: Record<string, any>;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ plugins?: any[]
}
const resolveUrls = (line: string, baseUrl: string): string => line.replace(
/src="(?!http)(.*)"[\s>]/,
(match, url) => `src="${baseUrl}/${url}?sanitize=true"`,
).replace(
- /\[(.*\]?.*)\]\((?!http)(.+?)\)/,
+ /\[(.*\]?.*)\]\((?!http)(.+?)\)/g,
(match, text, url) => `[${text}](${baseUrl}/${url})`,
);
-const Markdown: React.FC<PropTypes> = ({ data, url }) => {
- const [markdown, setMarkdown] = useState<string>(data || '');
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const WrappedInlineCode = (context: Record<string, any>): React.FC => ({ children }) => {
+ if (typeof children === 'string' && children?.startsWith('$')) {
+ const symbol = children.slice(1);
+ return context[symbol] || null;
+ }
+ return <InlineCode>{children}</InlineCode>;
+};
+
+const Markdown: React.FC<PropTypes> = ({
+ children,
+ url,
+ source,
+ context = {},
+ plugins = [],
+}) => {
+ const [markdown, setMarkdown] = useState<string>(source || '');
- if (url) axios.get(url).then(response => setMarkdown(response.data));
+ useEffect(() => {
+ if (url) axios.get(url).then(response => setMarkdown(response.data));
+ }, [url]);
useEffect(() => {
- if (!url) setMarkdown(data || '');
- }, [data, url]);
+ if (source) setMarkdown(source);
+ }, [source]);
+
+ useEffect(() => {
+ if (children && typeof children === 'string') setMarkdown(children);
+ }, [children]);
const baseUrl = url?.slice(0, url.lastIndexOf('/')) || '';
- const lines = markdown.split(/\r?\n/).map(line => resolveUrls(line, baseUrl));
- return <Section rawLines={lines} />;
+ const sanitized = resolveUrls(markdown, baseUrl);
+
+ const renderers = {
+ heading: Heading,
+ code: CodeBlock,
+ link: Link,
+ image: Image,
+ inlineCode: WrappedInlineCode(context),
+ };
+
+ return (
+ <Typography>
+ <ReactMarkdown
+ source={sanitized}
+ renderers={renderers}
+ plugins={[emoji, ...plugins]}
+ allowDangerousHtml
+ />
+ </Typography>
+ );
};
diff --git a/src/lib/Markdown/Section.tsx b/src/lib/Markdown/Section.tsx
deleted file mode 100644
index fb2933d..0000000
--- a/src/lib/Markdown/Section.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import React from 'react';
-import { Typography } from '@material-ui/core';
-import ContentSection from '../ContentSection/ContentSection';
-import Content from './Content';
-import { ParserPropTypes } from './types';
-
-interface PropTypes extends ParserPropTypes {
- level?: number;
-}
-
-interface MapperPropTypes extends PropTypes {
- SectionComponent: React.FC<PropTypes>;
-}
-
-const getHeaderLevel = (header: string): number => {
- if (!header) return 0;
- let level = 0;
- while (header[level] === '#') level += 1;
- return level;
-};
-
-const SectionMapper: React.FC<MapperPropTypes> = ({ rawLines, level = 0, SectionComponent }) => {
- const children = rawLines
- .reduce((sections: string[][], line: string) => {
- if (line) {
- if (getHeaderLevel(line) === level) sections.push([]);
- if (sections.length) sections[sections.length - 1].push(line);
- }
- return sections;
- }, [])
- .map(sectionLines => <SectionComponent rawLines={sectionLines} level={level} />);
-
- return <>{children}</>;
-};
-
-
-const Section: React.FC<PropTypes> = ({ rawLines, level = 0 }) => {
- const deeperLevelIndex = rawLines.findIndex(line => line.match(`^#{${level + 1},} .*$`));
- const rawContent = rawLines.splice(0, (deeperLevelIndex < 0) ? rawLines.length : deeperLevelIndex);
-
- if (!level) {
- return (
- <>
- <Typography><Content rawLines={rawContent} /></Typography>
- <SectionMapper rawLines={rawLines} level={getHeaderLevel(rawLines[0])} SectionComponent={Section} />
- </>
- );
- }
-
- const sectionName = rawContent.splice(0, 1)[0].slice(level).trim();
- const deeperLevel = getHeaderLevel(rawLines[0]);
- return (
- <ContentSection sectionName={sectionName} level={level}>
- <Content rawLines={rawContent} />
- <SectionMapper rawLines={rawLines} level={deeperLevel} SectionComponent={Section} />
- </ContentSection>
- );
-};
-
-export default Section;
-
diff --git a/src/lib/Markdown/SyntacticSpan.tsx b/src/lib/Markdown/SyntacticSpan.tsx
deleted file mode 100644
index 11cc024..0000000
--- a/src/lib/Markdown/SyntacticSpan.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import React from 'react';
-import { Link, makeStyles } from '@material-ui/core';
-
-import { lib as emojiLib } from 'emojilib';
-
-interface PropTypes {
- span: string;
-}
-
-interface RegexPair {
- global: RegExp;
- local: RegExp;
-}
-
-interface Emoji {
- name: string;
- char: string;
-}
-
-const enclosureRegex = (e: string): RegexPair => ({
- local: new RegExp(`${e}([^${e}]+)${e}`),
- global: new RegExp(`(${e}[^${e}]+${e})`),
-});
-
-const regex: Record<string, RegexPair> = {
- conceal: {
- global: /(!?\[.+?\]\(.+?\))(?!])/g,
- local: /!?\[(.*\]?.*)\]\((.+?)\)/,
- },
- rawLink: {
- // eslint-disable-next-line max-len
- global: /((?:(?:[A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www\.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)(?:(?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[.!/\\\w]*))?)/,
- local: /&^/,
- },
- emoji: enclosureRegex(':'),
- bold: enclosureRegex('\\*\\*'),
- italic: enclosureRegex('\\*'),
- code: enclosureRegex('`'),
- strikeThrough: enclosureRegex('~~'),
-};
-
-const splitter = new RegExp(Object.values(regex).map(pair => pair.global.source).join('|'));
-
-const emojiList: Emoji[] = [];
-Object.keys(emojiLib).forEach(name => emojiList.push({ name, char: emojiLib[name].char }));
-
-const useStyles = makeStyles(theme => ({
- code: {
- background: theme.palette.background.default,
- borderRadius: theme.spacing(0.5),
- padding: theme.spacing(0.5),
- fontFamily: 'Monospace',
- },
- image: {
- maxWidth: '100%',
- maxHeight: '100%',
- },
-}));
-
-const SyntacticSpan: React.FC<PropTypes> = ({ span }) => {
- const classes = useStyles();
- if (!span) return null;
-
- const matchConceal = regex.conceal.local.exec(span);
- if (matchConceal) {
- if (span[0] === '!') return <img src={matchConceal[2]} alt={matchConceal[1]} className={classes.image} />;
- return <Link href={matchConceal[2]}><SyntacticSpan span={matchConceal[1]} /></Link>;
- }
-
- const matchEmoji = span.match(regex.emoji.local);
- if (matchEmoji) {
- const emoji = emojiList.find(e => e.name === matchEmoji[1]);
- return <span>{emoji ? emoji.char : span}</span>;
- }
-
- const matchCode = span.match(regex.code.local);
- if (matchCode) return <span className={classes.code}>{matchCode[1]}</span>;
-
- const matchBold = span.match(regex.bold.local);
- if (matchBold) return <b>{matchBold[1]}</b>;
-
- const matchItalic = span.match(regex.italic.local);
- if (matchItalic) return <i>{matchItalic[1]}</i>;
-
- const matchStrikeThrough = span.match(regex.strikeThrough.local);
- if (matchStrikeThrough) return <span style={{ textDecoration: 'line-through' }}>{matchStrikeThrough[1]}</span>;
-
- if (span.match(regex.rawLink.global)) return <Link href={span}>{span}</Link>;
-
- return <>{span}</>;
-};
-
-
-export { splitter };
-export default SyntacticSpan;
-
diff --git a/src/lib/Markdown/Text.tsx b/src/lib/Markdown/Text.tsx
deleted file mode 100644
index be715fd..0000000
--- a/src/lib/Markdown/Text.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from 'react';
-import SyntacticSpan, { splitter } from './SyntacticSpan';
-
-interface PropTypes {
- line: string;
-}
-
-const Text: React.FC<PropTypes> = ({ line }) => {
- return <>{line.split(splitter).map(span => <SyntacticSpan span={span} />)}</>;
-};
-
-export default Text;
-
diff --git a/src/lib/Markdown/emojilib.d.ts b/src/lib/Markdown/emojilib.d.ts
deleted file mode 100644
index cddfeea..0000000
--- a/src/lib/Markdown/emojilib.d.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-declare module 'emojilib';
-
diff --git a/src/lib/Markdown/types.d.ts b/src/lib/Markdown/types.d.ts
new file mode 100644
index 0000000..f444cf1
--- /dev/null
+++ b/src/lib/Markdown/types.d.ts
@@ -0,0 +1,7 @@
+declare module 'remark-gemoji';
+declare module '*.md' {
+ // eslint-disable-next-line import/no-mutable-exports
+ let Markdown: string;
+ export default Markdown;
+}
+
diff --git a/src/lib/Markdown/types.ts b/src/lib/Markdown/types.ts
deleted file mode 100644
index 0b6f4b6..0000000
--- a/src/lib/Markdown/types.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export interface ParserPropTypes {
- rawLines: string[];
-}
-
diff --git a/src/lib/index.ts b/src/lib/index.ts
index 545e6f8..2e6dd9e 100644
--- a/src/lib/index.ts
+++ b/src/lib/index.ts
@@ -1,3 +1,5 @@
-export { default as ContentSection } from './ContentSection/ContentSection';
export { default as Benzin } from './Benzin/Benzin';
export { default as Markdown } from './Markdown/Markdown';
+export { default as CodeBlock } from './Markdown/CodeBlock';
+export { default as InlineCode } from './Markdown/InlineCode';
+export { default as Heading } from './Markdown/Heading';