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
@@ -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
|
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
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
38
|
+
def process_numbered_block!(block, document)
|
39
39
|
context = block.context
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
45
|
-
|
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
|
-
|
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
|
55
|
-
|
56
|
-
when
|
57
|
-
|
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
|
66
|
-
|
84
|
+
def bullet(depth)
|
85
|
+
case depth
|
86
|
+
when 1 then "‐"
|
87
|
+
when 2 then "∗"
|
88
|
+
when 3 then "◦"
|
89
|
+
else "•"
|
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
|
-
|
76
|
-
|
77
|
-
|
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 =
|
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
|
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
|
159
|
+
register_reftext! item, ref_li_mark(mark, 0)
|
109
160
|
end
|
110
161
|
|
111
|
-
def
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
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
|
128
|
-
if
|
129
|
-
|
130
|
-
|
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!
|
137
|
-
elsif
|
138
|
-
process_flat_item!
|
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
|
145
|
-
listdepth -= 1 if
|
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">© #{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
|
-
|
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
|
data/lib/asciidoctor/html.rb
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
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"
|