diff options
author | Eugene Sokolov <eug-vs@keemail.me> | 2020-04-18 21:31:11 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-18 21:31:11 +0300 |
commit | dee39c8ff8e9a417ba2b338beb942f9ad443adf3 (patch) | |
tree | a6b1bfa00f5ad925d57d3ded655a8103b911fad4 | |
parent | 62df0ff96fc9ab832212d223150862c7667d9ffc (diff) | |
parent | 0c061c74bfd473be17be755cd19c8952dc115a91 (diff) | |
download | react-benzin-dee39c8ff8e9a417ba2b338beb942f9ad443adf3.tar.gz |
Merge pull request #13 from eug-vs/develop
Patch 3.1.1
-rw-r--r-- | .circleci/config.yml | 59 | ||||
-rw-r--r-- | .eslintrc.json | 15 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | package-lock.json | 422 | ||||
-rw-r--r-- | package.json | 16 | ||||
-rw-r--r-- | src/index.tsx | 137 | ||||
-rw-r--r-- | src/lib/Benzin/Benzin.tsx | 8 | ||||
-rw-r--r-- | src/lib/ContentSection/ContentSection.tsx | 11 | ||||
-rw-r--r-- | src/lib/Header/Header.tsx | 48 | ||||
-rw-r--r-- | src/lib/Markdown/CodeBlock.tsx | 6 | ||||
-rw-r--r-- | src/lib/Markdown/Content.tsx | 51 | ||||
-rw-r--r-- | src/lib/Markdown/Markdown.tsx | 15 | ||||
-rw-r--r-- | src/lib/Markdown/Section.tsx | 22 | ||||
-rw-r--r-- | src/lib/Markdown/SyntacticSpan.tsx | 26 | ||||
-rw-r--r-- | src/lib/Markdown/Text.tsx | 2 | ||||
-rw-r--r-- | src/lib/SmartList/SmartList.tsx | 3 | ||||
-rw-r--r-- | src/lib/Window/Window.tsx | 5 | ||||
-rw-r--r-- | src/lib/Window/WindowSurface.tsx | 6 | ||||
-rw-r--r-- | src/react-app-env.d.ts | 1 |
19 files changed, 685 insertions, 172 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml index 84c6a35..85fd519 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -29,7 +29,31 @@ jobs: name: Test syntax and perform type checking command: npm test - deploy: + deploy_pages: + <<: *defaults + steps: + - checkout + - restore_cache: + keys: + - v1-dependencies-{{ checksum "package.json" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + + - add_ssh_keys: + fingerprints: + - "02:a9:ad:b9:38:7c:39:70:20:ee:92:4c:86:27:43:9d" + + - run: + name: Configure github user + command: | + git config user.email "eug-vs@keemail.me" + git config user.name "eug-vs" + + - run: + name: Deploy to gh-pages + command: npm run deploy-pages + + publish_package: <<: *defaults steps: - checkout @@ -42,9 +66,10 @@ jobs: - run: name: Authenticate with registry command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc + - run: - name: Deploy package - command: npm run deploy + name: Publish package to NPM + command: npm run publish-package workflows: @@ -52,22 +77,34 @@ workflows: test: jobs: - - checkout_and_test + - checkout_and_test: + filters: + branches: + ignore: /^(master|develop)$/ deploy: jobs: - checkout_and_test: filters: branches: - ignore: /.*/ - tags: - only: /^v.*/ - - deploy: + only: develop + - deploy_pages: + filters: + branches: + only: develop + requires: + - checkout_and_test + + publish: + jobs: + - checkout_and_test: + filters: + branches: + only: master + - publish_package: filters: branches: - ignore: /.*/ - tags: - only: /^v.*/ + only: master requires: - checkout_and_test diff --git a/.eslintrc.json b/.eslintrc.json index ac05a7f..c86a906 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,12 +1,23 @@ { "parser": "@typescript-eslint/parser", "extends": [ - "react-app", + "airbnb-typescript", "plugin:@typescript-eslint/recommended" ], + "parserOptions": { + "project": "./tsconfig.json" + }, "ignorePatterns": "dist/", "rules": { "jsx-quotes": ["error", "prefer-double"], - "quotes": ["error", "single"] + "quotes": ["error", "single"], + "no-multiple-empty-lines": [2, { "max": 2, "maxEOF": 1 } ], + "max-len": ["error", { "code": 120 }], + "arrow-parens": [2, "as-needed"], + "arrow-body-style": 0, + "no-cond-assign": 0, + "react/prop-types": 0, + "react/no-children-prop": 0, + "react/no-danger": 0 } } @@ -59,4 +59,6 @@ $ 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. +Publishing to `npm` is fully automated through **CircleCI** - package is deployed on every push into `master`. Therefore only release *PR*'s should be merged into `master` branch. + +Deploying to `gh-pages` is automatically performed on every commit into `develop` branch. diff --git a/package-lock.json b/package-lock.json index c7282df..428aef5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-benzin", - "version": "3.1.0", + "version": "3.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1082,13 +1082,21 @@ } }, "@babel/runtime-corejs3": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.8.4.tgz", - "integrity": "sha512-+wpLqy5+fbQhvbllvlJEVRIpYj+COUWnnsm+I4jZlA8Lo7/MJmBhGTCHyk1/RWfOqBRJ2MbadddG6QltTKTlrg==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.9.2.tgz", + "integrity": "sha512-HHxmgxbIzOfFlZ+tdeRKtaxWOMUoCG5Mu3wKeUmOxjYrwb3AAHgnmtCUbPPK11/raIWLIBK250t8E2BPO0p7jA==", "dev": true, "requires": { "core-js-pure": "^3.0.0", - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "dev": true + } } }, "@babel/template": { @@ -1806,33 +1814,33 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.19.0.tgz", - "integrity": "sha512-u7IcQ9qwsB6U806LupZmINRnQjC+RJyv36sV/ugaFWMHTbFm/hlLTRx3gGYJgHisxcGSTnf+I/fPDieRMhPSQQ==", + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.28.0.tgz", + "integrity": "sha512-w0Ugcq2iatloEabQP56BRWJowliXUP5Wv6f9fKzjJmDW81hOTBxRoJ4LoEOxRpz9gcY51Libytd2ba3yLmSOfg==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "2.19.0", - "eslint-utils": "^1.4.3", + "@typescript-eslint/experimental-utils": "2.28.0", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.0.0", "tsutils": "^3.17.1" }, "dependencies": { "@typescript-eslint/experimental-utils": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.19.0.tgz", - "integrity": "sha512-zwpg6zEOPbhB3+GaQfufzlMUOO6GXCNZq6skk+b2ZkZAIoBhVoanWK255BS1g5x9bMwHpLhX0Rpn5Fc3NdCZdg==", + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.28.0.tgz", + "integrity": "sha512-4SL9OWjvFbHumM/Zh/ZeEjUFxrYKtdCi7At4GyKTbQlrj1HcphIDXlje4Uu4cY+qzszR5NdVin4CCm6AXCjd6w==", "dev": true, "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "2.19.0", - "eslint-scope": "^5.0.0" + "@typescript-eslint/typescript-estree": "2.28.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" } }, "@typescript-eslint/typescript-estree": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.19.0.tgz", - "integrity": "sha512-n6/Xa37k0jQdwpUszffi19AlNbVCR0sdvCs3DmSKMD7wBttKY31lhD2fug5kMD91B2qW4mQldaTEc1PEzvGu8w==", + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.28.0.tgz", + "integrity": "sha512-HDr8MP9wfwkiuqzRVkuM3BeDrOC4cKbO5a6BymZBHUt5y/2pL0BXD6I/C/ceq2IZoHWhcASk+5/zo+dwgu9V8Q==", "dev": true, "requires": { "debug": "^4.1.1", @@ -1843,6 +1851,15 @@ "semver": "^6.3.0", "tsutils": "^3.17.1" } + }, + "eslint-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", + "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } } } }, @@ -2497,14 +2514,10 @@ } }, "axobject-query": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.1.tgz", - "integrity": "sha512-lF98xa/yvy6j3fBHAgQXIYl+J4eZadOSqsPojemUqClzNbBV38wWGpUbQbVEyf4eUF5yF7eHmGgGA2JiHyjeqw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.4", - "@babel/runtime-corejs3": "^7.7.4" - } + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.2.tgz", + "integrity": "sha512-ICt34ZmrVt8UQnvPl6TVyDTkmhXmAyAT4Jh5ugfGUX4MOrZ+U/ZY6/sdylRw3qGNr9Ub5AJsaHeDMzNLehRdOQ==", + "dev": true }, "babel-code-frame": { "version": "6.26.0", @@ -3882,9 +3895,9 @@ } }, "core-js-pure": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz", - "integrity": "sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw==", + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", + "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==", "dev": true }, "core-util-is": { @@ -5069,13 +5082,87 @@ } } }, - "eslint-config-react-app": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-5.1.0.tgz", - "integrity": "sha512-hBaxisHC6HXRVvxX+/t1n8mOdmCVIKgkXsf2WoUkJi7upHJTwYTsdCmx01QPOjKNT34QMQQ9sL0tVBlbiMFjxA==", + "eslint-config-airbnb": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.1.0.tgz", + "integrity": "sha512-kZFuQC/MPnH7KJp6v95xsLBf63G/w7YqdPfQ0MUanxQ7zcKUNG8j+sSY860g3NwCBOa62apw16J6pRN+AOgXzw==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^14.1.0", + "object.assign": "^4.1.0", + "object.entries": "^1.1.1" + } + }, + "eslint-config-airbnb-base": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.1.0.tgz", + "integrity": "sha512-+XCcfGyCnbzOnktDVhwsCAx+9DmrzEmuwxyHUJpw+kqBVT744OUBrB09khgFKlK1lshVww6qXGsYPZpavoNjJw==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.9", + "object.assign": "^4.1.0", + "object.entries": "^1.1.1" + } + }, + "eslint-config-airbnb-typescript": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-7.2.1.tgz", + "integrity": "sha512-D3elVKUbdsCfkOVstSyWuiu+KGCVTrYxJPoenPIqZtL6Li/R4xBeVTXjZIui8B8D17bDN3Pz5dSr7jRLY5HqIg==", "dev": true, "requires": { - "confusing-browser-globals": "^1.0.9" + "@typescript-eslint/parser": "^2.24.0", + "eslint-config-airbnb": "^18.1.0", + "eslint-config-airbnb-base": "^14.1.0" + }, + "dependencies": { + "@typescript-eslint/experimental-utils": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.28.0.tgz", + "integrity": "sha512-4SL9OWjvFbHumM/Zh/ZeEjUFxrYKtdCi7At4GyKTbQlrj1HcphIDXlje4Uu4cY+qzszR5NdVin4CCm6AXCjd6w==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "2.28.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.28.0.tgz", + "integrity": "sha512-RqPybRDquui9d+K86lL7iPqH6Dfp9461oyqvlXMNtap+PyqYbkY5dB7LawQjDzot99fqzvS0ZLZdfe+1Bt3Jgw==", + "dev": true, + "requires": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "2.28.0", + "@typescript-eslint/typescript-estree": "2.28.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.28.0.tgz", + "integrity": "sha512-HDr8MP9wfwkiuqzRVkuM3BeDrOC4cKbO5a6BymZBHUt5y/2pL0BXD6I/C/ceq2IZoHWhcASk+5/zo+dwgu9V8Q==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "eslint-visitor-keys": "^1.1.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^6.3.0", + "tsutils": "^3.17.1" + } + }, + "eslint-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", + "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + } } }, "eslint-import-resolver-node": { @@ -5119,9 +5206,9 @@ } }, "eslint-module-utils": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz", - "integrity": "sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", + "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", "dev": true, "requires": { "debug": "^2.6.9", @@ -5207,9 +5294,9 @@ } }, "eslint-plugin-import": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.0.tgz", - "integrity": "sha512-NK42oA0mUc8Ngn4kONOPsPB1XhbUvNHqF+g307dPV28aknPoiNnKLFd9em4nkswwepdF5ouieqv5Th/63U7YJQ==", + "version": "2.20.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz", + "integrity": "sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg==", "dev": true, "requires": { "array-includes": "^3.0.3", @@ -5379,9 +5466,9 @@ } }, "eslint-plugin-react": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.18.0.tgz", - "integrity": "sha512-p+PGoGeV4SaZRDsXqdj9OWcOrOpZn8gXoGPcIQTzo2IDMbAKhNDnME9myZWqO3Ic4R3YmwAZ1lDjWl2R2hMUVQ==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz", + "integrity": "sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ==", "dev": true, "requires": { "array-includes": "^3.1.1", @@ -5392,7 +5479,10 @@ "object.fromentries": "^2.0.2", "object.values": "^1.1.1", "prop-types": "^15.7.2", - "resolve": "^1.14.2" + "resolve": "^1.15.1", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.2", + "xregexp": "^4.3.0" }, "dependencies": { "doctrine": { @@ -5403,13 +5493,22 @@ "requires": { "esutils": "^2.0.2" } + }, + "resolve": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.16.1.tgz", + "integrity": "sha512-rmAglCSqWWMrrBv/XM6sW0NuRFiKViw/W4d9EbC4pt+49H8JwHy+mcGmALTEg504AUDcLTvb1T2q3E9AnmY+ig==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } } } }, "eslint-plugin-react-hooks": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz", - "integrity": "sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.5.1.tgz", + "integrity": "sha512-Y2c4b55R+6ZzwtTppKwSmK/Kar8AdLiC2f9NADCuxbcTgPPg41Gyqa6b9GppgXSvCtkRw43ZE86CT5sejKC6/g==", "dev": true }, "eslint-scope": { @@ -7064,6 +7163,17 @@ "ipaddr.js": "^1.9.0" } }, + "internal-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", + "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "has": "^1.0.3", + "side-channel": "^1.0.2" + } + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -12064,6 +12174,25 @@ "workbox-webpack-plugin": "4.3.1" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, "eslint-config-react-app": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-5.2.0.tgz", @@ -12072,6 +12201,166 @@ "requires": { "confusing-browser-globals": "^1.0.9" } + }, + "eslint-plugin-import": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.0.tgz", + "integrity": "sha512-NK42oA0mUc8Ngn4kONOPsPB1XhbUvNHqF+g307dPV28aknPoiNnKLFd9em4nkswwepdF5ouieqv5Th/63U7YJQ==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "array.prototype.flat": "^1.2.1", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.1", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.0", + "read-pkg-up": "^2.0.0", + "resolve": "^1.12.0" + } + }, + "eslint-plugin-react": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.18.0.tgz", + "integrity": "sha512-p+PGoGeV4SaZRDsXqdj9OWcOrOpZn8gXoGPcIQTzo2IDMbAKhNDnME9myZWqO3Ic4R3YmwAZ1lDjWl2R2hMUVQ==", + "dev": true, + "requires": { + "array-includes": "^3.1.1", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.2.3", + "object.entries": "^1.1.1", + "object.fromentries": "^2.0.2", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "resolve": "^1.14.2" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + } + } + }, + "eslint-plugin-react-hooks": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz", + "integrity": "sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA==", + "dev": true + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } } } }, @@ -12215,9 +12504,9 @@ } }, "regexpp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz", - "integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", "dev": true }, "regexpu-core": { @@ -12982,6 +13271,16 @@ "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", "dev": true }, + "side-channel": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", + "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "object-inspect": "^1.7.0" + } + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -13547,6 +13846,20 @@ } } }, + "string.prototype.matchall": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz", + "integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "has-symbols": "^1.0.1", + "internal-slot": "^1.0.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.2" + } + }, "string.prototype.trimleft": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", @@ -16603,6 +16916,15 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "xregexp": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz", + "integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==", + "dev": true, + "requires": { + "@babel/runtime-corejs3": "^7.8.3" + } + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index a915372..8493d86 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-benzin", - "version": "3.1.0", + "version": "3.1.1", "description": "A powerful React Material components library.", "homepage": "https://eug-vs.github.io/react-benzin", "main": "dist/index.js", @@ -12,8 +12,10 @@ "start": "react-scripts start", "lint": "eslint . --ext ts,tsx --max-warnings 0", "test": "npm run lint && tsc", - "build": "rm -rf dist && tsc --project tsconfig.release.json", - "deploy": "npm run lint && npm run build && npm publish --public" + "build-pages": "react-scripts build", + "deploy-pages": "npm run build-pages && gh-pages -d build", + "compile-dist": "rm -rf dist && tsc --project tsconfig.release.json", + "publish-package": "npm run lint && npm run build && npm publish --public" }, "license": "MIT", "dependencies": { @@ -32,10 +34,14 @@ "@types/react-dom": "^16.9.5", "@types/react-virtualized-auto-sizer": "^1.0.0", "@types/react-window": "^1.8.1", - "@typescript-eslint/eslint-plugin": "^2.19.0", + "@typescript-eslint/eslint-plugin": "^2.28.0", "@typescript-eslint/parser": "^2.19.0", "eslint": "^6.8.0", - "eslint-config-react-app": "^5.1.0", + "eslint-config-airbnb-typescript": "^7.2.1", + "eslint-plugin-import": "^2.20.2", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-react": "^7.19.0", + "eslint-plugin-react-hooks": "^2.5.1", "gh-pages": "^2.2.0", "react-scripts": "^3.3.1", "typescript": "^3.7.5" diff --git a/src/index.tsx b/src/index.tsx index b64b207..c5e1989 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,13 +1,14 @@ -import React, { useState } from 'react'; +import React, { useState, useRef } from 'react'; import ReactDOM from 'react-dom'; -import { makeStyles } from '@material-ui/core'; +import { makeStyles, TextField, Button } from '@material-ui/core'; import { Benzin, Header, Window, Markdown, + ContentSection, } from './lib'; import icon from './assets/icon.svg'; @@ -21,37 +22,114 @@ interface RenderPropTypes { const useStyles = makeStyles(theme => ({ window: { padding: theme.spacing(4), - } + }, + promoButton: { + display: 'flex', + justifyContent: 'center', + marginTop: theme.spacing(4), + }, })); -const Icon = <img src={icon} width="32px" height="37px" alt="logo"/> +const Icon = <img src={icon} width="32px" height="37px" alt="logo" />; const headerContents = { home: null, - space: null, - 'spacevim': null, - 'emoji': null, + spacevim: null, 'material-ui': null, + custom: null, + 'live preview': null, }; const pageMap: Record<string, string> = { home: 'https://raw.githubusercontent.com/eug-vs/react-benzin/develop/README.md', - space: 'https://raw.githubusercontent.com/eug-vs/space/master/docs/environment.md', - 'spacevim': 'https://raw.githubusercontent.com/spacevim/spacevim/master/README.md', - emoji: 'https://raw.githubusercontent.com/muan/emoji/gh-pages/README.md', + spacevim: 'https://raw.githubusercontent.com/spacevim/spacevim/master/README.md', 'material-ui': 'https://raw.githubusercontent.com/mui-org/material-ui/master/README.md', }; +const CustomPage: React.FC = () => { + const [url, setUrl] = useState<string>(''); + const inputEl = useRef<HTMLInputElement>(null); + + const handleParseUrl = (): void => { + setUrl(inputEl.current?.value || ''); + }; + + return ( + <> + <ContentSection sectionName="Render custom markdown document" level={2}> + <p> + This should be a link to a valid markdown file. Response should give the file contents. + If you copy README file from GitHub, make sure you provide link to raw view. + </p> + <p> + <TextField + fullWidth + inputRef={inputEl} + variant="outlined" + color="secondary" + label="Markdown url" + /> + </p> + <Button variant="contained" color="secondary" onClick={handleParseUrl}> + Render! + </Button> + </ContentSection> + <Markdown url={url} /> + </> + ); +}; + +interface LivePropTypes { + setLivePreviewData: (livePreviewData: string) => void; +} + +const LivePreviewPage: React.FC<LivePropTypes> = ({ setLivePreviewData }) => { + const inputEl = useRef<HTMLInputElement>(null); + + const handleRender = (): void => { + setLivePreviewData(inputEl.current?.value || ''); + }; + + return ( + <> + <ContentSection sectionName="Markdown live preview" level={2}> + <p> + Start typing and see your text rendered on the left window! + We recommend starting with # Header. + </p> + <p> + <TextField + fullWidth + multiline + inputRef={inputEl} + variant="outlined" + color="primary" + label="Markdown" + onChange={handleRender} + /> + </p> + </ContentSection> + </> + ); +}; + + const App: React.FC = () => { const classes = useStyles(); - const [page, setPage] = useState('home'); + const [page, setPage] = useState<string>('home'); + const [livePreviewData, setLivePreviewData] = useState<string>(''); + + const handleGoLivePreview = (): void => { + setPage('live preview'); + }; const url = pageMap[page]; - const fileName = url.slice(url.lastIndexOf('/') + 1); - const metadata = [ - `## Markdown\n [Markdown file](${url}) *(...${fileName})* that you can see on the left was parsed and processed by **BENZIN**! :rocket:`, + const fileName = url?.slice(url.lastIndexOf('/') + 1); + const info = [ + /* eslint-disable max-len */ + `## Markdown\n [Markdown file](${url}) *(...${fileName})* that you can see on the left was parsed and rendered by **BENZIN**! :rocket:`, 'Switch between tabs on the header to explore other markdown templates. :recycle: ', 'Currently **only core features** of markdown function.', 'Templates on the left are being loaded from the [GitHub](https://github.com), though this pane is generated from plaintext. :pen:', @@ -61,8 +139,15 @@ const App: React.FC = () => { 'const data = \'# Header\\nHello, *world!*\';', 'ReactDOM.render(<Markdown data={data}/>, document.getElementById(\'root\'));', '```', + /* eslint-enable max-len */ ].join('\n'); + let primaryWindowContent = <Markdown url={url} />; + if (page === 'custom') primaryWindowContent = <CustomPage />; + else if (page === 'live preview') { + primaryWindowContent = <Markdown data={livePreviewData || '# Start typing in the right window!'} />; + } + return ( <Benzin> <Header @@ -75,13 +160,29 @@ const App: React.FC = () => { setPage={setPage} /> <Window type="primary"> - <div className={classes.window}> - <Markdown url={url} /> - </div> + <div className={classes.window}>{primaryWindowContent}</div> </Window> <Window type="secondary" name="Feature preview"> <div className={classes.window}> - <Markdown data={metadata} /> + { + (page === 'live preview') + ? <LivePreviewPage setLivePreviewData={setLivePreviewData} /> + : ( + <> + <Markdown data={info} /> + <p className={classes.promoButton}> + <Button + variant="contained" + color="primary" + size="large" + onClick={handleGoLivePreview} + > + Try it yourself! + </Button> + </p> + </> + ) + } </div> </Window> </Benzin> diff --git a/src/lib/Benzin/Benzin.tsx b/src/lib/Benzin/Benzin.tsx index 83ed0b0..bc436f7 100644 --- a/src/lib/Benzin/Benzin.tsx +++ b/src/lib/Benzin/Benzin.tsx @@ -8,9 +8,9 @@ import 'typeface-roboto'; declare module '@material-ui/core/styles/createPalette' { interface TypeBackground { - elevation1: string; - elevation2: string; - elevation3: string; + elevation1: string; + elevation2: string; + elevation3: string; } } @@ -34,7 +34,7 @@ const benzinTheme = createMuiTheme({ text: { primary: '#f4f4f4', secondary: 'rgba(255, 255, 255, 0.6)', - } + }, }, }); diff --git a/src/lib/ContentSection/ContentSection.tsx b/src/lib/ContentSection/ContentSection.tsx index ba8b882..28b1ad5 100644 --- a/src/lib/ContentSection/ContentSection.tsx +++ b/src/lib/ContentSection/ContentSection.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { Typography, Divider, - makeStyles + makeStyles, } from '@material-ui/core'; @@ -26,22 +26,21 @@ const useStyles = makeStyles(theme => ({ const ContentSection: React.FC<PropTypes> = ({ sectionName, children, level = 0 }) => { const classes = useStyles(); - level += 2; // Make everything smaller - if (level > 6) level = 6; + let adjustedLevel = level + 2; // Make everything smaller + if (adjustedLevel > 6) adjustedLevel = 6; type Variant = 'h3' | 'h4' | 'h5' | 'h6'; - const variant: Variant = 'h' + level as Variant; + const variant: Variant = `h${adjustedLevel}` as Variant; return ( <> <Typography variant={variant}>{sectionName}</Typography> - <Divider variant="middle"/> + <Divider variant="middle" /> <Typography component="div" className={classes.content}> {children} </Typography> </> ); - }; diff --git a/src/lib/Header/Header.tsx b/src/lib/Header/Header.tsx index 233eacb..58be989 100644 --- a/src/lib/Header/Header.tsx +++ b/src/lib/Header/Header.tsx @@ -40,13 +40,15 @@ const useStyles = makeStyles(theme => ({ '& svg': { marginRight: theme.spacing(1), marginBottom: '0 !important', - } - } - } + }, + }, + }, })); -const Header: React.FC<PropTypes> = ({ logo, contents, page, setPage }) => { +const Header: React.FC<PropTypes> = ({ + logo, contents, page, setPage, +}) => { const classes = useStyles(); const handleChange = (event: React.ChangeEvent<{}>, newPage: string): void => { @@ -54,25 +56,25 @@ const Header: React.FC<PropTypes> = ({ logo, contents, page, setPage }) => { }; return ( - <AppBar position="sticky" className={classes.root}> - <Toolbar> - {logo.icon} - <Typography variant="h5" className={classes.logo} color="primary"> - {logo.title} - </Typography> - <Tabs onChange={handleChange} value={page}> - {contents && Object.keys(contents).map((item: string) => ( - <Tab - label={item} - icon={contents[item] as JSX.Element} - value={item} - className={classes.tab} - key={item} - /> - ))} - </Tabs> - </Toolbar> - </AppBar> + <AppBar position="sticky" className={classes.root}> + <Toolbar> + {logo.icon} + <Typography variant="h5" className={classes.logo} color="primary"> + {logo.title} + </Typography> + <Tabs onChange={handleChange} value={page}> + {contents && Object.keys(contents).map((item: string) => ( + <Tab + label={item} + icon={contents[item] as JSX.Element} + value={item} + className={classes.tab} + key={item} + /> + ))} + </Tabs> + </Toolbar> + </AppBar> ); }; diff --git a/src/lib/Markdown/CodeBlock.tsx b/src/lib/Markdown/CodeBlock.tsx index 5b8edec..394458e 100644 --- a/src/lib/Markdown/CodeBlock.tsx +++ b/src/lib/Markdown/CodeBlock.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import { ParserPropTypes } from './types'; import { Paper } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; +import { ParserPropTypes } from './types'; const useStyles = makeStyles(theme => ({ root: { @@ -10,7 +10,7 @@ const useStyles = makeStyles(theme => ({ padding: theme.spacing(1), overflowX: 'auto', fontFamily: 'Monospace', - scrollbarColor: 'auto' + scrollbarColor: 'auto', }, })); @@ -21,7 +21,7 @@ const CodeBlock: React.FC<ParserPropTypes> = ({ rawLines }) => { {rawLines.map(line => <pre>{line}</pre>)} </Paper> ); -} +}; export default CodeBlock; diff --git a/src/lib/Markdown/Content.tsx b/src/lib/Markdown/Content.tsx index aaea100..88409fa 100644 --- a/src/lib/Markdown/Content.tsx +++ b/src/lib/Markdown/Content.tsx @@ -6,28 +6,32 @@ import { ParserPropTypes } from './types'; const denotesCodeBlock = (line: string): boolean => { - return line.match(/^```.*$/) !== null; -} + return line.match(/^\s*```.*$/) !== null; +}; const denotesDottedList = (line: string): boolean => { - return line.match(/^ ?- .*$/) !== null; -} + return line.match(/^ ?[-*] .*$/) !== null; +}; -const denotesOpenHtml= (line: string): string => { +const denotesOpenHtml = (line: string): string => { const regex = /<([^/\s]*)[^<]*[^/]>/g; const match = regex.exec(line); return match ? match[1] : ''; -} +}; -const denotesClosingHtml= (line: string, tag: string): boolean => { +const denotesClosingHtml = (line: string, tag: string): boolean => { const regex = new RegExp(`</${tag}[^<]*>`); return line.match(regex) !== null; -} +}; const denotesSelfClosingHtml = (line: string): string[] | null => { const regex = /(<[^/\s]*[^<]*\/>)/g; return line.match(regex); -} +}; + +const declaresNoLineBreak = (line: string): boolean => { + return line.match(/\\\|$/) !== null; +}; const Content: React.FC<ParserPropTypes> = ({ rawLines }) => { if (!rawLines.length) return null; @@ -36,33 +40,42 @@ const Content: React.FC<ParserPropTypes> = ({ rawLines }) => { let buffer; if (denotesCodeBlock(line)) { - const closeIndex = rawLines.findIndex(line => denotesCodeBlock(line)); + const closeIndex = rawLines.findIndex(rawLine => denotesCodeBlock(rawLine)); const codeBlockLines = rawLines.splice(0, closeIndex + 1).slice(0, closeIndex); - buffer = <CodeBlock rawLines={codeBlockLines} /> + buffer = <CodeBlock rawLines={codeBlockLines} />; } else if (denotesDottedList(line)) { - const closeIndex = rawLines.findIndex(line => !denotesDottedList(line)); + const closeIndex = rawLines.findIndex(rawLine => !denotesDottedList(rawLine)); const dottedListLines = rawLines.splice(0, closeIndex).slice(0, closeIndex); dottedListLines.unshift(line); buffer = <ul>{dottedListLines.map(li => <li><Text line={li.slice(2)} /></li>)}</ul>; } else if ((buffer = denotesOpenHtml(line))) { const tag = buffer; - const closeIndex = rawLines.findIndex(line => denotesClosingHtml(line, tag)); - const htmlLines = rawLines.splice(0, closeIndex + 1).slice(0, closeIndex); + const closeIndex = denotesClosingHtml(line, tag) ? -1 : rawLines.findIndex( + rawLine => denotesClosingHtml(rawLine, tag), + ); + const htmlLines = rawLines.splice(0, closeIndex + 1); htmlLines.unshift(line); - buffer = <div dangerouslySetInnerHTML={{ __html: htmlLines.join('\n') }}></div>; + buffer = <div dangerouslySetInnerHTML={{ __html: htmlLines.join('\n') }} />; } else if ((buffer = denotesSelfClosingHtml(line)) !== null) { const match = buffer[0]; const [before, after] = line.split(match); - console.log({ line, match, before, after}); buffer = ( <> <Text line={before} /> - <div dangerouslySetInnerHTML={{ __html: match }}></div> + <div dangerouslySetInnerHTML={{ __html: match }} /> <Text line={after} /> </> ); + } else if (declaresNoLineBreak(line)) { + const closeIndex = rawLines.findIndex(rawLine => !declaresNoLineBreak(rawLine)); + const lineBreakLines = rawLines.splice(0, closeIndex).map(rawLine => rawLine.slice(0, -2)); + lineBreakLines.unshift(line.slice(0, -2)); + lineBreakLines.push(rawLines.splice(0, 1)[0]); + buffer = <p>{lineBreakLines.map(lineBreakLine => <Text line={lineBreakLine} />)}</p>; + } else if (denotesClosingHtml(line, '')) { + buffer = null; } else { - buffer = <p><Text line={line} /></p> + buffer = <p><Text line={line} /></p>; } return ( @@ -71,7 +84,7 @@ const Content: React.FC<ParserPropTypes> = ({ rawLines }) => { <Content rawLines={rawLines} /> </> ); -} +}; export default Content; diff --git a/src/lib/Markdown/Markdown.tsx b/src/lib/Markdown/Markdown.tsx index 09ad54a..cfcf117 100644 --- a/src/lib/Markdown/Markdown.tsx +++ b/src/lib/Markdown/Markdown.tsx @@ -8,15 +8,26 @@ interface PropTypes { url?: string; } +const resolveUrls = (line: string, baseUrl: string): string => line.replace( + /src="(?!http)(.*)"[\s>]/, + (match, url) => `src="${baseUrl}/${url}?sanitize=true"`, +).replace( + /\[(.*\]?.*)\]\((?!http)(.+?)\)/, + (match, text, url) => `[${text}](${baseUrl}/${url})`, +); + const Markdown: React.FC<PropTypes> = ({ data, url }) => { const [markdown, setMarkdown] = useState<string>(data || ''); + if (url) axios.get(url).then(response => setMarkdown(response.data)); + useEffect(() => { if (!url) setMarkdown(data || ''); }, [data, url]); - if (url) axios.get(url).then(response => setMarkdown(response.data)); - return <Section rawLines={markdown.split('\n')} /> + const baseUrl = url?.slice(0, url.lastIndexOf('/')) || ''; + const lines = markdown.split(/\r?\n/).map(line => resolveUrls(line, baseUrl)); + return <Section rawLines={lines} />; }; diff --git a/src/lib/Markdown/Section.tsx b/src/lib/Markdown/Section.tsx index 5ce8954..fc208b1 100644 --- a/src/lib/Markdown/Section.tsx +++ b/src/lib/Markdown/Section.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Typography } from '@material-ui/core'; import ContentSection from '../ContentSection/ContentSection'; import Content from './Content'; import { ParserPropTypes } from './types'; @@ -10,9 +11,9 @@ interface PropTypes extends ParserPropTypes { const getHeaderLevel = (header: string): number => { if (!header) return 0; let level = 0; - while(header[level] === '#') level++; + while (header[level] === '#') level += 1; return level; -} +}; const ChildrenSections: React.FC<PropTypes> = ({ rawLines, level = 0 }) => { const childrenSectionLines = rawLines.reduce((sections: string[][], line: string) => { @@ -22,15 +23,22 @@ const ChildrenSections: React.FC<PropTypes> = ({ rawLines, level = 0 }) => { } return sections; }, []); - const children = childrenSectionLines.map(sectionLines => <Section rawLines={sectionLines} level={level}/>); - return <> {children} </>; -} + const children = childrenSectionLines.map(sectionLines => <Section rawLines={sectionLines} level={level} />); + return <>{children}</>; +}; const Section: React.FC<PropTypes> = ({ rawLines, level = 0 }) => { const deeperLevelIndex = rawLines.findIndex(line => line.match(`^#{${level + 1},} .*$`)); const rawContent = rawLines.splice(0, (deeperLevelIndex < 0) ? rawLines.length : deeperLevelIndex); - if (!level) return <ChildrenSections rawLines={rawLines} level={getHeaderLevel(rawLines[0])}/>; + if (!level) { + return ( + <> + <Typography><Content rawLines={rawContent} /></Typography> + <ChildrenSections rawLines={rawLines} level={getHeaderLevel(rawLines[0])} /> + </> + ); + } const sectionName = rawContent.splice(0, 1)[0].slice(level).trim(); const deeperLevel = getHeaderLevel(rawLines[0]); @@ -40,7 +48,7 @@ const Section: React.FC<PropTypes> = ({ rawLines, level = 0 }) => { <ChildrenSections rawLines={rawLines} level={deeperLevel} /> </ContentSection> ); -} +}; export default Section; diff --git a/src/lib/Markdown/SyntacticSpan.tsx b/src/lib/Markdown/SyntacticSpan.tsx index 299bf87..11cc024 100644 --- a/src/lib/Markdown/SyntacticSpan.tsx +++ b/src/lib/Markdown/SyntacticSpan.tsx @@ -19,41 +19,41 @@ interface Emoji { const enclosureRegex = (e: string): RegexPair => ({ local: new RegExp(`${e}([^${e}]+)${e}`), - global: new RegExp(`(${e}[^${e}]+${e})`) + global: new RegExp(`(${e}[^${e}]+${e})`), }); const regex: Record<string, RegexPair> = { conceal: { - global: /(!?\[.+?\]\(.+?\))/g, - local: /!?\[(.+?)\]\((.+?)\)/ + global: /(!?\[.+?\]\(.+?\))(?!])/g, + local: /!?\[(.*\]?.*)\]\((.+?)\)/, }, rawLink: { + // eslint-disable-next-line max-len global: /((?:(?:[A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www\.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)(?:(?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[.!/\\\w]*))?)/, - local: /&^/ + 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), + borderRadius: theme.spacing(0.5), + padding: theme.spacing(0.5), fontFamily: 'Monospace', }, image: { maxWidth: '100%', - maxHeight: '100%' + maxHeight: '100%', }, })); @@ -64,12 +64,12 @@ const SyntacticSpan: React.FC<PropTypes> = ({ span }) => { const matchConceal = regex.conceal.local.exec(span); if (matchConceal) { if (span[0] === '!') return <img src={matchConceal[2]} alt={matchConceal[1]} className={classes.image} />; - return <Link href={matchConceal[2]}>{matchConceal[1]}</Link>; + return <Link href={matchConceal[2]}><SyntacticSpan span={matchConceal[1]} /></Link>; } const matchEmoji = span.match(regex.emoji.local); if (matchEmoji) { - const emoji = emojiList.find(emoji => emoji.name === matchEmoji[1]); + const emoji = emojiList.find(e => e.name === matchEmoji[1]); return <span>{emoji ? emoji.char : span}</span>; } @@ -83,12 +83,12 @@ const SyntacticSpan: React.FC<PropTypes> = ({ span }) => { if (matchItalic) return <i>{matchItalic[1]}</i>; const matchStrikeThrough = span.match(regex.strikeThrough.local); - if (matchStrikeThrough) return <span style={{textDecoration: 'line-through' }}>{matchStrikeThrough[1]}</span>; + if (matchStrikeThrough) return <span style={{ textDecoration: 'line-through' }}>{matchStrikeThrough[1]}</span>; if (span.match(regex.rawLink.global)) return <Link href={span}>{span}</Link>; return <>{span}</>; -} +}; export { splitter }; diff --git a/src/lib/Markdown/Text.tsx b/src/lib/Markdown/Text.tsx index e287dee..be715fd 100644 --- a/src/lib/Markdown/Text.tsx +++ b/src/lib/Markdown/Text.tsx @@ -7,7 +7,7 @@ interface PropTypes { const Text: React.FC<PropTypes> = ({ line }) => { return <>{line.split(splitter).map(span => <SyntacticSpan span={span} />)}</>; -} +}; export default Text; diff --git a/src/lib/SmartList/SmartList.tsx b/src/lib/SmartList/SmartList.tsx index 22cd3b2..c86c127 100644 --- a/src/lib/SmartList/SmartList.tsx +++ b/src/lib/SmartList/SmartList.tsx @@ -21,8 +21,7 @@ interface Size { const SmartList: React.FC<PropTypes> = ({ itemSize, itemCount, renderItem }) => { - - const ResizedList: React.FC<Size> = ({ width, height}) => ( + const ResizedList: React.FC<Size> = ({ width, height }) => ( <FixedSizeList height={height} width={width} diff --git a/src/lib/Window/Window.tsx b/src/lib/Window/Window.tsx index 6821593..beaa672 100644 --- a/src/lib/Window/Window.tsx +++ b/src/lib/Window/Window.tsx @@ -47,12 +47,13 @@ const Window: React.FC<PropTypes> = ({ type, name, children }) => { size={size} position={position} > - {name && + {name + && ( <div> <Typography variant="h5" className={classes.header}>{name}</Typography> <Divider /> </div> - } + )} {children} </WindowSurface> ); diff --git a/src/lib/Window/WindowSurface.tsx b/src/lib/Window/WindowSurface.tsx index a65e398..1900901 100644 --- a/src/lib/Window/WindowSurface.tsx +++ b/src/lib/Window/WindowSurface.tsx @@ -22,7 +22,7 @@ const useStyles = makeStyles(theme => ({ '& a.MuiTypography-root': { color: theme.palette.primary.light, }, - } + }, })); @@ -32,12 +32,12 @@ const WindowSurface: React.FC<PropTypes> = ({ size, position, children }) => { return ( <Paper variant="outlined" - style={{...size, ...position}} + style={{ ...size, ...position }} className={classes.surface} > {children} </Paper> - ) + ); }; diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index 6431bc5..c7466ce 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -1 +1,2 @@ +// eslint-disable-next-line /// <reference types="react-scripts" /> |