├── .gitignore ├── LICENSE ├── README.md ├── example ├── .env.development ├── .env.production ├── README.md ├── gatsby-config.js ├── package.json ├── setup.js ├── src │ └── @draftbox-co │ │ └── gatsby-theme-ghost-attila │ │ └── components │ │ ├── disqus.jsx │ │ └── fb-comments.jsx └── static │ ├── alternateLogo.svg │ ├── cover.png │ ├── facebookImage.png │ ├── favicon.ico │ ├── favicon.png │ ├── images │ └── logo.svg │ ├── logo.svg │ ├── robots.txt │ └── twitterImage.png ├── gatsby-theme-ghost-attila ├── README.md ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── package.json ├── plugins │ └── gatsby-plugin-ghost-manifest │ │ ├── .babelrc │ │ ├── common.js │ │ ├── gatsby-node.js │ │ ├── gatsby-ssr.js │ │ ├── index.js │ │ ├── package.json │ │ ├── src │ │ ├── common.js │ │ ├── gatsby-node.js │ │ └── gatsby-ssr.js │ │ └── yarn.lock ├── src │ ├── amp-styles │ │ └── post.amp.css │ ├── components │ │ ├── Layout.jsx │ │ ├── contact-form.jsx │ │ ├── copy-link.jsx │ │ ├── disqus.jsx │ │ ├── fb-comments.jsx │ │ ├── footer.jsx │ │ ├── header.jsx │ │ ├── meta │ │ │ ├── ArticleMeta.js │ │ │ ├── AuthorMeta.js │ │ │ ├── ContactMeta.js │ │ │ ├── ImageMeta.js │ │ │ ├── MetaData.js │ │ │ ├── WebsiteMeta.js │ │ │ ├── getAuthorProperties.js │ │ │ └── index.js │ │ ├── navbar.jsx │ │ ├── pagination.jsx │ │ ├── post-card.jsx │ │ └── subscribe-form.jsx │ ├── context │ │ └── form-context.jsx │ ├── hook │ │ └── useForm.jsx │ ├── images │ │ ├── copied.svg │ │ ├── copy.svg │ │ ├── ghost-icon.png │ │ ├── publication-cover.png │ │ └── rss.svg │ ├── pages │ │ ├── 404.jsx │ │ ├── contact.jsx │ │ └── offline.jsx │ ├── styles │ │ ├── font │ │ │ ├── icons.eot │ │ │ ├── icons.svg │ │ │ ├── icons.ttf │ │ │ ├── icons.woff │ │ │ └── icons.woff2 │ │ ├── prism-theme │ │ │ └── prism_dracula.scss │ │ └── sass │ │ │ ├── _colors.scss │ │ │ ├── _fonts.scss │ │ │ ├── _highlight.scss │ │ │ ├── _icons.scss │ │ │ ├── _normalize.scss │ │ │ ├── _utilities.scss │ │ │ └── style.scss │ ├── templates │ │ ├── authorTemplate.jsx │ │ ├── indexTemplate.jsx │ │ ├── pageTemplate.jsx │ │ ├── postTemplate.amp.jsx │ │ ├── postTemplate.jsx │ │ └── tagsTemplate.jsx │ └── utils │ │ ├── .ghost.json │ │ ├── fragments.js │ │ ├── rss │ │ └── generate-feed.js │ │ └── siteConfig.js └── static │ ├── alternateLogo.svg │ ├── cover.png │ ├── coverImage.png │ ├── facebookImage.png │ ├── favicon.ico │ ├── favicon.png │ ├── fbCardImage.png │ ├── images │ └── logo.svg │ ├── logo.svg │ ├── robots.txt │ ├── sw.js │ ├── twitterCardImage.png │ └── twitterImage.png ├── package.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | public 3 | node_modules 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Draftbox 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 | [![Draftbox](https://res.cloudinary.com/thinkcdnimages/image/upload/v1589291053/Draftbox/draftbox-for-github.svg)](https://draftbox.co) 2 | 3 | # gatsby-attila-theme-ghost 4 | 5 | A Gatsby theme plugin for creating blogs from headless Ghost CMS. 6 | 7 | Turn your Ghost blog into a lightning fast static website. This Gatsby theme is a frontend replacement of the Ghost handlebars engine featuring the standard Ghost Casper skin and functionality. All content is sourced from a headless Ghost CMS. 8 | 9 | > This theme is being used at [Draftbox](https://draftbox.co). Get lightning fast, secure front-end for your WordPress or Ghost blog, in 5 minutes or less, without coding. For our fellow devs, we also provide code export feature. 10 | 11 | ## Demo 12 | 13 | Play with the [Demo](https://ghost-attila-preview.draftbox.co/) to get a first impression. 14 | 15 | 16 | ## Features 17 | 18 | - Ghost Attila skin 19 | - SEO optimized 20 | - Fully responsive 21 | - Gatsby images 22 | - Styled 404 page 23 | - RSS Feed 24 | - AMP Pages 25 | - Sitemap 26 | - Contact Form 27 | - Subscribe Form 28 | - Social Sharing 29 | - Composable and extensible 30 | 31 | 32 | ## Quick Start 33 | 34 | Head over to the [starter repo](https://github.com/draftbox-co/gatsby-attila-theme-starter) to get up and running quickly! The starter is recommended if you are creating a new site. 35 | 36 | 37 | ## Installation 38 | 39 | This repository contains the example code **and** the Gatsby theme. If you are here to install the Gatsby theme plugin in your existing project, check out the [theme specific README](/gatsby-attila-theme-ghost/README.md) for further details. 40 | 41 | In case you want to work with this repository (for local development, pull requests, etc.): 42 | 43 | 1. Clone or fork this repository: 44 | ```bash 45 | git clone https://github.com/draftbox-co/gatsby-attila-theme-ghost.git 46 | cd gatsby-attila-theme-ghost 47 | ``` 48 | 49 | 2. Run `yarn` to install dependencies. 50 | 51 | 3. Run `yarn workspace example develop` to start the example locally. 52 | 53 | 54 | ## Authors 55 | - Arun Priyadarshi ([@Gunnerforlife](https://github.com/Gunnerforlife)) – [Draftbox](https://draftbox.co) 56 | - Keyur Raval ([@thandaanda](https://github.com/thandaanda)) – [Draftbox](https://draftbox.co) 57 | - Shyam Lohar ([@shyamlohar](https://github.com/shyamlohar)) – [Draftbox](https://draftbox.co) 58 | - Tanmay Desai ([@tanmaydesai89](https://github.com/tanmaydesai89)) – [Draftbox](https://draftbox.co) 59 | 60 | ## Contributions 61 | PRs are welcome! Consider contributing to this project if you are missing feature that is also useful for others. 62 | 63 | ## Credits 64 | Inspired from [gatsby-theme-try-ghost](https://github.com/styxlab/gatsby-theme-try-ghost) 65 | 66 | Theme Ported from [Attila](https://github.com/zutrinken/attila) 67 | 68 | Special Thanks to [Ghost](https://ghost.org) 69 | 70 | # Copyright & License 71 | 72 | Copyright (c) 2020 [Draftbox](https://draftbox.co) - Released under the [MIT license](LICENSE). -------------------------------------------------------------------------------- /example/.env.development: -------------------------------------------------------------------------------- 1 | FORM_URL=http://localhost:3000/api/project/5e81b8923d8c2d64d8549add/forms 2 | GATSBY_DISQUS_SHORTNAME= 3 | GATSBY_FB_APP_ID= 4 | -------------------------------------------------------------------------------- /example/.env.production: -------------------------------------------------------------------------------- 1 | FORM_URL=http://localhost:3000/api/project/5e81b8923d8c2d64d8549add/forms 2 | DISQUS_SHORTNAME= 3 | FB_APP_ID= 4 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Gatsby Theme Minimal Example 2 | 3 | > See the [base README](https://github.com/draftbox-co/gatsby-attila-theme-ghost/blob/master/README.md) for instructions on how to use this example. 4 | 5 | # Copyright & License 6 | 7 | Copyright (c) 2020 Draftbox - Released under the [MIT license](LICENSE). -------------------------------------------------------------------------------- /example/gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | { 4 | resolve: `@draftbox-co/gatsby-theme-ghost-attila`, 5 | options: { 6 | siteConfig: { 7 | siteUrl: "https://ghost-attila-preview.draftbox.co", 8 | postsPerPage: 10, 9 | siteTitleMeta: "Built with Draftbox", 10 | siteDescriptionMeta: 11 | "Lightning fast, secure front-end for your WordPress or Ghost blog, without coding.", 12 | shareImageWidth: 1000, 13 | shareImageHeight: 523, 14 | shortTitle: "Built with Draftbox", 15 | siteIcon: "favicon.png", 16 | backgroundColor: "#e9e9e9", 17 | themeColor: "#15171A", 18 | apiUrl: "https://ghost.theasdfghjkl.com", 19 | subscribeWidget: { 20 | visible: true, 21 | title: "Subscribe to Built with Draftbox", 22 | helpText: "Get the latest posts delivered right to your inbox.", 23 | successMessage: "Thanks for subscribing to Built with Draftbox.", 24 | }, 25 | header: { 26 | navigation: [ 27 | { 28 | label: "Home", 29 | url: "https://ghost-attila-preview.draftbox.co/", 30 | }, 31 | { 32 | label: "Contact", 33 | url: "https://ghost-attila-preview.draftbox.co/contact", 34 | }, 35 | ], 36 | }, 37 | footer: { 38 | copyright: "Built with Draftbox", 39 | navigation: [ 40 | { 41 | label: "Home", 42 | url: "https://ghost-attila-preview.draftbox.co/", 43 | }, 44 | { 45 | label: "Sitemap", 46 | url: "https://ghost-attila-preview.draftbox.co/sitemap.xml", 47 | }, 48 | { 49 | label: "RSS", 50 | url: "https://ghost-attila-preview.draftbox.co/rss.xml", 51 | }, 52 | { 53 | label: "Contact", 54 | url: "https://ghost-attila-preview.draftbox.co/contact", 55 | }, 56 | ], 57 | }, 58 | socialLinks: { 59 | twitter: "https://twitter.com/@DraftboxHQ", 60 | facebook: "", 61 | instagram: "", 62 | linkedin: "", 63 | github: "", 64 | whatsapp: "", 65 | pinterest: "", 66 | youtube: "", 67 | dribbble: "", 68 | behance: "", 69 | externalLink: "", 70 | }, 71 | contactWidget: { 72 | title: "Contact Built with Draftbox", 73 | successMessage: "We’ll get in touch with you soon.", 74 | }, 75 | metadata: { 76 | title: "Built with Draftbox", 77 | description: 78 | "Lightning fast, secure front-end for your WordPress or Ghost blog, without coding. Draftbox is a new-age blogging platform for everyone, built on Gatsby.", 79 | }, 80 | twitterCard: { 81 | title: "Built with Draftbox", 82 | description: 83 | "Lightning fast, secure front-end for your WordPress or Ghost blog, without coding. Draftbox is a new-age blogging platform for everyone, built on Gatsby.", 84 | imageUrl: "twitterImage.png", 85 | username: "@DraftboxHQ", 86 | }, 87 | facebookCard: { 88 | title: "Built with Draftbox", 89 | description: 90 | "Lightning fast, secure front-end for your WordPress or Ghost blog, without coding. Draftbox is a new-age blogging platform for everyone, built on Gatsby.", 91 | imageUrl: "facebookImage.png", 92 | appId: "", 93 | width: 1000, 94 | height: 523, 95 | }, 96 | siteTitle: "Built with Draftbox", 97 | siteDescription: 98 | "Lightning fast, secure front-end for your WordPress or Ghost blog, without coding.", 99 | language: "en", 100 | logoUrl: "logo.svg", 101 | iconUrl: 102 | "https://ghost.theasdfghjkl.com/content/images/2020/05/draftbox-colored-icon.png", 103 | coverUrl: "cover.png", 104 | alternateLogoUrl: "alternateLogo.svg", 105 | themeConfig: { 106 | variables: [ 107 | { 108 | varName: "--primary-color", 109 | value: "#e05431", 110 | }, 111 | { 112 | varName: "--primary-color-active", 113 | value: "#d13f21", 114 | }, 115 | { 116 | varName: "--cardo-font", 117 | value: "Cardo", 118 | }, 119 | { 120 | varName: "--cardo-font-normal", 121 | value: "400", 122 | }, 123 | { 124 | varName: "--fira-sans-font", 125 | value: "Fira Sans", 126 | }, 127 | { 128 | varName: "--fira-sans-font-normal", 129 | value: "400", 130 | }, 131 | { 132 | varName: "--fira-sans-font-semibold", 133 | value: "600", 134 | }, 135 | { 136 | varName: "--fira-sans-font-bold", 137 | value: "700", 138 | }, 139 | ], 140 | fonts: [ 141 | { 142 | family: "Cardo", 143 | variants: ["400", "400i", "700"], 144 | fontDisplay: "swap", 145 | strategy: "selfHosted", 146 | }, 147 | { 148 | family: "Fira Sans", 149 | variants: ["400", "500", "700"], 150 | fontDisplay: "swap", 151 | strategy: "selfHosted", 152 | }, 153 | ], 154 | }, 155 | }, 156 | ghostConfig: { 157 | development: { 158 | apiUrl: "https://ghost.theasdfghjkl.com", 159 | contentApiKey: "3d17fad3efaa911df1ed577638", 160 | version: "v3", 161 | }, 162 | production: { 163 | apiUrl: "https://ghost.theasdfghjkl.com", 164 | contentApiKey: "3d17fad3efaa911df1ed577638", 165 | version: "v3", 166 | }, 167 | }, 168 | }, 169 | }, 170 | ], 171 | }; 172 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Draftbox", 6 | "license": "MIT", 7 | "scripts": { 8 | "develop": "gatsby develop", 9 | "build": "gatsby build", 10 | "serve": "gatsby serve" 11 | }, 12 | "dependencies": { 13 | "@draftbox-co/gatsby-theme-ghost-attila": "^1.0.0", 14 | "disqus-react": "^1.0.7", 15 | "gatsby": "2.20.24", 16 | "node-fetch": "^2.6.0", 17 | "react": "^16.12.0", 18 | "react-dom": "^16.12.0", 19 | "react-facebook": "^8.1.4" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example/setup.js: -------------------------------------------------------------------------------- 1 | (async () => { 2 | const gatsbyConfig = require("./gatsby-config"); 3 | const fetch = require("node-fetch"); 4 | const fs = require("fs"); 5 | const path = require("path"); 6 | 7 | if (gatsbyConfig.plugins.length) { 8 | const index = gatsbyConfig.plugins.findIndex( 9 | (obj) => obj.resolve === "@draftbox-co/gatsby-theme-ghost-attila" 10 | ); 11 | console.log(index); 12 | if (index > -1) { 13 | const pluginOptions = gatsbyConfig.plugins[index]; 14 | if (pluginOptions.options.ghostConfig) { 15 | console.log(process.argv[2]); 16 | const environment = process.argv[2] || "development"; 17 | let config = { 18 | apiUrl: "", 19 | contentApiKey: "", 20 | version: "", 21 | }; 22 | if (environment === "development") { 23 | config = Object.assign( 24 | config, 25 | pluginOptions.options.ghostConfig.development 26 | ); 27 | } 28 | if (environment === "production") { 29 | config = Object.assign( 30 | config, 31 | pluginOptions.options.ghostConfig.production 32 | ); 33 | } 34 | try { 35 | const apiUrl = `${config.apiUrl}/ghost/api/${config.version}/content/settings/?key=${config.contentApiKey}`; 36 | console.log(apiUrl); 37 | const resp = await fetch(apiUrl); 38 | console.log(resp.status); 39 | if (resp.status === 200) { 40 | const finalRes = await resp.json(); 41 | console.log(finalRes); 42 | const siteData = finalRes.settings; 43 | const masterConfig = { 44 | subscribeWidget: { 45 | title: "", 46 | helpText: "", 47 | successMessage: "", 48 | visible: true, 49 | }, 50 | footer: { copyright: "", navigation: [{ label: "", url: "" }] }, 51 | header: { navigation: [{ label: "", url: "" }] }, 52 | socialLinks: { 53 | twitter: "", 54 | facebook: "", 55 | instagram: "", 56 | linkedin: "", 57 | github: "", 58 | pinterest: "", 59 | youtube: "", 60 | dribbble: "", 61 | behance: "", 62 | externalLink: "", 63 | }, 64 | contactWidget: { title: "", successMessage: "" }, 65 | identity: { 66 | siteTitle: "", 67 | siteDescription: "", 68 | language: "en", 69 | postsPerPage: 10, 70 | logoUrl: "", 71 | iconUrl: "", 72 | coverUrl: "", 73 | alternateLogoUrl: "", 74 | }, 75 | metadata: { title: "", description: "" }, 76 | twitterCard: { 77 | title: "", 78 | description: "", 79 | imageUrl: "", 80 | username: "", 81 | }, 82 | facebookCard: { 83 | title: "", 84 | description: "", 85 | imageUrl: "", 86 | appId: "", 87 | width: 1000, 88 | height: 523, 89 | }, 90 | }; 91 | const newSiteConfig = Object.assign({}, masterConfig); 92 | console.log( 93 | path.resolve( 94 | __dirname, 95 | "..", 96 | `gatsby-theme-ghost-attila`, 97 | "static" 98 | ) 99 | ); 100 | if (siteData.title) { 101 | newSiteConfig.subscribeWidget.title = `Subscribe to ${siteData.title}`; 102 | newSiteConfig.subscribeWidget.successMessage = `Thanks for subscribing to ${siteData.title}.`; 103 | newSiteConfig.subscribeWidget.helpText = 104 | "Get the latest posts delivered right to your inbox."; 105 | newSiteConfig.footer.copyright = siteData.title; 106 | newSiteConfig.contactWidget.title = `Contact ${siteData.title}`; 107 | newSiteConfig.contactWidget.successMessage = `We'll get in touch with you soon.`; 108 | newSiteConfig.identity.siteTitle = siteData.title || ""; 109 | newSiteConfig.identity.siteDescription = 110 | siteData.description || ""; 111 | newSiteConfig.identity.logoUrl = siteData.logo || ""; 112 | newSiteConfig.identity.iconUrl = siteData.icon || ""; 113 | newSiteConfig.identity.coverUrl = siteData.cover_image || ""; 114 | newSiteConfig.identity.alternateLogoUrl = siteData.logo || ""; 115 | newSiteConfig.metadata.title = 116 | siteData.meta_title || siteData.title || ""; 117 | newSiteConfig.metadata.description = 118 | siteData.meta_description || siteData.description || ""; 119 | newSiteConfig.twitterCard.title = 120 | siteData.twitter_title || siteData.title || ""; 121 | newSiteConfig.twitterCard.description = 122 | siteData.twitter_description || siteData.description || ""; 123 | newSiteConfig.twitterCard.imageUrl = 124 | siteData.twitter_image || siteData.cover_image || ""; 125 | newSiteConfig.facebookCard.title = 126 | siteData.og_title || siteData.title || ""; 127 | newSiteConfig.facebookCard.description = 128 | siteData.og_description || siteData.description || ""; 129 | newSiteConfig.facebookCard.imageUrl = 130 | siteData.og_image || siteData.cover_image || ""; 131 | 132 | const imageJSON = { "": "" }; 133 | if (siteData.logo && siteData.logo !== "") { 134 | const resp = await fetch(siteData.logo); 135 | if (resp.status == 200) { 136 | const bufferData = await resp.buffer(); 137 | const fileName = `logo${path.extname(siteData.logo)}`; 138 | fs.writeFileSync( 139 | path.resolve( 140 | __dirname, 141 | "..", 142 | `gatsby-theme-ghost-attila`, 143 | "static", 144 | fileName 145 | ), 146 | bufferData 147 | ); 148 | imageJSON[siteData.logo] = fileName; 149 | } else { 150 | imageJSON[siteData.logo] = ""; 151 | } 152 | } 153 | 154 | if (siteData.cover_image && siteData.cover_image !== "") { 155 | const resp = await fetch(siteData.cover_image); 156 | if (resp.status == 200) { 157 | const bufferData = await resp.buffer(); 158 | const fileName = `coverImage${path.extname( 159 | siteData.cover_image 160 | )}`; 161 | fs.writeFileSync( 162 | path.resolve( 163 | __dirname, 164 | "..", 165 | `gatsby-theme-ghost-attila`, 166 | "static", 167 | fileName 168 | ), 169 | bufferData 170 | ); 171 | imageJSON[siteData.cover_image] = fileName; 172 | } else { 173 | imageJSON[siteData.cover_image] = ""; 174 | } 175 | } 176 | 177 | if (siteData.twitter_image && siteData.twitter_image !== "") { 178 | const resp = await fetch(siteData.twitter_image); 179 | if (resp.status == 200) { 180 | const bufferData = await resp.buffer(); 181 | const fileName = `twitterCardImage${path.extname( 182 | siteData.twitter_image 183 | )}`; 184 | fs.writeFileSync( 185 | path.resolve( 186 | __dirname, 187 | "..", 188 | `gatsby-theme-ghost-attila`, 189 | "static", 190 | fileName 191 | ), 192 | bufferData 193 | ); 194 | imageJSON[siteData.twitter_image] = fileName; 195 | } else { 196 | imageJSON[siteData.twitter_image] = ""; 197 | } 198 | } 199 | 200 | if (siteData.og_image && siteData.og_image !== "") { 201 | const resp = await fetch(siteData.og_image); 202 | if (resp.status == 200) { 203 | const bufferData = await resp.buffer(); 204 | const fileName = `fbCardImage${path.extname( 205 | siteData.og_image 206 | )}`; 207 | fs.writeFileSync( 208 | path.resolve( 209 | __dirname, 210 | "..", 211 | `gatsby-theme-ghost-attila`, 212 | "static", 213 | fileName 214 | ), 215 | bufferData 216 | ); 217 | imageJSON[siteData.og_image] = fileName; 218 | } else { 219 | imageJSON[siteData.og_image] = ""; 220 | } 221 | } 222 | console.log(imageJSON); 223 | newSiteConfig.identity.logoUrl = 224 | imageJSON[newSiteConfig.identity.logoUrl]; 225 | newSiteConfig.identity.coverUrl = 226 | imageJSON[newSiteConfig.identity.coverUrl]; 227 | newSiteConfig.identity.alternateLogoUrl = 228 | imageJSON[newSiteConfig.identity.alternateLogoUrl]; 229 | newSiteConfig.twitterCard.imageUrl = 230 | imageJSON[newSiteConfig.twitterCard.imageUrl]; 231 | newSiteConfig.facebookCard.imageUrl = 232 | imageJSON[newSiteConfig.facebookCard.imageUrl]; 233 | const finalConfig = Object.assign( 234 | {}, 235 | pluginOptions.options.siteConfig, 236 | siteConfig, 237 | { 238 | metadata: newSiteConfig.metadata, 239 | twitterCard: newSiteConfig.twitterCard, 240 | facebookCard: newSiteConfig.facebookCard, 241 | }, 242 | newSiteConfig.identity 243 | ); 244 | console.log("new json", JSON.stringify(finalConfig, null, 2)); 245 | pluginOptions.options.siteConfig = finalConfig; 246 | gatsbyConfig.plugins[index] = pluginOptions; 247 | fs.writeFileSync( 248 | path.join(__dirname, "gatsby-config.js"), 249 | `module.exports = ${JSON.stringify(gatsbyConfig, null, 2)}` 250 | ); 251 | console.log("done"); 252 | } 253 | } else { 254 | console.error("Config is invalid"); 255 | } 256 | } catch (err) { 257 | console.log(err); 258 | console.error("Config is invalid"); 259 | } 260 | } else { 261 | console.error("Theme plugin option doesn't exists"); 262 | } 263 | } else { 264 | console.error("Theme plugin doesn't exists"); 265 | } 266 | } else { 267 | console.error("Empty plugin array"); 268 | } 269 | })(); 270 | -------------------------------------------------------------------------------- /example/src/@draftbox-co/gatsby-theme-ghost-attila/components/disqus.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { DiscussionEmbed } from "disqus-react"; 3 | 4 | const Disqus = props => { 5 | const disqusConfig = { 6 | shortname: process.env.GATSBY_DISQUS_SHORTNAME, 7 | identifier: props.slug, 8 | title: props.slug 9 | }; 10 | return process.env.GATSBY_DISQUS_SHORTNAME ? ( 11 | 12 | ) : ( 13 | <> 14 | ); 15 | }; 16 | 17 | export default Disqus; 18 | -------------------------------------------------------------------------------- /example/src/@draftbox-co/gatsby-theme-ghost-attila/components/fb-comments.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { FacebookProvider, Comments } from 'react-facebook'; 3 | 4 | const FbComments = props => { 5 | return process.env.GATSBY_FB_APP_ID ? ( 6 | 7 | 8 | 9 | ) : ( 10 | <> 11 | ); 12 | }; 13 | 14 | export default FbComments; 15 | -------------------------------------------------------------------------------- /example/static/alternateLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /example/static/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/draftbox-co/gatsby-attila-theme-ghost/e74f112be4994e19d842d0797b878d2b4f32dbea/example/static/cover.png -------------------------------------------------------------------------------- /example/static/facebookImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/draftbox-co/gatsby-attila-theme-ghost/e74f112be4994e19d842d0797b878d2b4f32dbea/example/static/facebookImage.png -------------------------------------------------------------------------------- /example/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/draftbox-co/gatsby-attila-theme-ghost/e74f112be4994e19d842d0797b878d2b4f32dbea/example/static/favicon.ico -------------------------------------------------------------------------------- /example/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/draftbox-co/gatsby-attila-theme-ghost/e74f112be4994e19d842d0797b878d2b4f32dbea/example/static/favicon.png -------------------------------------------------------------------------------- /example/static/images/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/static/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /example/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | -------------------------------------------------------------------------------- /example/static/twitterImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/draftbox-co/gatsby-attila-theme-ghost/e74f112be4994e19d842d0797b878d2b4f32dbea/example/static/twitterImage.png -------------------------------------------------------------------------------- /gatsby-theme-ghost-attila/README.md: -------------------------------------------------------------------------------- 1 | [![Draftbox](https://res.cloudinary.com/thinkcdnimages/image/upload/v1589291053/Draftbox/draftbox-for-github.svg)](https://draftbox.co) 2 | 3 | # gatsby-attila-theme-ghost 4 | 5 | A Gatsby theme plugin for creating blogs from headless Ghost CMS. 6 | 7 | Turn your Ghost blog into a lightning fast static website. This Gatsby theme is a frontend replacement of the Ghost handlebars engine featuring the standard Casper 3 skin and functionality. All content is sourced from a headless Ghost CMS. 8 | 9 | 10 | > This theme is being used at [Draftbox](https://draftbox.co). Get lightning fast, secure front-end for your WordPress or Ghost blog, in 5 minutes or less, without coding. For our fellow devs, we also provide code export feature. 11 | 12 | ## Demo 13 | 14 | Play with the [Demo](https://ghost-attila-preview.draftbox.co/) to get a first impression. 15 | 16 | 17 | ## Features 18 | 19 | - Ghost Attila skin and functionality 20 | - SEO optimized 21 | - Fully responsive 22 | - Gatsby images 23 | - Styled 404 page 24 | - RSS Feed 25 | - AMP Pages 26 | - Sitemap 27 | - Contact Form 28 | - Subscribe Form 29 | - Social Sharing 30 | - Composable and extensible 31 | 32 | 33 | ## Installation 34 | 35 | > Head over to the [starter repo](https://github.com/draftbox-co/gatsby-attila-theme-starter) to get up and running quickly! 36 | 37 | 38 | If you want to add this blog theme to an existing site, follow these instructions: 39 | 40 | 1. Install the blog theme 41 | 42 | ```bash 43 | yarn add @draftbox-co/gatsby-theme-ghost-attila 44 | # or 45 | npm install @draftbox-co/gatsby-theme-ghost-attila --save 46 | ``` 47 | 48 | 2. Add the following configuration to your `gatsby-config.js` file 49 | 50 | ```js 51 | // gatsby-config.js 52 | module.exports = { 53 | plugins: [ 54 | { 55 | resolve: `@draftbox-co/gatsby-theme-ghost-attila`, 56 | options: { 57 | siteConfig: { 58 | siteUrl: `https://your-bog.com`, 59 | postsPerPage: 12, 60 | siteTitleMeta: `Gatsby Frontend powered by headless Ghost CMS`, 61 | siteDescriptionMeta: `Turn your Ghost blog into a lightning fast static website with Gatsby`, 62 | shareImageWidth: 1000, 63 | shareImageHeight: 523, 64 | shortTitle: `Ghost`, 65 | siteIcon: `favicon.png`, 66 | backgroundColor: `#e9e9e9`, 67 | themeColor: `#15171A`, 68 | }, 69 | ghostConfig: { 70 | "development": { 71 | "apiUrl": "http://localhost:2368", 72 | "contentApiKey": "9fcfdb1e5ea5b472e2e5b92942", 73 | }, 74 | "production": { 75 | "apiUrl": "https://your-ghost-cms.com", 76 | "contentApiKey": "9fcfdb1e5ea5b472e2e5b92942", 77 | }, 78 | }, 79 | }, 80 | }, 81 | ], 82 | } 83 | ``` 84 | 85 | 3. Update siteConfig 86 | 87 | In the configuration shown above, the most important fields to be changed are `siteUrl`, `siteTitleMeta` and `siteDescriptionMeta`. Update at least those to fit your needs. Also make sure your `favicon.png` can be found in folder `static` of your working directory. 88 | 89 | 4. Ghost Content API Keys 90 | 91 | Change the `apiUrl` value to the URL of your Ghost CMS site. Next, update the `contentApiKey` value to a key associated with the Ghost CMS site. A key can be provided by creating an integration within Ghost Admin. Navigate to Integrations and click "Add new integration". Name the integration appropriately and click create. 92 | 93 | 94 | 95 | ## Running 96 | 97 | Start the development server. You now have a Gatsby site pulling content from headless Ghost. 98 | 99 | ```bash 100 | gatsby develop 101 | ``` 102 | 103 | 104 | ## Optimizing 105 | 106 | You can disable the default Ghost Handlebars Theme front-end by enabling the `Make this site private` flag within your Ghost settings. This enables password protection in front of the Ghost install and sets `` so your Gatsby front-end becomes the source of truth for SEO. 107 | 108 | ## Authors 109 | - Arun Priyadarshi ([@Gunnerforlife](https://github.com/Gunnerforlife)) – [Draftbox](https://draftbox.co) 110 | - Keyur Raval ([@thandaanda](https://github.com/thandaanda)) – [Draftbox](https://draftbox.co) 111 | - Shyam Lohar ([@shyamlohar](https://github.com/shyamlohar)) – [Draftbox](https://draftbox.co) 112 | - Tanmay Desai ([@tanmaydesai89](https://github.com/tanmaydesai89)) – [Draftbox](https://draftbox.co) 113 | 114 | ## Contributions 115 | PRs are welcome! Consider contributing to this project if you are missing feature that is also useful for others. 116 | 117 | # Copyright & License 118 | 119 | Copyright (c) 2020 [Draftbox](https://draftbox.co) - Released under the [MIT license](LICENSE). 120 | -------------------------------------------------------------------------------- /gatsby-theme-ghost-attila/gatsby-browser.js: -------------------------------------------------------------------------------- 1 | /* global window, document */ 2 | 3 | const instaEmbedClasses = [".instagram-media"].join(","); 4 | const twitterEmbedClasses = [ 5 | ".twitter-tweet", 6 | ".twitter-timeline", 7 | ".twitter-follow-button", 8 | ".twitter-share-button", 9 | ].join(","); 10 | const scrollTo = (id) => () => { 11 | const el = document.querySelector(id); 12 | if (el) return window.scrollTo(0, el.offsetTop - 20); 13 | return false; 14 | }; 15 | 16 | const injectScript = function injectScript() { 17 | var js = document.createElement("script"); 18 | var firstScript = document.getElementsByTagName("script")[0]; 19 | js.id = "gatsby-plugin-instagram"; 20 | js.src = "https://instagram.com/embed.js"; 21 | firstScript.parentNode.insertBefore(js, firstScript); 22 | injected = true; 23 | if ( 24 | typeof instgrm !== "undefined" && 25 | window.instgrm.Embeds && 26 | typeof window.instgrm.Embeds.process === "function" 27 | ) { 28 | // manual process 29 | window.instgrm.Embeds.process(); 30 | } 31 | return true; 32 | }; 33 | 34 | var injectTwitterScript = function injectTwitterScript() { 35 | function addJS(jsCode) { 36 | var s = document.createElement("script"); 37 | s.type = "text/javascript"; 38 | s.innerText = jsCode; 39 | document.getElementsByTagName("head")[0].appendChild(s); 40 | injectedTwitterScript = true; 41 | 42 | if ( 43 | typeof twttr !== "undefined" && 44 | window.twttr.widgets && 45 | typeof window.twttr.widgets.load === "function" 46 | ) { 47 | window.twttr.widgets.load(); 48 | } 49 | } 50 | 51 | addJS( 52 | '\n window.twttr = (function(d, s, id) {\n var js,\n fjs = d.getElementsByTagName(s)[0],\n t = window.twttr || {};\n if (d.getElementById(id)) return t;\n js = d.createElement(s);\n js.id = id;\n js.src = "https://platform.twitter.com/widgets.js";\n fjs.parentNode.insertBefore(js, fjs);\n t._e = [];\n t.ready = function(f) {\n t._e.push(f);\n };\n return t;\n })(document, "script", "twitter-wjs");\n ' 53 | ); 54 | }; 55 | let injected = false; 56 | let injectedTwitterScript = false; 57 | 58 | export const onRouteUpdate = ({ location: { hash } }) => { 59 | if (hash) { 60 | window.setTimeout(scrollTo(hash), 10); 61 | } 62 | 63 | if (document.querySelector(instaEmbedClasses) !== null) { 64 | setTimeout(() => { 65 | if (!injected) { 66 | window.addEventListener( 67 | "scroll", 68 | function () { 69 | injectScript(); 70 | }, 71 | { once: true } 72 | ); 73 | } 74 | }, 2000); 75 | } 76 | 77 | if (document.querySelector(twitterEmbedClasses) !== null) { 78 | setTimeout(() => { 79 | if (!injectedTwitterScript) { 80 | window.addEventListener( 81 | "scroll", 82 | function () { 83 | injectTwitterScript(); 84 | }, 85 | { once: true } 86 | ); 87 | } 88 | }, 2000); 89 | } 90 | }; 91 | 92 | export const registerServiceWorker = () => true; -------------------------------------------------------------------------------- /gatsby-theme-ghost-attila/gatsby-config.js: -------------------------------------------------------------------------------- 1 | const path = require(`path`); 2 | 3 | const siteConfigDefaults = require(`./src/utils/siteConfig`); 4 | const ghostConfigDefaults = require(`./src/utils/.ghost.json`); 5 | 6 | const generateRSSFeed = require(`./src/utils/rss/generate-feed`); 7 | 8 | module.exports = (themeOptions) => { 9 | const siteConfig = themeOptions.siteConfig || siteConfigDefaults; 10 | const ghostConfig = themeOptions.ghostConfig || ghostConfigDefaults; 11 | const finalConfig = 12 | process.env.NODE_ENV === `development` 13 | ? ghostConfig.development 14 | : ghostConfig.production; 15 | 16 | siteConfig.apiUrl = finalConfig.apiUrl; 17 | const configOptions = { 18 | siteMetadata: siteConfig, 19 | plugins: [ 20 | { 21 | resolve: `gatsby-plugin-sass`, 22 | options: { 23 | sassRuleModulesTest: /.*\.module\.s(a|c)ss$/, 24 | }, 25 | }, 26 | { 27 | resolve: `gatsby-plugin-page-creator`, 28 | options: { 29 | path: path.join(__dirname, `src`, `pages`), 30 | }, 31 | }, 32 | { 33 | resolve: `gatsby-source-filesystem`, 34 | options: { 35 | path: path.join(__dirname, `src`, `images`), 36 | name: `images`, 37 | }, 38 | }, 39 | `gatsby-plugin-sharp`, 40 | `gatsby-transformer-sharp`, 41 | { 42 | resolve: `gatsby-source-ghost`, 43 | options: 44 | process.env.NODE_ENV === `development` 45 | ? ghostConfig.development 46 | : ghostConfig.production, 47 | }, 48 | { 49 | resolve: `gatsby-transformer-rehype`, 50 | options: { 51 | filter: (node) => 52 | node.internal.type === `GhostPost` || 53 | node.internal.type === `GhostPage`, 54 | plugins: [ 55 | { 56 | resolve: `gatsby-rehype-prismjs`, 57 | }, 58 | { 59 | resolve: `gatsby-rehype-ghost-links`, 60 | }, 61 | ], 62 | }, 63 | }, 64 | /** 65 | * Utility Plugins 66 | */ 67 | { 68 | resolve: require.resolve(`./plugins/gatsby-plugin-ghost-manifest`), 69 | options: { 70 | short_name: siteConfig.shortTitle, 71 | start_url: `/`, 72 | background_color: siteConfig.backgroundColor, 73 | theme_color: siteConfig.themeColor, 74 | display: `minimal-ui`, 75 | icon: `static/${siteConfig.siteIcon}`, 76 | legacy: true, 77 | query: `{ 78 | site { 79 | siteMetadata { 80 | siteTitle 81 | siteDescription 82 | } 83 | } 84 | }`, 85 | }, 86 | }, 87 | { 88 | resolve: `gatsby-plugin-feed`, 89 | options: { 90 | query: `{ 91 | allGhostSettings { 92 | edges { 93 | node { 94 | title 95 | description 96 | } 97 | } 98 | } 99 | }`, 100 | feeds: [generateRSSFeed(siteConfig)], 101 | }, 102 | }, 103 | { 104 | resolve: `gatsby-plugin-advanced-sitemap`, 105 | options: { 106 | query: `{ 107 | allGhostPost { 108 | edges { 109 | node { 110 | id 111 | slug 112 | updated_at 113 | created_at 114 | feature_image 115 | } 116 | } 117 | } 118 | allGhostPage { 119 | edges { 120 | node { 121 | id 122 | slug 123 | updated_at 124 | created_at 125 | feature_image 126 | } 127 | } 128 | } 129 | allGhostTag { 130 | edges { 131 | node { 132 | id 133 | slug 134 | feature_image 135 | } 136 | } 137 | } 138 | allGhostAuthor { 139 | edges { 140 | node { 141 | id 142 | slug 143 | profile_image 144 | } 145 | } 146 | } 147 | } 148 | `, 149 | mapping: { 150 | allGhostPost: { 151 | sitemap: `posts`, 152 | }, 153 | allGhostTag: { 154 | sitemap: `tags`, 155 | }, 156 | allGhostAuthor: { 157 | sitemap: `authors`, 158 | }, 159 | allGhostPage: { 160 | sitemap: `pages`, 161 | }, 162 | }, 163 | exclude: [ 164 | `/dev-404-page`, 165 | `/404`, 166 | `/404.html`, 167 | `/offline-plugin-app-shell-fallback`, 168 | `/offline`, 169 | `/offline.html`, 170 | ], 171 | createLinkInHead: true, 172 | addUncaughtPages: true, 173 | }, 174 | }, 175 | `gatsby-plugin-catch-links`, 176 | `gatsby-plugin-react-helmet`, 177 | `gatsby-plugin-force-trailing-slashes`, 178 | { 179 | resolve: `gatsby-plugin-postcss`, 180 | options: { 181 | postCssPlugins: [require(`cssnano`)()], 182 | }, 183 | }, 184 | { 185 | resolve: `@draftbox-co/gatsby-plugin-amp`, 186 | options: { 187 | canonicalBaseUrl: siteConfig.siteUrl, 188 | components: [`amp-form`], 189 | excludedPaths: [`/404*`, `/`, `/offline*`], 190 | pathIdentifier: `amp/`, 191 | relAmpHtmlPattern: `{{canonicalBaseUrl}}{{pathname}}{{pathIdentifier}}`, 192 | useAmpClientIdApi: true, 193 | dirName: __dirname, 194 | themePath: `src/amp-styles/post.amp.css`, 195 | }, 196 | }, 197 | { 198 | resolve: `gatsby-plugin-remove-generator`, 199 | options: { 200 | content: `Draftbox`, 201 | }, 202 | }, 203 | { 204 | resolve: `@draftbox-co/gatsby-plugin-css-variables`, 205 | options: { 206 | variables: siteConfig.themeConfig.variables, 207 | }, 208 | }, 209 | ], 210 | }; 211 | 212 | if (siteConfig.themeConfig.fonts && siteConfig.themeConfig.fonts.length > 0) { 213 | configOptions.plugins.push({ 214 | resolve: `@draftbox-co/gatsby-plugin-webfonts`, 215 | options: { 216 | fonts: { 217 | google: siteConfig.themeConfig.fonts, 218 | }, 219 | formats: ["woff2", "woff"], 220 | useMinify: true, 221 | usePreload: true, 222 | usePreconnect: true, 223 | blacklist: ["/amp"], 224 | }, 225 | }); 226 | } 227 | return configOptions; 228 | }; 229 | 230 | 231 | -------------------------------------------------------------------------------- /gatsby-theme-ghost-attila/gatsby-node.js: -------------------------------------------------------------------------------- 1 | const _ = require(`lodash`); 2 | const { paginate } = require(`gatsby-awesome-pagination`); 3 | const { createRemoteFileNode } = require(`gatsby-source-filesystem`); 4 | const readingTime = require("reading-time"); 5 | 6 | /** 7 | * Here is the place where Gatsby creates the URLs for all the 8 | * posts, tags, pages and authors that we fetched from the Ghost site. 9 | */ 10 | 11 | exports.createSchemaCustomization = ({ actions, schema }) => { 12 | const { createFieldExtension, createTypes } = actions; 13 | createFieldExtension({ 14 | name: "readingTime", 15 | extend(options, prevFieldConfig) { 16 | return { 17 | resolve(source) { 18 | if (source.html) { 19 | const readingTimeValue = readingTime(source.html); 20 | return readingTimeValue.text; 21 | } else { 22 | return "1 min read"; 23 | } 24 | }, 25 | }; 26 | }, 27 | }); 28 | 29 | createTypes(` 30 | type GhostPost implements Node { 31 | readingTime: String @readingTime 32 | } 33 | `); 34 | 35 | createTypes(` 36 | type GhostPage implements Node { 37 | readingTime: String @readingTime 38 | } 39 | `); 40 | }; 41 | 42 | exports.onCreateNode = async ({ 43 | node, 44 | actions, 45 | store, 46 | createNodeId, 47 | cache, 48 | }) => { 49 | // Check that we are modifying right node types. 50 | const nodeTypes = [`GhostPost`, `GhostPage`]; 51 | if (!nodeTypes.includes(node.internal.type)) { 52 | return; 53 | } 54 | 55 | const { createNode } = actions; 56 | 57 | // Download image and create a File node with gatsby-transformer-sharp. 58 | if (node.feature_image) { 59 | const fileNode = await createRemoteFileNode({ 60 | url: node.feature_image, 61 | store, 62 | cache, 63 | createNode, 64 | parentNodeId: node.id, 65 | createNodeId, 66 | }); 67 | 68 | if (fileNode) { 69 | // Link File node to GhostPost node at field image. 70 | node.localFeatureImage___NODE = fileNode.id; 71 | } 72 | } 73 | }; 74 | 75 | exports.createPages = async ({ graphql, actions }) => { 76 | const { createPage } = actions; 77 | 78 | const result = await graphql(` 79 | { 80 | allGhostPost( 81 | sort: { order: ASC, fields: published_at } 82 | filter: { slug: { ne: "data-schema" } } 83 | ) { 84 | edges { 85 | node { 86 | slug 87 | primary_tag { 88 | slug 89 | } 90 | } 91 | } 92 | } 93 | allGhostTag(sort: { order: ASC, fields: name }) { 94 | edges { 95 | node { 96 | slug 97 | url 98 | postCount 99 | } 100 | } 101 | } 102 | allGhostAuthor(sort: { order: ASC, fields: name }) { 103 | edges { 104 | node { 105 | slug 106 | url 107 | postCount 108 | } 109 | } 110 | } 111 | allGhostPage(sort: { order: ASC, fields: published_at }) { 112 | edges { 113 | node { 114 | slug 115 | url 116 | } 117 | } 118 | } 119 | site { 120 | siteMetadata { 121 | postsPerPage 122 | siteTitle 123 | } 124 | } 125 | 126 | ghostSettings { 127 | title 128 | } 129 | } 130 | `); 131 | 132 | // Check for any errors 133 | if (result.errors) { 134 | throw new Error(result.errors); 135 | } 136 | 137 | // Extract query results 138 | const tags = result.data.allGhostTag.edges; 139 | const authors = result.data.allGhostAuthor.edges; 140 | const pages = result.data.allGhostPage.edges; 141 | const posts = result.data.allGhostPost.edges; 142 | const postsPerPage = result.data.site.siteMetadata.postsPerPage; 143 | const websiteTitle = result.data.site.siteMetadata.siteTitle; 144 | //const websiteTitle = result.data.ghostSettings.title; 145 | 146 | // Load templates 147 | const indexTemplate = require.resolve(`./src/templates/indexTemplate.jsx`); 148 | const postTemplate = require.resolve("./src/templates/postTemplate.jsx"); 149 | const tagsTemplate = require.resolve(`./src/templates/tagsTemplate.jsx`); 150 | const authorTemplate = require.resolve(`./src/templates/authorTemplate.jsx`); 151 | const pageTemplate = require.resolve(`./src/templates/pageTemplate.jsx`); 152 | const postAmpTemplate = require.resolve( 153 | `./src/templates/postTemplate.amp.jsx` 154 | ); 155 | 156 | // Create author pages 157 | authors.forEach(({ node }) => { 158 | const totalPosts = node.postCount !== null ? node.postCount : 0; 159 | const numberOfPages = Math.ceil(totalPosts / postsPerPage); 160 | 161 | // This part here defines, that our author pages will use 162 | // a `/author/:slug/` permalink. 163 | node.url = `/author/${node.slug}/`; 164 | 165 | Array.from({ length: numberOfPages }).forEach((_, i) => { 166 | const currentPage = i + 1; 167 | const prevPageNumber = currentPage <= 1 ? null : currentPage - 1; 168 | const nextPageNumber = 169 | currentPage + 1 > numberOfPages ? null : currentPage + 1; 170 | const previousPagePath = prevPageNumber 171 | ? prevPageNumber === 1 172 | ? node.url 173 | : `${node.url}page/${prevPageNumber}/` 174 | : null; 175 | const nextPagePath = nextPageNumber 176 | ? `${node.url}page/${nextPageNumber}/` 177 | : null; 178 | 179 | createPage({ 180 | path: i === 0 ? node.url : `${node.url}page/${i + 1}/`, 181 | component: authorTemplate, 182 | context: { 183 | // Data passed to context is available 184 | // in page queries as GraphQL variables. 185 | slug: node.slug, 186 | limit: postsPerPage, 187 | skip: i * postsPerPage, 188 | numberOfPages: numberOfPages, 189 | humanPageNumber: currentPage, 190 | prevPageNumber: prevPageNumber, 191 | nextPageNumber: nextPageNumber, 192 | previousPagePath: previousPagePath, 193 | nextPagePath: nextPagePath, 194 | }, 195 | }); 196 | }); 197 | }); 198 | 199 | // Create pages 200 | 201 | posts.forEach(({ node }, index, array) => { 202 | createPage({ 203 | path: node.slug, 204 | component: postTemplate, 205 | context: { 206 | slug: node.slug, 207 | prev: index !== 0 ? array[index - 1].node.slug : null, 208 | next: index !== array.length - 1 ? array[index + 1].node.slug : null, 209 | }, 210 | }); 211 | 212 | createPage({ 213 | path: `${node.slug}/amp`, 214 | component: postAmpTemplate, 215 | context: { 216 | slug: node.slug, 217 | title: websiteTitle, 218 | amp: true, 219 | }, 220 | }); 221 | }); 222 | 223 | tags.forEach(({ node }, i) => { 224 | const totalPosts = node.postCount !== null ? node.postCount : 0; 225 | const numberOfPages = Math.ceil(totalPosts / postsPerPage); 226 | node.url = `/tag/${node.slug}/`; 227 | 228 | Array.from({ length: numberOfPages }).forEach((_, i) => { 229 | const currentPage = i + 1; 230 | const prevPageNumber = currentPage <= 1 ? null : currentPage - 1; 231 | const nextPageNumber = 232 | currentPage + 1 > numberOfPages ? null : currentPage + 1; 233 | const previousPagePath = prevPageNumber 234 | ? prevPageNumber === 1 235 | ? node.url 236 | : `${node.url}page/${prevPageNumber}/` 237 | : null; 238 | const nextPagePath = nextPageNumber 239 | ? `${node.url}page/${nextPageNumber}/` 240 | : null; 241 | 242 | createPage({ 243 | path: i === 0 ? node.url : `${node.url}page/${i + 1}/`, 244 | component: tagsTemplate, 245 | context: { 246 | // Data passed to context is available 247 | // in page queries as GraphQL variables. 248 | slug: node.slug, 249 | limit: postsPerPage, 250 | skip: i * postsPerPage, 251 | numberOfPages: numberOfPages, 252 | humanPageNumber: currentPage, 253 | prevPageNumber: prevPageNumber, 254 | nextPageNumber: nextPageNumber, 255 | previousPagePath: previousPagePath, 256 | nextPagePath: nextPagePath, 257 | }, 258 | }); 259 | }); 260 | }); 261 | 262 | pages 263 | .filter(({ node }) => !node.slug.startsWith("contact")) 264 | .forEach(({ node }) => { 265 | // This part here defines, that our pages will use 266 | // a `/:slug/` permalink. 267 | node.url = `/${node.slug}/`; 268 | 269 | createPage({ 270 | path: node.url, 271 | component: pageTemplate, 272 | context: { 273 | // Data passed to context is available 274 | // in page queries as GraphQL variables. 275 | slug: node.slug, 276 | }, 277 | }); 278 | }); 279 | 280 | // Create pagination 281 | paginate({ 282 | createPage, 283 | items: posts, 284 | itemsPerPage: postsPerPage, 285 | component: indexTemplate, 286 | pathPrefix: ({ pageNumber }) => { 287 | if (pageNumber === 0) { 288 | return `/`; 289 | } else { 290 | return `/page`; 291 | } 292 | }, 293 | }); 294 | }; 295 | -------------------------------------------------------------------------------- /gatsby-theme-ghost-attila/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@draftbox-co/gatsby-theme-ghost-attila", 3 | "description": "A Gatsby theme plugin for creating blogs from headless Ghost CMS.", 4 | "version": "1.0.48", 5 | "license": "MIT", 6 | "author": "Draftbox", 7 | "homepage": "https://github.com/draftbox-co/gatsby-attila-theme-ghost", 8 | "repository": { 9 | "url": "https://github.com/draftbox-co/gatsby-attila-theme-ghost", 10 | "type": "git" 11 | }, 12 | "main": "index.js", 13 | "resolutions": { 14 | "sharp": "0.23.4" 15 | }, 16 | "engines": { 17 | "node": ">= 8.9.0" 18 | }, 19 | "keywords": [ 20 | "gatsby", 21 | "gatsby-plugin", 22 | "gatsby-theme", 23 | "ghost", 24 | "ghost-theme", 25 | "casper", 26 | "blogging", 27 | "static-site", 28 | "static-site-generator", 29 | "gatsbyjs", 30 | "react" 31 | ], 32 | "scripts": { 33 | "serve": "gatsby build && NODE_ENV=production gatsby serve", 34 | "build": "gatsby build", 35 | "develop": "gatsby develop", 36 | "lint": "eslint . --ext .js --cache", 37 | "test": "echo \"Error: no test specified\" && exit 1" 38 | }, 39 | "devDependencies": { 40 | "babel-eslint": "^10.1.0", 41 | "eslint": "^6.8.0", 42 | "eslint-plugin-ghost": "^1.0.0", 43 | "eslint-plugin-react": "^7.18.3", 44 | "gatsby": "2.20.24", 45 | "react": "^16.12.0", 46 | "react-dom": "^16.12.0" 47 | }, 48 | "peerDependencies": { 49 | "gatsby": "2.20.24", 50 | "react": "^16.12.0", 51 | "react-dom": "^16.12.0" 52 | }, 53 | "dependencies": { 54 | "@draftbox-co/gatsby-plugin-amp": "^0.2.4", 55 | "@draftbox-co/gatsby-plugin-css-variables": "0.0.3", 56 | "@draftbox-co/gatsby-plugin-webfonts": "^1.1.3", 57 | "@tryghost/helpers": "^1.1.22", 58 | "@tryghost/url-utils": "^0.6.13", 59 | "autoprefixer": "^9.7.4", 60 | "babel-plugin-prismjs": "^2.0.1", 61 | "babel-plugin-styled-components": "^1.10.7", 62 | "babel-preset-gatsby": "^0.2.29", 63 | "cheerio": "^1.0.0-rc.3", 64 | "cssnano": "^4.1.10", 65 | "disqus-react": "^1.0.8", 66 | "gatsby-awesome-pagination": "^0.3.5", 67 | "gatsby-background-image": "^0.10.2", 68 | "gatsby-image": "^2.2.41", 69 | "gatsby-plugin-advanced-sitemap": "^1.5.1", 70 | "gatsby-plugin-catch-links": "^2.1.26", 71 | "gatsby-plugin-feed": "^2.3.27", 72 | "gatsby-plugin-force-trailing-slashes": "^1.0.4", 73 | "gatsby-plugin-manifest": "^2.2.42", 74 | "gatsby-plugin-page-creator": "^2.1.40", 75 | "gatsby-plugin-postcss": "^2.1.20", 76 | "gatsby-plugin-react-helmet": "^3.1.22", 77 | "gatsby-plugin-remove-generator": "^1.0.5", 78 | "gatsby-plugin-sass": "^2.1.29", 79 | "gatsby-plugin-sharp": "^2.4.5", 80 | "gatsby-rehype-ghost-links": "^1.1.0", 81 | "gatsby-rehype-prismjs": "^0.0.9", 82 | "gatsby-source-filesystem": "^2.1.48", 83 | "gatsby-source-ghost": "^4.0.3", 84 | "gatsby-transformer-rehype": "^1.1.0", 85 | "gatsby-transformer-sharp": "^2.3.14", 86 | "lodash": "^4.17.19", 87 | "node-sass": "^4.13.1", 88 | "prismjs": "^1.19.0", 89 | "react-helmet": "^5.2.1", 90 | "react-intersection-observer": "^8.26.2", 91 | "reading-time": "^1.2.0", 92 | "styled-components": "^5.0.1" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /gatsby-theme-ghost-attila/plugins/gatsby-plugin-ghost-manifest/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "babel-preset-gatsby-package", 5 | { 6 | "browser": true 7 | } 8 | ] 9 | ] 10 | } -------------------------------------------------------------------------------- /gatsby-theme-ghost-attila/plugins/gatsby-plugin-ghost-manifest/common.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require("fs"); // default icons for generating icons 4 | 5 | 6 | exports.defaultIcons = [{ 7 | src: "icons/icon-48x48.png", 8 | sizes: "48x48", 9 | type: "image/png" 10 | }, { 11 | src: "icons/icon-72x72.png", 12 | sizes: "72x72", 13 | type: "image/png" 14 | }, { 15 | src: "icons/icon-96x96.png", 16 | sizes: "96x96", 17 | type: "image/png" 18 | }, { 19 | src: "icons/icon-144x144.png", 20 | sizes: "144x144", 21 | type: "image/png" 22 | }, { 23 | src: "icons/icon-192x192.png", 24 | sizes: "192x192", 25 | type: "image/png" 26 | }, { 27 | src: "icons/icon-256x256.png", 28 | sizes: "256x256", 29 | type: "image/png" 30 | }, { 31 | src: "icons/icon-384x384.png", 32 | sizes: "384x384", 33 | type: "image/png" 34 | }, { 35 | src: "icons/icon-512x512.png", 36 | sizes: "512x512", 37 | type: "image/png" 38 | }]; 39 | /** 40 | * Check if the icon exists on the filesystem 41 | * 42 | * @param {String} srcIcon Path of the icon 43 | */ 44 | 45 | exports.doesIconExist = function doesIconExist(srcIcon) { 46 | try { 47 | return fs.statSync(srcIcon).isFile(); 48 | } catch (e) { 49 | if (e.code === "ENOENT") { 50 | return false; 51 | } else { 52 | throw e; 53 | } 54 | } 55 | }; -------------------------------------------------------------------------------- /gatsby-theme-ghost-attila/plugins/gatsby-plugin-ghost-manifest/gatsby-node.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 4 | 5 | var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); 6 | 7 | var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); 8 | 9 | var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose")); 10 | 11 | var fs = require("fs"); 12 | 13 | var path = require("path"); 14 | 15 | var Promise = require("bluebird"); 16 | 17 | var sharp = require("sharp"); 18 | 19 | var _require = require("./common.js"), 20 | defaultIcons = _require.defaultIcons, 21 | doesIconExist = _require.doesIconExist; 22 | 23 | sharp.simd(true); 24 | 25 | function generateIcons(icons, srcIcon) { 26 | return Promise.map(icons, function (icon) { 27 | var size = parseInt(icon.sizes.substring(0, icon.sizes.lastIndexOf("x"))); 28 | var imgPath = path.join("public", icon.src); 29 | return sharp(srcIcon).resize(size).toFile(imgPath).then(function () {}); 30 | }); 31 | } 32 | 33 | exports.onPostBuild = function _callee(_ref, pluginOptions) { 34 | var graphql, icon, manifest, _ref2, data, siteTitle, iconPath; 35 | 36 | return _regenerator.default.async(function _callee$(_context) { 37 | while (1) { 38 | switch (_context.prev = _context.next) { 39 | case 0: 40 | graphql = _ref.graphql; 41 | icon = pluginOptions.icon, manifest = (0, _objectWithoutPropertiesLoose2.default)(pluginOptions, ["icon"]); 42 | _context.next = 4; 43 | return _regenerator.default.awrap(graphql(pluginOptions.query)); 44 | 45 | case 4: 46 | _ref2 = _context.sent; 47 | data = _ref2.data; 48 | siteTitle = data.site.siteMetadata.siteTitle || "No Title"; 49 | manifest = (0, _extends2.default)({}, manifest, { 50 | name: siteTitle 51 | }); // Delete options we won't pass to the manifest.webmanifest. 52 | 53 | delete manifest.plugins; 54 | delete manifest.legacy; 55 | delete manifest.theme_color_in_head; 56 | delete manifest.query; // If icons are not manually defined, use the default icon set. 57 | 58 | if (!manifest.icons) { 59 | manifest.icons = defaultIcons; 60 | } // Determine destination path for icons. 61 | 62 | 63 | iconPath = path.join("public", path.dirname(manifest.icons[0].src)); //create destination directory if it doesn't exist 64 | 65 | if (!fs.existsSync(iconPath)) { 66 | fs.mkdirSync(iconPath); 67 | } 68 | 69 | fs.writeFileSync(path.join("public", "manifest.webmanifest"), JSON.stringify(manifest)); // Only auto-generate icons if a src icon is defined. 70 | 71 | if (icon !== undefined) { 72 | // Check if the icon exists 73 | if (!doesIconExist(icon)) { 74 | Promise.reject("icon (" + icon + ") does not exist as defined in gatsby-config.js. Make sure the file exists relative to the root of the site."); 75 | } 76 | 77 | generateIcons(manifest.icons, icon).then(function () { 78 | //images have been generated 79 | console.log("done generating icons for manifest"); 80 | Promise.resolve(); 81 | }); 82 | } else { 83 | Promise.resolve(); 84 | } 85 | 86 | case 17: 87 | case "end": 88 | return _context.stop(); 89 | } 90 | } 91 | }); 92 | }; -------------------------------------------------------------------------------- /gatsby-theme-ghost-attila/plugins/gatsby-plugin-ghost-manifest/gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 4 | 5 | var _react = _interopRequireDefault(require("react")); 6 | 7 | var _gatsby = require("gatsby"); 8 | 9 | var _common = require("./common.js"); 10 | 11 | var _jsxFileName = "C:\\Users\\Arun\\Documents\\code\\gatsby-attila-theme-ghost\\gatsby-theme-ghost-attila\\plugins\\gatsby-plugin-ghost-manifest\\src\\gatsby-ssr.js"; 12 | 13 | exports.onRenderBody = function (_ref, pluginOptions) { 14 | var setHeadComponents = _ref.setHeadComponents; 15 | // We use this to build a final array to pass as the argument to setHeadComponents at the end of onRenderBody. 16 | var headComponents = []; 17 | var icons = pluginOptions.icons || _common.defaultIcons; // If icons were generated, also add a favicon link. 18 | 19 | if (pluginOptions.icon) { 20 | var favicon = icons && icons.length ? icons[0].src : null; 21 | 22 | if (favicon) { 23 | headComponents.push(_react.default.createElement("link", { 24 | key: "gatsby-plugin-manifest-icon-link", 25 | rel: "shortcut icon", 26 | href: (0, _gatsby.withPrefix)(favicon), 27 | __source: { 28 | fileName: _jsxFileName, 29 | lineNumber: 17 30 | }, 31 | __self: this 32 | })); 33 | } 34 | } // Add manifest link tag. 35 | 36 | 37 | headComponents.push(_react.default.createElement("link", { 38 | key: "gatsby-plugin-manifest-link", 39 | rel: "manifest", 40 | href: (0, _gatsby.withPrefix)("/manifest.webmanifest"), 41 | __source: { 42 | fileName: _jsxFileName, 43 | lineNumber: 28 44 | }, 45 | __self: this 46 | })); // The user has an option to opt out of the theme_color meta tag being inserted into the head. 47 | 48 | if (pluginOptions.theme_color) { 49 | var insertMetaTag = Object.keys(pluginOptions).includes("theme_color_in_head") ? pluginOptions.theme_color_in_head : true; 50 | 51 | if (insertMetaTag) { 52 | headComponents.push(_react.default.createElement("meta", { 53 | key: "gatsby-plugin-manifest-meta", 54 | name: "theme-color", 55 | content: pluginOptions.theme_color, 56 | __source: { 57 | fileName: _jsxFileName, 58 | lineNumber: 44 59 | }, 60 | __self: this 61 | })); 62 | } 63 | } 64 | 65 | if (pluginOptions.legacy) { 66 | var iconLinkTags = icons.map(function (icon) { 67 | return _react.default.createElement("link", { 68 | key: "gatsby-plugin-manifest-apple-touch-icon-" + icon.sizes, 69 | rel: "apple-touch-icon", 70 | sizes: icon.sizes, 71 | href: (0, _gatsby.withPrefix)("" + icon.src), 72 | __source: { 73 | fileName: _jsxFileName, 74 | lineNumber: 55 75 | }, 76 | __self: this 77 | }); 78 | }); 79 | headComponents = [].concat(headComponents, iconLinkTags); 80 | } 81 | 82 | setHeadComponents(headComponents); 83 | }; -------------------------------------------------------------------------------- /gatsby-theme-ghost-attila/plugins/gatsby-plugin-ghost-manifest/index.js: -------------------------------------------------------------------------------- 1 | // noop -------------------------------------------------------------------------------- /gatsby-theme-ghost-attila/plugins/gatsby-plugin-ghost-manifest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-plugin-ghost-manifest", 3 | "description": "Gatsby plugin which adds a manifest.webmanifest to make sites progressive web apps", 4 | "version": "0.0.1", 5 | "author": "Ghost Foundation", 6 | "dependencies": { 7 | "@babel/runtime": "7.7.2", 8 | "bluebird": "3.7.1", 9 | "sharp": "0.22.1" 10 | }, 11 | "devDependencies": { 12 | "@babel/cli": "7.7.0", 13 | "@babel/core": "7.7.2", 14 | "babel-preset-gatsby-package": "0.2.11", 15 | "cross-env": "6.0.3" 16 | }, 17 | "keywords": [ 18 | "gatsby", 19 | "gatsby-plugin", 20 | "favicon", 21 | "icons", 22 | "manifest.webmanifest", 23 | "progressive-web-app", 24 | "pwa" 25 | ], 26 | "resolutions": { 27 | "sharp": "0.22.1" 28 | }, 29 | "license": "MIT", 30 | "main": "index.js", 31 | "peerDependencies": { 32 | "gatsby": ">2.0.0-alpha" 33 | }, 34 | "scripts": { 35 | "build": "babel src --out-dir . --ignore **/__tests__", 36 | "prepare": "cross-env NODE_ENV=production npm run build", 37 | "watch": "babel -w src --out-dir . --ignore **/__tests__" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /gatsby-theme-ghost-attila/plugins/gatsby-plugin-ghost-manifest/src/common.js: -------------------------------------------------------------------------------- 1 | const fs = require(`fs`) 2 | 3 | // default icons for generating icons 4 | exports.defaultIcons = [ 5 | { 6 | src: `icons/icon-48x48.png`, 7 | sizes: `48x48`, 8 | type: `image/png`, 9 | }, 10 | { 11 | src: `icons/icon-72x72.png`, 12 | sizes: `72x72`, 13 | type: `image/png`, 14 | }, 15 | { 16 | src: `icons/icon-96x96.png`, 17 | sizes: `96x96`, 18 | type: `image/png`, 19 | }, 20 | { 21 | src: `icons/icon-144x144.png`, 22 | sizes: `144x144`, 23 | type: `image/png`, 24 | }, 25 | { 26 | src: `icons/icon-192x192.png`, 27 | sizes: `192x192`, 28 | type: `image/png`, 29 | }, 30 | { 31 | src: `icons/icon-256x256.png`, 32 | sizes: `256x256`, 33 | type: `image/png`, 34 | }, 35 | { 36 | src: `icons/icon-384x384.png`, 37 | sizes: `384x384`, 38 | type: `image/png`, 39 | }, 40 | { 41 | src: `icons/icon-512x512.png`, 42 | sizes: `512x512`, 43 | type: `image/png`, 44 | }, 45 | ] 46 | 47 | /** 48 | * Check if the icon exists on the filesystem 49 | * 50 | * @param {String} srcIcon Path of the icon 51 | */ 52 | exports.doesIconExist = function doesIconExist(srcIcon) { 53 | try { 54 | return fs.statSync(srcIcon).isFile() 55 | } catch (e) { 56 | if (e.code === `ENOENT`) { 57 | return false 58 | } else { 59 | throw e 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /gatsby-theme-ghost-attila/plugins/gatsby-plugin-ghost-manifest/src/gatsby-node.js: -------------------------------------------------------------------------------- 1 | const fs = require(`fs`) 2 | const path = require(`path`) 3 | const Promise = require(`bluebird`) 4 | const sharp = require(`sharp`) 5 | const { defaultIcons, doesIconExist } = require(`./common.js`) 6 | 7 | sharp.simd(true) 8 | 9 | function generateIcons(icons, srcIcon) { 10 | return Promise.map(icons, (icon) => { 11 | const size = parseInt(icon.sizes.substring(0, icon.sizes.lastIndexOf(`x`))) 12 | const imgPath = path.join(`public`, icon.src) 13 | 14 | return sharp(srcIcon) 15 | .resize(size) 16 | .toFile(imgPath) 17 | .then(() => { }) 18 | }) 19 | } 20 | 21 | exports.onPostBuild = async ({ graphql }, pluginOptions) => { 22 | let { icon, ...manifest } = pluginOptions 23 | 24 | const { data } = await graphql(pluginOptions.query) 25 | const siteTitle = data.site.siteMetadata.siteTitle || `No Title` 26 | manifest = { 27 | ...manifest, 28 | name: siteTitle, 29 | } 30 | 31 | // Delete options we won't pass to the manifest.webmanifest. 32 | delete manifest.plugins 33 | delete manifest.legacy 34 | delete manifest.theme_color_in_head 35 | delete manifest.query 36 | 37 | // If icons are not manually defined, use the default icon set. 38 | if (!manifest.icons) { 39 | manifest.icons = defaultIcons 40 | } 41 | 42 | // Determine destination path for icons. 43 | const iconPath = path.join(`public`, path.dirname(manifest.icons[0].src)) 44 | 45 | //create destination directory if it doesn't exist 46 | if (!fs.existsSync(iconPath)) { 47 | fs.mkdirSync(iconPath) 48 | } 49 | 50 | fs.writeFileSync( 51 | path.join(`public`, `manifest.webmanifest`), 52 | JSON.stringify(manifest) 53 | ) 54 | 55 | // Only auto-generate icons if a src icon is defined. 56 | if (icon !== undefined) { 57 | // Check if the icon exists 58 | if (!doesIconExist(icon)) { 59 | Promise.reject( 60 | `icon (${icon}) does not exist as defined in gatsby-config.js. Make sure the file exists relative to the root of the site.` 61 | ) 62 | } 63 | generateIcons(manifest.icons, icon).then(() => { 64 | //images have been generated 65 | console.log(`done generating icons for manifest`) 66 | Promise.resolve() 67 | }) 68 | } else { 69 | Promise.resolve() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /gatsby-theme-ghost-attila/plugins/gatsby-plugin-ghost-manifest/src/gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { withPrefix } from "gatsby" 3 | import { defaultIcons } from "./common.js" 4 | 5 | exports.onRenderBody = ({ setHeadComponents }, pluginOptions) => { 6 | // We use this to build a final array to pass as the argument to setHeadComponents at the end of onRenderBody. 7 | let headComponents = [] 8 | 9 | const icons = pluginOptions.icons || defaultIcons 10 | 11 | // If icons were generated, also add a favicon link. 12 | if (pluginOptions.icon) { 13 | let favicon = icons && icons.length ? icons[0].src : null 14 | 15 | if (favicon) { 16 | headComponents.push( 17 | 22 | ) 23 | } 24 | } 25 | 26 | // Add manifest link tag. 27 | headComponents.push( 28 | 33 | ) 34 | // The user has an option to opt out of the theme_color meta tag being inserted into the head. 35 | if (pluginOptions.theme_color) { 36 | let insertMetaTag = Object.keys(pluginOptions).includes( 37 | `theme_color_in_head` 38 | ) 39 | ? pluginOptions.theme_color_in_head 40 | : true 41 | 42 | if (insertMetaTag) { 43 | headComponents.push( 44 | 49 | ) 50 | } 51 | } 52 | 53 | if (pluginOptions.legacy) { 54 | const iconLinkTags = icons.map(icon => ( 55 | 61 | )) 62 | 63 | headComponents = [...headComponents, ...iconLinkTags] 64 | } 65 | 66 | setHeadComponents(headComponents) 67 | } -------------------------------------------------------------------------------- /gatsby-theme-ghost-attila/src/components/Layout.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "../styles/sass/style.scss"; 3 | import Navbar from "./navbar"; 4 | import Footer from "./footer"; 5 | import { ArmadaFormsProvider } from "../context/form-context"; 6 | import { Helmet } from "react-helmet"; 7 | import { useStaticQuery, graphql } from "gatsby"; 8 | 9 | const Layout = (props) => { 10 | const data = useStaticQuery(graphql` 11 | { 12 | site { 13 | siteMetadata { 14 | language 15 | } 16 | } 17 | } 18 | `); 19 | 20 | return ( 21 | <> 22 | 29 | 30 | 31 | {props.children} 32 |