dss_tech_docs 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +29 -0
- data/.gitignore +37 -0
- data/.rspec +2 -0
- data/.rubocop.yml +11 -0
- data/.ruby-version +1 -0
- data/.travis.yml +9 -0
- data/CHANGELOG.md +274 -0
- data/CONTRIBUTING.md +9 -0
- data/Gemfile +4 -0
- data/LICENCE +21 -0
- data/README.md +89 -0
- data/Rakefile +13 -0
- data/docs/configuration.md +205 -0
- data/docs/core-layout-without-sidebar.png +0 -0
- data/docs/core-layout.png +0 -0
- data/docs/expired-page.png +0 -0
- data/docs/frontmatter.md +145 -0
- data/docs/last-reviewed-only.png +0 -0
- data/docs/last-reviewed-only.svg +1 -0
- data/docs/layout-layout.png +0 -0
- data/docs/not-expired-page.png +0 -0
- data/docs/page-expiry.md +85 -0
- data/dss_tech_docs.gemspec +46 -0
- data/example/.ruby-version +1 -0
- data/example/Gemfile +3 -0
- data/example/config.rb +9 -0
- data/example/config/hide-expiry.yml +51 -0
- data/example/config/tech-docs.yml +50 -0
- data/example/source/api-path.html.md +7 -0
- data/example/source/api-reference.html.md +5 -0
- data/example/source/child-of-expired-page.html.md +8 -0
- data/example/source/core-layout-without-sidebar.html.md.erb +7 -0
- data/example/source/core-layout.html.md.erb +12 -0
- data/example/source/expired-page-with-owner.html.md +10 -0
- data/example/source/expired-page.html.md +9 -0
- data/example/source/headings.html.md +11 -0
- data/example/source/index.html.md.erb +19 -0
- data/example/source/javascripts/application.js +1 -0
- data/example/source/not-expired-page.html.md +9 -0
- data/example/source/pets.yml +109 -0
- data/example/source/stylesheets/print.css.scss +3 -0
- data/example/source/stylesheets/screen-old-ie.css.scss +4 -0
- data/example/source/stylesheets/screen.css.scss +1 -0
- data/example/source/templates/proxy_template.html.md +8 -0
- data/lib/assets/javascripts/_analytics.js +58 -0
- data/lib/assets/javascripts/_govuk/modules.js +57 -0
- data/lib/assets/javascripts/_modules/anchored-headings.js +18 -0
- data/lib/assets/javascripts/_modules/collapsible-navigation.js +95 -0
- data/lib/assets/javascripts/_modules/in-page-navigation.js +132 -0
- data/lib/assets/javascripts/_modules/navigation.js +34 -0
- data/lib/assets/javascripts/_modules/page-expiry.js +15 -0
- data/lib/assets/javascripts/_modules/search.js +367 -0
- data/lib/assets/javascripts/_modules/table-of-contents.js +111 -0
- data/lib/assets/javascripts/_start-modules.js +13 -0
- data/lib/assets/javascripts/_vendor/fixedsticky.js +194 -0
- data/lib/assets/javascripts/_vendor/jquery.js +5 -0
- data/lib/assets/javascripts/_vendor/jquery.mark.js +1081 -0
- data/lib/assets/javascripts/_vendor/lodash.js +613 -0
- data/lib/assets/javascripts/_vendor/modernizr.js +3 -0
- data/lib/assets/javascripts/govuk_tech_docs.js +10 -0
- data/lib/assets/stylesheets/_accessibility.scss +9 -0
- data/lib/assets/stylesheets/_core.scss +71 -0
- data/lib/assets/stylesheets/_fonts.scss +29 -0
- data/lib/assets/stylesheets/_govuk_tech_docs.scss +2 -0
- data/lib/assets/stylesheets/_syntax-highlighting.scss +196 -0
- data/lib/assets/stylesheets/_variables.scss +12 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/_colours.scss +2 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/_conditionals.scss +81 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/_css3.scss +90 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/_device-pixels.scss +10 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/_font_stack.scss +19 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/_grid_layout.scss +136 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/_helpers.scss +16 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/_measurements.scss +14 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/_shims.scss +55 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/_typography.scss +249 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/_url-helpers.scss +16 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/colours/_organisation.scss +103 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/colours/_palette.scss +77 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/design-patterns/_alpha-beta.scss +66 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/design-patterns/_breadcrumbs.scss +53 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/design-patterns/_buttons.scss +141 -0
- data/lib/assets/stylesheets/govuk_frontend_toolkit/design-patterns/_media-player.scss +242 -0
- data/lib/assets/stylesheets/modules/_anchored-heading.scss +54 -0
- data/lib/assets/stylesheets/modules/_app-pane.scss +64 -0
- data/lib/assets/stylesheets/modules/_collapsible.scss +52 -0
- data/lib/assets/stylesheets/modules/_contribution-banner.scss +22 -0
- data/lib/assets/stylesheets/modules/_footer.scss +130 -0
- data/lib/assets/stylesheets/modules/_govuk-logo.scss +47 -0
- data/lib/assets/stylesheets/modules/_header.scss +290 -0
- data/lib/assets/stylesheets/modules/_page-review.scss +35 -0
- data/lib/assets/stylesheets/modules/_phase-banner.scss +22 -0
- data/lib/assets/stylesheets/modules/_search.scss +137 -0
- data/lib/assets/stylesheets/modules/_skip-link.scss +31 -0
- data/lib/assets/stylesheets/modules/_technical-documentation.scss +241 -0
- data/lib/assets/stylesheets/modules/_toc.scss +216 -0
- data/lib/assets/stylesheets/modules/_warning-text.scss +73 -0
- data/lib/assets/stylesheets/palette/_syntax-highlighting.scss +23 -0
- data/lib/assets/stylesheets/utilities/_fonts.scss +29 -0
- data/lib/assets/stylesheets/utilities/_printable.scss +13 -0
- data/lib/assets/stylesheets/vendor/_fixedsticky.scss +22 -0
- data/lib/dss_tech_docs.rb +121 -0
- data/lib/govuk_tech_docs/api_reference/api_reference_extension.rb +101 -0
- data/lib/govuk_tech_docs/api_reference/api_reference_renderer.rb +279 -0
- data/lib/govuk_tech_docs/api_reference/templates/api_reference_full.html.erb +19 -0
- data/lib/govuk_tech_docs/api_reference/templates/operation.html.erb +11 -0
- data/lib/govuk_tech_docs/api_reference/templates/parameters.html.erb +28 -0
- data/lib/govuk_tech_docs/api_reference/templates/path.html.erb +4 -0
- data/lib/govuk_tech_docs/api_reference/templates/responses.html.erb +33 -0
- data/lib/govuk_tech_docs/api_reference/templates/schema.html.erb +29 -0
- data/lib/govuk_tech_docs/contribution_banner.rb +62 -0
- data/lib/govuk_tech_docs/meta_tags.rb +67 -0
- data/lib/govuk_tech_docs/page_review.rb +52 -0
- data/lib/govuk_tech_docs/pages.rb +32 -0
- data/lib/govuk_tech_docs/redirects.rb +39 -0
- data/lib/govuk_tech_docs/table_of_contents/heading.rb +30 -0
- data/lib/govuk_tech_docs/table_of_contents/heading_tree.rb +27 -0
- data/lib/govuk_tech_docs/table_of_contents/heading_tree_builder.rb +41 -0
- data/lib/govuk_tech_docs/table_of_contents/heading_tree_renderer.rb +46 -0
- data/lib/govuk_tech_docs/table_of_contents/headings_builder.rb +39 -0
- data/lib/govuk_tech_docs/table_of_contents/helpers.rb +79 -0
- data/lib/govuk_tech_docs/tech_docs_html_renderer.rb +34 -0
- data/lib/govuk_tech_docs/unique_identifier_extension.rb +13 -0
- data/lib/govuk_tech_docs/unique_identifier_generator.rb +72 -0
- data/lib/govuk_tech_docs/version.rb +3 -0
- data/lib/govuk_tech_docs/warning_text_extension.rb +23 -0
- data/lib/source/api/pages.json.erb +1 -0
- data/lib/source/favicon.ico +0 -0
- data/lib/source/images/anchored-heading-icon-2x.png +0 -0
- data/lib/source/images/anchored-heading-icon.png +0 -0
- data/lib/source/images/gov.uk_logotype_crown-2x.png +0 -0
- data/lib/source/images/gov.uk_logotype_crown.png +0 -0
- data/lib/source/images/gov.uk_logotype_crown_invert_trans.png +0 -0
- data/lib/source/images/govuk-crest-2x.png +0 -0
- data/lib/source/images/govuk-crest.png +0 -0
- data/lib/source/images/govuk-icn-close.png +0 -0
- data/lib/source/images/govuk-icn-close@2x.png +0 -0
- data/lib/source/images/govuk-icn-numbered-list.png +0 -0
- data/lib/source/images/govuk-icn-numbered-list@2x.png +0 -0
- data/lib/source/images/open-government-licence.png +0 -0
- data/lib/source/images/open-government-licence_2x.png +0 -0
- data/lib/source/images/search-result-caret.svg +13 -0
- data/lib/source/layouts/_analytics.erb +15 -0
- data/lib/source/layouts/_footer.erb +10 -0
- data/lib/source/layouts/_header.erb +44 -0
- data/lib/source/layouts/_page_review.erb +22 -0
- data/lib/source/layouts/_search.erb +16 -0
- data/lib/source/layouts/core.erb +82 -0
- data/lib/source/layouts/layout.erb +18 -0
- metadata +474 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
(function($, root) {
|
2
|
+
"use strict";
|
3
|
+
root.GOVUK = root.GOVUK || {};
|
4
|
+
GOVUK.Modules = GOVUK.Modules || {};
|
5
|
+
|
6
|
+
GOVUK.modules = {
|
7
|
+
find: function(container) {
|
8
|
+
var modules,
|
9
|
+
moduleSelector = '[data-module]',
|
10
|
+
container = container || $('body');
|
11
|
+
|
12
|
+
modules = container.find(moduleSelector);
|
13
|
+
|
14
|
+
// Container could be a module too
|
15
|
+
if (container.is(moduleSelector)) {
|
16
|
+
modules = modules.add(container);
|
17
|
+
}
|
18
|
+
|
19
|
+
return modules;
|
20
|
+
},
|
21
|
+
|
22
|
+
start: function(container) {
|
23
|
+
var modules = this.find(container);
|
24
|
+
|
25
|
+
for (var i = 0, l = modules.length; i < l; i++) {
|
26
|
+
var module,
|
27
|
+
element = $(modules[i]),
|
28
|
+
type = camelCaseAndCapitalise(element.data('module')),
|
29
|
+
started = element.data('module-started');
|
30
|
+
|
31
|
+
|
32
|
+
if (typeof GOVUK.Modules[type] === "function" && !started) {
|
33
|
+
module = new GOVUK.Modules[type]();
|
34
|
+
module.start(element);
|
35
|
+
element.data('module-started', true);
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
// eg selectable-table to SelectableTable
|
40
|
+
function camelCaseAndCapitalise(string) {
|
41
|
+
return capitaliseFirstLetter(camelCase(string));
|
42
|
+
}
|
43
|
+
|
44
|
+
// http://stackoverflow.com/questions/6660977/convert-hyphens-to-camel-case-camelcase
|
45
|
+
function camelCase(string) {
|
46
|
+
return string.replace(/-([a-z])/g, function (g) {
|
47
|
+
return g[1].toUpperCase();
|
48
|
+
});
|
49
|
+
}
|
50
|
+
|
51
|
+
// http://stackoverflow.com/questions/1026069/capitalize-the-first-letter-of-string-in-javascript
|
52
|
+
function capitaliseFirstLetter(string) {
|
53
|
+
return string.charAt(0).toUpperCase() + string.slice(1);
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
})(jQuery, window);
|
@@ -0,0 +1,18 @@
|
|
1
|
+
(function($, Modules) {
|
2
|
+
'use strict';
|
3
|
+
|
4
|
+
Modules.AnchoredHeadings = function() {
|
5
|
+
this.start = function($element) {
|
6
|
+
var headings = $element.find('h1, h2, h3, h4, h5, h6');
|
7
|
+
headings.each(injectAnchor);
|
8
|
+
};
|
9
|
+
|
10
|
+
function injectAnchor() {
|
11
|
+
var $this = $(this);
|
12
|
+
$this.addClass('anchored-heading');
|
13
|
+
$this.prepend(
|
14
|
+
'<a href="#' + $this.attr('id') + '" class="anchored-heading__icon" aria-hidden="true"></a>'
|
15
|
+
);
|
16
|
+
};
|
17
|
+
};
|
18
|
+
})(jQuery, window.GOVUK.Modules);
|
@@ -0,0 +1,95 @@
|
|
1
|
+
(function($, Modules) {
|
2
|
+
'use strict';
|
3
|
+
|
4
|
+
Modules.CollapsibleNavigation = function () {
|
5
|
+
|
6
|
+
var $contentPane;
|
7
|
+
var $nav;
|
8
|
+
var $topLevelItems;
|
9
|
+
var $headings;
|
10
|
+
var $listings;
|
11
|
+
|
12
|
+
var $openLink;
|
13
|
+
var $closeLink;
|
14
|
+
|
15
|
+
this.start = function ($element) {
|
16
|
+
$contentPane = $('.app-pane__content');
|
17
|
+
$nav = $element;
|
18
|
+
$topLevelItems = $nav.find('> ul > li');
|
19
|
+
$headings = $topLevelItems.find('> a');
|
20
|
+
$listings = $topLevelItems.find('> ul');
|
21
|
+
|
22
|
+
// Attach collapsible heading functionality,on mobile and desktop
|
23
|
+
collapsibleHeadings();
|
24
|
+
openActiveHeading();
|
25
|
+
$contentPane.on('scroll', _.debounce(openActiveHeading, 100, { maxWait: 100 }));
|
26
|
+
|
27
|
+
};
|
28
|
+
|
29
|
+
function collapsibleHeadings() {
|
30
|
+
for (var i = $topLevelItems.length - 1; i >= 0; i--) {
|
31
|
+
var $topLevelItem = $($topLevelItems[i]);
|
32
|
+
var $heading = $topLevelItem.find('> a');
|
33
|
+
var $listing = $topLevelItem.find('> ul');
|
34
|
+
var id = 'toc-' + $heading.text().toLowerCase().replace(' ', '-');
|
35
|
+
// Only add collapsible functionality if there are children.
|
36
|
+
if ($listing.length == 0) {
|
37
|
+
continue;
|
38
|
+
}
|
39
|
+
$topLevelItem.addClass('collapsible');
|
40
|
+
$listing.addClass('collapsible__body')
|
41
|
+
.attr('id', id)
|
42
|
+
.attr('aria-expanded', 'false');
|
43
|
+
$heading.addClass('collapsible__heading')
|
44
|
+
.after('<button class="collapsible__toggle" aria-controls="' + id +'"><span class="collapsible__toggle-label">Expand ' + $heading.text() + '</span><span class="collapsible__toggle-icon" aria-hidden="true"></button>')
|
45
|
+
$topLevelItem.on('click', '.collapsible__toggle', function(e) {
|
46
|
+
e.preventDefault();
|
47
|
+
var $parent = $(this).parent();
|
48
|
+
toggleHeading($parent);
|
49
|
+
});
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
function toggleHeading($topLevelItem) {
|
54
|
+
var isOpen = $topLevelItem.hasClass('is-open');
|
55
|
+
var $heading = $topLevelItem.find('> a');
|
56
|
+
var $body = $topLevelItem.find('.collapsible__body');
|
57
|
+
var $toggleLabel = $topLevelItem.find('.collapsible__toggle-label');
|
58
|
+
|
59
|
+
$topLevelItem.toggleClass('is-open', !isOpen);
|
60
|
+
$body.attr('aria-expanded', isOpen ? 'false' : 'true');
|
61
|
+
$toggleLabel.text(isOpen ? 'Expand ' + $heading.text() : 'Collapse ' + $heading.text());
|
62
|
+
}
|
63
|
+
|
64
|
+
function openActiveHeading() {
|
65
|
+
var $activeElement;
|
66
|
+
var currentPath = window.location.pathname;
|
67
|
+
var isActiveTrail = '[href*="' + currentPath + '"]';
|
68
|
+
// Add an exception for the root page, as every href includes /
|
69
|
+
if(currentPath == '/') {
|
70
|
+
isActiveTrail = '[href="' + currentPath + window.location.hash + '"]'
|
71
|
+
}
|
72
|
+
for (var i = $topLevelItems.length - 1; i >= 0; i--) {
|
73
|
+
var $element = $($topLevelItems[i]);
|
74
|
+
var $heading = $element.find('> a');
|
75
|
+
// Check if this item href matches
|
76
|
+
if($heading.is(isActiveTrail)) {
|
77
|
+
$activeElement = $element;
|
78
|
+
break;
|
79
|
+
}
|
80
|
+
// Otherwise check the children
|
81
|
+
var $children = $element.find('li > a');
|
82
|
+
var $matchingChildren = $children.filter(isActiveTrail);
|
83
|
+
if ($matchingChildren.length) {
|
84
|
+
$activeElement = $element;
|
85
|
+
break;
|
86
|
+
}
|
87
|
+
}
|
88
|
+
if($activeElement && !$activeElement.hasClass('is-open')) {
|
89
|
+
toggleHeading($activeElement);
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
|
94
|
+
};
|
95
|
+
})(jQuery, window.GOVUK.Modules);
|
@@ -0,0 +1,132 @@
|
|
1
|
+
(function($, Modules) {
|
2
|
+
'use strict';
|
3
|
+
|
4
|
+
Modules.InPageNavigation = function InPageNavigation() {
|
5
|
+
var $tocPane;
|
6
|
+
var $contentPane;
|
7
|
+
var $tocItems;
|
8
|
+
var $targets;
|
9
|
+
|
10
|
+
this.start = function start($element) {
|
11
|
+
$tocPane = $element.find('.app-pane__toc');
|
12
|
+
$contentPane = $element.find('.app-pane__content');
|
13
|
+
$tocItems = $('.js-toc-list').find('a');
|
14
|
+
$targets = $contentPane.find('[id]');
|
15
|
+
|
16
|
+
$contentPane.on('scroll', _.debounce(handleScrollEvent, 100, { maxWait: 100 }));
|
17
|
+
|
18
|
+
if (Modernizr.history) {
|
19
|
+
// Popstate is triggered when using the back button to navigate 'within'
|
20
|
+
// the page, i.e. changing the anchor part of the URL.
|
21
|
+
$(window).on('popstate', function (event) {
|
22
|
+
restoreScrollPosition(event.originalEvent.state);
|
23
|
+
});
|
24
|
+
|
25
|
+
if (history.state && history.state.scrollTop) {
|
26
|
+
// Restore existing state when e.g. using the back button to return to
|
27
|
+
// this page
|
28
|
+
restoreScrollPosition(history.state);
|
29
|
+
} else {
|
30
|
+
// Store the initial position so that we can restore it even if we
|
31
|
+
// never scroll.
|
32
|
+
handleInitialLoadEvent();
|
33
|
+
}
|
34
|
+
}
|
35
|
+
};
|
36
|
+
|
37
|
+
function restoreScrollPosition(state) {
|
38
|
+
if (state && typeof state.scrollTop !== 'undefined') {
|
39
|
+
$contentPane.scrollTop(state.scrollTop);
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
function handleInitialLoadEvent() {
|
44
|
+
var fragment = fragmentForTargetElement();
|
45
|
+
|
46
|
+
if (!fragment) {
|
47
|
+
fragment = fragmentForFirstElementInView();
|
48
|
+
}
|
49
|
+
|
50
|
+
handleChangeInActiveItem(fragment);
|
51
|
+
}
|
52
|
+
|
53
|
+
function handleScrollEvent() {
|
54
|
+
handleChangeInActiveItem(fragmentForFirstElementInView());
|
55
|
+
}
|
56
|
+
|
57
|
+
function handleChangeInActiveItem(fragment) {
|
58
|
+
storeCurrentPositionInHistoryApi(fragment);
|
59
|
+
highlightActiveItemInToc(fragment);
|
60
|
+
}
|
61
|
+
|
62
|
+
function storeCurrentPositionInHistoryApi(fragment) {
|
63
|
+
if (Modernizr.history && fragment) {
|
64
|
+
history.replaceState(
|
65
|
+
{ scrollTop: $contentPane.scrollTop() },
|
66
|
+
"",
|
67
|
+
fragment
|
68
|
+
);
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
function highlightActiveItemInToc(fragment) {
|
73
|
+
// Navigation items for single page navigation don't necessarily include the path name, but
|
74
|
+
// navigation items for multipage navigation items do include it. This checks for either case.
|
75
|
+
var $activeTocItem = $tocItems.filter(
|
76
|
+
'[href="' + window.location.pathname + fragment + '"],[href="' + fragment + '"]'
|
77
|
+
);
|
78
|
+
// Navigation items with children don't contain fragments in their url
|
79
|
+
// Check to see if any nav items contain just the path name.
|
80
|
+
if(!$activeTocItem.get(0)) {
|
81
|
+
$activeTocItem = $tocItems.filter('[href="' + window.location.pathname + '"]');
|
82
|
+
}
|
83
|
+
if ($activeTocItem.get(0)) {
|
84
|
+
$tocItems.removeClass('toc-link--in-view');
|
85
|
+
$activeTocItem.addClass('toc-link--in-view');
|
86
|
+
scrollTocToActiveItem($activeTocItem);
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
function scrollTocToActiveItem($activeTocItem) {
|
91
|
+
var paneHeight = $tocPane.height();
|
92
|
+
var linkTop = $activeTocItem.position().top;
|
93
|
+
var linkBottom = linkTop + $activeTocItem.outerHeight();
|
94
|
+
|
95
|
+
var offset = null;
|
96
|
+
|
97
|
+
if (linkTop < 0) {
|
98
|
+
offset = linkTop;
|
99
|
+
} else if (linkBottom >= paneHeight) {
|
100
|
+
offset = -(paneHeight - linkBottom);
|
101
|
+
} else {
|
102
|
+
return;
|
103
|
+
}
|
104
|
+
|
105
|
+
var newScrollTop = $tocPane.scrollTop() + offset;
|
106
|
+
|
107
|
+
$tocPane.scrollTop(newScrollTop);
|
108
|
+
}
|
109
|
+
|
110
|
+
function fragmentForTargetElement() {
|
111
|
+
return window.location.hash;
|
112
|
+
}
|
113
|
+
|
114
|
+
function fragmentForFirstElementInView() {
|
115
|
+
var result = null;
|
116
|
+
|
117
|
+
$($targets.get().reverse()).each(function checkIfInView(index) {
|
118
|
+
if (result) {
|
119
|
+
return;
|
120
|
+
}
|
121
|
+
|
122
|
+
var $this = $(this);
|
123
|
+
|
124
|
+
if (Math.floor($this.position().top) <= 0) {
|
125
|
+
result = $this;
|
126
|
+
}
|
127
|
+
});
|
128
|
+
|
129
|
+
return result ? '#' + result.attr('id') : false;
|
130
|
+
}
|
131
|
+
};
|
132
|
+
})(jQuery, window.GOVUK.Modules);
|
@@ -0,0 +1,34 @@
|
|
1
|
+
(function($, Modules) {
|
2
|
+
'use strict';
|
3
|
+
|
4
|
+
Modules.Navigation = function () {
|
5
|
+
var $html = $('html');
|
6
|
+
|
7
|
+
var $navToggle;
|
8
|
+
var $nav;
|
9
|
+
|
10
|
+
this.start = function ($element) {
|
11
|
+
$navToggle = $('.js-nav-toggle', $element);
|
12
|
+
$nav = $('.js-nav', $element);
|
13
|
+
|
14
|
+
updateAriaAttributes();
|
15
|
+
|
16
|
+
$navToggle.on('click', toggleNavigation);
|
17
|
+
$(window).on('resize', updateAriaAttributes);
|
18
|
+
}
|
19
|
+
|
20
|
+
function updateAriaAttributes() {
|
21
|
+
var navIsVisible = $nav.is(':visible');
|
22
|
+
|
23
|
+
$navToggle.attr('aria-expanded', navIsVisible ? 'true' : 'false');
|
24
|
+
$nav.attr('aria-hidden', navIsVisible ? 'false' : 'true');
|
25
|
+
}
|
26
|
+
|
27
|
+
function toggleNavigation() {
|
28
|
+
var navIsVisible = !$html.hasClass('nav-open');
|
29
|
+
|
30
|
+
$html.toggleClass('nav-open', navIsVisible);
|
31
|
+
updateAriaAttributes();
|
32
|
+
}
|
33
|
+
};
|
34
|
+
})(jQuery, window.GOVUK.Modules);
|
@@ -0,0 +1,15 @@
|
|
1
|
+
(function($, Modules) {
|
2
|
+
'use strict';
|
3
|
+
|
4
|
+
Modules.PageExpiry = function PageExpiry() {
|
5
|
+
this.start = function start($element) {
|
6
|
+
var rawDate = $element.data('last-reviewed-on');
|
7
|
+
var isExpired = Date.parse(rawDate) < new Date();
|
8
|
+
|
9
|
+
if (isExpired) {
|
10
|
+
$element.find('.page-expiry--not-expired').hide(0);
|
11
|
+
$element.find('.page-expiry--expired').show(0);
|
12
|
+
}
|
13
|
+
};
|
14
|
+
};
|
15
|
+
})(jQuery, window.GOVUK.Modules);
|
@@ -0,0 +1,367 @@
|
|
1
|
+
//= require lunr.min
|
2
|
+
//= require _vendor/jquery.mark.js
|
3
|
+
(function($, Modules) {
|
4
|
+
'use strict';
|
5
|
+
|
6
|
+
Modules.Search = function Search() {
|
7
|
+
var s = this;
|
8
|
+
var $html = $('html');
|
9
|
+
s.lunrIndex;
|
10
|
+
s.lunrData;
|
11
|
+
var $searchForm;
|
12
|
+
var $searchLabel;
|
13
|
+
var $searchInput;
|
14
|
+
var $searchResults;
|
15
|
+
var $searchResultsTitle;
|
16
|
+
var $searchResultsWrapper;
|
17
|
+
var $searchResultsClose;
|
18
|
+
var results;
|
19
|
+
var query;
|
20
|
+
var queryTimer;
|
21
|
+
var maxSearchEntries = 20;
|
22
|
+
|
23
|
+
this.start = function start($element) {
|
24
|
+
$searchForm = $element.find('form');
|
25
|
+
$searchInput = $element.find('#search');
|
26
|
+
$searchLabel = $element.find('.search__label');
|
27
|
+
$searchResultsWrapper = $element.find('.search-results')
|
28
|
+
$searchResults = $searchResultsWrapper.find('.search-results__content');
|
29
|
+
$searchResultsTitle = $searchResultsWrapper.find('.search-results__title');
|
30
|
+
$searchResultsClose = $searchResultsWrapper.find('.search-results__close');
|
31
|
+
s.downloadSearchIndex();
|
32
|
+
attach();
|
33
|
+
changeSearchLabel();
|
34
|
+
};
|
35
|
+
|
36
|
+
this.downloadSearchIndex = function downloadSearchIndex() {
|
37
|
+
updateTitle('Loading search index')
|
38
|
+
$.ajax({
|
39
|
+
url: '/search.json',
|
40
|
+
cache: true,
|
41
|
+
method: 'GET',
|
42
|
+
success: function(data) {
|
43
|
+
s.lunrData = data;
|
44
|
+
s.lunrIndex = lunr.Index.load(s.lunrData.index);
|
45
|
+
replaceStopWordFilter();
|
46
|
+
$(document).trigger('lunrIndexLoaded');
|
47
|
+
}
|
48
|
+
});
|
49
|
+
}
|
50
|
+
|
51
|
+
function attach() {
|
52
|
+
// Search functionality on search text input
|
53
|
+
$searchInput.on('input', function (e) {
|
54
|
+
e.preventDefault();
|
55
|
+
query = $(this).val();
|
56
|
+
s.search(query, function(r) {
|
57
|
+
results = r;
|
58
|
+
renderResults(query);
|
59
|
+
updateTitle();
|
60
|
+
});
|
61
|
+
if(window.ga) {
|
62
|
+
window.clearTimeout(queryTimer);
|
63
|
+
queryTimer = window.setTimeout(sendQueryToAnalytics, 1000);
|
64
|
+
}
|
65
|
+
});
|
66
|
+
|
67
|
+
// Set focus on the first search result instead of submiting the search
|
68
|
+
// form to Google
|
69
|
+
$searchForm.on('submit', function(e) {
|
70
|
+
e.preventDefault();
|
71
|
+
showResults();
|
72
|
+
$searchResults.find('.search-result__title a').first().focus();
|
73
|
+
});
|
74
|
+
|
75
|
+
// Closing the search results, move focus back to the search input
|
76
|
+
$searchResultsClose.on('click', function(e) {
|
77
|
+
e.preventDefault();
|
78
|
+
$searchInput.focus();
|
79
|
+
hideResults();
|
80
|
+
});
|
81
|
+
|
82
|
+
// Attach analytics events to search result clicks
|
83
|
+
if(window.ga) {
|
84
|
+
$searchResults.on('click', '.search-result__title a', function() {
|
85
|
+
var href = $(this).attr('href');
|
86
|
+
ga('send', {
|
87
|
+
hitType: 'event',
|
88
|
+
eventCategory: 'Search result',
|
89
|
+
eventAction: 'click',
|
90
|
+
eventLabel: href,
|
91
|
+
transport: 'beacon'
|
92
|
+
});
|
93
|
+
});
|
94
|
+
}
|
95
|
+
|
96
|
+
// When selecting navigation link, close the search results.
|
97
|
+
$('.toc').on('click','a', function(e) {
|
98
|
+
hideResults();
|
99
|
+
})
|
100
|
+
}
|
101
|
+
|
102
|
+
function changeSearchLabel() {
|
103
|
+
$searchLabel.text('Search');
|
104
|
+
}
|
105
|
+
|
106
|
+
function getResults(query) {
|
107
|
+
var results = [];
|
108
|
+
s.lunrIndex.search(query).forEach( function (item, index) {
|
109
|
+
if ( index < maxSearchEntries ) {
|
110
|
+
results.push(s.lunrData.docs[item.ref]);
|
111
|
+
}
|
112
|
+
});
|
113
|
+
return results;
|
114
|
+
}
|
115
|
+
|
116
|
+
this.search = function search(query, callback) {
|
117
|
+
if(query === '') {
|
118
|
+
hideResults();
|
119
|
+
return;
|
120
|
+
}
|
121
|
+
showResults();
|
122
|
+
// The index has not been downloaded yet, exit early and wait.
|
123
|
+
if(!s.lunrIndex) {
|
124
|
+
$(document).on('lunrIndexLoaded', function() {
|
125
|
+
s.search(query, callback);
|
126
|
+
});
|
127
|
+
return;
|
128
|
+
}
|
129
|
+
callback(getResults(query));
|
130
|
+
}
|
131
|
+
|
132
|
+
function renderResults(query) {
|
133
|
+
var output = '';
|
134
|
+
if (results.length == 0) {
|
135
|
+
output += '<p>Nothing found</p>';
|
136
|
+
}
|
137
|
+
output += '<ul>';
|
138
|
+
for(var index in results) {
|
139
|
+
var result = results[index];
|
140
|
+
var content = s.processContent(result.content, query);
|
141
|
+
output += '<li class="search-result">';
|
142
|
+
output += '<h3 class="search-result__title">';
|
143
|
+
output += '<a href="' + result.url + '">';
|
144
|
+
output += result.title;
|
145
|
+
output += '</a>';
|
146
|
+
output += '</h3>';
|
147
|
+
if(typeof content !== 'undefined') {
|
148
|
+
output += '<p>' + content + '</p>';
|
149
|
+
}
|
150
|
+
output += '</li>';
|
151
|
+
}
|
152
|
+
output += '</ul>';
|
153
|
+
|
154
|
+
$searchResults.html( output );
|
155
|
+
}
|
156
|
+
|
157
|
+
this.processContent = function processContent(content, query) {
|
158
|
+
var output;
|
159
|
+
content = '<div>'+ content + '</div>';
|
160
|
+
content = $(content).mark(query);
|
161
|
+
|
162
|
+
// Split content by sentence.
|
163
|
+
var sentences = content.html().replace(/(\.+|\:|\!|\?|\r|\n)(\"*|\'*|\)*|}*|]*)/gm, "|").split("|");
|
164
|
+
|
165
|
+
// Select the first five sentences that contain a <mark>
|
166
|
+
var selectedSentences = [];
|
167
|
+
for (var i = 0; i < sentences.length; i++) {
|
168
|
+
if(selectedSentences.length === 5) {
|
169
|
+
break;
|
170
|
+
}
|
171
|
+
|
172
|
+
var sentence = sentences[i].trim();
|
173
|
+
var containsMark = sentence.includes('mark>');
|
174
|
+
if (containsMark && (selectedSentences.indexOf(sentence) == -1)) {
|
175
|
+
selectedSentences.push(sentence);
|
176
|
+
}
|
177
|
+
}
|
178
|
+
if(selectedSentences.length > 0) {
|
179
|
+
output = ' … ' + selectedSentences.join(' … ') + ' … ';
|
180
|
+
}
|
181
|
+
return output;
|
182
|
+
}
|
183
|
+
|
184
|
+
// Default text is to display the number of search results
|
185
|
+
function updateTitle(text) {
|
186
|
+
if(typeof text == "undefined") {
|
187
|
+
var count = results.length;
|
188
|
+
var text = count + ' results';
|
189
|
+
}
|
190
|
+
$searchResultsTitle.text(text);
|
191
|
+
}
|
192
|
+
|
193
|
+
function showResults() {
|
194
|
+
$searchResultsWrapper.addClass('is-open')
|
195
|
+
.attr('aria-hidden', 'false');
|
196
|
+
$html.addClass('has-search-results-open');
|
197
|
+
}
|
198
|
+
|
199
|
+
function hideResults() {
|
200
|
+
$searchResultsWrapper.removeClass('is-open')
|
201
|
+
.attr('aria-hidden', 'true');
|
202
|
+
$html.removeClass('has-search-results-open');
|
203
|
+
}
|
204
|
+
|
205
|
+
function sendQueryToAnalytics() {
|
206
|
+
if(query === '') {
|
207
|
+
return;
|
208
|
+
}
|
209
|
+
var stripped = window.stripPIIFromString(query)
|
210
|
+
ga('send', {
|
211
|
+
hitType: 'event',
|
212
|
+
eventCategory: 'Search query',
|
213
|
+
eventAction: 'type',
|
214
|
+
eventLabel: stripped,
|
215
|
+
transport: 'beacon'
|
216
|
+
});
|
217
|
+
}
|
218
|
+
|
219
|
+
function replaceStopWordFilter() {
|
220
|
+
// Replace the default stopWordFilter as it excludes useful words like
|
221
|
+
// 'get'
|
222
|
+
// See: https://lunrjs.com/docs/stop_word_filter.js.html#line43
|
223
|
+
s.lunrIndex.pipeline.remove(lunr.stopWordFilter);
|
224
|
+
s.lunrIndex.pipeline.add(s.govukStopWorldFilter);
|
225
|
+
}
|
226
|
+
|
227
|
+
this.govukStopWorldFilter = lunr.generateStopWordFilter([
|
228
|
+
'a',
|
229
|
+
'able',
|
230
|
+
'about',
|
231
|
+
'across',
|
232
|
+
'after',
|
233
|
+
'all',
|
234
|
+
'almost',
|
235
|
+
'also',
|
236
|
+
'am',
|
237
|
+
'among',
|
238
|
+
'an',
|
239
|
+
'and',
|
240
|
+
'any',
|
241
|
+
'are',
|
242
|
+
'as',
|
243
|
+
'at',
|
244
|
+
'be',
|
245
|
+
'because',
|
246
|
+
'been',
|
247
|
+
'but',
|
248
|
+
'by',
|
249
|
+
'can',
|
250
|
+
'cannot',
|
251
|
+
'could',
|
252
|
+
'dear',
|
253
|
+
'did',
|
254
|
+
'do',
|
255
|
+
'does',
|
256
|
+
'either',
|
257
|
+
'else',
|
258
|
+
'ever',
|
259
|
+
'every',
|
260
|
+
'for',
|
261
|
+
'from',
|
262
|
+
'got',
|
263
|
+
'had',
|
264
|
+
'has',
|
265
|
+
'have',
|
266
|
+
'he',
|
267
|
+
'her',
|
268
|
+
'hers',
|
269
|
+
'him',
|
270
|
+
'his',
|
271
|
+
'how',
|
272
|
+
'however',
|
273
|
+
'i',
|
274
|
+
'if',
|
275
|
+
'in',
|
276
|
+
'into',
|
277
|
+
'is',
|
278
|
+
'it',
|
279
|
+
'its',
|
280
|
+
'just',
|
281
|
+
'least',
|
282
|
+
'let',
|
283
|
+
'like',
|
284
|
+
'likely',
|
285
|
+
'may',
|
286
|
+
'me',
|
287
|
+
'might',
|
288
|
+
'most',
|
289
|
+
'must',
|
290
|
+
'my',
|
291
|
+
'neither',
|
292
|
+
'no',
|
293
|
+
'nor',
|
294
|
+
'not',
|
295
|
+
'of',
|
296
|
+
'off',
|
297
|
+
'often',
|
298
|
+
'on',
|
299
|
+
'only',
|
300
|
+
'or',
|
301
|
+
'other',
|
302
|
+
'our',
|
303
|
+
'own',
|
304
|
+
'rather',
|
305
|
+
'said',
|
306
|
+
'say',
|
307
|
+
'says',
|
308
|
+
'she',
|
309
|
+
'should',
|
310
|
+
'since',
|
311
|
+
'so',
|
312
|
+
'some',
|
313
|
+
'than',
|
314
|
+
'that',
|
315
|
+
'the',
|
316
|
+
'their',
|
317
|
+
'them',
|
318
|
+
'then',
|
319
|
+
'there',
|
320
|
+
'these',
|
321
|
+
'they',
|
322
|
+
'this',
|
323
|
+
'tis',
|
324
|
+
'to',
|
325
|
+
'too',
|
326
|
+
'twas',
|
327
|
+
'us',
|
328
|
+
'wants',
|
329
|
+
'was',
|
330
|
+
'we',
|
331
|
+
'were',
|
332
|
+
'what',
|
333
|
+
'when',
|
334
|
+
'where',
|
335
|
+
'which',
|
336
|
+
'while',
|
337
|
+
'who',
|
338
|
+
'whom',
|
339
|
+
'why',
|
340
|
+
'will',
|
341
|
+
'with',
|
342
|
+
'would',
|
343
|
+
'yet',
|
344
|
+
'you',
|
345
|
+
'your'
|
346
|
+
])
|
347
|
+
};
|
348
|
+
|
349
|
+
// Polyfill includes
|
350
|
+
if (!String.prototype.includes) {
|
351
|
+
String.prototype.includes = function(search, start) {
|
352
|
+
'use strict';
|
353
|
+
if (typeof start !== 'number') {
|
354
|
+
start = 0;
|
355
|
+
}
|
356
|
+
|
357
|
+
if (start + search.length > this.length) {
|
358
|
+
return false;
|
359
|
+
} else {
|
360
|
+
return this.indexOf(search, start) !== -1;
|
361
|
+
}
|
362
|
+
};
|
363
|
+
}
|
364
|
+
})(jQuery, window.GOVUK.Modules);
|
365
|
+
|
366
|
+
|
367
|
+
|