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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -3
- data/CHANGELOG.md +4 -0
- data/README.md +28 -11
- data/Rakefile +15 -6
- data/assets/css/fonts/bootstrap-icons.woff +0 -0
- data/assets/css/fonts/bootstrap-icons.woff2 +0 -0
- data/assets/css/styles.css +5 -0
- data/assets/css/styles.css.map +1 -0
- data/exe/adoctohtml +6 -0
- data/lib/asciidoctor/html/bi_inline_macro.rb +25 -0
- data/lib/asciidoctor/html/book.rb +222 -0
- data/lib/asciidoctor/html/cli.rb +112 -0
- data/lib/asciidoctor/html/converter.rb +165 -24
- data/lib/asciidoctor/html/cref_inline_macro.rb +37 -0
- data/lib/asciidoctor/html/figure.rb +10 -10
- data/lib/asciidoctor/html/highlightjs.rb +99 -0
- data/lib/asciidoctor/html/list.rb +38 -0
- data/lib/asciidoctor/html/popovers.rb +49 -0
- data/lib/asciidoctor/html/ref_tree_processor.rb +142 -64
- data/lib/asciidoctor/html/template.rb +127 -0
- data/lib/asciidoctor/html/tree_walker.rb +3 -1
- data/lib/asciidoctor/html/utils.rb +6 -0
- data/lib/asciidoctor/html/webmanifest.rb +23 -0
- data/lib/asciidoctor/html.rb +13 -1
- data/lib/minitest/html_plugin.rb +18 -22
- metadata +52 -27
- data/docs/_config.yml +0 -5
- data/docs/_layouts/default.html +0 -25
- data/docs/_sass/_custom.scss +0 -35
- data/docs/_sass/_example.scss +0 -30
- data/docs/_sass/_figure.scss +0 -17
- data/docs/_sass/_olist.scss +0 -101
- data/docs/_sass/main.scss +0 -40
- data/docs/assets/css/fonts +0 -1
- data/docs/assets/css/styles.scss +0 -3
- data/docs/assets/img/cat1.jpg +0 -0
- data/docs/assets/img/cat2.jpg +0 -0
- data/docs/assets/img/cat3.jpg +0 -0
- data/docs/package-lock.json +0 -59
- data/docs/package.json +0 -6
- data/docs/site.webmanifest +0 -1
- data/lib/asciidoctor/html/olist.rb +0 -18
- data/lib/asciidoctor/html/version.rb +0 -7
- /data/{docs → assets/favicon}/android-chrome-192x192.png +0 -0
- /data/{docs → assets/favicon}/android-chrome-512x512.png +0 -0
- /data/{docs → assets/favicon}/apple-touch-icon.png +0 -0
- /data/{docs → assets/favicon}/favicon-16x16.png +0 -0
- /data/{docs → assets/favicon}/favicon-32x32.png +0 -0
- /data/{docs → assets/favicon}/favicon.ico +0 -0
data/exe/adoctohtml
ADDED
@@ -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 "
|
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
|
49
|
-
|
50
|
-
image
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
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,
|
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
|
-
|
25
|
-
|
26
|
-
|
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.
|
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
|