asciidoctor-html5s 0.1.0.beta.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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.adoc +62 -0
  4. data/asciidoctor-html5s.gemspec +34 -0
  5. data/data/templates/_attribution.html.slim +4 -0
  6. data/data/templates/_footer.html.slim +8 -0
  7. data/data/templates/_footnotes.html.slim +11 -0
  8. data/data/templates/_hdlist.html.slim +20 -0
  9. data/data/templates/_header.html.slim +27 -0
  10. data/data/templates/_qanda.html.slim +12 -0
  11. data/data/templates/_toc.html.slim +4 -0
  12. data/data/templates/admonition.html.slim +10 -0
  13. data/data/templates/audio.html.slim +7 -0
  14. data/data/templates/colist.html.slim +4 -0
  15. data/data/templates/dlist.html.slim +13 -0
  16. data/data/templates/document.html.slim +30 -0
  17. data/data/templates/embedded.html.slim +5 -0
  18. data/data/templates/example.html.slim +2 -0
  19. data/data/templates/floating_title.html.slim +2 -0
  20. data/data/templates/helpers.rb +665 -0
  21. data/data/templates/image.html.slim +3 -0
  22. data/data/templates/inline_anchor.html.slim +12 -0
  23. data/data/templates/inline_break.html.slim +2 -0
  24. data/data/templates/inline_button.html.slim +1 -0
  25. data/data/templates/inline_callout.html.slim +1 -0
  26. data/data/templates/inline_footnote.html.slim +9 -0
  27. data/data/templates/inline_image.html.slim +10 -0
  28. data/data/templates/inline_indexterm.html.slim +2 -0
  29. data/data/templates/inline_kbd.html.slim +7 -0
  30. data/data/templates/inline_menu.html.slim +18 -0
  31. data/data/templates/inline_quoted.html.slim +26 -0
  32. data/data/templates/listing.html.slim +16 -0
  33. data/data/templates/literal.html.slim +2 -0
  34. data/data/templates/olist.html.slim +4 -0
  35. data/data/templates/open.html.slim +7 -0
  36. data/data/templates/outline.html.slim +9 -0
  37. data/data/templates/page_break.html.slim +1 -0
  38. data/data/templates/paragraph.html.slim +6 -0
  39. data/data/templates/pass.html.slim +1 -0
  40. data/data/templates/preamble.html.slim +4 -0
  41. data/data/templates/quote.html.slim +6 -0
  42. data/data/templates/section.html.slim +13 -0
  43. data/data/templates/sidebar.html.slim +4 -0
  44. data/data/templates/stem.html.slim +2 -0
  45. data/data/templates/table.html.slim +39 -0
  46. data/data/templates/thematic_break.html.slim +1 -0
  47. data/data/templates/toc.html.slim +9 -0
  48. data/data/templates/ulist.html.slim +11 -0
  49. data/data/templates/verse.html.slim +7 -0
  50. data/data/templates/video.html.slim +18 -0
  51. data/lib/asciidoctor-html5s.rb +1 -0
  52. data/lib/asciidoctor/html5s.rb +8 -0
  53. data/lib/asciidoctor/html5s/attached_colist_treeprocessor.rb +24 -0
  54. data/lib/asciidoctor/html5s/converter.rb +3009 -0
  55. data/lib/asciidoctor/html5s/version.rb +5 -0
  56. metadata +226 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c16c90fa30631f549c6e9a6f9677919571dc2d69
4
+ data.tar.gz: 73ea5fbfd2ee70c5d361e1c8b6d5ef3ef4b6d804
5
+ SHA512:
6
+ metadata.gz: 9fd08e3a180262d33d54d187fe373286306405b65d0a49e5a295e0b5c7116c790dcf678e4a6300d6a20c43ea4f6746463539b292f177e23a30fc4985e2c7a30e
7
+ data.tar.gz: 87bc0fba47221dd500b4ffe14c1f507aa608d9816ca809b18788c29a3cd8f27eefa49ce488b81122e0f93f3e97669d8faed8a89250d37613d5210f6d46b8ddc2
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright 2014-2017 Jakub Jirutka <jakub@jirutka.cz> and the Asciidoctor Project.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.adoc ADDED
@@ -0,0 +1,62 @@
1
+ = Semantic HTML5 Backend For Asciidoctor
2
+ // custom
3
+ :gem-name: asciidoctor-html5s
4
+ :gh-name: jirutka/{gem-name}
5
+ :gh-branch: master
6
+
7
+ ifdef::env-github[]
8
+ image:https://travis-ci.org/{gh-name}.svg?branch={gh-branch}[Build Status, link="https://travis-ci.org/{gh-name}"]
9
+ image:https://img.shields.io/gem/v/{gem-name}.svg?style=flat[Gem Version, link="https://rubygems.org/gems/{gem-name}"]
10
+ endif::env-github[]
11
+
12
+ This project provides alternative HTML5 converter (backend) for http://asciidoctor.org/[Asciidoctor] that focuses on correct semantics, accessibility and compatibility with common typographic CSS styles.
13
+
14
+
15
+ == Goals
16
+
17
+ * Clean markup with correct HTML5 semantics.
18
+ * Good accessibility for people with disabilities.
19
+ * Compatibility with common typographic CSS styles when possible and especially with GitHub and GitLab.
20
+ * Full standalone converter without fallback to the built-in Asciidoctor converters.
21
+ * Easy to use and integrate into third-party projects.
22
+ * Well readable and maintainable code – this should be never sacrificed for performance (I’m looking at you, Asciidoctor!).
23
+
24
+
25
+ == Non-goals
26
+
27
+ * Compatibility with existing Asciidoctor CSS styles.
28
+
29
+
30
+ == Requirements
31
+
32
+ * https://www.ruby-lang.org/[Ruby] 2.0+ or http://jruby.org/[JRuby] 9.1+
33
+ * https://rubygems.org/gems/asciidoctor/[Asciidoctor] 1.5.5+
34
+ * https://rubygems.org/gems/thread_safe/[thread_safe] (not required, but recommended)
35
+
36
+ Note: This converter consists of https://github.com/slim-template/slim/[Slim] templates, but they are precompiled into pure Ruby code using https://github.com/jirutka/asciidoctor-templates-compiler/[asciidoctor-templates-compiler], so you don’t need Slim to use it!
37
+
38
+
39
+ == Installation
40
+
41
+ Install {gem-name} from Rubygems:
42
+
43
+ [source, sh, subs="+attributes"]
44
+ gem install {gem-name}
45
+
46
+ or to get the latest development version:
47
+
48
+ [source, sh, subs="+attributes"]
49
+ gem install --pre {gem-name}
50
+
51
+
52
+ == Usage
53
+
54
+ [source, sh, subs="+attributes"]
55
+ asciidoctor -r {gem-name} -b html5s FILE...
56
+
57
+
58
+ == License
59
+
60
+ This project is licensed under http://opensource.org/licenses/MIT/[MIT License].
61
+ For the full text of the license, see the link:LICENSE[LICENSE] file.
62
+
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ require File.expand_path('lib/asciidoctor/html5s/version', __dir__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'asciidoctor-html5s'
6
+ s.version = Asciidoctor::Html5s::VERSION
7
+ s.author = 'Jakub Jirutka'
8
+ s.email = 'jakub@jirutka.cz'
9
+ s.homepage = 'https://github.com/jirutka/asciidoctor-html5s'
10
+ s.license = 'MIT'
11
+
12
+ s.summary = 'Semantic HTML5 converter (backend) for Asciidoctor'
13
+ s.description = <<-EOF
14
+ This project provides alternative HTML5 converter (backend) for Asciidoctor
15
+ that focuses on correct semantics, accessibility and compatibility with common
16
+ typographic CSS styles.
17
+ EOF
18
+
19
+ s.files = Dir['data/**/*', 'lib/**/*', '*.gemspec', 'LICENSE*', 'README*']
20
+ s.has_rdoc = false
21
+
22
+ s.required_ruby_version = '>= 2.0'
23
+
24
+ s.add_runtime_dependency 'asciidoctor', '~> 1.5.5'
25
+ s.add_runtime_dependency 'thread_safe', '~> 0.3.4'
26
+
27
+ s.add_development_dependency 'asciidoctor-doctest', '= 2.0.0.beta.4'
28
+ s.add_development_dependency 'asciidoctor-templates-compiler', '~> 0.1.2'
29
+ s.add_development_dependency 'bundler', '~> 1.6'
30
+ s.add_development_dependency 'coderay', '~> 1.1'
31
+ s.add_development_dependency 'rake', '~> 10.0'
32
+ s.add_development_dependency 'slim', '~> 3.0'
33
+ s.add_development_dependency 'slim-htag', '~> 0.1.0'
34
+ end
@@ -0,0 +1,4 @@
1
+ footer
2
+ '&#8212;
3
+ cite
4
+ =[(attr :attribution), (attr :citetitle)].compact.join(', ')
@@ -0,0 +1,8 @@
1
+ #footer-text
2
+ - if attr? :revnumber
3
+ | #{attr 'version-label'} #{attr :revnumber}
4
+ - if attr? 'last-update-label'
5
+ br
6
+ | #{attr 'last-update-label'} #{attr :docdatetime}
7
+ - unless (docinfo_content = (docinfo :footer)).empty?
8
+ =docinfo_content
@@ -0,0 +1,11 @@
1
+ section.footnotes aria-label='Footnotes' role='doc-endnotes'
2
+ hr
3
+ ol.footnotes
4
+ - footnotes.each do |fn|
5
+ li.footnote id=(footnote_id fn.index) role='doc-endnote'
6
+ ="#{fn.text} "
7
+ a.footnote-backref [
8
+ href="##{footnoteref_id fn.index}"
9
+ role='doc-backlink'
10
+ title='Jump to the first occurrence in the text' ]
11
+ | &#8617;
@@ -0,0 +1,20 @@
1
+ = block_with_title :class=>'hdlist'
2
+ table
3
+ - if (attr? :labelwidth) || (attr? :itemwidth)
4
+ colgroup
5
+ col style=style_value(width: [(attr :labelwidth), '%'])
6
+ col style=style_value(width: [(attr :itemwidth), '%'])
7
+ - items.each do |terms, dd|
8
+ tr
9
+ th.hdlist1 class=('strong' if option? 'strong')
10
+ - terms = [*terms]
11
+ - terms.each_with_index do |dt, idx|
12
+ =dt.text
13
+ - unless idx >= terms.count - 1
14
+ br
15
+ td.hdlist2
16
+ - unless dd.nil?
17
+ - if dd.text?
18
+ p =dd.text
19
+ - if dd.blocks?
20
+ =dd.content
@@ -0,0 +1,27 @@
1
+ - if has_header?
2
+ - unless notitle
3
+ h1 =header.title
4
+ - if [:author, :revnumber, :revdate, :revremark].any? {|a| attr? a }
5
+ .details
6
+ - if attr? :author
7
+ span.author#author =(attr :author)
8
+ br
9
+ - if attr? :email
10
+ span.email#email =sub_macros(attr :email)
11
+ br
12
+ - if (authorcount = (attr :authorcount).to_i) > 1
13
+ - (2..authorcount).each do |idx|
14
+ span.author id="author#{idx}" =(attr "author_#{idx}")
15
+ br
16
+ - if attr? "email_#{idx}"
17
+ span.email id="email#{idx}" =sub_macros(attr "email_#{idx}")
18
+ - if attr? :revnumber
19
+ span#revnumber #{((attr 'version-label') || '').downcase} #{attr :revnumber}#{',' if attr? :revdate}
20
+ '
21
+ - if attr? :revdate
22
+ time#revdate datetime=revdate_iso =(attr :revdate)
23
+ - if attr? :revremark
24
+ br
25
+ span#revremark =(attr :revremark)
26
+ - if (attr? :toc) && (attr? 'toc-placement', 'auto')
27
+ include _toc.html
@@ -0,0 +1,12 @@
1
+ = block_with_title :class=>'qlist qanda', :role=>'doc-qna'
2
+ dl.qanda
3
+ - items.each do |questions, answer|
4
+ - [*questions].each do |question|
5
+ dt.qanda-question =question.text
6
+ - unless answer.nil?
7
+ dd.qanda-answer
8
+ - if answer.text?
9
+ = html_tag_if answer.blocks?, :p
10
+ =answer.text
11
+ - if answer.blocks?
12
+ =answer.content
@@ -0,0 +1,4 @@
1
+ nav#toc class=(attr 'toc-class', 'toc') role='doc-toc'
2
+ h2#toctitle =(attr 'toc-title')
3
+ / Renders block_outline.html.
4
+ = converter.convert document, 'outline'
@@ -0,0 +1,10 @@
1
+ - capture
2
+ h6 =(title || caption)
3
+ = html_tag_if !blocks?, :p
4
+ =content
5
+ - if admonition_aside?
6
+ aside.admonitionblock id=id class=[(attr :name), role] role=admonition_aria
7
+ - yield_capture
8
+ - else
9
+ section.admonitionblock id=id class=[(attr :name), role] role=admonition_aria
10
+ - yield_capture
@@ -0,0 +1,7 @@
1
+ = block_with_caption :bottom, :class=>'audioblock'
2
+ audio [
3
+ src=media_uri(attr :target)
4
+ autoplay=(option? 'autoplay')
5
+ controls=!(option? 'nocontrols')
6
+ loop=(option? 'loop') ]
7
+ | Your browser does not support the audio tag.
@@ -0,0 +1,4 @@
1
+ / Note: We ignore title on callout list here.
2
+ ol.colist id=id class=[style, role]
3
+ - items.each do |item|
4
+ li =item.text
@@ -0,0 +1,13 @@
1
+ - case style
2
+ - when 'qanda'
3
+ include _qanda.html
4
+ - when 'horizontal'
5
+ include _hdlist.html
6
+ - else
7
+ = block_with_title :class=>['dlist', style]
8
+ dl
9
+ - items.each do |terms, dd|
10
+ - [*terms].each do |dt|
11
+ dt =dt.text
12
+ - unless dd.nil?
13
+ dd =(print_item_content dd)
@@ -0,0 +1,30 @@
1
+ doctype 5
2
+ html lang=(attr :lang, 'en' unless attr? :nolang)
3
+ head
4
+ meta charset=(attr :encoding, 'UTF-8')
5
+ /[if IE]
6
+ meta http-equiv="X-UA-Compatible" content="IE=edge"
7
+ meta name='viewport' content='width=device-width, initial-scale=1.0'
8
+ meta name='generator' content="Asciidoctor #{attr 'asciidoctor-version'}"
9
+ = html_meta_if 'application-name', (attr 'app-name')
10
+ = html_meta_if 'author', (attr :authors)
11
+ = html_meta_if 'copyright', (attr :copyright)
12
+ = html_meta_if 'description', (attr :description)
13
+ = html_meta_if 'keywords', (attr :keywords)
14
+ title=((doctitle sanitize: true) || (attr 'untitled-label'))
15
+ = styles_and_scripts
16
+ - unless (docinfo_content = docinfo).empty?
17
+ =docinfo_content
18
+ body [
19
+ id=id
20
+ class=[(attr :doctype), ("#{attr 'toc-class'} toc-#{attr 'toc-position', 'left'}" if (attr? 'toc-class') && (attr? :toc) && (attr? 'toc-placement', 'auto'))]
21
+ style=style_value(max_width: (attr 'max-width')) ]
22
+ - unless noheader
23
+ header
24
+ include _header.html
25
+ #content =content
26
+ - unless !footnotes? || (attr? :nofootnotes)
27
+ include _footnotes.html
28
+ - unless nofooter
29
+ footer
30
+ include _footer.html
@@ -0,0 +1,5 @@
1
+ - unless notitle || !has_header?
2
+ h1 id=id =header.title
3
+ =content
4
+ - unless !footnotes? || (attr? :nofootnotes)
5
+ include _footnotes.html
@@ -0,0 +1,2 @@
1
+ = block_with_caption :top, :class=>'exampleblock'
2
+ =content
@@ -0,0 +1,2 @@
1
+ h level=(level + 1) id=id class=[style, role]
2
+ =title
@@ -0,0 +1,665 @@
1
+ require 'asciidoctor'
2
+ require 'asciidoctor/html5s'
3
+ require 'date'
4
+ require 'json'
5
+
6
+ # Needed only in compile-time.
7
+ require 'slim-htag' if defined? Slim
8
+
9
+ if Gem::Version.new(Asciidoctor::VERSION) <= Gem::Version.new('1.5.1')
10
+ fail 'asciidoctor: FAILED: HTML5/Slim backend needs Asciidoctor >=1.5.2!'
11
+ end
12
+
13
+ # Add custom functions to this module that you want to use in your Slim
14
+ # templates. Within the template you can invoke them as top-level functions
15
+ # just like in Haml.
16
+ module Slim::Helpers
17
+
18
+ # URIs of external assets.
19
+ FONT_AWESOME_URI = '//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.1.0/css/font-awesome.min.css'
20
+ HIGHLIGHTJS_BASE_URI = '//cdnjs.cloudflare.com/ajax/libs/highlight.js/7.4'
21
+ MATHJAX_JS_URI = '//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-MML-AM_HTMLorMML'
22
+
23
+ # Defaults
24
+ DEFAULT_HIGHLIGHTJS_THEME = 'github'
25
+ DEFAULT_SECTNUMLEVELS = 3
26
+ DEFAULT_TOCLEVELS = 2
27
+
28
+ # The MathJax configuration.
29
+ MATHJAX_CONFIG = {
30
+ tex2jax: {
31
+ inlineMath: [::Asciidoctor::INLINE_MATH_DELIMITERS[:latexmath].inspect],
32
+ displayMath: [::Asciidoctor::BLOCK_MATH_DELIMITERS[:latexmath].inspect],
33
+ ignoreClass: 'nostem|nolatexmath',
34
+ },
35
+ asciimath2jax: {
36
+ delimiters: [::Asciidoctor::BLOCK_MATH_DELIMITERS[:asciimath].inspect],
37
+ ignoreClass: 'nostem|noasciimath',
38
+ }
39
+ }.to_json
40
+
41
+ VOID_ELEMENTS = %w(area base br col command embed hr img input keygen link
42
+ meta param source track wbr)
43
+
44
+
45
+ ##
46
+ # Captures the given block for later yield.
47
+ #
48
+ # @example Basic capture usage.
49
+ # - capture
50
+ # img src=image_uri
51
+ # - if title?
52
+ # figure.image
53
+ # - yield_capture
54
+ # figcaption =captioned_title
55
+ # - else
56
+ # - yield_capture
57
+ #
58
+ # @example Capture with passing parameters.
59
+ # - capture do |id|
60
+ # img src=image_uri
61
+ # - if title?
62
+ # figure id=@id
63
+ # - yield_capture
64
+ # figcaption =caption
65
+ # - else
66
+ # - yield_capture @id
67
+ #
68
+ # @see yield_capture
69
+ def capture(&block)
70
+ @_html5s_capture = block
71
+ nil
72
+ end
73
+
74
+ ##
75
+ # Yields the captured block (see {#capture}).
76
+ #
77
+ # @param *params parameters to pass to the block.
78
+ # @return A content of the captured block.
79
+ # @see capture
80
+ def yield_capture(*params)
81
+ @_html5s_capture.call(*params) if @_html5s_capture
82
+ end
83
+
84
+ ##
85
+ # Creates an HTML tag with the given name and optionally attributes. Can take
86
+ # a block that will run between the opening and closing tags.
87
+ #
88
+ # @param name [#to_s] the name of the tag.
89
+ # @param attributes [Hash] (default: {})
90
+ # @param content [#to_s] the content; +nil+ to call the block. (default: nil).
91
+ # @yield The block of Slim/HTML code within the tag (optional).
92
+ # @return [String] a rendered HTML element.
93
+ #
94
+ def html_tag(name, attributes = {}, content = nil)
95
+ attrs = attributes.inject([]) do |attrs, (k, v)|
96
+ next attrs if !v || v.nil_or_empty?
97
+ v = v.compact.join(' ') if v.is_a? Array
98
+ attrs << (v == true ? k : %(#{k}="#{v}"))
99
+ end
100
+ attrs_str = attrs.empty? ? '' : attrs.join(' ').prepend(' ')
101
+
102
+ if VOID_ELEMENTS.include? name.to_s
103
+ %(<#{name}#{attrs_str}>)
104
+ else
105
+ content ||= yield if block_given?
106
+ %(<#{name}#{attrs_str}>#{content}</#{name}>)
107
+ end
108
+ end
109
+
110
+ ##
111
+ # Conditionally wraps a block in an element. If condition is +true+ then it
112
+ # renders the specified tag with optional attributes and the given
113
+ # block inside, otherwise it just renders the block.
114
+ #
115
+ # For example:
116
+ #
117
+ # = html_tag_if link?, 'a', {class: 'image', href: (attr :link)}
118
+ # img src='./img/tux.png'
119
+ #
120
+ # will produce:
121
+ #
122
+ # <a href="http://example.org" class="image">
123
+ # <img src="./img/tux.png">
124
+ # </a>
125
+ #
126
+ # if +link?+ is truthy, and just
127
+ #
128
+ # <img src="./img/tux.png">
129
+ #
130
+ # otherwise.
131
+ #
132
+ # @param condition [Boolean] the condition to test to determine whether to
133
+ # render the enclosing tag.
134
+ # @param name (see #html_tag)
135
+ # @param attributes (see #html_tag)
136
+ # @param content (see #html_tag)
137
+ # @yield (see #html_tag)
138
+ # @return [String] a rendered HTML fragment.
139
+ #
140
+ def html_tag_if(condition, name, attributes = {}, content = nil, &block)
141
+ if condition
142
+ html_tag name, attributes, content, &block
143
+ else
144
+ content || yield
145
+ end
146
+ end
147
+
148
+ ##
149
+ # Wraps a block in a div element with the specified class and optionally
150
+ # the node's +id+ and +role+(s). If the node's +title+ is not empty, then a
151
+ # nested div with the class "title" and the title's content is added as well.
152
+ #
153
+ # @example When @id, @role and @title attributes are set.
154
+ # = block_with_title :class=>['quoteblock', 'center']
155
+ # blockquote =content
156
+ #
157
+ # <section id="myid" class="quoteblock center myrole1 myrole2">
158
+ # <h6>Block Title</h6>
159
+ # <blockquote>Lorem ipsum</blockquote>
160
+ # </section>
161
+ #
162
+ # @example When @id, @role and @title attributes are empty.
163
+ # = block_with_title :class=>'quoteblock center', :style=>style_value(float: 'left')
164
+ # blockquote =content
165
+ #
166
+ # <div class="quoteblock center" style="float: left;">
167
+ # <blockquote>Lorem ipsum</blockquote>
168
+ # </div>
169
+ #
170
+ # @example When shorthand style for class attribute is used.
171
+ # = block_with_title 'quoteblock center'
172
+ # blockquote =content
173
+ #
174
+ # <div class="quoteblock center">
175
+ # <blockquote>Lorem ipsum</blockquote>
176
+ # </div>
177
+ #
178
+ # @param attrs [Hash, String] the tag's attributes as Hash),
179
+ # or the tag's class if it's not a Hash.
180
+ # @param title [String, nil] the title.
181
+ # @yield The block of Slim/HTML code within the tag (optional).
182
+ # @return [String] a rendered HTML fragment.
183
+ #
184
+ def block_with_title(attrs = {}, title = @title, &block)
185
+ if (klass = attrs[:class]).is_a? String
186
+ klass = klass.split(' ')
187
+ end
188
+ attrs[:class] = [klass, role].flatten.uniq
189
+ attrs[:id] = id
190
+
191
+ if title.nil_or_empty?
192
+ # XXX quick hack
193
+ nested = is_a?(::Asciidoctor::List) &&
194
+ (parent.is_a?(::Asciidoctor::ListItem) || parent.is_a?(::Asciidoctor::List))
195
+ html_tag_if !nested, :div, attrs, yield
196
+ else
197
+ html_tag :section, attrs do
198
+ [html_tag(:h6, {}, title), yield].join("\n")
199
+ end
200
+ end
201
+ end
202
+
203
+ def block_with_caption(position = :bottom, attrs = {}, &block)
204
+ if (klass = attrs[:class]).is_a? String
205
+ klass = klass.split(' ')
206
+ end
207
+ attrs[:class] = [klass, role].flatten.uniq
208
+ attrs[:id] = id
209
+
210
+ if title.nil_or_empty?
211
+ html_tag :div, attrs, yield
212
+ else
213
+ html_tag :figure, attrs do
214
+ ary = [yield, html_tag(:figcaption) { captioned_title }]
215
+ ary.reverse! if position == :top
216
+ ary.compact.join("\n")
217
+ end
218
+ end
219
+ end
220
+
221
+ ##
222
+ # Delimite the given equation as a STEM of the specified type.
223
+ #
224
+ # @param equation [String] the equation to delimite.
225
+ # @param type [#to_sym] the type of the STEM renderer (latexmath, or asciimath).
226
+ # @return [String] the delimited equation.
227
+ #
228
+ def delimit_stem(equation, type)
229
+ if is_a? ::Asciidoctor::Block
230
+ open, close = ::Asciidoctor::BLOCK_MATH_DELIMITERS[type.to_sym]
231
+ else
232
+ open, close = ::Asciidoctor::INLINE_MATH_DELIMITERS[type.to_sym]
233
+ end
234
+
235
+ if !equation.start_with?(open) || !equation.end_with?(close)
236
+ equation = [open, equation, close].join
237
+ end
238
+ equation
239
+ end
240
+
241
+ ##
242
+ # Formats the given hash as CSS declarations for an inline style.
243
+ #
244
+ # @example
245
+ # style_value(text_align: 'right', float: 'left')
246
+ # => "text-align: right; float: left;"
247
+ #
248
+ # style_value(text_align: nil, float: 'left')
249
+ # => "float: left;"
250
+ #
251
+ # style_value(width: [90, '%'], height: '50px')
252
+ # => "width: 90%; height: 50px;"
253
+ #
254
+ # style_value(width: ['120px', 'px'])
255
+ # => "width: 90px;"
256
+ #
257
+ # style_value(width: [nil, 'px'])
258
+ # => nil
259
+ #
260
+ # @param declarations [Hash]
261
+ # @return [String, nil]
262
+ #
263
+ def style_value(declarations)
264
+ decls = []
265
+
266
+ declarations.each do |prop, value|
267
+ next if value.nil?
268
+
269
+ if value.is_a? Array
270
+ value, unit = value
271
+ next if value.nil?
272
+ value = value.to_s + unit unless value.end_with? unit
273
+ end
274
+ prop = prop.to_s.gsub('_', '-')
275
+ decls << "#{prop}: #{value}"
276
+ end
277
+
278
+ decls.empty? ? nil : decls.join('; ') + ';'
279
+ end
280
+
281
+ def urlize(*segments)
282
+ path = segments * '/'
283
+ if path.start_with? '//'
284
+ @_uri_scheme ||= document.attr('asset-uri-scheme', 'https')
285
+ path = "#{@_uri_scheme}:#{path}" unless @_uri_scheme.empty?
286
+ end
287
+ normalize_web_path path
288
+ end
289
+
290
+
291
+ ##
292
+ # Gets the value of the specified attribute in this node.
293
+ #
294
+ # This is just an alias for +attr+ method with disabled _inherit_ to make it
295
+ # more clear.
296
+ #
297
+ # @param name [String, Symbol] the name of the attribute to lookup.
298
+ # @param default_val the value to return if the attribute is not found.
299
+ # @return value of the attribute or +default_val+ if not found.
300
+ #
301
+ def local_attr(name, default_val = nil)
302
+ attr(name, default_val, false)
303
+ end
304
+
305
+ ##
306
+ # Checks if the attribute is defined on this node, optionally performing
307
+ # a comparison of its value if +expect_val+ is not nil.
308
+ #
309
+ # This is just an alias for +attr?+ method with disabled _inherit_ to make it
310
+ # more clear.
311
+ #
312
+ # @param name [String, Symbol] the name of the attribute to lookup.
313
+ # @param default_val the expected value of the attribute.
314
+ # @return [Boolean] whether the attribute exists and, if +expect_val+ is
315
+ # specified, whether the value of the attribute matches the +expect_val+.
316
+ #
317
+ def local_attr?(name, expect_val = nil)
318
+ attr?(name, expect_val, false)
319
+ end
320
+
321
+ ##
322
+ # @param index [Integer] the footnote's index.
323
+ # @return [String] footnote id to be used in a link.
324
+ def footnote_id(index = local_attr(:index))
325
+ "_footnote_#{index}"
326
+ end
327
+
328
+ ##
329
+ # @param index (see #footnote_id)
330
+ # @return [String] footnoteref id to be used in a link.
331
+ def footnoteref_id(index = local_attr(:index))
332
+ "_footnoteref_#{index}"
333
+ end
334
+
335
+ def icons?
336
+ document.attr? :icons
337
+ end
338
+
339
+ def font_icons?
340
+ document.attr? :icons, 'font'
341
+ end
342
+
343
+ def nowrap?
344
+ 'nowrap' if !document.attr?(:prewrap) || option?('nowrap')
345
+ end
346
+
347
+ def print_item_content(item)
348
+ wrap = item.blocks? && !item.blocks.all? { |b| b.is_a? ::Asciidoctor::List }
349
+ [ (html_tag_if(wrap, :p) { item.text } if item.text?), item.content ].join
350
+ end
351
+
352
+ ##
353
+ # Returns corrected section level.
354
+ #
355
+ # @param sec [Asciidoctor::Section] the section node (default: self).
356
+ # @return [Integer]
357
+ #
358
+ def section_level(sec = self)
359
+ @_section_level ||= (sec.level == 0 && sec.special) ? 1 : sec.level
360
+ end
361
+
362
+ ##
363
+ # Returns the captioned section's title, optionally numbered.
364
+ #
365
+ # @param sec [Asciidoctor::Section] the section node (default: self).
366
+ # @return [String]
367
+ #
368
+ def section_title(sec = self)
369
+ sectnumlevels = document.attr(:sectnumlevels, DEFAULT_SECTNUMLEVELS).to_i
370
+
371
+ if sec.numbered && !sec.caption && sec.level <= sectnumlevels
372
+ [sec.sectnum, sec.captioned_title].join(' ')
373
+ else
374
+ sec.captioned_title
375
+ end
376
+ end
377
+
378
+ def link_rel
379
+ 'noopener' if option?('noopener') || attr(:window) == '_blank'
380
+ end
381
+
382
+ #--------------------------------------------------------
383
+ # block_admonition
384
+ #
385
+
386
+ ##
387
+ # @return [Boolean] should be this admonition wrapped in aside element?
388
+ def admonition_aside?
389
+ %w[note tip].include? attr(:name)
390
+ end
391
+
392
+ ##
393
+ # @return [String, nil] WAI-ARIA role of this admonition.
394
+ def admonition_aria
395
+ case attr(:name)
396
+ when 'note'
397
+ 'note' # https://www.w3.org/TR/wai-aria/roles#note
398
+ when 'tip'
399
+ 'doc-tip' # https://www.w3.org/TR/dpub-aria-1.0/#doc-tip
400
+ when 'caution', 'important', 'warning'
401
+ 'doc-notice' # https://www.w3.org/TR/dpub-aria-1.0/#doc-notice
402
+ end
403
+ end
404
+
405
+ #--------------------------------------------------------
406
+ # block_listing
407
+ #
408
+
409
+ ##
410
+ # @return [String] a canonical name of the source-highlighter to be used as
411
+ # a style class.
412
+ def highlighter
413
+ @_highlighter ||=
414
+ case (highlighter = document.attr('source-highlighter'))
415
+ when 'coderay'; 'CodeRay'
416
+ when 'highlight.js'; 'highlightjs'
417
+ else highlighter
418
+ end
419
+ end
420
+
421
+ ##
422
+ # Returns the callout list attached to this listing node, or +nil+ if none.
423
+ #
424
+ # Note: This variable is set by extension
425
+ # {Asciidoctor::Html5s::AttachedColistTreeprocessor}.
426
+ #
427
+ # @return [Asciidoctor::List, nil]
428
+ def callout_list
429
+ @html5s_colist
430
+ end
431
+
432
+ def source_lang
433
+ local_attr :language, false
434
+ end
435
+
436
+ #--------------------------------------------------------
437
+ # block_open
438
+ #
439
+
440
+ ##
441
+ # Returns +true+ if an abstract block is allowed in this document type,
442
+ # otherwise prints warning and returns +false+.
443
+ def abstract_allowed?
444
+ if result = (parent == document && document.doctype == 'book')
445
+ puts 'asciidoctor: WARNING: abstract block cannot be used in a document
446
+ without a title when doctype is book. Excluding block content.'
447
+ end
448
+ !result
449
+ end
450
+
451
+ ##
452
+ # Returns +true+ if a partintro block is allowed in this context, otherwise
453
+ # prints warning and returns +false+.
454
+ def partintro_allowed?
455
+ if result = (level != 0 || parent.context != :section || document.doctype != 'book')
456
+ puts "asciidoctor: ERROR: partintro block can only be used when doctype
457
+ is book and it's a child of a book part. Excluding block content."
458
+ end
459
+ !result
460
+ end
461
+
462
+ #--------------------------------------------------------
463
+ # block_table
464
+ #
465
+
466
+ def autowidth?
467
+ option? :autowidth
468
+ end
469
+
470
+ def spread?
471
+ if !autowidth? || local_attr?('width')
472
+ 'spread' if attr? :tablepcwidth, 100
473
+ end
474
+ end
475
+
476
+ #--------------------------------------------------------
477
+ # block_video
478
+ #
479
+
480
+ # @return [Boolean] +true+ if the video should be embedded in an iframe.
481
+ def video_iframe?
482
+ ['vimeo', 'youtube'].include? attr(:poster)
483
+ end
484
+
485
+ def video_uri
486
+ case attr(:poster, '').to_sym
487
+ when :vimeo
488
+ params = {
489
+ autoplay: (1 if option? 'autoplay'),
490
+ loop: (1 if option? 'loop')
491
+ }
492
+ start_anchor = "#at=#{attr :start}" if attr? :start
493
+ "//player.vimeo.com/video/#{attr :target}#{start_anchor}#{url_query params}"
494
+
495
+ when :youtube
496
+ video_id, list_id = attr(:target).split('/', 2)
497
+ params = {
498
+ rel: 0,
499
+ start: (attr :start),
500
+ end: (attr :end),
501
+ list: (attr :list, list_id),
502
+ autoplay: (1 if option? 'autoplay'),
503
+ loop: (1 if option? 'loop'),
504
+ controls: (0 if option? 'nocontrols')
505
+ }
506
+ "//www.youtube.com/embed/#{video_id}#{url_query params}"
507
+ else
508
+ anchor = [attr(:start), attr(:end)].join(',').chomp(',')
509
+ anchor.prepend('#t=') unless anchor.empty?
510
+ media_uri "#{attr :target}#{anchor}"
511
+ end
512
+ end
513
+
514
+ # Formats URL query parameters.
515
+ def url_query(params)
516
+ str = params.map { |k, v|
517
+ next if v.nil? || v.to_s.empty?
518
+ [k, v] * '='
519
+ }.compact.join('&amp;')
520
+
521
+ str.prepend('?') unless str.empty?
522
+ end
523
+
524
+ #--------------------------------------------------------
525
+ # document
526
+ #
527
+
528
+ ##
529
+ # @return [String, nil] the revision date in ISO 8601, or nil if not
530
+ # available or in invalid format.
531
+ def revdate_iso
532
+ ::Date.parse(revdate).iso8601
533
+ rescue ArgumentError
534
+ nil
535
+ end
536
+
537
+ ##
538
+ # Returns HTML meta tag if the given +content+ is not +nil+.
539
+ #
540
+ # @param name [#to_s] the name for the metadata.
541
+ # @param content [#to_s, nil] the value of the metadata, or +nil+.
542
+ # @return [String, nil] the meta tag, or +nil+ if the +content+ is +nil+.
543
+ #
544
+ def html_meta_if(name, content)
545
+ %(<meta name="#{name}" content="#{content}">) if content
546
+ end
547
+
548
+ # Returns formatted style/link and script tags for header.
549
+ def styles_and_scripts
550
+ scripts = []
551
+ styles = []
552
+ tags = []
553
+
554
+ stylesheet = attr :stylesheet
555
+ stylesdir = attr :stylesdir, ''
556
+ default_style = ::Asciidoctor::DEFAULT_STYLESHEET_KEYS.include? stylesheet
557
+ linkcss = attr?(:linkcss) || safe >= ::Asciidoctor::SafeMode::SECURE
558
+ ss = ::Asciidoctor::Stylesheets.instance
559
+
560
+ if linkcss
561
+ path = default_style ? ::Asciidoctor::DEFAULT_STYLESHEET_NAME : stylesheet
562
+ styles << { href: [stylesdir, path] }
563
+ elsif default_style
564
+ styles << { text: ss.primary_stylesheet_data }
565
+ else
566
+ styles << { text: read_asset(normalize_system_path(stylesheet, stylesdir), true) }
567
+ end
568
+
569
+ if attr? :icons, 'font'
570
+ if attr? 'iconfont-remote'
571
+ styles << { href: attr('iconfont-cdn', FONT_AWESOME_URI) }
572
+ else
573
+ styles << { href: [stylesdir, "#{attr 'iconfont-name', 'font-awesome'}.css"] }
574
+ end
575
+ end
576
+
577
+ if attr? 'stem'
578
+ scripts << { src: MATHJAX_JS_URI }
579
+ scripts << { type: 'text/x-mathjax-config', text: "MathJax.Hub.Config(#{MATHJAX_CONFIG});" }
580
+ end
581
+
582
+ case attr 'source-highlighter'
583
+ when 'coderay'
584
+ if attr('coderay-css', 'class') == 'class'
585
+ if linkcss
586
+ styles << { href: [stylesdir, ss.coderay_stylesheet_name] }
587
+ else
588
+ styles << { text: ss.coderay_stylesheet_data }
589
+ end
590
+ end
591
+
592
+ when 'highlightjs'
593
+ hjs_base = attr :highlightjsdir, HIGHLIGHTJS_BASE_URI
594
+ hjs_theme = attr 'highlightjs-theme', DEFAULT_HIGHLIGHTJS_THEME
595
+
596
+ scripts << { src: [hjs_base, 'highlight.min.js'] }
597
+ scripts << { src: [hjs_base, 'lang/common.min.js'] }
598
+ scripts << { text: 'hljs.initHighlightingOnLoad()' }
599
+ styles << { href: [hjs_base, "styles/#{hjs_theme}.min.css"] }
600
+ end
601
+
602
+ styles.each do |item|
603
+ if item.key?(:text)
604
+ tags << html_tag(:style) { item[:text] }
605
+ else
606
+ tags << html_tag(:link, rel: 'stylesheet', href: urlize(*item[:href]))
607
+ end
608
+ end
609
+
610
+ scripts.each do |item|
611
+ if item.key? :text
612
+ tags << html_tag(:script, type: item[:type]) { item[:text] }
613
+ else
614
+ tags << html_tag(:script, type: item[:type], src: urlize(*item[:src]))
615
+ end
616
+ end
617
+
618
+ tags.join("\n")
619
+ end
620
+
621
+ #--------------------------------------------------------
622
+ # inline_anchor
623
+ #
624
+
625
+ # @return [String] text of the xref anchor.
626
+ def xref_text
627
+ str =
628
+ if text
629
+ text
630
+ elsif (path = local_attr :path)
631
+ path
632
+ elsif document.respond_to? :catalog # Asciidoctor >=1.5.6
633
+ ref = document.catalog[:refs][attr :refid]
634
+ if ref.kind_of? Asciidoctor::AbstractNode
635
+ ref.xreftext((@_xrefstyle ||= document.attributes['xrefstyle']))
636
+ end
637
+ else # Asciidoctor < 1.5.6
638
+ document.references[:ids][attr :refid || target]
639
+ end
640
+ (str || "[#{attr :refid}]").tr_s("\n", ' ')
641
+ end
642
+
643
+ # @return [String, nil] text of the bibref anchor, or +nil+ if not found.
644
+ def bibref_text
645
+ if document.respond_to? :catalog # Asciidoctor >=1.5.6
646
+ # NOTE: Technically it should be `reftext`, but subs have already been applied to text.
647
+ text
648
+ else
649
+ "[#{target}]"
650
+ end
651
+ end
652
+
653
+ #--------------------------------------------------------
654
+ # inline_image
655
+ #
656
+
657
+ # @return [Array] style classes for a Font Awesome icon.
658
+ def icon_fa_classes
659
+ [ "fa fa-#{target}",
660
+ ("fa-#{attr :size}" if attr? :size),
661
+ ("fa-rotate-#{attr :rotate}" if attr? :rotate),
662
+ ("fa-flip-#{attr :flip}" if attr? :flip)
663
+ ].compact
664
+ end
665
+ end