hof 20.0.0-beta.9 → 20.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/.nyc_output/7c548a7f-5c40-44b2-b9fb-648341a23d6f.json +1 -0
- package/.nyc_output/processinfo/7c548a7f-5c40-44b2-b9fb-648341a23d6f.json +1 -0
- package/.nyc_output/processinfo/index.json +1 -1
- package/README.md +335 -256
- package/components/index.js +2 -1
- package/components/notify/index.js +60 -0
- package/components/notify/notify.js +25 -0
- package/config/sanitisation-rules.js +20 -17
- package/controller/base-controller.js +5 -3
- package/controller/controller.js +19 -4
- package/frontend/template-mixins/mixins/template-mixins.js +7 -3
- package/frontend/template-mixins/partials/forms/checkbox-group.html +10 -1
- package/frontend/template-mixins/partials/forms/input-text-date.html +1 -1
- package/frontend/template-mixins/partials/forms/input-text-group.html +5 -3
- package/frontend/template-mixins/partials/forms/option-group.html +9 -0
- package/frontend/template-mixins/partials/forms/select.html +1 -1
- package/frontend/template-mixins/partials/forms/textarea-group.html +2 -2
- package/frontend/template-partials/views/layout.html +10 -3
- package/frontend/template-partials/views/partials/cookie-banner.html +1 -1
- package/frontend/template-partials/views/partials/form.html +2 -1
- package/frontend/template-partials/views/partials/maincontent-left.html +2 -2
- package/frontend/template-partials/views/partials/navigation.html +2 -2
- package/frontend/template-partials/views/partials/summary-table-row.html +2 -2
- package/frontend/template-partials/views/partials/warn.html +7 -0
- package/frontend/template-partials/views/session-timeout.html +2 -1
- package/frontend/themes/gov-uk/styles/govuk.scss +4 -0
- package/frontend/themes/gov-uk/styles/modules/_validation.scss +2 -2
- package/frontend/toolkit/assets/javascript/form-focus.js +10 -1
- package/frontend/toolkit/assets/javascript/progressive-reveal.js +3 -1
- package/frontend/toolkit/assets/javascript/validation.js +6 -1
- package/index.js +1 -1
- package/middleware/errors.js +2 -0
- package/package.json +4 -2
- package/sandbox/apps/sandbox/fields.js +1 -0
- package/sandbox/apps/sandbox/index.js +1 -5
- package/sandbox/assets/scss/app.scss +0 -52
- package/sandbox/package.json +2 -0
- package/sandbox/public/css/app.css +4938 -4990
- package/sandbox/public/js/bundle.js +79 -65
- package/sandbox/server.js +2 -1
- package/sandbox/yarn.lock +39 -564
- package/wizard/middleware/check-progress.js +36 -1
- package/.nyc_output/e2fdc3eb-4fd2-47e0-a392-fe5f665776a4.json +0 -1
- package/.nyc_output/processinfo/e2fdc3eb-4fd2-47e0-a392-fe5f665776a4.json +0 -1
package/components/index.js
CHANGED
@@ -0,0 +1,60 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const Notify = require('./notify');
|
4
|
+
const Hogan = require('hogan.js');
|
5
|
+
const fs = require('fs');
|
6
|
+
|
7
|
+
module.exports = config => {
|
8
|
+
const notify = new Notify(config);
|
9
|
+
config.parse = config.parse || (data => data);
|
10
|
+
|
11
|
+
if (!config.recipient) {
|
12
|
+
throw new Error('Email recipient must be defined');
|
13
|
+
}
|
14
|
+
if (typeof config.template !== 'string') {
|
15
|
+
throw new Error('Email template must be defined');
|
16
|
+
}
|
17
|
+
|
18
|
+
return superclass => class NotifyBehaviour extends superclass {
|
19
|
+
successHandler(req, res, next) {
|
20
|
+
Promise.resolve()
|
21
|
+
.then(() => {
|
22
|
+
return new Promise((resolve, reject) => {
|
23
|
+
fs.readFile(config.template, (err, template) => err ? reject(err) : resolve(template.toString('utf8')));
|
24
|
+
});
|
25
|
+
})
|
26
|
+
.then(template => {
|
27
|
+
const data = config.parse(req.sessionModel.toJSON(), req.translate);
|
28
|
+
return Hogan.compile(template).render(data);
|
29
|
+
})
|
30
|
+
.then(body => {
|
31
|
+
const settings = { body };
|
32
|
+
|
33
|
+
if (typeof config.recipient === 'function') {
|
34
|
+
settings.recipient = config.recipient(req.sessionModel.toJSON());
|
35
|
+
} else {
|
36
|
+
settings.recipient = req.sessionModel.get(config.recipient) || config.recipient;
|
37
|
+
}
|
38
|
+
if (typeof settings.recipient !== 'string' || !settings.recipient.includes('@')) {
|
39
|
+
throw new Error('hof-behaviour-emailer: invalid recipient');
|
40
|
+
}
|
41
|
+
|
42
|
+
if (typeof config.subject === 'function') {
|
43
|
+
settings.subject = config.subject(req.sessionModel.toJSON(), req.translate);
|
44
|
+
} else {
|
45
|
+
settings.subject = config.subject;
|
46
|
+
}
|
47
|
+
|
48
|
+
return settings;
|
49
|
+
})
|
50
|
+
.then(settings => {
|
51
|
+
return notify.send(settings);
|
52
|
+
})
|
53
|
+
.then(() => {
|
54
|
+
super.successHandler(req, res, next);
|
55
|
+
}, next);
|
56
|
+
}
|
57
|
+
};
|
58
|
+
};
|
59
|
+
|
60
|
+
module.exports.Notify = Notify;
|
@@ -0,0 +1,25 @@
|
|
1
|
+
'use strict';
|
2
|
+
const NotifyClient = require('notifications-node-client').NotifyClient;
|
3
|
+
const { v4: uuidv4 } = require('uuid');
|
4
|
+
|
5
|
+
module.exports = class Notify {
|
6
|
+
constructor(opts) {
|
7
|
+
const options = opts || {};
|
8
|
+
this.options = options;
|
9
|
+
this.notifyClient = new NotifyClient(options.notifyApiKey);
|
10
|
+
this.notifyTemplate = options.notifyTemplate;
|
11
|
+
}
|
12
|
+
|
13
|
+
send(email) {
|
14
|
+
const reference = uuidv4();
|
15
|
+
|
16
|
+
return this.notifyClient.sendEmail(this.notifyTemplate, email.recipient, {
|
17
|
+
personalisation: {
|
18
|
+
'email-subject': email.subject,
|
19
|
+
'email-body': email.body
|
20
|
+
},
|
21
|
+
reference });
|
22
|
+
}
|
23
|
+
};
|
24
|
+
|
25
|
+
module.exports.NotifyClient = NotifyClient;
|
@@ -3,27 +3,30 @@
|
|
3
3
|
|
4
4
|
const sanitisationBlacklistArray = {
|
5
5
|
// Input will be sanitised using the below rules
|
6
|
+
// Each one is an array which will run sequentially
|
7
|
+
// Some have multiple steps which allow us to remove dups then append the suffix
|
8
|
+
// Useful for ensuring we're not re-sanitising the same input multiple times (and appending extra hypens each time)
|
6
9
|
// The key is what we're sanitising out
|
7
10
|
// The regex is the rule we used to find them (note some dictate repeating characters)
|
8
11
|
// And the replace is what we're replacing that pattern with. Usually nothing sometimes a
|
9
12
|
// single character or sometimes a single character followed by a "-"
|
10
|
-
'/*': { regex: '\/\\*', replace: '-' },
|
11
|
-
'*/': { regex: '\\*\\/', replace: '-' },
|
12
|
-
'|': { regex: '\\|', replace: '-' },
|
13
|
-
'&&': { regex: '&&+', replace: '&' },
|
14
|
-
'@@': { regex: '@@+', replace: '@' },
|
15
|
-
'/..;/': { regex: '/\\.\\.;/', replace: '-' }, // Purposely input before ".." as they conflict
|
16
|
-
// '..': { regex: '\\.\\.+', replace: '.' }, // Agreed to disable this rule for now unless its specifically required
|
17
|
-
'/etc/passwd': { regex: '\/etc\/passwd', replace: '-' },
|
18
|
-
'c:\\': { regex: 'c:\\\\', replace: '-' },
|
19
|
-
'cmd.exe': { regex: 'cmd\\.exe', replace: '-' },
|
20
|
-
'<': { regex: '<', replace: '<-' },
|
21
|
-
'>': { regex: '>', replace: '>-' },
|
22
|
-
'[': { regex: '\\[+', replace: '[-' },
|
23
|
-
']': { regex: '\\]+', replace: ']-' },
|
24
|
-
'~': { regex: '~', replace: '~-' },
|
25
|
-
'&#': { regex: '&#', replace: '-' },
|
26
|
-
'%U': { regex: '%U', replace: '-' }
|
13
|
+
'/*': [{ regex: '\/\\*', replace: '-' }],
|
14
|
+
'*/': [{ regex: '\\*\\/', replace: '-' }],
|
15
|
+
'|': [{ regex: '\\|', replace: '-' }],
|
16
|
+
'&&': [{ regex: '&&+', replace: '&' }],
|
17
|
+
'@@': [{ regex: '@@+', replace: '@' }],
|
18
|
+
'/..;/': [{ regex: '/\\.\\.;/', replace: '-' }], // Purposely input before ".." as they conflict
|
19
|
+
// '..': [{ regex: '\\.\\.+', replace: '.' }], // Agreed to disable this rule for now unless its specifically required
|
20
|
+
'/etc/passwd': [{ regex: '\/etc\/passwd', replace: '-' }],
|
21
|
+
'c:\\': [{ regex: 'c:\\\\', replace: '-' }],
|
22
|
+
'cmd.exe': [{ regex: 'cmd\\.exe', replace: '-' }],
|
23
|
+
'<': [{ regex: '<+', replace: '<' }, { regex: '<(?!-)', replace: '<-' }],
|
24
|
+
'>': [{ regex: '>+', replace: '>' }, { regex: '>(?!-)', replace: '>-' }],
|
25
|
+
'[': [{ regex: '\\[+', replace: '[' }, { regex: '\\[(?!-)', replace: '[-' }],
|
26
|
+
']': [{ regex: '\\]+', replace: ']-' }, { regex: '\\](?!-)', replace: ']-' }],
|
27
|
+
'~': [{ regex: '~+', replace: '~' }, { regex: '~(?!-)', replace: '~-' }],
|
28
|
+
'&#': [{ regex: '&#', replace: '-' }],
|
29
|
+
'%U': [{ regex: '%U', replace: '-' }]
|
27
30
|
};
|
28
31
|
|
29
32
|
module.exports = sanitisationBlacklistArray;
|
@@ -177,9 +177,11 @@ module.exports = class BaseController extends EventEmitter {
|
|
177
177
|
// For each property in our form data
|
178
178
|
Object.keys(sanitisationBlacklistArray).forEach(function (blacklisted, blacklistedIndex) {
|
179
179
|
const blacklistedDetail = sanitisationBlacklistArray[blacklisted];
|
180
|
-
|
181
|
-
|
182
|
-
|
180
|
+
blacklistedDetail.forEach(step => {
|
181
|
+
const regexQuery = new RegExp(step.regex, 'gi');
|
182
|
+
// Will perform the required replace based on our passed in regex and the replace string
|
183
|
+
req.form.values[property] = req.form.values[property].replace(regexQuery, step.replace);
|
184
|
+
});
|
183
185
|
});
|
184
186
|
}
|
185
187
|
});
|
package/controller/controller.js
CHANGED
@@ -113,8 +113,10 @@ module.exports = class Controller extends BaseController {
|
|
113
113
|
baseUrl: req.baseUrl,
|
114
114
|
skipToMain: this.getFirstFormItem(req.form.options.fields),
|
115
115
|
title: this.getTitle(route, lookup, req.form.options.fields, res.locals),
|
116
|
+
journeyHeaderURL: this.getJourneyHeaderURL(req.baseUrl),
|
116
117
|
header: this.getHeader(route, lookup, res.locals),
|
117
118
|
captionHeading: this.getCaptionHeading(route, lookup, res.locals),
|
119
|
+
warning: this.getWarning(route, lookup, res.locals),
|
118
120
|
subHeading: this.getSubHeading(route, lookup, res.locals),
|
119
121
|
intro: this.getIntro(route, lookup, res.locals),
|
120
122
|
backLink: this.getBackLink(req, res),
|
@@ -123,6 +125,10 @@ module.exports = class Controller extends BaseController {
|
|
123
125
|
}, stepLocals);
|
124
126
|
}
|
125
127
|
|
128
|
+
getJourneyHeaderURL(url) {
|
129
|
+
return url === '' ? '/' : url;
|
130
|
+
}
|
131
|
+
|
126
132
|
getFirstFormItem(fields) {
|
127
133
|
let firstFieldKey;
|
128
134
|
if (_.size(fields)) {
|
@@ -143,6 +149,10 @@ module.exports = class Controller extends BaseController {
|
|
143
149
|
return lookup(`pages.${route}.subHeading`, locals);
|
144
150
|
}
|
145
151
|
|
152
|
+
getWarning(route, lookup, locals) {
|
153
|
+
return lookup(`pages.${route}.warning`, locals);
|
154
|
+
}
|
155
|
+
|
146
156
|
getTitle(route, lookup, fields, locals) {
|
147
157
|
let fieldName = '';
|
148
158
|
if (_.size(fields)) {
|
@@ -166,13 +176,18 @@ module.exports = class Controller extends BaseController {
|
|
166
176
|
Object.keys(req.form.errors).forEach(key => {
|
167
177
|
if (req.form && req.form.options && req.form.options.fields) {
|
168
178
|
const field = req.form.options.fields[key];
|
169
|
-
// get first option for radios
|
170
|
-
if (field.mixin === 'radio-group') {
|
171
|
-
|
179
|
+
// get first option for radios and checkbox
|
180
|
+
if (field.mixin === 'radio-group' || field.mixin === 'checkbox-group') {
|
181
|
+
// get first option for radios and checkbox where there is a toggle
|
182
|
+
if(typeof field.options[0] === 'object') {
|
183
|
+
req.form.errors[key].errorLinkId = key + '-' + field.options[0].value;
|
184
|
+
} else {
|
185
|
+
req.form.errors[key].errorLinkId = key + '-' + field.options[0];
|
186
|
+
}
|
172
187
|
// eslint-disable-next-line brace-style
|
173
188
|
}
|
174
189
|
// get first field for date input control
|
175
|
-
else if (field && field.
|
190
|
+
else if (field && field.mixin === 'input-date') {
|
176
191
|
req.form.errors[key].errorLinkId = key + '-day';
|
177
192
|
} else {
|
178
193
|
req.form.errors[key].errorLinkId = key;
|
@@ -215,6 +215,7 @@ module.exports = function (options) {
|
|
215
215
|
child: field.child,
|
216
216
|
isPageHeading: field.isPageHeading,
|
217
217
|
attributes: field.attributes,
|
218
|
+
isPrefixOrSuffix: _.map(field.attributes, item => {if (item.prefix || item.suffix !== undefined) return true;}),
|
218
219
|
renderChild: renderChild.bind(this)
|
219
220
|
});
|
220
221
|
}
|
@@ -224,6 +225,7 @@ module.exports = function (options) {
|
|
224
225
|
const field = Object.assign({}, this.options.fields[key] || options.fields[key]);
|
225
226
|
const legend = field.legend;
|
226
227
|
const detail = field.detail;
|
228
|
+
const warningValue = 'fields.' + key + '.warning';
|
227
229
|
let legendClassName;
|
228
230
|
let legendValue = 'fields.' + key + '.legend';
|
229
231
|
if (legend) {
|
@@ -241,6 +243,8 @@ module.exports = function (options) {
|
|
241
243
|
legendClassName: legendClassName,
|
242
244
|
role: opts.type === 'radio' ? 'radiogroup' : 'group',
|
243
245
|
isPageHeading: field.isPageHeading,
|
246
|
+
isWarning: field.isWarning,
|
247
|
+
warning: t(warningValue),
|
244
248
|
detail: detail ? detail : '',
|
245
249
|
hint: conditionalTranslate(getTranslationKey(field, key, 'hint')),
|
246
250
|
options: _.map(field.options, function (obj) {
|
@@ -303,7 +307,7 @@ module.exports = function (options) {
|
|
303
307
|
invalid: this.errors && this.errors[key] && opts.required,
|
304
308
|
label: t(fieldLabel || 'fields.' + key + '.label'),
|
305
309
|
selected: selected,
|
306
|
-
className: classNames(field) || '
|
310
|
+
className: classNames(field) || 'govuk-label govuk-checkboxes__label',
|
307
311
|
child: field.child,
|
308
312
|
renderChild: renderChild.bind(this)
|
309
313
|
});
|
@@ -444,8 +448,8 @@ module.exports = function (options) {
|
|
444
448
|
parts.push(dayPart);
|
445
449
|
}
|
446
450
|
|
447
|
-
const monthPart = compiled['partials/forms/input-text-date'].render(inputText.call(this, key + '-month', { pattern: '[0-9]*', min: 1, max: 12, maxlength: 2, hintId: key + '-hint', date: true, autocomplete: autocomplete.month, formGroupClassName, className: classNameMonth,
|
448
|
-
const yearPart = compiled['partials/forms/input-text-date'].render(inputText.call(this, key + '-year', { pattern: '[0-9]*', maxlength: 4, hintId: key + '-hint', date: true, autocomplete: autocomplete.year, formGroupClassName, className: classNameYear,
|
451
|
+
const monthPart = compiled['partials/forms/input-text-date'].render(inputText.call(this, key + '-month', { pattern: '[0-9]*', min: 1, max: 12, maxlength: 2, hintId: key + '-hint', date: true, autocomplete: autocomplete.month, formGroupClassName, className: classNameMonth, isThisRequired }));
|
452
|
+
const yearPart = compiled['partials/forms/input-text-date'].render(inputText.call(this, key + '-year', { pattern: '[0-9]*', maxlength: 4, hintId: key + '-hint', date: true, autocomplete: autocomplete.year, formGroupClassName, className: classNameYear, isThisRequired }));
|
449
453
|
|
450
454
|
return parts.concat(monthPart, yearPart).join('\n');
|
451
455
|
};
|
@@ -5,6 +5,15 @@
|
|
5
5
|
{{legend}}
|
6
6
|
{{#isPageHeading}}</h1>{{/isPageHeading}}
|
7
7
|
</legend>
|
8
|
+
{{#isWarning}}
|
9
|
+
<div class="govuk-warning-text">
|
10
|
+
<span class="govuk-warning-text__icon" aria-hidden="true">!</span>
|
11
|
+
<strong class="govuk-warning-text__text">
|
12
|
+
<span class="govuk-warning-text__assistive">Warning</span>
|
13
|
+
{{warning}}
|
14
|
+
</strong>
|
15
|
+
</div>
|
16
|
+
{{/isWarning}}
|
8
17
|
{{#hint}}<div id="{{key}}-hint" class="govuk-hint">{{hint}}</div>{{/hint}}
|
9
18
|
{{#error}}
|
10
19
|
<p id="{{key}}-error" class="govuk-error-message">
|
@@ -28,7 +37,7 @@
|
|
28
37
|
>
|
29
38
|
<label class="govuk-label govuk-checkboxes__label" for="{{key}}-{{value}}">
|
30
39
|
{{{label}}}
|
31
|
-
{{#optionHint}}<div id="{{key}}-{{value}}-item-hint" class="govuk-hint
|
40
|
+
{{#optionHint}}<div id="{{key}}-{{value}}-item-hint" class="govuk-hint">{{optionHint}}</div>{{/optionHint}}
|
32
41
|
</label>
|
33
42
|
</div>
|
34
43
|
{{#renderChild}}{{/renderChild}}
|
@@ -14,7 +14,7 @@
|
|
14
14
|
type="{{type}}"
|
15
15
|
name="{{id}}"
|
16
16
|
id="{{id}}"
|
17
|
-
class="govuk-input{{#className}} {{className}}{{/className}}{{#error}}
|
17
|
+
class="govuk-input{{#className}} {{className}}{{/className}}{{#error}} govuk-input--error{{/error}}"
|
18
18
|
aria-required="{{required}}"
|
19
19
|
{{#value}} value="{{value}}"{{/value}}
|
20
20
|
{{#min}} min="{{min}}"{{/min}}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<div id="{{id}}-group" class="{{#compound}} form-group-compound{{/compound}}{{#formGroupClassName}}{{formGroupClassName}}{{/formGroupClassName}}{{#error}} govuk-form-group--error{{/error}}">
|
2
|
-
{{#isPageHeading}}<h1 class="govuk-label-wrapper">{{/isPageHeading}}<label for="{{id}}" class="{{labelClassName}}">
|
2
|
+
{{#isPageHeading}}<h1 class="govuk-label-wrapper">{{/isPageHeading}}<label for="{{id}}" class="{{labelClassName}}{{#isPageHeading}}govuk-label--l{{/isPageHeading}}">
|
3
3
|
{{{label}}}
|
4
4
|
</label>
|
5
5
|
{{#isPageHeading}}</h1>{{/isPageHeading}}
|
@@ -10,6 +10,7 @@
|
|
10
10
|
</p>
|
11
11
|
{{/error}}
|
12
12
|
{{#renderChild}}{{/renderChild}}
|
13
|
+
{{#isPrefixOrSuffix}}<div class="govuk-input__wrapper">{{/isPrefixOrSuffix}}
|
13
14
|
{{#attributes}}
|
14
15
|
{{#prefix}}
|
15
16
|
<div class="govuk-input__prefix" aria-hidden="true">{{prefix}}</div>
|
@@ -19,7 +20,7 @@
|
|
19
20
|
type="{{type}}"
|
20
21
|
name="{{id}}"
|
21
22
|
id="{{id}}"
|
22
|
-
class="{{^className}}govuk-input{{/className}}{{#className}}{{className}}{{/className}}{{#error}}
|
23
|
+
class="{{^className}}govuk-input{{/className}}{{#className}}{{className}}{{/className}}{{#error}} govuk-input--error{{/error}}"
|
23
24
|
aria-required="{{required}}"
|
24
25
|
{{#value}} value="{{value}}"{{/value}}
|
25
26
|
{{#min}} min="{{min}}"{{/min}}
|
@@ -35,7 +36,8 @@
|
|
35
36
|
>
|
36
37
|
{{#attributes}}
|
37
38
|
{{#suffix}}
|
38
|
-
<div class="govuk-
|
39
|
+
<div class="govuk-input__suffix" aria-hidden="true">{{suffix}}</div>
|
39
40
|
{{/suffix}}
|
40
41
|
{{/attributes}}
|
42
|
+
{{#isPrefixOrSuffix}}</div>{{/isPrefixOrSuffix}}
|
41
43
|
</div>
|
@@ -5,6 +5,15 @@
|
|
5
5
|
{{legend}}
|
6
6
|
{{#isPageHeading}}</h1>{{/isPageHeading}}
|
7
7
|
</legend>
|
8
|
+
{{#isWarning}}
|
9
|
+
<div class="govuk-warning-text">
|
10
|
+
<span class="govuk-warning-text__icon" aria-hidden="true">!</span>
|
11
|
+
<strong class="govuk-warning-text__text">
|
12
|
+
<span class="govuk-warning-text__assistive">Warning</span>
|
13
|
+
{{warning}}
|
14
|
+
</strong>
|
15
|
+
</div>
|
16
|
+
{{/isWarning}}
|
8
17
|
{{#hint}}<div id="{{key}}-hint" class="govuk-hint">{{hint}}</div>{{/hint}}
|
9
18
|
{{#error}}<p id="{{key}}-error" class="govuk-error-message"><span class="govuk-visually-hidden">Error:</span> {{error.message}}</p>{{/error}}
|
10
19
|
{{{detail}}}
|
@@ -9,7 +9,7 @@
|
|
9
9
|
{{/error}}
|
10
10
|
</label>
|
11
11
|
{{#isPageHeading}}</h1>{{/isPageHeading}}
|
12
|
-
<select id="{{id}}" class="govuk-select{{#className}} {{className}}{{/className}}{{#error}}
|
12
|
+
<select id="{{id}}" class="govuk-select{{#className}} {{className}}{{/className}}{{#error}} govuk-select--error{{/error}}" name="{{id}}" aria-required="{{required}}">
|
13
13
|
{{#options}}
|
14
14
|
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{label}}</option>
|
15
15
|
{{/options}}
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<div class="govuk-character-count" data-module="govuk-character-count" data-maxlength="{{maxlength}}">
|
3
3
|
{{/maxlength}}
|
4
4
|
<div id="{{id}}-group" class="{{#compound}}form-group-compound {{/compound}}{{#formGroupClassName}}{{formGroupClassName}}{{/formGroupClassName}}{{#error}} govuk-form-group--error{{/error}}">
|
5
|
-
{{#isPageHeading}}<h1 class="govuk-label-wrapper">{{/isPageHeading}}<label for="{{id}}" class="{{labelClassName}}">
|
5
|
+
{{#isPageHeading}}<h1 class="govuk-label-wrapper">{{/isPageHeading}}<label for="{{id}}" class="{{labelClassName}}{{#isPageHeading}}govuk-label--l{{/isPageHeading}}">
|
6
6
|
{{{label}}}
|
7
7
|
{{#error}}
|
8
8
|
<p id="{{id}}-error" class="govuk-error-message">
|
@@ -16,7 +16,7 @@
|
|
16
16
|
<textarea
|
17
17
|
name="{{id}}"
|
18
18
|
id="{{id}}"
|
19
|
-
class="govuk-textarea{{#className}} {{className}}{{/className}} {{#maxlength}}maxlength{{/maxlength}}{{#error}}
|
19
|
+
class="govuk-textarea{{#className}} {{className}}{{/className}} {{#maxlength}}maxlength{{/maxlength}}{{#error}} govuk-input--error{{/error}}"
|
20
20
|
aria-required="{{required}}"
|
21
21
|
{{#maxlength}} maxlength="{{maxlength}}"{{/maxlength}}
|
22
22
|
{{#attributes}}
|
@@ -44,9 +44,16 @@
|
|
44
44
|
{{/gaTagId}}
|
45
45
|
{{/cookieMessage}}
|
46
46
|
{{$footerSupportLinks}}
|
47
|
-
<
|
48
|
-
|
49
|
-
|
47
|
+
<ul class="govuk-footer__inline-list">
|
48
|
+
{{#footerSupportLinks}}
|
49
|
+
<li class="govuk-footer__inline-list-item"><a class="govuk-footer__link" href="{{path}}">{{#t}}{{property}}{{/t}}</a></li>
|
50
|
+
{{/footerSupportLinks}}
|
51
|
+
{{^footerSupportLinks}}
|
52
|
+
<li class="govuk-footer__inline-list-item"><a class="govuk-footer__link" href="/cookies">{{#t}}base.cookies{{/t}}</a></li>
|
53
|
+
<li class="govuk-footer__inline-list-item"><a class="govuk-footer__link" href="/accessibility">{{#t}}base.accessibility{{/t}}</a></li>
|
54
|
+
<li class="govuk-footer__inline-list-item"><a class="govuk-footer__link" href="/terms-and-conditions">{{#t}}base.terms{{/t}}</a></li>
|
55
|
+
{{/footerSupportLinks}}
|
56
|
+
</ul>
|
50
57
|
{{/footerSupportLinks}}
|
51
58
|
{{$bodyEnd}}
|
52
59
|
{{> partials-gatag}}
|
@@ -20,7 +20,7 @@
|
|
20
20
|
</div>
|
21
21
|
<div class="gem-c-cookie-banner__confirmation govuk-width-container" tabindex="0" hidden="" id="cookie-banner-submitted" >
|
22
22
|
<p class="gem-c-cookie-banner__confirmation-message" role="alert">
|
23
|
-
Your cookie preferences have been saved. You can <a class="govuk-link" data-module="gem-track-click" data-track-category="cookieBanner" data-track-action="Cookie banner settings clicked from confirmation" href="/
|
23
|
+
Your cookie preferences have been saved. You can <a class="govuk-link" data-module="gem-track-click" data-track-category="cookieBanner" data-track-action="Cookie banner settings clicked from confirmation" href="/cookies">change your cookie settings</a> at any time.
|
24
24
|
</p>
|
25
25
|
<div class="govuk-button-group">
|
26
26
|
<button class="gem-c-cookie-banner__hide-button govuk-button" id="hide-cookie-banner">Hide this message</button>
|
@@ -1,7 +1,8 @@
|
|
1
1
|
<form action="" method="POST" {{$encoding}}{{/encoding}} autocomplete="off" novalidate="true" spellcheck="false">
|
2
2
|
{{$intro}}
|
3
|
-
{{#intro}}<
|
3
|
+
{{#intro}}<p>{{intro}}</p>{{/intro}}
|
4
4
|
{{/intro}}
|
5
|
+
|
5
6
|
{{$form}}{{/form}}
|
6
7
|
{{#csrf-token}}
|
7
8
|
<input type="hidden" name="x-csrf-token" value="{{csrf-token}}" />
|
@@ -3,8 +3,8 @@
|
|
3
3
|
<div class="govuk-grid-column-two-thirds">
|
4
4
|
{{$validationSummary}}{{/validationSummary}}
|
5
5
|
{{#captionHeading}}<span class="govuk-caption-l">{{captionHeading}}</span>{{/captionHeading}}
|
6
|
-
{{#header}}<h1 class="govuk-heading-
|
7
|
-
{{#subHeading}}<div id="page-header" class="govuk-heading-
|
6
|
+
{{#header}}<h1 class="govuk-heading-l">{{header}}</h1>{{/header}}
|
7
|
+
{{#subHeading}}<div id="page-header"><h2 class="govuk-heading-m">{{subHeading}}</h2></div>{{/subHeading}}
|
8
8
|
{{$content}}{{/content}}
|
9
9
|
</div>
|
10
10
|
{{/content-outer}}
|
@@ -1,8 +1,8 @@
|
|
1
1
|
<div class="govuk-header__content">
|
2
2
|
{{#startPageRedirectUrl}}
|
3
|
-
<a href="{{startPageRedirectUrl}}" class="govuk-header__link govuk-header__link--service-name" id="proposition-name">{{$journeyHeader}}{{/journeyHeader}}</a>
|
3
|
+
<a href="{{startPageRedirectUrl}}" class="govuk-header__link govuk-header__link--service-name" id="proposition-name">{{$journeyHeader}}{{/journeyHeader}}</a>
|
4
4
|
{{/startPageRedirectUrl}}
|
5
5
|
{{^startPageRedirectUrl}}
|
6
|
-
<a href="{{
|
6
|
+
<a href="{{journeyHeaderURL}}" class="govuk-header__link govuk-header__link--service-name" id="proposition-name">{{$journeyHeader}}{{/journeyHeader}}</a>
|
7
7
|
{{/startPageRedirectUrl}}
|
8
8
|
</div>
|
@@ -5,10 +5,10 @@
|
|
5
5
|
<dt class="confirm-label">{{label}}</dt>
|
6
6
|
<dd class="confirm-value" data-value="{{value}}">{{value}}</dd>
|
7
7
|
{{#changeLink}}
|
8
|
-
<dd><span class="link"
|
8
|
+
<dd><span><a class="govuk-link" href="{{changeLink}}" id="{{field}}-change-{{index}}">{{#t}}buttons.change{{/t}} <span class="visuallyhidden">{{changeLinkDescription}} from {{value}}</span></a></span></dd>
|
9
9
|
{{/changeLink}}
|
10
10
|
{{^changeLink}}
|
11
|
-
<dd><span class="link"
|
11
|
+
<dd><span><a class="govuk-link" href="{{baseUrl}}{{step}}/edit#{{field}}" id="{{field}}-change">{{#t}}buttons.change{{/t}} <span class="visuallyhidden">{{changeLinkDescription}} from {{value}}</span></a></span></dd>
|
12
12
|
{{/changeLink}}
|
13
13
|
{{/isSeparator}}
|
14
14
|
</div>
|
@@ -1,6 +1,7 @@
|
|
1
1
|
{{<error}}
|
2
2
|
{{$content}}
|
3
|
-
<
|
3
|
+
<h1 class="govuk-heading-l">{{{content.title}}}</h1>
|
4
|
+
<h2 class="govuk-heading-m">{{{content.message}}}</h2>
|
4
5
|
<a href="{{startLink}}" class="govuk-button" role="button">{{#t}}buttons.start-again{{/t}}</a>
|
5
6
|
{{/content}}
|
6
7
|
{{/error}}
|
@@ -12,6 +12,10 @@ $path: "/public/images/" !default;
|
|
12
12
|
// https://github.com/alphagov/govuk_elements/blob/master/packages/govuk-elements-sass/public/sass/_elements.scss
|
13
13
|
@import "govuk-elements-sass/public/sass/elements";
|
14
14
|
|
15
|
+
// Govuk frontend
|
16
|
+
// https://github.com/alphagov/govuk-frontend-docs
|
17
|
+
@import "govuk-frontend";
|
18
|
+
|
15
19
|
// Custom
|
16
20
|
@import "base";
|
17
21
|
@import "layout";
|
@@ -25,7 +25,7 @@
|
|
25
25
|
.govuk-form-group--error {
|
26
26
|
box-sizing: border-box;
|
27
27
|
padding-left: $gutter-half - $validation-bdr-size;
|
28
|
-
border-left: $validation-bdr-size-lg solid $error-colour;
|
28
|
+
//border-left: $validation-bdr-size-lg solid $error-colour;
|
29
29
|
|
30
30
|
&:focus {
|
31
31
|
outline: $focus-outline;
|
@@ -42,7 +42,7 @@
|
|
42
42
|
margin-bottom: 0.5em;
|
43
43
|
}
|
44
44
|
@include bold-19;
|
45
|
-
color: $error-colour;
|
45
|
+
//color: $error-colour;
|
46
46
|
}
|
47
47
|
|
48
48
|
.invalid-input,
|
@@ -71,11 +71,20 @@ function formFocus() {
|
|
71
71
|
var labels;
|
72
72
|
var summaries;
|
73
73
|
|
74
|
-
|
74
|
+
var editMode = getElementFromSummaryLink && getEditPath === 'edit';
|
75
|
+
|
76
|
+
if (getElementFromSummaryLink && document.getElementById(getElementFromSummaryLink) && editMode) {
|
75
77
|
document.getElementById(getElementFromSummaryLink).focus();
|
78
|
+
}
|
79
|
+
|
80
|
+
if (getElementFromSummaryLink && document.getElementById(getElementFromSummaryLink + '-group') && editMode) {
|
76
81
|
document.getElementById(getElementFromSummaryLink + '-group').scrollIntoView();
|
77
82
|
}
|
78
83
|
|
84
|
+
if (document.getElementById(getElementFromSummaryLink + '-day') && forms.length === 1 && editMode) {
|
85
|
+
document.getElementById(getElementFromSummaryLink + '-day').focus();
|
86
|
+
}
|
87
|
+
|
79
88
|
if (forms.length > 0) {
|
80
89
|
labels = document.getElementsByTagName('label');
|
81
90
|
if (labels) {
|
@@ -7,12 +7,14 @@ var groupBy = require('lodash').groupBy;
|
|
7
7
|
var helpers = require('./helpers');
|
8
8
|
var inputs; var groups;
|
9
9
|
var toggleAttr = 'data-toggle';
|
10
|
-
var
|
10
|
+
var checkboxHiddenClass = 'govuk-checkboxes__conditional--hidden';
|
11
|
+
var radioHiddenClass = 'govuk-radios__conditional--hidden';
|
11
12
|
|
12
13
|
function inputClicked(e, target) {
|
13
14
|
target = target || helpers.target(e);
|
14
15
|
var shown;
|
15
16
|
each(groups[target.name], function (input) {
|
17
|
+
var hiddenClass = (input.type.match(/checkbox/)) ? checkboxHiddenClass : radioHiddenClass;
|
16
18
|
var id = input.getAttribute('aria-controls');
|
17
19
|
var toggle = document.getElementById(id);
|
18
20
|
if (toggle) {
|
@@ -24,7 +24,12 @@ function clicked(e) {
|
|
24
24
|
}
|
25
25
|
|
26
26
|
if (inputs) {
|
27
|
-
inputs[0].
|
27
|
+
if (inputs[0].getAttribute('type') === 'hidden') {
|
28
|
+
var getVisibleElements = group.querySelectorAll('input[type=text]');
|
29
|
+
getVisibleElements[0].focus();
|
30
|
+
} else {
|
31
|
+
inputs[0].focus();
|
32
|
+
}
|
28
33
|
}
|
29
34
|
}
|
30
35
|
}
|
package/index.js
CHANGED
@@ -79,7 +79,7 @@ const getContentSecurityPolicy = (config, res) => {
|
|
79
79
|
fontSrc: ['fonts.gstatic.com '],
|
80
80
|
scriptSrc: ['www.google-analytics.com', 'ssl.google-analytics.com'],
|
81
81
|
imgSrc: ['www.google-analytics.com', 'ssl.gstatic.com'],
|
82
|
-
connectSrc: ['www.google-analytics.com']
|
82
|
+
connectSrc: ['https://www.google-analytics.com', 'https://region1.google-analytics.com']
|
83
83
|
};
|
84
84
|
|
85
85
|
if (config.gaTagId) {
|
package/middleware/errors.js
CHANGED
@@ -12,6 +12,8 @@ const getContent = (err, translate) => {
|
|
12
12
|
if (err.code === 'SESSION_TIMEOUT') {
|
13
13
|
err.status = 401;
|
14
14
|
err.template = 'session-timeout';
|
15
|
+
err.title = (translate && translate('errors.session.title'));
|
16
|
+
err.message = (translate && translate('errors.session.message'));
|
15
17
|
content.title = (translate && translate('errors.session.title'));
|
16
18
|
content.message = (translate && translate('errors.session.message'));
|
17
19
|
}
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "hof",
|
3
3
|
"description": "A bootstrap for HOF projects",
|
4
|
-
"version": "20.0.0
|
4
|
+
"version": "20.0.0",
|
5
5
|
"license": "MIT",
|
6
6
|
"main": "index.js",
|
7
7
|
"author": "HomeOffice",
|
@@ -72,13 +72,14 @@
|
|
72
72
|
"minimatch": "^3.0.3",
|
73
73
|
"minimist": "^1.2.6",
|
74
74
|
"mixwith": "^0.1.1",
|
75
|
-
"moment": "^2.29.
|
75
|
+
"moment": "^2.29.4",
|
76
76
|
"morgan": "^1.10.0",
|
77
77
|
"mustache": "^2.3.0",
|
78
78
|
"nodemailer": "^6.6.3",
|
79
79
|
"nodemailer-ses-transport": "^1.5.0",
|
80
80
|
"nodemailer-smtp-transport": "^2.7.4",
|
81
81
|
"nodemailer-stub-transport": "^1.1.0",
|
82
|
+
"notifications-node-client": "^5.1.1",
|
82
83
|
"redis": "^3.1.2",
|
83
84
|
"reqres": "^3.0.1",
|
84
85
|
"request": "^2.79.0",
|
@@ -88,6 +89,7 @@
|
|
88
89
|
"uglify-js": "^3.14.3",
|
89
90
|
"underscore": "^1.12.1",
|
90
91
|
"urijs": "^1.19.11",
|
92
|
+
"uuid": "^8.3.2",
|
91
93
|
"winston": "^3.7.2"
|
92
94
|
},
|
93
95
|
"devDependencies": {
|