govspeak 6.5.1 → 6.5.6

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: d1153c71bd7cd224750aa162c1ea63c89a902309cd08238a888d07a60f079d89
4
- data.tar.gz: a7efca4529a1c6017224a77965e45994d30eba3a3e05f6d209dadff85a516cd9
3
+ metadata.gz: cd25c3749cd867192b6243a3d607d83b4b53def14cfb81f9cfaec0f9194052fe
4
+ data.tar.gz: 9ba6512c5ff9c8a410ea424ea880baef90d0a90e1cc8b660755d3428148bb899
5
5
  SHA512:
6
- metadata.gz: 33209bc51e340ccc5a2a0dc3b43f887eddbd464b687232fb4a17c6c34ae0a3097321f7f51da3ba17762e6ad1307424204872c3374dfa99eeb0100992641f4911
7
- data.tar.gz: ab00d13be257f7730b4b7c2a858a9a9eba3802148dfc851f60fdf91eb65acdb80510a5c67a1e599f2cedbf945502cf29d6567526d695872118025ebb384318f9
6
+ metadata.gz: adc39b260589d92b0ba86e2ee04a513677fece35b50e338ba7716a927b4706c7e2782e13b50a4f5158429621dd2248b40d4dda7804bc176e60d8e73ee3f348d2
7
+ data.tar.gz: 46a366da4e41699f7bf182967a71e1cf3cc92f06f477b655eadd939b8ca25dfd1088591dd8d1b83f563d9ebcc49b797726c59ce2c72d9ce6b63a8dc3992f7513
@@ -1,4 +1,25 @@
1
+ ## 6.5.6
2
+
3
+ * Update Kramdown version to 2.3.0 or greater
4
+
5
+ ## 6.5.5
6
+
7
+ * Prevent links in table headers being stripped (PR#187)
8
+
9
+ ## 6.5.4
10
+
11
+ * Require Sanitize to be at least 5.2.1 due to https://nvd.nist.gov/vuln/detail/CVE-2020-4054
12
+
13
+ ## 6.5.3
14
+
15
+ * Use button component for buttons (PR#176)
16
+
17
+ ## 6.5.2
18
+
19
+ * Allow `data` attributes on `div` tags (PR#173)
20
+
1
21
  ## 6.5.1
22
+
2
23
  * Change unicode testing characters after external gem change
3
24
  * Move from govuk-lint to rubocop-govuk
4
25
  * Allow version 6 of actionview
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
@@ -1,15 +1,18 @@
1
1
  require "rake"
2
2
  require "rake/testtask"
3
+ require "rubocop/rake_task"
3
4
  require "bundler"
4
5
 
5
6
  Bundler::GemHelper.install_tasks
6
7
 
8
+ RuboCop::RakeTask.new
9
+
7
10
  desc "Run basic tests"
8
- Rake::TestTask.new("test") { |t|
11
+ Rake::TestTask.new("test") do |t|
9
12
  t.libs << "test"
10
13
  t.pattern = "test/*_test.rb"
11
14
  t.verbose = true
12
15
  t.warning = true
13
- }
16
+ end
14
17
 
15
- task default: [:test]
18
+ task default: %i[test rubocop]
@@ -57,7 +57,9 @@ module Govspeak
57
57
  @links = Array.wrap(options.delete(:links))
58
58
  @contacts = Array.wrap(options.delete(:contacts))
59
59
  @locale = options.fetch(:locale, "en")
60
- @options = { input: PARSER_CLASS_NAME, sanitize: true }.merge(options)
60
+ @options = { input: PARSER_CLASS_NAME,
61
+ sanitize: true,
62
+ syntax_highlighter: nil }.merge(options)
61
63
  @options[:entity_output] = :symbolic
62
64
  end
63
65
 
@@ -108,9 +110,9 @@ module Govspeak
108
110
  source = Govspeak::BlockquoteExtraQuoteRemover.remove(source)
109
111
  source = remove_forbidden_characters(source)
110
112
  self.class.extensions.each do |_, regexp, block|
111
- source.gsub!(regexp) {
113
+ source.gsub!(regexp) do
112
114
  instance_exec(*Regexp.last_match.captures, &block)
113
- }
115
+ end
114
116
  end
115
117
  source
116
118
  end
@@ -127,20 +129,20 @@ module Govspeak
127
129
  end
128
130
 
129
131
  def self.surrounded_by(open, close = nil)
130
- open = Regexp::escape(open)
132
+ open = Regexp.escape(open)
131
133
  if close
132
- close = Regexp::escape(close)
133
- %r+(?:\r|\n|^)#{open}(.*?)#{close} *(\r|\n|$)?+m
134
+ close = Regexp.escape(close)
135
+ %r{(?:\r|\n|^)#{open}(.*?)#{close} *(\r|\n|$)?}m
134
136
  else
135
- %r+(?:\r|\n|^)#{open}(.*?)#{open}? *(\r|\n|$)+m
137
+ %r{(?:\r|\n|^)#{open}(.*?)#{open}? *(\r|\n|$)}m
136
138
  end
137
139
  end
138
140
 
139
141
  def self.wrap_with_div(class_name, character, parser = Kramdown::Document)
140
- extension(class_name, surrounded_by(character)) { |body|
142
+ extension(class_name, surrounded_by(character)) do |body|
141
143
  content = parser ? parser.new("#{body.strip}\n").to_html : body.strip
142
- %{\n<div class="#{class_name}">\n#{content}</div>\n}
143
- }
144
+ %(\n<div class="#{class_name}">\n#{content}</div>\n)
145
+ end
144
146
  end
145
147
 
146
148
  def insert_strong_inside_p(body, parser = Govspeak::Document)
@@ -160,11 +162,11 @@ module Govspeak
160
162
  \s* # any whitespace between opening bracket and link
161
163
  {\/button} # match ending bracket
162
164
  (?:\r|\n|$) # non-capturing match to make sure end of line and linebreak
163
- }x) { |attributes, text, href|
164
- button_classes = "button"
165
- button_classes << " button-start" if attributes =~ /start/
165
+ }x) do |attributes, text, href|
166
+ button_classes = "govuk-button"
166
167
  /cross-domain-tracking:(?<cross_domain_tracking>.[^\s*]+)/ =~ attributes
167
168
  data_attribute = ""
169
+ data_attribute << " data-start='true'" if attributes =~ /start/
168
170
  if cross_domain_tracking
169
171
  data_attribute << " data-module='cross-domain-tracking'"
170
172
  data_attribute << " data-tracking-code='#{cross_domain_tracking.strip}'"
@@ -173,36 +175,36 @@ module Govspeak
173
175
  text = text.strip
174
176
  href = href.strip
175
177
 
176
- %{\n<a role="button" class="#{button_classes}" href="#{href}" #{data_attribute}>#{text}</a>\n}
177
- }
178
+ %(\n<a role="button" class="#{button_classes}" href="#{href}" #{data_attribute}>#{text}</a>\n)
179
+ end
178
180
 
179
- extension("highlight-answer") { |body|
180
- %{\n\n<div class="highlight-answer">
181
- #{Govspeak::Document.new(body.strip).to_html}</div>\n}
182
- }
181
+ extension("highlight-answer") do |body|
182
+ %(\n\n<div class="highlight-answer">
183
+ #{Govspeak::Document.new(body.strip).to_html}</div>\n)
184
+ end
183
185
 
184
- extension("stat-headline", %r${stat-headline}(.*?){/stat-headline}$m) { |body|
185
- %{\n\n<aside class="stat-headline">
186
- #{Govspeak::Document.new(body.strip).to_html}</aside>\n}
187
- }
186
+ extension("stat-headline", %r${stat-headline}(.*?){/stat-headline}$m) do |body|
187
+ %(\n\n<aside class="stat-headline">
188
+ #{Govspeak::Document.new(body.strip).to_html}</aside>\n)
189
+ end
188
190
 
189
191
  # FIXME: these surrounded_by arguments look dodgy
190
- extension("external", surrounded_by("x[", ")x")) { |body|
192
+ extension("external", surrounded_by("x[", ")x")) do |body|
191
193
  Kramdown::Document.new("[#{body.strip}){:rel='external'}").to_html
192
- }
194
+ end
193
195
 
194
- extension("informational", surrounded_by("^")) { |body|
195
- %{\n\n<div role="note" aria-label="Information" class="application-notice info-notice">
196
- #{Govspeak::Document.new(body.strip).to_html}</div>\n}
197
- }
196
+ extension("informational", surrounded_by("^")) do |body|
197
+ %(\n\n<div role="note" aria-label="Information" class="application-notice info-notice">
198
+ #{Govspeak::Document.new(body.strip).to_html}</div>\n)
199
+ end
198
200
 
199
- extension("important", surrounded_by("@")) { |body|
200
- %{\n\n<div role="note" aria-label="Important" class="advisory">#{insert_strong_inside_p(body)}</div>\n}
201
- }
201
+ extension("important", surrounded_by("@")) do |body|
202
+ %(\n\n<div role="note" aria-label="Important" class="advisory">#{insert_strong_inside_p(body)}</div>\n)
203
+ end
202
204
 
203
- extension("helpful", surrounded_by("%")) { |body|
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
+ extension("helpful", surrounded_by("%")) do |body|
206
+ %(\n\n<div role="note" aria-label="Warning" class="application-notice help-notice">\n#{Govspeak::Document.new(body.strip).to_html}</div>\n)
207
+ end
206
208
 
207
209
  extension("barchart", /{barchart(.*?)}/) do |captures|
208
210
  stacked = ".mc-stacked" if captures.include? "stacked"
@@ -210,13 +212,13 @@ module Govspeak
210
212
  negative = ".mc-negative" if captures.include? "negative"
211
213
 
212
214
  [
213
- "{:",
214
- ".js-barchart-table",
215
- stacked,
216
- compact,
217
- negative,
218
- ".mc-auto-outdent",
219
- "}",
215
+ "{:",
216
+ ".js-barchart-table",
217
+ stacked,
218
+ compact,
219
+ negative,
220
+ ".mc-auto-outdent",
221
+ "}",
220
222
  ].join(" ")
221
223
  end
222
224
 
@@ -234,12 +236,12 @@ module Govspeak
234
236
 
235
237
  attachment = AttachmentPresenter.new(attachment)
236
238
 
237
- span_id = attachment.id ? %{ id="attachment_#{attachment.id}"} : ""
239
+ span_id = attachment.id ? %( id="attachment_#{attachment.id}") : ""
238
240
  # new lines inside our title cause problems with govspeak rendering as this is expected to be on one line.
239
241
  title = (attachment.title || "").tr("\n", " ")
240
242
  link = attachment.link(title, attachment.url)
241
243
  attributes = attachment.attachment_attributes.empty? ? "" : " (#{attachment.attachment_attributes})"
242
- %{<span#{span_id} class="attachment-inline">#{link}#{attributes}</span>}
244
+ %(<span#{span_id} class="attachment-inline">#{link}#{attributes}</span>)
243
245
  end
244
246
 
245
247
  # DEPRECATED: use 'Image:image-id' instead
@@ -261,10 +263,10 @@ module Govspeak
261
263
  #
262
264
  # This issue is not considered a bug by kramdown: https://github.com/gettalong/kramdown/issues/191
263
265
  def render_image(image)
264
- id_attr = image.id ? %{ id="attachment_#{image.id}"} : ""
266
+ id_attr = image.id ? %( id="attachment_#{image.id}") : ""
265
267
  lines = []
266
- lines << %{<figure#{id_attr} class="image embedded">}
267
- lines << %{<div class="img"><img src="#{encode(image.url)}" alt="#{encode(image.alt_text)}"></div>}
268
+ lines << %(<figure#{id_attr} class="image embedded">)
269
+ lines << %(<div class="img"><img src="#{encode(image.url)}" alt="#{encode(image.alt_text)}"></div>)
268
270
  lines << image.figcaption_html if image.figcaption?
269
271
  lines << "</figure>"
270
272
  lines.join
@@ -279,9 +281,9 @@ module Govspeak
279
281
  wrap_with_div("example", "$E", Govspeak::Document)
280
282
  wrap_with_div("call-to-action", "$CTA", Govspeak::Document)
281
283
 
282
- extension("address", surrounded_by("$A")) { |body|
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
+ extension("address", surrounded_by("$A")) do |body|
285
+ %(\n<div class="address"><div class="adr org fn"><p>\n#{body.sub("\n", '').gsub("\n", '<br />')}\n</p></div></div>\n)
286
+ end
285
287
 
286
288
  extension("legislative list", /#{NEW_PARAGRAPH_LOOKBEHIND}\$LegislativeList\s*$(.*?)\$EndLegislativeList/m) do |body|
287
289
  Govspeak::KramdownOverrides.with_kramdown_ordered_lists_disabled do
@@ -295,9 +297,9 @@ module Govspeak
295
297
 
296
298
  extension("numbered list", /^[ \t]*((s\d+\.\s.*(?:\n|$))+)/) do |body|
297
299
  body.gsub!(/s(\d+)\.\s(.*)(?:\n|$)/) do
298
- "<li>#{Govspeak::Document.new($2.strip).to_html}</li>\n"
300
+ "<li>#{Govspeak::Document.new(Regexp.last_match(2).strip).to_html}</li>\n"
299
301
  end
300
- %{<ol class="steps">\n#{body}</ol>}
302
+ %(<ol class="steps">\n#{body}</ol>)
301
303
  end
302
304
 
303
305
  def self.devolved_options
@@ -362,13 +364,13 @@ module Govspeak
362
364
  extension("Attachment", /#{NEW_PARAGRAPH_LOOKBEHIND}\[Attachment:\s*(.*?)\s*\]/) do |attachment_id|
363
365
  next "" if attachments.none? { |a| a[:id] == attachment_id }
364
366
 
365
- %{<govspeak-embed-attachment id="#{attachment_id}"></govspeak-embed-attachment>}
367
+ %(<govspeak-embed-attachment id="#{attachment_id}"></govspeak-embed-attachment>)
366
368
  end
367
369
 
368
370
  extension("AttachmentLink", /\[AttachmentLink:\s*(.*?)\s*\]/) do |attachment_id|
369
371
  next "" if attachments.none? { |a| a[:id] == attachment_id }
370
372
 
371
- %{<govspeak-embed-attachment-link id="#{attachment_id}"></govspeak-embed-attachment-link>}
373
+ %(<govspeak-embed-attachment-link id="#{attachment_id}"></govspeak-embed-attachment-link>)
372
374
  end
373
375
 
374
376
  private
@@ -51,12 +51,15 @@ class Govspeak::HtmlSanitizer
51
51
  def sanitize_config
52
52
  Sanitize::Config.merge(
53
53
  Sanitize::Config::RELAXED,
54
- 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],
55
55
  attributes: {
56
56
  :all => Sanitize::Config::RELAXED[:attributes][:all] + %w[role aria-label],
57
- "a" => Sanitize::Config::RELAXED[:attributes]["a"] + [:data],
58
- "th" => Sanitize::Config::RELAXED[:attributes]["th"] + %w[style],
59
- "td" => Sanitize::Config::RELAXED[:attributes]["td"] + %w[style],
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],
61
+ "th" => Sanitize::Config::RELAXED[:attributes]["th"] + %w[style],
62
+ "td" => Sanitize::Config::RELAXED[:attributes]["td"] + %w[style],
60
63
  "govspeak-embed-attachment" => %w[content-id],
61
64
  },
62
65
  )
@@ -1,9 +1,9 @@
1
1
  module Govspeak
2
2
  module KramdownOverrides
3
- # This depends on two internal parts of Kramdown.
3
+ # This depends on two internal parts of Kramdown.
4
4
  # 1. Parser registry (kramdown/parser/kramdown.rb#define_parser)
5
5
  # 2. Kramdown list regexes (kramdown/parser/kramdown/list.rb)
6
- # Updating the Kramdown gem therefore also means updating this file to to
6
+ # Updating the Kramdown gem therefore also means updating this file to to
7
7
  # match Kramdown's internals.
8
8
 
9
9
  def self.with_kramdown_ordered_lists_disabled
@@ -14,9 +14,9 @@ module Govspeak
14
14
  attr_reader :document, :website_root
15
15
 
16
16
  def extract_links
17
- document_anchors.
18
- map { |link| extract_href_from_link(link) }.
19
- reject(&:blank?)
17
+ document_anchors
18
+ .map { |link| extract_href_from_link(link) }
19
+ .reject(&:blank?)
20
20
  end
21
21
 
22
22
  def extract_href_from_link(link)
@@ -92,11 +92,31 @@ module Govspeak
92
92
  end
93
93
 
94
94
  document.css(":not(thead) tr td:first-child").map do |el|
95
- if el.content.match?(/^#($|\s.*$)/)
96
- el.content = el.content.gsub(/^#($|\s)/, "") # Replace '# ' and '#', but not '#Word'.
97
- el.name = "th" if el.content.present? # This also prevents a `th` with nothing inside it; a `td` is preferable.
98
- el[:scope] = "row" if el.content.present? # `scope` shouldn't be used if there's nothing in the table heading.
99
- end
95
+ next unless el.content.match?(/^#($|\s.*$)/)
96
+
97
+ # Replace '# ' and '#', but not '#Word'.
98
+ # This runs on the first child of the element to preserve any links
99
+ el.children.first.content = el.children.first.content.gsub(/^#($|\s)/, "")
100
+ el.name = "th" if el.content.present? # This also prevents a `th` with nothing inside it; a `td` is preferable.
101
+ el[:scope] = "row" if el.content.present? # `scope` shouldn't be used if there's nothing in the table heading.
102
+ end
103
+ end
104
+
105
+ extension("use gem component for buttons") do |document|
106
+ document.css(".govuk-button").map do |el|
107
+ button_html = GovukPublishingComponents.render(
108
+ "govuk_publishing_components/components/button",
109
+ text: el.content,
110
+ href: el["href"],
111
+ start: el["data-start"],
112
+ data_attributes: {
113
+ module: el["data-module"],
114
+ "tracking-code": el["data-tracking-code"],
115
+ "tracking-name": el["data-tracking-name"],
116
+ },
117
+ ).squish.gsub("> <", "><").gsub!(/\s+/, " ")
118
+
119
+ el.swap(button_html)
100
120
  end
101
121
  end
102
122
 
@@ -55,46 +55,46 @@ module Govspeak
55
55
 
56
56
  def humanized_content_type(file_extension)
57
57
  file_extension_vs_humanized_content_type = {
58
- "chm" => file_abbr_tag("CHM", "Microsoft Compiled HTML Help"),
59
- "csv" => file_abbr_tag("CSV", "Comma-separated Values"),
58
+ "chm" => file_abbr_tag("CHM", "Microsoft Compiled HTML Help"),
59
+ "csv" => file_abbr_tag("CSV", "Comma-separated Values"),
60
60
  "diff" => file_abbr_tag("DIFF", "Plain text differences"),
61
- "doc" => MS_WORD_DOCUMENT_HUMANIZED_CONTENT_TYPE,
61
+ "doc" => MS_WORD_DOCUMENT_HUMANIZED_CONTENT_TYPE,
62
62
  "docx" => MS_WORD_DOCUMENT_HUMANIZED_CONTENT_TYPE,
63
- "dot" => file_abbr_tag("DOT", "MS Word Document Template"),
64
- "dxf" => file_abbr_tag("DXF", "AutoCAD Drawing Exchange Format"),
65
- "eps" => file_abbr_tag("EPS", "Encapsulated PostScript"),
66
- "gif" => file_abbr_tag("GIF", "Graphics Interchange Format"),
67
- "gml" => file_abbr_tag("GML", "Geography Markup Language"),
63
+ "dot" => file_abbr_tag("DOT", "MS Word Document Template"),
64
+ "dxf" => file_abbr_tag("DXF", "AutoCAD Drawing Exchange Format"),
65
+ "eps" => file_abbr_tag("EPS", "Encapsulated PostScript"),
66
+ "gif" => file_abbr_tag("GIF", "Graphics Interchange Format"),
67
+ "gml" => file_abbr_tag("GML", "Geography Markup Language"),
68
68
  "html" => file_abbr_tag("HTML", "Hypertext Markup Language"),
69
69
  "ics" => file_abbr_tag("ICS", "iCalendar file"),
70
- "jpg" => "JPEG",
71
- "odp" => file_abbr_tag("ODP", "OpenDocument Presentation"),
72
- "ods" => file_abbr_tag("ODS", "OpenDocument Spreadsheet"),
73
- "odt" => file_abbr_tag("ODT", "OpenDocument Text document"),
74
- "pdf" => file_abbr_tag("PDF", "Portable Document Format"),
75
- "png" => file_abbr_tag("PNG", "Portable Network Graphic"),
76
- "ppt" => MS_POWERPOINT_PRESENTATION_HUMANIZED_CONTENT_TYPE,
70
+ "jpg" => "JPEG",
71
+ "odp" => file_abbr_tag("ODP", "OpenDocument Presentation"),
72
+ "ods" => file_abbr_tag("ODS", "OpenDocument Spreadsheet"),
73
+ "odt" => file_abbr_tag("ODT", "OpenDocument Text document"),
74
+ "pdf" => file_abbr_tag("PDF", "Portable Document Format"),
75
+ "png" => file_abbr_tag("PNG", "Portable Network Graphic"),
76
+ "ppt" => MS_POWERPOINT_PRESENTATION_HUMANIZED_CONTENT_TYPE,
77
77
  "pptx" => MS_POWERPOINT_PRESENTATION_HUMANIZED_CONTENT_TYPE,
78
- "ps" => file_abbr_tag("PS", "PostScript"),
79
- "rdf" => file_abbr_tag("RDF", "Resource Description Framework"),
80
- "rtf" => file_abbr_tag("RTF", "Rich Text Format"),
81
- "sch" => file_abbr_tag("SCH", "XML based Schematic"),
82
- "txt" => "Plain text",
78
+ "ps" => file_abbr_tag("PS", "PostScript"),
79
+ "rdf" => file_abbr_tag("RDF", "Resource Description Framework"),
80
+ "rtf" => file_abbr_tag("RTF", "Rich Text Format"),
81
+ "sch" => file_abbr_tag("SCH", "XML based Schematic"),
82
+ "txt" => "Plain text",
83
83
  "wsdl" => file_abbr_tag("WSDL", "Web Services Description Language"),
84
- "xls" => MS_EXCEL_SPREADSHEET_HUMANIZED_CONTENT_TYPE,
84
+ "xls" => MS_EXCEL_SPREADSHEET_HUMANIZED_CONTENT_TYPE,
85
85
  "xlsm" => file_abbr_tag("XLSM", "MS Excel Macro-Enabled Workbook"),
86
86
  "xlsx" => MS_EXCEL_SPREADSHEET_HUMANIZED_CONTENT_TYPE,
87
- "xlt" => file_abbr_tag("XLT", "MS Excel Spreadsheet Template"),
88
- "xsd" => file_abbr_tag("XSD", "XML Schema"),
87
+ "xlt" => file_abbr_tag("XLT", "MS Excel Spreadsheet Template"),
88
+ "xsd" => file_abbr_tag("XSD", "XML Schema"),
89
89
  "xslt" => file_abbr_tag("XSLT", "Extensible Stylesheet Language Transformation"),
90
- "zip" => file_abbr_tag("ZIP", "Zip archive"),
90
+ "zip" => file_abbr_tag("ZIP", "Zip archive"),
91
91
  }
92
92
  file_extension_vs_humanized_content_type.fetch(file_extension.to_s.downcase, "")
93
93
  end
94
94
 
95
95
  def link(body, url, options = {})
96
- options_str = options.map { |k, v| %{#{encode(k)}="#{encode(v)}"} }.join(" ")
97
- %{<a href="#{encode(url)}" #{options_str}>#{body}</a>}
96
+ options_str = options.map { |k, v| %(#{encode(k)}="#{encode(v)}") }.join(" ")
97
+ %(<a href="#{encode(url)}" #{options_str}>#{body}</a>)
98
98
  end
99
99
 
100
100
  private