asciidoctor-html 0.1.2 → 0.1.4

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 +6 -3
  3. data/README.md +29 -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 +228 -0
  12. data/lib/asciidoctor/html/cli.rb +113 -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 +145 -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,228 @@
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
+ # - short_title
47
+ # - author
48
+ # - date
49
+ # - se_id
50
+ # - chapname
51
+ def initialize(opts = {})
52
+ opts = DEFAULT_OPTS.merge opts
53
+ @title = ERB::Escape.html_escape opts[:title]
54
+ @short_title = ERB::Escape.html_escape opts[:short_title]
55
+ @author = ERB::Escape.html_escape opts[:author]
56
+ @date = opts.include?(:date) ? Date.parse(opts[:date]) : Date.today
57
+ @se_id = opts[:se_id]
58
+ @chapname = opts[:chapname]
59
+ @refs = {} # Hash(docname => Hash(id => reftext))
60
+ @templates = {} # Hash(docname => TData)
61
+ end
62
+
63
+ # params:
64
+ # - chapters: array of filenames
65
+ # - appendices: array of filenames
66
+ # returns: Hash(file_basename_without_ext => html)
67
+ def read(chapters = [], appendices = [])
68
+ docs = {} # Hash(docname => document)
69
+ chapters.each_with_index do |filename, idx|
70
+ doc = chapter filename, idx
71
+ register! docs, filename, doc
72
+ end
73
+ appendices.each_with_index do |filename, idx|
74
+ doc = appendix filename, idx, appendices.size
75
+ register! docs, filename, doc
76
+ end
77
+ html docs
78
+ end
79
+
80
+ # params:
81
+ # - chapters: array of filenames
82
+ # - appendices: array of filenames
83
+ # - outdir: directory to write the converted html files to
84
+ def write(chapters, appendices, outdir)
85
+ read(chapters, appendices).each do |name, html|
86
+ File.write("#{outdir}/#{name}.html", html)
87
+ end
88
+ File.write("#{outdir}/#{SEARCH_PAGE}", search_page(@se_id)) if @se_id
89
+ end
90
+
91
+ private
92
+
93
+ def search_page(se_id)
94
+ content = <<~HTML
95
+ <script async src="https://cse.google.com/cse.js?cx=#{se_id}"></script>
96
+ <div class="gcse-search"></div>
97
+ HTML
98
+ Template.html(
99
+ content,
100
+ nav_items,
101
+ title: @title,
102
+ short_title: @short_title,
103
+ author: @author,
104
+ date: @date,
105
+ chapnum: "",
106
+ chaptitle: "Search",
107
+ langs: []
108
+ )
109
+ end
110
+
111
+ def register!(docs, filename, doc)
112
+ key = key filename
113
+ docs[key] = doc
114
+ end
115
+
116
+ def langs(doc)
117
+ doc.attr?("source-langs") ? doc.attr("source-langs").keys : []
118
+ end
119
+
120
+ def doctitle(doc)
121
+ doc.doctitle sanitize: true, use_fallback: true
122
+ end
123
+
124
+ def chapter(filename, idx)
125
+ numeral = idx.to_s
126
+ doc = parse_file filename, @chapname, numeral
127
+ chaptitle = doctitle doc
128
+ chapref = idx.zero? ? chaptitle : chapref_default(@chapname, numeral)
129
+ chapnum = idx.zero? ? "" : numeral
130
+ process_doc key(filename), doc, chapnum:, chaptitle:, chapref:
131
+ end
132
+
133
+ def appendix(filename, idx, num_appendices)
134
+ chapname = "Appendix"
135
+ numeral = ("a".."z").to_a[idx].upcase
136
+ doc = parse_file filename, chapname, numeral
137
+ chapref = num_appendices == 1 ? chapname : chapref_default(chapname, numeral)
138
+ chapnum = ""
139
+ chaptitle = Template.appendix_title chapname, numeral, doctitle(doc), num_appendices
140
+ process_doc key(filename), doc, chapnum:, chaptitle:, chapref:
141
+ end
142
+
143
+ def key(filename)
144
+ Pathname(filename).basename.sub_ext("").to_s
145
+ end
146
+
147
+ # opts:
148
+ # - chapnum
149
+ # - chaptitle
150
+ # - chapref
151
+ def process_doc(key, doc, opts)
152
+ val = doc.catalog[:refs].transform_values(&method(:reftext)).compact
153
+ val["chapref"] = opts[:chapref]
154
+ @refs[key] = val
155
+ @templates[key] = TData.new(
156
+ chapnum: opts[:chapnum],
157
+ chaptitle: opts[:chaptitle]
158
+ )
159
+ doc
160
+ end
161
+
162
+ def parse_file(filename, chapname, numeral)
163
+ attributes = { "chapnum" => numeral, "chapname" => chapname }.merge DOCATTRS
164
+ Asciidoctor.load_file filename, safe: :unsafe, attributes:
165
+ end
166
+
167
+ def chapref_default(chapname, numeral)
168
+ "#{ERB::Escape.html_escape chapname} #{numeral}"
169
+ end
170
+
171
+ def reftext(node)
172
+ node.reftext || (node.title unless node.inline?) || "[#{node.id}]" if node.id
173
+ end
174
+
175
+ def outline(doc)
176
+ items = []
177
+ doc.sections.each do |section|
178
+ next unless section.id && section.level == 1
179
+
180
+ items << Template.nav_item("##{section.id}", section.title)
181
+ end
182
+ items.size > 1 ? "<ul>#{items.join "\n"}</ul>" : ""
183
+ end
184
+
185
+ def html(docs)
186
+ html = {} # Hash(docname => html)
187
+ docs.each do |key, doc|
188
+ html[key] = build_template key, doc
189
+ end
190
+ html
191
+ end
192
+
193
+ def nav_items(active_key = -1, doc = nil)
194
+ items = @templates.map do |k, td|
195
+ active = (k == active_key)
196
+ subnav = active && doc ? outline(doc) : ""
197
+ navtext = Template.nav_text td.chapnum, td.chaptitle
198
+ Template.nav_item "#{k}.html", navtext, subnav, active:
199
+ end
200
+ return items unless @se_id
201
+
202
+ items.unshift(Template.nav_item(
203
+ SEARCH_PAGE,
204
+ %(<i class="bi bi-search"></i> Search),
205
+ active: (active_key == -1)
206
+ ))
207
+ end
208
+
209
+ def build_template(key, doc)
210
+ tdata = @templates[key]
211
+ nav_items = nav_items key, doc
212
+ content = ERB.new(doc.convert).result(binding)
213
+ Template.html(
214
+ content,
215
+ nav_items,
216
+ title: @title,
217
+ short_title: @short_title,
218
+ author: @author,
219
+ date: @date,
220
+ chapnum: tdata.chapnum,
221
+ chaptitle: tdata.chaptitle,
222
+ langs: langs(doc),
223
+ nav: (nav_items.size > 1)
224
+ )
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,113 @@
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] ||= []
49
+ config[prop] = config[prop].map do |f|
50
+ File.expand_path(f, config_dir)
51
+ end
52
+ end
53
+ config
54
+ end
55
+
56
+ def self.setup_outdir(outdir)
57
+ assets_dir = "#{outdir}/#{ASSETS_PATH}"
58
+ FileUtils.mkdir_p assets_dir unless File.directory?(assets_dir)
59
+ rootdir = File.absolute_path "#{__dir__}/../../.."
60
+ %W[#{CSS_PATH} #{FAVICON_PATH}].each do |p|
61
+ dir = "#{outdir}/#{p}"
62
+ next if Dir.exist?(dir)
63
+
64
+ puts "Generating #{dir}"
65
+ FileUtils.cp_r "#{rootdir}/#{p}", assets_dir
66
+ end
67
+ end
68
+
69
+ def self.generate_webmanifest(outdir, name, short_name)
70
+ filename = "#{outdir}/#{FAVICON_PATH}/site.webmanifest"
71
+ puts "Generating #{filename}"
72
+ File.write filename, Webmanifest.generate(name, short_name)
73
+ end
74
+
75
+ def self.generate_bookopts(config)
76
+ book_opts = {}
77
+ %i[title short_title author date se_id chapname].each do |opt|
78
+ key = opt.to_s
79
+ book_opts[opt] = config[key] if config.include?(key)
80
+ end
81
+ book_opts[:short_title] ||= book_opts[:title]
82
+ book_opts
83
+ end
84
+
85
+ def self.run(opts = nil)
86
+ opts ||= parse_opts
87
+ config = read_config opts[:"config-file"]
88
+ outdir = config["outdir"]
89
+ book_opts = generate_bookopts config
90
+ setup_outdir outdir
91
+ generate_webmanifest outdir, book_opts[:title], book_opts[:short_title]
92
+ book = Book.new book_opts
93
+ puts "Writing book to #{outdir}"
94
+ book.write config["chapters"], config["appendices"], config["outdir"]
95
+ return unless opts[:watch]
96
+
97
+ Filewatcher.new("#{config["srcdir"]}/*.adoc").watch do |changes|
98
+ chapters = []
99
+ appendices = []
100
+ changes.each_key do |filename|
101
+ puts "Detected change in #{filename}"
102
+ chapters.append(filename) if config["chapters"].include?(filename)
103
+ appendices.append(filename) if config["appendices"].include?(filename)
104
+ end
105
+ puts "Regenerating book:"
106
+ puts " Chapters: #{chapters.map { |c| Pathname(c).basename }.join ", "}" unless chapters.empty?
107
+ puts " Appendices: #{appendices.map { |a| Pathname(a).basename }.join ", "}" unless appendices.empty?
108
+ book.write chapters, appendices, config["outdir"]
109
+ end
110
+ end
111
+ end
112
+ end
113
+ 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