@x-govuk/nhsuk-eleventy-plugin 1.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.
Files changed (68) hide show
  1. package/LICENSE.txt +22 -0
  2. package/README.md +35 -0
  3. package/package.json +102 -0
  4. package/src/application.js +11 -0
  5. package/src/application.scss +2 -0
  6. package/src/components/_index.scss +12 -0
  7. package/src/components/attribution/_index.scss +13 -0
  8. package/src/components/blockquote/_index.scss +22 -0
  9. package/src/components/card-group/macro.njk +3 -0
  10. package/src/components/card-group/template.njk +26 -0
  11. package/src/components/code/_index.scss +153 -0
  12. package/src/components/contents-list/_index.scss +18 -0
  13. package/src/components/document-header/macro.njk +3 -0
  14. package/src/components/document-header/template.njk +10 -0
  15. package/src/components/document-list/macro.njk +3 -0
  16. package/src/components/document-list/template.njk +22 -0
  17. package/src/components/footnotes-list/_index.scss +55 -0
  18. package/src/components/heading/macro.njk +3 -0
  19. package/src/components/heading/template.njk +15 -0
  20. package/src/components/hero/_index.scss +17 -0
  21. package/src/components/hero/macro.njk +3 -0
  22. package/src/components/hero/template.njk +36 -0
  23. package/src/components/image/_index.scss +8 -0
  24. package/src/components/metadata/_index.scss +24 -0
  25. package/src/components/metadata/macro.njk +3 -0
  26. package/src/components/metadata/template.njk +50 -0
  27. package/src/components/prose-scope/_index.scss +72 -0
  28. package/src/components/prose-scope/macro.njk +3 -0
  29. package/src/components/prose-scope/template.njk +4 -0
  30. package/src/components/related/_index.scss +10 -0
  31. package/src/components/related/macro.njk +3 -0
  32. package/src/components/related/template.njk +17 -0
  33. package/src/components/search/_index.scss +120 -0
  34. package/src/components/search/search.js +133 -0
  35. package/src/components/sub-navigation/_index.scss +71 -0
  36. package/src/components/sub-navigation/macro.njk +3 -0
  37. package/src/components/sub-navigation/template.njk +27 -0
  38. package/src/data/eleventy-computed.js +17 -0
  39. package/src/data/options.js +49 -0
  40. package/src/events/generate-nhsuk-assets.js +57 -0
  41. package/src/filters/current-page.js +18 -0
  42. package/src/filters/index.js +21 -0
  43. package/src/filters/items-from-collection.js +73 -0
  44. package/src/filters/markdown.js +20 -0
  45. package/src/index.js +136 -0
  46. package/src/index.scss +5 -0
  47. package/src/layouts/base.njk +81 -0
  48. package/src/layouts/collection.njk +38 -0
  49. package/src/layouts/error.njk +11 -0
  50. package/src/layouts/feed.njk +32 -0
  51. package/src/layouts/hub.njk +31 -0
  52. package/src/layouts/page.njk +31 -0
  53. package/src/layouts/post.njk +47 -0
  54. package/src/layouts/product.njk +32 -0
  55. package/src/layouts/sitemap.njk +25 -0
  56. package/src/layouts/sub-navigation.njk +40 -0
  57. package/src/layouts/tag.njk +14 -0
  58. package/src/layouts/tags.njk +18 -0
  59. package/src/markdown-it/alert.js +36 -0
  60. package/src/markdown-it/deflist.js +16 -0
  61. package/src/markdown-it/figure.js +12 -0
  62. package/src/markdown-it/footnote.js +47 -0
  63. package/src/markdown-it/table.js +18 -0
  64. package/src/markdown-it.js +91 -0
  65. package/src/nunjucks.js +65 -0
  66. package/src/vendor/nhsuk-frontend/_index.scss +2 -0
  67. package/src/vendor/nhsuk-frontend/components/_index.scss +15 -0
  68. package/src/vendor/nhsuk-frontend/core/_index.scss +1 -0
@@ -0,0 +1,50 @@
1
+ {%- macro _authorLink(author) -%}
2
+ {%- if author.url -%}
3
+ <a class="nhsuk-link" href="{{ author.url }}">{{ author.name }}</a>
4
+ {%- else -%}
5
+ {{- author.name or author -}}
6
+ {%- endif -%}
7
+ {% endmacro -%}
8
+
9
+ <dl class="app-metadata nhsuk-u-reading-width">
10
+ {% if params.author %}
11
+ <div class="app-metadata__item">
12
+ <dt class="app-metadata__term">Posted by</dt>
13
+ <dd class="app-metadata__definition">
14
+ {%- if params.author is string or params.author is mapping -%}
15
+ {{- _authorLink(params.author) -}}
16
+ {%- else -%}
17
+ {%- for author in params.author -%}
18
+ {{- " and " if loop.last and loop.length > 1 else (", " if not loop.first) -}}
19
+ {{- _authorLink(author) -}}
20
+ {%- endfor -%}
21
+ {%- endif -%}
22
+ </dd>
23
+ </div>
24
+ {% endif %}
25
+ {% if params.date %}
26
+ <div class="app-metadata__item">
27
+ <dt class="app-metadata__term">Published</dt>
28
+ <dd class="app-metadata__definition">
29
+ <time datetime="{{ params.date | isoDate }}">{{ params.date | nhsukDate }}</time>
30
+ </dd>
31
+ </div>
32
+ {% endif %}
33
+ {% if params.modified %}
34
+ <div class="app-metadata__item">
35
+ <dt class="app-metadata__term">Last updated</dt>
36
+ <dd class="app-metadata__definition">
37
+ <time datetime="{{ params.modified | isoDate }}">{{ params.modified | nhsukDate }}</time>
38
+ </dd>
39
+ </div>
40
+ {% endif %}
41
+ {% if params.tags | length > 0 %}
42
+ <div class="app-metadata__item">
43
+ <dt class="app-metadata__term">Tagged</dt>
44
+ <dd class="app-metadata__definition">{% for tag in params.tags -%}
45
+ <a href="/tags/{{ tag | slug }}" class="nhsuk-link">{{ tag }}</a>
46
+ {%- if not loop.last %}, {% endif -%}
47
+ {%- endfor %}</dd>
48
+ </div>
49
+ {% endif %}
50
+ </dl>
@@ -0,0 +1,72 @@
1
+ @use "../../vendor/nhsuk-frontend" as *;
2
+
3
+ @include nhsuk-exports("nhsuk-eleventy-plugin/components/prose-scope") {
4
+ .app-prose-scope {
5
+ @include nhsuk-responsive-margin(7, "bottom");
6
+
7
+ // Distinguish inserted text from that inside links
8
+ ins {
9
+ text-decoration-style: double;
10
+ }
11
+
12
+ // Use NHS.UK pale yellow colour for highlighted text
13
+ mark {
14
+ background-color: nhsuk-colour("pale-yellow");
15
+ }
16
+
17
+ // Use sensible defaults for embedded media
18
+ iframe,
19
+ img,
20
+ video {
21
+ height: auto;
22
+ max-width: 100%;
23
+ width: auto;
24
+ }
25
+
26
+ a:focus > img {
27
+ @include nhsuk-focused-box;
28
+ }
29
+
30
+ // Heading anchor
31
+ [class^="nhsuk-heading"][tabindex] {
32
+ outline: none;
33
+ }
34
+
35
+ [class^="nhsuk-heading"][tabindex] .nhsuk-link {
36
+ text-decoration: none;
37
+
38
+ @include nhsuk-link-style-text;
39
+
40
+ &:hover::after,
41
+ :target &::after {
42
+ color: nhsuk-colour("grey-1");
43
+ content: "#";
44
+ font-variant: all-small-caps;
45
+ font-weight: normal;
46
+ margin-left: nhsuk-spacing(1);
47
+ opacity: 0.5;
48
+ }
49
+ }
50
+
51
+ // Responsive table
52
+ .nhsuk-table[tabindex] {
53
+ display: block;
54
+ overflow-x: auto;
55
+ scrollbar-color: nhsuk-colour("grey-1") $nhsuk-border-colour;
56
+ scrollbar-width: thin;
57
+
58
+ &:focus,
59
+ &:focus-visible {
60
+ box-shadow:
61
+ 0 0 0 #{$nhsuk-focus-width * 3} nhsuk-colour("black"),
62
+ 0 0 0 #{$nhsuk-focus-width * 4} $nhsuk-focus-colour;
63
+ outline: #{$nhsuk-focus-width * 2} solid nhsuk-colour("grey-5");
64
+ }
65
+
66
+ &:focus:not(:focus-visible) {
67
+ box-shadow: none;
68
+ outline: none;
69
+ }
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,3 @@
1
+ {% macro appProseScope(params) %}
2
+ {%- include "./template.njk" -%}
3
+ {% endmacro %}
@@ -0,0 +1,4 @@
1
+ <div class="app-prose-scope nhsuk-u-reading-width">
2
+ {{- params if params -}}
3
+ {{- caller() if caller -}}
4
+ </div>
@@ -0,0 +1,10 @@
1
+ @use "../../vendor/nhsuk-frontend" as *;
2
+
3
+ @include nhsuk-exports("nhsuk-eleventy-plugin/components/related") {
4
+ .app-related {
5
+ border-top: 1px solid $nhsuk-border-colour;
6
+
7
+ @include nhsuk-responsive-margin(7, "bottom");
8
+ @include nhsuk-responsive-padding(4, "top");
9
+ }
10
+ }
@@ -0,0 +1,3 @@
1
+ {% macro appRelated(params) %}
2
+ {%- include "./template.njk" -%}
3
+ {% endmacro %}
@@ -0,0 +1,17 @@
1
+ <aside class="app-related{% if layout == "collection" or layout == "page" or layout == "post" or layout == "sub-navigation" %} nhsuk-u-reading-width{% endif %}">
2
+ {% for related in ([params] if params is mapping else params) %}
3
+ <h2 class="nhsuk-heading-s nhsuk-u-margin-bottom-2">
4
+ {{- related.title or "Related" -}}
5
+ </h2>
6
+ {{ related.description | markdown if related.description }}
7
+ {% if related.items %}
8
+ <ul class="nhsuk-list">
9
+ {% for item in related.items %}
10
+ <li>
11
+ <a class="nhsuk-link" href="{{ item.href }}">{{ item.text }}</a>
12
+ </li>
13
+ {% endfor %}
14
+ </ul>
15
+ {% endif %}
16
+ {% endfor %}
17
+ </aside>
@@ -0,0 +1,120 @@
1
+ @use "../../vendor/nhsuk-frontend" as *;
2
+
3
+ // Site search using Accessible autocomplete
4
+ // Styles below are based on the default accessible autocomplete style sheet
5
+
6
+ $_icon-size: 28px;
7
+ $_input-padding-right: nhsuk-px-to-rem(12px + $_icon-size);
8
+
9
+ @include nhsuk-exports("nhsuk-eleventy-plugin/components/search") {
10
+ .nhsuk-header__search-form[hidden] {
11
+ display: none;
12
+ }
13
+
14
+ .app-search__wrapper {
15
+ background-color: $nhsuk-input-background-colour;
16
+ border-radius: $nhsuk-border-radius;
17
+ min-width: 260px;
18
+ position: relative;
19
+ }
20
+
21
+ .nhsuk-header__search-input.app-search__hint,
22
+ .nhsuk-header__search-input.app-search__input {
23
+ background-color: transparent;
24
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m20.7 19.3-4.1-4.1a7 7 0 1 0-1.4 1.4l4 4.1a1 1 0 0 0 1.5 0c.4-.4.4-1 0-1.4ZM6 11a5 5 0 1 1 10 0 5 5 0 0 1-10 0Z' fill='%23#{str-slice(#{$nhsuk-brand-colour}, 2)}'/></svg>");
25
+ background-position: top nhsuk-px-to-rem(4px) right nhsuk-px-to-rem(7px);
26
+ background-repeat: no-repeat;
27
+ background-size: nhsuk-px-to-rem($_icon-size);
28
+ border-radius: $nhsuk-border-radius;
29
+ padding-right: $_input-padding-right;
30
+ width: 100%;
31
+
32
+ &:focus {
33
+ background-position: top nhsuk-px-to-rem(3px) right nhsuk-px-to-rem(6px);
34
+ padding-right: $_input-padding-right;
35
+ }
36
+ }
37
+
38
+ .nhsuk-header__search-input.app-search__hint {
39
+ color: nhsuk-colour("grey-2");
40
+ position: absolute;
41
+ }
42
+
43
+ .app-search__menu {
44
+ background-color: nhsuk-colour("white");
45
+ border-radius: $nhsuk-border-radius;
46
+ color: $nhsuk-text-colour;
47
+ margin: 0;
48
+ max-height: 560px;
49
+ overflow-x: hidden;
50
+ padding: 0;
51
+ width: 100%;
52
+ }
53
+
54
+ .app-search__menu--visible {
55
+ display: block;
56
+ }
57
+
58
+ .app-search__menu--hidden {
59
+ display: none;
60
+ }
61
+
62
+ .app-search__menu--overlay {
63
+ box-shadow: 0 0 3px 0 rgba(nhsuk-colour("grey-1"), 0.5);
64
+ left: 0;
65
+ position: absolute;
66
+ top: 100%;
67
+ z-index: 100;
68
+ }
69
+
70
+ .app-search__option {
71
+ border-bottom: 1px solid nhsuk-colour("grey-5");
72
+ color: $nhsuk-secondary-text-colour;
73
+ cursor: pointer;
74
+ display: block;
75
+ margin: 0;
76
+ padding: nhsuk-spacing(3);
77
+ position: relative;
78
+
79
+ @include nhsuk-font($size: 16);
80
+
81
+ & > * {
82
+ pointer-events: none;
83
+ }
84
+ }
85
+
86
+ // Add a transparent outline for when users change their colours.
87
+ .app-search__option--focused {
88
+ border-bottom-color: transparent;
89
+ outline: $nhsuk-focus-width solid transparent;
90
+ outline-offset: -$nhsuk-focus-width;
91
+
92
+ @include nhsuk-focused-text;
93
+ }
94
+
95
+ .app-search__option--no-results {
96
+ cursor: default;
97
+ }
98
+
99
+ .app-search__option-title {
100
+ text-decoration: underline;
101
+
102
+ @include nhsuk-link-style-default;
103
+
104
+ .app-search__option:hover & {
105
+ text-decoration: none;
106
+
107
+ @include nhsuk-link-style-hover;
108
+ }
109
+
110
+ .app-search__option--focused & {
111
+ color: $nhsuk-focus-text-colour;
112
+ text-decoration: none;
113
+ }
114
+ }
115
+
116
+ .app-search__option-title,
117
+ .app-search__option-metadata {
118
+ display: block;
119
+ }
120
+ }
@@ -0,0 +1,133 @@
1
+ import accessibleAutocomplete from 'accessible-autocomplete'
2
+
3
+ export class SearchElement extends HTMLElement {
4
+ constructor() {
5
+ super()
6
+
7
+ this.statusMessage = null
8
+ this.$container = this.querySelector('.nhsuk-header__search')
9
+ this.$searchForm = this.querySelector('.nhsuk-header__search-form')
10
+ this.$searchInput = this.querySelector('.nhsuk-header__search-input')
11
+ this.searchIndex = null
12
+ this.searchIndexUrl = this.getAttribute('index')
13
+ this.searchResults = []
14
+ this.searchTimeout = 10
15
+ }
16
+
17
+ async fetchSearchIndex(indexUrl) {
18
+ this.statusMessage = 'Loading search index'
19
+
20
+ try {
21
+ const response = await fetch(indexUrl, {
22
+ signal: AbortSignal.timeout(this.searchTimeout * 1000)
23
+ })
24
+
25
+ if (!response.ok) {
26
+ throw Error('Search index not found')
27
+ }
28
+
29
+ const json = await response.json()
30
+ this.statusMessage = 'No results found'
31
+ this.searchIndex = json
32
+ } catch (error) {
33
+ this.statusMessage = 'Failed to load search index'
34
+ console.error(this.statusMessage, error.message)
35
+ }
36
+ }
37
+
38
+ findResults(searchQuery, searchIndex) {
39
+ return searchIndex.filter((item) => {
40
+ const regex = new RegExp(searchQuery, 'gi')
41
+ return (
42
+ item?.title?.match(regex) ||
43
+ item?.description?.match(regex) ||
44
+ item?.tokens?.match(regex)
45
+ )
46
+ })
47
+ }
48
+
49
+ renderResults(query, populateResults) {
50
+ if (!this.searchIndex) {
51
+ return populateResults(this.searchResults)
52
+ }
53
+
54
+ this.searchResults = this.findResults(query, this.searchIndex).reverse()
55
+
56
+ populateResults(this.searchResults)
57
+ }
58
+
59
+ handleOnConfirm(result) {
60
+ const path = result.url
61
+ if (!path) {
62
+ return
63
+ }
64
+
65
+ window.location.href = path
66
+ }
67
+
68
+ handleNoResults() {
69
+ return this.statusMessage
70
+ }
71
+
72
+ inputValueTemplate(result) {
73
+ if (result) {
74
+ return result.title
75
+ }
76
+ }
77
+
78
+ suggestionTemplate(result) {
79
+ if (result) {
80
+ const container = document.createElement('span')
81
+
82
+ // Add title of result to container
83
+ const title = document.createElement('span')
84
+ title.className = 'app-search__option-title'
85
+ title.textContent = result.title
86
+
87
+ container.appendChild(title)
88
+
89
+ // Add section and/or data to container
90
+ if (result.date || result.section) {
91
+ const section = document.createElement('span')
92
+ section.className = 'app-search__option-metadata'
93
+
94
+ section.innerHTML =
95
+ result.date && result.section
96
+ ? `${result.section}<br>${result.date}`
97
+ : result.section || result.date
98
+
99
+ container.appendChild(section)
100
+ }
101
+
102
+ return container.innerHTML
103
+ }
104
+ }
105
+
106
+ async connectedCallback() {
107
+ if (!this.searchIndexUrl) {
108
+ return
109
+ }
110
+
111
+ await this.fetchSearchIndex(this.searchIndexUrl)
112
+
113
+ this.$searchForm.hidden = true
114
+
115
+ return accessibleAutocomplete({
116
+ element: this.$container,
117
+ id: this.$searchInput.id,
118
+ cssNamespace: 'app-search',
119
+ inputClasses: 'nhsuk-header__search-input nhsuk-input',
120
+ displayMenu: 'overlay',
121
+ minLength: 2,
122
+ confirmOnBlur: false,
123
+ placeholder: this.$searchInput.placeholder,
124
+ source: this.renderResults.bind(this),
125
+ onConfirm: this.handleOnConfirm,
126
+ templates: {
127
+ inputValue: this.inputValueTemplate,
128
+ suggestion: (value) => this.suggestionTemplate(value)
129
+ },
130
+ tNoResults: this.handleNoResults.bind(this)
131
+ })
132
+ }
133
+ }
@@ -0,0 +1,71 @@
1
+ @use "../../vendor/nhsuk-frontend" as *;
2
+
3
+ @include nhsuk-exports("nhsuk-eleventy-plugin/components/sub-navigation") {
4
+ .app-sub-navigation {
5
+ @include nhsuk-responsive-margin(7, "bottom");
6
+ }
7
+
8
+ .app-sub-navigation__heading {
9
+ color: $nhsuk-secondary-text-colour;
10
+ margin-bottom: 12px;
11
+ padding-top: nhsuk-spacing(1);
12
+
13
+ @include nhsuk-font(19, $line-height: 1.25, $weight: bold);
14
+ }
15
+
16
+ .app-sub-navigation__list {
17
+ @include nhsuk-font(16, $line-height: 1.25);
18
+ @include nhsuk-responsive-margin(4, "bottom");
19
+ }
20
+
21
+ .app-sub-navigation__item {
22
+ padding-bottom: nhsuk-spacing(1);
23
+ padding-top: nhsuk-spacing(1);
24
+ }
25
+
26
+ .app-sub-navigation__item--current {
27
+ border-left: $nhsuk-border-width solid $nhsuk-brand-colour;
28
+ margin-left: #{$nhsuk-gutter-half * -1};
29
+ padding-left: #{$nhsuk-gutter-half - $nhsuk-border-width};
30
+
31
+ .app-sub-navigation__link {
32
+ font-weight: $nhsuk-font-bold;
33
+ }
34
+ }
35
+
36
+ .app-sub-navigation__link {
37
+ @include nhsuk-link-style-default;
38
+ @include nhsuk-link-style-no-visited-state;
39
+
40
+ &:link {
41
+ text-decoration: none;
42
+ }
43
+ }
44
+
45
+ .app-sub-navigation__list--nested {
46
+ @include nhsuk-responsive-margin(2, "bottom");
47
+ @include nhsuk-responsive-margin(3, "top");
48
+
49
+ .app-sub-navigation__item {
50
+ display: flex;
51
+ gap: nhsuk-spacing(1);
52
+ list-style: none;
53
+
54
+ @include nhsuk-font(16, $line-height: 1.3);
55
+
56
+ &::before {
57
+ color: nhsuk-colour("grey-3");
58
+ content: "—";
59
+ margin-left: nhsuk-spacing(-3);
60
+ }
61
+ }
62
+
63
+ .app-sub-navigation__link {
64
+ font-weight: 400;
65
+ }
66
+ }
67
+
68
+ .app-sub-navigation__link[aria-current] {
69
+ font-weight: bold;
70
+ }
71
+ }
@@ -0,0 +1,3 @@
1
+ {% macro appSubNavigation(params) %}
2
+ {%- include "./template.njk" -%}
3
+ {% endmacro %}
@@ -0,0 +1,27 @@
1
+ <nav class="app-sub-navigation{%- if params.classes %} {{ params.classes }}{% endif %}" aria-labelledby="sub-navigation-heading"
2
+ {%- for attribute, value in params.attributes %} {{ attribute }}="{{value}}"{% endfor %}>
3
+ <h2 class="nhsuk-u-visually-hidden" id="sub-navigation-heading">{{ params.visuallyHiddenTitle or "Pages in this section" }}</h2>
4
+ {% for theme, items in params.items | groupby("theme") %}
5
+ {% if theme != "undefined" %}
6
+ <h3 class="app-sub-navigation__heading">{{ theme }}</h3>
7
+ {% endif %}
8
+ <ul class="nhsuk-list app-sub-navigation__list">
9
+ {% for item in items %}
10
+ {% if item %}
11
+ <li class="app-sub-navigation__item{% if item.parent or item.current %} app-sub-navigation__item--current{% endif %}">
12
+ <a class="app-sub-navigation__link" href="{{ item.href }}"{% if item.current %} aria-current="true"{% endif %}>{{ item.text }}</a>
13
+ {% if item.parent and item.children | length > 0 %}
14
+ <ul class="app-sub-navigation__list--nested">
15
+ {% for child in item.children %}
16
+ <li class="app-sub-navigation__item">
17
+ <a class="app-sub-navigation__link" href="{{ child.href }}"{% if child.current %} aria-current="true"{% endif %}>{{ child.text }}</a>
18
+ </li>
19
+ {% endfor %}
20
+ </ul>
21
+ {% endif %}
22
+ </li>
23
+ {% endif %}
24
+ {% endfor %}
25
+ </ul>
26
+ {% endfor %}
27
+ </nav>
@@ -0,0 +1,17 @@
1
+ import {
2
+ getNavigationKey,
3
+ getNavigationParent
4
+ } from '@x-govuk/govuk-eleventy-plugin/utils'
5
+
6
+ /**
7
+ * Set sensible defaults for eleventyNavigation
8
+ *
9
+ * @see {@link https://www.11ty.dev/docs/plugins/navigation/}
10
+ */
11
+ export const eleventyComputed = {
12
+ eleventyNavigation: {
13
+ key: (data) => getNavigationKey(data),
14
+ parent: (data) => getNavigationParent(data),
15
+ excerpt: (data) => data.eleventyNavigation.excerpt || data.description
16
+ }
17
+ }
@@ -0,0 +1,49 @@
1
+ import deepmerge from 'deepmerge'
2
+
3
+ /**
4
+ * Default option values
5
+ *
6
+ * @see {@link https://x-govuk.github.io/nhsuk-eleventy-plugin/options/}
7
+ */
8
+ const defaults = {
9
+ homeKey: 'Home',
10
+ icons: {
11
+ mask: '/assets/favicons/favicon.svg',
12
+ shortcut: '/assets/favicons/favicon.ico',
13
+ touch: '/assets/favicons/apple-touch-icon.png'
14
+ },
15
+ stylesheets: [],
16
+ titleSuffix: 'NHS',
17
+ url: false,
18
+ templates: {
19
+ error404: true,
20
+ sitemap: true // Linked to from default 404 page
21
+ }
22
+ }
23
+
24
+ export function defaultPluginOptions(options, pathPrefix) {
25
+ options.pathPrefix = pathPrefix
26
+
27
+ // Let `true` mean the default title suffix (`true` is rendered as a string)
28
+ if (options.titleSuffix === true) {
29
+ delete options.titleSuffix
30
+ }
31
+
32
+ if (options.templates?.searchIndex) {
33
+ // Enable search in header
34
+ if (options.header) {
35
+ options.header.search = options.header?.search || true
36
+ }
37
+
38
+ // Add _searchIndexPath to enable search field to be initialised
39
+ options._searchIndexPath =
40
+ options.templates?.searchIndex?.permalink || '/search-index.json'
41
+ }
42
+
43
+ if (options.templates?.feed) {
44
+ // Add _feedPath to enable feed to be linked to from page head
45
+ options._feedPath = options.templates?.feed?.permalink || '/feed.xml'
46
+ }
47
+
48
+ return deepmerge(defaults, options)
49
+ }
@@ -0,0 +1,57 @@
1
+ import fs from 'node:fs/promises'
2
+ import path from 'node:path'
3
+
4
+ import commonJs from '@rollup/plugin-commonjs'
5
+ import { nodeResolve } from '@rollup/plugin-node-resolve'
6
+ import terser from '@rollup/plugin-terser'
7
+ import { rollup } from 'rollup'
8
+ import * as sass from 'sass'
9
+
10
+ /**
11
+ * Generate NHS.UK Frontend assets
12
+ *
13
+ * @param {object} dir - Project directories
14
+ * @param {object} options - Plugin options
15
+ * @returns {Function} Eleventy event
16
+ */
17
+ export async function generateAssets(dir, options) {
18
+ // Generate default CSS
19
+ // Ignored if custom stylesheets are used
20
+ if (options.stylesheets.length === 0) {
21
+ const inputFilePath = path.join(import.meta.dirname, '../application.scss')
22
+ const inputFile = await fs.readFile(inputFilePath)
23
+ const outputFile = `${dir.output}/assets/application.css`
24
+
25
+ try {
26
+ const result = sass.compileString(inputFile.toString(), {
27
+ importers: [new sass.NodePackageImporter()], // Imports with `pkg:`
28
+ loadPaths: ['./node_modules', '.'], // Imports without `pkg:`
29
+ quietDeps: true,
30
+ style: 'compressed'
31
+ })
32
+ fs.writeFile(outputFile, result.css)
33
+ } catch (error) {
34
+ console.error(error)
35
+ }
36
+ }
37
+
38
+ // Bundle JavaScript
39
+ try {
40
+ const jsFile = `${dir.output}/assets/application.js`
41
+ const bundle = await rollup({
42
+ input: path.join(import.meta.dirname, '../application.js'),
43
+ context: 'window',
44
+ plugins: [
45
+ nodeResolve(),
46
+ commonJs(),
47
+ terser({ format: { comments: false } })
48
+ ]
49
+ })
50
+
51
+ const { output } = await bundle.generate({ format: 'es' })
52
+ const { code } = output[0]
53
+ fs.writeFile(jsFile, code)
54
+ } catch (error) {
55
+ console.error(error)
56
+ }
57
+ }
@@ -0,0 +1,18 @@
1
+ import { currentPage as getCurrentPage } from '@x-govuk/govuk-eleventy-plugin/filters'
2
+
3
+ /**
4
+ * Indicate which is the current item in header navigation items
5
+ *
6
+ * @param {object} header - Header options
7
+ * @param {string} pageUrl - URL of current page
8
+ * @returns {Array} Header items
9
+ */
10
+ export function currentPage(header, pageUrl) {
11
+ if (!header?.navigation?.items) {
12
+ return header
13
+ }
14
+
15
+ header.navigation.items = getCurrentPage(header.navigation.items, pageUrl)
16
+
17
+ return header
18
+ }
@@ -0,0 +1,21 @@
1
+ export {
2
+ canonicalUrl,
3
+ govukDate, // Used by page templates in govuk-eleventy-plugin
4
+ govukDate as nhsukDate,
5
+ isoDate,
6
+ dateToRfc3339,
7
+ getNewestCollectionItemDate,
8
+ getNextNavigationItem,
9
+ getPreviousNavigationItem,
10
+ includes,
11
+ itemsFromNavigation,
12
+ itemsFromPagination,
13
+ noOrphans,
14
+ pretty,
15
+ sliceFromCollection,
16
+ smart,
17
+ tokenize
18
+ } from '@x-govuk/govuk-eleventy-plugin/filters'
19
+ export { currentPage } from './current-page.js'
20
+ export { itemsFromCollection } from './items-from-collection.js'
21
+ export { markdown } from './markdown.js'