govspeak 6.4.0 → 6.5.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fbff4078955867159e914ac1dd26310ab9295a0618a58529b42b64c7e55399fd
4
- data.tar.gz: ce83e999fab5cec2919c21b4561cf06a26c42056dde7c1740b04ca7b1df9d1df
3
+ metadata.gz: a09b3928e9718aa64cd781681dbc8ac51d8534ddc27b26f54e8416b21f60e501
4
+ data.tar.gz: f8b6da3c1bf3c3b4c7792263853837886b42f7dc6abe6346d81289695ea7986d
5
5
  SHA512:
6
- metadata.gz: 1972d6f7430d74f570dd23e9e529e581460fb02b535c834abada9c1c14b4d59914ddfeedec8c6209e6992a303c44211c0759fd9592ab5e9be42b776824d5efcb
7
- data.tar.gz: 139c744c3c51456f6ceac2b75f1cba186cdca54cf518b06b36768bd675560e00f9c95306228b076adadd6f08c6c2ad505447df42e5cce52095fc874bc4eeff21
6
+ metadata.gz: 9de09348513cfd10e76a9960bfe8012213a99447244cf9d89a2e3da8cb18f93b28e8b63dd63304de06c51a6e3b9e82bc5b1f2de43ff531dfb23f4832d1a18e0b
7
+ data.tar.gz: 0b464f824b1ff7f8ee77619d2dd1195a22c8777b79b675d9e7fbb1b88011075552f496b294e26d9b65908f2a26031de30ff93c1a12ba3a9743786a4219cfb2de
@@ -1,8 +1,30 @@
1
+ ## 6.5.4
2
+
3
+ * Require Sanitize to be at least 5.2.1 due to https://nvd.nist.gov/vuln/detail/CVE-2020-4054
4
+
5
+ ## 6.5.3
6
+
7
+ * Use button component for buttons (PR#176)
8
+
9
+ ## 6.5.2
10
+
11
+ * Allow `data` attributes on `div` tags (PR#173)
12
+
13
+ ## 6.5.1
14
+
15
+ * Change unicode testing characters after external gem change
16
+ * Move from govuk-lint to rubocop-govuk
17
+ * Allow version 6 of actionview
18
+
19
+ ## 6.5.0
20
+
21
+ * Allow data attributes on links
22
+
1
23
  ## 6.4.0
2
24
 
3
- * Add table heading syntax that allows a table cell outside of `thead` to be marked as a table heading with a scope of row. (PR#161)
25
+ * Add table heading syntax that allows a table cell outside of `thead` to be marked as a table heading with a scope of row. (PR#161)
4
26
 
5
- ## 6.3.0
27
+ ## 6.3.0
6
28
 
7
29
  * Unicode characters forbidden in HTML are stripped from input
8
30
  * Validation is now more lenient for HTML input
data/README.md CHANGED
@@ -619,8 +619,7 @@ will output
619
619
 
620
620
  An accessible way to add button links into content, that can also allow cross domain tracking with [Google Analytics](https://support.google.com/analytics/answer/7372977?hl=en)
621
621
 
622
- This button component is [extended from static](http://govuk-static.herokuapp.com/component-guide/button) for [use in govspeak](http://govuk-static.herokuapp.com/component-guide/govspeak/button)
623
- Note: Ideally we'd use the original component directly but this currently isnt possible
622
+ This button component uses the component from the [components gem](https://components.publishing.service.gov.uk/component-guide/button) for use in govspeak.
624
623
 
625
624
  You must use the [link](https://daringfireball.net/projects/markdown/syntax#link) syntax within the button tags.
626
625
 
@@ -635,7 +634,7 @@ To get the most basic button.
635
634
  which outputs
636
635
 
637
636
  ```html
638
- <a role="button" class="button" href="https://gov.uk/random">
637
+ <a role="button" class="gem-c-button govuk-button" href="https://gov.uk/random">
639
638
  Continue
640
639
  </a>
641
640
  ```
@@ -649,8 +648,9 @@ To turn a button into a ['Start now' button](https://www.gov.uk/service-manual/d
649
648
  which outputs
650
649
 
651
650
  ```html
652
- <a role="button" class="button button-start" href="https://gov.uk/random">
651
+ <a role="button" class="gem-c-button govuk-button govuk-button--start" href="https://gov.uk/random">
653
652
  Start Now
653
+ <svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewBox="0 0 33 40" role="presentation" focusable="false"><path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path></svg>
654
654
  </a>
655
655
  ```
656
656
 
@@ -667,12 +667,13 @@ which outputs
667
667
  ```html
668
668
  <a
669
669
  role="button"
670
- class="button button-start"
670
+ class="gem-c-button govuk-button govuk-button--start"
671
671
  href="https://example.com/external-service/start-now"
672
672
  data-module="cross-domain-tracking"
673
673
  data-tracking-code="UA-XXXXXX-Y"
674
674
  data-tracking-name="govspeakButtonTracker"
675
675
  >
676
676
  Start Now
677
+ <svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewBox="0 0 33 40" role="presentation" focusable="false"><path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path></svg>
677
678
  </a>
678
679
  ```
data/Rakefile CHANGED
@@ -7,7 +7,7 @@ Bundler::GemHelper.install_tasks
7
7
  desc "Run basic tests"
8
8
  Rake::TestTask.new("test") { |t|
9
9
  t.libs << "test"
10
- t.pattern = 'test/*_test.rb'
10
+ t.pattern = "test/*_test.rb"
11
11
  t.verbose = true
12
12
  t.warning = true
13
13
  }
@@ -1,32 +1,32 @@
1
- require 'active_support/core_ext/hash'
2
- require 'active_support/core_ext/array'
3
- require 'erb'
4
- require 'govuk_publishing_components'
5
- require 'htmlentities'
6
- require 'kramdown'
7
- require 'kramdown/parser/govuk'
8
- require 'nokogiri'
9
- require 'nokogumbo'
10
- require 'rinku'
11
- require 'sanitize'
12
- require 'govspeak/header_extractor'
13
- require 'govspeak/structured_header_extractor'
14
- require 'govspeak/html_validator'
15
- require 'govspeak/html_sanitizer'
16
- require 'govspeak/kramdown_overrides'
17
- require 'govspeak/blockquote_extra_quote_remover'
18
- require 'govspeak/post_processor'
19
- require 'govspeak/link_extractor'
20
- require 'govspeak/template_renderer'
21
- require 'govspeak/presenters/attachment_presenter'
22
- require 'govspeak/presenters/contact_presenter'
23
- require 'govspeak/presenters/h_card_presenter'
24
- require 'govspeak/presenters/image_presenter'
25
- require 'govspeak/presenters/attachment_image_presenter'
1
+ require "active_support/core_ext/hash"
2
+ require "active_support/core_ext/array"
3
+ require "erb"
4
+ require "govuk_publishing_components"
5
+ require "htmlentities"
6
+ require "kramdown"
7
+ require "kramdown/parser/govuk"
8
+ require "nokogiri"
9
+ require "nokogumbo"
10
+ require "rinku"
11
+ require "sanitize"
12
+ require "govspeak/header_extractor"
13
+ require "govspeak/structured_header_extractor"
14
+ require "govspeak/html_validator"
15
+ require "govspeak/html_sanitizer"
16
+ require "govspeak/kramdown_overrides"
17
+ require "govspeak/blockquote_extra_quote_remover"
18
+ require "govspeak/post_processor"
19
+ require "govspeak/link_extractor"
20
+ require "govspeak/template_renderer"
21
+ require "govspeak/presenters/attachment_presenter"
22
+ require "govspeak/presenters/contact_presenter"
23
+ require "govspeak/presenters/h_card_presenter"
24
+ require "govspeak/presenters/image_presenter"
25
+ require "govspeak/presenters/attachment_image_presenter"
26
26
 
27
27
  module Govspeak
28
28
  def self.root
29
- File.expand_path('..', File.dirname(__FILE__))
29
+ File.expand_path("..", File.dirname(__FILE__))
30
30
  end
31
31
 
32
32
  class Document
@@ -44,8 +44,8 @@ module Govspeak
44
44
  new(source, options).to_html
45
45
  end
46
46
 
47
- def self.extensions
48
- @extensions
47
+ class << self
48
+ attr_reader :extensions
49
49
  end
50
50
 
51
51
  def initialize(source, options = {})
@@ -118,7 +118,7 @@ module Govspeak
118
118
  def remove_forbidden_characters(source)
119
119
  # These are characters that are not deemed not suitable for
120
120
  # markup: https://www.w3.org/TR/unicode-xml/#Charlist
121
- source.gsub(Sanitize::REGEX_UNSUITABLE_CHARS, '')
121
+ source.gsub(Sanitize::REGEX_UNSUITABLE_CHARS, "")
122
122
  end
123
123
 
124
124
  def self.extension(title, regexp = nil, &block)
@@ -147,7 +147,7 @@ module Govspeak
147
147
  parser.new(body.strip).to_html.sub(/^<p>(.*)<\/p>$/, "<p><strong>\\1</strong></p>")
148
148
  end
149
149
 
150
- extension('button', %r{
150
+ extension("button", %r{
151
151
  (?:\r|\n|^) # non-capturing match to make sure start of line and linebreak
152
152
  {button(.*?)} # match opening bracket and capture attributes
153
153
  \s* # any whitespace between opening bracket and link
@@ -161,10 +161,10 @@ module Govspeak
161
161
  {\/button} # match ending bracket
162
162
  (?:\r|\n|$) # non-capturing match to make sure end of line and linebreak
163
163
  }x) { |attributes, text, href|
164
- button_classes = "button"
165
- button_classes << " button-start" if attributes =~ /start/
164
+ button_classes = "govuk-button"
166
165
  /cross-domain-tracking:(?<cross_domain_tracking>.[^\s*]+)/ =~ attributes
167
166
  data_attribute = ""
167
+ data_attribute << " data-start='true'" if attributes =~ /start/
168
168
  if cross_domain_tracking
169
169
  data_attribute << " data-module='cross-domain-tracking'"
170
170
  data_attribute << " data-tracking-code='#{cross_domain_tracking.strip}'"
@@ -176,51 +176,51 @@ module Govspeak
176
176
  %{\n<a role="button" class="#{button_classes}" href="#{href}" #{data_attribute}>#{text}</a>\n}
177
177
  }
178
178
 
179
- extension('highlight-answer') { |body|
179
+ extension("highlight-answer") { |body|
180
180
  %{\n\n<div class="highlight-answer">
181
181
  #{Govspeak::Document.new(body.strip).to_html}</div>\n}
182
182
  }
183
183
 
184
- extension('stat-headline', %r${stat-headline}(.*?){/stat-headline}$m) { |body|
184
+ extension("stat-headline", %r${stat-headline}(.*?){/stat-headline}$m) { |body|
185
185
  %{\n\n<aside class="stat-headline">
186
186
  #{Govspeak::Document.new(body.strip).to_html}</aside>\n}
187
187
  }
188
188
 
189
189
  # FIXME: these surrounded_by arguments look dodgy
190
- extension('external', surrounded_by("x[", ")x")) { |body|
190
+ extension("external", surrounded_by("x[", ")x")) { |body|
191
191
  Kramdown::Document.new("[#{body.strip}){:rel='external'}").to_html
192
192
  }
193
193
 
194
- extension('informational', surrounded_by("^")) { |body|
194
+ extension("informational", surrounded_by("^")) { |body|
195
195
  %{\n\n<div role="note" aria-label="Information" class="application-notice info-notice">
196
196
  #{Govspeak::Document.new(body.strip).to_html}</div>\n}
197
197
  }
198
198
 
199
- extension('important', surrounded_by("@")) { |body|
199
+ extension("important", surrounded_by("@")) { |body|
200
200
  %{\n\n<div role="note" aria-label="Important" class="advisory">#{insert_strong_inside_p(body)}</div>\n}
201
201
  }
202
202
 
203
- extension('helpful', surrounded_by("%")) { |body|
203
+ extension("helpful", surrounded_by("%")) { |body|
204
204
  %{\n\n<div role="note" aria-label="Warning" class="application-notice help-notice">\n#{Govspeak::Document.new(body.strip).to_html}</div>\n}
205
205
  }
206
206
 
207
- extension('barchart', /{barchart(.*?)}/) do |captures|
208
- stacked = '.mc-stacked' if captures.include? 'stacked'
209
- compact = '.compact' if captures.include? 'compact'
210
- negative = '.mc-negative' if captures.include? 'negative'
207
+ extension("barchart", /{barchart(.*?)}/) do |captures|
208
+ stacked = ".mc-stacked" if captures.include? "stacked"
209
+ compact = ".compact" if captures.include? "compact"
210
+ negative = ".mc-negative" if captures.include? "negative"
211
211
 
212
212
  [
213
- '{:',
214
- '.js-barchart-table',
213
+ "{:",
214
+ ".js-barchart-table",
215
215
  stacked,
216
216
  compact,
217
217
  negative,
218
- '.mc-auto-outdent',
219
- '}'
220
- ].join(' ')
218
+ ".mc-auto-outdent",
219
+ "}",
220
+ ].join(" ")
221
221
  end
222
222
 
223
- extension('attached-image', /^!!([0-9]+)/) do |image_number|
223
+ extension("attached-image", /^!!([0-9]+)/) do |image_number|
224
224
  image = images[image_number.to_i - 1]
225
225
  next "" unless image
226
226
 
@@ -228,7 +228,7 @@ module Govspeak
228
228
  end
229
229
 
230
230
  # DEPRECATED: use 'AttachmentLink:attachment-id' instead
231
- extension('embed attachment inline', /\[embed:attachments:inline:\s*(.*?)\s*\]/) do |content_id|
231
+ extension("embed attachment inline", /\[embed:attachments:inline:\s*(.*?)\s*\]/) do |content_id|
232
232
  attachment = attachments.detect { |a| a[:content_id] == content_id }
233
233
  next "" unless attachment
234
234
 
@@ -243,7 +243,7 @@ module Govspeak
243
243
  end
244
244
 
245
245
  # DEPRECATED: use 'Image:image-id' instead
246
- extension('attachment image', /\[embed:attachments:image:\s*(.*?)\s*\]/) do |content_id|
246
+ extension("attachment image", /\[embed:attachments:image:\s*(.*?)\s*\]/) do |content_id|
247
247
  attachment = attachments.detect { |a| a[:content_id] == content_id }
248
248
  next "" unless attachment
249
249
 
@@ -266,29 +266,29 @@ module Govspeak
266
266
  lines << %{<figure#{id_attr} class="image embedded">}
267
267
  lines << %{<div class="img"><img src="#{encode(image.url)}" alt="#{encode(image.alt_text)}"></div>}
268
268
  lines << image.figcaption_html if image.figcaption?
269
- lines << '</figure>'
269
+ lines << "</figure>"
270
270
  lines.join
271
271
  end
272
272
 
273
- wrap_with_div('summary', '$!')
274
- wrap_with_div('form-download', '$D')
275
- wrap_with_div('contact', '$C')
276
- wrap_with_div('place', '$P', Govspeak::Document)
277
- wrap_with_div('information', '$I', Govspeak::Document)
278
- wrap_with_div('additional-information', '$AI')
279
- wrap_with_div('example', '$E', Govspeak::Document)
280
- wrap_with_div('call-to-action', '$CTA', Govspeak::Document)
273
+ wrap_with_div("summary", "$!")
274
+ wrap_with_div("form-download", "$D")
275
+ wrap_with_div("contact", "$C")
276
+ wrap_with_div("place", "$P", Govspeak::Document)
277
+ wrap_with_div("information", "$I", Govspeak::Document)
278
+ wrap_with_div("additional-information", "$AI")
279
+ wrap_with_div("example", "$E", Govspeak::Document)
280
+ wrap_with_div("call-to-action", "$CTA", Govspeak::Document)
281
281
 
282
- extension('address', surrounded_by("$A")) { |body|
282
+ extension("address", surrounded_by("$A")) { |body|
283
283
  %{\n<div class="address"><div class="adr org fn"><p>\n#{body.sub("\n", '').gsub("\n", '<br />')}\n</p></div></div>\n}
284
284
  }
285
285
 
286
286
  extension("legislative list", /#{NEW_PARAGRAPH_LOOKBEHIND}\$LegislativeList\s*$(.*?)\$EndLegislativeList/m) do |body|
287
287
  Govspeak::KramdownOverrides.with_kramdown_ordered_lists_disabled do
288
288
  Kramdown::Document.new(body.strip).to_html.tap do |doc|
289
- doc.gsub!('<ul>', '<ol>')
290
- doc.gsub!('</ul>', '</ol>')
291
- doc.sub!('<ol>', '<ol class="legislative-list">')
289
+ doc.gsub!("<ul>", "<ol>")
290
+ doc.gsub!("</ul>", "</ol>")
291
+ doc.sub!("<ol>", '<ol class="legislative-list">')
292
292
  end
293
293
  end
294
294
  end
@@ -301,12 +301,12 @@ module Govspeak
301
301
  end
302
302
 
303
303
  def self.devolved_options
304
- { 'scotland' => 'Scotland',
305
- 'england' => 'England',
306
- 'england-wales' => 'England and Wales',
307
- 'northern-ireland' => 'Northern Ireland',
308
- 'wales' => 'Wales',
309
- 'london' => 'London' }
304
+ { "scotland" => "Scotland",
305
+ "england" => "England",
306
+ "england-wales" => "England and Wales",
307
+ "northern-ireland" => "Northern Ireland",
308
+ "wales" => "Wales",
309
+ "london" => "London" }
310
310
  end
311
311
 
312
312
  devolved_options.each do |k, v|
@@ -333,7 +333,7 @@ module Govspeak
333
333
  end
334
334
  end
335
335
 
336
- extension('embed link', /\[embed:link:\s*(.*?)\s*\]/) do |content_id|
336
+ extension("embed link", /\[embed:link:\s*(.*?)\s*\]/) do |content_id|
337
337
  link = links.detect { |l| l[:content_id] == content_id }
338
338
  next "" unless link
339
339
 
@@ -344,28 +344,28 @@ module Govspeak
344
344
  end
345
345
  end
346
346
 
347
- extension('Contact', /\[Contact:\s*(.*?)\s*\]/) do |content_id|
347
+ extension("Contact", /\[Contact:\s*(.*?)\s*\]/) do |content_id|
348
348
  contact = contacts.detect { |c| c[:content_id] == content_id }
349
349
  next "" unless contact
350
350
 
351
- renderer = TemplateRenderer.new('contact.html.erb', locale)
351
+ renderer = TemplateRenderer.new("contact.html.erb", locale)
352
352
  renderer.render(contact: ContactPresenter.new(contact))
353
353
  end
354
354
 
355
- extension('Image', /#{NEW_PARAGRAPH_LOOKBEHIND}\[Image:\s*(.*?)\s*\]/) do |image_id|
355
+ extension("Image", /#{NEW_PARAGRAPH_LOOKBEHIND}\[Image:\s*(.*?)\s*\]/) do |image_id|
356
356
  image = images.detect { |c| c.is_a?(Hash) && c[:id] == image_id }
357
357
  next "" unless image
358
358
 
359
359
  render_image(ImagePresenter.new(image))
360
360
  end
361
361
 
362
- extension('Attachment', /#{NEW_PARAGRAPH_LOOKBEHIND}\[Attachment:\s*(.*?)\s*\]/) do |attachment_id|
362
+ extension("Attachment", /#{NEW_PARAGRAPH_LOOKBEHIND}\[Attachment:\s*(.*?)\s*\]/) do |attachment_id|
363
363
  next "" if attachments.none? { |a| a[:id] == attachment_id }
364
364
 
365
365
  %{<govspeak-embed-attachment id="#{attachment_id}"></govspeak-embed-attachment>}
366
366
  end
367
367
 
368
- extension('AttachmentLink', /\[AttachmentLink:\s*(.*?)\s*\]/) do |attachment_id|
368
+ extension("AttachmentLink", /\[AttachmentLink:\s*(.*?)\s*\]/) do |attachment_id|
369
369
  next "" if attachments.none? { |a| a[:id] == attachment_id }
370
370
 
371
371
  %{<govspeak-embed-attachment-link id="#{attachment_id}"></govspeak-embed-attachment-link>}
@@ -384,5 +384,5 @@ module Govspeak
384
384
  end
385
385
 
386
386
  I18n.load_path.unshift(
387
- *Dir.glob(File.expand_path('locales/*.yml', Govspeak.root))
387
+ *Dir.glob(File.expand_path("locales/*.yml", Govspeak.root)),
388
388
  )
@@ -20,7 +20,7 @@ module Govspeak
20
20
  private
21
21
 
22
22
  def id(element)
23
- element.attr.fetch('id', generate_id(element.options[:raw_text]))
23
+ element.attr.fetch("id", generate_id(element.options[:raw_text]))
24
24
  end
25
25
 
26
26
  def build_header(element)
@@ -1,4 +1,4 @@
1
- require 'addressable/uri'
1
+ require "addressable/uri"
2
2
 
3
3
  class Govspeak::HtmlSanitizer
4
4
  class ImageSourceWhitelister
@@ -10,7 +10,7 @@ class Govspeak::HtmlSanitizer
10
10
  return unless sanitize_context[:node_name] == "img"
11
11
 
12
12
  node = sanitize_context[:node]
13
- image_uri = Addressable::URI.parse(node['src'])
13
+ image_uri = Addressable::URI.parse(node["src"])
14
14
  unless image_uri.relative? || @allowed_image_hosts.include?(image_uri.host)
15
15
  node.unlink # the node isn't sanitary. Remove it from the document.
16
16
  end
@@ -25,8 +25,8 @@ class Govspeak::HtmlSanitizer
25
25
 
26
26
  # Kramdown uses text-align to allow table cells to be aligned
27
27
  # http://kramdown.gettalong.org/quickref.html#tables
28
- if invalid_style_attribute?(node['style'])
29
- node.remove_attribute('style')
28
+ if invalid_style_attribute?(node["style"])
29
+ node.remove_attribute("style")
30
30
  end
31
31
  end
32
32
 
@@ -48,25 +48,20 @@ class Govspeak::HtmlSanitizer
48
48
  Sanitize.clean(@dirty_html, Sanitize::Config.merge(sanitize_config, transformers: transformers))
49
49
  end
50
50
 
51
- def button_sanitize_config
52
- %w[
53
- data-module
54
- data-tracking-code
55
- data-tracking-name
56
- ]
57
- end
58
-
59
51
  def sanitize_config
60
52
  Sanitize::Config.merge(
61
53
  Sanitize::Config::RELAXED,
62
- elements: Sanitize::Config::RELAXED[:elements] + %w[govspeak-embed-attachment govspeak-embed-attachment-link],
54
+ elements: Sanitize::Config::RELAXED[:elements] + %w[govspeak-embed-attachment govspeak-embed-attachment-link svg path],
63
55
  attributes: {
64
56
  :all => Sanitize::Config::RELAXED[:attributes][:all] + %w[role aria-label],
65
- "a" => Sanitize::Config::RELAXED[:attributes]["a"] + button_sanitize_config,
57
+ "a" => Sanitize::Config::RELAXED[:attributes]["a"] + [:data],
58
+ "svg" => Sanitize::Config::RELAXED[:attributes][:all] + %w[xmlns width height viewbox focusable],
59
+ "path" => Sanitize::Config::RELAXED[:attributes][:all] + %w[fill d],
60
+ "div" => [:data],
66
61
  "th" => Sanitize::Config::RELAXED[:attributes]["th"] + %w[style],
67
62
  "td" => Sanitize::Config::RELAXED[:attributes]["td"] + %w[style],
68
63
  "govspeak-embed-attachment" => %w[content-id],
69
- }
64
+ },
70
65
  )
71
66
  end
72
67
  end