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