govuk_publishing_components 21.51.0 → 21.55.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/govuk_publishing_components/action-link--nhs.png +0 -0
  3. data/app/assets/images/govuk_publishing_components/action-link--nhs.svg +1 -0
  4. data/app/assets/images/govuk_publishing_components/action-link-arrow--dark.png +0 -0
  5. data/app/assets/images/govuk_publishing_components/action-link-arrow--simple.png +0 -0
  6. data/app/assets/images/govuk_publishing_components/action-link-arrow.png +0 -0
  7. data/app/assets/stylesheets/govuk_publishing_components/components/_action-link.scss +38 -7
  8. data/app/assets/stylesheets/govuk_publishing_components/components/_back-link.scss +0 -38
  9. data/app/assets/stylesheets/govuk_publishing_components/components/_input.scss +13 -4
  10. data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_steps.scss +1 -1
  11. data/app/views/govuk_publishing_components/components/_action_link.html.erb +23 -3
  12. data/app/views/govuk_publishing_components/components/_contextual_breadcrumbs.html.erb +5 -36
  13. data/app/views/govuk_publishing_components/components/_input.html.erb +12 -1
  14. data/app/views/govuk_publishing_components/components/_step_by_step_nav_header.html.erb +28 -8
  15. data/app/views/govuk_publishing_components/components/docs/action_link.yml +43 -15
  16. data/app/views/govuk_publishing_components/components/docs/input.yml +9 -0
  17. data/app/views/govuk_publishing_components/components/docs/step_by_step_nav_header.yml +23 -0
  18. data/lib/govuk_publishing_components.rb +1 -0
  19. data/lib/govuk_publishing_components/presenters/breadcrumb_selector.rb +107 -0
  20. data/lib/govuk_publishing_components/presenters/content_breadcrumbs_based_on_priority.rb +26 -9
  21. data/lib/govuk_publishing_components/presenters/content_breadcrumbs_based_on_taxons.rb +1 -3
  22. data/lib/govuk_publishing_components/presenters/contextual_navigation.rb +40 -19
  23. data/lib/govuk_publishing_components/presenters/machine_readable/faq_page_schema.rb +10 -33
  24. data/lib/govuk_publishing_components/presenters/machine_readable/html_publication_schema.rb +77 -0
  25. data/lib/govuk_publishing_components/presenters/schema_org.rb +24 -16
  26. data/lib/govuk_publishing_components/version.rb +1 -1
  27. data/node_modules/axe-core/CHANGELOG.md +6 -0
  28. data/node_modules/axe-core/axe.js +19 -3
  29. data/node_modules/axe-core/axe.min.js +2 -2
  30. data/node_modules/axe-core/bower.json +1 -1
  31. data/node_modules/axe-core/lib/commons/dom/get-element-stack.js +27 -1
  32. data/node_modules/axe-core/package.json +21 -21
  33. data/node_modules/axe-core/sri-history.json +4 -0
  34. data/node_modules/govuk-frontend/README.md +6 -6
  35. data/node_modules/govuk-frontend/govuk/_base.scss +3 -0
  36. data/node_modules/govuk-frontend/govuk/all.js +1 -1
  37. data/node_modules/govuk-frontend/govuk/all.scss +1 -3
  38. data/node_modules/govuk-frontend/govuk/components/_all.scss +31 -29
  39. data/node_modules/govuk-frontend/govuk/components/accordion/_accordion.scss +2 -208
  40. data/node_modules/govuk-frontend/govuk/components/accordion/_index.scss +207 -0
  41. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js +1 -1
  42. data/node_modules/govuk-frontend/govuk/components/back-link/_back-link.scss +2 -65
  43. data/node_modules/govuk-frontend/govuk/components/back-link/_index.scss +112 -0
  44. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/_breadcrumbs.scss +2 -118
  45. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/_index.scss +138 -0
  46. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/macro-options.json +6 -0
  47. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/template.njk +12 -1
  48. data/node_modules/govuk-frontend/govuk/components/button/_button.scss +2 -284
  49. data/node_modules/govuk-frontend/govuk/components/button/_index.scss +280 -0
  50. data/node_modules/govuk-frontend/govuk/components/character-count/_character-count.scss +2 -31
  51. data/node_modules/govuk-frontend/govuk/components/character-count/_index.scss +28 -0
  52. data/node_modules/govuk-frontend/govuk/components/checkboxes/_checkboxes.scss +2 -308
  53. data/node_modules/govuk-frontend/govuk/components/checkboxes/_index.scss +304 -0
  54. data/node_modules/govuk-frontend/govuk/components/date-input/_date-input.scss +2 -30
  55. data/node_modules/govuk-frontend/govuk/components/date-input/_index.scss +26 -0
  56. data/node_modules/govuk-frontend/govuk/components/details/_details.scss +2 -88
  57. data/node_modules/govuk-frontend/govuk/components/details/_index.scss +84 -0
  58. data/node_modules/govuk-frontend/govuk/components/error-message/_error-message.scss +2 -15
  59. data/node_modules/govuk-frontend/govuk/components/error-message/_index.scss +11 -0
  60. data/node_modules/govuk-frontend/govuk/components/error-summary/_error-summary.scss +2 -59
  61. data/node_modules/govuk-frontend/govuk/components/error-summary/_index.scss +55 -0
  62. data/node_modules/govuk-frontend/govuk/components/fieldset/_fieldset.scss +2 -68
  63. data/node_modules/govuk-frontend/govuk/components/fieldset/_index.scss +64 -0
  64. data/node_modules/govuk-frontend/govuk/components/file-upload/_file-upload.scss +2 -81
  65. data/node_modules/govuk-frontend/govuk/components/file-upload/_index.scss +77 -0
  66. data/node_modules/govuk-frontend/govuk/components/footer/_footer.scss +2 -244
  67. data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +238 -0
  68. data/node_modules/govuk-frontend/govuk/components/header/_header.scss +2 -318
  69. data/node_modules/govuk-frontend/govuk/components/header/_index.scss +312 -0
  70. data/node_modules/govuk-frontend/govuk/components/header/macro-options.json +7 -1
  71. data/node_modules/govuk-frontend/govuk/components/header/template.njk +2 -2
  72. data/node_modules/govuk-frontend/govuk/components/hint/_hint.scss +2 -50
  73. data/node_modules/govuk-frontend/govuk/components/hint/_index.scss +46 -0
  74. data/node_modules/govuk-frontend/govuk/components/input/_index.scss +99 -0
  75. data/node_modules/govuk-frontend/govuk/components/input/_input.scss +2 -103
  76. data/node_modules/govuk-frontend/govuk/components/inset-text/_index.scss +24 -0
  77. data/node_modules/govuk-frontend/govuk/components/inset-text/_inset-text.scss +2 -28
  78. data/node_modules/govuk-frontend/govuk/components/label/_index.scss +41 -0
  79. data/node_modules/govuk-frontend/govuk/components/label/_label.scss +2 -45
  80. data/node_modules/govuk-frontend/govuk/components/panel/_index.scss +40 -0
  81. data/node_modules/govuk-frontend/govuk/components/panel/_panel.scss +2 -44
  82. data/node_modules/govuk-frontend/govuk/components/phase-banner/_index.scss +27 -0
  83. data/node_modules/govuk-frontend/govuk/components/phase-banner/_phase-banner.scss +2 -31
  84. data/node_modules/govuk-frontend/govuk/components/radios/_index.scss +342 -0
  85. data/node_modules/govuk-frontend/govuk/components/radios/_radios.scss +2 -346
  86. data/node_modules/govuk-frontend/govuk/components/select/_index.scss +53 -0
  87. data/node_modules/govuk-frontend/govuk/components/select/_select.scss +2 -57
  88. data/node_modules/govuk-frontend/govuk/components/skip-link/_index.scss +33 -0
  89. data/node_modules/govuk-frontend/govuk/components/skip-link/_skip-link.scss +2 -37
  90. data/node_modules/govuk-frontend/govuk/components/summary-list/_index.scss +153 -0
  91. data/node_modules/govuk-frontend/govuk/components/summary-list/_summary-list.scss +2 -157
  92. data/node_modules/govuk-frontend/govuk/components/table/_index.scss +50 -0
  93. data/node_modules/govuk-frontend/govuk/components/table/_table.scss +2 -54
  94. data/node_modules/govuk-frontend/govuk/components/tabs/_index.scss +138 -0
  95. data/node_modules/govuk-frontend/govuk/components/tabs/_tabs.scss +2 -142
  96. data/node_modules/govuk-frontend/govuk/components/tag/_index.scss +87 -0
  97. data/node_modules/govuk-frontend/govuk/components/tag/_tag.scss +2 -91
  98. data/node_modules/govuk-frontend/govuk/components/textarea/_index.scss +51 -0
  99. data/node_modules/govuk-frontend/govuk/components/textarea/_textarea.scss +2 -55
  100. data/node_modules/govuk-frontend/govuk/components/warning-text/_index.scss +56 -0
  101. data/node_modules/govuk-frontend/govuk/components/warning-text/_warning-text.scss +2 -60
  102. data/node_modules/govuk-frontend/govuk/core/_global-styles.scss +5 -3
  103. data/node_modules/govuk-frontend/govuk/core/_links.scss +5 -3
  104. data/node_modules/govuk-frontend/govuk/core/_lists.scss +17 -3
  105. data/node_modules/govuk-frontend/govuk/core/_section-break.scss +5 -3
  106. data/node_modules/govuk-frontend/govuk/core/_template.scss +5 -3
  107. data/node_modules/govuk-frontend/govuk/core/_typography.scss +5 -3
  108. data/node_modules/govuk-frontend/govuk/helpers/_clearfix.scss +1 -1
  109. data/node_modules/govuk-frontend/govuk/helpers/_focused.scss +1 -1
  110. data/node_modules/govuk-frontend/govuk/helpers/_grid.scss +2 -1
  111. data/node_modules/govuk-frontend/govuk/helpers/_links.scss +1 -1
  112. data/node_modules/govuk-frontend/govuk/helpers/_media-queries.scss +1 -1
  113. data/node_modules/govuk-frontend/govuk/helpers/_shape-arrow.scss +1 -1
  114. data/node_modules/govuk-frontend/govuk/helpers/_spacing.scss +1 -1
  115. data/node_modules/govuk-frontend/govuk/helpers/_typography.scss +1 -1
  116. data/node_modules/govuk-frontend/govuk/helpers/_visually-hidden.scss +1 -1
  117. data/node_modules/govuk-frontend/govuk/objects/_form-group.scss +1 -3
  118. data/node_modules/govuk-frontend/govuk/objects/_grid.scss +1 -3
  119. data/node_modules/govuk-frontend/govuk/objects/_main-wrapper.scss +5 -3
  120. data/node_modules/govuk-frontend/govuk/objects/_width-container.scss +2 -4
  121. data/node_modules/govuk-frontend/govuk/overrides/_display.scss +5 -3
  122. data/node_modules/govuk-frontend/govuk/overrides/_spacing.scss +5 -3
  123. data/node_modules/govuk-frontend/govuk/overrides/_typography.scss +5 -3
  124. data/node_modules/govuk-frontend/govuk/overrides/_width.scss +5 -3
  125. data/node_modules/govuk-frontend/govuk/settings/_ie8.scss +1 -1
  126. data/node_modules/govuk-frontend/govuk/tools/_compatibility.scss +1 -1
  127. data/node_modules/govuk-frontend/govuk/tools/_font-url.scss +1 -1
  128. data/node_modules/govuk-frontend/govuk/tools/_ie8.scss +1 -1
  129. data/node_modules/govuk-frontend/govuk/tools/_image-url.scss +1 -1
  130. data/node_modules/govuk-frontend/govuk/tools/_px-to-em.scss +1 -1
  131. data/node_modules/govuk-frontend/govuk/tools/_px-to-rem.scss +1 -1
  132. data/node_modules/govuk-frontend/package.json +21 -21
  133. metadata +50 -2
@@ -140,6 +140,15 @@ examples:
140
140
  name: "lead-times"
141
141
  width: 10
142
142
  suffix: "days"
143
+ with_prefix_and_suffix:
144
+ description: To help users understand how the input should look like. Often used for units of measurement.
145
+ data:
146
+ label:
147
+ text: "Cost per item, in pounds"
148
+ name: "Cost-per-item"
149
+ width: 10
150
+ prefix: "£"
151
+ suffix: "per item"
143
152
  with_suffix_and_error:
144
153
  description: To help users understand how the input should look like. Often used for units of measurement.
145
154
  data:
@@ -20,9 +20,32 @@ examples:
20
20
  data:
21
21
  title: 'Coronavirus: businesses and self-employed people'
22
22
  path: /childcare-parenting/pregnancy-and-birth
23
+ with_margin_bottom:
24
+ description: |
25
+ The component accepts a number for margin bottom from 0 to 9 (0px to 60px) using the [GOV.UK Frontend spacing scale](https://design-system.service.gov.uk/styles/spacing/#the-responsive-spacing-scale). It defaults to having a margin bottom of 30px.
26
+ data:
27
+ title: 'Learn to practice flexible spacing: step by step'
28
+ margin_bottom: 9
23
29
  with_unique_tracking:
24
30
  description: In order to identify the step by step navigation the component is part of, we need to track a unique ID of the navigation in Google Analytics. This ID should be the same across all pages that are linked from and are part of that navigation. Its value is included in any tracking events, specifically in dimension96. It refers to the ID of the step nav that the component links to.
25
31
  data:
26
32
  title: 'With a tracking id'
27
33
  path: '#'
28
34
  tracking_id: 'this-is-the-tracking-id'
35
+ with_custom_tracking:
36
+ data:
37
+ title: 'With a custom tracking'
38
+ path: '#'
39
+ tracking_category: "customTrackingCategoryClicked"
40
+ tracking_action: "customTrackingAction"
41
+ tracking_label: "customTrackingLabel"
42
+ tracking_dimension: "customTrackingDimension"
43
+ tracking_dimension_index: "23"
44
+ without_custom_dimension:
45
+ data:
46
+ title: 'Without custom dimensions'
47
+ path: '#'
48
+ tracking_category: "customTrackingCategoryClicked"
49
+ tracking_action: "customTrackingAction"
50
+ tracking_label: "customTrackingLabel"
51
+ tracking_dimension_enabled: false
@@ -6,6 +6,7 @@ require "govuk_publishing_components/engine"
6
6
  require "govuk_publishing_components/presenters/shared_helper"
7
7
  require "govuk_publishing_components/presenters/attachment"
8
8
  require "govuk_publishing_components/presenters/breadcrumbs"
9
+ require "govuk_publishing_components/presenters/breadcrumb_selector"
9
10
  require "govuk_publishing_components/presenters/button_helper"
10
11
  require "govuk_publishing_components/presenters/contextual_navigation"
11
12
  require "govuk_publishing_components/presenters/related_navigation_helper"
@@ -0,0 +1,107 @@
1
+ module GovukPublishingComponents
2
+ module Presenters
3
+ class BreadcrumbSelector
4
+ def self.call(*args)
5
+ new(*args).output
6
+ end
7
+
8
+ attr_reader :content_item, :request, :prioritise_taxon_breadcrumbs
9
+
10
+ def initialize(content_item, request, prioritise_taxon_breadcrumbs)
11
+ @content_item = content_item
12
+ @request = request
13
+ @prioritise_taxon_breadcrumbs = prioritise_taxon_breadcrumbs
14
+ end
15
+
16
+ def content_item_navigation
17
+ @content_item_navigation ||= ContextualNavigation.new(content_item, request)
18
+ end
19
+
20
+ def parent_item_navigation
21
+ @parent_item_navigation ||= ContextualNavigation.new(parent_item, request)
22
+ end
23
+
24
+ def parent_item
25
+ @parent_item ||= Services.content_store.content_item(content_item_navigation.parent_api_path)
26
+ rescue GdsApi::ContentStore::ItemNotFound
27
+ # Do nothing
28
+ end
29
+
30
+ def parent_item_options
31
+ @parent_item_options ||= options(parent_item_navigation)
32
+ end
33
+
34
+ def content_item_options
35
+ @content_item_options ||= options(content_item_navigation)
36
+ end
37
+
38
+ def parent_breadcrumbs
39
+ breadcrumbs = [parent_item_options[:breadcrumbs]].flatten # to ensure breadcrumbs always an array
40
+ breadcrumbs.last[:is_page_parent] = false
41
+ breadcrumbs << {
42
+ title: parent_item["title"],
43
+ url: parent_item["base_path"],
44
+ is_page_parent: true,
45
+ }
46
+ end
47
+
48
+ def output
49
+ return content_item_options unless content_item_navigation.html_publication_with_parent?
50
+ return parent_item_options if parent_item_navigation.priority_breadcrumbs
51
+
52
+ step_by_step_header = parent_item_options[:step_by_step]
53
+
54
+ {
55
+ step_by_step: step_by_step_header,
56
+ breadcrumbs: step_by_step_header ? parent_breadcrumbs.first : parent_breadcrumbs,
57
+ }
58
+ end
59
+
60
+ def options(navigation)
61
+ if navigation.priority_breadcrumbs
62
+ {
63
+ step_by_step: true,
64
+ breadcrumbs: navigation.priority_breadcrumbs,
65
+ }
66
+ elsif navigation.content_tagged_to_current_step_by_step?
67
+ {
68
+ step_by_step: true,
69
+ breadcrumbs: navigation.step_nav_helper.header,
70
+ }
71
+ elsif navigation.content_tagged_to_a_finder?
72
+ {
73
+ step_by_step: false,
74
+ breadcrumbs: navigation.breadcrumbs,
75
+ }
76
+ elsif navigation.content_is_tagged_to_a_live_taxon? && prioritise_taxon_breadcrumbs
77
+ {
78
+ step_by_step: false,
79
+ breadcrumbs: navigation.taxon_breadcrumbs,
80
+ }
81
+ elsif navigation.content_tagged_to_mainstream_browse_pages?
82
+ {
83
+ step_by_step: false,
84
+ breadcrumbs: navigation.breadcrumbs,
85
+ }
86
+ elsif navigation.content_has_curated_related_items?
87
+ {
88
+ step_by_step: false,
89
+ breadcrumbs: navigation.breadcrumbs,
90
+ }
91
+ elsif navigation.use_taxon_breadcrumbs?
92
+ {
93
+ step_by_step: false,
94
+ breadcrumbs: navigation.taxon_breadcrumbs,
95
+ }
96
+ elsif navigation.breadcrumbs.any?
97
+ {
98
+ step_by_step: false,
99
+ breadcrumbs: navigation.breadcrumbs,
100
+ }
101
+ else
102
+ {}
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -11,7 +11,7 @@ module GovukPublishingComponents
11
11
 
12
12
  # Returns the highest priority taxon that has a content_id matching those in PRIORITY_TAXONS
13
13
  def self.call(content_item)
14
- new(content_item).taxon
14
+ new(content_item).breadcrumbs
15
15
  end
16
16
 
17
17
  attr_reader :content_item
@@ -24,19 +24,36 @@ module GovukPublishingComponents
24
24
  @taxon ||= priority_taxons.min_by { |t| PRIORITY_TAXONS.values.index(t["content_id"]) }
25
25
  end
26
26
 
27
+ def breadcrumbs
28
+ taxon && {
29
+ title: taxon["title"],
30
+ path: taxon["base_path"],
31
+ tracking_category: "breadcrumbClicked",
32
+ tracking_action: "superBreadcrumb",
33
+ tracking_label: content_item["base_path"],
34
+ tracking_dimension_enabled: false,
35
+ }
36
+ end
37
+
27
38
  private
28
39
 
29
40
  def priority_taxons
30
- taxons = content_item.dig("links", "taxons")&.map do |taxon|
31
- taxon.dig("links", "parent_taxons")&.select do |parent_taxon|
32
- PRIORITY_TAXONS.values.include?(parent_taxon["content_id"])
33
- end
41
+ return [] unless content_item["links"].is_a?(Hash)
42
+
43
+ taxons = content_item.dig("links", "taxons")
44
+ taxon_tree(taxons).select do |taxon|
45
+ priority_taxon?(taxon)
34
46
  end
35
- return [] unless taxons
47
+ end
48
+
49
+ def taxon_tree(taxons)
50
+ return [] if taxons.blank?
51
+
52
+ taxons + taxons.flat_map { |taxon| taxon_tree(taxon.dig("links", "parent_taxons")) }
53
+ end
36
54
 
37
- taxons.flatten!
38
- taxons.compact!
39
- taxons
55
+ def priority_taxon?(taxon)
56
+ PRIORITY_TAXONS.values.include?(taxon["content_id"])
40
57
  end
41
58
  end
42
59
  end
@@ -21,9 +21,7 @@ module GovukPublishingComponents
21
21
  is_page_parent: ordered_parents.empty?,
22
22
  }
23
23
 
24
- {
25
- breadcrumbs: ordered_parents.reverse,
26
- }
24
+ ordered_parents.reverse
27
25
  end
28
26
 
29
27
  private
@@ -16,32 +16,41 @@ module GovukPublishingComponents
16
16
  content_item["document_type"] == "simple_smart_answer"
17
17
  end
18
18
 
19
+ def html_publication_with_parent?
20
+ (content_item["document_type"] == "html_publication") && parent_api_path
21
+ end
22
+
23
+ def parent_api_path
24
+ parent = content_item.dig("links", "parent")&.first
25
+ parent["base_path"] if parent
26
+ end
27
+
19
28
  def taxon_breadcrumbs
20
29
  @taxon_breadcrumbs ||= ContentBreadcrumbsBasedOnTaxons.new(content_item).breadcrumbs
21
30
  end
22
31
 
23
- def priority_taxon
24
- @priority_taxon ||= ContentBreadcrumbsBasedOnPriority.call(content_item)
32
+ def priority_breadcrumbs
33
+ @priority_breadcrumbs ||= ContentBreadcrumbsBasedOnPriority.call(content_item)
25
34
  end
26
35
 
27
36
  def breadcrumbs
28
- if content_tagged_to_a_finder?
29
- parent_finder = content_item.dig("links", "finder", 0)
30
- return [] unless parent_finder
31
-
32
- [
33
- {
34
- title: "Home",
35
- url: "/",
36
- },
37
- {
38
- title: parent_finder["title"],
39
- url: parent_finder["base_path"],
40
- },
41
- ]
42
- else
43
- ContentBreadcrumbsBasedOnParent.new(content_item).breadcrumbs[:breadcrumbs]
44
- end
37
+ return breadcrumbs_based_on_parent unless content_tagged_to_a_finder?
38
+ return [] unless parent_finder
39
+
40
+ [
41
+ {
42
+ title: "Home",
43
+ url: "/",
44
+ },
45
+ {
46
+ title: parent_finder["title"],
47
+ url: parent_finder["base_path"],
48
+ },
49
+ ]
50
+ end
51
+
52
+ def use_taxon_breadcrumbs?
53
+ content_is_tagged_to_a_live_taxon? && !content_is_a_specialist_document?
45
54
  end
46
55
 
47
56
  def content_tagged_to_a_finder?
@@ -64,6 +73,10 @@ module GovukPublishingComponents
64
73
  content_item["schema_name"] == "specialist_document"
65
74
  end
66
75
 
76
+ def content_is_a_html_publication?
77
+ content_item["document_type"] == "html_publication"
78
+ end
79
+
67
80
  def tagged_to_brexit?
68
81
  taxons = content_item.dig("links", "taxons").to_a
69
82
  brexit_taxon = "d6c2de5d-ef90-45d1-82d4-5f2438369eea"
@@ -106,9 +119,17 @@ module GovukPublishingComponents
106
119
  step_nav_helper.show_also_part_of_step_nav?
107
120
  end
108
121
 
122
+ def breadcrumbs_based_on_parent
123
+ ContentBreadcrumbsBasedOnParent.new(content_item).breadcrumbs[:breadcrumbs]
124
+ end
125
+
109
126
  def step_nav_helper
110
127
  @step_nav_helper ||= PageWithStepByStepNavigation.new(content_item, request_path, query_parameters)
111
128
  end
129
+
130
+ def parent_finder
131
+ @parent_finder ||= content_item.dig("links", "finder", 0)
132
+ end
112
133
  end
113
134
  end
114
135
  end
@@ -24,9 +24,8 @@ module GovukPublishingComponents
24
24
  end
25
25
 
26
26
  def questions_and_answers_markup
27
- question_and_answers(page.body)
28
- .select { |_, value| value[:answer].present? }
29
- .map do |question, value|
27
+ question_and_answers.select { |_, value| value[:answer].present? }
28
+ .map do |question, value|
30
29
  q_and_a_url = section_url(value[:anchor])
31
30
 
32
31
  {
@@ -52,35 +51,14 @@ module GovukPublishingComponents
52
51
  #
53
52
  # - :anchor: the id of the h2 (autogenerated by the markdown converter).
54
53
  # This is used to build links directly to the section in question
55
- def question_and_answers(html)
56
- doc = Nokogiri::HTML(html)
57
-
54
+ def question_and_answers
58
55
  question = page.title
59
56
 
60
57
  doc.xpath("html/body").children.each_with_object({}) do |element, q_and_as|
61
- _q_and_as, question = recursive_question_and_answers(element, question, q_and_as)
62
- end
63
- end
64
-
65
- def recursive_question_and_answers(element, question, q_and_as)
66
- if is_a_question?(element)
67
- question = element.text
68
- q_and_as[question] = { anchor: element["id"] }
69
- else
70
- q_and_as = add_answer_to_question(q_and_as, element, question)
71
- element.children.each do |child_element|
72
- if child_element.element?
73
- q_and_as, question = recursive_question_and_answers(child_element, question, q_and_as)
74
- end
75
- end
76
- end
77
-
78
- [q_and_as, question]
79
- end
80
-
81
- def add_answer_to_question(q_and_as, element, question)
82
- if no_questions_in_subtree?(element)
83
- if question_hash_is_unset?(q_and_as, question)
58
+ if question_element?(element)
59
+ question = element.text
60
+ q_and_as[question] = { anchor: element["id"] }
61
+ elsif question_hash_is_unset?(q_and_as, question)
84
62
  q_and_as[question] = { answer: element.to_s }
85
63
  elsif answer_is_unset?(q_and_as, question)
86
64
  q_and_as[question][:answer] = element.to_s
@@ -88,11 +66,10 @@ module GovukPublishingComponents
88
66
  q_and_as[question][:answer] << element.to_s
89
67
  end
90
68
  end
91
- q_and_as
92
69
  end
93
70
 
94
- def no_questions_in_subtree?(element)
95
- element.search(QUESTION_TAG).none?
71
+ def doc
72
+ @doc ||= Nokogiri::HTML(page.body)
96
73
  end
97
74
 
98
75
  def question_hash_is_unset?(q_and_as, question)
@@ -106,7 +83,7 @@ module GovukPublishingComponents
106
83
  # we use H2 tags as the "question" and the html between them as the "answer"
107
84
  QUESTION_TAG = "h2".freeze
108
85
 
109
- def is_a_question?(element)
86
+ def question_element?(element)
110
87
  element.name == QUESTION_TAG
111
88
  end
112
89
 
@@ -0,0 +1,77 @@
1
+ module GovukPublishingComponents
2
+ module Presenters
3
+ class HtmlPublicationSchema < FaqPageSchema
4
+ attr_reader :page
5
+
6
+ def initialize(page)
7
+ @page = page
8
+ end
9
+
10
+ def structured_data
11
+ return ArticleSchema.new(page).structured_data if less_than_two_headings_of_any_one_type?
12
+
13
+ super
14
+ end
15
+
16
+ def heading_counts
17
+ @heading_counts ||= (1..5).each_with_object({}) do |n, hash|
18
+ heading = "h#{n}"
19
+ hash[heading] = doc.xpath("//*[@class=\"govspeak\"]//#{heading}").count
20
+ end
21
+ end
22
+
23
+ def less_than_two_headings_of_any_one_type?
24
+ heading_counts.values.max < 2
25
+ end
26
+
27
+ def question_and_answers
28
+ headings.each_with_object({}) do |heading, hash|
29
+ question = heading.text
30
+
31
+ next_heading = heading_pairs[heading]
32
+ next_heading_path = next_heading && next_heading.path
33
+ answer = content_between(heading.path, next_heading_path)
34
+
35
+ hash[question] = {
36
+ answer: answer.map(&:to_s).join,
37
+ anchor: heading.attr(:id),
38
+ }
39
+ end
40
+ end
41
+
42
+ def headings
43
+ @headings ||= doc.xpath("//*[@class=\"govspeak\"]//#{first_heading_type_with_more_than_one_occurance}")
44
+ end
45
+
46
+ def first_heading_type_with_more_than_one_occurance
47
+ heading_counts.detect { |_k, v| v > 1 }.first
48
+ end
49
+
50
+ def heading_pairs
51
+ @heading_pairs ||= pairs_hash(headings)
52
+ end
53
+
54
+ # Converts [:a, :b, :c] into
55
+ # {:a => :b, :b => :c}
56
+ def pairs_hash(array)
57
+ all_but_last = array[0..-2]
58
+ all_but_first = array[1..-1]
59
+ pairs = [all_but_last, all_but_first].transpose
60
+ Hash[pairs]
61
+ end
62
+
63
+ # From: https://stackoverflow.com/a/7816046/1014251
64
+ # If `stop_xpath` is `nil` gets text to end of content
65
+ def content_between(start_xpath, stop_xpath = nil)
66
+ node = doc.at_xpath(start_xpath).next_element
67
+ stop = stop_xpath && doc.at_xpath(stop_xpath)
68
+ [].tap do |content|
69
+ while node && node != stop
70
+ content << node
71
+ node = node.next_element
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end