dss_tech_docs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +29 -0
  3. data/.gitignore +37 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +11 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +9 -0
  8. data/CHANGELOG.md +274 -0
  9. data/CONTRIBUTING.md +9 -0
  10. data/Gemfile +4 -0
  11. data/LICENCE +21 -0
  12. data/README.md +89 -0
  13. data/Rakefile +13 -0
  14. data/docs/configuration.md +205 -0
  15. data/docs/core-layout-without-sidebar.png +0 -0
  16. data/docs/core-layout.png +0 -0
  17. data/docs/expired-page.png +0 -0
  18. data/docs/frontmatter.md +145 -0
  19. data/docs/last-reviewed-only.png +0 -0
  20. data/docs/last-reviewed-only.svg +1 -0
  21. data/docs/layout-layout.png +0 -0
  22. data/docs/not-expired-page.png +0 -0
  23. data/docs/page-expiry.md +85 -0
  24. data/dss_tech_docs.gemspec +46 -0
  25. data/example/.ruby-version +1 -0
  26. data/example/Gemfile +3 -0
  27. data/example/config.rb +9 -0
  28. data/example/config/hide-expiry.yml +51 -0
  29. data/example/config/tech-docs.yml +50 -0
  30. data/example/source/api-path.html.md +7 -0
  31. data/example/source/api-reference.html.md +5 -0
  32. data/example/source/child-of-expired-page.html.md +8 -0
  33. data/example/source/core-layout-without-sidebar.html.md.erb +7 -0
  34. data/example/source/core-layout.html.md.erb +12 -0
  35. data/example/source/expired-page-with-owner.html.md +10 -0
  36. data/example/source/expired-page.html.md +9 -0
  37. data/example/source/headings.html.md +11 -0
  38. data/example/source/index.html.md.erb +19 -0
  39. data/example/source/javascripts/application.js +1 -0
  40. data/example/source/not-expired-page.html.md +9 -0
  41. data/example/source/pets.yml +109 -0
  42. data/example/source/stylesheets/print.css.scss +3 -0
  43. data/example/source/stylesheets/screen-old-ie.css.scss +4 -0
  44. data/example/source/stylesheets/screen.css.scss +1 -0
  45. data/example/source/templates/proxy_template.html.md +8 -0
  46. data/lib/assets/javascripts/_analytics.js +58 -0
  47. data/lib/assets/javascripts/_govuk/modules.js +57 -0
  48. data/lib/assets/javascripts/_modules/anchored-headings.js +18 -0
  49. data/lib/assets/javascripts/_modules/collapsible-navigation.js +95 -0
  50. data/lib/assets/javascripts/_modules/in-page-navigation.js +132 -0
  51. data/lib/assets/javascripts/_modules/navigation.js +34 -0
  52. data/lib/assets/javascripts/_modules/page-expiry.js +15 -0
  53. data/lib/assets/javascripts/_modules/search.js +367 -0
  54. data/lib/assets/javascripts/_modules/table-of-contents.js +111 -0
  55. data/lib/assets/javascripts/_start-modules.js +13 -0
  56. data/lib/assets/javascripts/_vendor/fixedsticky.js +194 -0
  57. data/lib/assets/javascripts/_vendor/jquery.js +5 -0
  58. data/lib/assets/javascripts/_vendor/jquery.mark.js +1081 -0
  59. data/lib/assets/javascripts/_vendor/lodash.js +613 -0
  60. data/lib/assets/javascripts/_vendor/modernizr.js +3 -0
  61. data/lib/assets/javascripts/govuk_tech_docs.js +10 -0
  62. data/lib/assets/stylesheets/_accessibility.scss +9 -0
  63. data/lib/assets/stylesheets/_core.scss +71 -0
  64. data/lib/assets/stylesheets/_fonts.scss +29 -0
  65. data/lib/assets/stylesheets/_govuk_tech_docs.scss +2 -0
  66. data/lib/assets/stylesheets/_syntax-highlighting.scss +196 -0
  67. data/lib/assets/stylesheets/_variables.scss +12 -0
  68. data/lib/assets/stylesheets/govuk_frontend_toolkit/_colours.scss +2 -0
  69. data/lib/assets/stylesheets/govuk_frontend_toolkit/_conditionals.scss +81 -0
  70. data/lib/assets/stylesheets/govuk_frontend_toolkit/_css3.scss +90 -0
  71. data/lib/assets/stylesheets/govuk_frontend_toolkit/_device-pixels.scss +10 -0
  72. data/lib/assets/stylesheets/govuk_frontend_toolkit/_font_stack.scss +19 -0
  73. data/lib/assets/stylesheets/govuk_frontend_toolkit/_grid_layout.scss +136 -0
  74. data/lib/assets/stylesheets/govuk_frontend_toolkit/_helpers.scss +16 -0
  75. data/lib/assets/stylesheets/govuk_frontend_toolkit/_measurements.scss +14 -0
  76. data/lib/assets/stylesheets/govuk_frontend_toolkit/_shims.scss +55 -0
  77. data/lib/assets/stylesheets/govuk_frontend_toolkit/_typography.scss +249 -0
  78. data/lib/assets/stylesheets/govuk_frontend_toolkit/_url-helpers.scss +16 -0
  79. data/lib/assets/stylesheets/govuk_frontend_toolkit/colours/_organisation.scss +103 -0
  80. data/lib/assets/stylesheets/govuk_frontend_toolkit/colours/_palette.scss +77 -0
  81. data/lib/assets/stylesheets/govuk_frontend_toolkit/design-patterns/_alpha-beta.scss +66 -0
  82. data/lib/assets/stylesheets/govuk_frontend_toolkit/design-patterns/_breadcrumbs.scss +53 -0
  83. data/lib/assets/stylesheets/govuk_frontend_toolkit/design-patterns/_buttons.scss +141 -0
  84. data/lib/assets/stylesheets/govuk_frontend_toolkit/design-patterns/_media-player.scss +242 -0
  85. data/lib/assets/stylesheets/modules/_anchored-heading.scss +54 -0
  86. data/lib/assets/stylesheets/modules/_app-pane.scss +64 -0
  87. data/lib/assets/stylesheets/modules/_collapsible.scss +52 -0
  88. data/lib/assets/stylesheets/modules/_contribution-banner.scss +22 -0
  89. data/lib/assets/stylesheets/modules/_footer.scss +130 -0
  90. data/lib/assets/stylesheets/modules/_govuk-logo.scss +47 -0
  91. data/lib/assets/stylesheets/modules/_header.scss +290 -0
  92. data/lib/assets/stylesheets/modules/_page-review.scss +35 -0
  93. data/lib/assets/stylesheets/modules/_phase-banner.scss +22 -0
  94. data/lib/assets/stylesheets/modules/_search.scss +137 -0
  95. data/lib/assets/stylesheets/modules/_skip-link.scss +31 -0
  96. data/lib/assets/stylesheets/modules/_technical-documentation.scss +241 -0
  97. data/lib/assets/stylesheets/modules/_toc.scss +216 -0
  98. data/lib/assets/stylesheets/modules/_warning-text.scss +73 -0
  99. data/lib/assets/stylesheets/palette/_syntax-highlighting.scss +23 -0
  100. data/lib/assets/stylesheets/utilities/_fonts.scss +29 -0
  101. data/lib/assets/stylesheets/utilities/_printable.scss +13 -0
  102. data/lib/assets/stylesheets/vendor/_fixedsticky.scss +22 -0
  103. data/lib/dss_tech_docs.rb +121 -0
  104. data/lib/govuk_tech_docs/api_reference/api_reference_extension.rb +101 -0
  105. data/lib/govuk_tech_docs/api_reference/api_reference_renderer.rb +279 -0
  106. data/lib/govuk_tech_docs/api_reference/templates/api_reference_full.html.erb +19 -0
  107. data/lib/govuk_tech_docs/api_reference/templates/operation.html.erb +11 -0
  108. data/lib/govuk_tech_docs/api_reference/templates/parameters.html.erb +28 -0
  109. data/lib/govuk_tech_docs/api_reference/templates/path.html.erb +4 -0
  110. data/lib/govuk_tech_docs/api_reference/templates/responses.html.erb +33 -0
  111. data/lib/govuk_tech_docs/api_reference/templates/schema.html.erb +29 -0
  112. data/lib/govuk_tech_docs/contribution_banner.rb +62 -0
  113. data/lib/govuk_tech_docs/meta_tags.rb +67 -0
  114. data/lib/govuk_tech_docs/page_review.rb +52 -0
  115. data/lib/govuk_tech_docs/pages.rb +32 -0
  116. data/lib/govuk_tech_docs/redirects.rb +39 -0
  117. data/lib/govuk_tech_docs/table_of_contents/heading.rb +30 -0
  118. data/lib/govuk_tech_docs/table_of_contents/heading_tree.rb +27 -0
  119. data/lib/govuk_tech_docs/table_of_contents/heading_tree_builder.rb +41 -0
  120. data/lib/govuk_tech_docs/table_of_contents/heading_tree_renderer.rb +46 -0
  121. data/lib/govuk_tech_docs/table_of_contents/headings_builder.rb +39 -0
  122. data/lib/govuk_tech_docs/table_of_contents/helpers.rb +79 -0
  123. data/lib/govuk_tech_docs/tech_docs_html_renderer.rb +34 -0
  124. data/lib/govuk_tech_docs/unique_identifier_extension.rb +13 -0
  125. data/lib/govuk_tech_docs/unique_identifier_generator.rb +72 -0
  126. data/lib/govuk_tech_docs/version.rb +3 -0
  127. data/lib/govuk_tech_docs/warning_text_extension.rb +23 -0
  128. data/lib/source/api/pages.json.erb +1 -0
  129. data/lib/source/favicon.ico +0 -0
  130. data/lib/source/images/anchored-heading-icon-2x.png +0 -0
  131. data/lib/source/images/anchored-heading-icon.png +0 -0
  132. data/lib/source/images/gov.uk_logotype_crown-2x.png +0 -0
  133. data/lib/source/images/gov.uk_logotype_crown.png +0 -0
  134. data/lib/source/images/gov.uk_logotype_crown_invert_trans.png +0 -0
  135. data/lib/source/images/govuk-crest-2x.png +0 -0
  136. data/lib/source/images/govuk-crest.png +0 -0
  137. data/lib/source/images/govuk-icn-close.png +0 -0
  138. data/lib/source/images/govuk-icn-close@2x.png +0 -0
  139. data/lib/source/images/govuk-icn-numbered-list.png +0 -0
  140. data/lib/source/images/govuk-icn-numbered-list@2x.png +0 -0
  141. data/lib/source/images/open-government-licence.png +0 -0
  142. data/lib/source/images/open-government-licence_2x.png +0 -0
  143. data/lib/source/images/search-result-caret.svg +13 -0
  144. data/lib/source/layouts/_analytics.erb +15 -0
  145. data/lib/source/layouts/_footer.erb +10 -0
  146. data/lib/source/layouts/_header.erb +44 -0
  147. data/lib/source/layouts/_page_review.erb +22 -0
  148. data/lib/source/layouts/_search.erb +16 -0
  149. data/lib/source/layouts/core.erb +82 -0
  150. data/lib/source/layouts/layout.erb +18 -0
  151. 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
+