├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── book.md ├── book ├── book-dark.pdf ├── book-light.pdf └── book.epub ├── bookInfo.js ├── gatsby-browser.js ├── gatsby-config.js ├── generate-book ├── epub.js ├── pdf.js ├── templates.js └── toc.html ├── package.json ├── readme-images └── 1.png ├── src ├── components │ ├── Layout.js │ ├── MenuToggle.js │ └── SEO.js ├── html.js ├── pages │ ├── 404.js │ └── index.js ├── styles │ └── style.scss └── utils │ └── renderers.js ├── static ├── cover.png └── robots.txt └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | browser: true, 5 | es2021: true, 6 | }, 7 | extends: ["eslint:recommended", "plugin:react/recommended"], 8 | parserOptions: { 9 | ecmaFeatures: { 10 | jsx: true, 11 | }, 12 | ecmaVersion: 12, 13 | sourceType: "module", 14 | }, 15 | plugins: ["react"], 16 | rules: { "react/prop-types": 0 }, 17 | 18 | settings: { 19 | react: { 20 | version: "latest", 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | node_modules 3 | public 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Sara Vieira 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Book Starter 2 | 3 | A starter built with gatsby to help you create books with ease. 4 | 5 | - 📖 Create Book from markdown files 6 | - 🖼 Generate PDF with Light and Dark Mode 7 | - 📚 Generates Epub file 8 | - 👩‍💻 Creates a preview site 9 | 10 | [PDF Light Example](./book/book-light.pdf) 11 | 12 | [PDF Dark Example](./book/book-dark.pdf) 13 | 14 | [Epub Example](./book/book.epub) 15 | 16 | [Website Example](https://wizardly-snyder-c98440.netlify.com/) (add `?theme=dark` to preview dark version) 17 | 18 | ## Docs 19 | 20 | Docs can be found at [https://saravieira.github.io/starter-book/#/](https://saravieira.github.io/starter-book/#/) 21 | 22 | --- 23 | 24 | Any issues please make a PR or file a bug 25 | 26 | Licensed under the MIT license 27 | -------------------------------------------------------------------------------- /book.md: -------------------------------------------------------------------------------- 1 | # Vincas in oscula inter reserabo 2 | 3 | ## Quod et temptaretque fallax vertice 4 | 5 | Lorem markdownum manibus piscator vanam dimittere in erat! Procul nulla Amyclide 6 | [Haemonio](http://trepidans.io/inspirat.php) mota fama guttura munus 7 | reminiscitur perspicuus Iuppiter umeris _est_, quae nos, auras loca. Ossa terras 8 | haec, sua [videbitur huius](http://ferrovolucris.org/nullius.html), vitulus 9 | parvumque vel bracchia rapitur mittere seducunt. Prima poposcerit ad simul 10 | Proserpina corpus, memor caput est: virum sincera neque suus sic _caelestibus_ 11 | quod? Quoque plaudenda usus, et veniunt pectora, sex, Licha qui cum. 12 | 13 | > Genitor capillis? Tuas tantum [meorum](http://aderat.net/servo) ferarum 14 | > furiisque sicca aut tyranni quarum Cerbere credens. 15 | 16 | ## Utraque voluistis ambiguum tremulaeque risit maris 17 | 18 | Livor primum dicta; sit addunt aula Quas de coniunx, famulae ante! Atque voverat 19 | Pleiadum longa tollere mole non saevitiam undas, coniunx foedantem, accingere 20 | occupat: eunti nisi. Suus cervice facit eburnea quas turbae illa arbor, cetera 21 | Tmolus delphines. 22 | 23 | > Hospite quoniam et calcatis esse madefactaque haec quodcumque aliae translucet 24 | > et flumina nasci hoc nosse marmore, me quas. Illo ira, quo Iano pocula venit 25 | > calidusque ignis rubori, fratres ambarum: si nec duxit. Est quaecumque 26 | > probarunt quid coniunx sine veros, gratissima sed vestra cuncta 27 | > [de](http://www.vocalia.net/)! Offensamque triceps ponto, 28 | > [trepidum](http://www.apta.com/) fretoque exosus gaudere. 29 | 30 | ## Demittit primordia lacrimarum ire ausus Astraea 31 | 32 | Erit nisi neque, Bacchus **seque**. Est quam veloque; quas dixit, inpius 33 | vellentem ab grandia equidem dixit ignibus ullo sum, _recepta_ est. 34 | 35 | Pone avidamque quisquis deo per. Dianae Ityosque veniam circumspicit nisi 36 | multos. Ausa cita quid nec sumus. Aras uritur comes mihi **gavisa** permanet 37 | scylla cum ad neu. Aeacus et ante primordia infectis altaribus Tartessia putet, 38 | cecidisse, medias sternit, verborum. 39 | 40 | Clipeum venenum [igitur multumque aerias](http://www.arvis.org/), flammamque 41 | subito quid, _omnes_. Fontes et Sisyphon **formatur** duritia et inserui canos 42 | **beatam ignes**. Dura licet, ire petis ille sanguine et Clarium dedit nullus 43 | sumptaque imagine fit ficta **vires**; armis. Quem verba sine tota 44 | [mihi](http://cecropisvota.net/deforme), hostibus supposita illa teloque. [Est 45 | spernite Achille](http://utrumque-illic.net/), quaedam divis orant, ipsa 46 | recidendum fistula; viro vero quod et! 47 | 48 | ## Et caligine freta sive corona in hasta 49 | 50 | Lorem markdownum si brevis Saeculaque vestrum posuere; tamen adventuque poenas? 51 | Palmis nubila, causas neve licet ad primaque concurrere referre! 52 | 53 | ## Viisque relevere habuisse paulatim consurgere passim 54 | 55 | Medio quodque. Dum illa per non mille non prodis, quoque [evolvere pectore 56 | concordes](http://sparsaque.com/) nulla et ferox Cnosiaco sequuntur iam adhuc 57 | tuo. Pectus ululasse tuus simulacraque domus hostiliter pondus **nymphae 58 | natarum**. Time sum; deo felix et cecidit: vobis hoc curae iura magis **solo 59 | horum** et. Tempora Iuno tenui prima: usus spectarat, procumbit mea si egredior 60 | celeberrimus visum, flavescere. 61 | 62 | - Sanguine umeros mora eurus es inania opertum 63 | - Decorum via 64 | - Flebile cessit prohibetis venam sustinuere Cephalus censu 65 | - Erat tenebat esse inmunis 66 | 67 | ## Hoc quoque summa parta filia oris anguis 68 | 69 | Unda temptat non iaces, mecum cinis recentes corpus tum iusque silvas adesset. 70 | Redis pro Dymantida capillos optavit vivit per, placido ambos siquis adnuerat, 71 | legesque calcitrat [rostro qui](http://www.illa.org/anni) seu moenibus, 72 | turpique. Facinus sua spatium [haec 73 | copia](http://www.primoque-duro.org/lanceacum.html) iubenti restat; non adhuc 74 | sub, o tibi postquam multi sanguine certas mundo deus. 75 | 76 | Cadit mare `iamque` artes; victor, pedes. Cum ponti res, his agri est mora danti 77 | purpureusque [lunae](http://www.per.net/) increscere flerunt invidia venit. 78 | Membra porrigit quaecumque Lycaeo verba, deos baca Somni, occuluit reliquerat 79 | cristae **natusque**; praelate _unum_. 80 | 81 | ## Dum modus suumque 82 | 83 | ```jsx 84 | import React, { createContext, useReducer } from "react"; 85 | import airportList from "./data"; 86 | 87 | export const AirportContext = createContext(null); 88 | 89 | function reducer(state, action) { 90 | switch (action.type) { 91 | case "toggleVisited": 92 | return { 93 | airports: state.airports.map((airport) => { 94 | if (airport.id === action.value) { 95 | return { 96 | ...airport, 97 | visited: !airport.visited, 98 | }; 99 | } 100 | 101 | return airport; 102 | }), 103 | }; 104 | default: 105 | return state; 106 | } 107 | } 108 | 109 | export default ({ children }) => { 110 | const [state, dispatch] = useReducer(reducer, { 111 | airports: airportList, 112 | }); 113 | 114 | return ( 115 | 116 | {children} 117 | 118 | ); 119 | }; 120 | ``` 121 | 122 | [Phasias](http://www.pectora-igne.org/discedens) dant illo et quo auro fluit, et 123 | atque caelestia **imagine poenas**, referebat, et _lingua_. Et tecum texerat 124 | oves poscunt, ponitur **mihi** abdita et magno in _nitor aberant iurant_: 125 | remugis matri facere. Laetitiam pedem bellare cerno quoque, infractaque inde 126 | frustra vocis? Tereusque infuso **ambiguis** Phoebo supera, inferior quem si 127 | memorant attollit Euhan fratrum Inachus mendacia sic tamen una. Corpus tenus 128 | regia flammis usu convellere videtque illa cui mores agnovit recepit brevissimus 129 | cum. 130 | 131 | Cadit habitasse ipsum, ad partem sequiturque vaga, quae deficit, dummodo, ignis 132 | novem pertimuitque. Alte pondera mollibus, et rogant terrae de cogor tentoria 133 | explevit. Putabat duris auceps, _et timidas_ inter? Eratis ipse poplitibus, 134 | quem. 135 | -------------------------------------------------------------------------------- /book/book-dark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaraVieira/starter-book/60e67d103e33205c956ed4a218f3b900710c3e58/book/book-dark.pdf -------------------------------------------------------------------------------- /book/book-light.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaraVieira/starter-book/60e67d103e33205c956ed4a218f3b900710c3e58/book/book-light.pdf -------------------------------------------------------------------------------- /book/book.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaraVieira/starter-book/60e67d103e33205c956ed4a218f3b900710c3e58/book/book.epub -------------------------------------------------------------------------------- /bookInfo.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "My Book", 3 | author: "Jane Doe", 4 | subject: "Good Book, best book ever", 5 | keywords: ["things", "stuff"], 6 | // relative to the generate-book directory 7 | cover: "../static/cover.png", 8 | localURL: "http://localhost:8000", 9 | siteUrl: "http://localhost:8000", 10 | social: { 11 | twitter: "test", 12 | }, 13 | PWA: { 14 | backgroundColor: `#ffffff`, 15 | themeColor: `#663399`, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | // custom typefaces 2 | import "typeface-montserrat"; 3 | import "typeface-merriweather"; 4 | import "firacode"; 5 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | const info = require("./bookInfo"); 2 | 3 | module.exports = { 4 | siteMetadata: { 5 | title: info.title, 6 | author: info.author, 7 | description: info.subject, 8 | siteUrl: info.siteUrl, 9 | social: info.social, 10 | }, 11 | plugins: [ 12 | { 13 | resolve: `gatsby-source-filesystem`, 14 | options: { 15 | path: `${__dirname}/book.md`, 16 | name: `book`, 17 | }, 18 | }, 19 | `gatsby-plugin-sass`, 20 | { 21 | resolve: `gatsby-transformer-remark`, 22 | options: { 23 | plugins: [ 24 | `gatsby-remark-prismjs`, 25 | { 26 | resolve: `gatsby-remark-images`, 27 | options: { 28 | maxWidth: 590, 29 | }, 30 | }, 31 | { 32 | resolve: `gatsby-remark-responsive-iframe`, 33 | options: { 34 | wrapperStyle: `margin-bottom: 1.0725rem`, 35 | }, 36 | }, 37 | `gatsby-remark-prismjs`, 38 | `gatsby-remark-copy-linked-files`, 39 | `gatsby-remark-smartypants`, 40 | ], 41 | }, 42 | }, 43 | `gatsby-transformer-sharp`, 44 | `gatsby-plugin-sharp`, 45 | { 46 | resolve: `gatsby-plugin-google-analytics`, 47 | options: { 48 | //trackingId: `ADD YOUR TRACKING ID HERE`, 49 | }, 50 | }, 51 | { 52 | resolve: `gatsby-plugin-manifest`, 53 | options: { 54 | name: info.title, 55 | start_url: `/`, 56 | background_color: info.PWA.backgroundColor, 57 | theme_color: info.PWA.themeColor, 58 | display: `minimal-ui`, 59 | }, 60 | }, 61 | `gatsby-plugin-offline`, 62 | `gatsby-plugin-react-helmet`, 63 | ], 64 | }; 65 | -------------------------------------------------------------------------------- /generate-book/epub.js: -------------------------------------------------------------------------------- 1 | const { epubCSS } = require("./templates"); 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | const Epub = require("epub-gen"); 5 | const info = require("../bookInfo"); 6 | const cheerio = require("cheerio"); 7 | 8 | const savePath = (ext) => path.join(__dirname, "../book/book." + ext); 9 | const html = fs.readFileSync( 10 | path.join(__dirname, "../public/index.html"), 11 | "utf8" 12 | ); 13 | 14 | const $ = cheerio.load(html); 15 | const chapters = []; 16 | 17 | $("h1").each(function (i, elem) { 18 | chapters.push({ 19 | data: $(elem).nextUntil("h1").toArray().map($.html).join(""), 20 | title: $(elem).text(), 21 | }); 22 | }); 23 | 24 | const option = { 25 | title: info.title, 26 | verbose: true, 27 | author: info.author, 28 | cover: path.join(__dirname, info.cover), 29 | customHtmlTocTemplatePath: path.join(__dirname, "./toc.html"), 30 | css: epubCSS, 31 | content: chapters, 32 | }; 33 | 34 | new Epub(option, savePath("epub")); 35 | -------------------------------------------------------------------------------- /generate-book/pdf.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require("puppeteer"); 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | const { PDFDocument, PageSizes } = require("pdf-lib"); 5 | const info = require("../bookInfo"); 6 | const { footerTemplate, footerDark } = require("./templates"); 7 | 8 | async function printPDFLight() { 9 | try { 10 | const browser = await puppeteer.launch({ headless: true }); 11 | const page = await browser.newPage(); 12 | await page.goto(info.localURL, { 13 | waitUntil: "networkidle2", 14 | timeout: 0, 15 | }); 16 | const book = await page.pdf({ 17 | displayHeaderFooter: true, 18 | printBackground: true, 19 | format: "A4", 20 | margin: { 21 | top: "100px", 22 | bottom: "100px", 23 | }, 24 | headerTemplate: " ", 25 | footerTemplate, 26 | }); 27 | await browser.close(); 28 | const pdfDoc = await PDFDocument.load(book); 29 | const newPage = pdfDoc.insertPage(0, PageSizes.A4); 30 | const imageBytes = fs.readFileSync(path.join(__dirname, info.cover)); 31 | const image = await pdfDoc.embedPng(imageBytes); 32 | pdfDoc.setTitle(info.title); 33 | pdfDoc.setAuthor(info.author); 34 | pdfDoc.setSubject(info.subject); 35 | pdfDoc.setKeywords(info.keywords); 36 | // Get the width and height of the first page 37 | const { width, height } = newPage.getSize(); 38 | 39 | newPage.drawImage(image, { 40 | x: 0, 41 | y: 0, 42 | width, 43 | height, 44 | }); 45 | 46 | // Serialize the PDFDocument to bytes (a Uint8Array) 47 | const pdfBytes = await pdfDoc.save(); 48 | fs.writeFile( 49 | path.join(__dirname, "../book/book-light.pdf"), 50 | pdfBytes, 51 | function (err) { 52 | if (err) { 53 | return console.log(err); 54 | } 55 | 56 | console.log("pdf built"); 57 | } 58 | ); 59 | } catch (e) { 60 | console.log(e); 61 | } 62 | } 63 | 64 | async function printPDFDark() { 65 | try { 66 | const browser = await puppeteer.launch({ headless: true }); 67 | const page = await browser.newPage(); 68 | await page.goto(info.localURL + "?theme=dark", { 69 | waitUntil: "networkidle2", 70 | timeout: 0, 71 | }); 72 | 73 | const book = await page.pdf({ 74 | displayHeaderFooter: true, 75 | printBackground: true, 76 | format: "A4", 77 | margin: { 78 | top: "100px", 79 | bottom: "100px", 80 | }, 81 | headerTemplate: " ", 82 | footerTemplate: footerDark, 83 | }); 84 | await browser.close(); 85 | const pdfDoc = await PDFDocument.load(book); 86 | const newPage = pdfDoc.insertPage(0, PageSizes.A4); 87 | const { width, height } = newPage.getSize(); 88 | 89 | const imageBytes = fs.readFileSync(path.join(__dirname, info.cover)); 90 | const image = await pdfDoc.embedPng(imageBytes); 91 | pdfDoc.setTitle(info.title); 92 | pdfDoc.setAuthor(info.author); 93 | pdfDoc.setSubject(info.subject); 94 | pdfDoc.setKeywords(info.keywords); 95 | // Get the width and height of the first page 96 | 97 | newPage.drawImage(image, { 98 | x: 0, 99 | y: 0, 100 | width, 101 | height, 102 | }); 103 | 104 | // Serialize the PDFDocument to bytes (a Uint8Array) 105 | const pdfBytes = await pdfDoc.save(); 106 | fs.writeFile( 107 | path.join(__dirname, "../book/book-dark.pdf"), 108 | pdfBytes, 109 | function (err) { 110 | if (err) { 111 | return console.log(err); 112 | } 113 | 114 | console.log("pdf built"); 115 | } 116 | ); 117 | } catch (e) { 118 | console.log(e); 119 | } 120 | } 121 | 122 | printPDFLight(); 123 | printPDFDark(); 124 | -------------------------------------------------------------------------------- /generate-book/templates.js: -------------------------------------------------------------------------------- 1 | const footerTemplate = 2 | '

Page of

'; 3 | 4 | const footerDark = 5 | '

Page of

'; 6 | 7 | const epubCSS = ` 8 | @font-face { 9 | font-family: "Helvetica"; 10 | font-style: normal; 11 | font-weight: normal; 12 | src: url("./Helvetica.ttf"); 13 | } 14 | body {color: #000; font-family: "Helvetica", sans-serif;} 15 | .alert, .title {display: none;} 16 | .menu-toggle, 17 | .title, 18 | .alert { 19 | display: none; 20 | } 21 | pre, 22 | .toc { 23 | page-break-inside: avoid; 24 | } 25 | code[class*="language-"], 26 | pre[class*="language-"] { 27 | color: #464646; 28 | text-align: left; 29 | white-space: pre; 30 | word-spacing: normal; 31 | word-break: normal; 32 | word-wrap: normal; 33 | line-height: 1.5; 34 | -moz-tab-size: 4; 35 | -o-tab-size: 4; 36 | tab-size: 4; 37 | -webkit-hyphens: none; 38 | -moz-hyphens: none; 39 | -ms-hyphens: none; 40 | hyphens: none; 41 | } 42 | pre[class*="language-"]::-moz-selection, 43 | pre[class*="language-"] ::-moz-selection, 44 | code[class*="language-"]::-moz-selection, 45 | code[class*="language-"] ::-moz-selection { 46 | text-shadow: none; 47 | background: #f7f7f7; 48 | } 49 | pre[class*="language-"]::selection, 50 | pre[class*="language-"] ::selection, 51 | code[class*="language-"]::selection, 52 | code[class*="language-"] ::selection { 53 | text-shadow: none; 54 | background: #f7f7f7; 55 | } 56 | @media print { 57 | code[class*="language-"], 58 | pre[class*="language-"] { 59 | text-shadow: none; 60 | } 61 | } 62 | /* Code blocks */ 63 | pre[class*="language-"] { 64 | padding: 1em; 65 | margin: 0.5em 0; 66 | overflow: auto; 67 | } 68 | :not(pre) > code[class*="language-"], 69 | pre[class*="language-"] { 70 | color: white; 71 | background: #f7f7f7; 72 | } 73 | :not(pre) > code[class*="language-"] { 74 | padding: 0.1em; 75 | border-radius: 0.3em; 76 | white-space: normal; 77 | } 78 | .token.variable { 79 | color: #464646; 80 | 81 | } 82 | .token.operator { 83 | color: #464646; 84 | 85 | } 86 | .token.keyword { 87 | color: #999999; 88 | 89 | } 90 | .token.number { 91 | color: #999999; 92 | 93 | } 94 | .token.constant { 95 | color: #999999; 96 | 97 | } 98 | .token.attr-name { 99 | color: #999999; 100 | 101 | } 102 | .token.builtin { 103 | color: #868686; 104 | 105 | } 106 | .token.string { 107 | color: #868686; 108 | 109 | } 110 | .token.char { 111 | color: #868686; 112 | 113 | } 114 | .token.tag { 115 | color: #7C7C7C; 116 | 117 | } 118 | .token.deleted { 119 | color: #7C7C7C; 120 | 121 | } 122 | .token.selector { 123 | color: #747474; 124 | 125 | } 126 | .token.changed { 127 | color: #747474; 128 | 129 | } 130 | .token.inserted { 131 | color: #8E8E8E; 132 | 133 | } 134 | .token.important, 135 | .token.bold { 136 | font-weight: bold; 137 | } 138 | .token.italic { 139 | font-style: italic; 140 | } 141 | `; 142 | 143 | module.exports = { 144 | footerDark, 145 | footerTemplate, 146 | epubCSS, 147 | }; 148 | -------------------------------------------------------------------------------- /generate-book/toc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= title %> 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-starter-book", 3 | "private": true, 4 | "description": "A starter for a book powered by Markdown", 5 | "version": "0.1.0", 6 | "author": "Sara Vieira", 7 | "bugs": { 8 | "url": "https://github.com/SaraVieira/gatsby-starter-book/issues" 9 | }, 10 | "dependencies": { 11 | "firacode": "^2.0.0", 12 | "framer-motion": "1.8.4", 13 | "gatsby": "2.19.12", 14 | "gatsby-plugin-google-analytics": "2.1.35", 15 | "gatsby-plugin-manifest": "2.2.41", 16 | "gatsby-plugin-no-javascript": "2.0.5", 17 | "gatsby-plugin-offline": "3.0.34", 18 | "gatsby-plugin-react-helmet": "3.1.22", 19 | "gatsby-plugin-sass": "^2.3.13", 20 | "gatsby-plugin-sharp": "2.4.5", 21 | "gatsby-remark-copy-linked-files": "2.1.37", 22 | "gatsby-remark-images": "3.1.44", 23 | "gatsby-remark-prismjs": "3.3.31", 24 | "gatsby-remark-responsive-iframe": "2.2.32", 25 | "gatsby-remark-smartypants": "2.1.21", 26 | "gatsby-source-filesystem": "2.1.48", 27 | "gatsby-transformer-remark": "2.6.50", 28 | "gatsby-transformer-sharp": "2.3.14", 29 | "node-sass": "^4.14.1", 30 | "prism-theme-night-owl": "1.2.0", 31 | "prism-themes": "^1.3.0", 32 | "prismjs": "1.23.0", 33 | "react": "16.12.0", 34 | "react-dom": "16.12.0", 35 | "react-helmet": "5.2.1", 36 | "react-markdown": "4.3.1", 37 | "rehype-react": "4.0.1", 38 | "typeface-merriweather": "0.0.72", 39 | "typeface-montserrat": "0.0.75" 40 | }, 41 | "devDependencies": { 42 | "cheerio": "^1.0.0-rc.3", 43 | "epub-gen": "0.1.0", 44 | "eslint": "^7.9.0", 45 | "eslint-plugin-react": "^7.20.6", 46 | "pdf-lib": "1.3.1", 47 | "prettier": "^2.1.1", 48 | "puppeteer": "2.1.1" 49 | }, 50 | "homepage": "https://github.com/SaraVieira/gatsby-starter-book#readme", 51 | "keywords": [ 52 | "gatsby" 53 | ], 54 | "license": "MIT", 55 | "main": "n/a", 56 | "repository": { 57 | "type": "git", 58 | "url": "git+https://github.com/SaraVieira/gatsby-starter-book.git" 59 | }, 60 | "scripts": { 61 | "build:site": "gatsby build", 62 | "build:book": "node generate-book/pdf.js && GATSBY_SCRAPPER=1 gatsby build && node generate-book/epub.js", 63 | "develop": "gatsby develop", 64 | "format": "prettier --write \"**/*.{js,jsx,json,md}\"", 65 | "lint": "eslint .", 66 | "start": "npm run develop", 67 | "serve": "gatsby serve", 68 | "clean": "gatsby clean", 69 | "build": "npm run build:book && npm run build:site" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /readme-images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaraVieira/starter-book/60e67d103e33205c956ed4a218f3b900710c3e58/readme-images/1.png -------------------------------------------------------------------------------- /src/components/Layout.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import info from "../../bookInfo"; 3 | 4 | const Layout = ({ children }) => ( 5 |
6 |
7 |

{info.title}

8 |
9 |
{children}
10 |
11 | ); 12 | 13 | export default Layout; 14 | -------------------------------------------------------------------------------- /src/components/MenuToggle.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { motion } from "framer-motion"; 3 | 4 | const Path = props => ( 5 | 12 | ); 13 | 14 | export const MenuToggle = ({ toggle }) => ( 15 | 39 | ); 40 | -------------------------------------------------------------------------------- /src/components/SEO.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SEO component that queries for data with 3 | * Gatsby's useStaticQuery React hook 4 | * 5 | * See: https://www.gatsbyjs.org/docs/use-static-query/ 6 | */ 7 | 8 | import React from "react"; 9 | import Helmet from "react-helmet"; 10 | import { useStaticQuery, graphql } from "gatsby"; 11 | 12 | function SEO({ description = "", lang = "en", meta = [], title }) { 13 | const { site } = useStaticQuery( 14 | graphql` 15 | query { 16 | site { 17 | siteMetadata { 18 | title 19 | description 20 | author 21 | } 22 | } 23 | } 24 | ` 25 | ); 26 | 27 | const metaDescription = description || site.siteMetadata.description; 28 | 29 | return ( 30 | 71 | ); 72 | } 73 | 74 | export default SEO; 75 | -------------------------------------------------------------------------------- /src/html.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function HTML(props) { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | {props.headComponents} 16 | 17 | 18 | {props.preBodyComponents} 19 |
24 | {props.postBodyComponents} 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { graphql } from "gatsby"; 3 | 4 | import Layout from "../components/Layout"; 5 | import SEO from "../components/SEO"; 6 | 7 | class NotFoundPage extends React.Component { 8 | render() { 9 | const { data } = this.props; 10 | const siteTitle = data.site.siteMetadata.title; 11 | 12 | return ( 13 | 14 | 15 |

Not Found

16 |

You just hit a route that doesn't exist... the sadness.

17 |
18 | ); 19 | } 20 | } 21 | 22 | export default NotFoundPage; 23 | 24 | export const pageQuery = graphql` 25 | query { 26 | site { 27 | siteMetadata { 28 | title 29 | } 30 | } 31 | } 32 | `; 33 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { graphql } from "gatsby"; 3 | import Layout from "../components/Layout"; 4 | import SEO from "../components/SEO"; 5 | 6 | import ReactMarkdown from "react-markdown"; 7 | import Prism from "prismjs"; 8 | import "prismjs/components/prism-bash"; 9 | import "prismjs/components/prism-jsx"; 10 | import "prismjs/components/prism-typescript"; 11 | import "prismjs/components/prism-graphql"; 12 | import "prismjs/components/prism-tsx"; 13 | import "prismjs/components/prism-json"; 14 | import "prism-theme-night-owl"; 15 | // Use any from here: https://www.npmjs.com/package/prism-themes 16 | // import "prism-themes/themes/prism-vs.css"; 17 | import "./../styles/style.scss"; 18 | import { 19 | imageRenderer, 20 | headingRenderer, 21 | RootRenderer, 22 | SmallRootRenderer, 23 | } from "../utils/renderers"; 24 | import info from "../../bookInfo"; 25 | 26 | if (typeof window !== "undefined") { 27 | const queryString = window.location.search; 28 | const urlParams = new URLSearchParams(queryString); 29 | const theme = urlParams.get("theme"); 30 | 31 | if (theme === "dark") { 32 | document.getElementsByTagName("body")[0].classList += "dark"; 33 | } 34 | } 35 | 36 | function codeBlock({ value, language }) { 37 | if (!value) return null; 38 | 39 | const lang = language || "bash"; 40 | var html = Prism.highlight(value, Prism.languages[lang]); 41 | var cls = `lang-${lang}`; 42 | 43 | return ( 44 |
45 |       
46 |     
47 | ); 48 | } 49 | 50 | const Index = ({ data, location }) => { 51 | const siteTitle = data.site.siteMetadata.title; 52 | const markdownBook = data.markdownRemark; 53 | const renderers = 54 | process.env.GATSBY_SCRAPPER === "1" 55 | ? { 56 | heading: headingRenderer, 57 | root: SmallRootRenderer, 58 | image: imageRenderer, 59 | } 60 | : { 61 | code: codeBlock, 62 | heading: headingRenderer, 63 | root: RootRenderer, 64 | image: imageRenderer, 65 | }; 66 | 67 | return ( 68 | 69 | 70 | 74 | 75 | ); 76 | }; 77 | 78 | export default Index; 79 | 80 | export const pageQuery = graphql` 81 | query { 82 | site { 83 | siteMetadata { 84 | title 85 | } 86 | } 87 | markdownRemark { 88 | rawMarkdownBody 89 | } 90 | } 91 | `; 92 | -------------------------------------------------------------------------------- /src/styles/style.scss: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: sans-serif; 3 | -ms-text-size-adjust: 100%; 4 | -moz-text-size-adjust: 100%; 5 | -webkit-text-size-adjust: 100%; 6 | } 7 | 8 | body { 9 | margin: 0; 10 | } 11 | 12 | article, 13 | aside, 14 | details, 15 | figcaption, 16 | figure, 17 | footer, 18 | header, 19 | main, 20 | menu, 21 | nav, 22 | section, 23 | summary { 24 | display: block; 25 | } 26 | 27 | audio, 28 | canvas, 29 | progress, 30 | video { 31 | display: inline-block; 32 | } 33 | 34 | audio:not([controls]) { 35 | display: none; 36 | height: 0; 37 | } 38 | 39 | progress { 40 | vertical-align: baseline; 41 | } 42 | 43 | [hidden], 44 | template { 45 | display: none; 46 | } 47 | 48 | a { 49 | background-color: transparent; 50 | -webkit-text-decoration-skip: objects; 51 | } 52 | 53 | a:active, 54 | a:hover { 55 | outline-width: 0; 56 | } 57 | 58 | abbr[title] { 59 | border-bottom: none; 60 | text-decoration: underline; 61 | text-decoration: underline dotted; 62 | } 63 | 64 | b, 65 | strong { 66 | font-weight: inherit; 67 | font-weight: bolder; 68 | } 69 | 70 | dfn { 71 | font-style: italic; 72 | } 73 | 74 | h1 { 75 | font-size: 2em; 76 | margin: 0.67em 0; 77 | } 78 | 79 | mark { 80 | background-color: #ff0; 81 | color: #000; 82 | } 83 | 84 | small { 85 | font-size: 80%; 86 | } 87 | 88 | figcaption { 89 | color: rgba(0, 0, 0, 0.59); 90 | text-align: center; 91 | font-size: 80%; 92 | padding-top: 5px; 93 | } 94 | 95 | sub, 96 | sup { 97 | font-size: 75%; 98 | line-height: 0; 99 | position: relative; 100 | vertical-align: baseline; 101 | } 102 | 103 | sub { 104 | bottom: -0.25em; 105 | } 106 | 107 | sup { 108 | top: -0.5em; 109 | } 110 | 111 | img { 112 | border-style: none; 113 | } 114 | 115 | svg:not(:root) { 116 | overflow: hidden; 117 | } 118 | 119 | code, 120 | kbd, 121 | pre, 122 | samp { 123 | font-family: monospace, monospace; 124 | font-size: 1em; 125 | } 126 | 127 | figure { 128 | margin: 1em 40px; 129 | } 130 | 131 | hr { 132 | box-sizing: content-box; 133 | height: 0; 134 | overflow: visible; 135 | } 136 | 137 | button, 138 | input, 139 | optgroup, 140 | select, 141 | textarea { 142 | font: inherit; 143 | margin: 0; 144 | } 145 | 146 | optgroup { 147 | font-weight: 700; 148 | } 149 | 150 | button, 151 | input { 152 | overflow: visible; 153 | } 154 | 155 | button, 156 | select { 157 | text-transform: none; 158 | } 159 | 160 | [type="reset"], 161 | [type="submit"], 162 | button, 163 | html [type="button"] { 164 | -webkit-appearance: button; 165 | } 166 | 167 | [type="button"]::-moz-focus-inner, 168 | [type="reset"]::-moz-focus-inner, 169 | [type="submit"]::-moz-focus-inner, 170 | button::-moz-focus-inner { 171 | border-style: none; 172 | padding: 0; 173 | } 174 | 175 | [type="button"]:-moz-focusring, 176 | [type="reset"]:-moz-focusring, 177 | [type="submit"]:-moz-focusring, 178 | button:-moz-focusring { 179 | outline: 1px dotted ButtonText; 180 | } 181 | 182 | fieldset { 183 | border: 1px solid silver; 184 | margin: 0 2px; 185 | padding: 0.35em 0.625em 0.75em; 186 | } 187 | 188 | legend { 189 | box-sizing: border-box; 190 | color: inherit; 191 | display: table; 192 | max-width: 100%; 193 | padding: 0; 194 | white-space: normal; 195 | } 196 | 197 | textarea { 198 | overflow: auto; 199 | } 200 | 201 | [type="checkbox"], 202 | [type="radio"] { 203 | box-sizing: border-box; 204 | padding: 0; 205 | } 206 | 207 | [type="number"]::-webkit-inner-spin-button, 208 | [type="number"]::-webkit-outer-spin-button { 209 | height: auto; 210 | } 211 | 212 | [type="search"] { 213 | -webkit-appearance: textfield; 214 | outline-offset: -2px; 215 | } 216 | 217 | [type="search"]::-webkit-search-cancel-button, 218 | [type="search"]::-webkit-search-decoration { 219 | -webkit-appearance: none; 220 | } 221 | 222 | ::-webkit-input-placeholder { 223 | color: inherit; 224 | opacity: 0.54; 225 | } 226 | 227 | ::-webkit-file-upload-button { 228 | -webkit-appearance: button; 229 | font: inherit; 230 | } 231 | 232 | html { 233 | font: 100%/1.75 "Merriweather", "Georgia", serif; 234 | box-sizing: border-box; 235 | overflow-y: scroll; 236 | } 237 | 238 | * { 239 | box-sizing: inherit; 240 | } 241 | 242 | *:before { 243 | box-sizing: inherit; 244 | } 245 | 246 | *:after { 247 | box-sizing: inherit; 248 | } 249 | 250 | body { 251 | color: hsla(0, 0%, 0%, 0.9); 252 | font-family: "Merriweather", "Georgia", serif; 253 | font-weight: 400; 254 | word-wrap: break-word; 255 | font-kerning: normal; 256 | -moz-font-feature-settings: "kern", "liga", "clig", "calt"; 257 | -ms-font-feature-settings: "kern", "liga", "clig", "calt"; 258 | -webkit-font-feature-settings: "kern", "liga", "clig", "calt"; 259 | font-feature-settings: "kern", "liga", "clig", "calt"; 260 | } 261 | 262 | img { 263 | max-width: 100%; 264 | margin-left: 0; 265 | margin-right: 0; 266 | margin-top: 0; 267 | padding-bottom: 0; 268 | padding-left: 0; 269 | padding-right: 0; 270 | padding-top: 0; 271 | margin-bottom: 1.75rem; 272 | } 273 | 274 | h1 { 275 | margin-left: 0; 276 | margin-right: 0; 277 | margin-top: 0; 278 | padding-bottom: 0; 279 | padding-left: 0; 280 | padding-right: 0; 281 | padding-top: 0; 282 | margin-bottom: 1.75rem; 283 | color: inherit; 284 | font-family: Montserrat, sans-serif; 285 | font-weight: 900; 286 | text-rendering: optimizeLegibility; 287 | font-size: 2.5rem; 288 | line-height: 1.1; 289 | } 290 | 291 | h2 { 292 | margin-left: 0; 293 | margin-right: 0; 294 | margin-top: 0; 295 | padding-bottom: 0; 296 | padding-left: 0; 297 | padding-right: 0; 298 | padding-top: 0; 299 | margin-bottom: 1.75rem; 300 | color: inherit; 301 | font-family: "Merriweather", "Georgia", serif; 302 | font-weight: 900; 303 | text-rendering: optimizeLegibility; 304 | font-size: 1.73286rem; 305 | line-height: 1.1; 306 | } 307 | 308 | h3 { 309 | margin-left: 0; 310 | margin-right: 0; 311 | margin-top: 0; 312 | padding-bottom: 0; 313 | padding-left: 0; 314 | padding-right: 0; 315 | padding-top: 0; 316 | margin-bottom: 1.75rem; 317 | color: inherit; 318 | font-family: "Merriweather", "Georgia", serif; 319 | font-weight: 900; 320 | text-rendering: optimizeLegibility; 321 | font-size: 1.4427rem; 322 | line-height: 1.1; 323 | } 324 | 325 | h4 { 326 | margin-left: 0; 327 | margin-right: 0; 328 | margin-top: 0; 329 | padding-bottom: 0; 330 | padding-left: 0; 331 | padding-right: 0; 332 | padding-top: 0; 333 | margin-bottom: 1.75rem; 334 | color: inherit; 335 | font-family: "Merriweather", "Georgia", serif; 336 | font-weight: 900; 337 | text-rendering: optimizeLegibility; 338 | font-size: 1rem; 339 | line-height: 1.1; 340 | letter-spacing: 0.140625em; 341 | text-transform: uppercase; 342 | } 343 | 344 | h5 { 345 | margin-left: 0; 346 | margin-right: 0; 347 | margin-top: 0; 348 | padding-bottom: 0; 349 | padding-left: 0; 350 | padding-right: 0; 351 | padding-top: 0; 352 | margin-bottom: 1.75rem; 353 | color: inherit; 354 | font-family: "Merriweather", "Georgia", serif; 355 | font-weight: 900; 356 | text-rendering: optimizeLegibility; 357 | font-size: 0.83255rem; 358 | line-height: 1.1; 359 | } 360 | 361 | h6 { 362 | margin-left: 0; 363 | margin-right: 0; 364 | margin-top: 0; 365 | padding-bottom: 0; 366 | padding-left: 0; 367 | padding-right: 0; 368 | padding-top: 0; 369 | margin-bottom: 1.75rem; 370 | color: inherit; 371 | font-family: "Merriweather", "Georgia", serif; 372 | font-weight: 900; 373 | text-rendering: optimizeLegibility; 374 | font-size: 0.75966rem; 375 | line-height: 1.1; 376 | font-style: italic; 377 | } 378 | 379 | hgroup { 380 | margin-left: 0; 381 | margin-right: 0; 382 | margin-top: 0; 383 | padding-bottom: 0; 384 | padding-left: 0; 385 | padding-right: 0; 386 | padding-top: 0; 387 | margin-bottom: 1.75rem; 388 | } 389 | 390 | ul { 391 | margin-left: 1.75rem; 392 | margin-right: 0; 393 | margin-top: 0; 394 | padding-bottom: 0; 395 | padding-left: 0; 396 | padding-right: 0; 397 | padding-top: 0; 398 | margin-bottom: 1.75rem; 399 | list-style-position: outside; 400 | list-style-image: none; 401 | list-style: disc; 402 | } 403 | 404 | ol { 405 | margin-left: 1.75rem; 406 | margin-right: 0; 407 | margin-top: 0; 408 | padding-bottom: 0; 409 | padding-left: 0; 410 | padding-right: 0; 411 | padding-top: 0; 412 | margin-bottom: 1.75rem; 413 | list-style-position: outside; 414 | list-style-image: none; 415 | } 416 | 417 | dl { 418 | margin-left: 0; 419 | margin-right: 0; 420 | margin-top: 0; 421 | padding-bottom: 0; 422 | padding-left: 0; 423 | padding-right: 0; 424 | padding-top: 0; 425 | margin-bottom: 1.75rem; 426 | } 427 | 428 | dd { 429 | margin-left: 0; 430 | margin-right: 0; 431 | margin-top: 0; 432 | padding-bottom: 0; 433 | padding-left: 0; 434 | padding-right: 0; 435 | padding-top: 0; 436 | margin-bottom: 1.75rem; 437 | } 438 | 439 | p { 440 | margin-left: 0; 441 | margin-right: 0; 442 | margin-top: 0; 443 | padding-bottom: 0; 444 | padding-left: 0; 445 | padding-right: 0; 446 | padding-top: 0; 447 | margin-bottom: 1.75rem; 448 | } 449 | 450 | figure { 451 | margin-left: 0; 452 | margin-right: 0; 453 | margin-top: 0; 454 | padding-bottom: 0; 455 | padding-left: 0; 456 | padding-right: 0; 457 | padding-top: 0; 458 | margin-bottom: 1.75rem; 459 | } 460 | 461 | pre { 462 | margin-left: 0; 463 | margin-right: 0; 464 | margin-top: 0; 465 | padding-bottom: 0; 466 | padding-left: 0; 467 | padding-right: 0; 468 | padding-top: 0; 469 | margin-bottom: 1.75rem; 470 | font-size: 0.85rem; 471 | line-height: 1.75rem; 472 | } 473 | 474 | table { 475 | margin-left: 0; 476 | margin-right: 0; 477 | margin-top: 0; 478 | padding-bottom: 0; 479 | padding-left: 0; 480 | padding-right: 0; 481 | padding-top: 0; 482 | margin-bottom: 1.75rem; 483 | font-size: 1rem; 484 | line-height: 1.75rem; 485 | border-collapse: collapse; 486 | width: 100%; 487 | } 488 | 489 | fieldset { 490 | margin-left: 0; 491 | margin-right: 0; 492 | margin-top: 0; 493 | padding-bottom: 0; 494 | padding-left: 0; 495 | padding-right: 0; 496 | padding-top: 0; 497 | margin-bottom: 1.75rem; 498 | } 499 | 500 | blockquote { 501 | margin-left: -1.75rem; 502 | margin-right: 1.75rem; 503 | margin-top: 0; 504 | padding-bottom: 0; 505 | padding-left: 1.42188rem; 506 | padding-right: 0; 507 | padding-top: 0; 508 | margin-bottom: 1.75rem; 509 | font-size: 1.20112rem; 510 | line-height: 1.75rem; 511 | color: hsla(0, 0%, 0%, 0.59); 512 | font-style: italic; 513 | border-left: 0.32813rem solid hsla(0, 0%, 0%, 0.9); 514 | } 515 | 516 | form { 517 | margin-left: 0; 518 | margin-right: 0; 519 | margin-top: 0; 520 | padding-bottom: 0; 521 | padding-left: 0; 522 | padding-right: 0; 523 | padding-top: 0; 524 | margin-bottom: 1.75rem; 525 | } 526 | 527 | noscript { 528 | margin-left: 0; 529 | margin-right: 0; 530 | margin-top: 0; 531 | padding-bottom: 0; 532 | padding-left: 0; 533 | padding-right: 0; 534 | padding-top: 0; 535 | margin-bottom: 1.75rem; 536 | } 537 | 538 | iframe { 539 | margin-left: 0; 540 | margin-right: 0; 541 | margin-top: 0; 542 | padding-bottom: 0; 543 | padding-left: 0; 544 | padding-right: 0; 545 | padding-top: 0; 546 | margin-bottom: 1.75rem; 547 | } 548 | 549 | hr { 550 | margin-left: 0; 551 | margin-right: 0; 552 | margin-top: 0; 553 | padding-bottom: 0; 554 | padding-left: 0; 555 | padding-right: 0; 556 | padding-top: 0; 557 | margin-bottom: calc(1.75rem - 1px); 558 | background: hsla(0, 0%, 0%, 0.2); 559 | border: none; 560 | height: 1px; 561 | } 562 | 563 | address { 564 | margin-left: 0; 565 | margin-right: 0; 566 | margin-top: 0; 567 | padding-bottom: 0; 568 | padding-left: 0; 569 | padding-right: 0; 570 | padding-top: 0; 571 | margin-bottom: 1.75rem; 572 | } 573 | 574 | b { 575 | font-weight: 700; 576 | } 577 | 578 | strong { 579 | font-weight: 700; 580 | } 581 | 582 | dt { 583 | font-weight: 700; 584 | } 585 | 586 | th { 587 | font-weight: 700; 588 | } 589 | 590 | li { 591 | margin-bottom: calc(1.75rem / 2); 592 | } 593 | 594 | ol li { 595 | padding-left: 0; 596 | } 597 | 598 | ul li { 599 | padding-left: 0; 600 | } 601 | 602 | li > ol { 603 | margin-left: 1.75rem; 604 | margin-bottom: calc(1.75rem / 2); 605 | margin-top: calc(1.75rem / 2); 606 | } 607 | 608 | li > ul { 609 | margin-left: 1.75rem; 610 | margin-bottom: calc(1.75rem / 2); 611 | margin-top: calc(1.75rem / 2); 612 | } 613 | 614 | blockquote *:last-child { 615 | margin-bottom: 0; 616 | } 617 | 618 | li *:last-child { 619 | margin-bottom: 0; 620 | } 621 | 622 | p *:last-child { 623 | margin-bottom: 0; 624 | } 625 | 626 | li > p { 627 | margin-bottom: calc(1.75rem / 2); 628 | } 629 | 630 | code { 631 | font-size: 0.85rem; 632 | line-height: 1.75rem; 633 | } 634 | 635 | kbd { 636 | font-size: 0.85rem; 637 | line-height: 1.75rem; 638 | } 639 | 640 | samp { 641 | font-size: 0.85rem; 642 | line-height: 1.75rem; 643 | } 644 | 645 | abbr { 646 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 647 | cursor: help; 648 | } 649 | 650 | acronym { 651 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 652 | cursor: help; 653 | } 654 | 655 | abbr[title] { 656 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 657 | cursor: help; 658 | text-decoration: none; 659 | } 660 | 661 | thead { 662 | text-align: left; 663 | } 664 | 665 | td, 666 | th { 667 | text-align: left; 668 | border-bottom: 1px solid hsla(0, 0%, 0%, 0.12); 669 | font-feature-settings: "tnum"; 670 | -moz-font-feature-settings: "tnum"; 671 | -ms-font-feature-settings: "tnum"; 672 | -webkit-font-feature-settings: "tnum"; 673 | padding-left: 1.16667rem; 674 | padding-right: 1.16667rem; 675 | padding-top: 0.875rem; 676 | padding-bottom: calc(0.875rem - 1px); 677 | } 678 | 679 | th:first-child, 680 | td:first-child { 681 | padding-left: 0; 682 | } 683 | 684 | th:last-child, 685 | td:last-child { 686 | padding-right: 0; 687 | } 688 | 689 | blockquote > :last-child { 690 | margin-bottom: 0; 691 | } 692 | 693 | blockquote cite { 694 | font-size: 1rem; 695 | line-height: 1.75rem; 696 | color: hsla(0, 0%, 0%, 0.9); 697 | font-weight: 400; 698 | } 699 | 700 | blockquote cite:before { 701 | content: "— "; 702 | } 703 | 704 | ul, 705 | ol { 706 | margin-left: 0; 707 | } 708 | 709 | @media only screen and (max-width: 480px) { 710 | ul, 711 | ol { 712 | margin-left: 1.75rem; 713 | } 714 | 715 | blockquote { 716 | margin-left: -1.3125rem; 717 | margin-right: 0; 718 | padding-left: 0.98438rem; 719 | } 720 | } 721 | 722 | h1, 723 | h2, 724 | h3, 725 | h4, 726 | h5, 727 | h6 { 728 | margin-top: 3.5rem; 729 | } 730 | 731 | a { 732 | box-shadow: 0 1px 0 0 currentColor; 733 | color: #007acc; 734 | text-decoration: none; 735 | } 736 | 737 | a:hover, 738 | a:active { 739 | box-shadow: none; 740 | } 741 | 742 | mark, 743 | ins { 744 | background: #007acc; 745 | color: white; 746 | padding: 0.10938rem 0.21875rem; 747 | text-decoration: none; 748 | } 749 | 750 | a.gatsby-resp-image-link { 751 | box-shadow: none; 752 | } 753 | 754 | ul { 755 | margin-left: 1.75rem; 756 | } 757 | 758 | #table-of-contents { 759 | display: none; 760 | } 761 | 762 | img { 763 | display: block; 764 | margin: auto; 765 | max-width: 80%; 766 | /* filter: grayscale(1); */ 767 | } 768 | 769 | #table-of-contents + ul { 770 | margin: 0; 771 | } 772 | 773 | #table-of-contents + ul li, 774 | #table-of-contents + ul { 775 | list-style: none; 776 | } 777 | 778 | #table-of-contents + ul a { 779 | text-decoration: none; 780 | box-shadow: none; 781 | } 782 | 783 | a { 784 | color: #385b73; 785 | box-shadow: none; 786 | font-weight: 500; 787 | } 788 | 789 | aside { 790 | position: fixed; 791 | background: white; 792 | top: 0; 793 | left: 0; 794 | overflow: scroll; 795 | max-height: 100%; 796 | padding: 50px; 797 | padding-top: 100px; 798 | height: 100%; 799 | } 800 | 801 | aside li { 802 | list-style: none; 803 | } 804 | 805 | aside ul { 806 | display: none; 807 | } 808 | 809 | aside > ul { 810 | display: block; 811 | margin: 0; 812 | } 813 | 814 | aside > ul > li > ul { 815 | display: block; 816 | } 817 | 818 | .menu-toggle { 819 | outline: none; 820 | border: none; 821 | -webkit-user-select: none; 822 | -moz-user-select: none; 823 | cursor: pointer; 824 | position: fixed; 825 | top: 18px; 826 | left: 15px; 827 | width: 50px; 828 | height: 50px; 829 | border-radius: 50%; 830 | background: transparent; 831 | z-index: 3; 832 | } 833 | 834 | .background { 835 | position: fixed; 836 | top: 0; 837 | left: 0; 838 | bottom: 0; 839 | width: 300px; 840 | background: #fff; 841 | } 842 | 843 | .alert { 844 | padding: 10px; 845 | background: #1a1a1a; 846 | color: white; 847 | margin-bottom: 40px; 848 | font-family: Montserrat, sans-serif; 849 | } 850 | 851 | code, 852 | kbd, 853 | pre, 854 | samp { 855 | font-family: "Fira Code", monospace; 856 | font-size: 1em; 857 | } 858 | 859 | pre { 860 | margin-bottom: 1rem; 861 | padding: 0.5rem; 862 | } 863 | 864 | .wrapper { 865 | margin-left: auto; 866 | margin-right: auto; 867 | max-width: 42rem; 868 | padding: 2.625rem 1.3125rem; 869 | } 870 | 871 | .title { 872 | font-size: 3.95285rem; 873 | line-height: 4.375rem; 874 | margin-bottom: 2.625rem; 875 | margin-top: 0; 876 | } 877 | 878 | blockquote { 879 | margin-left: 0; 880 | border-left: 0.32813rem solid hsla(0, 0%, 0%, 0.25); 881 | } 882 | 883 | :not(pre) > code { 884 | font-size: 0.95em; 885 | color: white !important; 886 | background: hsl(207deg, 35%, 35%) !important; 887 | padding: 0.25ch 0.5ch; 888 | border-radius: 2px; 889 | } 890 | 891 | header + main > ul { 892 | display: none; 893 | } 894 | 895 | html * { 896 | -webkit-print-color-adjust: exact; 897 | } 898 | 899 | ul { 900 | margin-left: 1.75rem; 901 | } 902 | 903 | #table-of-contents { 904 | display: none; 905 | 906 | & + ul { 907 | margin: 0; 908 | 909 | a { 910 | text-decoration: none; 911 | box-shadow: none; 912 | } 913 | } 914 | 915 | & + ul li, 916 | & + ul { 917 | list-style: none; 918 | } 919 | } 920 | 921 | img { 922 | display: block; 923 | margin: auto; 924 | max-width: 80%; 925 | } 926 | 927 | a { 928 | color: #385b73; 929 | box-shadow: none; 930 | font-weight: 500; 931 | } 932 | 933 | aside { 934 | position: fixed; 935 | background: white; 936 | top: 0; 937 | left: 0; 938 | overflow: scroll; 939 | max-height: 100%; 940 | padding: 50px; 941 | padding-top: 100px; 942 | 943 | li { 944 | list-style: none; 945 | } 946 | 947 | ul { 948 | display: none; 949 | } 950 | 951 | > ul { 952 | display: block; 953 | margin: 0; 954 | 955 | > li > ul { 956 | display: block; 957 | } 958 | } 959 | } 960 | 961 | .menu-toggle { 962 | outline: none; 963 | border: none; 964 | -webkit-user-select: none; 965 | -moz-user-select: none; 966 | -ms-user-select: none; 967 | cursor: pointer; 968 | position: fixed; 969 | top: 18px; 970 | left: 15px; 971 | width: 50px; 972 | height: 50px; 973 | border-radius: 50%; 974 | background: transparent; 975 | z-index: 3; 976 | } 977 | 978 | .background { 979 | position: fixed; 980 | top: 0; 981 | left: 0; 982 | bottom: 0; 983 | width: 300px; 984 | background: #fff; 985 | } 986 | 987 | .alert { 988 | padding: 10px; 989 | background: #1a1a1a; 990 | color: white; 991 | margin-bottom: 40px; 992 | font-family: Montserrat, sans-serif; 993 | } 994 | 995 | code, 996 | kbd, 997 | pre, 998 | samp { 999 | font-family: "mono_lisaregular", monospace; 1000 | font-size: 1em; 1001 | white-space: pre-wrap; 1002 | } 1003 | 1004 | @media screen { 1005 | .toc { 1006 | display: none; 1007 | } 1008 | } 1009 | 1010 | @media print { 1011 | h1 { 1012 | page-break-before: always; 1013 | } 1014 | 1015 | .menu-toggle, 1016 | .background, 1017 | .title, 1018 | .alert { 1019 | display: none; 1020 | } 1021 | 1022 | .toc { 1023 | margin-bottom: 60px; 1024 | } 1025 | 1026 | .toc a { 1027 | color: hsla(0, 0%, 0%, 0.9); 1028 | font-size: 18px; 1029 | text-decoration: underline; 1030 | } 1031 | 1032 | .toc:before { 1033 | content: "Table of Contents"; 1034 | margin-bottom: 1.75rem; 1035 | display: block; 1036 | color: inherit; 1037 | font-family: Montserrat, sans-serif; 1038 | font-weight: 900; 1039 | text-rendering: optimizeLegibility; 1040 | font-size: 2.5rem; 1041 | line-height: 1.1; 1042 | } 1043 | 1044 | .toc > ul { 1045 | list-style-type: none; 1046 | } 1047 | 1048 | .toc { 1049 | page-break-inside: avoid; 1050 | } 1051 | } 1052 | 1053 | .dark { 1054 | color: rgb(245, 250, 255); 1055 | background: rgb(32, 40, 51); 1056 | 1057 | .toc a { 1058 | color: rgb(245, 250, 255); 1059 | } 1060 | 1061 | a { 1062 | color: rgb(102, 184, 255); 1063 | } 1064 | 1065 | figcaption { 1066 | color: #fbededcc; 1067 | } 1068 | 1069 | blockquote { 1070 | color: #fbededcc; 1071 | border-left: 0.32813rem solid #fbededcc; 1072 | } 1073 | 1074 | :not(pre) > code { 1075 | background: #0b1e3c !important; 1076 | } 1077 | } 1078 | -------------------------------------------------------------------------------- /src/utils/renderers.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { motion } from "framer-motion"; 3 | import ReactMarkdown from "react-markdown"; 4 | import { MenuToggle } from "../components/MenuToggle"; 5 | 6 | const sidebar = { 7 | open: (height = 1000) => ({ 8 | clipPath: `circle(${height * 2 + 200}px at 40px 40px)`, 9 | transition: { 10 | type: "spring", 11 | stiffness: 20, 12 | restDelta: 2, 13 | }, 14 | }), 15 | closed: { 16 | clipPath: "circle(30px at 40px 40px)", 17 | transition: { 18 | type: "spring", 19 | stiffness: 400, 20 | damping: 40, 21 | }, 22 | }, 23 | }; 24 | 25 | const flatten = (text, child) => { 26 | return typeof child === "string" 27 | ? text + child 28 | : React.Children.toArray(child.props.children).reduce(flatten, text); 29 | }; 30 | 31 | export const headingRenderer = ({ children: reactChildren, level }) => { 32 | var children = React.Children.toArray(reactChildren); 33 | var text = children.reduce(flatten, ""); 34 | var slug = text.toLowerCase().replace(/\W/g, "-"); 35 | return React.createElement("h" + level, { id: slug }, children); 36 | }; 37 | 38 | export const imageRenderer = (props) => { 39 | return ( 40 |
41 | {props.alt} 42 |
{props.alt}
43 |
44 | ); 45 | }; 46 | 47 | export const RootRenderer = ({ children }) => { 48 | const [open, setOpen] = useState(false); 49 | const TOCLines = children.reduce((acc, { key, props }) => { 50 | // Skip non-headings 51 | if (key.indexOf("heading") !== 0) { 52 | return acc; 53 | } 54 | let indent = ""; 55 | for (let idx = 1; idx < props.level; idx++) { 56 | indent = `${indent} `; 57 | } 58 | const value = props.children[0].props.value; 59 | const link = value.toLowerCase().replace(/\W/g, "-"); 60 | return acc.concat([`${indent}* [${value}](#${link})`]); 61 | }, []); 62 | 63 | const variants = { 64 | open: { opacity: 1, x: 0 }, 65 | closed: { opacity: 0, x: "-100%" }, 66 | }; 67 | 68 | return ( 69 | 70 | 75 | setOpen((o) => !o)} /> 76 | setOpen(false)} 81 | > 82 | 83 | 84 |
85 | 86 |
87 |
{children}
88 |
89 | ); 90 | }; 91 | 92 | export const SmallRootRenderer = ({ children }) => { 93 | const TOCLines = children.reduce((acc, { key, props }) => { 94 | // Skip non-headings 95 | if (key.indexOf("heading") !== 0) { 96 | return acc; 97 | } 98 | let indent = ""; 99 | for (let idx = 1; idx < props.level; idx++) { 100 | indent = `${indent} `; 101 | } 102 | const value = props.children[0].props.value; 103 | const link = value.toLowerCase().replace(/\W/g, "-"); 104 | return acc.concat([`${indent}* [${value}](#${link})`]); 105 | }, []); 106 | 107 | return ( 108 | <> 109 | 110 |
{children}
111 | 112 | ); 113 | }; 114 | -------------------------------------------------------------------------------- /static/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaraVieira/starter-book/60e67d103e33205c956ed4a218f3b900710c3e58/static/cover.png -------------------------------------------------------------------------------- /static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | --------------------------------------------------------------------------------