govuk_publishing_components 71.0.0 → 71.0.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 74db3ccfb835e4b5298212da9a2fbcdd6c6df533a174622e62ef8c09f838e163
4
- data.tar.gz: 6c8d9fba438b66e7f71191021547d009ffffff268061665ee7b3b7701888aef1
3
+ metadata.gz: 8e03438d3c963276e86d21ca8e2da336a8cd15220298b2ffbb57cbf53ca86e56
4
+ data.tar.gz: 8e2052d81d95e829bf5239d2cde0080fc21a62ba7aa2097fe707d131db63ae20
5
5
  SHA512:
6
- metadata.gz: 6a304e13a7a304c5851f17efc0e6920589401ebe9943f16837b0e548a3ca80d9f4468ddb6b3d5114506ab0cfd1cd90f34e619f01591f31df4420eddb4f6b22ad
7
- data.tar.gz: d0814535e7016e51788030010ad2cc7b6a490a138e81ccca427e3dc80cb9e143dfceb3792593571ec8c94da87ad536930b3cb55bf3542d3fdfc45a801b64aa9c
6
+ metadata.gz: 5278c358eb404521e67e9bb9fb95e8df365f7ae1a7df0ba88a9f43b57a5281d735206b7265c28cab4f06843ba30b57d43800b516ff701ed794c31c411bc36713
7
+ data.tar.gz: 5aecaf5b41cb5c02b8d85cd194b561e21b8d120adadd4f09dbd1bd6e07fb031d9eecde6d63cf4f3af955f8b0593967ba7b4d4a3d683594b24eb9c41e8161b0e7
@@ -1,68 +1,139 @@
1
- /* global XMLHttpRequest */
1
+ /* global fetch, DOMException, AbortController */
2
2
  (function (Modules) {
3
- function SinglePageNotificationButton ($module) {
4
- this.$module = $module
5
- this.basePath = this.$module.querySelector('input[name="base_path"]').value
6
- this.buttonLocation = this.$module.getAttribute('data-button-location')
7
- this.buttonVisibleClass = 'gem-c-single-page-notification-button--visible'
8
-
9
- this.personalisationEndpoint = '/api/personalisation/check-email-subscription?base_path=' + this.basePath
10
- // This attribute is passed through to the personalisation API to ensure the updated button has the same button_location for analytics
11
- if (this.buttonLocation) this.personalisationEndpoint += '&button_location=' + this.buttonLocation
12
- }
3
+ class SinglePageNotificationButton {
4
+ constructor ($module) {
5
+ /** @type {HTMLDivElement} */
6
+ this.$module = $module
7
+
8
+ /** @type {HTMLInputElement | null} */
9
+ this.basePathInput = this.$module.querySelector('input[name="base_path"]')
10
+
11
+ /** @type {HTMLButtonElement | null} */
12
+ this.button = this.$module.querySelector('.gem-c-button__outline--notification')
13
+
14
+ /** @type {string | null} */
15
+ this.buttonLocation = this.$module.getAttribute('data-button-location')
16
+
17
+ /** @type {string | null} */
18
+ this.customSubscribeText = this.$module.getAttribute('data-button-text-subscribe')
19
+
20
+ /** @type {string | null} */
21
+ this.customUnsubscribeText = this.$module.getAttribute('data-button-text-unsubscribe')
22
+
23
+ /** @type {string} */
24
+ this.buttonVisibleClass = 'gem-c-single-page-notification-button--visible'
25
+
26
+ /** @type {number | null} */
27
+ this.fetchRequestTimeoutId = null
28
+ }
13
29
 
14
- SinglePageNotificationButton.prototype.init = function () {
15
- var xhr = new XMLHttpRequest()
16
- xhr.open('GET', this.personalisationEndpoint, true)
17
- // if XHR to the personalisation endpoint is taking an incredibly long time to complete, we are better off leaving the button in its default unpersonalised state. Content changing before the user's eyes while they are browsing can be jarring and should be avoided.
18
- xhr.timeout = 10000
19
-
20
- xhr.ontimeout = function () {
21
- this.makeVisible(this.$module)
22
- }.bind(this)
23
-
24
- xhr.onreadystatechange = function () {
25
- if (xhr.readyState === 4) {
26
- if (xhr.status === 200) {
27
- var responseText = xhr.responseText
28
- // if response text exists and is JSON parse-able, parse the response and update the button html
29
- if (responseText && this.responseIsJSON(responseText)) {
30
- var active = JSON.parse(responseText).active
31
-
32
- var customSubscribeText = this.$module.getAttribute('data-button-text-subscribe')
33
- var customUnsubscribeText = this.$module.getAttribute('data-button-text-unsubscribe')
34
- // Only set custom button text if both text items are provided
35
- var customText = customSubscribeText && customUnsubscribeText
36
-
37
- // If response returns active, user has subscribed to notifications
38
- if (active === true) {
39
- if (customText) {
40
- this.$module.querySelector('.gem-c-button__outline--notification').textContent = customUnsubscribeText
41
- }
42
- } else {
43
- if (customText) {
44
- this.$module.querySelector('.gem-c-button__outline--notification').textContent = customSubscribeText
45
- }
46
- }
47
- }
30
+ /**
31
+ * Initialise the single page notification button
32
+ *
33
+ */
34
+ async init () {
35
+ try {
36
+ if (this.button === null) {
37
+ throw new Error('Button required')
48
38
  }
39
+
40
+ if (this.basePathInput === null) {
41
+ throw new Error('Base path required')
42
+ }
43
+
44
+ const personalisationEndpoint = this.getPersonalisationEndpoint(this.basePathInput.value, this.buttonLocation)
45
+ const personalisationData = await this.getPersonalisationData(personalisationEndpoint, 10000)
46
+ const customButtonText = this.getCustomButtonText(personalisationData.active, this.customSubscribeText, this.customUnsubscribeText)
47
+
48
+ if (customButtonText) {
49
+ this.button.textContent = customButtonText
50
+ }
51
+ } catch (error) {
52
+ console.error(error)
53
+ } finally {
49
54
  this.makeVisible(this.$module)
55
+ clearTimeout(this.fetchRequestTimeoutId)
50
56
  }
51
- }.bind(this)
52
- xhr.send()
53
- }
57
+ }
58
+
59
+ /**
60
+ * Get the personalisation data endpoint
61
+ *
62
+ * @param {string} basePath The base path to use
63
+ * @param {string | null} buttonLocation This attribute is passed through to the personalisation API to ensure the updated button has the same button_location for analytics
64
+ * @returns {string} The personalisation endpoint to use
65
+ */
66
+ getPersonalisationEndpoint (basePath, buttonLocation) {
67
+ const checkEmailSubscriptionEndpoint = '/api/personalisation/check-email-subscription'
54
68
 
55
- SinglePageNotificationButton.prototype.responseIsJSON = function (string) {
56
- try {
57
- JSON.parse(string)
58
- } catch (e) {
59
- return false
69
+ if (buttonLocation) {
70
+ return `${checkEmailSubscriptionEndpoint}?base_path=${basePath}&button_location=${buttonLocation}`
71
+ }
72
+
73
+ return `${checkEmailSubscriptionEndpoint}?base_path=${basePath}`
60
74
  }
61
- return true
62
- }
63
75
 
64
- SinglePageNotificationButton.prototype.makeVisible = function (target) {
65
- target.classList.add(this.buttonVisibleClass)
76
+ /**
77
+ * Fetch data from the personalisation endpoint for checking if an email subscription is active
78
+ *
79
+ * @param {string} personalisationEndpoint The personalisation URL to fetch data from
80
+ * @param {number} timer Set the time in milliseconds when the fetch request should abort
81
+ * @returns {Promise<EmailSubscriptionData>}
82
+ * @throws {DOMException} if the request times out
83
+ * @throws {Error} if the request response is not ok
84
+ */
85
+ async getPersonalisationData (personalisationEndpoint, timer) {
86
+ const abortController = new AbortController()
87
+ this.fetchRequestTimeoutId = setTimeout(() => abortController.abort(new DOMException('Timeout', 'AbortError')), timer)
88
+
89
+ const response = await fetch(personalisationEndpoint, {
90
+ headers: { Accept: 'application/json' },
91
+ signal: abortController.signal
92
+ })
93
+
94
+ if (!response.ok) {
95
+ throw new Error(`Response status: ${response.status}`)
96
+ }
97
+
98
+ return await response.json()
99
+ }
100
+
101
+ /**
102
+ * Make the single page notification button visible
103
+ *
104
+ * @param {HTMLDivElement} module The element to add the visible class to
105
+ */
106
+ makeVisible (module) {
107
+ module.classList.add(this.buttonVisibleClass)
108
+ }
109
+
110
+ /**
111
+ * Get custom button text depending on whether the email subscription is active
112
+ *
113
+ * @param {boolean} activeState If the user has an email subscription to the page
114
+ * @param {string} customSubscribeText The custom subscribe text used when the activeState is false
115
+ * @param {string} customUnsubscribeText The custom subscribe text used when the activeState is true
116
+ * @returns {string | undefined} Returns the custom button text to use or `undefined` if the custom button text cannot be determined
117
+ */
118
+ getCustomButtonText (activeState, customSubscribeText, customUnsubscribeText) {
119
+ if (typeof activeState !== 'boolean') return
120
+ // Only set custom button text if both text items are provided
121
+ if (!customSubscribeText || !customUnsubscribeText) {
122
+ return
123
+ }
124
+
125
+ if (activeState === true) return customUnsubscribeText
126
+ if (activeState === false) return customSubscribeText
127
+ }
66
128
  }
129
+
67
130
  Modules.SinglePageNotificationButton = SinglePageNotificationButton
68
131
  })(window.GOVUK.Modules)
132
+
133
+ /**
134
+ * @typedef {object} EmailSubscriptionData
135
+ * @property {string} base_path the base_path parameter
136
+ * @property {boolean} active whether the email subscription is active
137
+ *
138
+ * @see {@link https://docs.publishing.service.gov.uk/repos/account-api/api.html#get-apipersonalisationcheck-email-subscription}
139
+ */
@@ -1,3 +1,4 @@
1
+ /* global ResizeObserver */
1
2
  (function (GOVUK) {
2
3
  'use strict'
3
4
 
@@ -17,15 +18,26 @@
17
18
  this.windowResized()
18
19
  }, 10)
19
20
 
21
+ // for all browsers
20
22
  window.onresize = () => {
21
- if (this.windowResizeTimeout) {
22
- clearTimeout(this.windowResizeTimeout)
23
- }
23
+ this.detectSizeChange()
24
+ }
24
25
 
25
- this.windowResizeTimeout = setTimeout(function () {
26
- this.windowResized()
27
- }.bind(this), 500)
26
+ // for Firefox with 'zoom text only' (does not trigger onresize)
27
+ const resizeObserver = new ResizeObserver(() => {
28
+ this.detectSizeChange()
29
+ })
30
+ resizeObserver.observe(this.tables[0])
31
+ }
32
+
33
+ TableScroll.prototype.detectSizeChange = function () {
34
+ if (this.windowResizeTimeout) {
35
+ clearTimeout(this.windowResizeTimeout)
28
36
  }
37
+
38
+ this.windowResizeTimeout = setTimeout(function () {
39
+ this.windowResized()
40
+ }.bind(this), 300)
29
41
  }
30
42
 
31
43
  TableScroll.prototype.windowResized = function () {
@@ -98,6 +98,10 @@
98
98
  }
99
99
 
100
100
  .govspeak-table-scrollbar {
101
+ position: sticky;
102
+ background: #FFFFFF;
103
+ z-index: 1;
104
+ top: 0;
101
105
  overflow-x: scroll;
102
106
  overflow-y: hidden;
103
107
  height: 20px;
@@ -1128,6 +1128,7 @@ examples:
1128
1128
  </tbody>
1129
1129
  </table>
1130
1130
  wide_table:
1131
+ description: Wide tables are enhanced with two features to help users navigate. Left/right indicators are added to show that more content is available to the sides, and a secondary scrollbar is added at the top of the table, that remains at the top of the page as the table is scrolled downwards. This allows users to scroll left/right at any point, which is useful where tables are taller than their browser.
1131
1132
  data:
1132
1133
  block: |
1133
1134
  <table>
@@ -1,8 +1,6 @@
1
1
  module GovukPublishingComponents
2
2
  module Presenters
3
3
  class HasPartSchema
4
- attr_reader :has_part_url
5
-
6
4
  def initialize(has_part_url)
7
5
  @has_part_url = has_part_url
8
6
  end
@@ -12,7 +10,7 @@ module GovukPublishingComponents
12
10
  {
13
11
  "@context" => "http://schema.org",
14
12
  "@type" => "CreativeWork",
15
- "sameAs" => has_part_url,
13
+ "sameAs" => @has_part_url,
16
14
  }
17
15
  end
18
16
  end
@@ -1,3 +1,3 @@
1
1
  module GovukPublishingComponents
2
- VERSION = "71.0.0".freeze
2
+ VERSION = "71.0.1".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: govuk_publishing_components
3
3
  version: !ruby/object:Gem::Version
4
- version: 71.0.0
4
+ version: 71.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - GOV.UK Dev