From 08e88fb9d139dbfbdcd4cbde79f4b8e8c29a7601 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Sat, 26 May 2018 12:07:04 +0300 Subject: [PATCH] Add Books page. --- .pug-lintrc.json | 4 +-- app.js | 2 ++ config/_extras.yml | 13 ++++++++ config/helmet-csp.js | 5 ++- routes/books.js | 18 +++++++++++ routes/index.js | 2 ++ test/books_test.js | 65 ++++++++++++++++++++++++++++++++++++++ test/jobs_test.js | 2 +- views/_partials/books.pug | 16 ++++++++++ views/_partials/header.pug | 1 + views/books.pug | 19 +++++++++++ 11 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 routes/books.js create mode 100644 test/books_test.js create mode 100644 views/_partials/books.pug create mode 100644 views/books.pug diff --git a/.pug-lintrc.json b/.pug-lintrc.json index 2cdb7222d..c27f3eda8 100644 --- a/.pug-lintrc.json +++ b/.pug-lintrc.json @@ -14,9 +14,7 @@ "requireSpecificAttributes": [ { "img": [ - "alt", - "width", - "height" + "alt" ] } ], diff --git a/app.js b/app.js index 644d3ec05..1fa8efb31 100644 --- a/app.js +++ b/app.js @@ -162,6 +162,7 @@ app.locals.semver = semver; app.use('/', routes.indexRoute); app.use('/about/', routes.aboutRoute); app.use('/alpha/?|/beta/?', routes.redirectToRoot); +app.use('/books/', routes.booksRoute); app.use('/bootlint/', routes.bootlintRoute); app.use('/bootswatch/', routes.bootswatchRoute); app.use('/bootswatch4/', routes.bootswatch4Route); @@ -214,6 +215,7 @@ if (ENV.ENABLE_CRAWLING) { map.generate4(app, [ '/', '/about', + '/books', '/bootlint', '/bootswatch', '/fontawesome', diff --git a/config/_extras.yml b/config/_extras.yml index e73ef7c80..b1634f125 100644 --- a/config/_extras.yml +++ b/config/_extras.yml @@ -1,3 +1,16 @@ +books: + - name: 'Jump Start Bootstrap: Get Up to Speed With Bootstrap in a Weekend' + url: 'https://www.amazon.com/Jump-Start-Bootstrap-Speed-Weekend/dp/0992279437/ref=as_li_ss_il?_encoding=UTF8&psc=1&refRID=2WP7MEQ0KPAHQS6YT117&linkCode=li3&tag=bcdn-20&linkId=b44f87f6e04d5eb7e6b49af61cd0e422' + asin: '0992279437' + - name: 'Building Progressive Web Apps: Bringing the Power of Native to the Browser' + url: 'https://www.amazon.com/Building-Progressive-Web-Apps-Bringing-ebook/dp/B075HP52WY/ref=as_li_ss_il?ie=UTF8&linkCode=li3&tag=bcdn-20&linkId=013ca3a2eb57883d255384829ec0a6a2' + asin: 'B075HP52WY' + - name: 'Coding For Dummies (For Dummies (Computer/Tech))' + url: 'https://www.amazon.com/Coding-Dummies-Computer-Tech/dp/1119293324/ref=as_li_ss_il?ie=UTF8&linkCode=li3&tag=bcdn-20&linkId=fb405dc117a040e91557b45f6dc1094b' + asin: '1119293324' + - name: 'Bootstrap in 24 Hours, Sams Teach Yourself' + url: 'https://www.amazon.com/Bootstrap-Hours-Sams-Teach-Yourself-ebook/dp/B017M8OWZQ/ref=as_li_ss_il?ie=UTF8&linkCode=li3&tag=bcdn-20&linkId=f8a39275b5204f5f2d0427a1ef1c4f83' + asin: 'B017M8OWZQ' showcase: - name: Microsoft Education img: /assets/img/showcase/microsoft-education.png diff --git a/config/helmet-csp.js b/config/helmet-csp.js index 36719e652..dec78cdfd 100644 --- a/config/helmet-csp.js +++ b/config/helmet-csp.js @@ -51,7 +51,10 @@ const CSP = { '*.2mdn.net', 'launchbit.com', 'www.launchbit.com', - 'www.ziprecruiter.com' + 'www.ziprecruiter.com', + 'ws-na.amazon-adsystem.com', + 'ir-na.amazon-adsystem.com', + 'images-na.ssl-images-amazon.com' ], fontSrc: [ '\'self\'', diff --git a/routes/books.js b/routes/books.js new file mode 100644 index 000000000..a810918d0 --- /dev/null +++ b/routes/books.js @@ -0,0 +1,18 @@ +'use strict'; + +const express = require('express'); +const appendLocals = require('./appendLocals.js'); + +const router = express.Router(); + +router.get('/', (req, res) => { + res = appendLocals(req, res); + res.render('books.pug', { + title: 'Bootstrap Books', + description: 'Books that feature BootstrapCDN.' + }); +}); + +module.exports = router; + +// vim: ft=javascript sw=4 sts=4 et: diff --git a/routes/index.js b/routes/index.js index 652d0b70f..290bdc292 100644 --- a/routes/index.js +++ b/routes/index.js @@ -3,6 +3,7 @@ // This file just holds all route requires const notFoundRoute = require('./404.js'); const aboutRoute = require('./about.js'); +const booksRoute = require('./books.js'); const bootlintRoute = require('./bootlint.js'); const bootswatch4Route = require('./bootswatch4.js'); const bootswatchRoute = require('./bootswatch.js'); @@ -19,6 +20,7 @@ const showcaseRoute = require('./showcase.js'); const routes = { notFoundRoute, aboutRoute, + booksRoute, bootlintRoute, bootswatch4Route, bootswatchRoute, diff --git a/test/books_test.js b/test/books_test.js new file mode 100644 index 000000000..897e97041 --- /dev/null +++ b/test/books_test.js @@ -0,0 +1,65 @@ +'use strict'; + +const assert = require('assert').strict; +const htmlEncode = require('htmlencode').htmlEncode; +const helpers = require('./test_helpers.js'); + +describe('books', () => { + const config = helpers.getConfig(); + const uri = helpers.getURI('books'); + let response = {}; + + before((done) => { + helpers.prefetch(uri, (res) => { + response = res; + done(); + }); + }); + + it('works', (done) => { + helpers.assert.itWorks(response.statusCode, done); + }); + + it('valid html', (done) => { + helpers.assert.validHTML(response, done); + }); + + it('contains authors', (done) => { + helpers.assert.authors(response, done); + }); + + it('has page header', (done) => { + helpers.assert.pageHeader('Books', response, done); + }); + + config.books.forEach((book) => { + describe(book.name, () => { + const beforeTrackImgUrl = `https://ws-na.amazon-adsystem.com/widgets/q?_encoding=UTF8&ASIN=${book.asin}&Format=_SL250_&ID=AsinImage&MarketPlace=US&ServiceVersion=20070822&WS=1&tag=bcdn-20`; + const afterTrackImgUrl = `https://ir-na.amazon-adsystem.com/e/ir?t=bcdn-20&l=li3&o=1&a=${book.asin}`; + + it('has name', (done) => { + assert.ok(response.body.includes(book.name), + `Expects response body to include "${book.name}"`); + done(); + }); + + it('has url', (done) => { + assert.ok(response.body.includes(htmlEncode(book.url)), + `Expects response body to include "${book.url}"`); + done(); + }); + + it('has before tracking image', (done) => { + assert.ok(response.body.includes(htmlEncode(beforeTrackImgUrl)), + `Expects response body to include "${beforeTrackImgUrl}"`); + done(); + }); + + it('has after tracking image', (done) => { + assert.ok(response.body.includes(htmlEncode(afterTrackImgUrl)), + `Expects response body to include "${afterTrackImgUrl}"`); + done(); + }); + }); + }); +}); diff --git a/test/jobs_test.js b/test/jobs_test.js index 6e03fd7f5..ce0c8b7fd 100644 --- a/test/jobs_test.js +++ b/test/jobs_test.js @@ -2,7 +2,7 @@ const helpers = require('./test_helpers.js'); -describe('Jobs', () => { +describe('jobs', () => { const uri = helpers.getURI('jobs'); let response = {}; diff --git a/views/_partials/books.pug b/views/_partials/books.pug new file mode 100644 index 000000000..15db33737 --- /dev/null +++ b/views/_partials/books.pug @@ -0,0 +1,16 @@ +- + const beforeTrackImgUrl = `https://ws-na.amazon-adsystem.com/widgets/q?_encoding=UTF8&ASIN=${book.asin}&Format=_SL250_&ID=AsinImage&MarketPlace=US&ServiceVersion=20070822&WS=1&tag=bcdn-20`; + const afterTrackImgUrl = `https://ir-na.amazon-adsystem.com/e/ir?t=bcdn-20&l=li3&o=1&a=${book.asin}`; + +.card-deck + .card.border-primary.py-3.px-md-3.mb-3.text-center + .card-header.bg-transparent.border-0 + h3.h4.card-title.mb-0=book.name + .card-body.bg-transparent.border-0 + a(href=book.url, rel='noopener', target='_blank') + img.border-0(src=beforeTrackImgUrl, alt='') + img.border-0.m-0(src=afterTrackImgUrl, width='1', height='1', alt='') + .card-footer.bg-transparent.border-0 + a.btn.btn-primary.btn-lg.btn-block.w-75.mx-auto.py-3(href=book.url, rel='noopener', target='_blank') Buy from Amazon + +//- vim: ft=pug sw=4 sts=4 et: diff --git a/views/_partials/header.pug b/views/_partials/header.pug index 4abf60c4e..1a92cdd92 100644 --- a/views/_partials/header.pug +++ b/views/_partials/header.pug @@ -22,6 +22,7 @@ nav.navbar.navbar-expand-lg.navbar-dark.bg-dark +menuItem('Bootswatch Legacy', '/legacy/bootswatch/')(extra-class='dropdown-item bg-dark') +menuItem('Font Awesome Legacy', '/legacy/fontawesome/')(extra-class='dropdown-item bg-dark') ul.nav.navbar-nav.ml-auto + +menuItem('Books', '/books/') +menuItem('Jobs', '/jobs/') +menuItem('Showcase', '/showcase/') +menuItem('Integrations', '/integrations/') diff --git a/views/books.pug b/views/books.pug new file mode 100644 index 000000000..ac357994b --- /dev/null +++ b/views/books.pug @@ -0,0 +1,19 @@ +extends layout.pug + +block content + h2.text-center.mb-4 Books + + .row + each book in config.books + .col-12.col-md-6 + include _partials/books.pug + + .row + .col-12 + .alert.alert-primary.my-3.text-center(role='alert') + p.m-0 + | You want to submit yours? Open a Pull Request or an Issue on our + a.alert-link(href='https://github.com/MaxCDN/bootstrapcdn') GitHub repository + | . + +//- vim: ft=pug sw=4 sts=4 et: