govspeak 8.4.1 → 8.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1fee9ba67d010cc01d04d929c4995d48be18802a9cc263eb766e4fb5dd64ea04
4
- data.tar.gz: 62206016e1f736157da903662d696438da0406e497d53c6dc0a2574a0cc3555c
3
+ metadata.gz: 23ac40c67eb46b8265549568542bc48bb0cb96fe47c8b217a1e6135789ab2134
4
+ data.tar.gz: 26d7a270ab0163d056c0fefff53548727019128456deab21409ee3b3ae6e01eb
5
5
  SHA512:
6
- metadata.gz: 3aacba8ee80a9e26f59042d100d74611aef458e90fcdd9b0f5070eb780f25b19a7c28d549ad147afe0af66c802394b134430d5a0d568c2bfd051ef4e2b3582b2
7
- data.tar.gz: 1fd96e90cb895b1e48f5b678ea63258ac092058aadaff415400785be8ed2654b8dda08975e70e9e7beba8ff5d2bd564d90cca41331e3f00b6d0d0803b8e25140
6
+ metadata.gz: e637261f8a573479a054e8cdcba539ed296c4c9a1d70c263ff4caea059faf80992806332922259da0d52983013a4c302a11bed5421b12534f6d1de6b105d9620
7
+ data.tar.gz: fa04e11f539f372793d6420f9dcdc81964d244530f3d9e397f0587ca22fac9e66e41b85d79cd224c88f187946bddeb04f9afdfe223328240c353b060bb45fba2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ### 8.5.0
2
+
3
+ * Support embeds in Govspeak
4
+
1
5
  ## 8.4.1
2
6
  * Do not pin version of govuk_publishing_components
3
7
 
data/README.md CHANGED
@@ -608,6 +608,34 @@ will output
608
608
  </div>
609
609
  ```
610
610
 
611
+ ### Content blocks
612
+
613
+ Authors can embed different types of [supported content](https://github.com/alphagov/govspeak/blob/main/lib/govspeak/embedded_content.rb#L3) created by the Content Block Manager
614
+
615
+ ```
616
+ {{embed:content_block_email_address:d308f561-e5ee-45b5-90b2-3ac36a23fad9}}
617
+ ```
618
+
619
+ with options provided
620
+
621
+ ```
622
+ {
623
+ embeds: [
624
+ {
625
+ content_id: "d308f561-e5ee-45b5-90b2-3ac36a23fad9",
626
+ title: "Government Digital Service",
627
+ details: { email_address: "test@example.com" },
628
+ }
629
+ ]
630
+ }
631
+ ```
632
+
633
+ will output
634
+
635
+ ```html
636
+ <span class="embed_content_block_email_address" id="embed_d308f561-e5ee-45b5-90b2-3ac36a23fad9">test@example.com</span>
637
+ ```
638
+
611
639
  ### Button
612
640
 
613
641
  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)
@@ -0,0 +1,17 @@
1
+ module Govspeak
2
+ class EmbedExtractor
3
+ def initialize(document)
4
+ @document = document
5
+ end
6
+
7
+ def content_references
8
+ @content_references ||= @document.scan(EmbeddedContent::EMBED_REGEX).map { |match|
9
+ EmbeddedContent.new(document_type: match[1], content_id: match[2], embed_code: match[0])
10
+ }.uniq
11
+ end
12
+
13
+ def content_ids
14
+ @content_ids ||= content_references.map(&:content_id)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module Govspeak
2
+ class EmbeddedContent
3
+ SUPPORTED_DOCUMENT_TYPES = %w[contact content_block_email_address].freeze
4
+ UUID_REGEX = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/
5
+ EMBED_REGEX = /({{embed:(#{SUPPORTED_DOCUMENT_TYPES.join('|')}):#{UUID_REGEX}}})/
6
+
7
+ attr_reader :document_type, :content_id, :embed_code
8
+
9
+ def initialize(document_type:, content_id:, embed_code:)
10
+ @document_type = document_type
11
+ @content_id = content_id
12
+ @embed_code = embed_code
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ require "action_view"
2
+ require "htmlentities"
3
+
4
+ module Govspeak
5
+ class EmbedPresenter
6
+ include ActionView::Helpers::TagHelper
7
+
8
+ attr_reader :embed
9
+
10
+ def initialize(embed)
11
+ @embed = ActiveSupport::HashWithIndifferentAccess.new(embed)
12
+ end
13
+
14
+ def content_id
15
+ embed[:content_id]
16
+ end
17
+
18
+ def document_type
19
+ embed[:document_type]
20
+ end
21
+
22
+ def render
23
+ body = if document_type == "content_block_email_address"
24
+ embed.dig(:details, :email_address)
25
+ else
26
+ embed[:title]
27
+ end
28
+
29
+ content_tag(:span, body, class: "embed embed-#{document_type}", id: "embed_#{content_id}")
30
+ end
31
+ end
32
+ end
@@ -1,3 +1,3 @@
1
1
  module Govspeak
2
- VERSION = "8.4.1".freeze
2
+ VERSION = "8.5.0".freeze
3
3
  end
data/lib/govspeak.rb CHANGED
@@ -14,11 +14,14 @@ require "govspeak/structured_header_extractor"
14
14
  require "govspeak/html_validator"
15
15
  require "govspeak/html_sanitizer"
16
16
  require "govspeak/blockquote_extra_quote_remover"
17
+ require "govspeak/embed_extractor"
18
+ require "govspeak/embedded_content"
17
19
  require "govspeak/post_processor"
18
20
  require "govspeak/link_extractor"
19
21
  require "govspeak/template_renderer"
20
22
  require "govspeak/presenters/attachment_presenter"
21
23
  require "govspeak/presenters/contact_presenter"
24
+ require "govspeak/presenters/embed_presenter"
22
25
  require "govspeak/presenters/h_card_presenter"
23
26
  require "govspeak/presenters/image_presenter"
24
27
  require "govspeak/presenters/attachment_image_presenter"
@@ -37,7 +40,7 @@ module Govspeak
37
40
  @extensions = []
38
41
 
39
42
  attr_accessor :images
40
- attr_reader :attachments, :contacts, :links, :locale
43
+ attr_reader :attachments, :contacts, :links, :locale, :embeds
41
44
 
42
45
  def self.to_html(source, options = {})
43
46
  new(source, options).to_html
@@ -57,6 +60,7 @@ module Govspeak
57
60
  @attachments = Array.wrap(options.delete(:attachments))
58
61
  @links = Array.wrap(options.delete(:links))
59
62
  @contacts = Array.wrap(options.delete(:contacts))
63
+ @embeds = Array.wrap(options.delete(:embeds))
60
64
  @locale = options.fetch(:locale, "en")
61
65
  @options = { input: PARSER_CLASS_NAME,
62
66
  sanitize: true,
@@ -255,6 +259,13 @@ module Govspeak
255
259
  render_image(AttachmentImagePresenter.new(attachment))
256
260
  end
257
261
 
262
+ extension("embeds", Govspeak::EmbeddedContent::EMBED_REGEX) do |_embed_code, _document_type, content_id|
263
+ embed = embeds.detect { |e| e[:content_id] == content_id }
264
+ next "" unless embed
265
+
266
+ EmbedPresenter.new(embed).render
267
+ end
268
+
258
269
  # As of version 1.12.0 of Kramdown the block elements (div & figcaption)
259
270
  # inside this html block will have it's < > converted into HTML Entities
260
271
  # when ever this code is used inside block level elements.
@@ -0,0 +1,59 @@
1
+ require "test_helper"
2
+
3
+ class EmbedExtractorTest < Minitest::Test
4
+ extend Minitest::Spec::DSL
5
+
6
+ describe "EmbedExtractor" do
7
+ subject { Govspeak::EmbedExtractor.new(document) }
8
+
9
+ describe "when there is no embedded content" do
10
+ let(:document) { "foo" }
11
+
12
+ describe "#content_references" do
13
+ it "returns an empty array" do
14
+ assert_equal [], subject.content_references
15
+ end
16
+ end
17
+
18
+ describe "#content_ids" do
19
+ it "returns an empty array" do
20
+ assert_equal [], subject.content_ids
21
+ end
22
+ end
23
+ end
24
+
25
+ describe "when there is embedded content" do
26
+ let(:contact_uuid) { SecureRandom.uuid }
27
+ let(:content_block_email_address_uuid) { SecureRandom.uuid }
28
+
29
+ let(:document) do
30
+ """
31
+ {{embed:contact:#{contact_uuid}}}
32
+ {{embed:content_block_email_address:#{content_block_email_address_uuid}}}
33
+ """
34
+ end
35
+
36
+ describe "#content_references" do
37
+ it "returns all references" do
38
+ result = subject.content_references
39
+
40
+ assert_equal 2, result.count
41
+
42
+ assert_equal "contact", result[0].document_type
43
+ assert_equal contact_uuid, result[0].content_id
44
+ assert_equal "{{embed:contact:#{contact_uuid}}}", result[0].embed_code
45
+
46
+ assert_equal "content_block_email_address", result[1].document_type
47
+ assert_equal content_block_email_address_uuid, result[1].content_id
48
+ assert_equal "{{embed:content_block_email_address:#{content_block_email_address_uuid}}}", result[1].embed_code
49
+ end
50
+ end
51
+
52
+ describe "#content_ids" do
53
+ it "returns all uuids as an array" do
54
+ assert_equal [contact_uuid, content_block_email_address_uuid], subject.content_ids
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,84 @@
1
+ require "test_helper"
2
+
3
+ class GovspeakEmbedsTest < Minitest::Test
4
+ extend Minitest::Spec::DSL
5
+
6
+ def compress_html(html)
7
+ html.gsub(/[\n\r]+\s*/, "")
8
+ end
9
+
10
+ let(:content_id) { SecureRandom.uuid }
11
+
12
+ it "renders an email address when present in options[:embeds]" do
13
+ embed = {
14
+ content_id:,
15
+ document_type: "content_block_email_address",
16
+ title: "foo",
17
+ details: {
18
+ email_address: "foo@example.com",
19
+ },
20
+ }
21
+ govspeak = "{{embed:content_block_email_address:#{content_id}}}"
22
+
23
+ rendered = Govspeak::Document.new(govspeak, embeds: [embed]).to_html
24
+
25
+ expected = "<p><span class=\"embed embed-content_block_email_address\" id=\"embed_#{content_id}\">#{embed[:details][:email_address]}</span></p>"
26
+
27
+ assert_equal compress_html(expected), compress_html(rendered)
28
+ end
29
+
30
+ it "renders the title when the document type is a contact" do
31
+ embed = {
32
+ content_id:,
33
+ document_type: "contact",
34
+ title: "foo",
35
+ }
36
+ govspeak = "{{embed:contact:#{content_id}}}"
37
+
38
+ rendered = Govspeak::Document.new(govspeak, embeds: [embed]).to_html
39
+
40
+ expected = "<p><span class=\"embed embed-contact\" id=\"embed_#{content_id}\">#{embed[:title]}</span></p>"
41
+
42
+ assert_equal compress_html(expected), compress_html(rendered)
43
+ end
44
+
45
+ it "ignores missing embeds" do
46
+ govspeak = "{{embed:contact:#{content_id}}}"
47
+
48
+ rendered = Govspeak::Document.new(govspeak, embeds: []).to_html
49
+
50
+ assert_equal compress_html(""), compress_html(rendered)
51
+ end
52
+
53
+ it "supports multiple embeds" do
54
+ embeds = [
55
+ {
56
+ content_id: SecureRandom.uuid,
57
+ document_type: "contact",
58
+ title: "foo",
59
+ },
60
+ {
61
+ content_id: SecureRandom.uuid,
62
+ document_type: "content_block_email_address",
63
+ title: "foo",
64
+ details: {
65
+ email_address: "foo@example.com",
66
+ },
67
+ },
68
+ ]
69
+
70
+ govspeak = %(Here is a contact: {{embed:contact:#{embeds[0][:content_id]}}}
71
+
72
+ Here is an email address: {{embed:content_block_email_address:#{embeds[1][:content_id]}}}
73
+ )
74
+
75
+ rendered = Govspeak::Document.new(govspeak, embeds:).to_html
76
+
77
+ expected = """
78
+ <p>Here is a contact: <span class=\"embed embed-contact\" id=\"embed_#{embeds[0][:content_id]}\">#{embeds[0][:title]}</span></p>
79
+ <p>Here is an email address: <span class=\"embed embed-content_block_email_address\" id=\"embed_#{embeds[1][:content_id]}\">#{embeds[1][:details][:email_address]}</span></p>
80
+ """
81
+
82
+ assert_equal compress_html(expected), compress_html(rendered)
83
+ end
84
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: govspeak
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.4.1
4
+ version: 8.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GOV.UK Dev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-10 00:00:00.000000000 Z
11
+ date: 2024-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionview
@@ -250,6 +250,8 @@ files:
250
250
  - config/address_formats.yml
251
251
  - lib/govspeak.rb
252
252
  - lib/govspeak/blockquote_extra_quote_remover.rb
253
+ - lib/govspeak/embed_extractor.rb
254
+ - lib/govspeak/embedded_content.rb
253
255
  - lib/govspeak/header_extractor.rb
254
256
  - lib/govspeak/html_sanitizer.rb
255
257
  - lib/govspeak/html_validator.rb
@@ -258,6 +260,7 @@ files:
258
260
  - lib/govspeak/presenters/attachment_image_presenter.rb
259
261
  - lib/govspeak/presenters/attachment_presenter.rb
260
262
  - lib/govspeak/presenters/contact_presenter.rb
263
+ - lib/govspeak/presenters/embed_presenter.rb
261
264
  - lib/govspeak/presenters/h_card_presenter.rb
262
265
  - lib/govspeak/presenters/image_presenter.rb
263
266
  - lib/govspeak/structured_header_extractor.rb
@@ -306,12 +309,14 @@ files:
306
309
  - locales/zh-tw.yml
307
310
  - locales/zh.yml
308
311
  - test/blockquote_extra_quote_remover_test.rb
312
+ - test/embed_extractor_test.rb
309
313
  - test/govspeak_attachment_link_test.rb
310
314
  - test/govspeak_attachment_test.rb
311
315
  - test/govspeak_attachments_image_test.rb
312
316
  - test/govspeak_attachments_inline_test.rb
313
317
  - test/govspeak_button_test.rb
314
318
  - test/govspeak_contacts_test.rb
319
+ - test/govspeak_embeds_test.rb
315
320
  - test/govspeak_extract_contact_content_ids_test.rb
316
321
  - test/govspeak_footnote_test.rb
317
322
  - test/govspeak_images_bang_test.rb
@@ -344,18 +349,20 @@ required_rubygems_version: !ruby/object:Gem::Requirement
344
349
  - !ruby/object:Gem::Version
345
350
  version: '0'
346
351
  requirements: []
347
- rubygems_version: 3.5.21
352
+ rubygems_version: 3.5.22
348
353
  signing_key:
349
354
  specification_version: 4
350
355
  summary: Markup language for single domain
351
356
  test_files:
352
357
  - test/blockquote_extra_quote_remover_test.rb
358
+ - test/embed_extractor_test.rb
353
359
  - test/govspeak_attachment_link_test.rb
354
360
  - test/govspeak_attachment_test.rb
355
361
  - test/govspeak_attachments_image_test.rb
356
362
  - test/govspeak_attachments_inline_test.rb
357
363
  - test/govspeak_button_test.rb
358
364
  - test/govspeak_contacts_test.rb
365
+ - test/govspeak_embeds_test.rb
359
366
  - test/govspeak_extract_contact_content_ids_test.rb
360
367
  - test/govspeak_footnote_test.rb
361
368
  - test/govspeak_images_bang_test.rb