├── .gitignore
├── .prettierrc
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── example
├── .npmignore
├── .prettierrc
├── index.html
├── index.tsx
├── package-lock.json
├── package.json
├── styles
│ └── style.css
├── tsconfig.json
└── utils
│ └── mockData.ts
├── package-lock.json
├── package.json
├── src
├── components
│ ├── Code
│ │ ├── Code.module.css
│ │ ├── Code.tsx
│ │ └── index.ts
│ ├── Delimiter
│ │ ├── Delimiter.module.css
│ │ ├── Delimiter.tsx
│ │ └── index.ts
│ ├── Header
│ │ ├── Header.module.css
│ │ ├── Header.tsx
│ │ └── index.ts
│ ├── Image
│ │ ├── Image.module.css
│ │ ├── Image.tsx
│ │ └── index.ts
│ ├── List
│ │ ├── List.module.css
│ │ ├── List.tsx
│ │ └── index.ts
│ ├── Paragraph
│ │ ├── Paragraph.module.css
│ │ ├── Paragraph.tsx
│ │ └── index.ts
│ ├── Quote
│ │ ├── Quote.module.css
│ │ ├── Quote.tsx
│ │ └── index.ts
│ ├── RawTool
│ │ ├── RawTool.module.css
│ │ ├── RawTool.tsx
│ │ └── index.ts
│ └── Table
│ │ ├── Table.module.css
│ │ ├── Table.tsx
│ │ └── index.ts
├── generalStyles.css
├── index.tsx
├── types
│ └── ParserData.ts
├── typings.d.ts
└── utils
│ ├── componentKeys.ts
│ └── parseText.ts
├── tsconfig.json
├── tsdx.config.js
└── workflows
├── main.yml
└── size.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | .cache
5 | dist
6 | .vscode
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "none",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "printWidth": 80,
6 | "endOfLine": "auto",
7 | "singleQuote": true
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "prettier.semi": false,
3 | "prettier.jsxSingleQuote": false,
4 | "prettier.singleQuote": true
5 | }
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 alkhipce
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @alkhipce/editorjs-react
2 |
3 | [](https://www.npmjs.com/package/@alkhipce/editorjs-react)
4 | [](https://github.com/etozhealkhipce/editorjs-react)
5 | [](https://editorjs-react.vercel.app/)
6 |
7 | ## Demo
8 |
9 | #### https://editorjs-react.vercel.app/
10 |
11 | ## Installation
12 |
13 | ```shell
14 | npm i @alkhipce/editorjs-react
15 | ```
16 |
17 | ## Usage
18 |
19 | ```javascript
20 | // import Parser component or import certain component like this (available components list below)
21 | import { Parser, Table } from '@alkhipce/editorjs-react';
22 |
23 | const App = () => {
24 | return (
25 | <>
26 |
27 |
;
28 | >
29 | );
30 | };
31 | ```
32 |
33 | ## Available components
34 |
35 | # Code
36 |
37 | | Props | Description | Type | Default |
38 | | ----- | ---------------------------------- | ------ | ------- |
39 | | code | display code in preformatted style | string | - |
40 |
41 | # Delimiter
42 |
43 | | Props | Description | Type | Default |
44 | | ----- | ----------- | ---- | ------- |
45 | | - | - | - | - |
46 |
47 | # Header
48 |
49 | | Props | Description | Type | Default |
50 | | ----- | ------------------ | ------------ | ------- |
51 | | level | heading text level | number (1-6) | 1 |
52 | | text | your text | string | - |
53 |
54 | # Image
55 |
56 | | Props | Description | Type | Default |
57 | | -------------- | ----------------------- | ------------------------------ | ------- |
58 | | file | contains image info | object with field url: string; | - |
59 | | caption | image caption | string or undefined | - |
60 | | withBorder | add border around image | boolean or undefined | false |
61 | | stretched | stretch image | boolean or undefined | false |
62 | | withBackground | add image background | boolean or undefined | false |
63 |
64 | # List
65 |
66 | | Props | Description | Type | Default |
67 | | ----- | ----------------------------------- | ------------------------------- | ----------- |
68 | | items | list items | string array | [] |
69 | | style | set ordered or unordered list style | 'ordered' or 'unordered' string | 'unordered' |
70 |
71 | # Paragraph
72 |
73 | | Props | Description | Type | Default |
74 | | ----- | ----------- | ------ | ------- |
75 | | text | your text | string | - |
76 |
77 | # Quote
78 |
79 | | Props | Description | Type | Default |
80 | | --------- | ------------------- | ------------------------- | ------- |
81 | | text | your text | string | - |
82 | | caption | quote caption | string | - |
83 | | alignment | set quote alignment | 'left' or 'center' string | 'left' |
84 |
85 | # RawTool
86 |
87 | | Props | Description | Type | Default |
88 | | ----- | ----------- | ------ | ------- |
89 | | html | your code | string | - |
90 |
91 | # Table
92 |
93 | | Props | Description | Type | Default |
94 | | ------------ | ------------------ | ---------- | ------- |
95 | | content | table data | string[][] | - |
96 | | withHeadings | add table headings | boolean | false |
97 |
--------------------------------------------------------------------------------
/example/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache
3 | dist
--------------------------------------------------------------------------------
/example/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "none",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "printWidth": 80,
6 | "endOfLine": "auto",
7 | "singleQuote": true
8 | }
9 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Playground
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/index.tsx:
--------------------------------------------------------------------------------
1 | import 'react-app-polyfill/ie11';
2 | import * as React from 'react';
3 | import * as ReactDOM from 'react-dom';
4 | <<<<<<< Updated upstream
5 | // import { Parser } from '../.';
6 | import { Parser, Table } from '@alkhipce/editorjs-react';
7 |
8 | =======
9 | <<<<<<< Updated upstream
10 | import { Parser } from '../.';
11 | =======
12 | // import { Parser } from '../.';
13 | import { Parser } from '@alkhipce/editorjs-react';
14 |
15 | >>>>>>> Stashed changes
16 | >>>>>>> Stashed changes
17 | import mockData from './utils/mockData';
18 | import './styles/style.css';
19 |
20 | const App = () => {
21 | return (
22 |
25 | );
26 | };
27 |
28 | ReactDOM.render(, document.getElementById('root'));
29 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "start": "parcel index.html",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {
11 | "@alkhipce/editorjs-react": "^0.1.1",
12 | "react-app-polyfill": "^1.0.0"
13 | },
14 | "alias": {
15 | "react": "../node_modules/react",
16 | "react-dom": "../node_modules/react-dom/profiling",
17 | "scheduler/tracing": "../node_modules/scheduler/tracing-profiling"
18 | },
19 | "devDependencies": {
20 | "@types/react": "^16.9.11",
21 | "@types/react-dom": "^16.8.4",
22 | "parcel-bundler": "^1.12.3",
23 | "typescript": "^3.4.5"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/example/styles/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #d4ecff;
3 | margin: 0;
4 | padding: 70px 60px;
5 | }
6 |
7 | .wrapper {
8 | max-width: 850px;
9 | margin: 0 auto;
10 | padding: 70px 50px;
11 | background-color: #ffffff;
12 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;
13 | border-radius: 8px;
14 | box-shadow: 0 24px 24px -18px rgb(69 104 129 / 33%), 0 9px 45px 0 rgb(114 119 160 / 12%);
15 | }
16 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": false,
4 | "target": "es5",
5 | "module": "commonjs",
6 | "jsx": "react",
7 | "moduleResolution": "node",
8 | "noImplicitAny": false,
9 | "noUnusedLocals": false,
10 | "noUnusedParameters": false,
11 | "removeComments": true,
12 | "strictNullChecks": true,
13 | "preserveConstEnums": true,
14 | "sourceMap": true,
15 | "lib": ["es2015", "es2016", "dom"],
16 | "types": ["node"]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/example/utils/mockData.ts:
--------------------------------------------------------------------------------
1 | const mockData = {
2 | time: 1652097236789,
3 | blocks: [
4 | {
5 | id: "jyLyQSFCwX",
6 | type: "header",
7 | data: {
8 | text: "Editor.js",
9 | level: 1
10 | }
11 | },
12 | {
13 | id: "hvOfuu-vfx",
14 | type: "header",
15 | data: {
16 | text: "Editor.js",
17 | level: 2
18 | }
19 | },
20 | {
21 | id: "VaKCmBAiNm",
22 | type: "header",
23 | data: {
24 | text: "Editor.js",
25 | level: 3
26 | }
27 | },
28 | {
29 | id: "vw-1FAjuOr",
30 | type: "header",
31 | data: {
32 | text: "Editor.js",
33 | level: 4
34 | }
35 | },
36 | {
37 | id: "7ZOouHbH6P",
38 | type: "header",
39 | data: {
40 | text: "Editor.js",
41 | level: 5
42 | }
43 | },
44 | {
45 | id: "y1366YniV2",
46 | type: "header",
47 | data: {
48 | text: "Editor.js",
49 | level: 6
50 | }
51 | },
52 | {
53 | id: "rbh77wBtsc",
54 | type: "paragraph",
55 | data: {
56 | text: "Hey. Meet the new Editor. On this page you can see it in action — try to edit this text."
57 | }
58 | },
59 | {
60 | id: "o9i2zb7yYP",
61 | type: "header",
62 | data: {
63 | text: "Key features",
64 | level: 3
65 | }
66 | },
67 | {
68 | id: "9AixFTtNHi",
69 | type: "list",
70 | data: {
71 | style: "unordered",
72 | items: [
73 | "It is a block-styled editor",
74 | "It returns clean data output in JSON",
75 | "Designed to be extendable and pluggable with a simple API"
76 | ]
77 | }
78 | },
79 | {
80 | id: "dg9iJGpId9",
81 | type: "header",
82 | data: {
83 | text: "What does it mean «block-styled editor»",
84 | level: 3
85 | }
86 | },
87 | {
88 | id: "HTo4Fz6FO1",
89 | type: "paragraph",
90 | data: {
91 | text: 'Workspace in classic editors is made of a single contenteditable element, used to create different HTML markups. Editor.js workspace consists of separate Blocks: paragraphs, headings, images, lists, quotes, etc. Each of them is an independent contenteditable element (or more complex structure) provided by Plugin and united by Editor\'s Core.'
92 | }
93 | },
94 | {
95 | id: "nv9BdzrBxC",
96 | type: "paragraph",
97 | data: {
98 | text: 'There are dozens of ready-to-use Blocks and the simple API for creation any Block you need. For example, you can implement Blocks for Tweets, Instagram posts, surveys and polls, CTA-buttons and even games.'
99 | }
100 | },
101 | {
102 | id: "CuDUO7PUPd",
103 | type: "header",
104 | data: {
105 | text: "What does it mean clean data output",
106 | level: 3
107 | }
108 | },
109 | {
110 | id: "sN-huxAmSl",
111 | type: "paragraph",
112 | data: {
113 | text: "Classic WYSIWYG-editors produce raw HTML-markup with both content data and content appearance. On the contrary, Editor.js outputs JSON object with data of each Block. You can see an example below"
114 | }
115 | },
116 | {
117 | id: "c7FB0SNNa3",
118 | type: "paragraph",
119 | data: {
120 | text: 'Given data can be used as you want: render with HTML for Web clients
, render natively for mobile apps
, create markup for Facebook Instant Articles
or Google AMP
, generate an audio version
and so on.'
121 | }
122 | },
123 | {
124 | id: "Rq7aerGnZ9",
125 | type: "paragraph",
126 | data: {
127 | text: "Clean data is useful to sanitize, validate and process on the backend."
128 | }
129 | },
130 | {
131 | id: "WRKV7zLD3O",
132 | type: "delimiter",
133 | data: {}
134 | },
135 | {
136 | id: "cUryubsjLm",
137 | type: "paragraph",
138 | data: {
139 | text: "We have been working on this project more than three years. Several large media projects help us to test and debug the Editor, to make it's core more stable. At the same time we significantly improved the API. Now, it can be used to create any plugin for any task. Hope you enjoy. 😏"
140 | }
141 | },
142 | {
143 | id: "oJ2T0cwRmJ",
144 | type: "image",
145 | data: {
146 | file: {
147 | url: "https://codex.so/public/app/img/external/codex2x.png"
148 | },
149 | caption: "Test",
150 | withBorder: true,
151 | stretched: false,
152 | withBackground: true
153 | }
154 | },
155 | {
156 | id: "oJ2T0cwRmG",
157 | type: "image",
158 | data: {
159 | file: {
160 | url: "https://codex.so/public/app/img/external/codex2x.png"
161 | },
162 | caption: "Stretched",
163 | withBorder: false,
164 | stretched: true,
165 | withBackground: false
166 | }
167 | },
168 | {
169 | id: "QERE0t_r2c",
170 | type: "code",
171 | data: {
172 | code: 'func main() {\n var name string\n var age int\n fmt.Print("Введите имя: ")\n fmt.Fscan(os.Stdin, &name) \n \n fmt.Print("Введите возраст: ")\n fmt.Fscan(os.Stdin, &age)\n \n fmt.Println(name, age)\n}'
173 | }
174 | },
175 | {
176 | id: "-fM5Vw-Bc5",
177 | type: "rawTool",
178 | data: {
179 | html: "dawdaw"
180 | }
181 | },
182 | {
183 | id: "h9o3IKatXu",
184 | type: "quote",
185 | data: {
186 | text: "dawdaw",
187 | caption: "dawd",
188 | alignment: "left"
189 | }
190 | },
191 | {
192 | id: "h9o3IKatLu",
193 | type: "quote",
194 | data: {
195 | text: "dawdaw",
196 | caption: "dawd",
197 | alignment: "center"
198 | }
199 | },
200 | {
201 | id: "J1V4bu3QIV",
202 | type: "table",
203 | data: {
204 | withHeadings: false,
205 | content: [
206 | ["1", "2", "3"],
207 | ["11", "22", "33"],
208 | ["111", "222", "333"],
209 | ["1111", "", ""]
210 | ]
211 | }
212 | },
213 | {
214 | id: "8K1iH232qHH57",
215 | type: "table",
216 | data: {
217 | withHeadings: true,
218 | content: [
219 | ["dawdaw", 'header1
', "jjjj"],
220 | ["dawd", "awd", ""],
221 | ["awd", "123", ""]
222 | ]
223 | }
224 | }
225 | ],
226 | version: "2.26.0"
227 | };
228 |
229 | export default mockData;
230 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "repository": {
3 | "type": "git",
4 | "url": "https://github.com/etozhealkhipce/editorjs-react"
5 | },
6 | "version": "0.1.4",
7 | "license": "MIT",
8 | "main": "dist/index.js",
9 | "typings": "dist/index.d.ts",
10 | "files": [
11 | "dist",
12 | "src"
13 | ],
14 | "engines": {
15 | "node": ">=10"
16 | },
17 | "scripts": {
18 | "start": "tsdx watch --transpileOnly",
19 | "build": "tsdx build --transpileOnly",
20 | "test": "tsdx test --passWithNoTests",
21 | "lint": "tsdx lint",
22 | "prepare": "tsdx build --transpileOnly",
23 | "size": "size-limit",
24 | "analyze": "size-limit --why"
25 | },
26 | "peerDependencies": {
27 | "react": ">=16"
28 | },
29 | "husky": {
30 | "hooks": {
31 | "pre-commit": "tsdx lint"
32 | }
33 | },
34 | "prettier": {
35 | "printWidth": 80,
36 | "semi": true,
37 | "singleQuote": true,
38 | "trailingComma": "es5"
39 | },
40 | "name": "@alkhipce/editorjs-react",
41 | "author": "alkhipce & YunesB",
42 | "module": "dist/editorjs-react.esm.js",
43 | "size-limit": [
44 | {
45 | "path": "dist/editorjs-react.cjs.production.min.js",
46 | "limit": "10 KB"
47 | },
48 | {
49 | "path": "dist/editorjs-react.esm.js",
50 | "limit": "10 KB"
51 | }
52 | ],
53 | "devDependencies": {
54 | "@babel/core": "^7.17.10",
55 | "@size-limit/preset-small-lib": "^7.0.8",
56 | "@types/react": "^18.0.9",
57 | "@types/react-dom": "^18.0.3",
58 | "autoprefixer": "^10.4.13",
59 | "babel-loader": "^8.2.5",
60 | "husky": "^8.0.0",
61 | "postcss": "^8.1.9",
62 | "postcss-import": "12.0.1",
63 | "react": "^18.1.0",
64 | "react-dom": "^18.1.0",
65 | "react-is": "^18.1.0",
66 | "rollup-plugin-postcss": "^4.0.2",
67 | "size-limit": "^7.0.8",
68 | "tsdx": "^0.14.1",
69 | "tslib": "^2.4.0",
70 | "typescript": "^4.6.4"
71 | },
72 | "dependencies": {
73 | "html-react-parser": "^1.4.12"
74 | },
75 | "keywords": [
76 | "editor js react parser",
77 | "Editor.js",
78 | "editor.js markdown",
79 | "react editor.js render",
80 | "editorjs react",
81 | "editorjs parser",
82 | "wysiwyg editor parser",
83 | "wysiwyg to html",
84 | "wysiwyg to react",
85 | "wysiwyg to tsx",
86 | "wysiwyg to jsx",
87 | "wysiwyg components"
88 | ]
89 | }
90 |
--------------------------------------------------------------------------------
/src/components/Code/Code.module.css:
--------------------------------------------------------------------------------
1 | .code {
2 | display: block;
3 | white-space: pre;
4 | -webkit-overflow-scrolling: touch;
5 | overflow: scroll;
6 | max-width: 100%;
7 | min-width: 100px;
8 | background: #f4f4f4;
9 | border: 1px solid #ddd;
10 | border-left: 4px solid rgb(138, 138, 138);
11 | color: #666;
12 | page-break-inside: avoid;
13 | line-height: 1.6;
14 | padding: 1rem 1.5rem;
15 | resize: vertical;
16 | margin: 1rem 0;
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/Code/Code.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import Styles from './Code.module.css';
3 |
4 | export type TCodeData = {
5 | code: string;
6 | };
7 |
8 | export const Code: FC = ({ code }) => {
9 | return (
10 |
11 | {code}
12 |
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/src/components/Code/index.ts:
--------------------------------------------------------------------------------
1 | export { Code, TCodeData } from './Code';
2 |
--------------------------------------------------------------------------------
/src/components/Delimiter/Delimiter.module.css:
--------------------------------------------------------------------------------
1 | .delimiter {
2 | font-size: 55px;
3 | color: #313649;
4 | letter-spacing: 0.005em;
5 | width: 100%;
6 | text-align: center;
7 | margin: 1rem 0;
8 | }
9 |
10 | .delimiter::before {
11 | display: inline-block;
12 | content: '***';
13 | font-size: 30px;
14 | height: 30px;
15 | letter-spacing: 0.2em;
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/Delimiter/Delimiter.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import Styles from './Delimiter.module.css';
3 |
4 | export type TDelimiterData = {};
5 |
6 | export const Delimiter: FC = () => (
7 |
8 | );
9 |
--------------------------------------------------------------------------------
/src/components/Delimiter/index.ts:
--------------------------------------------------------------------------------
1 | export { Delimiter, TDelimiterData } from './Delimiter';
2 |
--------------------------------------------------------------------------------
/src/components/Header/Header.module.css:
--------------------------------------------------------------------------------
1 | .header {
2 | font-family: Lucida Grande, Lucida Sans Unicode, Lucida Sans;
3 | letter-spacing: -0.03em;
4 | padding: 0.6em 0 3px;
5 | line-height: 1.25em;
6 | outline: none;
7 | margin: 1rem 0;
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/Header/Header.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import Styles from './Header.module.css';
3 |
4 | type TLevel = 1 | 2 | 3 | 4 | 5 | 6;
5 |
6 | export type THeaderData = {
7 | level: TLevel;
8 | text: string;
9 | };
10 |
11 | export const Header: FC = ({ level, text }) => {
12 | return (() => {
13 | switch (level) {
14 | case 1:
15 | return {text}
;
16 | case 2:
17 | return {text}
;
18 | case 3:
19 | return {text}
;
20 | case 4:
21 | return {text}
;
22 | case 5:
23 | return {text}
;
24 | case 6:
25 | return {text}
;
26 | default:
27 | return {text}
;
28 | }
29 | })();
30 | };
31 |
--------------------------------------------------------------------------------
/src/components/Header/index.ts:
--------------------------------------------------------------------------------
1 | export { Header, THeaderData } from './Header';
2 |
--------------------------------------------------------------------------------
/src/components/Image/Image.module.css:
--------------------------------------------------------------------------------
1 | .figure {
2 | width: 100%;
3 | margin: 0;
4 | }
5 |
6 | .stretched {
7 | width: none;
8 | margin: 0;
9 | }
10 |
11 | .background {
12 | padding: 15px 139px;
13 | background: #cdd1e0;
14 | }
15 |
16 | .backgroundBorder {
17 | composes: background;
18 | border: 1px solid #e8e8eb;
19 | }
20 |
21 | .image {
22 | width: 100%;
23 | }
24 |
25 | .imageBorder {
26 | composes: image;
27 | border: 1px solid #e8e8eb;
28 | }
29 |
30 | .caption {
31 | border: 1px solid rgb(230, 230, 230);
32 | padding: 0.5rem 1.5rem;
33 | width: 100%;
34 | display: inline-block;
35 | box-sizing: border-box;
36 | text-align: left;
37 | margin: 1rem 0;
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/Image/Image.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import Styles from './Image.module.css';
3 |
4 | export type TImageData = {
5 | file: {
6 | url: string;
7 | };
8 | caption?: string;
9 | withBorder?: boolean;
10 | stretched?: boolean;
11 | withBackground?: boolean;
12 | };
13 |
14 | export const Image: FC = ({
15 | file,
16 | caption,
17 | withBorder = false,
18 | withBackground = false,
19 | stretched = false,
20 | }) => {
21 | return (
22 |
23 | {withBackground ? (
24 |
27 |

32 |
33 | ) : (
34 |
39 | )}
40 | {caption && {caption}}
41 |
42 | );
43 | };
44 |
--------------------------------------------------------------------------------
/src/components/Image/index.ts:
--------------------------------------------------------------------------------
1 | export { Image, TImageData } from './Image';
2 |
--------------------------------------------------------------------------------
/src/components/List/List.module.css:
--------------------------------------------------------------------------------
1 | .list {
2 | margin: 1rem 0;
3 | margin: 0;
4 | padding-left: 40px;
5 | outline: none;
6 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
7 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
8 | }
9 |
10 | .listItem {
11 | padding: 5.5px 0 5.5px 3px;
12 | line-height: 1.6em;
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/List/List.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useMemo } from 'react';
2 | import Styles from './List.module.css';
3 | import { parseText } from '../../utils/parseText';
4 |
5 | const ORDERED_KEY = 'ordered';
6 | const UNORDERED_KEY = 'unordered';
7 |
8 | export type TListData = {
9 | items: string[];
10 | style?: typeof ORDERED_KEY | typeof UNORDERED_KEY;
11 | };
12 |
13 | export const List: FC = ({ items, style = UNORDERED_KEY }) => {
14 | const orderedList = useMemo(() => style === UNORDERED_KEY, []);
15 |
16 | return orderedList ? (
17 |
18 | {items.map((text, index) => (
19 | -
20 | {parseText(text)}
21 |
22 | ))}
23 |
24 | ) : (
25 |
26 | {items.map((text, index) => (
27 | -
28 | {parseText(text)}
29 |
30 | ))}
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/src/components/List/index.ts:
--------------------------------------------------------------------------------
1 | export { List, TListData } from './List';
2 |
--------------------------------------------------------------------------------
/src/components/Paragraph/Paragraph.module.css:
--------------------------------------------------------------------------------
1 | .paragraph {
2 | line-height: 1.6em;
3 | outline: none;
4 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
5 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
6 | margin: 1rem 0;
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/Paragraph/Paragraph.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import Styles from './Paragraph.module.css';
3 | import { parseText } from '../../utils/parseText';
4 |
5 | export type TParagraphData = {
6 | text: string;
7 | };
8 |
9 | export const Paragraph: FC = ({ text }) => (
10 | {parseText(text)}
11 | );
12 |
--------------------------------------------------------------------------------
/src/components/Paragraph/index.ts:
--------------------------------------------------------------------------------
1 | export { Paragraph, TParagraphData } from './Paragraph';
2 |
--------------------------------------------------------------------------------
/src/components/Quote/Quote.module.css:
--------------------------------------------------------------------------------
1 | .figure {
2 | width: 100%;
3 | margin: 0;
4 | }
5 |
6 | .blockquote {
7 | margin: 0;
8 | }
9 |
10 | .centeredQuote {
11 | composes: blockquote;
12 | text-align: center;
13 | }
14 |
15 | .paragraph {
16 | display: block;
17 | white-space: pre;
18 | max-width: 100%;
19 | background: #eee;
20 | padding: 1rem 1.5rem;
21 | line-height: 1.6;
22 | font-style: oblique;
23 | border-radius: 5px;
24 | margin: 1rem 0;
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/Quote/Quote.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useMemo } from 'react';
2 | import { parseText } from '../../utils/parseText';
3 | import Styles from './Quote.module.css';
4 |
5 | export type TQuoteData = {
6 | text: string;
7 | caption: string;
8 | alignment: 'left' | 'center';
9 | };
10 |
11 | export const Quote: FC = ({ text, caption, alignment }) => {
12 | const isTextCentered = useMemo(() => alignment === 'center', []);
13 |
14 | return (
15 |
16 |
19 | {parseText(text)}
20 |
21 | — {parseText(caption)}
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/components/Quote/index.ts:
--------------------------------------------------------------------------------
1 | export { Quote, TQuoteData } from './Quote';
2 |
--------------------------------------------------------------------------------
/src/components/RawTool/RawTool.module.css:
--------------------------------------------------------------------------------
1 | .rawTool {
2 | display: block;
3 | white-space: pre;
4 | -webkit-overflow-scrolling: touch;
5 | overflow: scroll;
6 | max-width: 100%;
7 | min-width: 100px;
8 | background: #2a2a2a;
9 | border: 1px solid rgb(42, 42, 42);
10 | color: rgb(255, 255, 255);
11 | page-break-inside: avoid;
12 | line-height: 1.6;
13 | padding: 1rem 1.5rem;
14 | resize: vertical;
15 | margin: 1rem 0;
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/RawTool/RawTool.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import Styles from './RawTool.module.css';
3 |
4 | export type TRawToolData = {
5 | html: string;
6 | };
7 |
8 | export const RawTool: FC = ({ html }) => {
9 | return (
10 |
11 | {html}
12 |
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/src/components/RawTool/index.ts:
--------------------------------------------------------------------------------
1 | export { RawTool, TRawToolData } from './RawTool';
2 |
--------------------------------------------------------------------------------
/src/components/Table/Table.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | overflow-x: auto;
3 | }
4 |
5 | .table {
6 | border-collapse: collapse;
7 | width: 100%;
8 | max-width: 100%;
9 | margin: 1rem 0;
10 | overflow: scroll;
11 | }
12 |
13 | .td {
14 | border: 1px solid #e8e8eb;
15 | padding: 0.5rem;
16 | }
17 |
18 | .tdBorderless {
19 | border: 1px solid #e8e8eb;
20 | padding: 0.5rem;
21 | border-left: none;
22 | border-right: none;
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/Table/Table.tsx:
--------------------------------------------------------------------------------
1 | import parse from 'html-react-parser';
2 | import React, { FC } from 'react';
3 | import Styles from './Table.module.css';
4 |
5 | export type TTableData = {
6 | content: string[][];
7 | withHeadings: boolean;
8 | };
9 |
10 | export const Table: FC = ({ withHeadings, content }) => {
11 | const _content = content.slice();
12 | const heading = withHeadings ? _content.splice(0, 1) : [];
13 |
14 | return (
15 |
16 |
17 | {withHeadings && (
18 |
19 |
20 | {heading[0]?.map((text, index) => (
21 |
29 | {parse(text)}
30 | |
31 | ))}
32 |
33 |
34 | )}
35 |
36 | {_content.map((row, index) => (
37 |
38 | {row.map((text, index) => (
39 |
47 | {parse(text)}
48 | |
49 | ))}
50 |
51 | ))}
52 |
53 |
54 |
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/src/components/Table/index.ts:
--------------------------------------------------------------------------------
1 | export { Table, TTableData } from './Table';
2 |
--------------------------------------------------------------------------------
/src/generalStyles.css:
--------------------------------------------------------------------------------
1 | .inline-code {
2 | background: rgba(250, 239, 240, 0.78);
3 | color: #b44437;
4 | padding: 3px 4px;
5 | border-radius: 5px;
6 | margin: 0 1px;
7 | font-family: inherit;
8 | font-size: 0.86em;
9 | font-weight: 500;
10 | letter-spacing: 0.3px;
11 | }
12 |
13 | .cdx-marker {
14 | background: rgba(245, 235, 111, 0.29);
15 | }
16 |
17 | a {
18 | color: #313649;
19 | }
20 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { Header } from './components/Header';
3 | import { List } from './components/List';
4 | import { Paragraph } from './components/Paragraph';
5 | import { Image } from './components/Image';
6 | import { Delimiter } from './components/Delimiter';
7 | import { Code } from './components/Code';
8 | import { RawTool } from './components/RawTool';
9 | import { Quote } from './components/Quote';
10 | import { Table } from './components/Table';
11 |
12 | import './generalStyles.css';
13 |
14 | import { IParser } from './types/ParserData';
15 |
16 | import {
17 | CODE_KEY,
18 | DATA_KEY,
19 | DELIMITER_KEY,
20 | ERROR_KEY,
21 | HEADER_KEY,
22 | IMAGE_KEY,
23 | LIST_KEY,
24 | PARAGRAPH_KEY,
25 | QUOTE_KEY,
26 | RAW_TOOL_KEY,
27 | TABLE_KEY,
28 | } from './utils/componentKeys';
29 |
30 | const Parser: FC> = ({ data }) => {
31 | return (
32 | <>
33 | {data?.blocks?.map(item => {
34 | const { type, data, id } = item;
35 |
36 | switch (type) {
37 | case HEADER_KEY:
38 | return ;
39 | case PARAGRAPH_KEY:
40 | return ;
41 | case LIST_KEY:
42 | return
;
43 | case DELIMITER_KEY:
44 | return ;
45 | case IMAGE_KEY:
46 | return (
47 |
55 | );
56 | case CODE_KEY:
57 | return
;
58 | case RAW_TOOL_KEY:
59 | return ;
60 | case QUOTE_KEY:
61 | return (
62 |
68 | );
69 | case TABLE_KEY:
70 | return (
71 |
76 | );
77 | default:
78 | return Error!
;
79 | }
80 | })}
81 | >
82 | );
83 | };
84 |
85 | export {
86 | Parser,
87 | Header,
88 | List,
89 | Paragraph,
90 | Image,
91 | Delimiter,
92 | Code,
93 | RawTool,
94 | Quote,
95 | Table,
96 | };
97 |
--------------------------------------------------------------------------------
/src/types/ParserData.ts:
--------------------------------------------------------------------------------
1 | import { TCodeData } from 'components/Code';
2 | import { TDelimiterData } from 'components/Delimiter';
3 | import { THeaderData } from 'components/Header';
4 | import { TImageData } from 'components/Image';
5 | import { TListData } from 'components/List';
6 | import { TParagraphData } from 'components/Paragraph';
7 | import { TQuoteData } from 'components/Quote';
8 | import { TRawToolData } from 'components/RawTool';
9 | import { TTableData } from 'components/Table';
10 |
11 | import {
12 | CODE_KEY,
13 | DELIMITER_KEY,
14 | HEADER_KEY,
15 | IMAGE_KEY,
16 | LIST_KEY,
17 | PARAGRAPH_KEY,
18 | QUOTE_KEY,
19 | RAW_TOOL_KEY,
20 | TABLE_KEY,
21 | } from '../utils/componentKeys';
22 |
23 | type TCommonType = {
24 | id: string;
25 | type: T;
26 | data: K;
27 | };
28 |
29 | type TParagraph = TCommonType;
30 | type THeader = TCommonType;
31 | type TList = TCommonType;
32 | type TDelimeter = TCommonType;
33 | type TImage = TCommonType;
34 | type TCode = TCommonType;
35 | type TRawData = TCommonType;
36 | type TQuote = TCommonType;
37 | type TTable = TCommonType;
38 |
39 | type TBlock =
40 | | TParagraph
41 | | THeader
42 | | TList
43 | | TDelimeter
44 | | TImage
45 | | TCode
46 | | TRawData
47 | | TQuote
48 | | TTable;
49 |
50 | export type IParser = {
51 | time: number;
52 | version: string;
53 | blocks?: TBlock[];
54 | };
55 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.module.css';
2 |
--------------------------------------------------------------------------------
/src/utils/componentKeys.ts:
--------------------------------------------------------------------------------
1 | export const HEADER_KEY = 'header';
2 | export const PARAGRAPH_KEY = 'paragraph';
3 | export const LIST_KEY = 'list';
4 | export const DELIMITER_KEY = 'delimiter';
5 | export const IMAGE_KEY = 'image';
6 | export const CODE_KEY = 'code';
7 | export const RAW_TOOL_KEY = 'rawTool';
8 | export const QUOTE_KEY = 'quote';
9 | export const TABLE_KEY = 'table';
10 |
11 | export const DATA_KEY = 'data';
12 | export const ERROR_KEY = 'error';
13 |
--------------------------------------------------------------------------------
/src/utils/parseText.ts:
--------------------------------------------------------------------------------
1 | import parse from 'html-react-parser';
2 |
3 | export const parseText = (text: string) => parse(text);
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs
3 | "include": ["src", "types"],
4 | "compilerOptions": {
5 | "baseUrl": "src",
6 | "module": "esnext",
7 | "lib": ["dom", "esnext"],
8 | "importHelpers": true,
9 | // output .d.ts declaration files for consumers
10 | "declaration": true,
11 | // output .js.map sourcemap files for consumers
12 | "sourceMap": true,
13 | // match output dir to input dir. e.g. dist/index instead of dist/src/index
14 | "rootDir": "./src",
15 | // stricter type-checking for stronger correctness. Recommended by TS
16 | "strict": true,
17 | // linter checks for common issues
18 | "noImplicitReturns": true,
19 | "noFallthroughCasesInSwitch": true,
20 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative
21 | "noUnusedLocals": true,
22 | "noUnusedParameters": true,
23 | // use Node's module resolution algorithm, instead of the legacy TS one
24 | "moduleResolution": "node",
25 | // transpile JSX to React.createElement
26 | "jsx": "react",
27 | // interop between ESM and CJS modules. Recommended by TS
28 | "esModuleInterop": true,
29 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS
30 | "skipLibCheck": true,
31 | // error out if import and file system have a casing mismatch. Recommended by TS
32 | "forceConsistentCasingInFileNames": true,
33 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc`
34 | "noEmit": true,
35 | "paths": {
36 | "@components/*": ["components/*"],
37 | "@utils/*": ["utils/*"]
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tsdx.config.js:
--------------------------------------------------------------------------------
1 | const postcss = require('rollup-plugin-postcss');
2 | const autoprefixer = require('autoprefixer');
3 | const cssnano = require('cssnano');
4 |
5 | module.exports = {
6 | rollup(config, options) {
7 | config.plugins.push(
8 | postcss({
9 | plugins: [
10 | autoprefixer(),
11 | cssnano({
12 | preset: 'default',
13 | }),
14 | ],
15 | inject: true,
16 | extract: !!options.writeMeta,
17 | })
18 | );
19 | return config;
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [push]
3 | jobs:
4 | build:
5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }}
6 |
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | node: ['10.x', '12.x', '14.x']
11 | os: [ubuntu-latest, windows-latest, macOS-latest]
12 |
13 | steps:
14 | - name: Checkout repo
15 | uses: actions/checkout@v2
16 |
17 | - name: Use Node ${{ matrix.node }}
18 | uses: actions/setup-node@v1
19 | with:
20 | node-version: ${{ matrix.node }}
21 |
22 | - name: Install deps and build (with cache)
23 | uses: bahmutov/npm-install@v1
24 |
25 | - name: Lint
26 | run: yarn lint
27 |
28 | - name: Test
29 | run: yarn test --ci --coverage --maxWorkers=2
30 |
31 | - name: Build
32 | run: yarn build
33 |
--------------------------------------------------------------------------------
/workflows/size.yml:
--------------------------------------------------------------------------------
1 | name: size
2 | on: [pull_request]
3 | jobs:
4 | size:
5 | runs-on: ubuntu-latest
6 | env:
7 | CI_JOB_NUMBER: 1
8 | steps:
9 | - uses: actions/checkout@v1
10 | - uses: andresz1/size-limit-action@v1
11 | with:
12 | github_token: ${{ secrets.GITHUB_TOKEN }}
13 |
--------------------------------------------------------------------------------