asciidoctor-html 0.1.1 → 0.1.3

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -3
  3. data/CHANGELOG.md +4 -0
  4. data/README.md +28 -11
  5. data/Rakefile +15 -6
  6. data/assets/css/fonts/bootstrap-icons.woff +0 -0
  7. data/assets/css/fonts/bootstrap-icons.woff2 +0 -0
  8. data/assets/css/styles.css +5 -0
  9. data/assets/css/styles.css.map +1 -0
  10. data/exe/adoctohtml +6 -0
  11. data/lib/asciidoctor/html/bi_inline_macro.rb +25 -0
  12. data/lib/asciidoctor/html/book.rb +222 -0
  13. data/lib/asciidoctor/html/cli.rb +112 -0
  14. data/lib/asciidoctor/html/converter.rb +165 -24
  15. data/lib/asciidoctor/html/cref_inline_macro.rb +37 -0
  16. data/lib/asciidoctor/html/figure.rb +10 -10
  17. data/lib/asciidoctor/html/highlightjs.rb +99 -0
  18. data/lib/asciidoctor/html/list.rb +38 -0
  19. data/lib/asciidoctor/html/popovers.rb +49 -0
  20. data/lib/asciidoctor/html/ref_tree_processor.rb +142 -64
  21. data/lib/asciidoctor/html/template.rb +127 -0
  22. data/lib/asciidoctor/html/tree_walker.rb +3 -1
  23. data/lib/asciidoctor/html/utils.rb +6 -0
  24. data/lib/asciidoctor/html/webmanifest.rb +23 -0
  25. data/lib/asciidoctor/html.rb +13 -1
  26. data/lib/minitest/html_plugin.rb +18 -22
  27. metadata +52 -27
  28. data/docs/_config.yml +0 -5
  29. data/docs/_layouts/default.html +0 -25
  30. data/docs/_sass/_custom.scss +0 -35
  31. data/docs/_sass/_example.scss +0 -30
  32. data/docs/_sass/_figure.scss +0 -17
  33. data/docs/_sass/_olist.scss +0 -101
  34. data/docs/_sass/main.scss +0 -40
  35. data/docs/assets/css/fonts +0 -1
  36. data/docs/assets/css/styles.scss +0 -3
  37. data/docs/assets/img/cat1.jpg +0 -0
  38. data/docs/assets/img/cat2.jpg +0 -0
  39. data/docs/assets/img/cat3.jpg +0 -0
  40. data/docs/package-lock.json +0 -59
  41. data/docs/package.json +0 -6
  42. data/docs/site.webmanifest +0 -1
  43. data/lib/asciidoctor/html/olist.rb +0 -18
  44. data/lib/asciidoctor/html/version.rb +0 -7
  45. /data/{docs → assets/favicon}/android-chrome-192x192.png +0 -0
  46. /data/{docs → assets/favicon}/android-chrome-512x512.png +0 -0
  47. /data/{docs → assets/favicon}/apple-touch-icon.png +0 -0
  48. /data/{docs → assets/favicon}/favicon-16x16.png +0 -0
  49. /data/{docs → assets/favicon}/favicon-32x32.png +0 -0
  50. /data/{docs → assets/favicon}/favicon.ico +0 -0
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asciidoctor
4
+ module Html
5
+ # Constants for the highlightjs syntax highlighting library
6
+ module Highlightjs
7
+ CDN_PATH = "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build"
8
+
9
+ INCLUDED_LANGS = {
10
+ "bash" => true,
11
+ "c" => true,
12
+ "cpp" => true,
13
+ "csharp" => true,
14
+ "css" => true,
15
+ "diff" => true,
16
+ "go" => true,
17
+ "graphql" => true,
18
+ "ini" => true,
19
+ "java" => true,
20
+ "javascript" => true,
21
+ "json" => true,
22
+ "kotlin" => true,
23
+ "less" => true,
24
+ "lua" => true,
25
+ "makefile" => true,
26
+ "markdown" => true,
27
+ "objectivec" => true,
28
+ "perl" => true,
29
+ "php" => true,
30
+ "php-template" => true,
31
+ "plaintext" => true,
32
+ "python" => true,
33
+ "python-repl" => true,
34
+ "r" => true,
35
+ "ruby" => true,
36
+ "rust" => true,
37
+ "scss" => true,
38
+ "shell" => true,
39
+ "sql" => true,
40
+ "swift" => true,
41
+ "typescript" => true,
42
+ "vbnet" => true,
43
+ "wasm" => true,
44
+ "xml" => true,
45
+ "yaml" => true
46
+ }.freeze
47
+
48
+ PLUGIN = <<~JS
49
+ function toggleCopyIcon(copyIcon) {
50
+ copyIcon.classList.toggle("bi-clipboard");
51
+ copyIcon.classList.toggle("bi-clipboard-check");
52
+ }
53
+ hljs.addPlugin({
54
+ "after:highlightElement": function({ el, result, text }) {
55
+ const wrapper = el.parentElement; // pre element
56
+ if(wrapper == null) { return; }
57
+
58
+ const overlay = document.createElement("div");
59
+ overlay.classList.add("copy-button");
60
+ overlay.textContent = result.language.toUpperCase() + ' ';
61
+
62
+ const copyButton = document.createElement("button");
63
+ copyButton.classList.add("btn");
64
+ copyButton.setAttribute("type", "button");
65
+ copyButton.setAttribute("data-bs-toggle", "tooltip");
66
+ copyButton.setAttribute("data-bs-title", "Copy to clipboard");
67
+ if(!touch) {bootstrap.Tooltip.getOrCreateInstance(copyButton);}
68
+
69
+ const copyIcon = document.createElement("i");
70
+ copyIcon.classList.add("bi", "bi-clipboard");
71
+
72
+ copyButton.append(copyIcon);
73
+ overlay.append(copyButton);
74
+
75
+ copyButton.onclick = function() {
76
+ navigator.clipboard.writeText(text);
77
+ if(!copyIcon.classList.contains("bi-clipboard-check")) {
78
+ toggleCopyIcon(copyIcon);
79
+ setTimeout(() => { toggleCopyIcon(copyIcon); }, 1500);
80
+ }
81
+ };
82
+
83
+ // Append the copy button to the wrapper
84
+ wrapper.appendChild(overlay);
85
+
86
+ // Find and replace inline callouts
87
+ const rgx = /[\u2460-\u2468]/gu;
88
+ if(text.match(rgx)) {
89
+ text = text.replaceAll(rgx, "");
90
+ el.innerHTML = el.innerHTML.replaceAll(rgx, (match) => {
91
+ return '<i class="bi bi-' + (match.charCodeAt() - 9311) + '-circle"></i>';
92
+ });
93
+ }
94
+ }
95
+ });
96
+ JS
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asciidoctor
4
+ module Html
5
+ # Helper functions for the list conversion.
6
+ module List
7
+ def self.convert(node, tag_name = :ol)
8
+ depth = node.attr "list-depth"
9
+ flat = node.attr? "flat-style"
10
+ level = depth + 1
11
+ classes = [
12
+ "list",
13
+ "list-#{node.context}",
14
+ "level-#{level}",
15
+ flat ? "pseudocode" : node.style,
16
+ node.role
17
+ ].compact
18
+ classes << "checklist" if node.option?("checklist")
19
+ result = [%(<#{tag_name}#{Utils.dyn_id_class_attr_str node, classes.join(" ")}>)]
20
+ node.items.each do |item|
21
+ result << display_list_item(item)
22
+ end
23
+ result << %(</#{tag_name}> <!-- .level-#{level} -->\n)
24
+ Utils.wrap_id_classes_with_title result.join("\n"), node, node.id, "list-wrapper"
25
+ end
26
+
27
+ def self.display_list_item(item)
28
+ result = []
29
+ result << %(<li#{Utils.id_class_attr_str item.id,
30
+ item.role}><div class="li-mark">#{item.attr "mark"}</div>)
31
+ result << %(<div class="li-content"><p>#{item.text}</p>)
32
+ result << "\n#{item.content}" if item.blocks?
33
+ result << %(</div></li>#{Utils.id_class_sel_comment item.id, item.role})
34
+ result.join "\n"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asciidoctor
4
+ module Html
5
+ # Configure the popovers for footnotes and citations.
6
+ module Popovers
7
+ POPOVERS = <<~JS
8
+ function initPopovers() {
9
+ document.querySelectorAll(".btn-po[data-contentid]").forEach(el => {
10
+ const id = el.dataset.contentid;
11
+ let content = document.getElementById(id);
12
+ if(content) {
13
+ if(content.tagName == "A") {
14
+ // This is an anchor of a bibitem
15
+ const listItem = content.parentElement.cloneNode(true)
16
+ listItem.removeChild(listItem.firstChild)
17
+ content = listItem
18
+ }
19
+ new bootstrap.Popover(el, {
20
+ trigger: "focus",
21
+ content: content,
22
+ html: true,
23
+ sanitize: false
24
+ });
25
+ }
26
+ });
27
+ }
28
+ MathJax = {
29
+ startup: {
30
+ pageReady: function() {
31
+ return MathJax.startup.defaultPageReady().then(initPopovers);
32
+ }
33
+ }
34
+ };
35
+ JS
36
+
37
+ TOOLTIPS = <<~JS
38
+ // Only enable tooltips on images if not a touch screen device
39
+ if(!touch) {
40
+ document.querySelectorAll('img[data-bs-toggle="tooltip"]').forEach(el => {
41
+ bootstrap.Tooltip.getOrCreateInstance(el);
42
+ });
43
+ }
44
+ JS
45
+
46
+ INIT = "#{TOOLTIPS}\n#{POPOVERS}".freeze
47
+ end
48
+ end
49
+ end
@@ -1,85 +1,116 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "asciidoctor"
3
4
  require "roman-numerals"
4
5
  require_relative "tree_walker"
6
+ require_relative "highlightjs"
5
7
 
6
8
  module Asciidoctor
7
9
  module Html
8
- # Traverses the document tree and attaches a correct reftext to
9
- # numbered nodes.
10
+ # Traverses the document tree and:
11
+ # - attaches a correct reftext to numbered nodes;
12
+ # - populates the text (= reftext for inline nodes) of anchors at
13
+ # the beginning of a list item for an ordered list;
14
+ # - registers every encountered source code language not included
15
+ # in the default highlightjs build.
10
16
  class RefTreeProcessor < Asciidoctor::Extensions::TreeProcessor
11
17
  NUMBERED_CONTEXTS = {
12
18
  example: "thm-number",
13
19
  table: "tbl-number",
14
- image: "fig-number"
20
+ image: "fig-number",
21
+ stem: "eqn-number",
22
+ listing: "ltg-number"
15
23
  }.freeze
16
24
 
17
- def number_within(document)
18
- return :chapter if document.attr? "chapnum"
19
- return :section if document.attr? "sectnums"
20
-
21
- :document
22
- end
23
-
24
25
  def assign_numeral!(node, document, counter_name)
25
26
  document.counters[counter_name] ||= 0
26
27
  node.numeral = (document.counters[counter_name] += 1)
27
28
  end
28
29
 
29
- def relative_numeral(node, document, sectnum)
30
- if node.numeral
31
- prefix_number = (document.attr("chapnum") || sectnum).to_i
32
- prefix_number.positive? ? "#{prefix_number}.#{node.numeral}" : node.numeral.to_s
33
- else
34
- ""
35
- end
30
+ def relative_numeral(node, document)
31
+ return "" unless node.numeral
32
+
33
+ chapnum = document.attr "chapnum"
34
+ has_prefix = chapnum && !chapnum.empty? && chapnum != "0"
35
+ has_prefix ? "#{chapnum}.#{node.numeral}" : node.numeral.to_s
36
36
  end
37
37
 
38
- def process_numbered_block!(block, document, sectnum)
38
+ def process_numbered_block!(block, document)
39
39
  context = block.context
40
- env = (block.style || context).to_s
41
- env = "figure" if context == :image || env == "figlist"
42
- block.set_attr "showcaption", true
40
+ style = block.style
41
+ context = :image if style == "figlist"
42
+ env = env context, style
43
+ block.set_attr("showcaption", true) unless context == :stem
43
44
  assign_numeral! block, document, NUMBERED_CONTEXTS[context]
44
- title_prefix = "#{env.capitalize} #{relative_numeral block, document, sectnum}"
45
- block.set_attr "reftext", title_prefix
45
+ relative_numeral = relative_numeral block, document
46
+ reftext = if context == :stem
47
+ "(#{relative_numeral})"
48
+ else
49
+ "#{env.capitalize} #{relative_numeral}"
50
+ end
51
+ block.set_attr "reftext", reftext
52
+ end
53
+
54
+ def env(context, style)
55
+ case context
56
+ when :image then "figure"
57
+ when :stem then "equation"
58
+ when :listing then "listing"
59
+ else style || context.to_s
60
+ end
46
61
  end
47
62
 
48
63
  def process_numbered_block?(block)
49
- NUMBERED_CONTEXTS.key?(block.context) || block.style == "figlist"
64
+ context = block.context
65
+ case context
66
+ when :olist
67
+ block.style == "figlist"
68
+ when :stem, :listing
69
+ block.option? "numbered"
70
+ else
71
+ NUMBERED_CONTEXTS.include? context
72
+ end
50
73
  end
51
74
 
52
75
  def li_mark(depth, idx)
53
76
  case depth
54
- when 0
55
- idx + 1
56
- when 1
57
- ("a".."z").to_a[idx]
58
- when 2
59
- RomanNumerals.to_roman(idx + 1).downcase
60
- when 3
61
- ("a".."z").to_a[idx].upcase
77
+ when 1 then ("a".."z").to_a[idx]
78
+ when 2 then RomanNumerals.to_roman(idx + 1).downcase
79
+ when 3 then ("a".."z").to_a[idx].upcase
80
+ else idx + 1
62
81
  end
63
82
  end
64
83
 
65
- def ref_li_mark(mark, depth)
66
- return mark.to_s if depth.even?
84
+ def bullet(depth)
85
+ case depth
86
+ when 1 then "&#8208;"
87
+ when 2 then "&#8727;"
88
+ when 3 then "&#9702;"
89
+ else "&#8226;"
90
+ end
91
+ end
92
+
93
+ def ref_li_mark(mark, depth, style = nil)
94
+ return "[#{mark}]" if style == "bibliography"
67
95
 
68
- "(#{mark})"
96
+ depth.even? ? mark.to_s : "(#{mark})"
69
97
  end
70
98
 
71
99
  def offset(list)
72
100
  list.attr?("start") ? (list.attr("start").to_i - 1) : 0
73
101
  end
74
102
 
75
- def register_reftext!(document, anchor_id, reftext)
76
- node = document.catalog[:refs][anchor_id]
77
- node&.text = reftext
103
+ # Finds an anchor at the start of item.text and updates
104
+ # its reftext to that of item's if necessary.
105
+ def register_reftext!(item, reftext)
106
+ item.set_attr "reftext", reftext
107
+ /\A<a id="(?<anchor_id>.+?)"/ =~ item.text
108
+ node = item.document.catalog[:refs][anchor_id]
109
+ node&.text ||= reftext
78
110
  end
79
111
 
80
112
  def process_olist!(block, depth, flat_style: false)
81
113
  parent_reftext = ""
82
- document = block.document
83
114
  if depth.positive?
84
115
  parent = block.parent
85
116
  parent = parent.parent until parent.context == :list_item
@@ -90,59 +121,106 @@ module Asciidoctor
90
121
  block.set_attr("flat-style", true)
91
122
  else
92
123
  offset = offset block
124
+ style = block.style
93
125
  block.items.each_with_index do |item, idx|
94
- d = block.style == "figlist" ? 1 : depth
126
+ d = style == "figlist" ? 1 : depth
95
127
  mark = li_mark(d, idx + offset)
96
128
  item.set_attr "mark", mark
97
- item_reftext = "#{parent_reftext}#{ref_li_mark mark, d}"
98
- item.set_attr "reftext", item_reftext
99
- /^<a id="(?<id>.+?)"/ =~ item.text
100
- register_reftext! document, id, item_reftext if id
129
+ item_reftext = "#{parent_reftext}#{ref_li_mark mark, d, style}"
130
+ register_reftext! item, item_reftext
101
131
  end
102
132
  end
103
133
  end
104
134
 
135
+ def process_colist!(block)
136
+ block.set_attr "list-depth", 0
137
+ block.items.each_with_index do |item, idx|
138
+ icon = %(<i class="bi bi-#{idx + 1}-circle"></i>)
139
+ item.set_attr "mark", icon
140
+ register_reftext! item, icon
141
+ end
142
+ end
143
+
144
+ def process_ulist!(block, depth)
145
+ block.set_attr "list-depth", depth
146
+ block.items.each do |item|
147
+ is_checkbox = item.attr? "checkbox"
148
+ icon_class = item.attr?("checked") ? "check-" : ""
149
+ icon = %(<i class="bi bi-#{icon_class}square"></i>)
150
+ mark = is_checkbox ? icon : bullet(depth)
151
+ item.role = "checked" if is_checkbox
152
+ item.set_attr "mark", mark
153
+ end
154
+ end
155
+
105
156
  def process_flat_item!(item, idx)
106
157
  mark = li_mark(0, idx)
107
158
  item.set_attr "mark", mark
108
- item.set_attr "reftext", ref_li_mark(mark, 0)
159
+ register_reftext! item, ref_li_mark(mark, 0)
109
160
  end
110
161
 
111
- def reset_counters!(document)
112
- counters = document.counters
113
- NUMBERED_CONTEXTS.each_value do |counter_name|
114
- counters[counter_name] = 0
115
- end
162
+ def process_source_code!(document, lang)
163
+ document.set_attr("source-langs", {}) unless document.attr?("source-langs")
164
+ langs = document.attr "source-langs"
165
+ langs[lang] = true unless Highlightjs::INCLUDED_LANGS.include?(lang)
166
+ end
167
+
168
+ def olist_item?(node)
169
+ node.context == :list_item && node.parent.context == :olist
170
+ end
171
+
172
+ def ulist_item?(node)
173
+ node.context == :list_item && node.parent.context == :ulist
174
+ end
175
+
176
+ def ulist?(node)
177
+ node.context == :ulist
178
+ end
179
+
180
+ def olist?(node)
181
+ node.context == :olist
182
+ end
183
+
184
+ def colist?(node)
185
+ node.context == :colist
186
+ end
187
+
188
+ def source_code?(node)
189
+ node.context == :listing && node.style == "source" && node.attr?("language")
116
190
  end
117
191
 
118
192
  def process(document)
119
- sectnum = 0
120
193
  listdepth = 0
194
+ bulletdepth = 0
121
195
  flat_style = false
122
196
  flat_idx = 0 # flat index for (pseudocode) list
123
- tw = TreeWalker.new(document)
197
+ tw = TreeWalker.new document
124
198
  while (block = tw.next_block)
125
- context = block.context
126
199
  unless block.attr? "refprocessed"
127
- process_numbered_block!(block, document, sectnum) if process_numbered_block?(block)
128
- if context == :section && block.level == 1 && number_within(document) == :section
129
- sectnum += 1
130
- reset_counters! document
131
- elsif context == :olist
200
+ process_numbered_block!(block, document) if process_numbered_block?(block)
201
+ if colist? block
202
+ process_colist! block
203
+ elsif olist? block
132
204
  if listdepth.zero?
133
205
  flat_style = (block.style == "pseudocode")
134
- flat_idx = offset block
206
+ flat_idx = offset block # rubocop:disable Lint/UselessAssignment
135
207
  end
136
- process_olist!(block, listdepth, flat_style:)
137
- elsif context == :list_item && flat_style
138
- process_flat_item!(block, flat_idx)
208
+ process_olist! block, listdepth, flat_style:
209
+ elsif olist_item?(block) && flat_style
210
+ process_flat_item! block, flat_idx
139
211
  flat_idx += 1
212
+ elsif source_code? block
213
+ process_source_code! document, block.attr("language")
214
+ elsif ulist? block
215
+ process_ulist! block, bulletdepth
140
216
  end
141
217
  block.set_attr "refprocessed", true
142
218
  end
143
219
  tw.walk do |move|
144
- listdepth += 1 if context == :olist && move == :explore
145
- listdepth -= 1 if context == :list_item && move == :retreat
220
+ listdepth += 1 if olist?(block) && move == :explore
221
+ listdepth -= 1 if olist_item?(block) && move == :retreat
222
+ bulletdepth += 1 if ulist?(block) && move == :explore
223
+ bulletdepth -= 1 if ulist_item?(block) && move == :retreat
146
224
  end
147
225
  end
148
226
  end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require_relative "highlightjs"
5
+ require_relative "popovers"
6
+
7
+ module Asciidoctor
8
+ module Html
9
+ # The template for the book layout
10
+ module Template
11
+ def self.nav_item(target, text, content = "", active: false)
12
+ active_class = active ? %( class="active") : ""
13
+ link = %(<a href="#{target}">#{text}</a>)
14
+ subnav = content.empty? ? content : "\n#{content}\n"
15
+ %(<li#{active_class}>#{link}#{subnav}</li>\n)
16
+ end
17
+
18
+ def self.nav_text(chapnum, chaptitle)
19
+ return chaptitle if chapnum.empty?
20
+
21
+ %(<span class="title-mark">#{chapnum}</span>#{chaptitle})
22
+ end
23
+
24
+ def self.appendix_title(chapname, numeral, doctitle, num_appendices)
25
+ numeral = num_appendices == 1 ? "" : " #{numeral}"
26
+ %(<span class="title-prefix">#{chapname}#{numeral}</span>#{doctitle})
27
+ end
28
+
29
+ def self.sidebar(nav_items)
30
+ <<~HTML
31
+ <div id="sidebar" class="sidebar collapse collapse-horizontal">
32
+ <nav id="sidenav"><ul>
33
+ #{nav_items.join "\n"}
34
+ </ul></nav>
35
+ </div> <!-- .sidebar -->
36
+ HTML
37
+ end
38
+
39
+ def self.main(content, chapnum, chaptitle, author, year)
40
+ <<~HTML
41
+ <main class="main">
42
+ <div class="content-container">
43
+ <h2>#{nav_text chapnum, chaptitle}</h2>
44
+ #{content}
45
+ #{footer author, year}
46
+ </div>
47
+ </main>
48
+ HTML
49
+ end
50
+
51
+ def self.header(title)
52
+ <<~HTML
53
+ <header class="header">
54
+ <a class="home" href="./">#{title}</a>
55
+ <button type="button" class="btn menu" data-bs-toggle="collapse" data-bs-target="#sidebar"
56
+ aria-expanded="false" aria-controls="sidebar">
57
+ <i class="bi bi-list"></i>
58
+ </button>
59
+ </header>
60
+ HTML
61
+ end
62
+
63
+ def self.footer(author, year)
64
+ %(<footer class="footer">&#169; #{year} #{author}</footer>\n)
65
+ end
66
+
67
+ def self.highlightjs(langs)
68
+ langs.map do |lang|
69
+ %(<script src="#{Highlightjs::CDN_PATH}/languages/#{lang}.min.js"></script>)
70
+ end.join("\n ")
71
+ end
72
+
73
+ def self.head(title, langs)
74
+ <<~HTML
75
+ <head>
76
+ <meta charset="utf-8">
77
+ <meta name="viewport" content="width=device-width, initial-scale=1">
78
+ <title>#{title}</title>
79
+ <link rel="apple-touch-icon" sizes="180x180" href="#{FAVICON_PATH}/apple-touch-icon.png">
80
+ <link rel="icon" type="image/png" sizes="32x32" href="#{FAVICON_PATH}/favicon-32x32.png">
81
+ <link rel="icon" type="image/png" sizes="16x16" href="#{FAVICON_PATH}/favicon-16x16.png">
82
+ <link rel="manifest" href="#{FAVICON_PATH}/site.webmanifest">
83
+ <link rel="stylesheet" href="#{CSS_PATH}/styles.css">
84
+ <link rel="stylesheet" href="#{Highlightjs::CDN_PATH}/styles/tomorrow-night-blue.min.css">
85
+ <script src="#{Highlightjs::CDN_PATH}/highlight.min.js"></script>
86
+ #{highlightjs langs}
87
+ <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
88
+ </head>
89
+ HTML
90
+ end
91
+
92
+ # opts:
93
+ # - title: String
94
+ # - author: String
95
+ # - date: Date
96
+ # - chapnum: Int
97
+ # - chaptitle: String
98
+ # - langs: Array[String]
99
+ def self.html(content, nav_items, opts = {})
100
+ <<~HTML
101
+ <!DOCTYPE html>
102
+ <html lang="en">
103
+ #{head opts[:title], opts[:langs]}
104
+ <body>
105
+ #{header opts[:title]}
106
+ #{sidebar nav_items}
107
+ #{main content, opts[:chapnum], opts[:chaptitle], opts[:author], opts[:date].year}
108
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/js/bootstrap.bundle.min.js"
109
+ integrity="sha384-j1CDi7MgGQ12Z7Qab0qlWQ/Qqz24Gc6BM0thvEMVjHnfYGF0rmFCozFSxQBxwHKO"
110
+ crossorigin="anonymous"></script>
111
+ <script>
112
+ const touch = matchMedia('(hover: none)').matches;
113
+ #{Highlightjs::PLUGIN}
114
+ hljs.highlightAll();
115
+ addEventListener('hashchange', function() {
116
+ collapse = bootstrap.Collapse.getInstance("#sidebar");
117
+ if(collapse) collapse.hide();
118
+ });
119
+ #{Popovers::POPOVERS}
120
+ </script>
121
+ </body>
122
+ </html>
123
+ HTML
124
+ end
125
+ end
126
+ end
127
+ end
@@ -23,7 +23,9 @@ module Asciidoctor
23
23
 
24
24
  if block.blocks? && @level < @max_levels && @idx[@level + 1] < block.blocks.size
25
25
  @level += 1
26
- @path.push(block.blocks[@idx[@level]])
26
+ el = block.blocks[@idx[@level]]
27
+ el = el.last if el.is_a?(Array) # Get the <dd> from an array of <dt>'s followed by <dd>
28
+ @path.push(el)
27
29
  callback.call(:explore)
28
30
  else
29
31
  @idx[@level + 1] = 0 if @level < @max_levels
@@ -65,6 +65,12 @@ module Asciidoctor
65
65
  def self.wrap_id_classes_with_title(content, node, id, classes, needs_prefix: false)
66
66
  show_title?(node) ? wrap_id_classes(display_title(node, needs_prefix:) + content, id, classes) : content
67
67
  end
68
+
69
+ def self.popover_button(content, content_id, classes = nil)
70
+ extra_classes = classes ? " #{classes}" : ""
71
+ attrs = %( tabindex="0" role="button" class="btn-po#{extra_classes}" data-contentid="#{content_id}")
72
+ %(<a#{attrs}>#{content}</a>)
73
+ end
68
74
  end
69
75
  end
70
76
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Asciidoctor
6
+ module Html
7
+ # Generates the site.webmanifest for Android
8
+ module Webmanifest
9
+ def self.generate(name, short_name)
10
+ {
11
+ name:,
12
+ short_name:,
13
+ icons: %w[192x192 512x512].map do |sizes|
14
+ { src: "#{FAVICON_PATH}/android-chrome-#{sizes}.png", sizes:, type: "image/png" }
15
+ end,
16
+ theme_color: "#ffffff",
17
+ background_color: "#ffffff",
18
+ display: "standalone"
19
+ }.to_json
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,5 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "html/version"
3
+ module Asciidoctor
4
+ # Constants in the Html namespace
5
+ module Html
6
+ ASSETS_PATH = "assets"
7
+ FAVICON_PATH = "#{ASSETS_PATH}/favicon".freeze
8
+ CSS_PATH = "#{ASSETS_PATH}/css".freeze
9
+ IMG_PATH = "#{ASSETS_PATH}/img".freeze
10
+ SEARCH_PAGE = "search.html"
11
+ end
12
+ end
13
+
4
14
  require_relative "html/converter"
5
15
  require_relative "html/ref_tree_processor"
16
+ require_relative "html/book"
17
+ require_relative "html/cli"