asciidoctor-html 0.1.2 → 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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -3
  3. data/README.md +28 -11
  4. data/Rakefile +15 -6
  5. data/assets/css/fonts/bootstrap-icons.woff +0 -0
  6. data/assets/css/fonts/bootstrap-icons.woff2 +0 -0
  7. data/assets/css/styles.css +5 -0
  8. data/assets/css/styles.css.map +1 -0
  9. data/exe/adoctohtml +6 -0
  10. data/lib/asciidoctor/html/bi_inline_macro.rb +25 -0
  11. data/lib/asciidoctor/html/book.rb +222 -0
  12. data/lib/asciidoctor/html/cli.rb +112 -0
  13. data/lib/asciidoctor/html/converter.rb +165 -24
  14. data/lib/asciidoctor/html/cref_inline_macro.rb +37 -0
  15. data/lib/asciidoctor/html/figure.rb +10 -10
  16. data/lib/asciidoctor/html/highlightjs.rb +99 -0
  17. data/lib/asciidoctor/html/list.rb +38 -0
  18. data/lib/asciidoctor/html/popovers.rb +49 -0
  19. data/lib/asciidoctor/html/ref_tree_processor.rb +134 -57
  20. data/lib/asciidoctor/html/template.rb +127 -0
  21. data/lib/asciidoctor/html/tree_walker.rb +3 -1
  22. data/lib/asciidoctor/html/utils.rb +6 -0
  23. data/lib/asciidoctor/html/webmanifest.rb +23 -0
  24. data/lib/asciidoctor/html.rb +13 -1
  25. data/lib/minitest/html_plugin.rb +18 -22
  26. metadata +52 -27
  27. data/docs/_config.yml +0 -5
  28. data/docs/_layouts/default.html +0 -25
  29. data/docs/_sass/_custom.scss +0 -35
  30. data/docs/_sass/_example.scss +0 -30
  31. data/docs/_sass/_figure.scss +0 -17
  32. data/docs/_sass/_olist.scss +0 -101
  33. data/docs/_sass/main.scss +0 -40
  34. data/docs/assets/css/fonts +0 -1
  35. data/docs/assets/css/styles.scss +0 -3
  36. data/docs/assets/img/cat1.jpg +0 -0
  37. data/docs/assets/img/cat2.jpg +0 -0
  38. data/docs/assets/img/cat3.jpg +0 -0
  39. data/docs/package-lock.json +0 -59
  40. data/docs/package.json +0 -6
  41. data/docs/site.webmanifest +0 -1
  42. data/lib/asciidoctor/html/olist.rb +0 -18
  43. data/lib/asciidoctor/html/version.rb +0 -7
  44. /data/{docs → assets/favicon}/android-chrome-192x192.png +0 -0
  45. /data/{docs → assets/favicon}/android-chrome-512x512.png +0 -0
  46. /data/{docs → assets/favicon}/apple-touch-icon.png +0 -0
  47. /data/{docs → assets/favicon}/favicon-16x16.png +0 -0
  48. /data/{docs → assets/favicon}/favicon-32x32.png +0 -0
  49. /data/{docs → assets/favicon}/favicon.ico +0 -0
data/exe/adoctohtml ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "asciidoctor/html"
5
+
6
+ Asciidoctor::Html::CLI.run
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "asciidoctor"
4
+ require "pathname"
5
+
6
+ module Asciidoctor
7
+ module Html
8
+ # Insert an icon from https://icons.getbootstrap.com/
9
+ class BiInlineMacro < Asciidoctor::Extensions::InlineMacroProcessor
10
+ use_dsl
11
+
12
+ named :bi
13
+ name_positional_attributes "size", "color"
14
+
15
+ def process(parent, target, attrs)
16
+ s_attr = c_attr = nil
17
+ s_attr = "font-size:#{attrs["size"]};" if attrs.include?("size")
18
+ c_attr = "color:#{attrs["color"]};" if attrs.include?("color")
19
+ attr_str = s_attr || c_attr ? %( style="#{s_attr}#{c_attr}") : ""
20
+ icon = %(<i class="bi bi-#{target}"#{attr_str}></i>)
21
+ create_inline_pass parent, icon
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "erb"
5
+ require "date"
6
+ require "asciidoctor"
7
+ require_relative "converter"
8
+ require_relative "ref_tree_processor"
9
+ require_relative "cref_inline_macro"
10
+ require_relative "bi_inline_macro"
11
+ require_relative "template"
12
+
13
+ module Asciidoctor
14
+ module Html
15
+ # A book is a collection of documents with cross referencing
16
+ # supported via the cref macro.
17
+ class Book
18
+ attr_reader :title, :author, :date, :chapname,
19
+ :refs, :templates
20
+
21
+ Asciidoctor::Extensions.register do
22
+ tree_processor RefTreeProcessor
23
+ inline_macro CrefInlineMacro
24
+ inline_macro BiInlineMacro
25
+ end
26
+
27
+ DOCATTRS = {
28
+ "sectids" => false,
29
+ "stem" => "latexmath",
30
+ "hide-uri-scheme" => true,
31
+ "source-highlighter" => "highlight.js",
32
+ "imagesdir" => IMG_PATH
33
+ }.freeze
34
+
35
+ DEFAULT_OPTS = {
36
+ title: "Untitled Book",
37
+ author: "Anonymous Author",
38
+ chapname: "Chapter"
39
+ }.freeze
40
+
41
+ # Template data to be processed by each document
42
+ TData = Struct.new("TData", :chapnum, :chaptitle)
43
+
44
+ # opts:
45
+ # - title
46
+ # - author
47
+ # - date
48
+ # - chapname
49
+ def initialize(opts = {})
50
+ opts = DEFAULT_OPTS.merge opts
51
+ @title = ERB::Escape.html_escape opts[:title]
52
+ @author = ERB::Escape.html_escape opts[:author]
53
+ @date = opts.include?(:date) ? Date.parse(opts[:date]) : Date.today
54
+ @se_id = opts[:se_id]
55
+ @chapname = opts[:chapname]
56
+ @refs = {} # Hash(docname => Hash(id => reftext))
57
+ @templates = {} # Hash(docname => TData)
58
+ end
59
+
60
+ # params:
61
+ # - chapters: array of filenames
62
+ # - appendices: array of filenames
63
+ # returns: Hash(file_basename_without_ext => html)
64
+ def read(chapters = [], appendices = [])
65
+ docs = {} # Hash(docname => document)
66
+ chapters.each_with_index do |filename, idx|
67
+ doc = chapter filename, idx
68
+ register! docs, filename, doc
69
+ end
70
+ appendices.each_with_index do |filename, idx|
71
+ doc = appendix filename, idx, appendices.size
72
+ register! docs, filename, doc
73
+ end
74
+ html docs
75
+ end
76
+
77
+ # params:
78
+ # - chapters: array of filenames
79
+ # - appendices: array of filenames
80
+ # - outdir: directory to write the converted html files to
81
+ def write(chapters, appendices, outdir)
82
+ read(chapters, appendices).each do |name, html|
83
+ File.write("#{outdir}/#{name}.html", html)
84
+ end
85
+ File.write("#{outdir}/#{SEARCH_PAGE}", search_page(@se_id)) if @se_id
86
+ end
87
+
88
+ private
89
+
90
+ def search_page(se_id)
91
+ content = <<~HTML
92
+ <script async src="https://cse.google.com/cse.js?cx=#{se_id}"></script>
93
+ <div class="gcse-search"></div>
94
+ HTML
95
+ Template.html(
96
+ content,
97
+ nav_items,
98
+ title: @title,
99
+ author: @author,
100
+ date: @date,
101
+ chapnum: "",
102
+ chaptitle: "Search",
103
+ langs: []
104
+ )
105
+ end
106
+
107
+ def register!(docs, filename, doc)
108
+ key = key filename
109
+ docs[key] = doc
110
+ end
111
+
112
+ def langs(doc)
113
+ doc.attr?("source-langs") ? doc.attr("source-langs").keys : []
114
+ end
115
+
116
+ def doctitle(doc)
117
+ doc.doctitle sanitize: true, use_fallback: true
118
+ end
119
+
120
+ def chapter(filename, idx)
121
+ numeral = idx.to_s
122
+ doc = parse_file filename, @chapname, numeral
123
+ chaptitle = doctitle doc
124
+ chapref = idx.zero? ? chaptitle : chapref_default(@chapname, numeral)
125
+ chapnum = idx.zero? ? "" : numeral
126
+ process_doc key(filename), doc, chapnum:, chaptitle:, chapref:
127
+ end
128
+
129
+ def appendix(filename, idx, num_appendices)
130
+ chapname = "Appendix"
131
+ numeral = ("a".."z").to_a[idx].upcase
132
+ doc = parse_file filename, chapname, numeral
133
+ chapref = num_appendices == 1 ? chapname : chapref_default(chapname, numeral)
134
+ chapnum = ""
135
+ chaptitle = Template.appendix_title chapname, numeral, doctitle(doc), num_appendices
136
+ process_doc key(filename), doc, chapnum:, chaptitle:, chapref:
137
+ end
138
+
139
+ def key(filename)
140
+ Pathname(filename).basename.sub_ext("").to_s
141
+ end
142
+
143
+ # opts:
144
+ # - chapnum
145
+ # - chaptitle
146
+ # - chapref
147
+ def process_doc(key, doc, opts)
148
+ val = doc.catalog[:refs].transform_values(&method(:reftext)).compact
149
+ val["chapref"] = opts[:chapref]
150
+ @refs[key] = val
151
+ @templates[key] = TData.new(
152
+ chapnum: opts[:chapnum],
153
+ chaptitle: opts[:chaptitle]
154
+ )
155
+ doc
156
+ end
157
+
158
+ def parse_file(filename, chapname, numeral)
159
+ attributes = { "chapnum" => numeral, "chapname" => chapname }.merge DOCATTRS
160
+ Asciidoctor.load_file filename, safe: :unsafe, attributes:
161
+ end
162
+
163
+ def chapref_default(chapname, numeral)
164
+ "#{ERB::Escape.html_escape chapname} #{numeral}"
165
+ end
166
+
167
+ def reftext(node)
168
+ node.reftext || (node.title unless node.inline?) || "[#{node.id}]" if node.id
169
+ end
170
+
171
+ def outline(doc)
172
+ items = []
173
+ doc.sections.each do |section|
174
+ next unless section.id && section.level == 1
175
+
176
+ items << Template.nav_item("##{section.id}", section.title)
177
+ end
178
+ items.size > 1 ? "<ul>#{items.join "\n"}</ul>" : ""
179
+ end
180
+
181
+ def html(docs)
182
+ html = {} # Hash(docname => html)
183
+ docs.each do |key, doc|
184
+ html[key] = build_template key, doc
185
+ end
186
+ html
187
+ end
188
+
189
+ def nav_items(active_key = -1, doc = nil)
190
+ items = @templates.map do |k, td|
191
+ active = (k == active_key)
192
+ subnav = active && doc ? outline(doc) : ""
193
+ navtext = Template.nav_text td.chapnum, td.chaptitle
194
+ Template.nav_item "#{k}.html", navtext, subnav, active:
195
+ end
196
+ return items unless @se_id
197
+
198
+ items.unshift(Template.nav_item(
199
+ SEARCH_PAGE,
200
+ %(<i class="bi bi-search"></i> Search),
201
+ active: (active_key == -1)
202
+ ))
203
+ end
204
+
205
+ def build_template(key, doc)
206
+ tdata = @templates[key]
207
+ nav_items = nav_items key, doc
208
+ content = ERB.new(doc.convert).result(binding)
209
+ Template.html(
210
+ content,
211
+ nav_items,
212
+ title: @title,
213
+ author: @author,
214
+ date: @date,
215
+ chapnum: tdata.chapnum,
216
+ chaptitle: tdata.chaptitle,
217
+ langs: langs(doc)
218
+ )
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "filewatcher"
5
+ require "optparse"
6
+ require "pathname"
7
+ require "psych"
8
+ require_relative "book"
9
+ require_relative "webmanifest"
10
+
11
+ module Asciidoctor
12
+ module Html
13
+ # The command line interface
14
+ module CLI
15
+ DEFAULT_OPTIONS = {
16
+ "config-file": "config.yml",
17
+ watch: false
18
+ }.freeze
19
+
20
+ DEFAULT_DIRS = {
21
+ "srcdir" => ".",
22
+ "outdir" => "www"
23
+ }.freeze
24
+
25
+ def self.parse_opts
26
+ options = DEFAULT_OPTIONS.dup
27
+ OptionParser.new do |parser|
28
+ parser.on("-w", "--watch",
29
+ "Watch for file changes in SRCDIR. Default: unset")
30
+ parser.on("-c", "--config-file CONFIG",
31
+ "Location of config file. Default: #{options[:"config-file"]}")
32
+ end.parse!(into: options)
33
+ options
34
+ end
35
+
36
+ def self.read_config(config_file)
37
+ begin
38
+ config = Psych.safe_load_file config_file
39
+ rescue StandardError
40
+ puts "Error opening configuration file #{config_file}"
41
+ exit 1
42
+ end
43
+ config_dir = Pathname(config_file).dirname
44
+ %w[outdir srcdir].each do |prop|
45
+ config[prop] = File.expand_path(config[prop] || DEFAULT_DIRS[prop], config_dir)
46
+ end
47
+ %w[chapters appendices].each do |prop|
48
+ config[prop] &&= config[prop].map do |f|
49
+ File.expand_path(f, config_dir)
50
+ end
51
+ end
52
+ config
53
+ end
54
+
55
+ def self.setup_outdir(outdir)
56
+ assets_dir = "#{outdir}/#{ASSETS_PATH}"
57
+ FileUtils.mkdir_p assets_dir unless File.directory?(assets_dir)
58
+ rootdir = File.absolute_path "#{__dir__}/../../.."
59
+ %W[#{CSS_PATH} #{FAVICON_PATH}].each do |p|
60
+ dir = "#{outdir}/#{p}"
61
+ next if Dir.exist?(dir)
62
+
63
+ puts "Generating #{dir}"
64
+ FileUtils.cp_r "#{rootdir}/#{p}", assets_dir
65
+ end
66
+ end
67
+
68
+ def self.generate_webmanifest(outdir, name, short_name)
69
+ filename = "#{outdir}/#{FAVICON_PATH}/site.webmanifest"
70
+ puts "Generating #{filename}"
71
+ File.write filename, Webmanifest.generate(name, short_name)
72
+ end
73
+
74
+ def self.generate_bookopts(config)
75
+ book_opts = {}
76
+ %i[title short_title author date se_id chapname].each do |opt|
77
+ key = opt.to_s
78
+ book_opts[opt] = config[key] if config.include?(key)
79
+ end
80
+ book_opts[:short_title] ||= book_opts[:title]
81
+ book_opts
82
+ end
83
+
84
+ def self.run(opts = nil)
85
+ opts ||= parse_opts
86
+ config = read_config opts[:"config-file"]
87
+ outdir = config["outdir"]
88
+ book_opts = generate_bookopts config
89
+ setup_outdir outdir
90
+ generate_webmanifest outdir, book_opts[:title], book_opts[:short_title]
91
+ book = Book.new book_opts
92
+ puts "Writing book to #{outdir}"
93
+ book.write config["chapters"], config["appendices"], config["outdir"]
94
+ return unless opts[:watch]
95
+
96
+ Filewatcher.new("#{config["srcdir"]}/*.adoc").watch do |changes|
97
+ chapters = []
98
+ appendices = []
99
+ changes.each_key do |filename|
100
+ puts "Detected change in #{filename}"
101
+ chapters.append(filename) if config["chapters"].include?(filename)
102
+ appendices.append(filename) if config["appendices"].include?(filename)
103
+ end
104
+ puts "Regenerating book:"
105
+ puts " Chapters: #{chapters.map { |c| Pathname(c).basename }.join ", "}" unless chapters.empty?
106
+ puts " Appendices: #{appendices.map { |a| Pathname(a).basename }.join ", "}" unless appendices.empty?
107
+ book.write chapters, appendices, config["outdir"]
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "asciidoctor"
4
- require_relative "olist"
4
+ require_relative "list"
5
5
  require_relative "utils"
6
6
  require_relative "figure"
7
7
 
@@ -13,59 +13,200 @@ module Asciidoctor
13
13
 
14
14
  include Figure
15
15
 
16
+ def convert_embedded(node)
17
+ result = [node.content]
18
+ if node.footnotes?
19
+ result << %(<div class="footnotes">)
20
+ node.footnotes.each do |fn|
21
+ result << %(<div class="footnote" id="_footnotedef_#{fn.index}">#{fn.text}</div>)
22
+ end
23
+ result << %(</div>)
24
+ end
25
+ result.join("\n")
26
+ end
27
+
28
+ def convert_preamble(node)
29
+ %(<div class="preamble">\n#{node.content}</div> <!-- .preamble -->\n)
30
+ end
31
+
16
32
  def convert_section(node)
17
33
  document = node.document
18
34
  level = node.level
19
35
  show_sectnum = node.numbered && level <= (document.attr("sectnumlevels") || 1).to_i
20
36
  tag_name = %(h#{[level + 2, 6].min})
21
37
  sectnum = show_sectnum ? %(<span class="title-mark">#{node.sectnum ""}</span>) : ""
22
- content = %(<#{tag_name}>#{sectnum}#{node.title}) +
23
- %(</#{tag_name}>\n#{node.content})
38
+ content = %(<#{tag_name}>#{sectnum}#{node.title}</#{tag_name}>\n#{node.content})
24
39
  Utils.wrap_node content, node, :section
25
40
  end
26
41
 
27
42
  def convert_paragraph(node)
28
- content = %(<p>#{node.content}</p>\n)
43
+ content = %(<p#{Utils.dyn_id_class_attr_str node, node.role}>#{node.content}</p>\n)
29
44
  Utils.wrap_node_with_title content, node
30
45
  end
31
46
 
47
+ def convert_quote(node)
48
+ attribution = node.attr?("attribution") ? node.attr("attribution") : nil
49
+ citetitle = node.attr?("citetitle") ? node.attr("citetitle") : nil
50
+ classes = ["blockquote", node.role].compact.join(" ")
51
+ cite_element = citetitle ? %(<cite>#{citetitle}</cite>) : ""
52
+ attr_element = attribution ? %(<span class="attribution">#{attribution}</span>) : ""
53
+ content = %(<blockquote#{Utils.dyn_id_class_attr_str node, classes}>\n#{node.content}\n</blockquote>)
54
+ if attribution || citetitle
55
+ caption = %(<figcaption class="blockquote-footer">\n#{attr_element}#{cite_element}\n</figcaption>)
56
+ content = %(<figure>\n#{content}\n#{caption}\n</figure>\n)
57
+ end
58
+ Utils.wrap_node_with_title content, node
59
+ end
60
+
61
+ def convert_admonition(node)
62
+ name = node.attr "name"
63
+ icon_class = case name
64
+ when "note" then "info-lg"
65
+ when "tip" then "lightbulb"
66
+ else "exclamation-lg"
67
+ end
68
+ icon = %(<div class="icon"><i class="bi bi-#{icon_class}"></i></div>)
69
+ content = %(#{icon}\n#{Utils.display_title node, needs_prefix: false}#{node.content})
70
+ Utils.wrap_id_classes content, node.id, "admonition admonition-#{name}"
71
+ end
72
+
73
+ def convert_sidebar(node)
74
+ classes = ["aside", node.role].compact.join(" ")
75
+ title = node.title? ? %(<h5 class="aside-title">#{node.title}</h5>\n) : ""
76
+ content = "#{title}#{node.content}"
77
+ %(<aside#{Utils.id_class_attr_str node.id, classes}>\n#{content}\n</aside>\n)
78
+ end
79
+
80
+ def convert_stem(node)
81
+ open, close = BLOCK_MATH_DELIMITERS[node.style.to_sym]
82
+ equation = node.content || ""
83
+ equation = "#{open}#{equation}#{close}" unless (equation.start_with? open) && (equation.end_with? close)
84
+ classes = ["stem"]
85
+ if node.option? "numbered"
86
+ equation = %(<div class="equation">\n#{equation}\n</div> <!-- .equation -->)
87
+ equation = %(#{equation}\n<div class="equation-number">#{node.reftext}</div>)
88
+ classes << "stem-equation"
89
+ end
90
+ content = %(<div#{Utils.dyn_id_class_attr_str node, classes.join(" ")}>\n#{equation}\n</div>\n)
91
+ Utils.wrap_id_classes_with_title content, node, node.id, "stem-wrapper"
92
+ end
93
+
94
+ def convert_inline_callout(node)
95
+ i = node.text.to_i
96
+ case i
97
+ when 1..20
98
+ (i + 9311).chr(Encoding::UTF_8)
99
+ when 21..50
100
+ (i + 3230).chr(Encoding::UTF_8)
101
+ else
102
+ "[#{node.text}]"
103
+ end
104
+ end
105
+
106
+ def convert_inline_footnote(node)
107
+ if (index = node.attr "index")
108
+ icon = %(<i class="bi bi-question-circle-fill"></i>)
109
+ %(<sup>#{Utils.popover_button icon, "_footnotedef_#{index}", "fnref"}</sup>)
110
+ else
111
+ %(<sup class="text-danger">[??]</sup>)
112
+ end
113
+ end
114
+
115
+ def convert_listing(node)
116
+ nowrap = (node.option? "nowrap") || !(node.document.attr? "prewrap")
117
+ if node.style == "source"
118
+ lang = node.attr "language"
119
+ code_open = %(<code#{%( class="language-#{lang}") if lang}>)
120
+ pre_open = %(<pre#{%( class="nowrap") if nowrap}>#{code_open})
121
+ pre_close = "</code></pre>"
122
+ else
123
+ pre_open = %(<pre#{%( class="nowrap") if nowrap}>)
124
+ pre_close = "</pre>"
125
+ end
126
+ needs_prefix = node.option? "numbered"
127
+ title = Utils.display_title(node, needs_prefix:)
128
+ content = title + pre_open + node.content + pre_close
129
+ Utils.wrap_node content, node
130
+ end
131
+
132
+ def convert_open(node)
133
+ collapsible = node.option? "collapsible"
134
+ title = if collapsible
135
+ %(<summary>#{node.title || "Details"}</summary>\n)
136
+ else
137
+ Utils.display_title(node, needs_prefix: false)
138
+ end
139
+ tag_name = collapsible ? :details : :div
140
+ Utils.wrap_node(title + node.content, node, tag_name)
141
+ end
142
+
32
143
  def convert_example(node)
33
- p node.context unless Utils.show_title?(node)
34
144
  Utils.wrap_node_with_title node.content, node, needs_prefix: true
35
145
  end
36
146
 
37
147
  def convert_image(node)
38
- return super if node.option?("inline") || node.option?("interactive")
39
-
40
148
  content = display_figure node
41
149
  Utils.wrap_id_classes content, node.id, ["figbox", node.role].compact.join(" ")
42
150
  end
43
151
 
44
152
  def convert_inline_image(node)
45
- return super if node.option?("inline") || node.option?("interactive")
46
-
47
153
  target = node.target
48
- mark = node.parent.attr("mark")
49
- attrs = image_attrs node
50
- image = display_image node, target, attrs
51
- title = node.attr?("title") ? node.attr("title") : ""
52
- caption = mark ? %(<span class="li-mark">#{mark}</span>#{title}) : title
53
- %( #{image}\n <figcaption>#{caption}</figcaption>)
154
+ mark = node.parent.attr "mark"
155
+ title_attr = node.attr? "title"
156
+ if mark # The image is part of a figlist
157
+ title = title_attr ? node.attr("title") : ""
158
+ %( #{display_image node, target}
159
+ <figcaption><span class="li-mark">#{mark}</span>#{title}</figcaption>).gsub(/^ /, "")
160
+ else
161
+ display_image node, target, title_attr:
162
+ end
54
163
  end
55
164
 
56
165
  def convert_olist(node)
57
166
  return convert_figlist(node) if node.style == "figlist"
58
167
 
59
- depth = node.attr "list-depth"
60
- flat = node.attr? "flat-style"
61
- level = depth + 1
62
- classes = ["olist level-#{level}", flat ? "pseudocode" : node.style, node.role].compact.join(" ")
63
- result = [%(<ol#{Utils.dyn_id_class_attr_str node, classes}>)]
64
- node.items.each do |item|
65
- result << Olist.display_list_item(item)
168
+ List.convert node
169
+ end
170
+
171
+ def convert_colist(node)
172
+ node.style = "arabic-circled"
173
+ List.convert node
174
+ end
175
+
176
+ def convert_ulist(node)
177
+ List.convert node, :ul
178
+ end
179
+
180
+ def convert_dlist(node)
181
+ classes = ["dlist", node.style, node.role].compact.join(" ")
182
+ result = [%(<dl#{Utils.dyn_id_class_attr_str node, classes}>)]
183
+ node.items.each do |terms, dd|
184
+ terms.each do |dt|
185
+ result << %(<dt>#{dt.text}</dt>)
186
+ end
187
+ next unless dd
188
+
189
+ result << "<dd>"
190
+ result << %(<p>#{dd.text}</p>) if dd.text?
191
+ result << dd.content if dd.blocks?
192
+ result << "</dd>"
193
+ end
194
+ result << "</dl>\n"
195
+ Utils.wrap_id_classes_with_title result.join("\n"), node, node.id, "dlist-wrapper"
196
+ end
197
+
198
+ def convert_inline_anchor(node)
199
+ if node.type == :xref && !node.text
200
+ target = node.document.catalog[:refs][node.attr("refid")]
201
+ if target&.inline?
202
+ text = target.text
203
+ return %(<a href="#{node.target}">#{text}</a>) if text&.match?(/\A<i class="bi/)
204
+
205
+ list_style = target.parent&.parent&.style
206
+ return Utils.popover_button(target.reftext, target.id, "bibref") if list_style == "bibliography"
207
+ end
66
208
  end
67
- result << %(</ol> <!-- .level-#{level} -->\n)
68
- Utils.wrap_id_classes_with_title result.join("\n"), node, node.id, "list-wrapper"
209
+ super
69
210
  end
70
211
  end
71
212
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "asciidoctor"
4
+ require "pathname"
5
+
6
+ module Asciidoctor
7
+ module Html
8
+ # Allow cross references between documents by creating a suitable
9
+ # ERB inline ruby string:
10
+ #
11
+ # cref:example.adoc#element-id[]
12
+ # => <a href="example.html#element-id"><%= refs.dig("example", "element-id") %></a>
13
+ #
14
+ # cref:example.adoc[]
15
+ # => <a href="example.html"><%= refs.dig("example", "chapref") %></a>
16
+ #
17
+ # cref:example.adoc[here]
18
+ # => <a href="example.html">here</a>
19
+ class CrefInlineMacro < Asciidoctor::Extensions::InlineMacroProcessor
20
+ use_dsl
21
+
22
+ named :cref
23
+ name_positional_attributes "text"
24
+
25
+ def process(parent, target, attrs)
26
+ path_tag = target.split "#"
27
+ path = path_tag.first
28
+ tag = path_tag.size > 1 ? path_tag[1] : "chapref"
29
+ doc_key = Pathname(path).sub_ext ""
30
+ text = attrs["text"] || %(<%= refs.dig("#{doc_key}", "#{tag}") || "[#{doc_key}][#{tag}]" %>)
31
+ hash_tag = path_tag.size > 1 ? "##{path_tag[1]}" : ""
32
+ href = "#{Pathname(path).sub_ext ".html"}#{hash_tag}"
33
+ create_anchor parent, text, type: :link, target: href
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,30 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "olist"
4
-
5
3
  module Asciidoctor
6
4
  module Html
7
5
  # Helper functions for the image/figure conversion.
8
6
  # Mixed into the Converter class.
9
7
  module Figure
10
- def display_image(node, target, attrs)
8
+ def display_image(node, target, title_attr: false)
9
+ attrs = image_attrs(node, title_attr:)
11
10
  %(<img src="#{node.image_uri target}" #{attrs}#{@void_element_slash}>)
12
11
  end
13
12
 
14
- def image_attrs(node)
13
+ def image_attrs(node, title_attr: false)
15
14
  width = node.attr?("width") ? %( width="#{node.attr "width"}") : ""
16
15
  height = node.attr?("height") ? %( height="#{node.attr "height"}") : ""
16
+ title = encode_attribute_value node.attr("title") if node.attr?("title") && title_attr
17
+ title = title ? %( data-bs-toggle="tooltip" data-bs-title="#{title}") : ""
17
18
  alt = encode_attribute_value node.alt
18
- %(alt="#{alt}"#{width}#{height})
19
+ %(alt="#{alt}"#{width}#{height}#{title})
19
20
  end
20
21
 
21
22
  def display_figure(node)
22
23
  target = node.attr "target"
23
24
  title = node.title? ? node.title : ""
24
- attrs = image_attrs node
25
- image = display_image node, target, attrs
26
- caption = %(<figcaption>#{Utils.display_title_prefix node}#{title}</figcaption>)
27
- %(<figure>\n #{image}\n #{caption}\n</figure>)
25
+ image = display_image node, target
26
+ caption = %( <figcaption>#{Utils.display_title_prefix node}#{title}</figcaption>)
27
+ %(<figure>\n #{image}\n#{caption}\n</figure>)
28
28
  end
29
29
 
30
30
  def convert_figlist(node)
@@ -32,7 +32,7 @@ module Asciidoctor
32
32
  %(<li#{Utils.id_class_attr_str item.id}><figure>\n#{item.text}\n</figure></li>)
33
33
  end
34
34
  content = Utils.wrap_id_classes result.join("\n"), nil, "figlist loweralpha", :ol
35
- title = Utils.display_title node
35
+ title = %(<div class="figlist-title">#{Utils.display_title_prefix(node)}#{node.title}</div>)
36
36
  classes = ["figlist-wrapper", node.role].compact.join(" ")
37
37
  Utils.wrap_id_classes %(#{content}#{title}), node.id, classes
38
38
  end