From 964fedcba9db4e2630b2f9d561b12d7a08574598 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Fri, 22 Nov 2019 18:49:05 -0500 Subject: [PATCH 01/54] enable extended body parsing for nested data --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index ea047fd..93ab59e 100755 --- a/app.js +++ b/app.js @@ -25,7 +25,7 @@ const app = express() // general app configuration. app.use(express.json()) -app.use(express.urlencoded({ extended: false })) +app.use(express.urlencoded({ extended: true })) app.use(cookieParser(process.env.app_session_secret)) app.use(require('./config/i18n.config').init) From dd08cffb48d01cf93d6a495bd604a75968e512fe Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Fri, 22 Nov 2019 18:57:06 -0500 Subject: [PATCH 02/54] BREAKING: deprecate `getViewData()` in favour of loadSession === why? === For the repeater (and other context-sensitive rendering), we will need to access and mutate view data through `res.locals`. This means that all our data needs to be available there. So, instead of passing in data to `res.render(...)`, we consistently use a middleware that loads data into `res.locals`. This results in easier-to-follow code, and it is clearer that the choice of what data to load comes from the schema. For locals that don't appear in the schema, implementers can use `loadKeys(...keys)` or `loadFullSession` to get data from the session, or implement a custom middleware that adds data to `res.locals`. For the vast majority of cases, rendering should be done with the middleware `route.render()`. --- package.json | 3 ++- utils/data.helpers.js | 20 -------------- utils/index.js | 4 --- utils/route.helpers.js | 55 ++++++++++++++++++++++++++++++++++++++- utils/session.helpers.js | 20 +++++--------- utils/validate.helpers.js | 27 +++++++++---------- 6 files changed, 76 insertions(+), 53 deletions(-) delete mode 100644 utils/data.helpers.js diff --git a/package.json b/package.json index 824412f..eaf1c15 100755 --- a/package.json +++ b/package.json @@ -81,7 +81,8 @@ "nodemonConfig": { "ext": "js,json,njk,scss", "ignore": [ - "public/dist/**/*.*" + "public/dist", + "locales" ] } } diff --git a/utils/data.helpers.js b/utils/data.helpers.js deleted file mode 100644 index 98dd6f0..0000000 --- a/utils/data.helpers.js +++ /dev/null @@ -1,20 +0,0 @@ -const { getSessionData } = require('./session.helpers') -const { getFlashMessage } = require('./flash.message.helpers') - -const getViewData = (req, optionalParams = {}) => { - const params = { - data: { ...getSessionData(req) }, - } - - const errors = getFlashMessage(req) - - if (errors) { - params.errors = errors - } - - return { ...params, ...optionalParams } -} - -module.exports = { - getViewData, -} diff --git a/utils/index.js b/utils/index.js index ea580a9..29e2685 100755 --- a/utils/index.js +++ b/utils/index.js @@ -1,5 +1,4 @@ const routeHelpers = require('./route.helpers.js') -const dataHelpers = require('./data.helpers.js') const sessionHelpers = require('./session.helpers.js') const urlHelpers = require('./url.helpers.js') const validateHelpers = require('./validate.helpers.js') @@ -15,18 +14,15 @@ module.exports = { ...validateHelpers, ...viewHelpers, ...flashMessageHelpers, - ...dataHelpers, ...loadHelpers, } const { getRouteByName } = require('./route.helpers') const { addViewPath } = require('./view.helpers') -const { getViewData } = require('./data.helpers') const { getDefaultMiddleware } = require('./route.helpers') module.exports.routeUtils = { getRouteByName, addViewPath, - getViewData, getDefaultMiddleware, } diff --git a/utils/route.helpers.js b/utils/route.helpers.js index a9468a4..56bc878 100644 --- a/utils/route.helpers.js +++ b/utils/route.helpers.js @@ -4,6 +4,7 @@ const url = require('url'); const { checkSchema } = require('express-validator') const { checkErrors } = require('./validate.helpers') const { addViewPath } = require('./view.helpers') +const { saveToSession } = require('./session.helpers') class RoutingTable { /** @@ -99,6 +100,8 @@ class Route { return this } + render() { return (req, res) => res.render(this.name) } + /** * The default middleware for this route, intended * for the POST method. @@ -110,8 +113,53 @@ class Route { ] } + /** + * + * @param {object} schema Schema object from the route folder + * + * This grabs every key that we expect from the schema, it grabs the default value and passes it to Loadkeys to be loaded inside the locals of the views + * + */ + loadSchema(schema) { + const defaults = {} + const keys = Object.keys(schema) + keys.forEach(k => { defaults[k] = schema[k].default }) + + return this.loadKeys(keys, defaults) + } + + /** + * + * @param {*} keys Are in the format of an array with the name of each key that you want added. This would be keys that are not already being added by loadSchema() + * @param {*} defaults object in the format of keyname: 'defaultValue', 'surname': 'Boisvert' (if you wanted a default surname of Boisvert...) + */ + loadKeys(keys, defaults = {}) { + return (req, res, next) => { + // copy data from these sources in order, falling through + // if each is not present. + keys.forEach(k => { + res.locals[k] = + (req.body || {})[k] + || (req.session || {})[k] + || defaults[k] + }) + + // make all variables available on a global `data` field, to + // enable dynamic lookup in views + // res.locals.data = res.locals + + next() + } + } + + loadFullSession(defaults = {}) { + return (req, res, next) => { + this.loadKeys(Object.keys(req.session || {}), defaults)(req, res, next) + } + } + applySchema(schema) { - return [checkSchema(schema), checkErrors(this.name)] + return [checkSchema(schema), saveToSession, checkErrors] } doRedirect(redirectTo = null) { @@ -144,6 +192,11 @@ class DrawRoutes { post(...args) { return this.request('post', ...args) } put(...args) { return this.request('put', ...args) } delete(...args) { return this.request('delete', ...args) } + + use(...args) { + this.route.eachLocale((path, locale) => { this.app.use(path, ...args) }) + return this + } } const oneHour = 1000 * 60 * 60 * 1 diff --git a/utils/session.helpers.js b/utils/session.helpers.js index b627b8b..5cefc9d 100644 --- a/utils/session.helpers.js +++ b/utils/session.helpers.js @@ -1,18 +1,12 @@ -const getSessionData = req => { - if (!req.session) return {} - return typeof req.session.formdata === 'object' ? req.session.formdata : {} -} - -const saveSessionData = req => { - // copy all posted parameters - const body = Object.assign({}, req.body) - delete body.redirect - delete body._csrf +const { matchedData } = require('express-validator') - req.session.formdata = { ...req.session.formdata, ...body } +const saveToSession = (req, res, next) => { + // matchedData() comes from express-validator, which + // only includes things mentioned in the schema. + Object.assign(req.session, matchedData(req)) + next() } module.exports = { - getSessionData, - saveSessionData, + saveToSession } diff --git a/utils/validate.helpers.js b/utils/validate.helpers.js index 958f935..9ef1a6c 100644 --- a/utils/validate.helpers.js +++ b/utils/validate.helpers.js @@ -53,25 +53,24 @@ const isValidDate = dateString => { * * @param string template The template string to render if errors are found (should match the one used for the GET request) */ -const checkErrors = template => { - return (req, res, next) => { - // check to see if the requests should respond with JSON - if (req.body.json) { - return checkErrorsJSON(req, res, next) - } - - const errors = validationResult(req) +const checkErrors = (req, res, next) => { + // check to see if the requests should respond with JSON + if (req.body.json) { + return checkErrorsJSON(req, res, next) + } - saveSessionData(req) + const errors = validationResult(req) - // flash error messages and redirect back on error - if (!errors.isEmpty()) { - req.session.flashmessage = errorArray2ErrorObject(errors) - return res.redirect('back') + if (!errors.isEmpty()) { + req.session.errorState = { + errors: errorArray2ErrorObject(errors), + firstError: errors[0].msg } - return next() + return res.redirect('back') } + + return next() } /** From 5518e9e11c98e785cfec29539d2aeda30ca4926c Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Fri, 22 Nov 2019 19:16:55 -0500 Subject: [PATCH 03/54] add context middleware primitives for data `enterContext` and `exitContext` allow us to dynamically traverse nested data in the session. Using these, we can change the "context" from which we get data and errors, and automatically generate `name` attributes appropriate for the nested data. --- app.js | 5 +++- utils/context.helpers.js | 57 ++++++++++++++++++++++++++++++++++++++++ utils/index.js | 2 ++ 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 utils/context.helpers.js diff --git a/app.js b/app.js index 93ab59e..a7589f6 100755 --- a/app.js +++ b/app.js @@ -9,7 +9,7 @@ const helmet = require('helmet') const path = require('path') const cookieSession = require('cookie-session') const cookieSessionConfig = require('./config/cookieSession.config') -const { hasData } = require('./utils') +const { hasData, contextMiddleware } = require('./utils') const { addNunjucksFilters } = require('./filters') const csp = require('./config/csp.config') const csrf = require('csurf') @@ -70,6 +70,9 @@ app.locals.hasData = hasData app.locals.basedir = path.join(__dirname, './views') app.set('views', [path.join(__dirname, './views')]) +// add in helpers for scoped data contexts (used in the repeater) +app.use(contextMiddleware) + app.routes = configRoutes(app, routes, locales) // view engine setup diff --git a/utils/context.helpers.js b/utils/context.helpers.js new file mode 100644 index 0000000..cb99025 --- /dev/null +++ b/utils/context.helpers.js @@ -0,0 +1,57 @@ +const lookupKeypath = (obj, keyPath) => { + var out = obj + keyPath.forEach(k => { out = out ? out[k] : undefined }) + return out +} + +// convert an array of keys to a path as used in express-validator +const errorPath = (keys) => { + const [first, ...rest] = keys + + const paths = rest.map(key => typeof key === 'number' ? `[${key}]` : `.${key}`) + + return first + paths.join('') +} + +// we define our helper functions inside the middleware because +// we need them to access `res.local`. +const contextMiddleware = (req, res, next) => { + console.log('contextMiddleware') + + // a request-global stack of key paths into res.locals. + // these should always be called as a pair - similar to do...end. + // we would ideally use a caller() macro for this, but those are... + // unreliable at best. + const keyPath = [] + res.locals.enterContext = (key) => { keyPath.push(key) } + res.locals.exitContext = () => { keyPath.pop() } + + // look up a key at the current keypath + res.locals.getData = (...keys) => lookupKeypath(res.locals, keyPath.concat(keys)) + res.locals.getError = (...keys) => (res.locals.errors || {})[errorPath(keyPath.concat(keys))] + + res.locals.pad = (arr, len) => { + if (!len || len < 1) len = 1 + if (!arr) arr = [] + if (!len || arr.length >= len) return arr + return arr.concat(new Array(len - arr.length).fill({})) + } + + // The qualified name for a locally defined key, for use in a + // name="..." attribute of a form control. + // + // If keyPath is empty, this will just return the same name. + res.locals.getName = (...names) => { + const [first, ...rest] = keyPath.concat(names) + return first + rest.map(x => `[${x}]`).join('') + } + + // TODO + res.locals.isFirstError = (name) => false + + next() +} + +module.exports = { + contextMiddleware +} diff --git a/utils/index.js b/utils/index.js index 29e2685..f153ae1 100755 --- a/utils/index.js +++ b/utils/index.js @@ -5,6 +5,7 @@ const validateHelpers = require('./validate.helpers.js') const viewHelpers = require('./view.helpers.js') const flashMessageHelpers = require('./flash.message.helpers') const loadHelpers = require('./load.helpers') +const contextHelpers = require('./context.helpers') module.exports = { ...routeHelpers, @@ -15,6 +16,7 @@ module.exports = { ...viewHelpers, ...flashMessageHelpers, ...loadHelpers, + ...contextHelpers, } const { getRouteByName } = require('./route.helpers') From 8fb11a55b4d75aea6a4084735fcd5e8e7437a43f Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Fri, 22 Nov 2019 19:19:33 -0500 Subject: [PATCH 04/54] update all the control macros to use getData(...) etc this way, the data, error, and name will all be relative to the context. --- views/macros/checkboxes.njk | 41 ++++++++++++++++++++++--------- views/macros/input-file.njk | 22 ++++++++++++----- views/macros/input-text.njk | 43 ++++++++++++++++++++++++--------- views/macros/input-textarea.njk | 43 ++++++++++++++++++++++----------- views/macros/radios.njk | 41 ++++++++++++++++++++++++------- 5 files changed, 139 insertions(+), 51 deletions(-) diff --git a/views/macros/checkboxes.njk b/views/macros/checkboxes.njk index 688c1ee..3e06699 100644 --- a/views/macros/checkboxes.njk +++ b/views/macros/checkboxes.njk @@ -1,26 +1,45 @@ -{% macro checkBoxes(key, values, selectedVals, question, errors, attributes) %} -
+{% macro checkBoxes(relkey, question, labels, + required=false, + hint=false, + class=false) %} + {% set error = getError(relkey) %} + {% set key = getName(relkey) %} + +
- {% if attributes.required %} + {% if required %} {% endif %} {{ __(question) }} - {% if attributes.required %} + {% if required %} {{ __("required")}} {% endif %} - {% if attributes.hint %} - {{ __(attributes.hint) }} + + {% if hint %} + {{ __(hint) }} {% endif %}
- {% if errors and errors[key] %} - {{ validationMessage(errors[key].msg, key) }} + {% if error %} + {{ validationMessage(error.msg, key) }} {% endif %} - {% for index, val in values %} + + {% for index, label in labels %} + {% set val = getData(relkey, index) %} + {% set name = getName(relkey, index) %} +
- - + +
{% endfor %}
diff --git a/views/macros/input-file.njk b/views/macros/input-file.njk index 760b7b6..3f789ee 100644 --- a/views/macros/input-file.njk +++ b/views/macros/input-file.njk @@ -1,9 +1,19 @@ -{% macro fileInput(label, divClasses, attributes) %} -
- -
diff --git a/views/macros/input-textarea.njk b/views/macros/input-textarea.njk index 9f106f3..428492a 100644 --- a/views/macros/input-textarea.njk +++ b/views/macros/input-textarea.njk @@ -34,7 +34,7 @@ aria-describedby="{{ name }}-error" aria-invalid="true" {% endif %} - {% if isFirstError(name) %} + {% if isFirstError(relname) %} autofocus="true" {% endif %} > From 2497783fe5598f3f9dbcdff454b062a69b82fe86 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 5 Dec 2019 10:36:49 -0500 Subject: [PATCH 36/54] add unique identifiers and styling to repeaters --- assets/scss/components/_repeater.scss | 12 ++++++++++++ assets/scss/styles.scss | 3 ++- public/js/repeater.js | 4 ++++ views/macros/repeater.njk | 11 +++++++++-- 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 assets/scss/components/_repeater.scss diff --git a/assets/scss/components/_repeater.scss b/assets/scss/components/_repeater.scss new file mode 100644 index 0000000..24e52d4 --- /dev/null +++ b/assets/scss/components/_repeater.scss @@ -0,0 +1,12 @@ +.repeater { +} + +.repeater-instance { + padding: 10px 20px; + border-left: 5px solid gray; + border-top: 5px solid gray; + + > legend { + padding: 10px 15px; + } +} diff --git a/assets/scss/styles.scss b/assets/scss/styles.scss index 1465374..db7c281 100755 --- a/assets/scss/styles.scss +++ b/assets/scss/styles.scss @@ -17,4 +17,5 @@ @import './components/_breakdown-table'; @import './components/_phase-banner'; @import './components/_input-file'; -@import './components/skip-link'; \ No newline at end of file +@import './components/_repeater'; +@import './components/skip-link'; diff --git a/public/js/repeater.js b/public/js/repeater.js index 3a08b95..641f737 100644 --- a/public/js/repeater.js +++ b/public/js/repeater.js @@ -108,6 +108,10 @@ window.Repeater = (function() { reindexProp(error, 'id', newIndex) }) + this.el.querySelectorAll('.remove-repeat-link').forEach(function(link) { + reindexProp(link, 'id', newIndex) + }) + // special elements that show the user which number they're looking at this.el.querySelectorAll('.repeat-number').forEach(function(el) { el.innerText = ''+(newIndex+1) diff --git a/views/macros/repeater.njk b/views/macros/repeater.njk index 08e0e55..1e2dbe1 100644 --- a/views/macros/repeater.njk +++ b/views/macros/repeater.njk @@ -16,11 +16,18 @@ {% endmacro %} {% macro repeatLink(name, text) %} - {{__(text)}} + {{__(text)}} {% endmacro %} {% macro removeRepeatLink(text) %} - {{__(text)}} + {{__(text)}} {% endmacro %} {% macro repeatNumber() -%} From 51068655ce158f913322e4a28bd871a51c647123 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 5 Dec 2019 15:37:32 -0500 Subject: [PATCH 37/54] allow pressing spacebar to activate the repeat and remove-repeat links --- public/js/repeater.js | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/public/js/repeater.js b/public/js/repeater.js index 641f737..d589c43 100644 --- a/public/js/repeater.js +++ b/public/js/repeater.js @@ -37,9 +37,8 @@ window.Repeater = (function() { // we use one global listener for removal, because the repeat links may // get added and removed from the DOM, and this means we don't have to // re-register the event listeners when we clone the nodes. - this.container.addEventListener('click', function(evt) { + handleLinkInteraction(this.container, function(evt) { if (!evt.target.classList.contains('remove-repeat-link')) return - evt.preventDefault() var instance = instanceFor(evt.target) instance && instance.remove() @@ -77,8 +76,15 @@ window.Repeater = (function() { } function clearField(control) { - if (control.tagName === 'textarea') control.innerText = '' - else control.value = '' + if (control.tagName === 'textarea') { + control.innerText = '' + } + else if (control.type === 'radio' || control.type === 'checkbox') { + control.checked = false + } + else { + control.value = '' + } } _.init = function(block, el, index) { @@ -121,10 +127,21 @@ window.Repeater = (function() { } _.clear = function() { - this.el.querySelectorAll('input,textarea,select').forEach(clearField) + var first + this.el.querySelectorAll('input,textarea,select').forEach(function(el) { + if (!first) first = el + clearField(el) + }) + + if (first) first.focus() return this } + _.focus = function() { + var first = this.el.querySelector('input,textarea,select') + if (first) first.focus() + } + _.remove = function() { // if we're the last one, we should just empty the fields if (this.block.instances.length === 1) return this.clear() @@ -140,6 +157,10 @@ window.Repeater = (function() { // remove from the list of instances this.block.instances.splice(this.index, 1) + // focus the next fieldset if it exists, otherwise the last one + var adjacent = this.block.instances[this.index] || this.block.instances[this.index-1] + if (adjacent) adjacent.focus() + return this } @@ -174,13 +195,22 @@ window.Repeater = (function() { // repeat links are expected to be *outside* the repeater, so can manage // their own event listeners. document.querySelectorAll('.repeat-link').forEach(function(link) { - link.addEventListener('click', function(evt) { + handleLinkInteraction(link, function(evt) { evt.preventDefault() repeat(link.dataset.target) }) }) } + function handleLinkInteraction(el, handler) { + el.addEventListener('click', handler) + el.addEventListener('keydown', function(evt) { + // spacebar + if (evt.keyCode !== 32) return + handler(evt) + }) + } + return { repeat: repeat, init: init From 95787685013137edf9513b92f14daaff36d12a12 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Fri, 6 Dec 2019 10:59:43 -0500 Subject: [PATCH 38/54] standardize on `client.js` for client javascript and have it automatically loaded if the file exists --- utils/fileInput.js => assets/js/file-input.js | 0 {public => assets}/js/repeater.js | 2 +- routes/addresses/addresses.njk | 5 ----- routes/addresses/client.js | 1 + routes/global/client.js | 1 + routes/personal/client.js | 3 +++ routes/personal/js/personal.js | 4 ---- utils/load.helpers.js | 10 +++------- utils/load.helpers.spec.js | 6 +++--- utils/route.helpers.js | 18 ++++++++++++++++++ views/base.njk | 8 +++----- webpack.config.js | 16 ++++++++++++---- 12 files changed, 45 insertions(+), 29 deletions(-) rename utils/fileInput.js => assets/js/file-input.js (100%) rename {public => assets}/js/repeater.js (99%) create mode 100644 routes/addresses/client.js create mode 100644 routes/global/client.js create mode 100644 routes/personal/client.js delete mode 100644 routes/personal/js/personal.js diff --git a/utils/fileInput.js b/assets/js/file-input.js similarity index 100% rename from utils/fileInput.js rename to assets/js/file-input.js diff --git a/public/js/repeater.js b/assets/js/repeater.js similarity index 99% rename from public/js/repeater.js rename to assets/js/repeater.js index d589c43..1efaa7b 100644 --- a/public/js/repeater.js +++ b/assets/js/repeater.js @@ -1,4 +1,4 @@ -window.Repeater = (function() { +export const Repeater = (function() { "use strict" var repeatedSets diff --git a/routes/addresses/addresses.njk b/routes/addresses/addresses.njk index b61754d..8055ad6 100644 --- a/routes/addresses/addresses.njk +++ b/routes/addresses/addresses.njk @@ -1,11 +1,6 @@ {% extends "base.njk" %} -{% block scripts %} - -{% endblock %} - {% block content %} -

{{ __('addresses.title') }}

diff --git a/routes/addresses/client.js b/routes/addresses/client.js new file mode 100644 index 0000000..7e67e32 --- /dev/null +++ b/routes/addresses/client.js @@ -0,0 +1 @@ +require('./../../assets/js/repeater.js') diff --git a/routes/global/client.js b/routes/global/client.js new file mode 100644 index 0000000..38c3635 --- /dev/null +++ b/routes/global/client.js @@ -0,0 +1 @@ +console.log('global script file in routes/global/client.js') diff --git a/routes/personal/client.js b/routes/personal/client.js new file mode 100644 index 0000000..c8eded6 --- /dev/null +++ b/routes/personal/client.js @@ -0,0 +1,3 @@ +import { fileInput } from './../../assets/js/file-input' + +fileInput() diff --git a/routes/personal/js/personal.js b/routes/personal/js/personal.js deleted file mode 100644 index 43e3f52..0000000 --- a/routes/personal/js/personal.js +++ /dev/null @@ -1,4 +0,0 @@ -import { fileInput } from '../../../utils/fileInput' -;(function(document, window, index) { - fileInput() -})(document, window, 0) diff --git a/utils/load.helpers.js b/utils/load.helpers.js index 26447c2..4043c18 100644 --- a/utils/load.helpers.js +++ b/utils/load.helpers.js @@ -2,17 +2,13 @@ // note: there's test to look for a false response // but coverage isn't catching -const path = require('path') const { clientJsDir, getClientJsPath } = require('./url.helpers') -const getClientJs = (req, routeName = '', jsPath = '../public') => { +const getClientJs = (req, routeName = '', jsPath = './public') => { const fs = require('fs') try { - const fileList = path.join( - __dirname, - `${jsPath}${clientJsDir}_filelist.json`, - ) + const fileList = `${jsPath}${clientJsDir}_filelist.json` const content = fs.readFileSync(fileList) const json = JSON.parse(content) @@ -25,7 +21,7 @@ const getClientJs = (req, routeName = '', jsPath = '../public') => { } */ const file = json[routeName] - const filePath = path.join(__dirname, `${jsPath}${clientJsDir}${file}`) + const filePath = `${jsPath}${clientJsDir}${file}` const fileExists = fs.readFileSync(filePath) if (fileExists) { diff --git a/utils/load.helpers.spec.js b/utils/load.helpers.spec.js index 66a3203..73407aa 100644 --- a/utils/load.helpers.spec.js +++ b/utils/load.helpers.spec.js @@ -6,7 +6,7 @@ describe('Can pull JavaScript file', () => { body: {}, headers: { host: 'localhost' }, } - expect(getClientJs(req, 'start', '../__tests__/fixtures')).toEqual( + expect(getClientJs(req, 'start', './__tests__/fixtures')).toEqual( 'http://localhost/dist/js/start.f1ed5571f87447db4451.js', ) }) @@ -16,7 +16,7 @@ describe('Can pull JavaScript file', () => { body: {}, headers: { host: 'localhost' }, } - expect(getClientJs(req, 'start', '../__tests__/fixtures_missing')).toEqual( + expect(getClientJs(req, 'start', './__tests__/fixtures_missing')).toEqual( false, ) }) @@ -26,6 +26,6 @@ describe('Can pull JavaScript file', () => { body: {}, headers: { host: 'localhost' }, } - expect(getClientJs(req, 'start1', '../__tests__/fixtures')).toEqual(false) + expect(getClientJs(req, 'start1', './__tests__/fixtures')).toEqual(false) }) }) diff --git a/utils/route.helpers.js b/utils/route.helpers.js index caa1cb0..f6fcf2a 100644 --- a/utils/route.helpers.js +++ b/utils/route.helpers.js @@ -5,6 +5,12 @@ const { checkSchema } = require('express-validator') const { checkErrors } = require('./validate.helpers') const { addViewPath } = require('./view.helpers') const { saveToSession } = require('./session.helpers') +const { getClientJs } = require('./load.helpers') + +const wrapArray = (val) => { + if (!val) return [] + return Array.isArray(val) ? val : [val] +} class RoutingTable { /** @@ -34,10 +40,19 @@ class RoutingTable { * Attach the route controllers to an app. */ config(app) { + app.use(this.globalHelpers()) this.routes.forEach(r => r.config(app)) require(`${this.directory}/global/global.controller`)(app, this) return this } + + globalHelpers() { return (req, res, next) => { + // overridden inside any route - this is so that jsPaths() still works + // in global routes like 404. + res.locals.jsPaths = () => [] + res.locals.globalJsPaths = () => wrapArray(getClientJs(req, 'global')) + return next() + } } } class Route { @@ -225,6 +240,9 @@ const routeMiddleware = (route, locale) => (req, res, next) => { return nameOrObj.path[locale] } + // override + res.locals.jsPaths = () => wrapArray(getClientJs(req, route.name)) + return next() } diff --git a/views/base.njk b/views/base.njk index 0643769..04b28f0 100644 --- a/views/base.njk +++ b/views/base.njk @@ -40,10 +40,8 @@ {% include "_includes/script.njk" %} {% block scripts %} - {% if jsFiles %} - {% for val in jsFiles %} - - {% endfor %} - {% endif %} + {% for val in globalJsPaths().concat(jsPaths()) %} + + {% endfor %} {% endblock %} diff --git a/webpack.config.js b/webpack.config.js index aa364e8..dd8201a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,13 +1,21 @@ const path = require('path') +const fs = require('fs') module.exports = (env, argv) => { const { getConfig } = require('@cdssnc/webpack-starter') + + const entry = { styles: './assets/scss/app.scss' } + + require('./config/routes.config').routes.forEach(route => { + const sourcePath = `./routes/${route.name}/client.js` + if (fs.existsSync(sourcePath)) entry[route.name] = sourcePath + }) + + entry.global = './routes/global/client.js' + const config = getConfig({ mode: argv.mode, - entry: { - styles: './assets/scss/app.scss', - personal: './routes/personal/js/personal.js', - }, + entry: entry, output: { filename: 'js/[name].[chunkhash].js', path: path.resolve(__dirname, 'public/dist'), From 1585efac2c25b9d2bdbbf02fface625fad57702c Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Fri, 6 Dec 2019 11:45:19 -0500 Subject: [PATCH 39/54] update repeater.js to es6 style --- assets/js/repeater.js | 127 ++++++++++++++++++++---------------------- 1 file changed, 61 insertions(+), 66 deletions(-) diff --git a/assets/js/repeater.js b/assets/js/repeater.js index 1efaa7b..0cba47c 100644 --- a/assets/js/repeater.js +++ b/assets/js/repeater.js @@ -1,43 +1,48 @@ -export const Repeater = (function() { +export const Repeater = (() => { "use strict" var repeatedSets - // Since we can't depend on Array.prototype.map - function map(arr, fn) { - var out = new Array(arr.length) - arr.forEach(function(x, i) { out[i] = fn(x, i) }) - return out + // mapping things that aren't quite arrays + const map = (arr, fn) => Array.prototype.map.call(arr, fn) + const query = (q, el=document) => el.querySelectorAll(q) + + const handleLinkInteraction = (el, handler) => { + el.addEventListener('click', handler) + el.addEventListener('keydown', (evt) => { + // spacebar + if (evt.keyCode !== 32) return + handler(evt) + }) } - function indexBy(key, arr) { + const indexBy = (key, arr) => { var out = {} - arr.forEach(function(x) { out[x[key]] = x }) + arr.forEach(x => { out[x[key]] = x }) return out } // a Block is the largest grouping in the repeater. It contains all the repeated // instances. - function Block() { this.init.apply(this, arguments) } - (function(_) { - _.init = function(el) { + class Block { + constructor(el) { var self = this // closure fix this.name = el.id this.container = el - this.instances = map(el.querySelectorAll('.repeater-instance'), function(fieldset, i) { + this.instances = map(query('.repeater-instance', el), (fieldset, i) => { var instance = new Instance(self, fieldset, i) instance.reindex(i) return instance }) } - _.setupListeners = function() { + setupListeners() { // we use one global listener for removal, because the repeat links may // get added and removed from the DOM, and this means we don't have to // re-register the event listeners when we clone the nodes. - handleLinkInteraction(this.container, function(evt) { + handleLinkInteraction(this.container, evt => { if (!evt.target.classList.contains('remove-repeat-link')) return evt.preventDefault() var instance = instanceFor(evt.target) @@ -47,7 +52,7 @@ export const Repeater = (function() { return this } - _.repeat = function() { + repeat() { if (!this.instances.length) throw new Error('empty instances, can\'t repeat!') var newIndex = this.instances.length @@ -58,77 +63,76 @@ export const Repeater = (function() { this.instances.push(newInstance) return newInstance } - })(Block.prototype) + } - // one instance of a repeater - function Instance() { return this.init.apply(this, arguments) } - (function(_) { - // private functions - function reindex(str, index) { - // it's always going to be the first [0] or [1] or etc. - return str.replace(/\[\d+\]/, '['+index+']') - } + const reindex = (str, index) => { + // it's always going to be the first [0] or [1] or etc. + return str.replace(/\[\d+\]/, '['+index+']') + } - function reindexProp(el, prop, index) { - var current = el.getAttribute(prop) - if (!current) return - el.setAttribute(prop, reindex(current, index)) - } + const reindexProp = (el, prop, index) => { + var current = el.getAttribute(prop) + if (!current) return + el.setAttribute(prop, reindex(current, index)) + } - function clearField(control) { - if (control.tagName === 'textarea') { - control.innerText = '' - } - else if (control.type === 'radio' || control.type === 'checkbox') { - control.checked = false - } - else { - control.value = '' - } + const clearField = (control) => { + if (control.tagName === 'textarea') { + control.innerText = '' + } + else if (control.type === 'radio' || control.type === 'checkbox') { + control.checked = false + } + else { + control.value = '' } + } - _.init = function(block, el, index) { + // one instance of a repeater + class Instance { + // private functions + constructor(block, el, index) { this.block = block this.el = el this.index = index } - _.reindex = function(newIndex, clear) { + reindex(newIndex, clear) { this.index = newIndex reindexProp(this.el, 'name', newIndex) this.el.dataset.index = newIndex - this.el.querySelectorAll('input,textarea,select').forEach(function(control) { + query('input,textarea,select', this.el).forEach(control => { reindexProp(control, 'name', newIndex) reindexProp(control, 'id', newIndex) reindexProp(control, 'aria-describedby', newIndex) if (clear) clearField(control) }) - this.el.querySelectorAll('label').forEach(function(label) { + query('label', this.el).forEach(label => { reindexProp(label, 'for', newIndex) }) - this.el.querySelectorAll('.validation-message').forEach(function(error) { + query('.validation-message', this.el).forEach(error => { reindexProp(error, 'id', newIndex) }) - this.el.querySelectorAll('.remove-repeat-link').forEach(function(link) { + query('.remove-repeat-link', this.el).forEach(link => { reindexProp(link, 'id', newIndex) }) // special elements that show the user which number they're looking at - this.el.querySelectorAll('.repeat-number').forEach(function(el) { + query('.repeat-number', this.el).forEach(el => { el.innerText = ''+(newIndex+1) }) return this } - _.clear = function() { + clear() { var first - this.el.querySelectorAll('input,textarea,select').forEach(function(el) { + query('input,textarea,select', this.el).forEach(el => { if (!first) first = el clearField(el) }) @@ -137,12 +141,12 @@ export const Repeater = (function() { return this } - _.focus = function() { + focus() { var first = this.el.querySelector('input,textarea,select') if (first) first.focus() } - _.remove = function() { + remove() { // if we're the last one, we should just empty the fields if (this.block.instances.length === 1) return this.clear() @@ -164,13 +168,13 @@ export const Repeater = (function() { return this } - })(Instance.prototype) + } - function repeat(name) { + const repeat = (name) => { repeatedSets[name] && repeatedSets[name].repeat() } - function instanceFor(el) { + const instanceFor = (el) => { var fieldset = el.closest('.repeater-instance') if (!fieldset) return null @@ -186,31 +190,22 @@ export const Repeater = (function() { return block.instances[index] } - function init() { + const init = () => { repeatedSets = indexBy('name', - map(document.querySelectorAll('.repeater'), function(el) { + map(query('.repeater'), (el) => { return new Block(el).setupListeners() })) // repeat links are expected to be *outside* the repeater, so can manage // their own event listeners. - document.querySelectorAll('.repeat-link').forEach(function(link) { - handleLinkInteraction(link, function(evt) { + query('.repeat-link').forEach(link => { + handleLinkInteraction(link, (evt) => { evt.preventDefault() repeat(link.dataset.target) }) }) } - function handleLinkInteraction(el, handler) { - el.addEventListener('click', handler) - el.addEventListener('keydown', function(evt) { - // spacebar - if (evt.keyCode !== 32) return - handler(evt) - }) - } - return { repeat: repeat, init: init From 6143c05590f043aa30603fd18ec1db8c62c42cdb Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Fri, 6 Dec 2019 11:50:19 -0500 Subject: [PATCH 40/54] delete an overly-brittle test --- routes/start/start.controller.spec.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/routes/start/start.controller.spec.js b/routes/start/start.controller.spec.js index 579b87f..bebea21 100644 --- a/routes/start/start.controller.spec.js +++ b/routes/start/start.controller.spec.js @@ -1,11 +1,5 @@ const request = require('supertest') const app = require('../../app.js') -const cheerio = require('cheerio') - -function countScriptTags(res) { - var $ = cheerio.load(res.text) - return $('script').length -} const mockFn = jest .fn((req, routePath, jsPath = null) => 'default') @@ -24,17 +18,3 @@ test('Can send get request to start route and have js src set', async () => { expect(response.statusCode).toBe(200) expect(response.text).toContain('digital.canada.ca') }) - -test('Can send get request to start route and have empty js src', async () => { - const route = app.routes.get('start') - const response = await request(app).get(route.path.en) - expect(response.statusCode).toBe(200) - // call to getClientJs should return false - // which means we should have X number of script tags - // i.e. whatever the amount is in the base view - if(process.env.GOOGLE_ANALYTICS){ - expect(countScriptTags(response)).toBe(4) - } else { - expect(countScriptTags(response)).toBe(2) - } -}) From 771cdc5c832c577c2f240daf678c4e2f01ebccb0 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Fri, 6 Dec 2019 12:10:18 -0500 Subject: [PATCH 41/54] better keyboard navigation behaviour --- assets/js/button-link.js | 15 +++++++++++++++ assets/js/repeater.js | 16 ++++------------ assets/scss/components/_repeater.scss | 13 +++++++++++++ routes/global/client.js | 2 +- views/_includes/script.njk | 14 +------------- views/macros/repeater.njk | 4 ++-- 6 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 assets/js/button-link.js diff --git a/assets/js/button-link.js b/assets/js/button-link.js new file mode 100644 index 0000000..b6237dd --- /dev/null +++ b/assets/js/button-link.js @@ -0,0 +1,15 @@ +// allow for certain links to be activated by pressing the space bar, +// for keyboard-navigating users. +// +// we are using a single global listener because button-link elements +// may be added and removed from the DOM dynamically. +document.addEventListener('keydown', (e) => { + // check when we press the spacebar on a button-link element + if (e.keyCode !== 32) return + if (!e.target.classList.contains('button-link')) return + + // trigger the click handlers instead of entering a space + e.preventDefault() + e.target.click() + return false +}) diff --git a/assets/js/repeater.js b/assets/js/repeater.js index 0cba47c..f0e712f 100644 --- a/assets/js/repeater.js +++ b/assets/js/repeater.js @@ -7,15 +7,6 @@ export const Repeater = (() => { const map = (arr, fn) => Array.prototype.map.call(arr, fn) const query = (q, el=document) => el.querySelectorAll(q) - const handleLinkInteraction = (el, handler) => { - el.addEventListener('click', handler) - el.addEventListener('keydown', (evt) => { - // spacebar - if (evt.keyCode !== 32) return - handler(evt) - }) - } - const indexBy = (key, arr) => { var out = {} arr.forEach(x => { out[x[key]] = x }) @@ -42,7 +33,7 @@ export const Repeater = (() => { // we use one global listener for removal, because the repeat links may // get added and removed from the DOM, and this means we don't have to // re-register the event listeners when we clone the nodes. - handleLinkInteraction(this.container, evt => { + this.container.addEventListener('click', (evt) => { if (!evt.target.classList.contains('remove-repeat-link')) return evt.preventDefault() var instance = instanceFor(evt.target) @@ -61,6 +52,7 @@ export const Repeater = (() => { newInstance.reindex(newIndex, true) this.container.appendChild(newEl) this.instances.push(newInstance) + newInstance.focus() return newInstance } } @@ -84,7 +76,7 @@ export const Repeater = (() => { control.checked = false } else { - control.value = '' + control.value = null } } @@ -199,7 +191,7 @@ export const Repeater = (() => { // repeat links are expected to be *outside* the repeater, so can manage // their own event listeners. query('.repeat-link').forEach(link => { - handleLinkInteraction(link, (evt) => { + link.addEventListener('click', (evt) => { evt.preventDefault() repeat(link.dataset.target) }) diff --git a/assets/scss/components/_repeater.scss b/assets/scss/components/_repeater.scss index 24e52d4..391b957 100644 --- a/assets/scss/components/_repeater.scss +++ b/assets/scss/components/_repeater.scss @@ -10,3 +10,16 @@ padding: 10px 15px; } } + +a.button-link.remove-repeat-link, +a.button-link.repeat-link { + padding: 5px; +} + +a.button-link.remove-repeat-link { + background-color: red; +} + +a.button-link.repeat-link { + background-color: green; +} diff --git a/routes/global/client.js b/routes/global/client.js index 38c3635..6e25e6d 100644 --- a/routes/global/client.js +++ b/routes/global/client.js @@ -1 +1 @@ -console.log('global script file in routes/global/client.js') +import './../../assets/js/button-link' diff --git a/views/_includes/script.njk b/views/_includes/script.njk index 25189ee..9b74d2c 100644 --- a/views/_includes/script.njk +++ b/views/_includes/script.njk @@ -1,15 +1,3 @@ - -{% endif %} \ No newline at end of file +{% endif %} diff --git a/views/macros/repeater.njk b/views/macros/repeater.njk index 1e2dbe1..29335e3 100644 --- a/views/macros/repeater.njk +++ b/views/macros/repeater.njk @@ -18,7 +18,7 @@ {% macro repeatLink(name, text) %} {{__(text)}} {% endmacro %} @@ -26,7 +26,7 @@ {% macro removeRepeatLink(text) %} {{__(text)}} {% endmacro %} From 3abc29026d3b9ac4d1b9886ccf69e781a0a00026 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Fri, 6 Dec 2019 12:18:17 -0500 Subject: [PATCH 42/54] move polyfill scripts to assets/js/ --- assets/js/details-polyfill-detect.js | 9 +++++++++ routes/global/client.js | 1 + views/_includes/script.njk | 5 ----- 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 assets/js/details-polyfill-detect.js diff --git a/assets/js/details-polyfill-detect.js b/assets/js/details-polyfill-detect.js new file mode 100644 index 0000000..6779c34 --- /dev/null +++ b/assets/js/details-polyfill-detect.js @@ -0,0 +1,9 @@ +const needsPolyfill = () => { + if (typeof Promise === 'function') return false + if (document.querySelector('details') !== null) return false + return true +} + +if (needsPolyfill()) { + document.write(' - {% if $env.GOOGLE_ANALYTICS %} + + diff --git a/views/_includes/script.njk b/views/_includes/script.njk deleted file mode 100644 index 4964f85..0000000 --- a/views/_includes/script.njk +++ /dev/null @@ -1,11 +0,0 @@ -{% if $env.GOOGLE_ANALYTICS %} - - -{% endif %} diff --git a/views/base.njk b/views/base.njk index 04b28f0..4e5be04 100644 --- a/views/base.njk +++ b/views/base.njk @@ -38,7 +38,11 @@ {% include "_includes/footer.njk" %}
- {% include "_includes/script.njk" %} + + {% if $env.GOOGLE_ANALYTICS %} + {% include "_includes/googleAnalytics.njk" %} + {% endif %} + {% block scripts %} {% for val in globalJsPaths().concat(jsPaths()) %} From 88a4d271896fa16cd716116b48da0944c57badf1 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Fri, 6 Dec 2019 13:28:26 -0500 Subject: [PATCH 44/54] add assets/js/ to the default module path for client js --- routes/addresses/client.js | 2 +- routes/global/client.js | 4 ++-- routes/personal/client.js | 2 +- webpack.config.js | 3 +++ 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/routes/addresses/client.js b/routes/addresses/client.js index 7e67e32..3e2180a 100644 --- a/routes/addresses/client.js +++ b/routes/addresses/client.js @@ -1 +1 @@ -require('./../../assets/js/repeater.js') +import 'repeater' diff --git a/routes/global/client.js b/routes/global/client.js index 0b0c679..cdfcc62 100644 --- a/routes/global/client.js +++ b/routes/global/client.js @@ -1,2 +1,2 @@ -import './../../assets/js/button-link' -import './../../assets/js/details-polyfill-detect' +import 'button-link' +import 'details-polyfill-detect' diff --git a/routes/personal/client.js b/routes/personal/client.js index c8eded6..e38da8d 100644 --- a/routes/personal/client.js +++ b/routes/personal/client.js @@ -1,3 +1,3 @@ -import { fileInput } from './../../assets/js/file-input' +import { fileInput } from 'file-input' fileInput() diff --git a/webpack.config.js b/webpack.config.js index dd8201a..0448164 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -21,6 +21,9 @@ module.exports = (env, argv) => { path: path.resolve(__dirname, 'public/dist'), }, stats: 'errors-only', + resolve: { + modules: ['./assets/js'], + } }) return config From a28a6061c579463334d65f52fc7e3c83d097f177 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Fri, 6 Dec 2019 16:52:17 -0500 Subject: [PATCH 45/54] add translations for the addresses page and fix confirmation --- locales/en.json | 28 ++++++++++++++-------------- routes/confirmation/confirmation.njk | 17 ++++++++++------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/locales/en.json b/locales/en.json index e29f6b0..7cabed5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -51,18 +51,18 @@ "required": "required", "Government of Canada": "Government of Canada", "addresses.title": "Enter all addresses from the last 2 years", - "addresses.intro": "addresses.intro", + "addresses.intro": "This information is needed because lorem ipsum dolor sit amet", "addresses.number": "Address #%s", - "addresses.street": "addresses.street", - "addresses.type": "addresses.type", - "addresses.type.house": "addresses.type.house", - "addresses.type.apartment": "addresses.type.apartment", - "addresses.type.other": "addresses.type.other", - "addresses.features": "addresses.features", - "addresses.features.laundry": "addresses.features.laundry", - "addresses.features.kitchen": "addresses.features.kitchen", - "addresses.features.yard": "addresses.features.yard", - "addresses.remove": "addresses.remove", - "addresses.add-another": "addresses.add-another", - "errors.address.length": "errors.address.length" -} \ No newline at end of file + "addresses.street": "Street address", + "addresses.type": "Address Type", + "addresses.type.house": "House", + "addresses.type.apartment": "Apartment", + "addresses.type.other": "Other", + "addresses.features": "Features", + "addresses.features.laundry": "Laundry", + "addresses.features.kitchen": "Kitchen", + "addresses.features.yard": "Yard", + "addresses.remove": "[- remove]", + "addresses.add-another": "[+ add another]", + "errors.address.length": "Please fill in an address" +} diff --git a/routes/confirmation/confirmation.njk b/routes/confirmation/confirmation.njk index a237722..bae0fb3 100644 --- a/routes/confirmation/confirmation.njk +++ b/routes/confirmation/confirmation.njk @@ -25,13 +25,16 @@
  • Addresses -
      - {% for address in addresses %} -
    • {{__('addresses.street')}}: {{ address.street }}
    • -
    • {{__('addresses.type')}}: {{ address.type }}
    • -
    • {{__('addresses.features')}}: {{ address.features | dump}}
    • - {% endfor %} -
    + {% for address in addresses %} +
    + Address #{{ loop.index }} +
      +
    • {{__('addresses.street')}}: {{ address.street }}
    • +
    • {{__('addresses.type')}}: {{ address.type }}
    • +
    • {{__('addresses.features')}}: {{ address.features | dump}}
    • +
    +
    + {% endfor %}
  • From 10c35e50a947866a1843f806640babbf8483fafd Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Fri, 6 Dec 2019 16:53:24 -0500 Subject: [PATCH 46/54] remove the push hooks - we will rely on CI --- package.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/package.json b/package.json index eaf1c15..dbe55a1 100755 --- a/package.json +++ b/package.json @@ -22,11 +22,6 @@ "aws-bootstrap": "node cdk/bootstrap.js", "aws-destroy": "node cdk/destroy.js" }, - "husky": { - "hooks": { - "pre-push": "npm run lint && npm test" - } - }, "dependencies": { "@cdssnc/webpack-starter": "^2.0.2", "compression": "^1.7.4", From 98928919ad80574533d9391cdd00cf4c43246ab9 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 9 Dec 2019 10:00:16 -0500 Subject: [PATCH 47/54] disable import-checking in eslint bad imports should be caught immediately in testing anyways --- .eslintrc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index aae8884..d1153e7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,7 +3,6 @@ module.exports = { 'standard', 'prettier', 'eslint:recommended', - 'plugin:import/recommended', 'plugin:security/recommended', ], plugins: ['jest', 'security'], From f8c323e8772fa5f1807df3f7d89e7240101ebcbe Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 9 Dec 2019 10:05:43 -0500 Subject: [PATCH 48/54] use modern variable scoping in repeater.js --- assets/js/repeater.js | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/assets/js/repeater.js b/assets/js/repeater.js index f0e712f..f43bd9b 100644 --- a/assets/js/repeater.js +++ b/assets/js/repeater.js @@ -1,14 +1,15 @@ export const Repeater = (() => { "use strict" - var repeatedSets + // state for the module + let repeatedSets // mapping things that aren't quite arrays const map = (arr, fn) => Array.prototype.map.call(arr, fn) const query = (q, el=document) => el.querySelectorAll(q) const indexBy = (key, arr) => { - var out = {} + const out = {} arr.forEach(x => { out[x[key]] = x }) return out } @@ -17,13 +18,11 @@ export const Repeater = (() => { // instances. class Block { constructor(el) { - var self = this // closure fix - this.name = el.id this.container = el this.instances = map(query('.repeater-instance', el), (fieldset, i) => { - var instance = new Instance(self, fieldset, i) + const instance = new Instance(this, fieldset, i) instance.reindex(i) return instance }) @@ -36,7 +35,7 @@ export const Repeater = (() => { this.container.addEventListener('click', (evt) => { if (!evt.target.classList.contains('remove-repeat-link')) return evt.preventDefault() - var instance = instanceFor(evt.target) + const instance = instanceFor(evt.target) instance && instance.remove() }) @@ -46,9 +45,9 @@ export const Repeater = (() => { repeat() { if (!this.instances.length) throw new Error('empty instances, can\'t repeat!') - var newIndex = this.instances.length - var newEl = this.instances[0].el.cloneNode(true) - var newInstance = new Instance(this, newEl, newIndex) + const newIndex = this.instances.length + const newEl = this.instances[0].el.cloneNode(true) + const newInstance = new Instance(this, newEl, newIndex) newInstance.reindex(newIndex, true) this.container.appendChild(newEl) this.instances.push(newInstance) @@ -63,7 +62,7 @@ export const Repeater = (() => { } const reindexProp = (el, prop, index) => { - var current = el.getAttribute(prop) + const current = el.getAttribute(prop) if (!current) return el.setAttribute(prop, reindex(current, index)) } @@ -123,7 +122,7 @@ export const Repeater = (() => { } clear() { - var first + let first query('input,textarea,select', this.el).forEach(el => { if (!first) first = el clearField(el) @@ -134,7 +133,7 @@ export const Repeater = (() => { } focus() { - var first = this.el.querySelector('input,textarea,select') + const first = this.el.querySelector('input,textarea,select') if (first) first.focus() } @@ -146,7 +145,7 @@ export const Repeater = (() => { this.el.parentElement.removeChild(this.el) // reindex everything that comes after, updating name and label attributes - for (var i = this.index+1; i < this.block.instances.length; i += 1) { + for (let i = this.index+1; i < this.block.instances.length; i += 1) { this.block.instances[i].reindex(i - 1) } @@ -154,7 +153,9 @@ export const Repeater = (() => { this.block.instances.splice(this.index, 1) // focus the next fieldset if it exists, otherwise the last one - var adjacent = this.block.instances[this.index] || this.block.instances[this.index-1] + const adjacent = this.block.instances[this.index] || + this.block.instances[this.index-1] + if (adjacent) adjacent.focus() return this @@ -167,16 +168,16 @@ export const Repeater = (() => { } const instanceFor = (el) => { - var fieldset = el.closest('.repeater-instance') + const fieldset = el.closest('.repeater-instance') if (!fieldset) return null - var match = fieldset.name.match(/^\w+/) + const match = fieldset.name.match(/^\w+/) if (!match) return null - var blockName = match[0] - var index = parseInt(fieldset.dataset.index) + const blockName = match[0] + const index = parseInt(fieldset.dataset.index) - var block = repeatedSets[blockName] + const block = repeatedSets[blockName] if (!block) return null return block.instances[index] From 8f1de1016e671d29734cf47bd2b53bace7e789b5 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 9 Dec 2019 10:07:24 -0500 Subject: [PATCH 49/54] use template strings in repeater.js --- assets/js/repeater.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/js/repeater.js b/assets/js/repeater.js index f43bd9b..36f7377 100644 --- a/assets/js/repeater.js +++ b/assets/js/repeater.js @@ -43,7 +43,7 @@ export const Repeater = (() => { } repeat() { - if (!this.instances.length) throw new Error('empty instances, can\'t repeat!') + if (!this.instances.length) throw new Error(`empty instances, can't repeat!`) const newIndex = this.instances.length const newEl = this.instances[0].el.cloneNode(true) @@ -58,7 +58,7 @@ export const Repeater = (() => { const reindex = (str, index) => { // it's always going to be the first [0] or [1] or etc. - return str.replace(/\[\d+\]/, '['+index+']') + return str.replace(/\[\d+\]/, `[${index}]`) } const reindexProp = (el, prop, index) => { @@ -115,7 +115,7 @@ export const Repeater = (() => { // special elements that show the user which number they're looking at query('.repeat-number', this.el).forEach(el => { - el.innerText = ''+(newIndex+1) + el.innerText = `${newIndex+1}` }) return this From 8eb5f9ecbb91ec544add1aecb6ef975db446f864 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 9 Dec 2019 10:20:25 -0500 Subject: [PATCH 50/54] rename a confusingly-named helper function --- assets/js/repeater.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/js/repeater.js b/assets/js/repeater.js index 36f7377..0907bdc 100644 --- a/assets/js/repeater.js +++ b/assets/js/repeater.js @@ -56,7 +56,7 @@ export const Repeater = (() => { } } - const reindex = (str, index) => { + const reindexValue = (str, index) => { // it's always going to be the first [0] or [1] or etc. return str.replace(/\[\d+\]/, `[${index}]`) } @@ -64,7 +64,7 @@ export const Repeater = (() => { const reindexProp = (el, prop, index) => { const current = el.getAttribute(prop) if (!current) return - el.setAttribute(prop, reindex(current, index)) + el.setAttribute(prop, reindexValue(current, index)) } const clearField = (control) => { From b5a609aa3019f241a1805cb1e9f47f33afde4230 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 9 Dec 2019 11:50:48 -0500 Subject: [PATCH 51/54] properly set up the webpack import resolver for eslint --- .eslintrc.js | 9 ++ package-lock.json | 213 +++++++++++++++++++++++++++++++--------------- package.json | 1 + 3 files changed, 156 insertions(+), 67 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index d1153e7..7c40684 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,6 +3,7 @@ module.exports = { 'standard', 'prettier', 'eslint:recommended', + 'plugin:import/recommended', 'plugin:security/recommended', ], plugins: ['jest', 'security'], @@ -15,4 +16,12 @@ module.exports = { 'security/detect-non-literal-require': 'off', 'security/detect-non-literal-fs-filename': 'off', }, + overrides: [ + { + files: ["routes/*/client.js", "assets/js/*.js"], + settings: { + "import/resolver": "webpack", + }, + } + ], } diff --git a/package-lock.json b/package-lock.json index 52d8074..c739602 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "node-starter-app", - "version": "6.0.3", + "version": "6.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -3232,6 +3232,12 @@ "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, + "array-find": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-find/-/array-find-1.0.0.tgz", + "integrity": "sha1-bI4obRHtdoMn+OYuzuhzU8o+eLg=", + "dev": true + }, "array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", @@ -5761,6 +5767,73 @@ } } }, + "eslint-import-resolver-webpack": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.12.0.tgz", + "integrity": "sha512-S0hJUrbcTsVjPQRa8bdNQrv/sc45bGwhtoJk3Ru/1qZWnR4oArGKHYe61xT03iHZSo9K/xL63rTMiayW5+NGMQ==", + "dev": true, + "requires": { + "array-find": "^1.0.0", + "debug": "^2.6.9", + "enhanced-resolve": "^0.9.1", + "find-root": "^1.1.0", + "has": "^1.0.3", + "interpret": "^1.2.0", + "lodash": "^4.17.15", + "node-libs-browser": "^1.0.0 || ^2.0.0", + "resolve": "^1.13.1", + "semver": "^5.7.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "enhanced-resolve": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", + "integrity": "sha1-TW5omzcl+GCQknzMhs2fFjW4ni4=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.2.0", + "tapable": "^0.1.8" + } + }, + "memory-fs": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz", + "integrity": "sha1-8rslNovBIeORwlIN6Slpyu4KApA=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "resolve": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.13.1.tgz", + "integrity": "sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "tapable": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz", + "integrity": "sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=", + "dev": true + } + } + }, "eslint-module-utils": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz", @@ -6582,6 +6655,12 @@ "pkg-dir": "^3.0.0" } }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": true + }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -6854,25 +6933,25 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "resolved": false, "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "optional": true }, "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "optional": true }, "aproba": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "resolved": false, "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "optional": true, "requires": { @@ -6882,13 +6961,13 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "optional": true }, "brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "optional": true, "requires": { @@ -6898,37 +6977,37 @@ }, "chownr": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "resolved": false, "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "optional": true }, "code-point-at": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "optional": true }, "concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "optional": true }, "console-control-strings": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "optional": true }, "core-util-is": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "resolved": false, "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "optional": true }, "debug": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "resolved": false, "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "optional": true, "requires": { @@ -6937,25 +7016,25 @@ }, "deep-extend": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "resolved": false, "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "optional": true }, "delegates": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "resolved": false, "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "optional": true }, "detect-libc": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "resolved": false, "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "optional": true }, "fs-minipass": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "resolved": false, "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "optional": true, "requires": { @@ -6964,13 +7043,13 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "resolved": false, "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "optional": true }, "gauge": { "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "resolved": false, "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "optional": true, "requires": { @@ -6986,7 +7065,7 @@ }, "glob": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "resolved": false, "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "optional": true, "requires": { @@ -7000,13 +7079,13 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "resolved": false, "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "optional": true }, "iconv-lite": { "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "resolved": false, "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "optional": true, "requires": { @@ -7015,7 +7094,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "resolved": false, "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "optional": true, "requires": { @@ -7024,7 +7103,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "resolved": false, "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "optional": true, "requires": { @@ -7034,19 +7113,19 @@ }, "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "optional": true }, "ini": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "resolved": false, "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "optional": true, "requires": { @@ -7055,13 +7134,13 @@ }, "isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "resolved": false, "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "optional": true, "requires": { @@ -7070,13 +7149,13 @@ }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "optional": true }, "minipass": { "version": "2.3.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "resolved": false, "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "optional": true, "requires": { @@ -7086,7 +7165,7 @@ }, "minizlib": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "resolved": false, "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "optional": true, "requires": { @@ -7095,7 +7174,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "optional": true, "requires": { @@ -7104,13 +7183,13 @@ }, "ms": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "resolved": false, "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "optional": true }, "needle": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.0.tgz", + "resolved": false, "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "optional": true, "requires": { @@ -7121,7 +7200,7 @@ }, "node-pre-gyp": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", + "resolved": false, "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "optional": true, "requires": { @@ -7139,7 +7218,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "resolved": false, "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "optional": true, "requires": { @@ -7149,13 +7228,13 @@ }, "npm-bundled": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "resolved": false, "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "optional": true }, "npm-packlist": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", + "resolved": false, "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "optional": true, "requires": { @@ -7165,7 +7244,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "resolved": false, "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "optional": true, "requires": { @@ -7177,19 +7256,19 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "optional": true }, "object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "resolved": false, "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "optional": true }, "once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "optional": true, "requires": { @@ -7198,19 +7277,19 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "resolved": false, "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": false, "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "optional": true }, "osenv": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "resolved": false, "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "optional": true, "requires": { @@ -7220,19 +7299,19 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": false, "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "optional": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "resolved": false, "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "optional": true }, "rc": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "resolved": false, "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "optional": true, "requires": { @@ -7244,7 +7323,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": false, "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "optional": true } @@ -7252,7 +7331,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "optional": true, "requires": { @@ -7267,7 +7346,7 @@ }, "rimraf": { "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "resolved": false, "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "optional": true, "requires": { @@ -7276,43 +7355,43 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "optional": true }, "safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "resolved": false, "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "optional": true }, "sax": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "resolved": false, "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "optional": true }, "semver": { "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "resolved": false, "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "optional": true }, "set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "resolved": false, "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "resolved": false, "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "optional": true }, "string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "optional": true, "requires": { @@ -7323,7 +7402,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "optional": true, "requires": { @@ -7332,7 +7411,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "optional": true, "requires": { @@ -7341,13 +7420,13 @@ }, "strip-json-comments": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "resolved": false, "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "optional": true }, "tar": { "version": "4.4.8", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "resolved": false, "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "optional": true, "requires": { @@ -7362,13 +7441,13 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "resolved": false, "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "optional": true }, "wide-align": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "resolved": false, "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "optional": true, "requires": { @@ -7377,13 +7456,13 @@ }, "wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "optional": true }, "yallist": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "resolved": false, "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "optional": true } diff --git a/package.json b/package.json index dbe55a1..83a1442 100755 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "eslint": "^6.6.0", "eslint-config-prettier": "^6.7.0", "eslint-config-standard": "^14.1.0", + "eslint-import-resolver-webpack": "^0.12.0", "eslint-plugin-import": "^2.18.2", "eslint-plugin-jest": "^23.0.4", "eslint-plugin-node": "^10.0.0", From 62b898a1cdc87e9704dfad02f061ef05eb3c0320 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 9 Dec 2019 12:40:35 -0500 Subject: [PATCH 52/54] suppress the lgtm bot warning for document.write --- assets/js/details-polyfill-detect.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/js/details-polyfill-detect.js b/assets/js/details-polyfill-detect.js index 6779c34..0a4fcb3 100644 --- a/assets/js/details-polyfill-detect.js +++ b/assets/js/details-polyfill-detect.js @@ -5,5 +5,6 @@ const needsPolyfill = () => { } if (needsPolyfill()) { - document.write('