content_block_tools 1.11.0 → 1.12.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 +4 -4
- data/app/components/content_block_tools/time_period/long_form_component.html.erb +3 -0
- data/app/components/content_block_tools/time_period/long_form_component.rb +24 -0
- data/app/components/content_block_tools/time_period/months_and_years_long_component.html.erb +3 -0
- data/app/components/content_block_tools/time_period/months_and_years_long_component.rb +24 -0
- data/app/components/content_block_tools/time_period/start_day_and_month_component.html.erb +3 -0
- data/app/components/content_block_tools/time_period/start_day_and_month_component.rb +23 -0
- data/app/components/content_block_tools/time_period/start_month_as_word_component.html.erb +3 -0
- data/app/components/content_block_tools/time_period/start_month_as_word_component.rb +23 -0
- data/app/components/content_block_tools/time_period/years_component.html.erb +3 -0
- data/app/components/content_block_tools/time_period/years_component.rb +24 -0
- data/app/components/content_block_tools/time_period/years_short_component.html.erb +3 -0
- data/app/components/content_block_tools/time_period/years_short_component.rb +24 -0
- data/app/components/content_block_tools/time_period_component.rb +49 -11
- data/lib/content_block_tools/content_block.rb +12 -81
- data/lib/content_block_tools/content_block_reference.rb +19 -15
- data/lib/content_block_tools/embed_code.rb +77 -0
- data/lib/content_block_tools/format.rb +5 -0
- data/lib/content_block_tools/internal_content_path.rb +49 -0
- data/lib/content_block_tools/normalised_date_range.rb +3 -4
- data/lib/content_block_tools/renderer.rb +107 -0
- data/lib/content_block_tools/version.rb +1 -1
- data/lib/content_block_tools.rb +6 -0
- metadata +50 -7
- data/app/components/content_block_tools/time_period_component.html.erb +0 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fc0f688a1e6de53fbecdc450c06f71d61a187030db24579d6b0251d21bb1fbcf
|
|
4
|
+
data.tar.gz: 46ee44026a45e846cbd033e463356186cc359476fdc8e2cf341da586255e26e5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 38288988c98837d354dfe65fcdb51845d58aef43abc48b38c8c159737baa6c98313b1a90f2f56c7641e7fd77763e2d720d3f4ff856c6102fa5948a9fe294c1b8
|
|
7
|
+
data.tar.gz: 9a1b9b2eeea4eff19d44144e12c1abb4a1711529020eb9a287a5f7047e96d1fd61717693f0077607a3de5b1e5eca4976762e2a854963d31bedcad0c9535c08d0
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module ContentBlockTools
|
|
2
|
+
module TimePeriod
|
|
3
|
+
class LongFormComponent < ContentBlockTools::BaseComponent
|
|
4
|
+
def initialize(start_date:, end_date:)
|
|
5
|
+
@start_date = start_date
|
|
6
|
+
@end_date = end_date
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def render
|
|
10
|
+
return "" unless start_date && end_date
|
|
11
|
+
|
|
12
|
+
render_in(view_context)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
attr_reader :start_date, :end_date
|
|
18
|
+
|
|
19
|
+
def formatted_date(date)
|
|
20
|
+
Presenters::FieldPresenters::TimePeriod::DatePresenter.new(date).render
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module ContentBlockTools
|
|
2
|
+
module TimePeriod
|
|
3
|
+
class MonthsAndYearsLongComponent < ContentBlockTools::BaseComponent
|
|
4
|
+
def initialize(start_date:, end_date:)
|
|
5
|
+
@start_date = start_date
|
|
6
|
+
@end_date = end_date
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def render
|
|
10
|
+
return "" unless start_date && end_date
|
|
11
|
+
|
|
12
|
+
render_in(view_context)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
attr_reader :start_date, :end_date
|
|
18
|
+
|
|
19
|
+
def formatted_date(date)
|
|
20
|
+
date.strftime("%B %Y")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module ContentBlockTools
|
|
2
|
+
module TimePeriod
|
|
3
|
+
class StartDayAndMonthComponent < ContentBlockTools::BaseComponent
|
|
4
|
+
def initialize(start_date:)
|
|
5
|
+
@start_date = start_date
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def render
|
|
9
|
+
return "" unless start_date
|
|
10
|
+
|
|
11
|
+
render_in(view_context)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
attr_reader :start_date
|
|
17
|
+
|
|
18
|
+
def formatted_date
|
|
19
|
+
start_date.strftime("%e %B").strip
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module ContentBlockTools
|
|
2
|
+
module TimePeriod
|
|
3
|
+
class StartMonthAsWordComponent < ContentBlockTools::BaseComponent
|
|
4
|
+
def initialize(start_date:)
|
|
5
|
+
@start_date = start_date
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def render
|
|
9
|
+
return "" unless start_date
|
|
10
|
+
|
|
11
|
+
render_in(view_context)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
attr_reader :start_date
|
|
17
|
+
|
|
18
|
+
def formatted_date
|
|
19
|
+
start_date.strftime("%B")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module ContentBlockTools
|
|
2
|
+
module TimePeriod
|
|
3
|
+
class YearsComponent < ContentBlockTools::BaseComponent
|
|
4
|
+
def initialize(start_date:, end_date:)
|
|
5
|
+
@start_date = start_date
|
|
6
|
+
@end_date = end_date
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def render
|
|
10
|
+
return "" unless start_date && end_date
|
|
11
|
+
|
|
12
|
+
render_in(view_context)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
attr_reader :start_date, :end_date
|
|
18
|
+
|
|
19
|
+
def formatted_years
|
|
20
|
+
"#{start_date.year}-#{end_date.year}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module ContentBlockTools
|
|
2
|
+
module TimePeriod
|
|
3
|
+
class YearsShortComponent < ContentBlockTools::BaseComponent
|
|
4
|
+
def initialize(start_date:, end_date:)
|
|
5
|
+
@start_date = start_date
|
|
6
|
+
@end_date = end_date
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def render
|
|
10
|
+
return "" unless start_date && end_date
|
|
11
|
+
|
|
12
|
+
render_in(view_context)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
attr_reader :start_date, :end_date
|
|
18
|
+
|
|
19
|
+
def formatted_years
|
|
20
|
+
"#{start_date.year}-#{end_date.strftime('%y')}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -1,32 +1,70 @@
|
|
|
1
1
|
module ContentBlockTools
|
|
2
2
|
class TimePeriodComponent < ContentBlockTools::BaseComponent
|
|
3
|
+
SUPPORTED_FORMATS = %w[
|
|
4
|
+
default
|
|
5
|
+
long_form
|
|
6
|
+
months_and_years_long
|
|
7
|
+
start_day_and_month
|
|
8
|
+
start_month_as_word
|
|
9
|
+
years
|
|
10
|
+
years_short
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
3
13
|
def initialize(content_block:, _block_type: nil, _block_name: nil)
|
|
4
14
|
@content_block = content_block
|
|
5
15
|
@normalised_date_range = normalise_date_range
|
|
16
|
+
validate_format!
|
|
6
17
|
end
|
|
7
18
|
|
|
8
|
-
def
|
|
9
|
-
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def end_date
|
|
13
|
-
presented_date(normalised_date_range.end_date)
|
|
19
|
+
def render
|
|
20
|
+
format_component.render
|
|
14
21
|
end
|
|
15
22
|
|
|
16
23
|
private
|
|
17
24
|
|
|
18
25
|
attr_reader :content_block, :normalised_date_range
|
|
19
26
|
|
|
27
|
+
delegate :format, to: :content_block
|
|
28
|
+
|
|
29
|
+
def format_component
|
|
30
|
+
case defaulted_format
|
|
31
|
+
when "long_form"
|
|
32
|
+
TimePeriod::LongFormComponent.new(start_date:, end_date:)
|
|
33
|
+
when "months_and_years_long"
|
|
34
|
+
TimePeriod::MonthsAndYearsLongComponent.new(start_date:, end_date:)
|
|
35
|
+
when "start_day_and_month"
|
|
36
|
+
TimePeriod::StartDayAndMonthComponent.new(start_date:)
|
|
37
|
+
when "start_month_as_word"
|
|
38
|
+
TimePeriod::StartMonthAsWordComponent.new(start_date:)
|
|
39
|
+
when "years"
|
|
40
|
+
TimePeriod::YearsComponent.new(start_date:, end_date:)
|
|
41
|
+
when "years_short"
|
|
42
|
+
TimePeriod::YearsShortComponent.new(start_date:, end_date:)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def defaulted_format
|
|
47
|
+
return "long_form" if format == Format::DEFAULT_FORMAT
|
|
48
|
+
|
|
49
|
+
format
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def validate_format!
|
|
53
|
+
return if SUPPORTED_FORMATS.include?(format)
|
|
54
|
+
|
|
55
|
+
raise InvalidFormatError, "Unknown format '#{format}' for time_period"
|
|
56
|
+
end
|
|
57
|
+
|
|
20
58
|
def normalise_date_range
|
|
21
59
|
NormalisedDateRange.new(content_block.details[:date_range])
|
|
22
60
|
end
|
|
23
61
|
|
|
24
|
-
def
|
|
25
|
-
|
|
62
|
+
def start_date
|
|
63
|
+
normalised_date_range.start_date
|
|
64
|
+
end
|
|
26
65
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
).render
|
|
66
|
+
def end_date
|
|
67
|
+
normalised_date_range.end_date
|
|
30
68
|
end
|
|
31
69
|
end
|
|
32
70
|
end
|
|
@@ -38,10 +38,13 @@ module ContentBlockTools
|
|
|
38
38
|
# content_block_reference.embed_code #=> "{{embed:content_block_pension:2b92cade-549c-4449-9796-e7a3957f3a86}}"
|
|
39
39
|
# content_block_reference.embed_code #=> "{{embed:content_block_contact:2b92cade-549c-4449-9796-e7a3957f3a86/field_name}}"
|
|
40
40
|
# @return [String]
|
|
41
|
+
#
|
|
42
|
+
# @!attribute [r] format
|
|
43
|
+
# The format specifier from the embed code, used to control rendering output
|
|
44
|
+
# @example
|
|
45
|
+
# content_block.format #=> "years_short"
|
|
46
|
+
# @return [String]
|
|
41
47
|
class ContentBlock
|
|
42
|
-
include ActionView::Helpers::TagHelper
|
|
43
|
-
class UnknownComponentError < StandardError; end
|
|
44
|
-
|
|
45
48
|
CONTENT_BLOCK_PREFIX = "content_block_".freeze
|
|
46
49
|
|
|
47
50
|
attr_reader :content_id, :title, :embed_code
|
|
@@ -86,22 +89,12 @@ module ContentBlockTools
|
|
|
86
89
|
@embed_code = embed_code
|
|
87
90
|
end
|
|
88
91
|
|
|
89
|
-
#
|
|
90
|
-
# block. Defaults to {Presenters::BasePresenter}
|
|
92
|
+
# Renders the content block to HTML using the appropriate component or presenter
|
|
91
93
|
#
|
|
92
|
-
# @return [
|
|
94
|
+
# @return [String] A HTML representation of the content block
|
|
95
|
+
# @see Renderer
|
|
93
96
|
def render
|
|
94
|
-
|
|
95
|
-
base_tag,
|
|
96
|
-
content,
|
|
97
|
-
class: %W[content-block content-block--#{document_type}],
|
|
98
|
-
data: {
|
|
99
|
-
content_block: "",
|
|
100
|
-
document_type: document_type,
|
|
101
|
-
content_id: content_id,
|
|
102
|
-
embed_code: embed_code,
|
|
103
|
-
},
|
|
104
|
-
)
|
|
97
|
+
Renderer.new(self).render
|
|
105
98
|
end
|
|
106
99
|
|
|
107
100
|
def details
|
|
@@ -112,70 +105,8 @@ module ContentBlockTools
|
|
|
112
105
|
@document_type.delete_prefix(CONTENT_BLOCK_PREFIX)
|
|
113
106
|
end
|
|
114
107
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def base_tag
|
|
118
|
-
rendering_block? ? :div : :span
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def content
|
|
122
|
-
field_names.present? ? field_or_block_content : component.new(content_block: self).render
|
|
123
|
-
rescue UnknownComponentError
|
|
124
|
-
title
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def field_or_block_content
|
|
128
|
-
content = details.dig(*field_names)
|
|
129
|
-
|
|
130
|
-
case content
|
|
131
|
-
when String
|
|
132
|
-
field_presenter(field_names.last).new(content).render
|
|
133
|
-
when Hash
|
|
134
|
-
if embedded_object_in_one_to_one_relationship?
|
|
135
|
-
field_presenter(field_names.last).new(content).render
|
|
136
|
-
else
|
|
137
|
-
component.new(content_block: self, block_type: field_names.first, block_name: field_names.last).render
|
|
138
|
-
end
|
|
139
|
-
else
|
|
140
|
-
ContentBlockTools.logger.warn("Content not found for content block #{content_id} and fields #{field_names}")
|
|
141
|
-
embed_code
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def embedded_object_in_one_to_one_relationship?
|
|
146
|
-
field_names.one?
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def rendering_block?
|
|
150
|
-
!field_names.present? || details.dig(*field_names).is_a?(Hash)
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def component
|
|
154
|
-
"ContentBlockTools::#{document_type.camelize}Component".constantize
|
|
155
|
-
rescue NameError
|
|
156
|
-
raise UnknownComponentError
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def field_presenter(field)
|
|
160
|
-
"ContentBlockTools::Presenters::FieldPresenters::#{document_type.camelize}::#{field.to_s.camelize}Presenter".constantize
|
|
161
|
-
rescue NameError
|
|
162
|
-
ContentBlockTools::Presenters::FieldPresenters::BasePresenter
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def field_names
|
|
166
|
-
@field_names ||= begin
|
|
167
|
-
embed_code_match = ContentBlockReference::EMBED_REGEX.match(embed_code)
|
|
168
|
-
if embed_code_match.present?
|
|
169
|
-
all_fields = embed_code_match[4]&.reverse&.chomp("/")&.reverse
|
|
170
|
-
all_fields&.split("/")&.map do |item|
|
|
171
|
-
is_number?(item) ? item.to_i : item.to_sym
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def is_number?(item)
|
|
178
|
-
Float(item, exception: false)
|
|
108
|
+
def format
|
|
109
|
+
EmbedCode.new(embed_code).format
|
|
179
110
|
end
|
|
180
111
|
end
|
|
181
112
|
end
|
|
@@ -40,10 +40,10 @@ module ContentBlockTools
|
|
|
40
40
|
UUID_REGEX = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/
|
|
41
41
|
# The regex used to find content ID aliases
|
|
42
42
|
CONTENT_ID_ALIAS_REGEX = /[a-z0-9\-–—]+/
|
|
43
|
-
# The regex to find optional
|
|
44
|
-
|
|
43
|
+
# The regex to find optional internal content path after the UUID, begins with '/'
|
|
44
|
+
INTERNAL_CONTENT_PATH_REGEX = /(?<internal_content_path>\/[a-z0-9_\-–—\/]*)?/
|
|
45
45
|
# The regex used when scanning a document using {ContentBlockTools::ContentBlockReference.find_all_in_document}
|
|
46
|
-
EMBED_REGEX = /({{embed:(
|
|
46
|
+
EMBED_REGEX = /(?<embed_code>{{embed:(?<document_type>#{SUPPORTED_DOCUMENT_TYPES.join('|')}):(?<identifier>#{UUID_REGEX}|#{CONTENT_ID_ALIAS_REGEX})#{INTERNAL_CONTENT_PATH_REGEX}}})/
|
|
47
47
|
|
|
48
48
|
# Returns if the identifier is an alias
|
|
49
49
|
#
|
|
@@ -73,8 +73,8 @@ module ContentBlockTools
|
|
|
73
73
|
#
|
|
74
74
|
# @return [Array<ContentBlockReference>] An array of content block references
|
|
75
75
|
def find_all_in_document(document)
|
|
76
|
-
document.scan
|
|
77
|
-
ContentBlockReference.from_match_data(
|
|
76
|
+
document.to_enum(:scan, EMBED_REGEX).map do
|
|
77
|
+
ContentBlockReference.from_match_data(Regexp.last_match)
|
|
78
78
|
end
|
|
79
79
|
end
|
|
80
80
|
|
|
@@ -97,7 +97,7 @@ module ContentBlockTools
|
|
|
97
97
|
match_data = embed_code.match(/^#{EMBED_REGEX}$/)
|
|
98
98
|
raise InvalidEmbedCodeError unless match_data
|
|
99
99
|
|
|
100
|
-
ContentBlockReference.from_match_data(match_data
|
|
100
|
+
ContentBlockReference.from_match_data(match_data)
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
# Converts match data from a regex scan into a ContentBlockReference object
|
|
@@ -107,8 +107,8 @@ module ContentBlockTools
|
|
|
107
107
|
# by replacing en/em dashes with double/triple dashes (which can occur due to Kramdown's
|
|
108
108
|
# markdown parsing) before creating the object.
|
|
109
109
|
#
|
|
110
|
-
# @param match_data [MatchData
|
|
111
|
-
# Expected
|
|
110
|
+
# @param match_data [MatchData] the match data from scanning with {EMBED_REGEX}
|
|
111
|
+
# Expected named captures: embed_code, document_type, identifier, internal_content_path
|
|
112
112
|
# @example Creating from match data
|
|
113
113
|
# match_data = "{{embed:content_block_pension:2b92cade-549c-4449-9796-e7a3957f3a86}}".match(EMBED_REGEX)
|
|
114
114
|
# ContentBlockReference.from_match_data(match_data)
|
|
@@ -121,7 +121,11 @@ module ContentBlockTools
|
|
|
121
121
|
def from_match_data(match_data)
|
|
122
122
|
match = prepare_match(match_data)
|
|
123
123
|
ContentBlockTools.logger.info("Found Content Block Reference: #{match}")
|
|
124
|
-
ContentBlockReference.new(
|
|
124
|
+
ContentBlockReference.new(
|
|
125
|
+
document_type: match[:document_type],
|
|
126
|
+
identifier: match[:identifier],
|
|
127
|
+
embed_code: match[:embed_code],
|
|
128
|
+
)
|
|
125
129
|
end
|
|
126
130
|
|
|
127
131
|
private
|
|
@@ -130,12 +134,12 @@ module ContentBlockTools
|
|
|
130
134
|
# because Kramdown (the markdown parser that Govspeak is based on) replaces double dashes with en dashes and
|
|
131
135
|
# triple dashes with em dashes
|
|
132
136
|
def prepare_match(match)
|
|
133
|
-
|
|
134
|
-
match[
|
|
135
|
-
match[
|
|
136
|
-
replace_dashes(match[
|
|
137
|
-
match[
|
|
138
|
-
|
|
137
|
+
{
|
|
138
|
+
embed_code: match[:embed_code],
|
|
139
|
+
document_type: match[:document_type],
|
|
140
|
+
identifier: replace_dashes(match[:identifier]),
|
|
141
|
+
internal_content_path: match[:internal_content_path],
|
|
142
|
+
}
|
|
139
143
|
end
|
|
140
144
|
|
|
141
145
|
def replace_dashes(value)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module ContentBlockTools
|
|
2
|
+
# Parses and represents an embed code string
|
|
3
|
+
#
|
|
4
|
+
# An embed code identifies a content block and optionally specifies which
|
|
5
|
+
# fields to render. The format is:
|
|
6
|
+
# {{embed:block_type:identifier}}
|
|
7
|
+
# {{embed:block_type:identifier/field1/nested_field2}}
|
|
8
|
+
# {{embed:block_type:identifier|format}}
|
|
9
|
+
# {{embed:block_type:identifier/field1|format}}
|
|
10
|
+
#
|
|
11
|
+
# @example Basic embed code
|
|
12
|
+
# embed_code = EmbedCode.new("{{embed:content_block_contact:main-office}}")
|
|
13
|
+
# embed_code.internal_content_path.present? #=> false
|
|
14
|
+
#
|
|
15
|
+
# @example Embed code with field path
|
|
16
|
+
# embed_code = EmbedCode.new("{{embed:content_block_contact:main-office/email_addresses/main}}")
|
|
17
|
+
# embed_code.internal_content_path.path #=> [:email_addresses, :main]
|
|
18
|
+
#
|
|
19
|
+
# @example Embed code with format
|
|
20
|
+
# embed_code = EmbedCode.new("{{embed:content_block_time_period:tax-year#years_short}}")
|
|
21
|
+
# embed_code.format #=> "years_short"
|
|
22
|
+
#
|
|
23
|
+
class EmbedCode
|
|
24
|
+
FORMAT_REGEX = /\#(?<format>[^}#]+)}}$/
|
|
25
|
+
|
|
26
|
+
def initialize(embed_code_string)
|
|
27
|
+
@embed_code_string = embed_code_string
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the internal content path for this embed code
|
|
31
|
+
#
|
|
32
|
+
# @return [InternalContentPath] The path to internal content
|
|
33
|
+
def internal_content_path
|
|
34
|
+
@internal_content_path ||= InternalContentPath.new(parse_path_segments)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns the format specifier from the embed code
|
|
38
|
+
#
|
|
39
|
+
# @return [String] The format name, or Format::DEFAULT_FORMAT if none specified
|
|
40
|
+
def format
|
|
41
|
+
@format ||= parse_format
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
attr_reader :embed_code_string
|
|
47
|
+
|
|
48
|
+
def parse_path_segments
|
|
49
|
+
match = ContentBlockReference::EMBED_REGEX.match(embed_code_string)
|
|
50
|
+
return [] unless match
|
|
51
|
+
|
|
52
|
+
all_segments = strip_leading_slash(match[:internal_content_path])
|
|
53
|
+
return [] if all_segments.nil?
|
|
54
|
+
|
|
55
|
+
all_segments.split("/").map { |item| convert_segment(item) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def strip_leading_slash(path)
|
|
59
|
+
path&.reverse&.chomp("/")&.reverse
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def convert_segment(item)
|
|
63
|
+
numeric?(item) ? item.to_i : item.to_sym
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def parse_format
|
|
67
|
+
match = FORMAT_REGEX.match(embed_code_string)
|
|
68
|
+
return Format::DEFAULT_FORMAT unless match
|
|
69
|
+
|
|
70
|
+
match[:format]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def numeric?(item)
|
|
74
|
+
Float(item, exception: false)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module ContentBlockTools
|
|
2
|
+
# Represents the path to internal content within a content block
|
|
3
|
+
#
|
|
4
|
+
# An internal content path identifies a specific piece of content within a
|
|
5
|
+
# content block's details hash. The path is used with `dig` to traverse the
|
|
6
|
+
# nested structure.
|
|
7
|
+
#
|
|
8
|
+
# @example Path to a block
|
|
9
|
+
# path = InternalContentPath.new([:email_addresses, :main])
|
|
10
|
+
# path.present? #=> true
|
|
11
|
+
# path.singular? #=> false
|
|
12
|
+
# path.block_type #=> :email_addresses
|
|
13
|
+
# path.block_name #=> :main
|
|
14
|
+
#
|
|
15
|
+
# @example Path to a field in a one-to-one relationship
|
|
16
|
+
# path = InternalContentPath.new([:date_range])
|
|
17
|
+
# path.singular? #=> true
|
|
18
|
+
# path.block_type #=> :date_range
|
|
19
|
+
# path.block_name #=> :date_range
|
|
20
|
+
#
|
|
21
|
+
# @example Empty path (render the whole block)
|
|
22
|
+
# path = InternalContentPath.new([])
|
|
23
|
+
# path.present? #=> false
|
|
24
|
+
# path.block_type #=> nil
|
|
25
|
+
#
|
|
26
|
+
class InternalContentPath
|
|
27
|
+
attr_reader :path
|
|
28
|
+
|
|
29
|
+
def initialize(path)
|
|
30
|
+
@path = path
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def present?
|
|
34
|
+
path.any?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def singular?
|
|
38
|
+
path.one?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def block_type
|
|
42
|
+
path.first
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def block_name
|
|
46
|
+
path.last
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -54,10 +54,9 @@ module ContentBlockTools
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def parse_iso8601_format(datetime_string)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
result
|
|
57
|
+
Time.parse(datetime_string)
|
|
58
|
+
rescue ArgumentError
|
|
59
|
+
raise ParseError, "Invalid ISO 8601 format: #{datetime_string.inspect}"
|
|
61
60
|
end
|
|
62
61
|
end
|
|
63
62
|
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
module ContentBlockTools
|
|
2
|
+
# Renders a ContentBlock to HTML
|
|
3
|
+
#
|
|
4
|
+
# This class encapsulates the logic for rendering a content block,
|
|
5
|
+
# including determining the appropriate component or presenter to use
|
|
6
|
+
# and wrapping the result in the standard content block markup.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# content_block = ContentBlock.new(...)
|
|
10
|
+
# html = Renderer.new(content_block).render
|
|
11
|
+
#
|
|
12
|
+
class Renderer
|
|
13
|
+
include ActionView::Helpers::TagHelper
|
|
14
|
+
|
|
15
|
+
class UnknownComponentError < StandardError; end
|
|
16
|
+
|
|
17
|
+
def initialize(content_block)
|
|
18
|
+
@content_block = content_block
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Renders the content block to HTML
|
|
22
|
+
#
|
|
23
|
+
# @return [String] HTML representation of the content block
|
|
24
|
+
def render
|
|
25
|
+
content_tag(
|
|
26
|
+
base_tag,
|
|
27
|
+
content,
|
|
28
|
+
class: %W[content-block content-block--#{content_block.document_type}],
|
|
29
|
+
data: {
|
|
30
|
+
content_block: "",
|
|
31
|
+
document_type: content_block.document_type,
|
|
32
|
+
content_id: content_block.content_id,
|
|
33
|
+
embed_code: content_block.embed_code,
|
|
34
|
+
},
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
attr_reader :content_block
|
|
41
|
+
|
|
42
|
+
def base_tag
|
|
43
|
+
rendering_block? ? :div : :span
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def content
|
|
47
|
+
internal_content_path.present? ? field_or_block_content : component.new(content_block:).render
|
|
48
|
+
rescue UnknownComponentError
|
|
49
|
+
content_block.title
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def field_or_block_content
|
|
53
|
+
field_content = content_block.details.dig(*internal_content_path.path)
|
|
54
|
+
|
|
55
|
+
case field_content
|
|
56
|
+
when String
|
|
57
|
+
field_presenter(internal_content_path.block_name).new(field_content).render
|
|
58
|
+
when Hash
|
|
59
|
+
render_hash_content(field_content)
|
|
60
|
+
else
|
|
61
|
+
log_content_not_found
|
|
62
|
+
content_block.embed_code
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def render_hash_content(field_content)
|
|
67
|
+
if internal_content_path.singular?
|
|
68
|
+
field_presenter(internal_content_path.block_name).new(field_content).render
|
|
69
|
+
else
|
|
70
|
+
component.new(
|
|
71
|
+
content_block:,
|
|
72
|
+
block_type: internal_content_path.block_type,
|
|
73
|
+
block_name: internal_content_path.block_name,
|
|
74
|
+
).render
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def log_content_not_found
|
|
79
|
+
ContentBlockTools.logger.warn(
|
|
80
|
+
"Content not found for content block #{content_block.content_id} " \
|
|
81
|
+
"and fields #{internal_content_path.path}",
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def rendering_block?
|
|
86
|
+
!internal_content_path.present? || content_block.details.dig(*internal_content_path.path).is_a?(Hash)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def component
|
|
90
|
+
"ContentBlockTools::#{content_block.document_type.camelize}Component".constantize
|
|
91
|
+
rescue NameError
|
|
92
|
+
raise UnknownComponentError
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def field_presenter(field)
|
|
96
|
+
presenter_class_name = "ContentBlockTools::Presenters::FieldPresenters::" \
|
|
97
|
+
"#{content_block.document_type.camelize}::#{field.to_s.camelize}Presenter"
|
|
98
|
+
presenter_class_name.constantize
|
|
99
|
+
rescue NameError
|
|
100
|
+
ContentBlockTools::Presenters::FieldPresenters::BasePresenter
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def internal_content_path
|
|
104
|
+
@internal_content_path ||= EmbedCode.new(content_block.embed_code).internal_content_path
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
data/lib/content_block_tools.rb
CHANGED
|
@@ -18,7 +18,11 @@ require "content_block_tools/presenters/field_presenters/time_period/end_present
|
|
|
18
18
|
|
|
19
19
|
require "content_block_tools/content_block"
|
|
20
20
|
require "content_block_tools/content_block_reference"
|
|
21
|
+
require "content_block_tools/embed_code"
|
|
22
|
+
require "content_block_tools/format"
|
|
23
|
+
require "content_block_tools/internal_content_path"
|
|
21
24
|
require "content_block_tools/normalised_date_range"
|
|
25
|
+
require "content_block_tools/renderer"
|
|
22
26
|
|
|
23
27
|
require "content_block_tools/engine"
|
|
24
28
|
|
|
@@ -27,11 +31,13 @@ require "content_block_tools/version"
|
|
|
27
31
|
module ContentBlockTools
|
|
28
32
|
class Error < StandardError; end
|
|
29
33
|
class InvalidEmbedCodeError < StandardError; end
|
|
34
|
+
class InvalidFormatError < StandardError; end
|
|
30
35
|
|
|
31
36
|
module Presenters; end
|
|
32
37
|
|
|
33
38
|
module Components
|
|
34
39
|
module Contacts; end
|
|
40
|
+
module TimePeriod; end
|
|
35
41
|
end
|
|
36
42
|
|
|
37
43
|
class << self
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: content_block_tools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.12.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- GOV.UK Dev
|
|
@@ -9,20 +9,48 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: cucumber
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '11.0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '11.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: pry-byebug
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
12
40
|
- !ruby/object:Gem::Dependency
|
|
13
41
|
name: rake
|
|
14
42
|
requirement: !ruby/object:Gem::Requirement
|
|
15
43
|
requirements:
|
|
16
44
|
- - '='
|
|
17
45
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 13.
|
|
46
|
+
version: 13.4.2
|
|
19
47
|
type: :development
|
|
20
48
|
prerelease: false
|
|
21
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
50
|
requirements:
|
|
23
51
|
- - '='
|
|
24
52
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 13.
|
|
53
|
+
version: 13.4.2
|
|
26
54
|
- !ruby/object:Gem::Dependency
|
|
27
55
|
name: rspec-html-matchers
|
|
28
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -57,14 +85,14 @@ dependencies:
|
|
|
57
85
|
requirements:
|
|
58
86
|
- - '='
|
|
59
87
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: 5.2.
|
|
88
|
+
version: 5.2.1
|
|
61
89
|
type: :development
|
|
62
90
|
prerelease: false
|
|
63
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
92
|
requirements:
|
|
65
93
|
- - '='
|
|
66
94
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: 5.2.
|
|
95
|
+
version: 5.2.1
|
|
68
96
|
- !ruby/object:Gem::Dependency
|
|
69
97
|
name: simplecov
|
|
70
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -180,14 +208,28 @@ files:
|
|
|
180
208
|
- app/components/content_block_tools/contacts/email_address_component.rb
|
|
181
209
|
- app/components/content_block_tools/contacts/telephone_component.html.erb
|
|
182
210
|
- app/components/content_block_tools/contacts/telephone_component.rb
|
|
183
|
-
- app/components/content_block_tools/
|
|
211
|
+
- app/components/content_block_tools/time_period/long_form_component.html.erb
|
|
212
|
+
- app/components/content_block_tools/time_period/long_form_component.rb
|
|
213
|
+
- app/components/content_block_tools/time_period/months_and_years_long_component.html.erb
|
|
214
|
+
- app/components/content_block_tools/time_period/months_and_years_long_component.rb
|
|
215
|
+
- app/components/content_block_tools/time_period/start_day_and_month_component.html.erb
|
|
216
|
+
- app/components/content_block_tools/time_period/start_day_and_month_component.rb
|
|
217
|
+
- app/components/content_block_tools/time_period/start_month_as_word_component.html.erb
|
|
218
|
+
- app/components/content_block_tools/time_period/start_month_as_word_component.rb
|
|
219
|
+
- app/components/content_block_tools/time_period/years_component.html.erb
|
|
220
|
+
- app/components/content_block_tools/time_period/years_component.rb
|
|
221
|
+
- app/components/content_block_tools/time_period/years_short_component.html.erb
|
|
222
|
+
- app/components/content_block_tools/time_period/years_short_component.rb
|
|
184
223
|
- app/components/content_block_tools/time_period_component.rb
|
|
185
224
|
- lib/content_block_tools.rb
|
|
186
225
|
- lib/content_block_tools/content_block.rb
|
|
187
226
|
- lib/content_block_tools/content_block_reference.rb
|
|
227
|
+
- lib/content_block_tools/embed_code.rb
|
|
188
228
|
- lib/content_block_tools/engine.rb
|
|
229
|
+
- lib/content_block_tools/format.rb
|
|
189
230
|
- lib/content_block_tools/helpers/govspeak.rb
|
|
190
231
|
- lib/content_block_tools/helpers/override_classes.rb
|
|
232
|
+
- lib/content_block_tools/internal_content_path.rb
|
|
191
233
|
- lib/content_block_tools/normalised_date_range.rb
|
|
192
234
|
- lib/content_block_tools/presenters/field_presenters/base_presenter.rb
|
|
193
235
|
- lib/content_block_tools/presenters/field_presenters/contact/email_presenter.rb
|
|
@@ -196,6 +238,7 @@ files:
|
|
|
196
238
|
- lib/content_block_tools/presenters/field_presenters/time_period/end_presenter.rb
|
|
197
239
|
- lib/content_block_tools/presenters/field_presenters/time_period/start_presenter.rb
|
|
198
240
|
- lib/content_block_tools/presenters/field_presenters/time_period/time_presenter.rb
|
|
241
|
+
- lib/content_block_tools/renderer.rb
|
|
199
242
|
- lib/content_block_tools/version.rb
|
|
200
243
|
- node_modules/govuk-frontend/README.md
|
|
201
244
|
- node_modules/govuk-frontend/dist/govuk-prototype-kit/functions.js
|
|
@@ -1063,7 +1106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
1063
1106
|
- !ruby/object:Gem::Version
|
|
1064
1107
|
version: '0'
|
|
1065
1108
|
requirements: []
|
|
1066
|
-
rubygems_version: 4.0.
|
|
1109
|
+
rubygems_version: 4.0.11
|
|
1067
1110
|
specification_version: 4
|
|
1068
1111
|
summary: A suite of tools for working with GOV.UK Content Blocks
|
|
1069
1112
|
test_files: []
|