aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugene Sokolov <eug-vs@keemail.me>2020-04-06 17:47:20 +0300
committerGitHub <noreply@github.com>2020-04-06 17:47:20 +0300
commit62df0ff96fc9ab832212d223150862c7667d9ffc (patch)
tree9159c443e970ea2a0819edffbe2fe5cc101c1001
parenta72027d21154ba94e26d6b96092afc9704b8288c (diff)
parent400330fe5ebd6951a97f07b6147b3af6113e034f (diff)
downloadreact-benzin-3.1.0.tar.gz
Merge pull request #8 from eug-vs/developv3.1.0
Markdown parser
-rw-r--r--.eslintignore1
-rw-r--r--.eslintrc.json1
-rw-r--r--.gitignore2
-rw-r--r--package-lock.json140
-rw-r--r--package.json8
-rw-r--r--src/index.tsx99
-rw-r--r--src/lib/Benzin/Benzin.tsx (renamed from src/lib/BenzinThemeProvider/BenzinThemeProvider.tsx)10
-rw-r--r--src/lib/ContentSection/ContentSection.tsx13
-rw-r--r--src/lib/Markdown/CodeBlock.tsx27
-rw-r--r--src/lib/Markdown/Content.tsx77
-rw-r--r--src/lib/Markdown/Markdown.tsx24
-rw-r--r--src/lib/Markdown/Section.tsx46
-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.ts4
-rw-r--r--src/lib/index.ts3
-rw-r--r--tsconfig.release.json (renamed from ts-compile-config.json)0
18 files changed, 491 insertions, 75 deletions
diff --git a/.eslintignore b/.eslintignore
deleted file mode 100644
index 1521c8b..0000000
--- a/.eslintignore
+++ /dev/null
@@ -1 +0,0 @@
-dist
diff --git a/.eslintrc.json b/.eslintrc.json
index 9111786..ac05a7f 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -4,6 +4,7 @@
"react-app",
"plugin:@typescript-eslint/recommended"
],
+ "ignorePatterns": "dist/",
"rules": {
"jsx-quotes": ["error", "prefer-double"],
"quotes": ["error", "single"]
diff --git a/.gitignore b/.gitignore
index 3324041..a555b89 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,5 @@
# Production
/dist
+/build
+
diff --git a/package-lock.json b/package-lock.json
index 18fa783..c7282df 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "react-benzin",
- "version": "3.0.0",
+ "version": "3.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -2465,6 +2465,37 @@
"integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==",
"dev": true
},
+ "axios": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
+ "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
+ "requires": {
+ "follow-redirects": "1.5.10"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "follow-redirects": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
+ "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
+ "requires": {
+ "debug": "=3.1.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
"axobject-query": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.1.tgz",
@@ -4755,12 +4786,23 @@
"minimalistic-crypto-utils": "^1.0.0"
}
},
+ "email-addresses": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz",
+ "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==",
+ "dev": true
+ },
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
+ "emojilib": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz",
+ "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw=="
+ },
"emojis-list": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
@@ -5883,6 +5925,33 @@
"dev": true,
"optional": true
},
+ "filename-reserved-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz",
+ "integrity": "sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q=",
+ "dev": true
+ },
+ "filenamify": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz",
+ "integrity": "sha1-qfL/0RxQO+0wABUCknI3jx8TZaU=",
+ "dev": true,
+ "requires": {
+ "filename-reserved-regex": "^1.0.0",
+ "strip-outer": "^1.0.0",
+ "trim-repeated": "^1.0.0"
+ }
+ },
+ "filenamify-url": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/filenamify-url/-/filenamify-url-1.0.0.tgz",
+ "integrity": "sha1-syvYExnvWGO3MHi+1Q9GpPeXX1A=",
+ "dev": true,
+ "requires": {
+ "filenamify": "^1.0.0",
+ "humanize-url": "^1.0.0"
+ }
+ },
"filesize": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-6.0.1.tgz",
@@ -6281,6 +6350,41 @@
"assert-plus": "^1.0.0"
}
},
+ "gh-pages": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-2.2.0.tgz",
+ "integrity": "sha512-c+yPkNOPMFGNisYg9r4qvsMIjVYikJv7ImFOhPIVPt0+AcRUamZ7zkGRLHz7FKB0xrlZ+ddSOJsZv9XAFVXLmA==",
+ "dev": true,
+ "requires": {
+ "async": "^2.6.1",
+ "commander": "^2.18.0",
+ "email-addresses": "^3.0.1",
+ "filenamify-url": "^1.0.0",
+ "fs-extra": "^8.1.0",
+ "globby": "^6.1.0"
+ },
+ "dependencies": {
+ "globby": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
+ "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
+ "dev": true,
+ "requires": {
+ "array-union": "^1.0.1",
+ "glob": "^7.0.3",
+ "object-assign": "^4.0.1",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ }
+ }
+ },
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
@@ -6779,6 +6883,16 @@
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
"dev": true
},
+ "humanize-url": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/humanize-url/-/humanize-url-1.0.1.tgz",
+ "integrity": "sha1-9KuZ4NKIF0yk4eUEB8VfuuRk7/8=",
+ "dev": true,
+ "requires": {
+ "normalize-url": "^1.0.0",
+ "strip-url-auth": "^1.0.0"
+ }
+ },
"hyphenate-style-name": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz",
@@ -13526,6 +13640,21 @@
"integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
"dev": true
},
+ "strip-outer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
+ "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==",
+ "dev": true,
+ "requires": {
+ "escape-string-regexp": "^1.0.2"
+ }
+ },
+ "strip-url-auth": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/strip-url-auth/-/strip-url-auth-1.0.1.tgz",
+ "integrity": "sha1-IrD6OkE4WzO+PzMVUbu4N/oM164=",
+ "dev": true
+ },
"style-loader": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.1.3.tgz",
@@ -13952,6 +14081,15 @@
"punycode": "^2.1.0"
}
},
+ "trim-repeated": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
+ "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=",
+ "dev": true,
+ "requires": {
+ "escape-string-regexp": "^1.0.2"
+ }
+ },
"ts-pnp": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.5.tgz",
diff --git a/package.json b/package.json
index 67de183..a915372 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,8 @@
{
"name": "react-benzin",
- "version": "3.0.0",
+ "version": "3.1.0",
"description": "A powerful React Material components library.",
+ "homepage": "https://eug-vs.github.io/react-benzin",
"main": "dist/index.js",
"files": [
"dist",
@@ -11,12 +12,14 @@
"start": "react-scripts start",
"lint": "eslint . --ext ts,tsx --max-warnings 0",
"test": "npm run lint && tsc",
- "build": "rm -rf dist && tsc --project ts-compile-config.json",
+ "build": "rm -rf dist && tsc --project tsconfig.release.json",
"deploy": "npm run lint && npm run build && npm publish --public"
},
"license": "MIT",
"dependencies": {
"@material-ui/core": "^4.9.0",
+ "axios": "^0.19.2",
+ "emojilib": "^2.4.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-virtualized-auto-sizer": "^1.0.2",
@@ -33,6 +36,7 @@
"@typescript-eslint/parser": "^2.19.0",
"eslint": "^6.8.0",
"eslint-config-react-app": "^5.1.0",
+ "gh-pages": "^2.2.0",
"react-scripts": "^3.3.1",
"typescript": "^3.7.5"
},
diff --git a/src/index.tsx b/src/index.tsx
index 9d32585..b64b207 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,15 +1,13 @@
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
-import { makeStyles, Link } from '@material-ui/core';
+import { makeStyles } from '@material-ui/core';
import {
- BenzinThemeProvider,
+ Benzin,
Header,
Window,
- ContentSection,
- SmartList,
- Button,
+ Markdown,
} from './lib';
import icon from './assets/icon.svg';
@@ -31,8 +29,18 @@ const Icon = <img src={icon} width="32px" height="37px" alt="logo"/>
const headerContents = {
home: null,
- page: null,
- 'another page': null,
+ space: null,
+ 'spacevim': null,
+ 'emoji': null,
+ 'material-ui': null,
+};
+
+const pageMap: Record<string, string> = {
+ home: 'https://raw.githubusercontent.com/eug-vs/react-benzin/develop/README.md',
+ space: 'https://raw.githubusercontent.com/eug-vs/space/master/docs/environment.md',
+ 'spacevim': 'https://raw.githubusercontent.com/spacevim/spacevim/master/README.md',
+ emoji: 'https://raw.githubusercontent.com/muan/emoji/gh-pages/README.md',
+ 'material-ui': 'https://raw.githubusercontent.com/mui-org/material-ui/master/README.md',
};
@@ -40,33 +48,23 @@ const App: React.FC = () => {
const classes = useStyles();
const [page, setPage] = useState('home');
- const renderItem: React.FC<RenderPropTypes> = ({ index, style}) => {
- return (
- <div style={style} className={classes.window}>
- <ContentSection sectionName={`Item ${index+1}`}>
- <p>
- Fusce commodo. Vestibulum convallis, lorem a tempus semper, dui dui euismod elit, vitae placerat urna tortor vitae lacus. Nullam libero mauris, consequat quis, varius et, dictum id, arcu. Mauris mollis tincidunt felis.
- </p>
- {(index % 2 === 0)?
- (
- <Button color="primary">
- primary
- </Button>
- )
- :
- (
- <Button color="secondary">
- secondary
- </Button>
- )
- }
- </ContentSection>
- </div>
- );
- };
+ const url = pageMap[page];
+ const fileName = url.slice(url.lastIndexOf('/') + 1);
+ const metadata = [
+ `## Markdown\n [Markdown file](${url}) *(...${fileName})* that you can see on the left was parsed and processed by **BENZIN**! :rocket:`,
+ 'Switch between tabs on the header to explore other markdown templates. :recycle: ',
+ 'Currently **only core features** of markdown function.',
+ '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\'));',
+ '```',
+ ].join('\n');
return (
- <BenzinThemeProvider>
+ <Benzin>
<Header
logo={{
icon: Icon,
@@ -78,40 +76,15 @@ const App: React.FC = () => {
/>
<Window type="primary">
<div className={classes.window}>
- <ContentSection sectionName="Library preview">
- <p>
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Pellentesque dapibus suscipit ligula. Donec posuere augue in quam. Etiam vel tortor sodales tellus ultricies commodo. Suspendisse potenti. Aenean in sem ac leo mollis blandit. Donec neque quam, dignissim in, mollis nec, sagittis eu, wisi. <Link href="#">Phasellus lacus.</Link> Etiam laoreet quam sed arcu. Phasellus at dui in ligula mollis ultricies. Integer placerat tristique nisl. Praesent augue. Fusce commodo.
- </p>
- <Button color="secondary">
- secondary
- </Button>
- <Button color="primary">
- primary
- </Button>
- </ContentSection>
- <ContentSection sectionName="Content section">
- <p>
- Fusce suscipit, wisi nec facilisis facilisis, est dui fermentum leo, quis tempor ligula erat quis odio. Nunc porta vulputate tellus. Nunc rutrum turpis sed pede. Sed bibendum. Aliquam posuere.
- </p>
- <p>
- <Link href="#">Link example</Link>
- </p>
- </ContentSection>
- <ContentSection sectionName="Content section">
- <p>
- Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec hendrerit tempor tellus. Donec pretium posuere tellus. Proin quam nisl, tincidunt et, mattis eget, convallis nec, purus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla posuere. Donec vitae dolor. Nullam tristique diam non turpis. Cras placerat accumsan <Link href="#">nulla</Link>. Nullam rutrum. Nam vestibulum accumsan nisl. Pellentesque dapibus suscipit ligula.
- </p>
- </ContentSection>
+ <Markdown url={url} />
</div>
</Window>
- <Window type="secondary" name="SmartList preview window">
- <SmartList
- itemSize={270}
- itemCount={100}
- renderItem={renderItem}
- />
+ <Window type="secondary" name="Feature preview">
+ <div className={classes.window}>
+ <Markdown data={metadata} />
+ </div>
</Window>
- </BenzinThemeProvider>
+ </Benzin>
);
};
diff --git a/src/lib/BenzinThemeProvider/BenzinThemeProvider.tsx b/src/lib/Benzin/Benzin.tsx
index efb4f86..83ed0b0 100644
--- a/src/lib/BenzinThemeProvider/BenzinThemeProvider.tsx
+++ b/src/lib/Benzin/Benzin.tsx
@@ -1,4 +1,6 @@
import React from 'react';
+import orange from '@material-ui/core/colors/orange';
+import purple from '@material-ui/core/colors/purple';
import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
import { CssBaseline } from '@material-ui/core';
import 'typeface-roboto';
@@ -17,10 +19,10 @@ const benzinTheme = createMuiTheme({
palette: {
type: 'dark',
primary: {
- main: '#ffa726',
+ main: orange[400],
},
secondary: {
- main: '#9c27b0',
+ main: purple[500],
},
background: {
default: '#121212',
@@ -37,7 +39,7 @@ const benzinTheme = createMuiTheme({
});
-const BenzinThemeProvider: React.FC = ({ children }) => (
+const Benzin: React.FC = ({ children }) => (
<ThemeProvider theme={benzinTheme}>
<CssBaseline />
{children}
@@ -45,5 +47,5 @@ const BenzinThemeProvider: React.FC = ({ children }) => (
);
-export default BenzinThemeProvider;
+export default Benzin;
diff --git a/src/lib/ContentSection/ContentSection.tsx b/src/lib/ContentSection/ContentSection.tsx
index 7ff47f9..ba8b882 100644
--- a/src/lib/ContentSection/ContentSection.tsx
+++ b/src/lib/ContentSection/ContentSection.tsx
@@ -9,11 +9,12 @@ import {
interface PropTypes {
sectionName: string;
+ level?: number;
}
const useStyles = makeStyles(theme => ({
content: {
- padding: theme.spacing(0, 2, 1, 2),
+ padding: theme.spacing(2, 2, 1, 3),
marginBottom: theme.spacing(1),
'& .MuiButton-root': {
@@ -22,12 +23,18 @@ const useStyles = makeStyles(theme => ({
},
}));
-const ContentSection: React.FC<PropTypes> = ({ sectionName, children }) => {
+const ContentSection: React.FC<PropTypes> = ({ sectionName, children, level = 0 }) => {
const classes = useStyles();
+ level += 2; // Make everything smaller
+ if (level > 6) level = 6;
+
+ type Variant = 'h3' | 'h4' | 'h5' | 'h6';
+ const variant: Variant = 'h' + level as Variant;
+
return (
<>
- <Typography variant="h4">{sectionName}</Typography>
+ <Typography variant={variant}>{sectionName}</Typography>
<Divider variant="middle"/>
<Typography component="div" className={classes.content}>
{children}
diff --git a/src/lib/Markdown/CodeBlock.tsx b/src/lib/Markdown/CodeBlock.tsx
new file mode 100644
index 0000000..5b8edec
--- /dev/null
+++ b/src/lib/Markdown/CodeBlock.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import { ParserPropTypes } from './types';
+import { Paper } from '@material-ui/core';
+
+import { makeStyles } from '@material-ui/core/styles';
+
+const useStyles = makeStyles(theme => ({
+ root: {
+ background: theme.palette.background.default,
+ padding: theme.spacing(1),
+ overflowX: 'auto',
+ fontFamily: 'Monospace',
+ scrollbarColor: 'auto'
+ },
+}));
+
+const CodeBlock: React.FC<ParserPropTypes> = ({ rawLines }) => {
+ const classes = useStyles();
+ return (
+ <Paper variant="outlined" className={classes.root}>
+ {rawLines.map(line => <pre>{line}</pre>)}
+ </Paper>
+ );
+}
+
+export default CodeBlock;
+
diff --git a/src/lib/Markdown/Content.tsx b/src/lib/Markdown/Content.tsx
new file mode 100644
index 0000000..aaea100
--- /dev/null
+++ b/src/lib/Markdown/Content.tsx
@@ -0,0 +1,77 @@
+import React from 'react';
+
+import CodeBlock from './CodeBlock';
+import Text from './Text';
+import { ParserPropTypes } from './types';
+
+
+const denotesCodeBlock = (line: string): boolean => {
+ return line.match(/^```.*$/) !== 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 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(line => denotesCodeBlock(line));
+ const codeBlockLines = rawLines.splice(0, closeIndex + 1).slice(0, closeIndex);
+ buffer = <CodeBlock rawLines={codeBlockLines} />
+ } else if (denotesDottedList(line)) {
+ const closeIndex = rawLines.findIndex(line => !denotesDottedList(line));
+ 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 = rawLines.findIndex(line => denotesClosingHtml(line, tag));
+ const htmlLines = rawLines.splice(0, closeIndex + 1).slice(0, closeIndex);
+ htmlLines.unshift(line);
+ buffer = <div dangerouslySetInnerHTML={{ __html: htmlLines.join('\n') }}></div>;
+ } else if ((buffer = denotesSelfClosingHtml(line)) !== null) {
+ const match = buffer[0];
+ const [before, after] = line.split(match);
+ console.log({ line, match, before, after});
+ buffer = (
+ <>
+ <Text line={before} />
+ <div dangerouslySetInnerHTML={{ __html: match }}></div>
+ <Text line={after} />
+ </>
+ );
+ } else {
+ buffer = <p><Text line={line} /></p>
+ }
+
+ return (
+ <>
+ { buffer }
+ <Content rawLines={rawLines} />
+ </>
+ );
+}
+
+export default Content;
+
diff --git a/src/lib/Markdown/Markdown.tsx b/src/lib/Markdown/Markdown.tsx
new file mode 100644
index 0000000..09ad54a
--- /dev/null
+++ b/src/lib/Markdown/Markdown.tsx
@@ -0,0 +1,24 @@
+import React, { useState, useEffect } from 'react';
+import axios from 'axios';
+
+import Section from './Section';
+
+interface PropTypes {
+ data?: string;
+ url?: string;
+}
+
+const Markdown: React.FC<PropTypes> = ({ data, url }) => {
+ const [markdown, setMarkdown] = useState<string>(data || '');
+
+ useEffect(() => {
+ if (!url) setMarkdown(data || '');
+ }, [data, url]);
+
+ if (url) axios.get(url).then(response => setMarkdown(response.data));
+ return <Section rawLines={markdown.split('\n')} />
+};
+
+
+export default Markdown;
+
diff --git a/src/lib/Markdown/Section.tsx b/src/lib/Markdown/Section.tsx
new file mode 100644
index 0000000..5ce8954
--- /dev/null
+++ b/src/lib/Markdown/Section.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import ContentSection from '../ContentSection/ContentSection';
+import Content from './Content';
+import { ParserPropTypes } from './types';
+
+interface PropTypes extends ParserPropTypes {
+ level?: number;
+}
+
+const getHeaderLevel = (header: string): number => {
+ if (!header) return 0;
+ let level = 0;
+ while(header[level] === '#') level++;
+ return level;
+}
+
+const ChildrenSections: React.FC<PropTypes> = ({ rawLines, level = 0 }) => {
+ const childrenSectionLines = 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;
+ }, []);
+ const children = childrenSectionLines.map(sectionLines => <Section 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 <ChildrenSections rawLines={rawLines} level={getHeaderLevel(rawLines[0])}/>;
+
+ const sectionName = rawContent.splice(0, 1)[0].slice(level).trim();
+ const deeperLevel = getHeaderLevel(rawLines[0]);
+ return (
+ <ContentSection sectionName={sectionName} level={level}>
+ <Content rawLines={rawContent} />
+ <ChildrenSections rawLines={rawLines} level={deeperLevel} />
+ </ContentSection>
+ );
+}
+
+export default Section;
+
diff --git a/src/lib/Markdown/SyntacticSpan.tsx b/src/lib/Markdown/SyntacticSpan.tsx
new file mode 100644
index 0000000..299bf87
--- /dev/null
+++ b/src/lib/Markdown/SyntacticSpan.tsx
@@ -0,0 +1,96 @@
+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: {
+ 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 }));
+console.log({emojiList})
+
+const useStyles = makeStyles(theme => ({
+ code: {
+ background: theme.palette.background.default,
+ borderRadius: theme.spacing(.5),
+ padding: theme.spacing(.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]}>{matchConceal[1]}</Link>;
+ }
+
+ const matchEmoji = span.match(regex.emoji.local);
+ if (matchEmoji) {
+ const emoji = emojiList.find(emoji => emoji.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
new file mode 100644
index 0000000..e287dee
--- /dev/null
+++ b/src/lib/Markdown/Text.tsx
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000..cddfeea
--- /dev/null
+++ b/src/lib/Markdown/emojilib.d.ts
@@ -0,0 +1,2 @@
+declare module 'emojilib';
+
diff --git a/src/lib/Markdown/types.ts b/src/lib/Markdown/types.ts
new file mode 100644
index 0000000..0b6f4b6
--- /dev/null
+++ b/src/lib/Markdown/types.ts
@@ -0,0 +1,4 @@
+export interface ParserPropTypes {
+ rawLines: string[];
+}
+
diff --git a/src/lib/index.ts b/src/lib/index.ts
index a41dd39..0f31104 100644
--- a/src/lib/index.ts
+++ b/src/lib/index.ts
@@ -2,5 +2,6 @@ export { default as Window } from './Window/Window';
export { default as Header } from './Header/Header';
export { default as ContentSection } from './ContentSection/ContentSection';
export { default as SmartList } from './SmartList/SmartList';
-export { default as BenzinThemeProvider } from './BenzinThemeProvider/BenzinThemeProvider';
+export { default as Benzin } from './Benzin/Benzin';
export { default as Button } from './Button/Button';
+export { default as Markdown } from './Markdown/Markdown';
diff --git a/ts-compile-config.json b/tsconfig.release.json
index 8f0256b..8f0256b 100644
--- a/ts-compile-config.json
+++ b/tsconfig.release.json