diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..f91e635 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "arrowParens": "avoid", + "proseWrap": "preserve", + "singleQuote": true, + "tabWidth": 2 +} diff --git a/README.md b/README.md index 99a5bb0..66acb13 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ --- -__Medium TOC__ is a simple tool to generate tables of contents for Medium articles. Check it out: [www.mediumtoc.com](https://www.mediumtoc.com) +**Medium TOC** is a simple tool to generate tables of contents for Medium articles. Check it out: [www.mediumtoc.com](https://www.mediumtoc.com) -| ![starting](https://github.com/adamisntdead/medium-toc/blob/master/resources/start.png?raw=true) | ![resulting](https://github.com/adamisntdead/medium-toc/blob/master/resources/result.png?raw=true) | -|---------|---------| -| Enter A URL | Get The Contents | +| ![starting](https://github.com/adamisntdead/medium-toc/blob/master/resources/start.png?raw=true) | ![resulting](https://github.com/adamisntdead/medium-toc/blob/master/resources/result.png?raw=true) | +| ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | +| Enter A URL | Get The Contents | ## Installation diff --git a/backend/app.js b/backend/app.js index 181ddf8..ced7aea 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1,25 +1,24 @@ -const express = require("express"); -const cors = require("cors"); -const createTOC = require("./lib/create-toc"); +const express = require('express'); +const cors = require('cors'); +const createTOC = require('./lib/create-toc'); -const app = express() -app.use(cors()) +const app = express(); +app.use(cors()); app.get('/', (req, res) => { - const url = 'https://' + req.query.url + const url = 'https://' + req.query.url; createTOC(url) - .then(results => { - res.send({ - url, - results + .then(results => { + res.send({ + url, + results, + }); }) - }) - .catch(error => { - res.sendStatus(502) - }) - -}) + .catch(error => { + res.sendStatus(502); + }); +}); app.listen(process.env.PORT || 8080, () => { - console.log('Server Started') -}) \ No newline at end of file + console.log('Server Started'); +}); diff --git a/backend/lib/create-toc.js b/backend/lib/create-toc.js index d79d87a..7d85a5c 100644 --- a/backend/lib/create-toc.js +++ b/backend/lib/create-toc.js @@ -1,17 +1,17 @@ -const rp = require("request-promise"); -const cheerio = require("cheerio"); +const rp = require('request-promise'); +const cheerio = require('cheerio'); function createTOC(uri) { return rp(uri) .then(body => cheerio.load(body)) .then($ => { // Get The Article ID - const articleId = uri.substring(uri.lastIndexOf("-") + 1); + const articleId = uri.substring(uri.lastIndexOf('-') + 1); // Get the headings let results = []; - $("body") - .find("h2, h3, h4, h5, h6") + $('body') + .find('h2, h3, h4, h5, h6') .each((_, elem) => { const title = elem.children[0].data; const headingCode = elem.attribs.id; @@ -25,7 +25,7 @@ function createTOC(uri) { results.push({ title, headingCode, - link + link, }); }); diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js index 740a444..f96650e 100644 --- a/config/webpack.config.dev.js +++ b/config/webpack.config.dev.js @@ -84,7 +84,6 @@ module.exports = { // for React Native Web. extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'], alias: { - // Support React Native Web // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 'react-native': 'react-native-web', @@ -115,7 +114,6 @@ module.exports = { options: { formatter: eslintFormatter, eslintPath: require.resolve('eslint'), - }, loader: require.resolve('eslint-loader'), }, @@ -144,7 +142,6 @@ module.exports = { include: paths.appSrc, loader: require.resolve('babel-loader'), options: { - // This is a feature of `babel-loader` for webpack (not Babel itself). // It enables caching results in ./node_modules/.cache/babel-loader/ // directory for faster rebuilds. diff --git a/config/webpack.config.prod.js b/config/webpack.config.prod.js index f1f7aac..9338693 100644 --- a/config/webpack.config.prod.js +++ b/config/webpack.config.prod.js @@ -90,8 +90,8 @@ module.exports = { // for React Native Web. extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'], alias: { - "react": "preact-compat", - "react-dom": "preact-compat", + react: 'preact-compat', + 'react-dom': 'preact-compat', // Support React Native Web // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 'react-native': 'react-native-web', @@ -122,7 +122,6 @@ module.exports = { options: { formatter: eslintFormatter, eslintPath: require.resolve('eslint'), - }, loader: require.resolve('eslint-loader'), }, @@ -150,7 +149,6 @@ module.exports = { include: paths.appSrc, loader: require.resolve('babel-loader'), options: { - compact: true, }, }, diff --git a/config/webpackDevServer.config.js b/config/webpackDevServer.config.js index f12d315..94d6da1 100644 --- a/config/webpackDevServer.config.js +++ b/config/webpackDevServer.config.js @@ -9,7 +9,7 @@ const paths = require('./paths'); const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; const host = process.env.HOST || '0.0.0.0'; -module.exports = function(proxy, allowedHost) { +module.exports = function (proxy, allowedHost) { return { // WebpackDevServer 2.4.3 introduced a security fix that prevents remote // websites from potentially accessing local content through DNS rebinding: diff --git a/package.json b/package.json index bd1ee5d..4874768 100644 --- a/package.json +++ b/package.json @@ -47,10 +47,13 @@ "webpack-manifest-plugin": "1.3.2", "whatwg-fetch": "2.0.3" }, - "devDependencies": {}, + "devDependencies": { + "prettier": "^2.0.4" + }, "scripts": { - "start": "node scripts/start.js", "build": "node scripts/build.js", + "format": "prettier --write '**/*.{css,md,js}'", + "start": "node scripts/start.js", "test": "node scripts/test.js --env=jsdom" }, "jest": { diff --git a/scripts/start.js b/scripts/start.js index c5acbe5..da82989 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -92,8 +92,8 @@ choosePort(HOST, DEFAULT_PORT) openBrowser(urls.localUrlForBrowser); }); - ['SIGINT', 'SIGTERM'].forEach(function(sig) { - process.on(sig, function() { + ['SIGINT', 'SIGTERM'].forEach(function (sig) { + process.on(sig, function () { devServer.close(); process.exit(); }); diff --git a/scripts/test.js b/scripts/test.js index 399bfd3..736f166 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -23,5 +23,4 @@ if (!process.env.CI && argv.indexOf('--coverage') < 0) { argv.push('--watch'); } - jest.run(argv); diff --git a/src/App.css b/src/App.css index 9aa00c7..b71b642 100644 --- a/src/App.css +++ b/src/App.css @@ -1,4 +1,5 @@ -html, body { +html, +body { padding: 0; margin: 0; box-sizing: border-box; @@ -25,7 +26,7 @@ html, body { h1 { font-family: 'Times New Roman', Times, serif; font-size: 3.5rem; - color: #4A4A4A; + color: #4a4a4a; letter-spacing: 1.27px; font-weight: bold; diff --git a/src/App.js b/src/App.js index 3043949..5b7ca5d 100644 --- a/src/App.js +++ b/src/App.js @@ -1,10 +1,10 @@ -import React, { Component } from "react"; -import Loadable from "react-loading-overlay"; +import React, { Component } from 'react'; +import Loadable from 'react-loading-overlay'; -import Instruction from "./instruction/Instruction.js"; -import Display from "./display/Display"; -import getHeadings from "./api/get-headings"; -import "./App.css"; +import Instruction from './instruction/Instruction.js'; +import Display from './display/Display'; +import getHeadings from './api/get-headings'; +import './App.css'; class App extends Component { constructor(props) { @@ -14,16 +14,15 @@ class App extends Component { message: "Just enter the public url of a medium article and we'll create a table of contents.", loading: false, - results: [] + results: [], }; this.handleSubmit = this.handleSubmit.bind(this); } - handleSubmit(url) { this.setState({ - loading: true + loading: true, }); getHeadings(url) @@ -33,7 +32,7 @@ class App extends Component { message: "Couldn't find headings for this URL. Please check it is correct, and is a Medium article. If the article is a draft, use the 'share' url.", error: true, - loading: false + loading: false, }); return; } @@ -41,17 +40,17 @@ class App extends Component { this.setState({ error: false, message: - "Done! Just copy and paste whats below into your Medium article!", + 'Done! Just copy and paste whats below into your Medium article!', results: results.results, - loading: false + loading: false, }); }) .catch(error => { this.setState({ message: - "There was an error fetching content. Make sure the URL is correct and you are connected to the internet.", + 'There was an error fetching content. Make sure the URL is correct and you are connected to the internet.', error: true, - loading: false + loading: false, }); }); } @@ -66,7 +65,9 @@ class App extends Component { text="Creating Your Table Of Contents..." >
-

Medium TOC

+

+ Medium TOC +

{ - const div = document.createElement("div"); +it('renders without crashing', () => { + const div = document.createElement('div'); ReactDOM.render(, div); ReactDOM.unmountComponentAtNode(div); }); diff --git a/src/api/get-headings.js b/src/api/get-headings.js index c1f0778..b2f84da 100644 --- a/src/api/get-headings.js +++ b/src/api/get-headings.js @@ -1,8 +1,8 @@ -const apiUrl = "https://toc-backend-oirlqadtnt.now.sh"; +const apiUrl = 'https://toc-backend-oirlqadtnt.now.sh'; function getHeadings(url) { - const urlPath = url.replace(/^(http|https):\/\//, ""); + const urlPath = url.replace(/^(http|https):\/\//, ''); - return fetch(apiUrl + "?url=" + encodeURI(urlPath)).then(resp => resp.json()); + return fetch(apiUrl + '?url=' + encodeURI(urlPath)).then(resp => resp.json()); } export default getHeadings; diff --git a/src/display/Display.js b/src/display/Display.js index 0088229..451d01b 100644 --- a/src/display/Display.js +++ b/src/display/Display.js @@ -1,13 +1,13 @@ -import React from 'react' -import TOC from "../toc/TOC"; -import UrlForm from "../form/Form"; +import React from 'react'; +import TOC from '../toc/TOC'; +import UrlForm from '../form/Form'; -const Display = ({results, handleSubmit}) => { +const Display = ({ results, handleSubmit }) => { if (results.length > 0) { return ; } else { return ; } -} +}; export default Display; diff --git a/src/form/Form.css b/src/form/Form.css index 44ac988..e7610ba 100644 --- a/src/form/Form.css +++ b/src/form/Form.css @@ -1,6 +1,6 @@ .button { display: block; - border: 1px solid #00AB6C; + border: 1px solid #00ab6c; border-radius: 8px; background-color: transparent; font-size: 24px; @@ -22,108 +22,132 @@ margin: auto; } -* { - box-sizing:border-box; +* { + box-sizing: border-box; } /* form starting stylings ------------------------------- */ -.group { - position:relative; - margin-bottom:45px; +.group { + position: relative; + margin-bottom: 45px; } -input { - font-size:18px; - padding:10px 10px 10px 5px; - display:block; - width:100%; - border:none; - border-bottom:1px solid #757575; -} -input:focus { outline:none; } +input { + font-size: 18px; + padding: 10px 10px 10px 5px; + display: block; + width: 100%; + border: none; + border-bottom: 1px solid #757575; +} +input:focus { + outline: none; +} input:invalid { box-shadow: none; } /* LABEL ======================================= */ -label { - color:#999; - font-size:18px; - font-weight:normal; - position:absolute; - pointer-events:none; - left:5px; - top:10px; - transition:0.2s ease all; - -moz-transition:0.2s ease all; - -webkit-transition:0.2s ease all; +label { + color: #999; + font-size: 18px; + font-weight: normal; + position: absolute; + pointer-events: none; + left: 5px; + top: 10px; + transition: 0.2s ease all; + -moz-transition: 0.2s ease all; + -webkit-transition: 0.2s ease all; } /* active state */ -input:focus ~ label, input:valid ~ label { - top:-20px; - font-size:14px; - color:#00AB6C; +input:focus ~ label, +input:valid ~ label { + top: -20px; + font-size: 14px; + color: #00ab6c; } /* BOTTOM BARS ================================= */ -.bar { position:relative; display:block; width:100%; } -.bar:before, .bar:after { - content:''; - height:2px; - width:0; - bottom:1px; - position:absolute; - background:#00AB6C; - transition:0.2s ease all; - -moz-transition:0.2s ease all; - -webkit-transition:0.2s ease all; +.bar { + position: relative; + display: block; + width: 100%; +} +.bar:before, +.bar:after { + content: ''; + height: 2px; + width: 0; + bottom: 1px; + position: absolute; + background: #00ab6c; + transition: 0.2s ease all; + -moz-transition: 0.2s ease all; + -webkit-transition: 0.2s ease all; } .bar:before { - left:50%; + left: 50%; } .bar:after { - right:50%; + right: 50%; } /* active state */ -input:focus ~ .bar:before, input:focus ~ .bar:after { - width:50%; +input:focus ~ .bar:before, +input:focus ~ .bar:after { + width: 50%; } /* HIGHLIGHTER ================================== */ .highlight { - position:absolute; - height:60%; - width:100px; - top:25%; - left:0; - pointer-events:none; - opacity:0.5; + position: absolute; + height: 60%; + width: 100px; + top: 25%; + left: 0; + pointer-events: none; + opacity: 0.5; } /* active state */ input:focus ~ .highlight { - -webkit-animation:inputHighlighter 0.3s ease; - -moz-animation:inputHighlighter 0.3s ease; - animation:inputHighlighter 0.3s ease; + -webkit-animation: inputHighlighter 0.3s ease; + -moz-animation: inputHighlighter 0.3s ease; + animation: inputHighlighter 0.3s ease; } /* ANIMATIONS ================ */ @-webkit-keyframes inputHighlighter { - from { background:#00AB6C; } - to { width:0; background:transparent; } + from { + background: #00ab6c; + } + to { + width: 0; + background: transparent; + } } @-moz-keyframes inputHighlighter { - from { background:#00AB6C; } - to { width:0; background:transparent; } + from { + background: #00ab6c; + } + to { + width: 0; + background: transparent; + } } @keyframes inputHighlighter { - from { background:#00AB6C; } - to { width:0; background:transparent; } + from { + background: #00ab6c; + } + to { + width: 0; + background: transparent; + } } .input { margin-top: 100px; -} \ No newline at end of file +} diff --git a/src/form/Form.js b/src/form/Form.js index a3afb03..f64106d 100644 --- a/src/form/Form.js +++ b/src/form/Form.js @@ -1,10 +1,10 @@ -import React, { Component } from "react"; -import "./Form.css"; +import React, { Component } from 'react'; +import './Form.css'; class Form extends Component { constructor(props) { super(props); - this.state = { value: "" }; + this.state = { value: '' }; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); @@ -34,7 +34,9 @@ class Form extends Component {
- + ); } diff --git a/src/index.js b/src/index.js index 7f76cb9..fae3e35 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,8 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import "./index.css"; -import App from "./App"; -import registerServiceWorker from "./registerServiceWorker"; +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './App'; +import registerServiceWorker from './registerServiceWorker'; -ReactDOM.render(, document.getElementById("root")); +ReactDOM.render(, document.getElementById('root')); registerServiceWorker(); diff --git a/src/instruction/Instruction.css b/src/instruction/Instruction.css index 537405a..c3af47b 100644 --- a/src/instruction/Instruction.css +++ b/src/instruction/Instruction.css @@ -2,7 +2,7 @@ font-family: Helvetica, sans-serif; font-weight: 300; font-size: 2rem; - color: #AEAEAE; + color: #aeaeae; letter-spacing: 0.71px; width: 100; } diff --git a/src/instruction/Instruction.js b/src/instruction/Instruction.js index 294394d..f51205c 100644 --- a/src/instruction/Instruction.js +++ b/src/instruction/Instruction.js @@ -1,10 +1,18 @@ -import React, { Component } from "react"; -import "./Instruction.css"; +import React, { Component } from 'react'; +import './Instruction.css'; -const extraHelp = Get more help. +const extraHelp = ( + + Get more help. + +); -const Instruction = ({error, message}) =>
-

{message} {(() => error ? extraHelp : '')()}

+const Instruction = ({ error, message }) => ( +
+

+ {message} {(() => (error ? extraHelp : ''))()} +

+); export default Instruction; diff --git a/src/registerServiceWorker.js b/src/registerServiceWorker.js index a682a17..a3e6c0c 100644 --- a/src/registerServiceWorker.js +++ b/src/registerServiceWorker.js @@ -9,9 +9,9 @@ // This link also includes instructions on opting out of this behavior. const isLocalhost = Boolean( - window.location.hostname === "localhost" || + window.location.hostname === 'localhost' || // [::1] is the IPv6 localhost address. - window.location.hostname === "[::1]" || + window.location.hostname === '[::1]' || // 127.0.0.1/8 is considered localhost for IPv4. window.location.hostname.match( /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ @@ -19,7 +19,7 @@ const isLocalhost = Boolean( ); export default function register() { - if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { // The URL constructor is available in all browsers that support SW. const publicUrl = new URL(process.env.PUBLIC_URL, window.location); if (publicUrl.origin !== window.location.origin) { @@ -29,7 +29,7 @@ export default function register() { return; } - window.addEventListener("load", () => { + window.addEventListener('load', () => { const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; if (isLocalhost) { @@ -40,8 +40,8 @@ export default function register() { // service worker/PWA documentation. navigator.serviceWorker.ready.then(() => { console.log( - "This web app is being served cache-first by a service " + - "worker. To learn more, visit https://goo.gl/SC7cgQ" + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://goo.gl/SC7cgQ' ); }); } else { @@ -59,25 +59,25 @@ function registerValidSW(swUrl) { registration.onupdatefound = () => { const installingWorker = registration.installing; installingWorker.onstatechange = () => { - if (installingWorker.state === "installed") { + if (installingWorker.state === 'installed') { if (navigator.serviceWorker.controller) { // At this point, the old content will have been purged and // the fresh content will have been added to the cache. // It's the perfect time to display a "New content is // available; please refresh." message in your web app. - console.log("New content is available; please refresh."); + console.log('New content is available; please refresh.'); } else { // At this point, everything has been precached. // It's the perfect time to display a // "Content is cached for offline use." message. - console.log("Content is cached for offline use."); + console.log('Content is cached for offline use.'); } } }; }; }) .catch(error => { - console.error("Error during service worker registration:", error); + console.error('Error during service worker registration:', error); }); } @@ -88,7 +88,7 @@ function checkValidServiceWorker(swUrl) { // Ensure service worker exists, and that we really are getting a JS file. if ( response.status === 404 || - response.headers.get("content-type").indexOf("javascript") === -1 + response.headers.get('content-type').indexOf('javascript') === -1 ) { // No service worker found. Probably a different app. Reload the page. navigator.serviceWorker.ready.then(registration => { @@ -103,13 +103,13 @@ function checkValidServiceWorker(swUrl) { }) .catch(() => { console.log( - "No internet connection found. App is running in offline mode." + 'No internet connection found. App is running in offline mode.' ); }); } export function unregister() { - if ("serviceWorker" in navigator) { + if ('serviceWorker' in navigator) { navigator.serviceWorker.ready.then(registration => { registration.unregister(); }); diff --git a/src/toc/TOC.css b/src/toc/TOC.css index cc0e191..06cbb71 100644 --- a/src/toc/TOC.css +++ b/src/toc/TOC.css @@ -7,4 +7,4 @@ .results a { color: #000; -} \ No newline at end of file +} diff --git a/src/toc/TOC.js b/src/toc/TOC.js index 8ba0ffe..a45d6ec 100644 --- a/src/toc/TOC.js +++ b/src/toc/TOC.js @@ -1,5 +1,5 @@ -import React, { Component } from "react"; -import "./TOC.css"; +import React, { Component } from 'react'; +import './TOC.css'; class TOC extends Component { render() { @@ -8,7 +8,11 @@ class TOC extends Component {

Table of Contents

diff --git a/yarn.lock b/yarn.lock index 07bf06f..43e4917 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4946,6 +4946,11 @@ preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" +prettier@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.4.tgz#2d1bae173e355996ee355ec9830a7a1ee05457ef" + integrity sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w== + pretty-bytes@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9"