govspeak 5.0.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 726774ba404422dd313e1afb12bbc3d1c2e036fd
4
- data.tar.gz: 4f7e5f0f7b4c9e67c33120050f3021048c13dec7
3
+ metadata.gz: 177a638111a0d8d422e3201f0e3e265218fb1cb9
4
+ data.tar.gz: 5be0baf8e84b10bfd28f0f5ed6e1c5e292fe4c1e
5
5
  SHA512:
6
- metadata.gz: a144b4353edd665f01109bd1eae1ff569f22ea6133bf564024e973d7df730e9f318d9eb76e76df5917e130b10dbf9897dcbcd75ccfdb816ba291b16e3e9349a0
7
- data.tar.gz: 8370760f5a9929f8c33b001d99fc5409900182d815bbae0059c33360d47ef7ccd560ae4b206bdec7940747b73ce9e544edffd10751a7107070b8a9aa49006e8f
6
+ metadata.gz: 77ca5d6c13299db9221804e96b01fb15412d0c38d3017219304992d225dab4382dec796015fd2533e62e7ad734d6f0199b216b175e4f45d099cc913b8ed24a6a
7
+ data.tar.gz: b52ed84b08d05f85c44a2a3bb3fe2d86835f2cf9934e9ead9b7c143b9be9fcbea03b84d8af70ca2245426a9ea1676b54a35a2714d7a3e679ad890d1ee87f46d9
@@ -1,3 +1,7 @@
1
+ ## 5.0.1
2
+ * Move presenters into the Govspeak namespace [#93](https://github.com/alphagov/govspeak/pull/93)
3
+ * Embedded links now will automatically be marked with `rel="external"` [#96](https://github.com/alphagov/govspeak/pull/96)
4
+
1
5
  ## 5.0.0
2
6
  * Update Kramdown version to 1.12.0
3
7
  * Add pry-byebug to development dependencies
data/README.md CHANGED
@@ -43,7 +43,7 @@ In addition to the [standard Markdown syntax](http://daringfireball.net/projects
43
43
  creates a callout with an info (i) icon.
44
44
 
45
45
  <div role="note" aria-label="Information" class="application-notice info-notice">
46
- <p>This is an information callout</p>
46
+ <p>This is an information callout</p>
47
47
  </div>
48
48
 
49
49
  ### Warning callouts
@@ -53,7 +53,7 @@ creates a callout with an info (i) icon.
53
53
  creates a callout with a warning or alert (!) icon
54
54
 
55
55
  <div role="note" aria-label="Help" class="application-notice help-notice">
56
- <p>This is a warning callout</p>
56
+ <p>This is a warning callout</p>
57
57
  </div>
58
58
 
59
59
  ### Example callout
@@ -158,6 +158,74 @@ creates a file download box
158
158
  <p><a href="http://example.com/" title="Example form" rel="external">An example form download link.</a></p>
159
159
  </div>
160
160
 
161
+ ## Place
162
+
163
+ $P
164
+ This is a place
165
+ $P
166
+
167
+ creates a place box
168
+
169
+ <div class="place">
170
+ <p>This is a place</p>
171
+ </div>
172
+
173
+ ## Information
174
+
175
+ $I
176
+ This is information
177
+ $I
178
+
179
+ creates an information box
180
+
181
+ <div class="information">
182
+ <p>This is information</p>
183
+ </div>
184
+
185
+ ## Additional Information
186
+
187
+ $AI
188
+ This is additional information
189
+ $AI
190
+
191
+ creates an additional information box
192
+
193
+ <div class="additional-information">
194
+ <p>This is additional information</p>
195
+ </div>
196
+
197
+ ## Call to Action
198
+
199
+ $CTA
200
+ This is a call to action
201
+ $CTA
202
+
203
+ creates an additional information box
204
+
205
+ <div class="call-to-action">
206
+ <p>This is a call to action</p>
207
+ </div>
208
+
209
+ ## Summary
210
+
211
+ $!
212
+ This is a summary
213
+ $!
214
+
215
+ creates a summary box
216
+
217
+ <div class="summary">
218
+ <p>This is a summary</p>
219
+ </div>
220
+
221
+ ## External Link
222
+
223
+ x[External Report](https://example.com/report)x
224
+
225
+ creates a link specified as external
226
+
227
+ <a href="https://example.com/report" rel="external">External Report</a>
228
+
161
229
  ## Steps
162
230
 
163
231
  Steps can be created similar to an ordered list:
@@ -183,17 +251,26 @@ For lists where you want to specify the numbering and have multiple indent level
183
251
  $EndLegislativeList
184
252
  (to indent, add 2 spaces)
185
253
 
186
- ## Abbreviations
187
-
188
- Abbreviations can be defined at the end of the document, and any occurrences elswhere in the document will wrapped in an `<abbr>` tag. They are parsed in the order in which they are defined, so `PCSOs` should be defined before `PCSO`, for example.
254
+ ## Priority Lists
189
255
 
190
- Special rules apply if you’re exporting a vehicle outside the EU.
256
+ For lists where you want to specify a number of items to be highlighted as priority.
191
257
 
192
- *[EU]:European Union
258
+ $PriorityList:3
259
+ - Item 1
260
+ - Item 2
261
+ - Item 3
262
+ - Item 4
263
+ - Item 5
193
264
 
194
- becomes
265
+ creates a list with priority items flagged with a class
195
266
 
196
- <p>Special rules apply if you’re exporting a vehicle outside the <abbr title="European Union">EU</abbr>.</p>
267
+ <ul>
268
+ <li class="primary-item">Item 1</li>
269
+ <li class="primary-item">Item 2</li>
270
+ <li class="primary-item">Item 3</li>
271
+ <li>Item 4</li>
272
+ <li>Item 5</li>
273
+ </ul>
197
274
 
198
275
  ## Devolved content
199
276
 
@@ -203,3 +280,190 @@ becomes
203
280
  :wales:content goes here:wales:
204
281
  :northern-ireland:content goes here:northern-ireland:
205
282
  :england-wales:content goes here:england-wales:
283
+
284
+ will create a box for the specified locality
285
+
286
+ <div class="devolved-content england">
287
+ <p class="devolved-header">This section applies to England</p>
288
+ <div class="devolved-body">
289
+ <p>content goes here</p>
290
+ </div>
291
+ </div>
292
+
293
+ ## Barcharts
294
+
295
+ For when you want a table to be progressively enhanced by Javascript to be
296
+ rendered as a bar chart.
297
+
298
+ |col|
299
+ |---|
300
+ |val|
301
+ {barchart}
302
+
303
+ will be rendered as
304
+ <table class="js-barchart-table mc-auto-outdent">
305
+ <thead>
306
+ <tr>
307
+ <th>col</th>
308
+ </tr>
309
+ </thead>
310
+ <tbody>
311
+ <tr>
312
+ <td>val</td>
313
+ </tr>
314
+ </tbody>
315
+ </table>
316
+
317
+ ## Embedded Content
318
+
319
+ Embedded content allows authors to reference a supporting item of a document by
320
+ referencing an id. The details of this content is passed to the publishing
321
+ application to govspeak at the time of rendering.
322
+
323
+ ### Attachments
324
+
325
+ To create an attachment callout
326
+
327
+ [embed:attachment:2b4d92f3-f8cd-4284-aaaa-25b3a640d26c]
328
+
329
+ with options provided
330
+
331
+ {
332
+ attachments: [
333
+ {
334
+ id: 123,
335
+ content_id: "2b4d92f3-f8cd-4284-aaaa-25b3a640d26c",
336
+ title: "Attachment Title",
337
+ url: "http://example.com/test.pdf",
338
+ order_url: "http://example.com/order",
339
+ price: 12.3,
340
+ isbn: "isbn-123",
341
+ attachment?: true,
342
+ }
343
+ ]
344
+ }
345
+
346
+ will output an attachment box
347
+
348
+ <section class="attachment embedded">
349
+ <div class="attachment-thumb">
350
+ <a href="http://example.com/test.pdf" aria-hidden="true" class="embedded"><img src="/images/pub-cover.png" alt="Pub cover"></a>
351
+ </div>
352
+ <div class="attachment-details">
353
+ <h2 class="title">
354
+ <a href="http://example.com/test.pdf" aria-describedby="attachment-123-accessibility-help">Attachment Title</a>
355
+ </h2>
356
+ <p class="metadata">
357
+ <span class="references">Ref: ISBN <span class="isbn">isbn-123</span></span>
358
+ </p>
359
+ <p>
360
+ <a href="http://example.com/order" class="order_url" title="Order a copy of the publication">Order a copy</a>(<span class="price">£12.30</span>)
361
+ </p>
362
+ </div>
363
+ </section>
364
+
365
+ ### Inline Attachment
366
+
367
+ Attachments can be linked to inline as well
368
+
369
+ Details referenced in [embed:attachments:inline:34f6dda0-21b1-4e78-8120-3ff4dcea522d]
370
+
371
+ with options provided
372
+
373
+ {
374
+ attachments: [
375
+ {
376
+ content_id: "34f6dda0-21b1-4e78-8120-3ff4dcea522d",
377
+ title: "My Thorough Study",
378
+ url: "http://example.com/my-thorough-study.pdf",
379
+ }
380
+ ]
381
+ }
382
+
383
+ will output an attachment within a block of text
384
+
385
+ <p>Details referenced in <span class="attachment-inline"><a href="http://example.com/my-thorough-study.pdf">My Thorough Study</a></span>
386
+
387
+ ### Image Attachments
388
+
389
+ Attachments can be used to embed an image within the document
390
+
391
+ [embed:attachments:image:45ee0eea-bc53-4f14-81eb-9e75d33c4d5e]
392
+
393
+ with options provided
394
+
395
+ {
396
+ attachments: [
397
+ {
398
+ content_id: "45ee0eea-bc53-4f14-81eb-9e75d33c4d5e",
399
+ title: "A lovely landscape",
400
+ url: "http://example.com/lovely-landscape.jpg",
401
+ }
402
+ ]
403
+ }
404
+
405
+ will output a image section
406
+
407
+ <figure class="image embedded"><div class="img"><img src="http://example.com/lovely-landscape.jpg" alt="A Lovely Landscape"></div></figure>
408
+
409
+ ### Link
410
+
411
+ Links to different documents can be embedded so they change when the documents
412
+ they reference change.
413
+
414
+ A link to [embed:link:c636b433-1e5c-46d4-96b0-b5a168fac26c]
415
+
416
+ with options provided
417
+
418
+ {
419
+ links: [
420
+ {
421
+ url: "http://example.com",
422
+ title: "An excellent website",
423
+ }
424
+ ]
425
+ }
426
+
427
+ will output
428
+
429
+ <p>A link to <a href="http://example.com">An excellent website</a></p>
430
+
431
+ ### Contact
432
+
433
+ [Contact:df62690f-34a0-4840-a7fa-4ef5acc18666]
434
+
435
+ with options provided
436
+
437
+ {
438
+ contacts: [
439
+ {
440
+ id: 123,
441
+ content_id: "df62690f-34a0-4840-a7fa-4ef5acc18666",
442
+ title: "Government Digital Service",
443
+ email: "people@digital.cabinet-office.gov.uk",
444
+ contact_numbers: [
445
+ { label: "helpdesk", number: "+4412345 67890"}
446
+ ]
447
+ }
448
+ ]
449
+ }
450
+
451
+ will output
452
+
453
+ <div id="contact_123" class="contact">
454
+ <div class="content">
455
+ <h3>Government Digital Service</h3>
456
+ <div class="vcard contact-inner">
457
+ <div class="email-url-number">
458
+ <p class="email">
459
+ <span class="type">Email</span>
460
+ <a href="mailto:people@digital.cabinet-office.gov.uk" class="email">people@digital.cabinet-office.gov.uk</a>
461
+ </p>
462
+ <p class="tel">
463
+ <span class="type">helpdesk</span>
464
+ +4412345 67890
465
+ </p>
466
+ </div>
467
+ </div>
468
+ </div>
469
+ </div>
@@ -1,6 +1,9 @@
1
- require 'kramdown'
2
1
  require 'active_support/core_ext/hash'
3
2
  require 'active_support/core_ext/array'
3
+ require 'erb'
4
+ require 'htmlentities'
5
+ require 'kramdown'
6
+ require 'kramdown/parser/kramdown_with_automatic_external_links'
4
7
  require 'govspeak/header_extractor'
5
8
  require 'govspeak/structured_header_extractor'
6
9
  require 'govspeak/html_validator'
@@ -8,12 +11,9 @@ require 'govspeak/html_sanitizer'
8
11
  require 'govspeak/kramdown_overrides'
9
12
  require 'govspeak/blockquote_extra_quote_remover'
10
13
  require 'govspeak/post_processor'
11
- require 'kramdown/parser/kramdown_with_automatic_external_links'
12
- require 'htmlentities'
13
- require 'presenters/attachment_presenter'
14
- require 'presenters/contact_presenter'
15
- require 'presenters/h_card_presenter'
16
- require 'erb'
14
+ require 'govspeak/presenters/attachment_presenter'
15
+ require 'govspeak/presenters/contact_presenter'
16
+ require 'govspeak/presenters/h_card_presenter'
17
17
 
18
18
  module Govspeak
19
19
 
@@ -32,7 +32,7 @@ module Govspeak
32
32
  end
33
33
 
34
34
  def initialize(source, options = {})
35
- options.deep_symbolize_keys!
35
+ options = options.dup.deep_symbolize_keys
36
36
  @source = source ? source.dup : ""
37
37
  @images = options.delete(:images) || []
38
38
  @attachments = Array.wrap(options.delete(:attachments))
@@ -303,7 +303,7 @@ module Govspeak
303
303
  link = links.detect { |l| l[:content_id].match(content_id) }
304
304
  next "" unless link
305
305
  if link[:url]
306
- %Q{<a href="#{encode(link[:url])}">#{link[:title]}</a>}
306
+ "[#{link[:title]}](#{link[:url]})"
307
307
  else
308
308
  link[:title]
309
309
  end
@@ -0,0 +1,288 @@
1
+ require "action_view"
2
+ require "money"
3
+ require "htmlentities"
4
+
5
+ module Govspeak
6
+ class AttachmentPresenter
7
+ attr_reader :attachment
8
+ include ActionView::Helpers::TagHelper
9
+ include ActionView::Helpers::NumberHelper
10
+ include ActionView::Helpers::AssetTagHelper
11
+ include ActionView::Helpers::TextHelper
12
+
13
+ def initialize(attachment)
14
+ @attachment = attachment
15
+ end
16
+
17
+ def id
18
+ attachment[:id]
19
+ end
20
+
21
+ def order_url
22
+ attachment[:order_url]
23
+ end
24
+
25
+ def opendocument?
26
+ attachment[:opendocument?]
27
+ end
28
+
29
+ def url
30
+ attachment[:url]
31
+ end
32
+
33
+ def external?
34
+ attachment[:external?]
35
+ end
36
+
37
+ def price
38
+ return unless attachment[:price]
39
+ Money.from_amount(attachment[:price], 'GBP').format
40
+ end
41
+
42
+ def accessible?
43
+ attachment[:accessible?]
44
+ end
45
+
46
+ def thumbnail_link
47
+ return if hide_thumbnail?
48
+ return if previewable?
49
+ link(attachment_thumbnail, url, "aria-hidden" => "true", "class" => attachment_class)
50
+ end
51
+
52
+ def help_block_toggle_id
53
+ "attachment-#{id}-accessibility-request"
54
+ end
55
+
56
+ def section_class
57
+ attachment[:external?] ? "hosted-externally" : "embedded"
58
+ end
59
+
60
+ def mail_to(email_address, name, options = {})
61
+ query_string = options.slice(:subject, :body).map { |k, v| "#{urlencode(k)}=#{urlencode(v)}" }.join("&")
62
+ "<a href='mailto:#{encode(email_address)}?#{encode(query_string)}'>#{name}</a>"
63
+ end
64
+
65
+ def alternative_format_order_link
66
+ attachment_info = []
67
+ attachment_info << " Title: #{title}"
68
+ attachment_info << " Original format: #{file_extension}" if file_extension.present?
69
+ attachment_info << " ISBN: #{attachment[:isbn]}" if attachment[:isbn].present?
70
+ attachment_info << " Unique reference: #{attachment[:unique_reference]}" if attachment[:unique_reference].present?
71
+ attachment_info << " Command paper number: #{attachment[:command_paper_number]}" if attachment[:command_paper_number].present?
72
+ if attachment[:hoc_paper_number].present?
73
+ attachment_info << " House of Commons paper number: #{attachment[:hoc_paper_number]}"
74
+ attachment_info << " Parliamentary session: #{attachment[:parliamentary_session]}"
75
+ end
76
+
77
+ options = {
78
+ subject: "Request for '#{title}' in an alternative format",
79
+ body: body_for_mail(attachment_info)
80
+ }
81
+
82
+ mail_to(alternative_format_contact_email, alternative_format_contact_email, options)
83
+ end
84
+
85
+ def body_for_mail(attachment_info)
86
+ <<-END
87
+ Details of document required:
88
+
89
+ #{attachment_info.join("\n")}
90
+
91
+ Please tell us:
92
+
93
+ 1. What makes this format unsuitable for you?
94
+ 2. What format you would prefer?
95
+ END
96
+ end
97
+
98
+ def alternative_format_contact_email
99
+ "govuk-feedback@digital.cabinet-office.gov.uk"
100
+ end
101
+
102
+ # FIXME: usage of image_tag will cause these to render at /images/ which seems
103
+ # very host dependent. I assume this will need links to static urls.
104
+ def attachment_thumbnail
105
+ if file_extension == "pdf" && attachment[:thumbnail_url]
106
+ image_tag(attachment[:thumbnail_url])
107
+ elsif file_extension == "html"
108
+ image_tag('pub-cover-html.png')
109
+ elsif %w{doc docx odt}.include?(file_extension)
110
+ image_tag('pub-cover-doc.png')
111
+ elsif %w{xls xlsx ods csv}.include?(file_extension)
112
+ image_tag('pub-cover-spreadsheet.png')
113
+ else
114
+ image_tag('pub-cover.png')
115
+ end
116
+ end
117
+
118
+ def reference
119
+ ref = []
120
+ if attachment[:isbn].present?
121
+ ref << "ISBN " + content_tag(:span, attachment[:isbn], class: "isbn")
122
+ end
123
+
124
+ if attachment[:unique_reference].present?
125
+ ref << content_tag(:span, attachment[:unique_reference], class: "unique_reference")
126
+ end
127
+
128
+ if attachment[:command_paper_number].present?
129
+ ref << content_tag(:span, attachment[:command_paper_number], class: "command_paper_number")
130
+ end
131
+
132
+ if attachment[:hoc_paper_number].present?
133
+ ref << content_tag(:span, "HC #{attachment[:hoc_paper_number]}", class: 'house_of_commons_paper_number') + ' ' +
134
+ content_tag(:span, attachment[:parliamentary_session], class: 'parliamentary_session')
135
+ end
136
+
137
+ ref.join(', ').html_safe
138
+ end
139
+
140
+ # FIXME this has english in it so will cause problems if the locale is not en
141
+ def references_for_title
142
+ references = []
143
+ references << "ISBN: #{attachment[:isbn]}" if attachment[:isbn].present?
144
+ references << "Unique reference: #{attachment[:unique_reference]}" if attachment[:unique_reference].present?
145
+ references << "Command paper number: #{attachment[:command_paper_number]}" if attachment[:command_paper_number].present?
146
+ references << "HC: #{attachment[:hoc_paper_number]} #{attachment[:parliamentary_session]}" if attachment[:hoc_paper_number].present?
147
+ prefix = references.size == 1 ? "and its reference" : "and its references"
148
+ references.any? ? ", #{prefix} (" + references.join(", ") + ")" : ""
149
+ end
150
+
151
+ def references?
152
+ !attachment[:isbn].to_s.empty? || !attachment[:unique_reference].to_s.empty? || !attachment[:command_paper_number].to_s.empty? || !attachment[:hoc_paper_number].to_s.empty?
153
+ end
154
+
155
+ def attachment_class
156
+ attachment[:external?] ? "hosted-externally" : "embedded"
157
+ end
158
+
159
+ def unnumbered_paper?
160
+ attachment[:unnumbered_command_paper?] || attachment[:unnumbered_hoc_paper?]
161
+ end
162
+
163
+ def unnumbered_command_paper?
164
+ attachment[:unnumbered_command_paper?]
165
+ end
166
+
167
+ def download_link
168
+ options = {}
169
+ options[:title] = number_to_human_size(attachment[:file_size]) if attachment[:file_size].present?
170
+ link("<strong>Download #{file_extension.upcase}</strong>", attachment[:url], options)
171
+ end
172
+
173
+ def attachment_attributes
174
+ attributes = []
175
+ if file_extension == "html"
176
+ attributes << content_tag(:span, 'HTML', class: 'type')
177
+ elsif attachment[:external?]
178
+ attributes << content_tag(:span, url, class: 'url')
179
+ else
180
+ attributes << content_tag(:span, humanized_content_type(file_extension), class: 'type') if file_extension
181
+ attributes << content_tag(:span, number_to_human_size(attachment[:file_size]), class: 'file-size') if attachment[:file_size]
182
+ attributes << content_tag(:span, pluralize(attachment[:number_of_pages], "page"), class: 'page-length') if attachment[:number_of_pages]
183
+ end
184
+ attributes.join(', ').html_safe
185
+ end
186
+
187
+ def preview_url
188
+ url + '/preview'
189
+ end
190
+
191
+ MS_WORD_DOCUMENT_HUMANIZED_CONTENT_TYPE = "MS Word Document".freeze
192
+ MS_EXCEL_SPREADSHEET_HUMANIZED_CONTENT_TYPE = "MS Excel Spreadsheet".freeze
193
+ MS_POWERPOINT_PRESENTATION_HUMANIZED_CONTENT_TYPE = "MS Powerpoint Presentation".freeze
194
+
195
+ def file_abbr_tag(abbr, title)
196
+ content_tag(:abbr, abbr, title: title)
197
+ end
198
+
199
+ def humanized_content_type(file_extension)
200
+ file_extension_vs_humanized_content_type = {
201
+ "chm" => file_abbr_tag('CHM', 'Microsoft Compiled HTML Help'),
202
+ "csv" => file_abbr_tag('CSV', 'Comma-separated Values'),
203
+ "diff" => file_abbr_tag('DIFF', 'Plain text differences'),
204
+ "doc" => MS_WORD_DOCUMENT_HUMANIZED_CONTENT_TYPE,
205
+ "docx" => MS_WORD_DOCUMENT_HUMANIZED_CONTENT_TYPE,
206
+ "dot" => file_abbr_tag('DOT', 'MS Word Document Template'),
207
+ "dxf" => file_abbr_tag('DXF', 'AutoCAD Drawing Exchange Format'),
208
+ "eps" => file_abbr_tag('EPS', 'Encapsulated PostScript'),
209
+ "gif" => file_abbr_tag('GIF', 'Graphics Interchange Format'),
210
+ "gml" => file_abbr_tag('GML', 'Geography Markup Language'),
211
+ "html" => file_abbr_tag('HTML', 'Hypertext Markup Language'),
212
+ "ics" => file_abbr_tag('ICS', 'iCalendar file'),
213
+ "jpg" => "JPEG",
214
+ "odp" => file_abbr_tag('ODP', 'OpenDocument Presentation'),
215
+ "ods" => file_abbr_tag('ODS', 'OpenDocument Spreadsheet'),
216
+ "odt" => file_abbr_tag('ODT', 'OpenDocument Text document'),
217
+ "pdf" => file_abbr_tag('PDF', 'Portable Document Format'),
218
+ "png" => file_abbr_tag('PNG', 'Portable Network Graphic'),
219
+ "ppt" => MS_POWERPOINT_PRESENTATION_HUMANIZED_CONTENT_TYPE,
220
+ "pptx" => MS_POWERPOINT_PRESENTATION_HUMANIZED_CONTENT_TYPE,
221
+ "ps" => file_abbr_tag('PS', 'PostScript'),
222
+ "rdf" => file_abbr_tag('RDF', 'Resource Description Framework'),
223
+ "rtf" => file_abbr_tag('RTF', 'Rich Text Format'),
224
+ "sch" => file_abbr_tag('SCH', 'XML based Schematic'),
225
+ "txt" => "Plain text",
226
+ "wsdl" => file_abbr_tag('WSDL', 'Web Services Description Language'),
227
+ "xls" => MS_EXCEL_SPREADSHEET_HUMANIZED_CONTENT_TYPE,
228
+ "xlsm" => file_abbr_tag('XLSM', 'MS Excel Macro-Enabled Workbook'),
229
+ "xlsx" => MS_EXCEL_SPREADSHEET_HUMANIZED_CONTENT_TYPE,
230
+ "xlt" => file_abbr_tag('XLT', 'MS Excel Spreadsheet Template'),
231
+ "xsd" => file_abbr_tag('XSD', 'XML Schema'),
232
+ "xslt" => file_abbr_tag('XSLT', 'Extensible Stylesheet Language Transformation'),
233
+ "zip" => file_abbr_tag('ZIP', 'Zip archive'),
234
+ }
235
+ file_extension_vs_humanized_content_type.fetch(file_extension.to_s.downcase, '')
236
+ end
237
+
238
+ def previewable?
239
+ file_extension == "csv"
240
+ end
241
+
242
+ def title
243
+ attachment[:title]
244
+ end
245
+
246
+ def file_extension
247
+ # Note: this is a separate parameter rather than being calculated from the
248
+ # filename because at the time of writing not all apps were using the effects
249
+ # of this field.
250
+ attachment[:file_extension]
251
+ end
252
+
253
+ def hide_thumbnail?
254
+ defined?(hide_thumbnail) && hide_thumbnail
255
+ end
256
+
257
+ def attachment_details
258
+ return if previewable?
259
+ link(title, url, title_link_options)
260
+ end
261
+
262
+ def title_link_options
263
+ title_link_options = {}
264
+ title_link_options["rel"] = "external" if attachment[:external?]
265
+ title_link_options["aria-describedby"] = help_block_id unless attachment[:accessible?]
266
+ title_link_options
267
+ end
268
+
269
+ def help_block_id
270
+ "attachment-#{id}-accessibility-help"
271
+ end
272
+
273
+ def link(body, url, options = {})
274
+ options_str = options.map { |k, v| %{#{encode(k)}="#{encode(v)}"} }.join(" ")
275
+ %{<a href="#{encode(url)}" #{options_str}>#{body}</a>}
276
+ end
277
+
278
+ private
279
+
280
+ def encode(text)
281
+ HTMLEntities.new.encode(text)
282
+ end
283
+
284
+ def urlencode(text)
285
+ ERB::Util.url_encode(text)
286
+ end
287
+ end
288
+ end
@@ -0,0 +1,24 @@
1
+ require 'active_support/core_ext/array'
2
+ require 'ostruct'
3
+
4
+ module Govspeak
5
+ class ContactPresenter
6
+ attr_reader :contact
7
+ delegate :id, :content_id, :title, :recipient, :street_address, :postal_code,
8
+ :locality, :region, :country_code, :country_name, :email, :contact_form_url,
9
+ :comments, :worldwide_organisation_path, to: :contact
10
+
11
+ def initialize(contact)
12
+ @contact = OpenStruct.new(contact)
13
+ end
14
+
15
+ def contact_numbers
16
+ Array.wrap(contact[:contact_numbers])
17
+ end
18
+
19
+ def has_postal_address?
20
+ recipient.present? || street_address.present? || locality.present? ||
21
+ region.present? || postal_code.present?
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,71 @@
1
+ module Govspeak
2
+ class HCardPresenter
3
+ def self.from_contact(contact)
4
+ new(contact_properties(contact), contact.country_code)
5
+ end
6
+
7
+ def self.contact_properties(contact)
8
+ { 'fn' => contact.recipient,
9
+ 'street-address' => contact.street_address,
10
+ 'postal-code' => contact.postal_code,
11
+ 'locality' => contact.locality,
12
+ 'region' => contact.region,
13
+ 'country-name' => country_name(contact) }
14
+ end
15
+
16
+ def self.country_name(contact)
17
+ contact.country_name unless contact.country_code == 'GB'
18
+ end
19
+
20
+ def self.property_keys
21
+ ['fn', 'street-address', 'postal-code', 'locality', 'region', 'country-name']
22
+ end
23
+
24
+ def self.address_formats
25
+ @address_formats ||= YAML.load_file('config/address_formats.yml')
26
+ end
27
+
28
+ attr_reader :properties, :country_code
29
+
30
+ def initialize(properties, country_code)
31
+ @properties = properties
32
+ @country_code = country_code
33
+ end
34
+
35
+ def render
36
+ "<p class=\"adr\">\n#{interpolate_address_template}\n</p>\n".html_safe
37
+ end
38
+
39
+ def interpolate_address_property(property_name)
40
+ value = properties[property_name]
41
+
42
+ if value.present?
43
+ "<span class=\"#{property_name}\">#{ERB::Util.html_escape(value)}</span>"
44
+ else
45
+ ""
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def interpolate_address_template
52
+ address = address_template
53
+
54
+ self.class.property_keys.each do |key|
55
+ address.gsub!(/\{\{#{key}\}\}/, interpolate_address_property(key))
56
+ end
57
+
58
+ address.gsub(/^\n/, '') # get rid of blank lines
59
+ .strip # get rid of any trailing whitespace
60
+ .gsub(/\n/, "<br />\n") # add break tags where appropriate
61
+ end
62
+
63
+ def address_template
64
+ (self.class.address_formats[country_code.to_s.downcase] || default_format_string).dup
65
+ end
66
+
67
+ def default_format_string
68
+ self.class.address_formats['gb']
69
+ end
70
+ end
71
+ end
@@ -1,3 +1,3 @@
1
1
  module Govspeak
2
- VERSION = "5.0.0"
2
+ VERSION = "5.0.1"
3
3
  end
@@ -6,27 +6,38 @@ class GovspeakLinkTest < Minitest::Test
6
6
  test "embedded link with link provided" do
7
7
  link = {
8
8
  content_id: "5572fee5-f38f-4641-8ffa-64fed9230ad4",
9
- url: "https://www.example.com/",
10
- title: "Example website"
9
+ url: "https://www.gov.uk/example",
10
+ title: "Example page",
11
11
  }
12
12
  govspeak = "[embed:link:5572fee5-f38f-4641-8ffa-64fed9230ad4]"
13
13
  rendered = Govspeak::Document.new(govspeak, links: link).to_html
14
- expected = %r{<a href="https://www.example.com/">Example website</a>}
14
+ expected = %r{<a href="https://www.gov.uk/example">Example page</a>}
15
15
  assert_match(expected, rendered)
16
16
  end
17
17
 
18
18
  test "embedded link with markdown title" do
19
19
  link = {
20
20
  content_id: "5572fee5-f38f-4641-8ffa-64fed9230ad4",
21
- url: "https://www.example.com/",
22
- title: "**Example website**"
21
+ url: "https://www.gov.uk/example",
22
+ title: "**Example page**",
23
23
  }
24
24
  govspeak = "[embed:link:5572fee5-f38f-4641-8ffa-64fed9230ad4]"
25
25
  rendered = Govspeak::Document.new(govspeak, links: link).to_html
26
- expected = %r{<a href="https://www.example.com/"><strong>Example website</strong></a>}
26
+ expected = %r{<a href="https://www.gov.uk/example"><strong>Example page</strong></a>}
27
27
  assert_match(expected, rendered)
28
28
  end
29
29
 
30
+ test "embedded link with external url" do
31
+ link = {
32
+ content_id: "5572fee5-f38f-4641-8ffa-64fed9230ad4",
33
+ url: "https://www.example.com/",
34
+ title: "Example website",
35
+ }
36
+ govspeak = "[embed:link:5572fee5-f38f-4641-8ffa-64fed9230ad4]"
37
+ rendered = Govspeak::Document.new(govspeak, links: link).to_html
38
+ expected = %r{<a rel="external" href="https://www.example.com/">Example website</a>}
39
+ assert_match(expected, rendered)
40
+ end
30
41
  test "embedded link with url not provided" do
31
42
  link = {
32
43
  content_id: "e510f1c1-4862-4333-889c-8d3acd443fbf",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: govspeak
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0
4
+ version: 5.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Griffiths
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-09-30 00:00:00.000000000 Z
12
+ date: 2016-10-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: kramdown
@@ -251,12 +251,12 @@ files:
251
251
  - lib/govspeak/html_validator.rb
252
252
  - lib/govspeak/kramdown_overrides.rb
253
253
  - lib/govspeak/post_processor.rb
254
+ - lib/govspeak/presenters/attachment_presenter.rb
255
+ - lib/govspeak/presenters/contact_presenter.rb
256
+ - lib/govspeak/presenters/h_card_presenter.rb
254
257
  - lib/govspeak/structured_header_extractor.rb
255
258
  - lib/govspeak/version.rb
256
259
  - lib/kramdown/parser/kramdown_with_automatic_external_links.rb
257
- - lib/presenters/attachment_presenter.rb
258
- - lib/presenters/contact_presenter.rb
259
- - lib/presenters/h_card_presenter.rb
260
260
  - lib/templates/attachment.html.erb
261
261
  - lib/templates/contact.html.erb
262
262
  - lib/templates/inline_attachment.html.erb
@@ -1,286 +0,0 @@
1
- require "action_view"
2
- require "money"
3
- require "htmlentities"
4
-
5
- class AttachmentPresenter
6
- attr_reader :attachment
7
- include ActionView::Helpers::TagHelper
8
- include ActionView::Helpers::NumberHelper
9
- include ActionView::Helpers::AssetTagHelper
10
- include ActionView::Helpers::TextHelper
11
-
12
- def initialize(attachment)
13
- @attachment = attachment
14
- end
15
-
16
- def id
17
- attachment[:id]
18
- end
19
-
20
- def order_url
21
- attachment[:order_url]
22
- end
23
-
24
- def opendocument?
25
- attachment[:opendocument?]
26
- end
27
-
28
- def url
29
- attachment[:url]
30
- end
31
-
32
- def external?
33
- attachment[:external?]
34
- end
35
-
36
- def price
37
- return unless attachment[:price]
38
- Money.from_amount(attachment[:price], 'GBP').format
39
- end
40
-
41
- def accessible?
42
- attachment[:accessible?]
43
- end
44
-
45
- def thumbnail_link
46
- return if hide_thumbnail?
47
- return if previewable?
48
- link(attachment_thumbnail, url, "aria-hidden" => "true", "class" => attachment_class)
49
- end
50
-
51
- def help_block_toggle_id
52
- "attachment-#{id}-accessibility-request"
53
- end
54
-
55
- def section_class
56
- attachment[:external?] ? "hosted-externally" : "embedded"
57
- end
58
-
59
- def mail_to(email_address, name, options = {})
60
- query_string = options.slice(:subject, :body).map { |k, v| "#{urlencode(k)}=#{urlencode(v)}" }.join("&")
61
- "<a href='mailto:#{encode(email_address)}?#{encode(query_string)}'>#{name}</a>"
62
- end
63
-
64
- def alternative_format_order_link
65
- attachment_info = []
66
- attachment_info << " Title: #{title}"
67
- attachment_info << " Original format: #{file_extension}" if file_extension.present?
68
- attachment_info << " ISBN: #{attachment[:isbn]}" if attachment[:isbn].present?
69
- attachment_info << " Unique reference: #{attachment[:unique_reference]}" if attachment[:unique_reference].present?
70
- attachment_info << " Command paper number: #{attachment[:command_paper_number]}" if attachment[:command_paper_number].present?
71
- if attachment[:hoc_paper_number].present?
72
- attachment_info << " House of Commons paper number: #{attachment[:hoc_paper_number]}"
73
- attachment_info << " Parliamentary session: #{attachment[:parliamentary_session]}"
74
- end
75
-
76
- options = {
77
- subject: "Request for '#{title}' in an alternative format",
78
- body: body_for_mail(attachment_info)
79
- }
80
-
81
- mail_to(alternative_format_contact_email, alternative_format_contact_email, options)
82
- end
83
-
84
- def body_for_mail(attachment_info)
85
- <<-END
86
- Details of document required:
87
-
88
- #{attachment_info.join("\n")}
89
-
90
- Please tell us:
91
-
92
- 1. What makes this format unsuitable for you?
93
- 2. What format you would prefer?
94
- END
95
- end
96
-
97
- def alternative_format_contact_email
98
- "govuk-feedback@digital.cabinet-office.gov.uk"
99
- end
100
-
101
- # FIXME: usage of image_tag will cause these to render at /images/ which seems
102
- # very host dependent. I assume this will need links to static urls.
103
- def attachment_thumbnail
104
- if file_extension == "pdf" && attachment[:thumbnail_url]
105
- image_tag(attachment[:thumbnail_url])
106
- elsif file_extension == "html"
107
- image_tag('pub-cover-html.png')
108
- elsif %w{doc docx odt}.include?(file_extension)
109
- image_tag('pub-cover-doc.png')
110
- elsif %w{xls xlsx ods csv}.include?(file_extension)
111
- image_tag('pub-cover-spreadsheet.png')
112
- else
113
- image_tag('pub-cover.png')
114
- end
115
- end
116
-
117
- def reference
118
- ref = []
119
- if attachment[:isbn].present?
120
- ref << "ISBN " + content_tag(:span, attachment[:isbn], class: "isbn")
121
- end
122
-
123
- if attachment[:unique_reference].present?
124
- ref << content_tag(:span, attachment[:unique_reference], class: "unique_reference")
125
- end
126
-
127
- if attachment[:command_paper_number].present?
128
- ref << content_tag(:span, attachment[:command_paper_number], class: "command_paper_number")
129
- end
130
-
131
- if attachment[:hoc_paper_number].present?
132
- ref << content_tag(:span, "HC #{attachment[:hoc_paper_number]}", class: 'house_of_commons_paper_number') + ' ' +
133
- content_tag(:span, attachment[:parliamentary_session], class: 'parliamentary_session')
134
- end
135
-
136
- ref.join(', ').html_safe
137
- end
138
-
139
- # FIXME this has english in it so will cause problems if the locale is not en
140
- def references_for_title
141
- references = []
142
- references << "ISBN: #{attachment[:isbn]}" if attachment[:isbn].present?
143
- references << "Unique reference: #{attachment[:unique_reference]}" if attachment[:unique_reference].present?
144
- references << "Command paper number: #{attachment[:command_paper_number]}" if attachment[:command_paper_number].present?
145
- references << "HC: #{attachment[:hoc_paper_number]} #{attachment[:parliamentary_session]}" if attachment[:hoc_paper_number].present?
146
- prefix = references.size == 1 ? "and its reference" : "and its references"
147
- references.any? ? ", #{prefix} (" + references.join(", ") + ")" : ""
148
- end
149
-
150
- def references?
151
- !attachment[:isbn].to_s.empty? || !attachment[:unique_reference].to_s.empty? || !attachment[:command_paper_number].to_s.empty? || !attachment[:hoc_paper_number].to_s.empty?
152
- end
153
-
154
- def attachment_class
155
- attachment[:external?] ? "hosted-externally" : "embedded"
156
- end
157
-
158
- def unnumbered_paper?
159
- attachment[:unnumbered_command_paper?] || attachment[:unnumbered_hoc_paper?]
160
- end
161
-
162
- def unnumbered_command_paper?
163
- attachment[:unnumbered_command_paper?]
164
- end
165
-
166
- def download_link
167
- options = {}
168
- options[:title] = number_to_human_size(attachment[:file_size]) if attachment[:file_size].present?
169
- link("<strong>Download #{file_extension.upcase}</strong>", attachment[:url], options)
170
- end
171
-
172
- def attachment_attributes
173
- attributes = []
174
- if file_extension == "html"
175
- attributes << content_tag(:span, 'HTML', class: 'type')
176
- elsif attachment[:external?]
177
- attributes << content_tag(:span, url, class: 'url')
178
- else
179
- attributes << content_tag(:span, humanized_content_type(file_extension), class: 'type') if file_extension
180
- attributes << content_tag(:span, number_to_human_size(attachment[:file_size]), class: 'file-size') if attachment[:file_size]
181
- attributes << content_tag(:span, pluralize(attachment[:number_of_pages], "page"), class: 'page-length') if attachment[:number_of_pages]
182
- end
183
- attributes.join(', ').html_safe
184
- end
185
-
186
- def preview_url
187
- url + '/preview'
188
- end
189
-
190
- MS_WORD_DOCUMENT_HUMANIZED_CONTENT_TYPE = "MS Word Document"
191
- MS_EXCEL_SPREADSHEET_HUMANIZED_CONTENT_TYPE = "MS Excel Spreadsheet"
192
- MS_POWERPOINT_PRESENTATION_HUMANIZED_CONTENT_TYPE = "MS Powerpoint Presentation"
193
-
194
- def file_abbr_tag(abbr, title)
195
- content_tag(:abbr, abbr, title: title)
196
- end
197
-
198
- def humanized_content_type(file_extension)
199
- file_extension_vs_humanized_content_type = {
200
- "chm" => file_abbr_tag('CHM', 'Microsoft Compiled HTML Help'),
201
- "csv" => file_abbr_tag('CSV', 'Comma-separated Values'),
202
- "diff" => file_abbr_tag('DIFF', 'Plain text differences'),
203
- "doc" => MS_WORD_DOCUMENT_HUMANIZED_CONTENT_TYPE,
204
- "docx" => MS_WORD_DOCUMENT_HUMANIZED_CONTENT_TYPE,
205
- "dot" => file_abbr_tag('DOT', 'MS Word Document Template'),
206
- "dxf" => file_abbr_tag('DXF', 'AutoCAD Drawing Exchange Format'),
207
- "eps" => file_abbr_tag('EPS', 'Encapsulated PostScript'),
208
- "gif" => file_abbr_tag('GIF', 'Graphics Interchange Format'),
209
- "gml" => file_abbr_tag('GML', 'Geography Markup Language'),
210
- "html" => file_abbr_tag('HTML', 'Hypertext Markup Language'),
211
- "ics" => file_abbr_tag('ICS', 'iCalendar file'),
212
- "jpg" => "JPEG",
213
- "odp" => file_abbr_tag('ODP', 'OpenDocument Presentation'),
214
- "ods" => file_abbr_tag('ODS', 'OpenDocument Spreadsheet'),
215
- "odt" => file_abbr_tag('ODT', 'OpenDocument Text document'),
216
- "pdf" => file_abbr_tag('PDF', 'Portable Document Format'),
217
- "png" => file_abbr_tag('PNG', 'Portable Network Graphic'),
218
- "ppt" => MS_POWERPOINT_PRESENTATION_HUMANIZED_CONTENT_TYPE,
219
- "pptx" => MS_POWERPOINT_PRESENTATION_HUMANIZED_CONTENT_TYPE,
220
- "ps" => file_abbr_tag('PS', 'PostScript'),
221
- "rdf" => file_abbr_tag('RDF', 'Resource Description Framework'),
222
- "rtf" => file_abbr_tag('RTF', 'Rich Text Format'),
223
- "sch" => file_abbr_tag('SCH', 'XML based Schematic'),
224
- "txt" => "Plain text",
225
- "wsdl" => file_abbr_tag('WSDL', 'Web Services Description Language'),
226
- "xls" => MS_EXCEL_SPREADSHEET_HUMANIZED_CONTENT_TYPE,
227
- "xlsm" => file_abbr_tag('XLSM', 'MS Excel Macro-Enabled Workbook'),
228
- "xlsx" => MS_EXCEL_SPREADSHEET_HUMANIZED_CONTENT_TYPE,
229
- "xlt" => file_abbr_tag('XLT', 'MS Excel Spreadsheet Template'),
230
- "xsd" => file_abbr_tag('XSD', 'XML Schema'),
231
- "xslt" => file_abbr_tag('XSLT', 'Extensible Stylesheet Language Transformation'),
232
- "zip" => file_abbr_tag('ZIP', 'Zip archive'),
233
- }
234
- file_extension_vs_humanized_content_type.fetch(file_extension.to_s.downcase, '')
235
- end
236
-
237
- def previewable?
238
- file_extension == "csv"
239
- end
240
-
241
- def title
242
- attachment[:title]
243
- end
244
-
245
- def file_extension
246
- # Note: this is a separate parameter rather than being calculated from the
247
- # filename because at the time of writing not all apps were using the effects
248
- # of this field.
249
- attachment[:file_extension]
250
- end
251
-
252
- def hide_thumbnail?
253
- defined?(hide_thumbnail) && hide_thumbnail
254
- end
255
-
256
- def attachment_details
257
- return if previewable?
258
- link(title, url, title_link_options)
259
- end
260
-
261
- def title_link_options
262
- title_link_options = {}
263
- title_link_options["rel"] = "external" if attachment[:external?]
264
- title_link_options["aria-describedby"] = help_block_id unless attachment[:accessible?]
265
- title_link_options
266
- end
267
-
268
- def help_block_id
269
- "attachment-#{id}-accessibility-help"
270
- end
271
-
272
- def link(body, url, options = {})
273
- options_str = options.map { |k, v| %{#{encode(k)}="#{encode(v)}"} }.join(" ")
274
- %{<a href="#{encode(url)}" #{options_str}>#{body}</a>}
275
- end
276
-
277
- private
278
-
279
- def encode(text)
280
- HTMLEntities.new.encode(text)
281
- end
282
-
283
- def urlencode(text)
284
- ERB::Util.url_encode(text)
285
- end
286
- end
@@ -1,22 +0,0 @@
1
- require 'active_support/core_ext/array'
2
- require 'ostruct'
3
-
4
- class ContactPresenter
5
- attr_reader :contact
6
- delegate :id, :content_id, :title, :recipient, :street_address, :postal_code,
7
- :locality, :region, :country_code, :country_name, :email, :contact_form_url,
8
- :comments, :worldwide_organisation_path, to: :contact
9
-
10
- def initialize(contact)
11
- @contact = OpenStruct.new(contact)
12
- end
13
-
14
- def contact_numbers
15
- Array.wrap(contact[:contact_numbers])
16
- end
17
-
18
- def has_postal_address?
19
- recipient.present? || street_address.present? || locality.present? ||
20
- region.present? || postal_code.present?
21
- end
22
- end
@@ -1,69 +0,0 @@
1
- class HCardPresenter
2
- def self.from_contact(contact)
3
- new(contact_properties(contact), contact.country_code)
4
- end
5
-
6
- def self.contact_properties(contact)
7
- { 'fn' => contact.recipient,
8
- 'street-address' => contact.street_address,
9
- 'postal-code' => contact.postal_code,
10
- 'locality' => contact.locality,
11
- 'region' => contact.region,
12
- 'country-name' => country_name(contact) }
13
- end
14
-
15
- def self.country_name(contact)
16
- contact.country_name unless contact.country_code == 'GB'
17
- end
18
-
19
- def self.property_keys
20
- ['fn', 'street-address', 'postal-code', 'locality', 'region', 'country-name']
21
- end
22
-
23
- def self.address_formats
24
- @address_formats ||= YAML.load_file('config/address_formats.yml')
25
- end
26
-
27
- attr_reader :properties, :country_code
28
-
29
- def initialize(properties, country_code)
30
- @properties = properties
31
- @country_code = country_code
32
- end
33
-
34
- def render
35
- "<p class=\"adr\">\n#{interpolate_address_template}\n</p>\n".html_safe
36
- end
37
-
38
- def interpolate_address_property(property_name)
39
- value = properties[property_name]
40
-
41
- if value.present?
42
- "<span class=\"#{property_name}\">#{ERB::Util.html_escape(value)}</span>"
43
- else
44
- ""
45
- end
46
- end
47
-
48
- private
49
-
50
- def interpolate_address_template
51
- address = address_template
52
-
53
- self.class.property_keys.each do |key|
54
- address.gsub!(/\{\{#{key}\}\}/, interpolate_address_property(key))
55
- end
56
-
57
- address.gsub(/^\n/, '') # get rid of blank lines
58
- .strip # get rid of any trailing whitespace
59
- .gsub(/\n/, "<br />\n") # add break tags where appropriate
60
- end
61
-
62
- def address_template
63
- (self.class.address_formats[country_code.to_s.downcase] || default_format_string).dup
64
- end
65
-
66
- def default_format_string
67
- self.class.address_formats['gb']
68
- end
69
- end