From eee96f01e29ddb8b95e9429c4e584fe6e7e7faec Mon Sep 17 00:00:00 2001 From: Eug-VS Date: Sat, 8 Feb 2020 15:31:14 +0300 Subject: feat: implement initial markdown component --- src/index.tsx | 17 +++---------- src/lib/Markdown/Markdown.tsx | 57 +++++++++++++++++++++++++++++++++++++++++++ src/lib/Markdown/example.md | 50 +++++++++++++++++++++++++++++++++++++ src/lib/index.ts | 1 + 4 files changed, 112 insertions(+), 13 deletions(-) create mode 100644 src/lib/Markdown/Markdown.tsx create mode 100644 src/lib/Markdown/example.md diff --git a/src/index.tsx b/src/index.tsx index 9d32585..cba71b6 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -10,6 +10,7 @@ import { ContentSection, SmartList, Button, + Markdown, } from './lib'; import icon from './assets/icon.svg'; @@ -27,6 +28,8 @@ const useStyles = makeStyles(theme => ({ })); +const data = "# Getting started\n## Installation \nGo and install it \n# Development\nMore info on dev"; + const Icon = logo const headerContents = { @@ -89,19 +92,7 @@ const App: React.FC = () => { primary - -

- 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. -

-

- Link example -

-
- -

- 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 nulla. Nullam rutrum. Nam vestibulum accumsan nisl. Pellentesque dapibus suscipit ligula. -

-
+ diff --git a/src/lib/Markdown/Markdown.tsx b/src/lib/Markdown/Markdown.tsx new file mode 100644 index 0000000..a3ffc38 --- /dev/null +++ b/src/lib/Markdown/Markdown.tsx @@ -0,0 +1,57 @@ +import React from 'react'; + +import ContentSection from '../ContentSection/ContentSection'; + + +interface PropTypes { + data: string; +} + +interface RawLinesPropType { + rawLines: string[]; + level?: number; +} + +const header = (level: number): string => { + return `^#{${level}} .*$`; +}; + +const Content: React.FC = ({ rawLines }) => { + const plainText = rawLines.join(); + return

{plainText}

; +} + +const Level: React.FC = ({ rawLines, level = 0 }) => { + const name = rawLines[0].slice(level); + const contentSize = rawLines.findIndex(line => line.match(header(level + 1))); + + const rawContent = (contentSize > 0) ? rawLines.slice(1, contentSize) : rawLines.slice(1); + const rawChildren = rawLines.slice(contentSize); + + const childrenLineGroups = rawChildren.reduce((acc: any[], cur: string) => { + if (cur.match(header(level + 1))) acc.push([]); + if (acc.length) acc[acc.length - 1].push(cur); + return acc; + }, []); + const children = childrenLineGroups.map(lineGroup => ) + + return level ? ( + + + {children} + + ) : ( + <> + {children} + + ); +} + +const Markdown: React.FC = ({ data }) => { + const rawLines = data.split('\n'); + return +}; + + +export default Markdown; + diff --git a/src/lib/Markdown/example.md b/src/lib/Markdown/example.md new file mode 100644 index 0000000..abc0910 --- /dev/null +++ b/src/lib/Markdown/example.md @@ -0,0 +1,50 @@ +# Getting started +## Installation +You can easily add **BENZIN** to your project with `npm`: +```bash +$ npm install react-benzin +``` +**BENZIN** works best in kick-starting new projects and allows you to focus on the functionality, while all the beauty will be maintained by our library. + +**TIP:** *Create-React-App with Typescript* is your GO-TO in most of the cases. [Learn more.](https://create-react-app.dev/docs/adding-typescript/) + +![Preview screenshot](https://user-images.githubusercontent.com/51545008/73991116-46b04f00-495c-11ea-9733-865bcc6c8807.png) + +You can find a minimal usage example [here](src/index.tsx). + +## Functionality +**BENZIN** provides you with a bunch of cool components that greatly integrate with each other. + +[Explore](src/lib) `src/lib/` folder to see what's available. Documentation is yet to come, but for now you can enjoy type definitons. + +[Chrono-Cube](https://github.com/eug-vs/chrono-cube/) will also be a great example of usage, since it's the actual project which inspired us to create **BENZIN**. + + +# Explore NPM package online +https://www.npmjs.com/package/react-benzin + + +# Development +## Running live demo +To run a live example, clone a repo and execute following commands: +```bash +$ npm i +$ npm start +``` +It's worth noticing that presence of React-App in this repo forces us to split some configurations. For example, we have 2 `Typescript` configs: one for `react-scripts` to run live-demo, and the other one to build *distribution files*. + +## Running tests +```bash +$ npm test +``` +**NOTE**: this command assures that `ESlint` does not throw any warnings and exits with a *non-zero status code* otherwise. That means `CircleCI` tests would fail *even if a single warning is present*. Therefore, you should always locally test your changes before publishing them. + +## Building +We've decided to use `Typescript compiler` to transpile our code, since we think `Babel` is a bit of an overkill here. +```bash +$ npm run build +``` +This command will generate `dist/` folder ready for distribution, which you of course can explore. Note that `tsc` creates type definitions (`.d.ts`) for every corresponding `.js` file. It's very useful because consumers also get access to them. + +## Deploying +Deploying to `npm` is fully automated through **CircleCI**: simply tag a commit as a Release and it will do the job. diff --git a/src/lib/index.ts b/src/lib/index.ts index a41dd39..121c908 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -4,3 +4,4 @@ export { default as ContentSection } from './ContentSection/ContentSection'; export { default as SmartList } from './SmartList/SmartList'; export { default as BenzinThemeProvider } from './BenzinThemeProvider/BenzinThemeProvider'; export { default as Button } from './Button/Button'; +export { default as Markdown } from './Markdown/Markdown'; -- cgit v1.2.3 From a2877c2844825853b8913a07a40f814fc03ac999 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Fri, 3 Apr 2020 03:30:16 +0300 Subject: feat: support Markdown by url --- package-lock.json | 31 +++++++++++++++++++++++++++++++ package.json | 1 + src/index.tsx | 4 +--- src/lib/Markdown/Markdown.tsx | 15 +++++++++------ 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 18fa783..4bde2bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 61075ab..d1d2e04 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "license": "MIT", "dependencies": { "@material-ui/core": "^4.9.0", + "axios": "^0.19.2", "react": "^16.12.0", "react-dom": "^16.12.0", "react-virtualized-auto-sizer": "^1.0.2", diff --git a/src/index.tsx b/src/index.tsx index cba71b6..9678beb 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -28,8 +28,6 @@ const useStyles = makeStyles(theme => ({ })); -const data = "# Getting started\n## Installation \nGo and install it \n# Development\nMore info on dev"; - const Icon = logo const headerContents = { @@ -92,7 +90,7 @@ const App: React.FC = () => { primary - +
diff --git a/src/lib/Markdown/Markdown.tsx b/src/lib/Markdown/Markdown.tsx index a3ffc38..944ac86 100644 --- a/src/lib/Markdown/Markdown.tsx +++ b/src/lib/Markdown/Markdown.tsx @@ -1,10 +1,12 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; import ContentSection from '../ContentSection/ContentSection'; interface PropTypes { - data: string; + data?: string; + url?: string; } interface RawLinesPropType { @@ -14,7 +16,7 @@ interface RawLinesPropType { const header = (level: number): string => { return `^#{${level}} .*$`; -}; +} const Content: React.FC = ({ rawLines }) => { const plainText = rawLines.join(); @@ -47,9 +49,10 @@ const Level: React.FC = ({ rawLines, level = 0 }) => { ); } -const Markdown: React.FC = ({ data }) => { - const rawLines = data.split('\n'); - return +const Markdown: React.FC = ({ data, url }) => { + const [markdown, setMarkdown] = useState(data || ''); + if (url) axios.get(url).then(response => setMarkdown(response.data)); + return }; -- cgit v1.2.3 From e491abb55723ab20a36ead83320e40627cca8e06 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Fri, 3 Apr 2020 03:37:31 +0300 Subject: fix: correct linting errors :rotating_light: --- src/index.tsx | 2 +- src/lib/Markdown/Markdown.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 9678beb..6646cf0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -90,7 +90,7 @@ const App: React.FC = () => { primary - + diff --git a/src/lib/Markdown/Markdown.tsx b/src/lib/Markdown/Markdown.tsx index 944ac86..b3119f1 100644 --- a/src/lib/Markdown/Markdown.tsx +++ b/src/lib/Markdown/Markdown.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import axios from 'axios'; import ContentSection from '../ContentSection/ContentSection'; @@ -30,7 +30,7 @@ const Level: React.FC = ({ rawLines, level = 0 }) => { const rawContent = (contentSize > 0) ? rawLines.slice(1, contentSize) : rawLines.slice(1); const rawChildren = rawLines.slice(contentSize); - const childrenLineGroups = rawChildren.reduce((acc: any[], cur: string) => { + const childrenLineGroups = rawChildren.reduce((acc: string[][], cur: string) => { if (cur.match(header(level + 1))) acc.push([]); if (acc.length) acc[acc.length - 1].push(cur); return acc; -- cgit v1.2.3 From badd3517cd11a68d70fa6ba7cdb1e5afaaf79310 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Fri, 3 Apr 2020 03:49:26 +0300 Subject: feat: add linebreaks --- src/lib/Markdown/Markdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Markdown/Markdown.tsx b/src/lib/Markdown/Markdown.tsx index b3119f1..332d08c 100644 --- a/src/lib/Markdown/Markdown.tsx +++ b/src/lib/Markdown/Markdown.tsx @@ -19,7 +19,7 @@ const header = (level: number): string => { } const Content: React.FC = ({ rawLines }) => { - const plainText = rawLines.join(); + const plainText = rawLines.map(line =>

{line}

); return

{plainText}

; } -- cgit v1.2.3 From fd0497b855ae03f8f1dfb65edca67ee53431712f Mon Sep 17 00:00:00 2001 From: eug-vs Date: Fri, 3 Apr 2020 04:03:46 +0300 Subject: fix: remove occasional file --- src/lib/Markdown/example.md | 50 --------------------------------------------- 1 file changed, 50 deletions(-) delete mode 100644 src/lib/Markdown/example.md diff --git a/src/lib/Markdown/example.md b/src/lib/Markdown/example.md deleted file mode 100644 index abc0910..0000000 --- a/src/lib/Markdown/example.md +++ /dev/null @@ -1,50 +0,0 @@ -# Getting started -## Installation -You can easily add **BENZIN** to your project with `npm`: -```bash -$ npm install react-benzin -``` -**BENZIN** works best in kick-starting new projects and allows you to focus on the functionality, while all the beauty will be maintained by our library. - -**TIP:** *Create-React-App with Typescript* is your GO-TO in most of the cases. [Learn more.](https://create-react-app.dev/docs/adding-typescript/) - -![Preview screenshot](https://user-images.githubusercontent.com/51545008/73991116-46b04f00-495c-11ea-9733-865bcc6c8807.png) - -You can find a minimal usage example [here](src/index.tsx). - -## Functionality -**BENZIN** provides you with a bunch of cool components that greatly integrate with each other. - -[Explore](src/lib) `src/lib/` folder to see what's available. Documentation is yet to come, but for now you can enjoy type definitons. - -[Chrono-Cube](https://github.com/eug-vs/chrono-cube/) will also be a great example of usage, since it's the actual project which inspired us to create **BENZIN**. - - -# Explore NPM package online -https://www.npmjs.com/package/react-benzin - - -# Development -## Running live demo -To run a live example, clone a repo and execute following commands: -```bash -$ npm i -$ npm start -``` -It's worth noticing that presence of React-App in this repo forces us to split some configurations. For example, we have 2 `Typescript` configs: one for `react-scripts` to run live-demo, and the other one to build *distribution files*. - -## Running tests -```bash -$ npm test -``` -**NOTE**: this command assures that `ESlint` does not throw any warnings and exits with a *non-zero status code* otherwise. That means `CircleCI` tests would fail *even if a single warning is present*. Therefore, you should always locally test your changes before publishing them. - -## Building -We've decided to use `Typescript compiler` to transpile our code, since we think `Babel` is a bit of an overkill here. -```bash -$ npm run build -``` -This command will generate `dist/` folder ready for distribution, which you of course can explore. Note that `tsc` creates type definitions (`.d.ts`) for every corresponding `.js` file. It's very useful because consumers also get access to them. - -## Deploying -Deploying to `npm` is fully automated through **CircleCI**: simply tag a commit as a Release and it will do the job. -- cgit v1.2.3 From 464b5fbef2f58cbcd134b7200c5f7c2f904202a0 Mon Sep 17 00:00:00 2001 From: Eug-VS Date: Sat, 4 Apr 2020 20:43:00 +0300 Subject: feat: parse CodeBlocks --- src/lib/Markdown/Markdown.tsx | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/lib/Markdown/Markdown.tsx b/src/lib/Markdown/Markdown.tsx index 332d08c..8d93437 100644 --- a/src/lib/Markdown/Markdown.tsx +++ b/src/lib/Markdown/Markdown.tsx @@ -18,9 +18,35 @@ const header = (level: number): string => { return `^#{${level}} .*$`; } +const CodeBlock: React.FC<{ rawLines: String[]}> = ({ rawLines }) => { + return ( +

+ {rawLines.map(line => <> {line}
)} +

+ ); +} + + const Content: React.FC = ({ rawLines }) => { - const plainText = rawLines.map(line =>

{line}

); - return

{plainText}

; + if (!rawLines.length) return <>; + const line = rawLines[0]; + const otherLines = rawLines.slice(1); + if (line.slice(0, 3) === '```') { + const closeIndex = otherLines.findIndex(line => line.slice(0, 3) === '```'); + console.log({ line, otherLines, closeIndex }); + return ( + <> + + + + ) + } + return ( + <> +

{line}

+ + + ) } const Level: React.FC = ({ rawLines, level = 0 }) => { -- cgit v1.2.3 From bdcc2edb38fb0e57604fa12d25b2a4b478261e18 Mon Sep 17 00:00:00 2001 From: Eug-VS Date: Sat, 4 Apr 2020 22:05:40 +0300 Subject: refactor: structurize Markdown component :recycle: --- src/lib/Markdown/CodeBlock.tsx | 13 ++++++++ src/lib/Markdown/Content.tsx | 34 ++++++++++++++++++++ src/lib/Markdown/Markdown.tsx | 71 ++---------------------------------------- src/lib/Markdown/Section.tsx | 39 +++++++++++++++++++++++ src/lib/Markdown/types.d.ts | 4 +++ 5 files changed, 92 insertions(+), 69 deletions(-) create mode 100644 src/lib/Markdown/CodeBlock.tsx create mode 100644 src/lib/Markdown/Content.tsx create mode 100644 src/lib/Markdown/Section.tsx create mode 100644 src/lib/Markdown/types.d.ts diff --git a/src/lib/Markdown/CodeBlock.tsx b/src/lib/Markdown/CodeBlock.tsx new file mode 100644 index 0000000..e449f92 --- /dev/null +++ b/src/lib/Markdown/CodeBlock.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { ParserPropTypes } from './types'; + +const CodeBlock: React.FC = ({ rawLines }) => { + return ( +

+ {rawLines.map(line => <> {line}
)} +

+ ); +} + +export default CodeBlock; + diff --git a/src/lib/Markdown/Content.tsx b/src/lib/Markdown/Content.tsx new file mode 100644 index 0000000..b7829ed --- /dev/null +++ b/src/lib/Markdown/Content.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +import CodeBlock from './CodeBlock'; +import { ParserPropTypes } from './types'; + + +const denotesCodeBlock = (line: string): boolean => { + return line.slice(0, 3) === '```'; +} + +const Content: React.FC = ({ rawLines }) => { + if (!rawLines.length) return null; + + const line = rawLines.splice(0, 1)[0]; + + let result; + if (denotesCodeBlock(line)) { + const closeIndex = rawLines.findIndex(line => denotesCodeBlock(line)); + const codeBlockLines = rawLines.splice(0, closeIndex); + result = + } else { + result =

{line}

+ } + + return ( + <> + { result } + + + ) +} + +export default Content; + diff --git a/src/lib/Markdown/Markdown.tsx b/src/lib/Markdown/Markdown.tsx index 8d93437..aee96e9 100644 --- a/src/lib/Markdown/Markdown.tsx +++ b/src/lib/Markdown/Markdown.tsx @@ -1,84 +1,17 @@ import React, { useState } from 'react'; import axios from 'axios'; -import ContentSection from '../ContentSection/ContentSection'; - +import Section from './Section'; interface PropTypes { data?: string; url?: string; } -interface RawLinesPropType { - rawLines: string[]; - level?: number; -} - -const header = (level: number): string => { - return `^#{${level}} .*$`; -} - -const CodeBlock: React.FC<{ rawLines: String[]}> = ({ rawLines }) => { - return ( -

- {rawLines.map(line => <> {line}
)} -

- ); -} - - -const Content: React.FC = ({ rawLines }) => { - if (!rawLines.length) return <>; - const line = rawLines[0]; - const otherLines = rawLines.slice(1); - if (line.slice(0, 3) === '```') { - const closeIndex = otherLines.findIndex(line => line.slice(0, 3) === '```'); - console.log({ line, otherLines, closeIndex }); - return ( - <> - - - - ) - } - return ( - <> -

{line}

- - - ) -} - -const Level: React.FC = ({ rawLines, level = 0 }) => { - const name = rawLines[0].slice(level); - const contentSize = rawLines.findIndex(line => line.match(header(level + 1))); - - const rawContent = (contentSize > 0) ? rawLines.slice(1, contentSize) : rawLines.slice(1); - const rawChildren = rawLines.slice(contentSize); - - const childrenLineGroups = rawChildren.reduce((acc: string[][], cur: string) => { - if (cur.match(header(level + 1))) acc.push([]); - if (acc.length) acc[acc.length - 1].push(cur); - return acc; - }, []); - const children = childrenLineGroups.map(lineGroup => ) - - return level ? ( - - - {children} - - ) : ( - <> - {children} - - ); -} - const Markdown: React.FC = ({ data, url }) => { const [markdown, setMarkdown] = useState(data || ''); if (url) axios.get(url).then(response => setMarkdown(response.data)); - return + return
}; diff --git a/src/lib/Markdown/Section.tsx b/src/lib/Markdown/Section.tsx new file mode 100644 index 0000000..c902379 --- /dev/null +++ b/src/lib/Markdown/Section.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import ContentSection from '../ContentSection/ContentSection'; +import Content from './Content'; +import { ParserPropTypes } from './types'; + +interface PropTypes extends ParserPropTypes { + level?: number; +} + +const matchHeaderLevel = (line: string, level: number): boolean => { + return line.match(`^#{${level}} .*$`) !== null; +} + +const Section: React.FC = ({ rawLines, level = 0 }) => { + const sectionName = rawLines.splice(0, 1)[0].slice(level).trim(); + const contentSize = rawLines.findIndex(line => matchHeaderLevel(line, level + 1)); + const rawContent = rawLines.splice(0, (contentSize < 0) ? rawLines.length : contentSize); + + const childrenSectionLines = rawLines.reduce((sections: string[][], line: string) => { + if (matchHeaderLevel(line, level + 1)) sections.push([]); + if (sections.length) sections[sections.length - 1].push(line); + return sections; + }, []); + const children = childrenSectionLines.map(sectionLines =>
) + + return level ? ( + + + {children} + + ) : ( + <> + {children} + + ); +} + +export default Section; + diff --git a/src/lib/Markdown/types.d.ts b/src/lib/Markdown/types.d.ts new file mode 100644 index 0000000..0b6f4b6 --- /dev/null +++ b/src/lib/Markdown/types.d.ts @@ -0,0 +1,4 @@ +export interface ParserPropTypes { + rawLines: string[]; +} + -- cgit v1.2.3 From 0dad2f90bc79bdaa909184c618da5c0473972e0c Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sat, 4 Apr 2020 22:19:46 +0300 Subject: fix: rename types.d.ts -> types.ts --- src/lib/Markdown/types.d.ts | 4 ---- src/lib/Markdown/types.ts | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 src/lib/Markdown/types.d.ts create mode 100644 src/lib/Markdown/types.ts diff --git a/src/lib/Markdown/types.d.ts b/src/lib/Markdown/types.d.ts deleted file mode 100644 index 0b6f4b6..0000000 --- a/src/lib/Markdown/types.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface ParserPropTypes { - rawLines: string[]; -} - 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[]; +} + -- cgit v1.2.3 From a35cdfebf2d8c53c77900fee0826d4a967290e5d Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 01:01:30 +0300 Subject: feat: add Paragraph component --- src/lib/Markdown/Content.tsx | 5 ++-- src/lib/Markdown/Paragraph.tsx | 67 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 src/lib/Markdown/Paragraph.tsx diff --git a/src/lib/Markdown/Content.tsx b/src/lib/Markdown/Content.tsx index b7829ed..2a9e2ee 100644 --- a/src/lib/Markdown/Content.tsx +++ b/src/lib/Markdown/Content.tsx @@ -1,6 +1,7 @@ import React from 'react'; import CodeBlock from './CodeBlock'; +import Paragraph from './Paragraph'; import { ParserPropTypes } from './types'; @@ -16,10 +17,10 @@ const Content: React.FC = ({ rawLines }) => { let result; if (denotesCodeBlock(line)) { const closeIndex = rawLines.findIndex(line => denotesCodeBlock(line)); - const codeBlockLines = rawLines.splice(0, closeIndex); + const codeBlockLines = rawLines.splice(0, closeIndex + 1).slice(0, closeIndex); result = } else { - result =

{line}

+ result = } return ( diff --git a/src/lib/Markdown/Paragraph.tsx b/src/lib/Markdown/Paragraph.tsx new file mode 100644 index 0000000..f433cf5 --- /dev/null +++ b/src/lib/Markdown/Paragraph.tsx @@ -0,0 +1,67 @@ +import React from 'react'; + +interface PropTypes { + data: string; +} + +interface Closures { + [key: string]: string; +} + +interface Patterns { + [key: string]: RegExp; +} + +interface Styles { + [key: string]: React.CSSProperties; +} + + +const captureInside = (closure: string): any => { + return new RegExp(closure + '([^' + closure + ']+)' + closure); +} +const capture = (closure: string): any => { + return new RegExp('(' + closure + '[^' + closure + ']+' + closure + ')'); +} + +const closures: Closures = { + inlineCode: '`', + bold: '\\*\\*', +}; + +const styles: Styles = { + inlineCode: { background: '#444444', padding: '4px' }, + bold: { fontWeight: 'bold' }, +}; +const patterns: Patterns = {}; + + +Object.keys(closures).forEach((key: string): void => { + patterns[key] = capture(closures[key]); +}); + +const matcher = new RegExp(Object.values(patterns).map(regex => regex.source).join('|')); + +Object.keys(closures).forEach((key: string): void => { + patterns[key] = captureInside(closures[key]); +}); + + +const SyntaxSpan: React.FC = ({ data }) => { + if (!data) return null; + for (let key in styles) { + const match = data.match(patterns[key]); + if (match) return {match[1]}; + }; + return <>{data}; +} + +const Paragraph: React.FC = ({ data }) => { + let result; + result = data.split(matcher); + result = result.map(span => ); + return

{result}

; +} + +export default Paragraph; + -- cgit v1.2.3 From 208322f596240d23ff8248733f4b196c1baf8a5a Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 02:35:03 +0300 Subject: feat: parse all inline styles --- src/lib/Markdown/Paragraph.tsx | 81 +++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/src/lib/Markdown/Paragraph.tsx b/src/lib/Markdown/Paragraph.tsx index f433cf5..5ecedfc 100644 --- a/src/lib/Markdown/Paragraph.tsx +++ b/src/lib/Markdown/Paragraph.tsx @@ -4,62 +4,63 @@ interface PropTypes { data: string; } -interface Closures { - [key: string]: string; -} +type RuleName = 'bold' | 'italic' | 'inlineCode' | 'strikeThrough'; -interface Patterns { - [key: string]: RegExp; +interface RuleProperties { + enclosure: string; + style: React.CSSProperties; + pattern?: RegExp; } -interface Styles { - [key: string]: React.CSSProperties; -} +const rules: Record= { // Order matters - lowest property has highest priority + strikeThrough: { + enclosure: '~~', + style: { textDecoration: 'line-through' }, + }, + inlineCode: { + enclosure: '`', + style: { background: '#444444', padding: '4px' }, + }, + italic: { + enclosure: '\\*', + style: { fontStyle: 'italic' }, + }, + bold: { + enclosure: '\\*\\*', + style: { fontWeight: 'bold' }, + }, +}; +const ruleNames = Object.keys(rules) as RuleName[]; -const captureInside = (closure: string): any => { - return new RegExp(closure + '([^' + closure + ']+)' + closure); +const capture = (enclosure: string): RegExp => { + return new RegExp(enclosure + '([^' + enclosure + ']+)' + enclosure); } -const capture = (closure: string): any => { - return new RegExp('(' + closure + '[^' + closure + ']+' + closure + ')'); +const captureSplit = (enclosure: string): string => { + return '(' + enclosure + '[^' + enclosure + ']+' + enclosure + ')'; } -const closures: Closures = { - inlineCode: '`', - bold: '\\*\\*', -}; - -const styles: Styles = { - inlineCode: { background: '#444444', padding: '4px' }, - bold: { fontWeight: 'bold' }, -}; -const patterns: Patterns = {}; - - -Object.keys(closures).forEach((key: string): void => { - patterns[key] = capture(closures[key]); -}); - -const matcher = new RegExp(Object.values(patterns).map(regex => regex.source).join('|')); - -Object.keys(closures).forEach((key: string): void => { - patterns[key] = captureInside(closures[key]); +const ruleSplitPatterns: string[] = []; +ruleNames.forEach(name => { + rules[name].pattern = capture(rules[name].enclosure); + ruleSplitPatterns.push(captureSplit(rules[name].enclosure)); }); +const splitter = new RegExp(ruleSplitPatterns.join('|')); const SyntaxSpan: React.FC = ({ data }) => { if (!data) return null; - for (let key in styles) { - const match = data.match(patterns[key]); - if (match) return {match[1]}; - }; - return <>{data}; + let span = <>{data}; + ruleNames.forEach(name => { + const rule = rules[name]; + const match = data.match(rule.pattern || ''); + if (match) span = {match[1]}; + }); + return span; } const Paragraph: React.FC = ({ data }) => { - let result; - result = data.split(matcher); - result = result.map(span => ); + const result = data.split(splitter).map(span => ); return

{result}

; } -- cgit v1.2.3 From 6087d49a95b6c4164068c219847efc1e70399cb5 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 02:38:47 +0300 Subject: feat: add some paddings --- src/lib/ContentSection/ContentSection.tsx | 2 +- src/lib/Markdown/CodeBlock.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/ContentSection/ContentSection.tsx b/src/lib/ContentSection/ContentSection.tsx index 7ff47f9..68ecbca 100644 --- a/src/lib/ContentSection/ContentSection.tsx +++ b/src/lib/ContentSection/ContentSection.tsx @@ -13,7 +13,7 @@ interface PropTypes { const useStyles = makeStyles(theme => ({ content: { - padding: theme.spacing(0, 2, 1, 2), + padding: theme.spacing(1, 2, 1, 3), marginBottom: theme.spacing(1), '& .MuiButton-root': { diff --git a/src/lib/Markdown/CodeBlock.tsx b/src/lib/Markdown/CodeBlock.tsx index e449f92..ee2114e 100644 --- a/src/lib/Markdown/CodeBlock.tsx +++ b/src/lib/Markdown/CodeBlock.tsx @@ -3,7 +3,7 @@ import { ParserPropTypes } from './types'; const CodeBlock: React.FC = ({ rawLines }) => { return ( -

+

{rawLines.map(line => <> {line}
)}

); -- cgit v1.2.3 From 351cbf596fdda867240d76b632f9d205f343153c Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 05:06:34 +0300 Subject: feat: parse links and images --- src/lib/Markdown/Paragraph.tsx | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/lib/Markdown/Paragraph.tsx b/src/lib/Markdown/Paragraph.tsx index 5ecedfc..a473142 100644 --- a/src/lib/Markdown/Paragraph.tsx +++ b/src/lib/Markdown/Paragraph.tsx @@ -1,23 +1,25 @@ import React from 'react'; +import { Link } from '@material-ui/core'; interface PropTypes { data: string; } -type RuleName = 'bold' | 'italic' | 'inlineCode' | 'strikeThrough'; +type InlineRuleName = 'bold' | 'italic' | 'code' | 'strikeThrough'; -interface RuleProperties { +interface InlineRuleProperties { enclosure: string; style: React.CSSProperties; pattern?: RegExp; } -const rules: Record= { // Order matters - lowest property has highest priority +const inlineRules: Record= { + // Order matters - lowest property has highest priority strikeThrough: { enclosure: '~~', style: { textDecoration: 'line-through' }, }, - inlineCode: { + code: { enclosure: '`', style: { background: '#444444', padding: '4px' }, }, @@ -31,28 +33,41 @@ const rules: Record= { // Order matters - lowest prop }, }; -const ruleNames = Object.keys(rules) as RuleName[]; +const inlineRuleNames = Object.keys(inlineRules) as InlineRuleName[]; -const capture = (enclosure: string): RegExp => { +const captureInline = (enclosure: string): RegExp => { return new RegExp(enclosure + '([^' + enclosure + ']+)' + enclosure); } -const captureSplit = (enclosure: string): string => { +const captureInlineSplit = (enclosure: string): string => { return '(' + enclosure + '[^' + enclosure + ']+' + enclosure + ')'; } +const concealRegex = /!?\[(.+?)\]\((.+?)\)/; +const concealRegexSplit = /(!?\[.+?\]\(.+?\))/g; +const rawLinkRegex = /((?:(?:[A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www\.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)(?:(?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[.!/\\\w]*))?)/; -const ruleSplitPatterns: string[] = []; -ruleNames.forEach(name => { - rules[name].pattern = capture(rules[name].enclosure); - ruleSplitPatterns.push(captureSplit(rules[name].enclosure)); +const ruleSplitPatterns: string[] = [concealRegexSplit.source, rawLinkRegex.source]; +inlineRuleNames.forEach(name => { + const enclosure = inlineRules[name].enclosure; + inlineRules[name].pattern = captureInline(enclosure); + ruleSplitPatterns.push(captureInlineSplit(enclosure)); }); const splitter = new RegExp(ruleSplitPatterns.join('|')); const SyntaxSpan: React.FC = ({ data }) => { if (!data) return null; + + const conceal = concealRegex.exec(data); + if (conceal) { + if (data[0] === '!') return {conceal[1]}; + return {conceal[1]}; + } + + if (data.match(rawLinkRegex)) return {data}; + let span = <>{data}; - ruleNames.forEach(name => { - const rule = rules[name]; + inlineRuleNames.forEach(name => { + const rule = inlineRules[name]; const match = data.match(rule.pattern || ''); if (match) span = {match[1]}; }); @@ -66,3 +81,4 @@ const Paragraph: React.FC = ({ data }) => { export default Paragraph; + -- cgit v1.2.3 From 0389a542776f8f7fc3bc71a54b0aca7179f6fed3 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 07:44:13 +0300 Subject: refactor: improve Markdown/Section :zap: --- src/lib/Markdown/Section.tsx | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/lib/Markdown/Section.tsx b/src/lib/Markdown/Section.tsx index c902379..5ae4a10 100644 --- a/src/lib/Markdown/Section.tsx +++ b/src/lib/Markdown/Section.tsx @@ -11,27 +11,33 @@ const matchHeaderLevel = (line: string, level: number): boolean => { return line.match(`^#{${level}} .*$`) !== null; } -const Section: React.FC = ({ rawLines, level = 0 }) => { - const sectionName = rawLines.splice(0, 1)[0].slice(level).trim(); - const contentSize = rawLines.findIndex(line => matchHeaderLevel(line, level + 1)); - const rawContent = rawLines.splice(0, (contentSize < 0) ? rawLines.length : contentSize); - +const ChildrenSections: React.FC = ({ rawLines, level = 1 }) => { const childrenSectionLines = rawLines.reduce((sections: string[][], line: string) => { - if (matchHeaderLevel(line, level + 1)) sections.push([]); + if (matchHeaderLevel(line, level)) sections.push([]); if (sections.length) sections[sections.length - 1].push(line); return sections; }, []); - const children = childrenSectionLines.map(sectionLines =>
) + const children = childrenSectionLines.map(sectionLines =>
); + return <> {children} ; +} + +const Section: React.FC = ({ rawLines, level = 0 }) => { + if (!level) { + const beforeMarkdown = rawLines.splice(0, rawLines.findIndex(line => line.match(/^#.+$/g))); + console.log(`This content was found in original .md file but will not be shown: ${beforeMarkdown.join()}`); + while(rawLines[0][level + 1] === '#') level++; + return ; + } + + const sectionName = rawLines.splice(0, 1)[0].slice(level).trim(); + const contentSize = rawLines.findIndex(line => matchHeaderLevel(line, level + 1)); + const rawContent = rawLines.splice(0, (contentSize < 0) ? rawLines.length : contentSize); - return level ? ( - + return ( + - {children} + - ) : ( - <> - {children} - ); } -- cgit v1.2.3 From 21cb0da607e4c0b71f847756e2736cf99b143885 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 08:57:10 +0300 Subject: feat: parse emoji :fire: --- src/lib/Markdown/Paragraph.tsx | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/lib/Markdown/Paragraph.tsx b/src/lib/Markdown/Paragraph.tsx index a473142..19f8382 100644 --- a/src/lib/Markdown/Paragraph.tsx +++ b/src/lib/Markdown/Paragraph.tsx @@ -1,11 +1,17 @@ import React from 'react'; import { Link } from '@material-ui/core'; +import axios from 'axios'; interface PropTypes { data: string; } -type InlineRuleName = 'bold' | 'italic' | 'code' | 'strikeThrough'; +interface Emoji { + name: string; + char: string; +} + +type InlineRuleName = 'bold' | 'italic' | 'code' | 'strikeThrough' | 'emoji'; interface InlineRuleProperties { enclosure: string; @@ -31,6 +37,10 @@ const inlineRules: Record= { enclosure: '\\*\\*', style: { fontWeight: 'bold' }, }, + emoji: { + enclosure: ':', + style: {}, + } }; const inlineRuleNames = Object.keys(inlineRules) as InlineRuleName[]; @@ -54,6 +64,11 @@ inlineRuleNames.forEach(name => { const splitter = new RegExp(ruleSplitPatterns.join('|')); +const emojiList: Emoji[] = []; +axios.get('https://unpkg.com/emojilib@2.4.0/emojis.json').then(response => { + Object.keys(response.data).forEach(name => emojiList.push({ name, char: response.data[name].char })); +}); + const SyntaxSpan: React.FC = ({ data }) => { if (!data) return null; @@ -69,7 +84,12 @@ const SyntaxSpan: React.FC = ({ data }) => { inlineRuleNames.forEach(name => { const rule = inlineRules[name]; const match = data.match(rule.pattern || ''); - if (match) span = {match[1]}; + if (match) { + if (name === 'emoji') { + const emoji = emojiList.find(emoji => emoji.name === match[1]); + span = {emoji ? emoji.char : ''}; + } else span = {match[1]}; + } }); return span; } -- cgit v1.2.3 From f67d9b549f8cba939a0420c16207a9e71b03be37 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 09:06:02 +0300 Subject: feat: change sectionName size respectively --- src/lib/ContentSection/ContentSection.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/lib/ContentSection/ContentSection.tsx b/src/lib/ContentSection/ContentSection.tsx index 68ecbca..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(1, 2, 1, 3), + 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 = ({ sectionName, children }) => { +const ContentSection: React.FC = ({ 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 ( <> - {sectionName} + {sectionName} {children} -- cgit v1.2.3 From bc8e9919925d9d7ee640e8091aebe9596f9e89eb Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 11:06:56 +0300 Subject: refactor: refine Section component --- src/lib/Markdown/Section.tsx | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/lib/Markdown/Section.tsx b/src/lib/Markdown/Section.tsx index 5ae4a10..5ce8954 100644 --- a/src/lib/Markdown/Section.tsx +++ b/src/lib/Markdown/Section.tsx @@ -7,14 +7,19 @@ interface PropTypes extends ParserPropTypes { level?: number; } -const matchHeaderLevel = (line: string, level: number): boolean => { - return line.match(`^#{${level}} .*$`) !== null; +const getHeaderLevel = (header: string): number => { + if (!header) return 0; + let level = 0; + while(header[level] === '#') level++; + return level; } -const ChildrenSections: React.FC = ({ rawLines, level = 1 }) => { +const ChildrenSections: React.FC = ({ rawLines, level = 0 }) => { const childrenSectionLines = rawLines.reduce((sections: string[][], line: string) => { - if (matchHeaderLevel(line, level)) sections.push([]); - if (sections.length) sections[sections.length - 1].push(line); + if (line) { + if (getHeaderLevel(line) === level) sections.push([]); + if (sections.length) sections[sections.length - 1].push(line); + } return sections; }, []); const children = childrenSectionLines.map(sectionLines =>
); @@ -22,21 +27,17 @@ const ChildrenSections: React.FC = ({ rawLines, level = 1 }) => { } const Section: React.FC = ({ rawLines, level = 0 }) => { - if (!level) { - const beforeMarkdown = rawLines.splice(0, rawLines.findIndex(line => line.match(/^#.+$/g))); - console.log(`This content was found in original .md file but will not be shown: ${beforeMarkdown.join()}`); - while(rawLines[0][level + 1] === '#') level++; - return ; - } + const deeperLevelIndex = rawLines.findIndex(line => line.match(`^#{${level + 1},} .*$`)); + const rawContent = rawLines.splice(0, (deeperLevelIndex < 0) ? rawLines.length : deeperLevelIndex); - const sectionName = rawLines.splice(0, 1)[0].slice(level).trim(); - const contentSize = rawLines.findIndex(line => matchHeaderLevel(line, level + 1)); - const rawContent = rawLines.splice(0, (contentSize < 0) ? rawLines.length : contentSize); + if (!level) return ; + const sectionName = rawContent.splice(0, 1)[0].slice(level).trim(); + const deeperLevel = getHeaderLevel(rawLines[0]); return ( - + ); } -- cgit v1.2.3 From 39e1d32c669545ccc30e0d424323c6a01317c4be Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 12:56:41 +0300 Subject: refactor: move some logic to InlineSyntax.tsx --- src/lib/Markdown/CodeBlock.tsx | 2 +- src/lib/Markdown/Content.tsx | 2 +- src/lib/Markdown/InlineSyntax.tsx | 81 ++++++++++++++++++++++++++++++ src/lib/Markdown/Paragraph.tsx | 100 ++------------------------------------ src/lib/Markdown/types.ts | 4 ++ 5 files changed, 91 insertions(+), 98 deletions(-) create mode 100644 src/lib/Markdown/InlineSyntax.tsx diff --git a/src/lib/Markdown/CodeBlock.tsx b/src/lib/Markdown/CodeBlock.tsx index ee2114e..2718647 100644 --- a/src/lib/Markdown/CodeBlock.tsx +++ b/src/lib/Markdown/CodeBlock.tsx @@ -3,7 +3,7 @@ import { ParserPropTypes } from './types'; const CodeBlock: React.FC = ({ rawLines }) => { return ( -

+

{rawLines.map(line => <> {line}
)}

); diff --git a/src/lib/Markdown/Content.tsx b/src/lib/Markdown/Content.tsx index 2a9e2ee..d0a2193 100644 --- a/src/lib/Markdown/Content.tsx +++ b/src/lib/Markdown/Content.tsx @@ -20,7 +20,7 @@ const Content: React.FC = ({ rawLines }) => { const codeBlockLines = rawLines.splice(0, closeIndex + 1).slice(0, closeIndex); result = } else { - result = + result = } return ( diff --git a/src/lib/Markdown/InlineSyntax.tsx b/src/lib/Markdown/InlineSyntax.tsx new file mode 100644 index 0000000..bf5669d --- /dev/null +++ b/src/lib/Markdown/InlineSyntax.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { Link } from '@material-ui/core'; +import axios from 'axios'; + +import { InlineParserPropTypes } from './types'; + +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 = { + 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[] = []; +axios.get('https://unpkg.com/emojilib@2.4.0/emojis.json').then(response => { + Object.keys(response.data).forEach(name => emojiList.push({ name, char: response.data[name].char })); +}); + + +const InlineSyntax: React.FC = ({ line }) => { + if (!line) return null; + + const matchConceal = regex.conceal.local.exec(line); + if (matchConceal) { + if (line[0] === '!') return {matchConceal[1]}; + return {matchConceal[1]}; + } + + const matchEmoji = line.match(regex.emoji.local); + if (matchEmoji) { + const emoji = emojiList.find(emoji => emoji.name === matchEmoji[1]); + return {emoji ? emoji.char : line}; + } + + const matchCode = line.match(regex.code.local); + if (matchCode) return {matchCode[1]}; + + const matchBold = line.match(regex.bold.local); + if (matchBold) return {matchBold[1]}; + + const matchItalic = line.match(regex.italic.local); + if (matchItalic) return {matchItalic[1]}; + + const matchStrikeThrough = line.match(regex.strikeThrough.local); + if (matchStrikeThrough) return {matchStrikeThrough[1]}; + + if (line.match(regex.rawLink.global)) return {line}; + + return <>{line}; +} + + +export { splitter }; +export default InlineSyntax; + diff --git a/src/lib/Markdown/Paragraph.tsx b/src/lib/Markdown/Paragraph.tsx index 19f8382..f46199e 100644 --- a/src/lib/Markdown/Paragraph.tsx +++ b/src/lib/Markdown/Paragraph.tsx @@ -1,101 +1,9 @@ import React from 'react'; -import { Link } from '@material-ui/core'; -import axios from 'axios'; +import { InlineParserPropTypes } from './types'; +import InlineSyntax, { splitter } from './InlineSyntax'; -interface PropTypes { - data: string; -} - -interface Emoji { - name: string; - char: string; -} - -type InlineRuleName = 'bold' | 'italic' | 'code' | 'strikeThrough' | 'emoji'; - -interface InlineRuleProperties { - enclosure: string; - style: React.CSSProperties; - pattern?: RegExp; -} - -const inlineRules: Record= { - // Order matters - lowest property has highest priority - strikeThrough: { - enclosure: '~~', - style: { textDecoration: 'line-through' }, - }, - code: { - enclosure: '`', - style: { background: '#444444', padding: '4px' }, - }, - italic: { - enclosure: '\\*', - style: { fontStyle: 'italic' }, - }, - bold: { - enclosure: '\\*\\*', - style: { fontWeight: 'bold' }, - }, - emoji: { - enclosure: ':', - style: {}, - } -}; - -const inlineRuleNames = Object.keys(inlineRules) as InlineRuleName[]; - -const captureInline = (enclosure: string): RegExp => { - return new RegExp(enclosure + '([^' + enclosure + ']+)' + enclosure); -} -const captureInlineSplit = (enclosure: string): string => { - return '(' + enclosure + '[^' + enclosure + ']+' + enclosure + ')'; -} -const concealRegex = /!?\[(.+?)\]\((.+?)\)/; -const concealRegexSplit = /(!?\[.+?\]\(.+?\))/g; -const rawLinkRegex = /((?:(?:[A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www\.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)(?:(?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[.!/\\\w]*))?)/; - -const ruleSplitPatterns: string[] = [concealRegexSplit.source, rawLinkRegex.source]; -inlineRuleNames.forEach(name => { - const enclosure = inlineRules[name].enclosure; - inlineRules[name].pattern = captureInline(enclosure); - ruleSplitPatterns.push(captureInlineSplit(enclosure)); -}); - -const splitter = new RegExp(ruleSplitPatterns.join('|')); - -const emojiList: Emoji[] = []; -axios.get('https://unpkg.com/emojilib@2.4.0/emojis.json').then(response => { - Object.keys(response.data).forEach(name => emojiList.push({ name, char: response.data[name].char })); -}); - -const SyntaxSpan: React.FC = ({ data }) => { - if (!data) return null; - - const conceal = concealRegex.exec(data); - if (conceal) { - if (data[0] === '!') return {conceal[1]}; - return {conceal[1]}; - } - - if (data.match(rawLinkRegex)) return {data}; - - let span = <>{data}; - inlineRuleNames.forEach(name => { - const rule = inlineRules[name]; - const match = data.match(rule.pattern || ''); - if (match) { - if (name === 'emoji') { - const emoji = emojiList.find(emoji => emoji.name === match[1]); - span = {emoji ? emoji.char : ''}; - } else span = {match[1]}; - } - }); - return span; -} - -const Paragraph: React.FC = ({ data }) => { - const result = data.split(splitter).map(span => ); +const Paragraph: React.FC = ({ line }) => { + const result = line.split(splitter).map(span => ); return

{result}

; } diff --git a/src/lib/Markdown/types.ts b/src/lib/Markdown/types.ts index 0b6f4b6..8fb28ed 100644 --- a/src/lib/Markdown/types.ts +++ b/src/lib/Markdown/types.ts @@ -1,3 +1,7 @@ +export interface InlineParserPropTypes { + line: string; +} + export interface ParserPropTypes { rawLines: string[]; } -- cgit v1.2.3 From 62a543258a111f0f45be74b6ec3d887e15f67376 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 14:18:45 +0300 Subject: feat: beautify CodeBlock --- src/lib/Markdown/CodeBlock.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/lib/Markdown/CodeBlock.tsx b/src/lib/Markdown/CodeBlock.tsx index 2718647..cbb3078 100644 --- a/src/lib/Markdown/CodeBlock.tsx +++ b/src/lib/Markdown/CodeBlock.tsx @@ -1,11 +1,23 @@ 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(2), + overflowX: 'auto', + }, +})); const CodeBlock: React.FC = ({ rawLines }) => { + const classes = useStyles(); return ( -

+ {rawLines.map(line => <> {line}
)} -

+ ); } -- cgit v1.2.3 From 16cf77f051048fbcca89184462d31bfcb1e6c699 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 15:26:18 +0300 Subject: feat: update index.tsx :fire: --- src/index.tsx | 82 +++++++++++++++++++++++++---------------------------------- 1 file changed, 34 insertions(+), 48 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 6646cf0..704805c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,15 +1,12 @@ 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, Header, Window, - ContentSection, - SmartList, - Button, Markdown, } from './lib'; @@ -32,8 +29,16 @@ const Icon = logo const headerContents = { home: null, - page: null, - 'another page': null, + space: null, + 'emoji': null, + 'material-ui': null, +}; + +const pageMap: Record = { + home: "https://raw.githubusercontent.com/eug-vs/react-benzin/develop/README.md", + space: "https://raw.githubusercontent.com/eug-vs/space/master/docs/environment.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", }; @@ -41,30 +46,24 @@ const App: React.FC = () => { const classes = useStyles(); const [page, setPage] = useState('home'); - const renderItem: React.FC = ({ index, style}) => { - return ( -
- -

- 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. -

- {(index % 2 === 0)? - ( - - ) - : - ( - - ) - } -
-
- ); - }; + const url = pageMap[page]; + const filename = url.slice(url.lastIndexOf('/') + 1); + const metadata = [ + `## Markdown\n [Markdown file](${url}) that you can see on the left was parsed and processed by **BENZIN**!`, + 'Switch between tabs on the header to explore other markdown templates.', + 'Currently **only core features** of markdown function.', + 'Templates on the left are being loaded from the internet, though this pane is generated from plaintext.', + '## How do I use this feature?', + '```', + 'import Markdown from \'react-benzin\';', + 'const data = \'# Header\\nHello, *world!* \';', + 'ReactDOM.render(, document.getElementById(\'root\'));', + '```', + 'Yep! **Or even simpler**:', + '```', + 'ReactDOM.render(, document.getElementById(\'root\'));', + '```', + ].join('\n'); return ( @@ -79,26 +78,13 @@ const App: React.FC = () => { />
- -

- 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. Phasellus lacus. Etiam laoreet quam sed arcu. Phasellus at dui in ligula mollis ultricies. Integer placerat tristique nisl. Praesent augue. Fusce commodo. -

- - -
- +
- - + +
+ +
); -- cgit v1.2.3 From 1a00cd504ff60f1749327ebc3896a4641d28670e Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 16:31:28 +0300 Subject: feat: install emojilib, beautify inline code --- package-lock.json | 5 +++++ package.json | 1 + src/lib/Markdown/CodeBlock.tsx | 1 + src/lib/Markdown/InlineSyntax.tsx | 29 +++++++++++++++++++++-------- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4bde2bb..0379211 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4792,6 +4792,11 @@ "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", diff --git a/package.json b/package.json index d1d2e04..a4dc5bd 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "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", diff --git a/src/lib/Markdown/CodeBlock.tsx b/src/lib/Markdown/CodeBlock.tsx index cbb3078..c4478eb 100644 --- a/src/lib/Markdown/CodeBlock.tsx +++ b/src/lib/Markdown/CodeBlock.tsx @@ -9,6 +9,7 @@ const useStyles = makeStyles(theme => ({ background: theme.palette.background.default, padding: theme.spacing(2), overflowX: 'auto', + fontFamily: 'Monospace', }, })); diff --git a/src/lib/Markdown/InlineSyntax.tsx b/src/lib/Markdown/InlineSyntax.tsx index bf5669d..48c0374 100644 --- a/src/lib/Markdown/InlineSyntax.tsx +++ b/src/lib/Markdown/InlineSyntax.tsx @@ -1,7 +1,8 @@ import React from 'react'; -import { Link } from '@material-ui/core'; -import axios from 'axios'; +import { Link, makeStyles } from '@material-ui/core'; +// @ts-ignore-next-line; +import { lib as emojiLib } from 'emojilib'; import { InlineParserPropTypes } from './types'; interface RegexPair { @@ -38,17 +39,29 @@ const regex: Record = { const splitter = new RegExp(Object.values(regex).map(pair => pair.global.source).join('|')); const emojiList: Emoji[] = []; -axios.get('https://unpkg.com/emojilib@2.4.0/emojis.json').then(response => { - Object.keys(response.data).forEach(name => emojiList.push({ name, char: response.data[name].char })); -}); - +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 InlineSyntax: React.FC = ({ line }) => { + const classes = useStyles(); if (!line) return null; const matchConceal = regex.conceal.local.exec(line); if (matchConceal) { - if (line[0] === '!') return {matchConceal[1]}; + if (line[0] === '!') return {matchConceal[1]}; return {matchConceal[1]}; } @@ -59,7 +72,7 @@ const InlineSyntax: React.FC = ({ line }) => { } const matchCode = line.match(regex.code.local); - if (matchCode) return {matchCode[1]}; + if (matchCode) return {matchCode[1]}; const matchBold = line.match(regex.bold.local); if (matchBold) return {matchBold[1]}; -- cgit v1.2.3 From 5abf2d02bd4d11097b2650861195cf605e42bbf7 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 16:32:44 +0300 Subject: fix: update markdown on hook --- src/lib/Markdown/Markdown.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/Markdown/Markdown.tsx b/src/lib/Markdown/Markdown.tsx index aee96e9..09ad54a 100644 --- a/src/lib/Markdown/Markdown.tsx +++ b/src/lib/Markdown/Markdown.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import axios from 'axios'; import Section from './Section'; @@ -10,6 +10,11 @@ interface PropTypes { const Markdown: React.FC = ({ data, url }) => { const [markdown, setMarkdown] = useState(data || ''); + + useEffect(() => { + if (!url) setMarkdown(data || ''); + }, [data, url]); + if (url) axios.get(url).then(response => setMarkdown(response.data)); return
}; -- cgit v1.2.3 From 63c51e7c3f266534624a4a115fdda21076dce562 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 17:26:07 +0300 Subject: feat: add emojis and spacevim tab to index --- src/index.tsx | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 704805c..0128140 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -30,15 +30,17 @@ const Icon = logo const headerContents = { home: null, space: null, + 'spacevim': null, 'emoji': null, 'material-ui': null, }; const pageMap: Record = { - home: "https://raw.githubusercontent.com/eug-vs/react-benzin/develop/README.md", - space: "https://raw.githubusercontent.com/eug-vs/space/master/docs/environment.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", + 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', }; @@ -47,16 +49,16 @@ const App: React.FC = () => { const [page, setPage] = useState('home'); const url = pageMap[page]; - const filename = url.slice(url.lastIndexOf('/') + 1); + const fileName = url.slice(url.lastIndexOf('/') + 1); const metadata = [ - `## Markdown\n [Markdown file](${url}) that you can see on the left was parsed and processed by **BENZIN**!`, - 'Switch between tabs on the header to explore other markdown templates.', + `## 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 internet, though this pane is generated from plaintext.', + 'Templates on the left are being loaded from the internet, though this pane is generated from plaintext. :pen:', '## How do I use this feature?', '```', 'import Markdown from \'react-benzin\';', - 'const data = \'# Header\\nHello, *world!* \';', + 'const data = \'# Header\\nHello, *world!*\';', 'ReactDOM.render(, document.getElementById(\'root\'));', '```', 'Yep! **Or even simpler**:', -- cgit v1.2.3 From b26b6c045419893bc375616d8f186bd41c5ccca1 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 17:51:58 +0300 Subject: fix: declare emojilib module --- src/lib/Markdown/InlineSyntax.tsx | 1 - src/react-app-env.d.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Markdown/InlineSyntax.tsx b/src/lib/Markdown/InlineSyntax.tsx index 48c0374..53cdaf0 100644 --- a/src/lib/Markdown/InlineSyntax.tsx +++ b/src/lib/Markdown/InlineSyntax.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Link, makeStyles } from '@material-ui/core'; -// @ts-ignore-next-line; import { lib as emojiLib } from 'emojilib'; import { InlineParserPropTypes } from './types'; diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index 6431bc5..46be55a 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -1 +1,2 @@ /// +declare module 'emojilib'; -- cgit v1.2.3 From eda4a1dceeb3d8db5ce47a555f098dbaa1deb996 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 20:02:05 +0300 Subject: feat: parse dotted lists --- src/lib/Markdown/Content.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lib/Markdown/Content.tsx b/src/lib/Markdown/Content.tsx index d0a2193..593b9e9 100644 --- a/src/lib/Markdown/Content.tsx +++ b/src/lib/Markdown/Content.tsx @@ -2,11 +2,16 @@ import React from 'react'; import CodeBlock from './CodeBlock'; import Paragraph from './Paragraph'; +import InlineSyntax from './InlineSyntax'; import { ParserPropTypes } from './types'; const denotesCodeBlock = (line: string): boolean => { - return line.slice(0, 3) === '```'; + return line.match(/^```.*$/) !== null; +} + +const denotesDottedList = (line: string): boolean => { + return line.match(/^ ?- .*$/) !== null; } const Content: React.FC = ({ rawLines }) => { @@ -19,6 +24,11 @@ const Content: React.FC = ({ rawLines }) => { const closeIndex = rawLines.findIndex(line => denotesCodeBlock(line)); const codeBlockLines = rawLines.splice(0, closeIndex + 1).slice(0, closeIndex); result = + } else if (denotesDottedList(line)) { + const closeIndex = rawLines.findIndex(line => !denotesDottedList(line)); + const dottedListLines = rawLines.splice(0, closeIndex).slice(0, closeIndex); + dottedListLines.unshift(line); + result =
    {dottedListLines.map(li =>
  • )}
; } else { result = } @@ -28,7 +38,7 @@ const Content: React.FC = ({ rawLines }) => { { result } - ) + ); } export default Content; -- cgit v1.2.3 From b6e58c0f8eebb0447e5330b983ec387dbf2a59c0 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 20:42:25 +0300 Subject: feat: parse html :rocket: --- src/lib/Markdown/Content.tsx | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/lib/Markdown/Content.tsx b/src/lib/Markdown/Content.tsx index 593b9e9..1f0fcfc 100644 --- a/src/lib/Markdown/Content.tsx +++ b/src/lib/Markdown/Content.tsx @@ -14,28 +14,45 @@ const denotesDottedList = (line: string): boolean => { return line.match(/^ ?- .*$/) !== null; } +const denotesOpenHtmlTag = (line: string): string => { + const regex = /<([^/\s]*)[^<]*[^/]>/g; + const match = regex.exec(line); + return match ? match[1] : ''; +} + +const denotesClosingHtmlTag = (line: string, tag: string): boolean => { + const regex = new RegExp(``); + return line.match(regex) !== null; +} + const Content: React.FC = ({ rawLines }) => { if (!rawLines.length) return null; const line = rawLines.splice(0, 1)[0]; - let result; + let buffer; if (denotesCodeBlock(line)) { const closeIndex = rawLines.findIndex(line => denotesCodeBlock(line)); const codeBlockLines = rawLines.splice(0, closeIndex + 1).slice(0, closeIndex); - result = + buffer = } else if (denotesDottedList(line)) { const closeIndex = rawLines.findIndex(line => !denotesDottedList(line)); const dottedListLines = rawLines.splice(0, closeIndex).slice(0, closeIndex); dottedListLines.unshift(line); - result =
    {dottedListLines.map(li =>
  • )}
; + buffer =
    {dottedListLines.map(li =>
  • )}
; + } else if (denotesOpenHtmlTag(line)) { + const tag = denotesOpenHtmlTag(line); + const closeIndex = rawLines.findIndex(line => denotesClosingHtmlTag(line, tag)); + const htmlLines = rawLines.splice(0, closeIndex + 1).slice(0, closeIndex); + htmlLines.unshift(line); + buffer =
; } else { - result = + buffer = } return ( <> - { result } + { buffer } ); -- cgit v1.2.3 From 1f67a4e15b9ab10f8ecba2ee93e6a4c44e3c71ec Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 21:10:50 +0300 Subject: fix: use
 in CodeBlock

---
 src/lib/Markdown/CodeBlock.tsx | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/lib/Markdown/CodeBlock.tsx b/src/lib/Markdown/CodeBlock.tsx
index c4478eb..5b8edec 100644
--- a/src/lib/Markdown/CodeBlock.tsx
+++ b/src/lib/Markdown/CodeBlock.tsx
@@ -7,9 +7,10 @@ import { makeStyles } from '@material-ui/core/styles';
 const useStyles = makeStyles(theme => ({
   root: {
     background: theme.palette.background.default,
-    padding: theme.spacing(2),
+    padding: theme.spacing(1),
     overflowX: 'auto',
     fontFamily: 'Monospace',
+    scrollbarColor: 'auto'
   },
 }));
 
@@ -17,7 +18,7 @@ const CodeBlock: React.FC = ({ rawLines }) => {
   const classes = useStyles();
   return (
     
-      {rawLines.map(line => <> {line} 
)} + {rawLines.map(line =>
{line}
)}
); } -- cgit v1.2.3 From 09f8c6865fb9a7e239cc907e313db44cccb7b5e8 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 21:57:13 +0300 Subject: feat: Paragraph -> Text, parse selfClosingHtml --- src/lib/Markdown/Content.tsx | 30 +++++++++++++++++++++++------- src/lib/Markdown/Paragraph.tsx | 12 ------------ src/lib/Markdown/Text.tsx | 10 ++++++++++ 3 files changed, 33 insertions(+), 19 deletions(-) delete mode 100644 src/lib/Markdown/Paragraph.tsx create mode 100644 src/lib/Markdown/Text.tsx diff --git a/src/lib/Markdown/Content.tsx b/src/lib/Markdown/Content.tsx index 1f0fcfc..e27b63f 100644 --- a/src/lib/Markdown/Content.tsx +++ b/src/lib/Markdown/Content.tsx @@ -1,7 +1,7 @@ import React from 'react'; import CodeBlock from './CodeBlock'; -import Paragraph from './Paragraph'; +import Text from './Text'; import InlineSyntax from './InlineSyntax'; import { ParserPropTypes } from './types'; @@ -14,17 +14,22 @@ const denotesDottedList = (line: string): boolean => { return line.match(/^ ?- .*$/) !== null; } -const denotesOpenHtmlTag = (line: string): string => { +const denotesOpenHtml= (line: string): string => { const regex = /<([^/\s]*)[^<]*[^/]>/g; const match = regex.exec(line); return match ? match[1] : ''; } -const denotesClosingHtmlTag = (line: string, tag: string): boolean => { +const denotesClosingHtml= (line: string, tag: string): boolean => { const regex = new RegExp(``); return line.match(regex) !== null; } +const denotesSelfClosingHtml = (line: string): any => { + const regex = /(<[^/\s]*[^<]*\/>)/g; + return line.match(regex); +} + const Content: React.FC = ({ rawLines }) => { if (!rawLines.length) return null; @@ -40,14 +45,25 @@ const Content: React.FC = ({ rawLines }) => { const dottedListLines = rawLines.splice(0, closeIndex).slice(0, closeIndex); dottedListLines.unshift(line); buffer =
    {dottedListLines.map(li =>
  • )}
; - } else if (denotesOpenHtmlTag(line)) { - const tag = denotesOpenHtmlTag(line); - const closeIndex = rawLines.findIndex(line => denotesClosingHtmlTag(line, tag)); + } else if (denotesOpenHtml(line)) { + const tag = denotesOpenHtml(line); + const closeIndex = rawLines.findIndex(line => denotesClosingHtml(line, tag)); const htmlLines = rawLines.splice(0, closeIndex + 1).slice(0, closeIndex); htmlLines.unshift(line); buffer =
; + } else if (denotesSelfClosingHtml(line) !== null) { + const match = denotesSelfClosingHtml(line)[0]; + const [before, after] = line.split(match); + console.log({ line, match, before, after}); + buffer = ( + <> + +
+ + + ); } else { - buffer = + buffer =

} return ( diff --git a/src/lib/Markdown/Paragraph.tsx b/src/lib/Markdown/Paragraph.tsx deleted file mode 100644 index f46199e..0000000 --- a/src/lib/Markdown/Paragraph.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { InlineParserPropTypes } from './types'; -import InlineSyntax, { splitter } from './InlineSyntax'; - -const Paragraph: React.FC = ({ line }) => { - const result = line.split(splitter).map(span => ); - return

{result}

; -} - -export default Paragraph; - - diff --git a/src/lib/Markdown/Text.tsx b/src/lib/Markdown/Text.tsx new file mode 100644 index 0000000..b989476 --- /dev/null +++ b/src/lib/Markdown/Text.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { InlineParserPropTypes } from './types'; +import InlineSyntax, { splitter } from './InlineSyntax'; + +const Text: React.FC = ({ line }) => { + return <>{line.split(splitter).map(span => )}; +} + +export default Text; + -- cgit v1.2.3 From 828946eb02bdfaa7ec5631f1e854881f874f7b7e Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 22:06:54 +0300 Subject: refactor: rename InlineSyntax -> SyntacticSpan --- src/lib/Markdown/Content.tsx | 3 +- src/lib/Markdown/InlineSyntax.tsx | 93 ------------------------------------ src/lib/Markdown/SyntacticSpan.tsx | 96 ++++++++++++++++++++++++++++++++++++++ src/lib/Markdown/Text.tsx | 11 +++-- 4 files changed, 104 insertions(+), 99 deletions(-) delete mode 100644 src/lib/Markdown/InlineSyntax.tsx create mode 100644 src/lib/Markdown/SyntacticSpan.tsx diff --git a/src/lib/Markdown/Content.tsx b/src/lib/Markdown/Content.tsx index e27b63f..caac91c 100644 --- a/src/lib/Markdown/Content.tsx +++ b/src/lib/Markdown/Content.tsx @@ -2,7 +2,6 @@ import React from 'react'; import CodeBlock from './CodeBlock'; import Text from './Text'; -import InlineSyntax from './InlineSyntax'; import { ParserPropTypes } from './types'; @@ -44,7 +43,7 @@ const Content: React.FC = ({ rawLines }) => { const closeIndex = rawLines.findIndex(line => !denotesDottedList(line)); const dottedListLines = rawLines.splice(0, closeIndex).slice(0, closeIndex); dottedListLines.unshift(line); - buffer =
    {dottedListLines.map(li =>
  • )}
; + buffer =
    {dottedListLines.map(li =>
  • )}
; } else if (denotesOpenHtml(line)) { const tag = denotesOpenHtml(line); const closeIndex = rawLines.findIndex(line => denotesClosingHtml(line, tag)); diff --git a/src/lib/Markdown/InlineSyntax.tsx b/src/lib/Markdown/InlineSyntax.tsx deleted file mode 100644 index 53cdaf0..0000000 --- a/src/lib/Markdown/InlineSyntax.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React from 'react'; -import { Link, makeStyles } from '@material-ui/core'; - -import { lib as emojiLib } from 'emojilib'; -import { InlineParserPropTypes } from './types'; - -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 = { - 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 InlineSyntax: React.FC = ({ line }) => { - const classes = useStyles(); - if (!line) return null; - - const matchConceal = regex.conceal.local.exec(line); - if (matchConceal) { - if (line[0] === '!') return {matchConceal[1]}; - return {matchConceal[1]}; - } - - const matchEmoji = line.match(regex.emoji.local); - if (matchEmoji) { - const emoji = emojiList.find(emoji => emoji.name === matchEmoji[1]); - return {emoji ? emoji.char : line}; - } - - const matchCode = line.match(regex.code.local); - if (matchCode) return {matchCode[1]}; - - const matchBold = line.match(regex.bold.local); - if (matchBold) return {matchBold[1]}; - - const matchItalic = line.match(regex.italic.local); - if (matchItalic) return {matchItalic[1]}; - - const matchStrikeThrough = line.match(regex.strikeThrough.local); - if (matchStrikeThrough) return {matchStrikeThrough[1]}; - - if (line.match(regex.rawLink.global)) return {line}; - - return <>{line}; -} - - -export { splitter }; -export default InlineSyntax; - 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 = { + 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 = ({ span }) => { + const classes = useStyles(); + if (!span) return null; + + const matchConceal = regex.conceal.local.exec(span); + if (matchConceal) { + if (span[0] === '!') return {matchConceal[1]}; + return {matchConceal[1]}; + } + + const matchEmoji = span.match(regex.emoji.local); + if (matchEmoji) { + const emoji = emojiList.find(emoji => emoji.name === matchEmoji[1]); + return {emoji ? emoji.char : span}; + } + + const matchCode = span.match(regex.code.local); + if (matchCode) return {matchCode[1]}; + + const matchBold = span.match(regex.bold.local); + if (matchBold) return {matchBold[1]}; + + const matchItalic = span.match(regex.italic.local); + if (matchItalic) return {matchItalic[1]}; + + const matchStrikeThrough = span.match(regex.strikeThrough.local); + if (matchStrikeThrough) return {matchStrikeThrough[1]}; + + if (span.match(regex.rawLink.global)) return {span}; + + return <>{span}; +} + + +export { splitter }; +export default SyntacticSpan; + diff --git a/src/lib/Markdown/Text.tsx b/src/lib/Markdown/Text.tsx index b989476..e287dee 100644 --- a/src/lib/Markdown/Text.tsx +++ b/src/lib/Markdown/Text.tsx @@ -1,9 +1,12 @@ import React from 'react'; -import { InlineParserPropTypes } from './types'; -import InlineSyntax, { splitter } from './InlineSyntax'; +import SyntacticSpan, { splitter } from './SyntacticSpan'; -const Text: React.FC = ({ line }) => { - return <>{line.split(splitter).map(span => )}; +interface PropTypes { + line: string; +} + +const Text: React.FC = ({ line }) => { + return <>{line.split(splitter).map(span => )}; } export default Text; -- cgit v1.2.3 From 43d65a56bbe91de967fa2ff86b25ec216bfd5ce1 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 22:16:02 +0300 Subject: style: fix linting errors :rotating_light: --- src/lib/Markdown/Content.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/Markdown/Content.tsx b/src/lib/Markdown/Content.tsx index caac91c..aaea100 100644 --- a/src/lib/Markdown/Content.tsx +++ b/src/lib/Markdown/Content.tsx @@ -24,7 +24,7 @@ const denotesClosingHtml= (line: string, tag: string): boolean => { return line.match(regex) !== null; } -const denotesSelfClosingHtml = (line: string): any => { +const denotesSelfClosingHtml = (line: string): string[] | null => { const regex = /(<[^/\s]*[^<]*\/>)/g; return line.match(regex); } @@ -44,14 +44,14 @@ const Content: React.FC = ({ rawLines }) => { const dottedListLines = rawLines.splice(0, closeIndex).slice(0, closeIndex); dottedListLines.unshift(line); buffer =
    {dottedListLines.map(li =>
  • )}
; - } else if (denotesOpenHtml(line)) { - const tag = denotesOpenHtml(line); + } 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 =
; - } else if (denotesSelfClosingHtml(line) !== null) { - const match = denotesSelfClosingHtml(line)[0]; + } else if ((buffer = denotesSelfClosingHtml(line)) !== null) { + const match = buffer[0]; const [before, after] = line.split(match); console.log({ line, match, before, after}); buffer = ( -- cgit v1.2.3 From 181129f0a99350f2731293dc179b37c9f4eb9abd Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 22:27:28 +0300 Subject: fix: remove unused type :wastebasket: --- src/lib/Markdown/types.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib/Markdown/types.ts b/src/lib/Markdown/types.ts index 8fb28ed..0b6f4b6 100644 --- a/src/lib/Markdown/types.ts +++ b/src/lib/Markdown/types.ts @@ -1,7 +1,3 @@ -export interface InlineParserPropTypes { - line: string; -} - export interface ParserPropTypes { rawLines: string[]; } -- cgit v1.2.3 From 5e32be8569d93f56266d3bf707a7b099cb88e524 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 22:37:55 +0300 Subject: docs: tweak text on frontpage --- src/index.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 0128140..a9f919f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -54,17 +54,13 @@ const App: React.FC = () => { `## 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 internet, though this pane is generated from plaintext. :pen:', + '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(, document.getElementById(\'root\'));', '```', - 'Yep! **Or even simpler**:', - '```', - 'ReactDOM.render(, document.getElementById(\'root\'));', - '```', ].join('\n'); return ( -- cgit v1.2.3 From e9c6eb6be709db76002c41e5bf7d13654493d0a8 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 22:47:04 +0300 Subject: build: move emojilib declaration to separate file --- src/lib/Markdown/emojilib.d.ts | 2 ++ src/react-app-env.d.ts | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 src/lib/Markdown/emojilib.d.ts 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/react-app-env.d.ts b/src/react-app-env.d.ts index 46be55a..6431bc5 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -1,2 +1 @@ /// -declare module 'emojilib'; -- cgit v1.2.3