├── .editorconfig ├── .eleventy.js ├── .eleventyignore ├── .github └── FUNDING.yml ├── .gitignore ├── .nvmrc ├── .travis.yml ├── LICENSE ├── README.md ├── functions └── google-analytics │ ├── google-analytics.js │ ├── package.json │ └── yarn.lock ├── netlify.toml ├── package.json ├── src ├── 404.md ├── _11ty │ └── getTagList.js ├── _data │ ├── metadata.json │ └── prefetchLinks.js ├── _includes │ ├── layouts │ │ ├── about.njk │ │ ├── base.njk │ │ ├── contact.njk │ │ ├── home.njk │ │ ├── menu.njk │ │ └── post.njk │ └── postslist.njk ├── about │ └── index.md ├── archive.njk ├── contact │ └── contact.md ├── css │ ├── index.css │ └── prism-base16-monokai.dark.css ├── feed │ ├── feed.njk │ └── htaccess.njk ├── img │ ├── .gitkeep │ ├── fries.png │ ├── tgif.png │ └── tgif.svg ├── index.njk ├── menu │ └── menu.md ├── page-list.njk ├── posts │ ├── firstpost.md │ ├── fourthpost.md │ ├── posts.json │ ├── secondpost.md │ └── thirdpost.md ├── sitemap.xml.njk ├── tags-list.njk └── tags.njk └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | -------------------------------------------------------------------------------- /.eleventy.js: -------------------------------------------------------------------------------- 1 | const { DateTime } = require("luxon"); 2 | const fs = require("fs"); 3 | const pluginRss = require("@11ty/eleventy-plugin-rss"); 4 | const pluginSyntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight"); 5 | const pluginNavigation = require("@11ty/eleventy-navigation"); 6 | const markdownIt = require("markdown-it"); 7 | const markdownItAnchor = require("markdown-it-anchor"); 8 | require("dotenv").config(); 9 | const THRESHOLD = 0.5 10 | 11 | module.exports = function(eleventyConfig) { 12 | 13 | let env = process.env.ELEVENTY_ENV; 14 | 15 | eleventyConfig.addPlugin(pluginRss); 16 | eleventyConfig.addPlugin(pluginSyntaxHighlight); 17 | eleventyConfig.addPlugin(pluginNavigation); 18 | 19 | eleventyConfig.setDataDeepMerge(true); 20 | 21 | eleventyConfig.addLayoutAlias("post", "./src/layouts/post.njk"); 22 | 23 | eleventyConfig.addFilter("readableDate", dateObj => { 24 | return DateTime.fromJSDate(dateObj, {zone: 'utc'}).toFormat("dd LLL yyyy"); 25 | }); 26 | 27 | eleventyConfig.addFilter('prefetchNextURL', (entry, url) => { 28 | // check threshold // 29 | if (entry.nextPageCertainty > 0.4) { 30 | return entry.nextPagePath.replace("-", "") 31 | } 32 | }) 33 | 34 | // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-date-string 35 | eleventyConfig.addFilter('htmlDateString', (dateObj) => { 36 | return DateTime.fromJSDate(dateObj, {zone: 'utc'}).toFormat('yyyy-LL-dd'); 37 | }); 38 | 39 | // Get the first `n` elements of a collection. 40 | eleventyConfig.addFilter("head", (array, n) => { 41 | if( n < 0 ) { 42 | return array.slice(n); 43 | } 44 | 45 | return array.slice(0, n); 46 | }); 47 | 48 | eleventyConfig.addCollection("tagList", require("./src/_11ty/getTagList")); 49 | 50 | eleventyConfig.addPassthroughCopy("src/img"); 51 | eleventyConfig.addPassthroughCopy("src/css"); 52 | 53 | /* Markdown Overrides */ 54 | let markdownLibrary = markdownIt({ 55 | html: true, 56 | breaks: true, 57 | linkify: true 58 | }).use(markdownItAnchor, { 59 | permalink: true, 60 | permalinkClass: "direct-link", 61 | permalinkSymbol: "#" 62 | }); 63 | eleventyConfig.setLibrary("md", markdownLibrary); 64 | 65 | // Browsersync Overrides 66 | eleventyConfig.setBrowserSyncConfig({ 67 | callbacks: { 68 | ready: function(err, browserSync) { 69 | const content_404 = fs.readFileSync('_site/404.html'); 70 | 71 | browserSync.addMiddleware("*", (req, res) => { 72 | // Provides the 404 content without redirect. 73 | res.write(content_404); 74 | res.end(); 75 | }); 76 | } 77 | } 78 | }); 79 | 80 | return { 81 | templateFormats: [ 82 | "md", 83 | "njk", 84 | "html", 85 | "liquid" 86 | ], 87 | 88 | // If your site lives in a different subdirectory, change this. 89 | // Leading or trailing slashes are all normalized away, so don’t worry about those. 90 | 91 | // If you don’t have a subdirectory, use "" or "/" (they do the same thing) 92 | // This is only used for link URLs (it does not affect your file structure) 93 | // You can also pass this in on the command line using `--pathprefix` 94 | 95 | // pathPrefix: "/", 96 | 97 | markdownTemplateEngine: "liquid", 98 | htmlTemplateEngine: "njk", 99 | dataTemplateEngine: "njk", 100 | 101 | // These are all optional, defaults are shown: 102 | dir: { 103 | input: "src", 104 | includes: "_includes", 105 | data: "_data", 106 | output: "_site" 107 | } 108 | }; 109 | }; 110 | -------------------------------------------------------------------------------- /.eleventyignore: -------------------------------------------------------------------------------- 1 | README.md 2 | _11ty/ 3 | functions/ 4 | /.netlify/functions/google-analytics -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | open_collective: 11ty 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _site/ 2 | node_modules/ 3 | package-lock.json 4 | .env 5 | *.pem 6 | *.p12 7 | 8 | .DS_Store 9 | # Local Netlify folder 10 | .netlify -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | before_script: 5 | - npm install @11ty/eleventy -g 6 | script: eleventy --pathprefix="/eleventy-base-blog/" 7 | deploy: 8 | local-dir: _site 9 | provider: pages 10 | skip-cleanup: true 11 | github-token: $GITHUB_TOKEN # Set in travis-ci.org dashboard, marked secure 12 | keep-history: true 13 | on: 14 | branch: master 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Zach Leatherman @zachleat 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 | # Predictive Prefetching Demo 2 | 3 | An example site showing how to do predictive prefetches with 11ty. 4 | 5 | 6 | ## Getting Started 7 | 8 | ### 1. Clone this repository: 9 | 10 | ``` 11 | git clone git@github.com:shortdiv/cant-dutch-this.git my-first-predictive-prefetch 12 | ``` 13 | 14 | 15 | ### 2. Navigate to the directory 16 | 17 | ``` 18 | cd my-first-predictive-prefetch 19 | ``` 20 | 21 | Specifically have a look at `.eleventy.js` to see if you want to configure any Eleventy options differently. 22 | 23 | ### 3. Install dependencies 24 | 25 | ``` 26 | npm install OR yarn 27 | ``` 28 | 29 | ### 4. Edit _data/metadata.json 30 | 31 | ### 5. Run Eleventy 32 | 33 | ``` 34 | npm run build OR yarn build 35 | ``` 36 | 37 | Or build and host locally for local development 38 | ``` 39 | npm run serve OR yarn serve 40 | ``` 41 | 42 | Or build automatically when a template changes: 43 | ``` 44 | npm run watch OR yarn watch 45 | ``` 46 | 47 | Or in debug mode: 48 | ``` 49 | DEBUG=* npx eleventy 50 | ``` 51 | 52 | ### Authentication Notes 53 | ## Create Your Credentials 54 | 55 | ### Create a Service Account 56 | Go to the Credentials page in the Google APIs console. 57 | 58 | If you don't have an existing project, click "Create" to create a new project. Otherwise, select an existing project from the project dropdown. 59 | 60 | Select "Service Account key" from the "Create credentials" dropdown. 61 | 62 | Fill out the form for creating a service account key: 63 | 64 | Service account dropdown: Select "New Service Account". 65 | Service account name: Create a service account a name. 66 | Role: Select "Service Accounts" > "Service Account User". 67 | Service account ID: Leave as is. 68 | Key type: Select P12 key. 69 | Click Create. 70 | 71 | ### Setup Your Private Key 72 | Note the private key password, you'll need this when you're converting your password to a pem file. 73 | 74 | Move this key into the root directory for this project, outside of /src. 75 | 76 | Generate a *.p12 certificate by running this command from the directory for this project: 77 | ``` 78 | $ openssl pkcs12 -in *.p12 -out key.pem -nodes -clcerts 79 | ``` 80 | 81 | ### Configure GA 82 | 83 | You now need to add this service account to GA for the proper permissions. 84 | 85 | In your GA account, create a new user and add the necessary permissions. 86 | 87 | Add a new user. (Admin > User Management > + > Add New Users) 88 | Email Address: example@example-project-123456.iam.gserviceaccount.com. 89 | 90 | Permissions: Select "Read & Analyze." 91 | 92 | **Note that you may need to Enable the Google Analytics Reporting API in your project** 93 | 94 | 95 | ### Configure env vars 96 | We'll need to now configure our env variables. To do this in 11ty, we'll lean on nodeenv, which should alr be a dependency in this project. To take advantage of this, add `VIEW_ID` and `SERVICE_ACCOUNT` to a .env file in your root dir. You can find these values in the view column of the accounts dropdown in GA. 97 | 98 | VIEW_ID=12345678 99 | SERVICE_ACCOUNT_EMAIL=cant-dutch-this@some-example-account-123456.iam.gserviceaccount.com 100 | -------------------------------------------------------------------------------- /functions/google-analytics/google-analytics.js: -------------------------------------------------------------------------------- 1 | const {google} = require('googleapis') 2 | const fs = require('fs') 3 | const path = require('path') 4 | 5 | const authClient = new google.auth.JWT({ 6 | email: process.env.SERVICE_ACCOUNT_EMAIL, 7 | key: fs.readFileSync(path.join(__dirname, "../../key1.pem"), 'utf8'), 8 | scopes: ['https://www.googleapis.com/auth/analytics.readonly'] 9 | }) 10 | 11 | const headers = { 12 | "Access-Control-Allow-Origin": "*", 13 | "Access-Control-Allow-Headers": "Content-Type" 14 | } 15 | 16 | const queryParams = { 17 | resource: { 18 | reportRequests: [ 19 | { 20 | viewId: process.env.VIEW_ID, 21 | dateRanges: [{startDate: '30daysAgo', endDate: 'yesterday'}], 22 | metrics: [ 23 | {expression: 'ga:pageviews'}, 24 | {expression: 'ga:exits'} 25 | ], 26 | dimensions: [ 27 | {name: 'ga:previousPagePath'}, 28 | {name: 'ga:pagePath'} 29 | ], 30 | orderBys: [ 31 | {fieldName: 'ga:previousPagePath', sortOrder: 'ASCENDING'}, 32 | {fieldName: 'ga:pageviews', sortOrder: 'DESCENDING'} 33 | ], 34 | pageSize: 10000 35 | } 36 | ] 37 | } 38 | } 39 | 40 | 41 | exports.handler = async (event, context, callback) => { 42 | try { 43 | await authClient.authorize() 44 | const analytics = google.analyticsreporting({ 45 | version: 'v4', 46 | auth: authClient 47 | }) 48 | const response = await analytics.reports.batchGet(queryParams) 49 | let [report] = response.data.reports 50 | 51 | let {rows} = report.data 52 | 53 | const data = {} 54 | 55 | for (let row of rows) { 56 | let [previousPagePath, pagePath] = row.dimensions 57 | let pageviews = +row.metrics[0].values[0] 58 | let exits = +row.metrics[0].values[1] 59 | 60 | if (/\?.*$/.test(pagePath) || /\?.*$/.test(previousPagePath)) { 61 | pagePath = pagePath.replace(/\?.*$/, '') 62 | previousPagePath = previousPagePath.replace(/\?.*$/, '') 63 | } 64 | 65 | // Ignore pageviews where the current and previous pages are the same. 66 | if (previousPagePath == pagePath) continue 67 | 68 | if (previousPagePath != '(entrance)') { 69 | data[previousPagePath] = data[previousPagePath] || { 70 | pagePath: previousPagePath, 71 | pageviews: 0, 72 | exits: 0, 73 | nextPageviews: 0, 74 | nextExits: 0, 75 | nextPages: {} 76 | } 77 | 78 | data[previousPagePath].nextPageviews += pageviews 79 | data[previousPagePath].nextExits += exits 80 | 81 | if (data[previousPagePath].nextPages[pagePath]) { 82 | data[previousPagePath].nextPages[pagePath] += pageviews 83 | } else { 84 | data[previousPagePath].nextPages[pagePath] = pageviews 85 | } 86 | } 87 | 88 | data[pagePath] = data[pagePath] || { 89 | pagePath: pagePath, 90 | pageviews: 0, 91 | exits: 0, 92 | nextPageviews: 0, 93 | nextExits: 0, 94 | nextPages: {} 95 | } 96 | 97 | data[pagePath].pageviews += pageviews 98 | data[pagePath].exits += exits 99 | } 100 | // Converts each pages `nextPages` object into a sorted array. 101 | Object.keys(data).forEach((pagePath) => { 102 | const page = data[pagePath] 103 | page.nextPages = Object.keys(page.nextPages) 104 | .map((pagePath) => ({ 105 | pagePath, 106 | pageviews: page.nextPages[pagePath] 107 | })) 108 | .sort((a, b) => { 109 | return b.pageviews - a.pageviews 110 | }) 111 | }) 112 | // Creates a sorted array of pages from the data object. 113 | const pages = Object.keys(data) 114 | .filter((pagePath) => data[pagePath].nextPageviews > 0) 115 | .map((pagePath) => { 116 | const page = data[pagePath] 117 | const {exits, nextPageviews, nextPages} = page 118 | page.percentExits = exits / (exits + nextPageviews) 119 | page.topNextPageProbability = 120 | nextPages[0].pageviews / (exits + nextPageviews) 121 | return page 122 | }) 123 | .sort((a, b) => { 124 | return b.pageviews - a.pageviews 125 | }) 126 | 127 | const aggregatePages = async (pages) => { 128 | const predictions = [] 129 | for (let page of pages) { 130 | const prediction = { 131 | pagePath: page.pagePath, 132 | nextPagePath: page.nextPages[0] ? page.nextPages[0].pagePath : '', 133 | nextPageCertainty: page.nextPages[0] ? page.topNextPageProbability : '' 134 | } 135 | predictions.push(prediction) 136 | } 137 | return predictions 138 | } 139 | 140 | const aggPages = await aggregatePages(pages); 141 | 142 | return { 143 | statusCode: 200, 144 | headers, 145 | body: JSON.stringify(aggPages) 146 | } 147 | } catch (err) { 148 | return { 149 | statusCode: 400, 150 | headers, 151 | body: JSON.stringify({ 152 | status: err 153 | }) 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /functions/google-analytics/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-analytics", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "google-analytics.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "googleapis": "^45.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /functions/google-analytics/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | abort-controller@^3.0.0: 6 | version "3.0.0" 7 | resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" 8 | integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== 9 | dependencies: 10 | event-target-shim "^5.0.0" 11 | 12 | agent-base@^4.3.0: 13 | version "4.3.0" 14 | resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" 15 | integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== 16 | dependencies: 17 | es6-promisify "^5.0.0" 18 | 19 | arrify@^2.0.0: 20 | version "2.0.1" 21 | resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" 22 | integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== 23 | 24 | base64-js@^1.3.0: 25 | version "1.3.1" 26 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" 27 | integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== 28 | 29 | bignumber.js@^7.0.0: 30 | version "7.2.1" 31 | resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" 32 | integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ== 33 | 34 | buffer-equal-constant-time@1.0.1: 35 | version "1.0.1" 36 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" 37 | integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= 38 | 39 | debug@^3.1.0: 40 | version "3.2.6" 41 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" 42 | integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== 43 | dependencies: 44 | ms "^2.1.1" 45 | 46 | ecdsa-sig-formatter@1.0.11: 47 | version "1.0.11" 48 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" 49 | integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== 50 | dependencies: 51 | safe-buffer "^5.0.1" 52 | 53 | es6-promise@^4.0.3: 54 | version "4.2.8" 55 | resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" 56 | integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== 57 | 58 | es6-promisify@^5.0.0: 59 | version "5.0.0" 60 | resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" 61 | integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= 62 | dependencies: 63 | es6-promise "^4.0.3" 64 | 65 | event-target-shim@^5.0.0: 66 | version "5.0.1" 67 | resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" 68 | integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== 69 | 70 | extend@^3.0.2: 71 | version "3.0.2" 72 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" 73 | integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== 74 | 75 | fast-text-encoding@^1.0.0: 76 | version "1.0.0" 77 | resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz#3e5ce8293409cfaa7177a71b9ca84e1b1e6f25ef" 78 | integrity sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ== 79 | 80 | gaxios@^2.0.1, gaxios@^2.1.0: 81 | version "2.1.0" 82 | resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-2.1.0.tgz#b5d04ec19bf853d4589ccc2e7d61f0f2ab62afee" 83 | integrity sha512-Gtpb5sdQmb82sgVkT2GnS2n+Kx4dlFwbeMYcDlD395aEvsLCSQXJJcHt7oJ2LrGxDEAeiOkK79Zv2A8Pzt6CFg== 84 | dependencies: 85 | abort-controller "^3.0.0" 86 | extend "^3.0.2" 87 | https-proxy-agent "^3.0.0" 88 | is-stream "^2.0.0" 89 | node-fetch "^2.3.0" 90 | 91 | gcp-metadata@^3.2.0: 92 | version "3.2.2" 93 | resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-3.2.2.tgz#dcac6bf65775d5caa3a2e161469c0af068849256" 94 | integrity sha512-vR7kcJMCYJG/mYWp/a1OszdOqnLB/XW1GorWW1hc1lWVNL26L497zypWb9cG0CYDQ4Bl1Wk0+fSZFFjwJlTQgQ== 95 | dependencies: 96 | gaxios "^2.1.0" 97 | json-bigint "^0.3.0" 98 | 99 | google-auth-library@^5.2.0: 100 | version "5.5.1" 101 | resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-5.5.1.tgz#2bf5ade93cb9d00c860d3fb15798db33b39a53a7" 102 | integrity sha512-zCtjQccWS/EHYyFdXRbfeSGM/gW+d7uMAcVnvXRnjBXON5ijo6s0nsObP0ifqileIDSbZjTlLtgo+UoN8IFJcg== 103 | dependencies: 104 | arrify "^2.0.0" 105 | base64-js "^1.3.0" 106 | fast-text-encoding "^1.0.0" 107 | gaxios "^2.1.0" 108 | gcp-metadata "^3.2.0" 109 | gtoken "^4.1.0" 110 | jws "^3.1.5" 111 | lru-cache "^5.0.0" 112 | 113 | google-p12-pem@^2.0.0: 114 | version "2.0.3" 115 | resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-2.0.3.tgz#14ecd78a94bd03bf86d74d9d0724787e85c7731f" 116 | integrity sha512-Tq2kBCANxYYPxaBpTgCpRfdoPs9+/lNzc/Iaee4kuMVW5ascD+HwhpBsTLwH85C9Ev4qfB8KKHmpPQYyD2vg2w== 117 | dependencies: 118 | node-forge "^0.9.0" 119 | 120 | googleapis-common@^3.1.0: 121 | version "3.1.1" 122 | resolved "https://registry.yarnpkg.com/googleapis-common/-/googleapis-common-3.1.1.tgz#387e75d914a3e09fec2cb3daf321e04194c8a379" 123 | integrity sha512-sXNS9oJifZOk2Pa6SzxoSfv0Mj9y/qIOsVV7D8WHuH//90CXNnpR/nCYVa+KcPMDT9ONq21sbtvjfKATMV1Bug== 124 | dependencies: 125 | extend "^3.0.2" 126 | gaxios "^2.0.1" 127 | google-auth-library "^5.2.0" 128 | qs "^6.7.0" 129 | url-template "^2.0.8" 130 | uuid "^3.3.2" 131 | 132 | googleapis@^45.0.0: 133 | version "45.0.0" 134 | resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-45.0.0.tgz#27917682f1eee9502164384ceacdc08a17ba9ffb" 135 | integrity sha512-v5+4Cw+MnG3z9FRNTdOCcOWBvzhp0zcbapI3D1E1ndykIRA7tE8oflSBRyyVpX6BDhRGSqNu2lU8G15Fe+Ih4g== 136 | dependencies: 137 | google-auth-library "^5.2.0" 138 | googleapis-common "^3.1.0" 139 | 140 | gtoken@^4.1.0: 141 | version "4.1.3" 142 | resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-4.1.3.tgz#efa9e42f59d02731f15de466b09331d7afc393cf" 143 | integrity sha512-ofW+FiXjswyKdkjMcDbe6E4K7cDDdE82dGDhZIc++kUECqaE7MSErf6arJPAjcnYn1qxE1/Ti06qQuqgVusovQ== 144 | dependencies: 145 | gaxios "^2.1.0" 146 | google-p12-pem "^2.0.0" 147 | jws "^3.1.5" 148 | mime "^2.2.0" 149 | 150 | https-proxy-agent@^3.0.0: 151 | version "3.0.1" 152 | resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz#b8c286433e87602311b01c8ea34413d856a4af81" 153 | integrity sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg== 154 | dependencies: 155 | agent-base "^4.3.0" 156 | debug "^3.1.0" 157 | 158 | is-stream@^2.0.0: 159 | version "2.0.0" 160 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" 161 | integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== 162 | 163 | json-bigint@^0.3.0: 164 | version "0.3.0" 165 | resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-0.3.0.tgz#0ccd912c4b8270d05f056fbd13814b53d3825b1e" 166 | integrity sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4= 167 | dependencies: 168 | bignumber.js "^7.0.0" 169 | 170 | jwa@^1.4.1: 171 | version "1.4.1" 172 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" 173 | integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== 174 | dependencies: 175 | buffer-equal-constant-time "1.0.1" 176 | ecdsa-sig-formatter "1.0.11" 177 | safe-buffer "^5.0.1" 178 | 179 | jws@^3.1.5: 180 | version "3.2.2" 181 | resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" 182 | integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== 183 | dependencies: 184 | jwa "^1.4.1" 185 | safe-buffer "^5.0.1" 186 | 187 | lru-cache@^5.0.0: 188 | version "5.1.1" 189 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" 190 | integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== 191 | dependencies: 192 | yallist "^3.0.2" 193 | 194 | mime@^2.2.0: 195 | version "2.4.4" 196 | resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" 197 | integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== 198 | 199 | ms@^2.1.1: 200 | version "2.1.2" 201 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 202 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 203 | 204 | node-fetch@^2.3.0: 205 | version "2.6.0" 206 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" 207 | integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== 208 | 209 | node-forge@^0.9.0: 210 | version "0.9.1" 211 | resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.1.tgz#775368e6846558ab6676858a4d8c6e8d16c677b5" 212 | integrity sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ== 213 | 214 | qs@^6.7.0: 215 | version "6.9.1" 216 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.1.tgz#20082c65cb78223635ab1a9eaca8875a29bf8ec9" 217 | integrity sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA== 218 | 219 | safe-buffer@^5.0.1: 220 | version "5.2.0" 221 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" 222 | integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== 223 | 224 | url-template@^2.0.8: 225 | version "2.0.8" 226 | resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" 227 | integrity sha1-/FZaPMy/93MMd19WQflVV5FDnyE= 228 | 229 | uuid@^3.3.2: 230 | version "3.3.3" 231 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" 232 | integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== 233 | 234 | yallist@^3.0.2: 235 | version "3.1.1" 236 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" 237 | integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== 238 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "_site" 3 | functions="functions" 4 | command = "DEBUG=* eleventy" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eleventy-base-blog", 3 | "version": "5.0.0", 4 | "description": "A starter repository for a blog web site using the Eleventy static site generator.", 5 | "scripts": { 6 | "build": "npx eleventy", 7 | "watch": "npx eleventy --watch", 8 | "serve": "npx eleventy --serve", 9 | "debug": "DEBUG=* npx eleventy" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/11ty/eleventy-base-blog.git" 14 | }, 15 | "author": { 16 | "name": "Divya Sasidharan", 17 | "email": "hello@shortdiv.com", 18 | "url": "shortdiv.com" 19 | }, 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/11ty/eleventy-base-blog/issues" 23 | }, 24 | "homepage": "https://github.com/11ty/eleventy-base-blog#readme", 25 | "devDependencies": { 26 | "@11ty/eleventy": "^0.9.0", 27 | "@11ty/eleventy-navigation": "^0.1.1", 28 | "@11ty/eleventy-plugin-rss": "^1.0.7", 29 | "@11ty/eleventy-plugin-syntaxhighlight": "^2.0.3", 30 | "luxon": "^1.12.0", 31 | "markdown-it": "^8.4.2", 32 | "markdown-it-anchor": "^5.0.2" 33 | }, 34 | "dependencies": { 35 | "dotenv": "^8.2.0", 36 | "googleapis": "^45.0.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/404.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/home.njk 3 | permalink: 404.html 4 | eleventyExcludeFromCollections: true 5 | --- 6 | # Content not found. 7 | 8 | Go home. 9 | 10 | {% comment %} 11 | Read more: https://www.11ty.io/docs/quicktips/not-found/ 12 | 13 | This will work for both GitHub pages and Netlify: 14 | 15 | * https://help.github.com/articles/creating-a-custom-404-page-for-your-github-pages-site/ 16 | * https://www.netlify.com/docs/redirects/#custom-404 17 | {% endcomment %} 18 | -------------------------------------------------------------------------------- /src/_11ty/getTagList.js: -------------------------------------------------------------------------------- 1 | module.exports = function(collection) { 2 | let tagSet = new Set(); 3 | collection.getAll().forEach(function(item) { 4 | if( "tags" in item.data ) { 5 | let tags = item.data.tags; 6 | 7 | tags = tags.filter(function(item) { 8 | switch(item) { 9 | // this list should match the `filter` list in tags.njk 10 | case "all": 11 | case "nav": 12 | case "post": 13 | case "posts": 14 | return false; 15 | } 16 | 17 | return true; 18 | }); 19 | 20 | for (const tag of tags) { 21 | tagSet.add(tag); 22 | } 23 | } 24 | }); 25 | 26 | // returning an array in addCollection works in Eleventy 0.5.3 27 | return [...tagSet]; 28 | }; 29 | -------------------------------------------------------------------------------- /src/_data/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Can't Dutch This", 3 | "url": "https://myurl.com/", 4 | "description": "I am writing about my experiences as a naval navel-gazer.", 5 | "feed": { 6 | "subtitle": "I am writing about my experiences as a naval navel-gazer.", 7 | "filename": "feed.xml", 8 | "path": "/feed/feed.xml", 9 | "url": "https://myurl.com/feed/feed.xml", 10 | "id": "https://myurl.com/" 11 | }, 12 | "author": { 13 | "name": "Your Name Here", 14 | "email": "youremailaddress@example.com" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/_data/prefetchLinks.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const {google} = require('googleapis') 3 | const fs = require('fs') 4 | const path = require('path') 5 | 6 | const authClient = new google.auth.JWT({ 7 | email: process.env.SERVICE_ACCOUNT_EMAIL, 8 | key: fs.readFileSync(path.join(__dirname, "../../key1.pem"), 'utf8'), 9 | scopes: ['https://www.googleapis.com/auth/analytics.readonly'] 10 | }) 11 | 12 | const queryParams = { 13 | resource: { 14 | reportRequests: [ 15 | { 16 | viewId: process.env.VIEW_ID, 17 | dateRanges: [{startDate: '30daysAgo', endDate: 'yesterday'}], 18 | metrics: [ 19 | {expression: 'ga:pageviews'}, 20 | {expression: 'ga:exits'} 21 | ], 22 | dimensions: [ 23 | {name: 'ga:previousPagePath'}, 24 | {name: 'ga:pagePath'} 25 | ], 26 | orderBys: [ 27 | {fieldName: 'ga:previousPagePath', sortOrder: 'ASCENDING'}, 28 | {fieldName: 'ga:pageviews', sortOrder: 'DESCENDING'} 29 | ], 30 | pageSize: 10000 31 | } 32 | ] 33 | } 34 | } 35 | 36 | module.exports = async function() { 37 | try { 38 | await authClient.authorize() 39 | const analytics = google.analyticsreporting({ 40 | version: 'v4', 41 | auth: authClient 42 | }) 43 | const response = await analytics.reports.batchGet(queryParams) 44 | let [report] = response.data.reports 45 | 46 | let {rows} = report.data 47 | 48 | const data = {} 49 | 50 | for (let row of rows) { 51 | let [previousPagePath, pagePath] = row.dimensions 52 | let pageviews = +row.metrics[0].values[0] 53 | let exits = +row.metrics[0].values[1] 54 | 55 | if (/\?.*$/.test(pagePath) || /\?.*$/.test(previousPagePath)) { 56 | pagePath = pagePath.replace(/\?.*$/, '') 57 | previousPagePath = previousPagePath.replace(/\?.*$/, '') 58 | } 59 | 60 | // Ignore pageviews where the current and previous pages are the same. 61 | if (previousPagePath == pagePath) continue 62 | 63 | if (previousPagePath != '(entrance)') { 64 | data[previousPagePath] = data[previousPagePath] || { 65 | pagePath: previousPagePath, 66 | pageviews: 0, 67 | exits: 0, 68 | nextPageviews: 0, 69 | nextExits: 0, 70 | nextPages: {} 71 | } 72 | 73 | data[previousPagePath].nextPageviews += pageviews 74 | data[previousPagePath].nextExits += exits 75 | 76 | if (data[previousPagePath].nextPages[pagePath]) { 77 | data[previousPagePath].nextPages[pagePath] += pageviews 78 | } else { 79 | data[previousPagePath].nextPages[pagePath] = pageviews 80 | } 81 | } 82 | 83 | data[pagePath] = data[pagePath] || { 84 | pagePath: pagePath, 85 | pageviews: 0, 86 | exits: 0, 87 | nextPageviews: 0, 88 | nextExits: 0, 89 | nextPages: {} 90 | } 91 | 92 | data[pagePath].pageviews += pageviews 93 | data[pagePath].exits += exits 94 | } 95 | // Converts each pages `nextPages` object into a sorted array. 96 | Object.keys(data).forEach((pagePath) => { 97 | const page = data[pagePath] 98 | page.nextPages = Object.keys(page.nextPages) 99 | .map((pagePath) => ({ 100 | pagePath, 101 | pageviews: page.nextPages[pagePath] 102 | })) 103 | .sort((a, b) => { 104 | return b.pageviews - a.pageviews 105 | }) 106 | }) 107 | // Creates a sorted array of pages from the data object. 108 | const pages = Object.keys(data) 109 | .filter((pagePath) => data[pagePath].nextPageviews > 0) 110 | .map((pagePath) => { 111 | const page = data[pagePath] 112 | const {exits, nextPageviews, nextPages} = page 113 | page.percentExits = exits / (exits + nextPageviews) 114 | page.topNextPageProbability = 115 | nextPages[0].pageviews / (exits + nextPageviews) 116 | return page 117 | }) 118 | .sort((a, b) => { 119 | return b.pageviews - a.pageviews 120 | }) 121 | 122 | const aggregatePages = async (pages) => { 123 | const predictions = [] 124 | for (let page of pages) { 125 | const prediction = { 126 | pagePath: page.pagePath, 127 | nextPagePath: page.nextPages[0] ? page.nextPages[0].pagePath : '', 128 | nextPageCertainty: page.nextPages[0] ? page.topNextPageProbability : '' 129 | } 130 | predictions.push(prediction) 131 | } 132 | return predictions 133 | } 134 | 135 | const aggPages = await aggregatePages(pages); 136 | console.log(aggPages) 137 | return aggPages 138 | } catch (err) { 139 | console.log(err) 140 | return err 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/_includes/layouts/about.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/base.njk 3 | templateClass: tmpl-menu 4 | --- 5 | 6 |

{{ title }}

7 | 8 | {{ content | safe }} 9 | -------------------------------------------------------------------------------- /src/_includes/layouts/base.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ renderData.title or title or metadata.title }} 7 | 8 | 9 | 10 | 11 | 12 | {% for entry in prefetchLinks %} 13 | {# {% if entry.pagePath.replace("-", "") == page.url %} #} 14 | 15 | {# {% endif %} #} 16 | {%- endfor -%} 17 | 18 | 19 |
20 |

21 | 22 | 23 | {{ metadata.title }} 24 | 25 |

26 | 35 |
36 | 37 | 38 | {{ content | safe }} 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 54 | 55 | -------------------------------------------------------------------------------- /src/_includes/layouts/contact.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/base.njk 3 | templateClass: tmpl-contact 4 | --- 5 | 6 |

{{ title }}

7 | 8 | {{ content | safe }} 9 | -------------------------------------------------------------------------------- /src/_includes/layouts/home.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/base.njk 3 | templateClass: tmpl-home 4 | --- 5 | 6 | {{ content | safe }} 7 | -------------------------------------------------------------------------------- /src/_includes/layouts/menu.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/base.njk 3 | templateClass: tmpl-menu 4 | --- 5 | 6 |

{{ title }}

7 | 8 | {{ content | safe }} 9 | -------------------------------------------------------------------------------- /src/_includes/layouts/post.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/base.njk 3 | templateClass: tmpl-post 4 | --- 5 |

{{ title }}

6 | 7 | {{ content | safe }} 8 | 9 |

← Home

10 | -------------------------------------------------------------------------------- /src/_includes/postslist.njk: -------------------------------------------------------------------------------- 1 |
    2 | {% for post in postslist | reverse %} 3 |
  1. 4 | {% if post.data.title %}{{ post.data.title }}{% else %}{{ post.url }}{% endif %} 5 | 6 | {% for tag in post.data.tags %} 7 | {%- if tag != "posts" -%} 8 | {% set tagUrl %}/tags/{{ tag }}/{% endset %} 9 | {{ tag }} 10 | {%- endif -%} 11 | {% endfor %} 12 |
  2. 13 | {% endfor %} 14 |
15 | -------------------------------------------------------------------------------- /src/about/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/about.njk 3 | title: About Us 4 | templateClass: tmpl-post 5 | eleventyNavigation: 6 | key: About Us 7 | order: 2 8 | --- 9 | 10 | We make good fries 11 | -------------------------------------------------------------------------------- /src/archive.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/home.njk 3 | permalink: /posts/ 4 | eleventyNavigation: 5 | key: Blog 6 | order: 3 7 | --- 8 | 9 |

Blog

10 | 11 | {% set postslist = collections.posts %} 12 | {% include "postslist.njk" %} 13 | -------------------------------------------------------------------------------- /src/contact/contact.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/contact.njk 3 | title: Contact Us 4 | templateClass: tmpl-contact 5 | eleventyNavigation: 6 | key: Contact 7 | order: 2 8 | --- -------------------------------------------------------------------------------- /src/css/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --red: #C5004A; 3 | --darkred: #7F0036; 4 | --lightgray: #e0e0e0; 5 | --gray: #C0C0C0; 6 | --darkgray: #333; 7 | --navy: #17050F; 8 | --blue: #082840; 9 | --white: #fff; 10 | } 11 | * { 12 | box-sizing: border-box; 13 | } 14 | html, 15 | body { 16 | padding: 0; 17 | margin: 0; 18 | font-family: sans-serif; 19 | color: var(--darkgray); 20 | background-color: var(--white); 21 | } 22 | p:last-child { 23 | margin-bottom: 0; 24 | } 25 | p, 26 | .tmpl-post li, 27 | img { 28 | max-width: 37.5em; /* 600px /16 */ 29 | } 30 | p, 31 | .tmpl-post li { 32 | line-height: 1.45; 33 | } 34 | a[href] { 35 | color: var(--blue); 36 | } 37 | a[href]:visited { 38 | color: var(--navy); 39 | } 40 | main { 41 | padding: 1rem; 42 | } 43 | main :first-child { 44 | margin-top: 0; 45 | } 46 | header { 47 | padding: 40px 40px 0; 48 | } 49 | header:after { 50 | content: ""; 51 | display: table; 52 | clear: both; 53 | } 54 | table { 55 | margin: 1em 0; 56 | } 57 | table td, 58 | table th { 59 | padding-right: 1em; 60 | } 61 | 62 | pre, 63 | code { 64 | font-family: Consolas, Menlo, Monaco, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", "Courier New", Courier, monospace; 65 | line-height: 1.5; 66 | } 67 | pre { 68 | font-size: 14px; 69 | line-height: 1.375; 70 | direction: ltr; 71 | text-align: left; 72 | white-space: pre; 73 | word-spacing: normal; 74 | word-break: normal; 75 | -moz-tab-size: 2; 76 | -o-tab-size: 2; 77 | tab-size: 2; 78 | -webkit-hyphens: none; 79 | -moz-hyphens: none; 80 | -ms-hyphens: none; 81 | hyphens: none; 82 | padding: 1em; 83 | margin: .5em 0; 84 | background-color: #f6f6f6; 85 | } 86 | .highlight-line { 87 | display: block; 88 | padding: 0.125em 1em; 89 | text-decoration: none; /* override del, ins, mark defaults */ 90 | color: inherit; /* override del, ins, mark defaults */ 91 | } 92 | 93 | /* allow highlighting empty lines */ 94 | .highlight-line:empty:before { 95 | content: " "; 96 | } 97 | /* avoid double line breaks when using display: block; */ 98 | .highlight-line + br { 99 | display: none; 100 | } 101 | 102 | .highlight-line-isdir { 103 | color: #b0b0b0; 104 | background-color: #222; 105 | } 106 | .highlight-line-active { 107 | background-color: #444; 108 | background-color: hsla(0, 0%, 27%, .8); 109 | } 110 | .highlight-line-add { 111 | background-color: #45844b; 112 | } 113 | .highlight-line-remove { 114 | background-color: #902f2f; 115 | } 116 | 117 | /* Header */ 118 | .home { 119 | padding: 0 1rem; 120 | float: left; 121 | margin: 1rem 0; /* 16px /16 */ 122 | font-size: 1em; /* 16px /16 */ 123 | } 124 | .home :link:not(:hover) { 125 | text-decoration: none; 126 | } 127 | 128 | /* Nav */ 129 | .nav { 130 | padding: 0; 131 | list-style: none; 132 | float: left; 133 | margin-left: 1em; 134 | } 135 | .nav-item { 136 | display: inline-block; 137 | margin-right: 1em; 138 | } 139 | .nav-item a[href]:not(:hover) { 140 | text-decoration: none; 141 | } 142 | .nav-item-active { 143 | font-weight: 700; 144 | text-decoration: underline; 145 | } 146 | 147 | /* Posts list */ 148 | .postlist { 149 | list-style: none; 150 | padding: 0; 151 | } 152 | .postlist-item { 153 | counter-increment: start-from -1; 154 | } 155 | .postlist-item:before { 156 | display: inline-block; 157 | pointer-events: none; 158 | content: "" counter(start-from, decimal-leading-zero) ". "; 159 | line-height: 100%; 160 | text-align: right; 161 | } 162 | .postlist-date, 163 | .postlist-item:before { 164 | font-size: 0.8125em; /* 13px /16 */ 165 | color: var(--darkgray); 166 | } 167 | .postlist-date { 168 | word-spacing: -0.5px; 169 | } 170 | .postlist-link { 171 | display: inline-block; 172 | padding: 0.25em 0.1875em; /* 4px 3px /16 */ 173 | } 174 | .postlist-item-active .postlist-link { 175 | font-weight: bold; 176 | } 177 | .tmpl-home .postlist-link { 178 | font-size: 1.1875em; /* 19px /16 */ 179 | font-weight: 700; 180 | } 181 | 182 | 183 | /* Tags */ 184 | .tag { 185 | display: inline-block; 186 | vertical-align: text-top; 187 | text-transform: uppercase; 188 | font-size: 0.625em; /* 10px /16 */ 189 | padding: 2px 4px; 190 | margin-left: 0.8em; /* 8px /10 */ 191 | background-color: var(--red); 192 | color: var(--white); 193 | border-radius: 0.25em; /* 3px /12 */ 194 | text-decoration: none; 195 | } 196 | a[href].tag, 197 | a[href].tag:visited { 198 | color: #fff; 199 | } 200 | 201 | /* Warning */ 202 | .warning { 203 | background-color: #ffc; 204 | padding: 1em 0.625em; /* 16px 10px /16 */ 205 | } 206 | .warning ol:only-child { 207 | margin: 0; 208 | } 209 | 210 | /* Direct Links / Markdown Headers */ 211 | .direct-link { 212 | font-family: sans-serif; 213 | text-decoration: none; 214 | font-style: normal; 215 | margin-left: .1em; 216 | } 217 | a[href].direct-link, 218 | a[href].direct-link:visited { 219 | color: transparent; 220 | } 221 | a[href].direct-link:focus, 222 | a[href].direct-link:focus:visited, 223 | :hover > a[href].direct-link, 224 | :hover > a[href].direct-link:visited { 225 | color: #aaa; 226 | } 227 | 228 | .logo-img { 229 | display: block; 230 | text-align: center; 231 | } 232 | 233 | .logo-img { 234 | height: auto; 235 | width: 100px; 236 | position: relative; 237 | margin: 0 auto; 238 | } 239 | 240 | .title { 241 | text-align: center; 242 | display: block; 243 | text-decoration: none; 244 | } 245 | 246 | nav { 247 | display: flex; 248 | margin-top: 30px; 249 | justify-content: center; 250 | align-items: center; 251 | border-top: 1px solid #ccc; 252 | border-bottom: 1px solid #ccc; 253 | padding: 8px 0; 254 | } 255 | 256 | .tmpl-menu li { 257 | list-style: none; 258 | border-bottom: 1px solid #cac3c5; 259 | padding: 1.5rem 0; 260 | position: relative; 261 | height: 5em; 262 | font-weight: bold; 263 | } 264 | 265 | .description { 266 | display: block; 267 | position: absolute; 268 | bottom: 12px; 269 | font-style: italic; 270 | font-weight: 100; 271 | } -------------------------------------------------------------------------------- /src/css/prism-base16-monokai.dark.css: -------------------------------------------------------------------------------- 1 | code[class*="language-"], pre[class*="language-"] { 2 | font-size: 14px; 3 | line-height: 1.375; 4 | direction: ltr; 5 | text-align: left; 6 | white-space: pre; 7 | word-spacing: normal; 8 | word-break: normal; 9 | -moz-tab-size: 2; 10 | -o-tab-size: 2; 11 | tab-size: 2; 12 | -webkit-hyphens: none; 13 | -moz-hyphens: none; 14 | -ms-hyphens: none; 15 | hyphens: none; 16 | background: #272822; 17 | color: #f8f8f2; 18 | } 19 | pre[class*="language-"] { 20 | padding: 1.5em 0; 21 | margin: .5em 0; 22 | overflow: auto; 23 | } 24 | :not(pre) > code[class*="language-"] { 25 | padding: .1em; 26 | border-radius: .3em; 27 | } 28 | .token.comment, .token.prolog, .token.doctype, .token.cdata { 29 | color: #75715e; 30 | } 31 | .token.punctuation { 32 | color: #f8f8f2; 33 | } 34 | .token.namespace { 35 | opacity: .7; 36 | } 37 | .token.operator, .token.boolean, .token.number { 38 | color: #fd971f; 39 | } 40 | .token.property { 41 | color: #f4bf75; 42 | } 43 | .token.tag { 44 | color: #66d9ef; 45 | } 46 | .token.string { 47 | color: #a1efe4; 48 | } 49 | .token.selector { 50 | color: #ae81ff; 51 | } 52 | .token.attr-name { 53 | color: #fd971f; 54 | } 55 | .token.entity, .token.url, .language-css .token.string, .style .token.string { 56 | color: #a1efe4; 57 | } 58 | .token.attr-value, .token.keyword, .token.control, .token.directive, .token.unit { 59 | color: #a6e22e; 60 | } 61 | .token.statement, .token.regex, .token.atrule { 62 | color: #a1efe4; 63 | } 64 | .token.placeholder, .token.variable { 65 | color: #66d9ef; 66 | } 67 | .token.deleted { 68 | text-decoration: line-through; 69 | } 70 | .token.inserted { 71 | border-bottom: 1px dotted #f9f8f5; 72 | text-decoration: none; 73 | } 74 | .token.italic { 75 | font-style: italic; 76 | } 77 | .token.important, .token.bold { 78 | font-weight: bold; 79 | } 80 | .token.important { 81 | color: #f92672; 82 | } 83 | .token.entity { 84 | cursor: help; 85 | } 86 | pre > code.highlight { 87 | outline: 0.4em solid #f92672; 88 | outline-offset: .4em; 89 | } 90 | -------------------------------------------------------------------------------- /src/feed/feed.njk: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: feed/feed.xml 3 | eleventyExcludeFromCollections: true 4 | --- 5 | 6 | 7 | {{ metadata.title }} 8 | {{ metadata.feed.subtitle }} 9 | 10 | 11 | {{ collections.posts | rssLastUpdatedDate }} 12 | {{ metadata.feed.id }} 13 | 14 | {{ metadata.author.name }} 15 | {{ metadata.author.email }} 16 | 17 | {%- for post in collections.posts %} 18 | {% set absolutePostUrl %}{{ post.url | url | absoluteUrl(metadata.url) }}{% endset %} 19 | 20 | {{ post.data.title }} 21 | 22 | {{ post.date | rssDate }} 23 | {{ absolutePostUrl }} 24 | {{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }} 25 | 26 | {%- endfor %} 27 | 28 | -------------------------------------------------------------------------------- /src/feed/htaccess.njk: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: feed/.htaccess 3 | eleventyExcludeFromCollections: true 4 | --- 5 | # For Apache, to show `{{ metadata.feed.filename }}` when browsing to directory /feed/ (hide the file!) 6 | DirectoryIndex {{ metadata.feed.filename }} 7 | -------------------------------------------------------------------------------- /src/img/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shortdiv/cant-dutch-this/2728491dec8698447a188fd6d685593ee601eadd/src/img/.gitkeep -------------------------------------------------------------------------------- /src/img/fries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shortdiv/cant-dutch-this/2728491dec8698447a188fd6d685593ee601eadd/src/img/fries.png -------------------------------------------------------------------------------- /src/img/tgif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shortdiv/cant-dutch-this/2728491dec8698447a188fd6d685593ee601eadd/src/img/tgif.png -------------------------------------------------------------------------------- /src/img/tgif.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 10 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/index.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/home.njk 3 | eleventyNavigation: 4 | key: Home 5 | order: 1 6 | --- 7 | 8 | -------------------------------------------------------------------------------- /src/menu/menu.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/menu.njk 3 | title: Menu 4 | templateClass: tmpl-menu 5 | eleventyNavigation: 6 | key: Menu 7 | order: 2 8 | --- 9 | 10 | - Patat Met 11 | Fries with Mayo 12 | - Friet Speciaal 13 | Mayo curry, ketchup and onions 14 | - Pataje Joppie 15 | A yellowish sauce including mayo, onions and curry spices 16 | - Kapsalon 17 | Fries with Kebap 18 | - Patatje oorlong 19 | Fries with sate sauce, mayo and onion 20 | -------------------------------------------------------------------------------- /src/page-list.njk: -------------------------------------------------------------------------------- 1 | --- 2 | pagination: 3 | data: collections.all 4 | size: 20 5 | alias: entries 6 | layout: layouts/home.njk 7 | permalink: /page-list/{% if pagination.pageNumber > 0 %}{{ pagination.pageNumber }}/{% endif %} 8 | --- 9 | 10 | 11 | 12 | 13 | 14 | 15 | {%- for entry in entries %} 16 | 17 | 18 | 19 | 20 | {%- endfor %} 21 | 22 |
URLPage Title
{{ entry.url }}{{ entry.data.title }}
23 | -------------------------------------------------------------------------------- /src/posts/firstpost.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: This is my first post. 3 | description: This is a post on My Blog about agile frameworks. 4 | date: 2018-05-01 5 | tags: 6 | - another-tag 7 | layout: layouts/post.njk 8 | --- 9 | Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment. 10 | 11 | Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution. User generated content in real-time will have multiple touchpoints for offshoring. 12 | 13 | ## Section Header 14 | 15 | Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps. Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line. 16 | 17 | ``` text/2-3 18 | // this is a command 19 | function myCommand() { 20 | let counter = 0; 21 | counter++; 22 | } 23 | 24 | // Test with a line break above this line. 25 | console.log('Test'); 26 | ``` 27 | -------------------------------------------------------------------------------- /src/posts/fourthpost.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: This is my fourth post. 3 | description: This is a post on My Blog about touchpoints and circling wagons. 4 | date: 2018-09-30 5 | tags: second-tag 6 | layout: layouts/post.njk 7 | --- 8 | Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment. 9 | 10 | Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution. User generated content in real-time will have multiple touchpoints for offshoring. 11 | 12 | ## Section Header 13 | 14 | Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps. Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line. 15 | 16 | -------------------------------------------------------------------------------- /src/posts/posts.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": [ 3 | "posts" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/posts/secondpost.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: This is my second post. 3 | description: This is a post on My Blog about leveraging agile frameworks. 4 | date: 2018-07-04 5 | tags: 6 | - number-2 7 | layout: layouts/post.njk 8 | --- 9 | Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment. 10 | 11 | ## Section Header 12 | 13 | First post 14 | Third post 15 | 16 | Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution. User generated content in real-time will have multiple touchpoints for offshoring. 17 | 18 | Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps. Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line. 19 | -------------------------------------------------------------------------------- /src/posts/thirdpost.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: This is my third post. 3 | description: This is a post on My Blog about win-win survival strategies. 4 | date: 2018-08-24 5 | tags: 6 | - second-tag 7 | layout: layouts/post.njk 8 | --- 9 | Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment. 10 | 11 | ``` js/2/4 12 | // this is a command 13 | function myCommand() { 14 | let counter = 0; 15 | 16 | counter++; 17 | 18 | } 19 | 20 | // Test with a line break above this line. 21 | console.log('Test'); 22 | ``` 23 | 24 | Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution. User generated content in real-time will have multiple touchpoints for offshoring. 25 | 26 | ## Section Header 27 | 28 | Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps. Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line. 29 | -------------------------------------------------------------------------------- /src/sitemap.xml.njk: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /sitemap.xml 3 | eleventyExcludeFromCollections: true 4 | --- 5 | 6 | 7 | {%- for page in collections.all %} 8 | {% set absoluteUrl %}{{ page.url | url | absoluteUrl(metadata.url) }}{% endset %} 9 | 10 | {{ absoluteUrl }} 11 | {{ page.date | htmlDateString }} 12 | 13 | {%- endfor %} 14 | 15 | -------------------------------------------------------------------------------- /src/tags-list.njk: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /tags/ 3 | layout: layouts/home.njk 4 | --- 5 |

Tags

6 | 7 | {% for tag in collections.tagList %} 8 | {% set tagUrl %}/tags/{{ tag }}/{% endset %} 9 | {{ tag }} 10 | {% endfor %} 11 | -------------------------------------------------------------------------------- /src/tags.njk: -------------------------------------------------------------------------------- 1 | --- 2 | pagination: 3 | data: collections 4 | size: 1 5 | alias: tag 6 | filter: 7 | - all 8 | - nav 9 | - post 10 | - posts 11 | - tagList 12 | addAllPagesToCollections: true 13 | layout: layouts/home.njk 14 | renderData: 15 | title: Tagged “{{ tag }}” 16 | permalink: /tags/{{ tag }}/ 17 | --- 18 |

Tagged “{{ tag }}”

19 | 20 | {% set postslist = collections[ tag ] %} 21 | {% include "postslist.njk" %} 22 | 23 |

See all tags.

24 | --------------------------------------------------------------------------------