hof 20.0.0-beta.9 → 20.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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": {
|