asciidoctor-html 2.0.3 → 2.1.0

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.
@@ -116,7 +116,7 @@ module Asciidoctor
116
116
  end
117
117
 
118
118
  def langs(doc)
119
- doc.attr?("source-langs") ? doc.attr("source-langs").keys : []
119
+ doc.attr("source-langs").keys if doc.attr?("source-langs")
120
120
  end
121
121
 
122
122
  def doctitle(doc)
@@ -207,12 +207,15 @@ module Asciidoctor
207
207
 
208
208
  def outline(key, doc, absolute: true)
209
209
  items = []
210
+ line_number = 0
210
211
  doc.sections.each do |section|
212
+ next if section.option? "skipnav"
211
213
  next unless section.id && section.level == 1
212
214
 
213
215
  prefix = Utils.display_sectnum(section) if section.numbered
214
216
  url = "#{"#{key}.html" if absolute}##{section.id}"
215
- items << BookTemplate.nav_item(url, "#{prefix}#{section.title}")
217
+ line_number += 1
218
+ items << BookTemplate.nav_item(url, "#{prefix}#{section.title}", line_number:)
216
219
  end
217
220
  return "" unless items.size > 1
218
221
 
@@ -256,6 +259,7 @@ module Asciidoctor
256
259
  content,
257
260
  nav_items,
258
261
  has_subnav: !outline.empty?,
262
+ has_live: doc.attr?("live"),
259
263
  title: @title,
260
264
  short_title: @short_title,
261
265
  authors: display_authors(doc),
@@ -264,6 +268,7 @@ module Asciidoctor
264
268
  chapheading: tdata.chapheading,
265
269
  chapsubheading: tdata.chapsubheading,
266
270
  langs: langs(doc),
271
+ linenumbering: doc.attr?("linenumbering"),
267
272
  paginator: pagination(key),
268
273
  pagestyle: doc.attr?("pagestyle") ? doc.attr("pagestyle").to_sym : :single
269
274
  )
@@ -5,6 +5,7 @@ require_relative "popovers"
5
5
  require_relative "sidebar"
6
6
  require_relative "scroll"
7
7
  require_relative "flip"
8
+ require_relative "live"
8
9
 
9
10
  module Asciidoctor
10
11
  module Html
@@ -17,11 +18,12 @@ module Asciidoctor
17
18
  </button>
18
19
  HTML
19
20
 
20
- def self.nav_item(target, text, content = "", active: false)
21
+ def self.nav_item(target, text, content = "", active: false, line_number: 0)
21
22
  active_class = active ? %( class="active") : ""
22
23
  link = %(<a href="#{target}">#{text}</a>)
23
24
  subnav = content.empty? ? content : "\n#{content}\n"
24
- %(<li#{active_class}>#{link}#{subnav}</li>\n)
25
+ live_attr = Utils.line_number_attr(line_number, live: line_number.positive?)
26
+ %(<li#{active_class}#{live_attr}>#{link}#{subnav}</li>\n)
25
27
  end
26
28
 
27
29
  def self.nav_text(chapprefix, chaptitle)
@@ -101,7 +103,7 @@ module Asciidoctor
101
103
  <main id="main" class="main">
102
104
  <div id="content-container" class="content-container dynamic-width" tabindex="0">
103
105
  #{chapheader opts[:chapheading], opts[:chapsubheading]}
104
- <div class="chaphead d-block">
106
+ <div class="chaphead flip d-block">
105
107
  #{toggle_button opts[:pagestyle] if opts[:has_subnav]}
106
108
  #{chapheading opts[:chapheading]}
107
109
  <h1 class="chaptitle">#{opts[:chapsubheading]}</h1>
@@ -161,27 +163,35 @@ module Asciidoctor
161
163
  HTML
162
164
  end
163
165
 
164
- def self.highlightjs(langs)
165
- langs.map do |lang|
166
- %(<script defer src="#{Highlightjs::CDN_PATH}/languages/#{lang}.min.js"></script>)
167
- end.join("\n ")
166
+ def self.highlightjs(opts)
167
+ return "" unless opts[:langs]
168
+
169
+ html = [
170
+ <<~HTML
171
+ <link rel="stylesheet" href="#{Highlightjs::CDN_PATH}/styles/tomorrow-night-blue.min.css">
172
+ <script defer src="#{Highlightjs::CDN_PATH}/highlight.min.js"></script>
173
+ HTML
174
+ ]
175
+ opts[:langs].each do |lang|
176
+ html << %(<script defer src="#{Highlightjs::CDN_PATH}/languages/#{lang}.min.js"></script>)
177
+ end
178
+ html << %(<script defer src="#{Highlightjs::LN_PLUGIN_PATH}"></script>) if opts[:linenumbering]
179
+ html.join "\n"
168
180
  end
169
181
 
170
- def self.head(title, description, authors, langs)
182
+ def self.head(opts)
171
183
  <<~HTML
172
184
  <meta charset="utf-8">
173
185
  <meta name="viewport" content="width=device-width, initial-scale=1">
174
- #{%(<meta name="description" content="#{description}">) if description}
175
- #{%(<meta name="author" content="#{authors}">) if authors}
176
- <title>#{title}</title>
186
+ #{%(<meta name="description" content="#{opts[:description]}">) if opts[:description]}
187
+ #{%(<meta name="author" content="#{opts[:authors]}">) if opts[:authors]}
188
+ <title>#{opts[:title]}</title>
177
189
  <link rel="apple-touch-icon" sizes="180x180" href="#{FAVICON_PATH}/apple-touch-icon.png">
178
190
  <link rel="icon" type="image/png" sizes="32x32" href="#{FAVICON_PATH}/favicon-32x32.png">
179
191
  <link rel="icon" type="image/png" sizes="16x16" href="#{FAVICON_PATH}/favicon-16x16.png">
180
192
  <link rel="manifest" href="#{FAVICON_PATH}/site.webmanifest" crossorigin="anonymous">
181
193
  <link rel="stylesheet" href="#{CSS_PATH}/styles.css">
182
- <link rel="stylesheet" href="#{Highlightjs::CDN_PATH}/styles/tomorrow-night-blue.min.css">
183
- <script defer src="#{Highlightjs::CDN_PATH}/highlight.min.js"></script>
184
- #{highlightjs langs}
194
+ #{highlightjs(opts)}
185
195
  <script>
186
196
  MathJax = {
187
197
  tex: {
@@ -202,6 +212,7 @@ module Asciidoctor
202
212
 
203
213
  # opts:
204
214
  # - has_subnav: Boolean
215
+ # - has_live: Boolean
205
216
  # - title: String
206
217
  # - short_title: String
207
218
  # - authors: String
@@ -223,7 +234,7 @@ module Asciidoctor
223
234
  <!DOCTYPE html>
224
235
  <html lang="en">
225
236
  <head>
226
- #{head opts[:title], opts[:description], opts[:authors], opts[:langs]}
237
+ #{head(opts)}
227
238
  #{opts[:at_head_end]}
228
239
  </head>
229
240
  <body>
@@ -235,11 +246,12 @@ module Asciidoctor
235
246
  </div> <!-- .page -->
236
247
  <script>document.getElementById("cr-year").textContent = (new Date()).getFullYear();</script>
237
248
  <script type="module">
238
- #{Highlightjs::PLUGIN}
249
+ #{Highlightjs::PLUGIN if opts[:langs]}
239
250
  #{Popovers::POPOVERS}
240
251
  #{Sidebar::TOGGLE if nav}
241
252
  #{Scroll::SCROLL}
242
253
  #{Flip::FLIP}
254
+ #{Live::LIVE if opts[:has_live]}
243
255
  </script>
244
256
  #{opts[:at_body_end]}
245
257
  </body>
@@ -29,7 +29,7 @@ module Asciidoctor
29
29
  end
30
30
 
31
31
  def convert_preamble(node)
32
- %(<div class="preamble d-block">\n#{node.content}</div> <!-- .preamble -->\n)
32
+ %(<div class="preamble flip d-block">\n#{node.content}</div> <!-- .preamble.flip -->\n)
33
33
  end
34
34
 
35
35
  def convert_section(node)
@@ -38,9 +38,10 @@ module Asciidoctor
38
38
  show_sectnum = node.numbered && level <= (document.attr("sectnumlevels") || 1).to_i
39
39
  tag_level = [level == 1 ? level + 1 : level + 2, 6].min
40
40
  tag_name = %(h#{tag_level})
41
+ classes = "section#{" flip" if level == 1}#{" #{node.role}" if node.role}"
41
42
  display_sectnum = Utils.display_sectnum(node, level) if show_sectnum
42
43
  content = %(<#{tag_name}>#{display_sectnum}#{node.title}</#{tag_name}>\n#{node.content})
43
- Utils.wrap_node content, node, :section
44
+ Utils.wrap_id_classes content, node.id, classes, :section
44
45
  end
45
46
 
46
47
  def convert_paragraph(node)
@@ -120,8 +121,11 @@ module Asciidoctor
120
121
  def convert_listing(node)
121
122
  nowrap = (node.option? "nowrap") || !(node.document.attr? "prewrap")
122
123
  if node.style == "source"
123
- lang = node.attr "language"
124
- code_open = %(<code#{%( class="language-#{lang}") if lang}>)
124
+ classes = []
125
+ classes << "language-#{node.attr "language"}" if node.attr?("language")
126
+ classes << "linenums" if (has_linenums = node.option? "linenums")
127
+ classes << "ln-plugin" if has_linenums || node.attr?("live")
128
+ code_open = %(<code#{%( class="#{classes.join " "}") unless classes.empty?}>)
125
129
  pre_open = %(<pre#{%( class="nowrap") if nowrap}>#{code_open})
126
130
  pre_close = "</code></pre>"
127
131
  else
@@ -130,7 +134,7 @@ module Asciidoctor
130
134
  end
131
135
  title = Utils.display_title(node)
132
136
  content = title + pre_open + node.content + pre_close
133
- Utils.wrap_node content, node
137
+ Utils.wrap_live Utils.wrap_node(content, node), node.attr("live")
134
138
  end
135
139
 
136
140
  def convert_literal(node)
@@ -192,19 +196,24 @@ module Asciidoctor
192
196
  def convert_dlist(node)
193
197
  classes = ["dlist", node.style, node.role].compact.join(" ")
194
198
  result = [%(<dl#{Utils.dyn_id_class_attr_str node, classes}>)]
199
+ live = node.attr? "live"
200
+ line_number = 1
195
201
  node.items.each do |terms, dd|
196
202
  terms.each do |dt|
197
- result << %(<dt>#{dt.text}</dt>)
203
+ result << %(<dt class="dterm"#{Utils.line_number_attr line_number, live:}>#{dt.text}</dt>)
204
+ line_number += 1
198
205
  end
199
206
  next unless dd
200
207
 
201
- result << "<dd>"
208
+ result << "<dd#{Utils.line_number_attr line_number, live:}>"
209
+ line_number += 1
202
210
  result << %(<p>#{dd.text}</p>) if dd.text?
203
211
  result << dd.content if dd.blocks?
204
212
  result << "</dd>"
205
213
  end
206
214
  result << "</dl>\n"
207
- Utils.wrap_id_classes_with_title result.join("\n"), node, node.id, "dlist-wrapper"
215
+ Utils.wrap_live(Utils.wrap_id_classes_with_title(result.join("\n"), node, node.id, "dlist-wrapper"),
216
+ node.attr("live"))
208
217
  end
209
218
 
210
219
  def convert_inline_anchor(node)
@@ -233,7 +242,7 @@ module Asciidoctor
233
242
  end
234
243
 
235
244
  def convert_table(node)
236
- classes = ["table", node.role].compact
245
+ classes = ["table"]
237
246
  classes << "table-striped" if node.option? "striped"
238
247
  classes << "table-bordered" if node.option? "bordered"
239
248
  classes << "table-sm" if node.option? "compact"
@@ -246,7 +255,9 @@ module Asciidoctor
246
255
  width_attribute = %( width="#{tablewidth}%")
247
256
  end
248
257
  result = [%(<table#{Utils.id_class_attr_str node.id, classes.join(" ")}#{width_attribute}>)]
249
- result << %(<caption class="table-title">#{Utils.display_title_prefix node}#{node.title}</caption>)
258
+ unless node.option? "nocaption"
259
+ result << %(<caption class="table-title">#{Utils.display_title_prefix node}#{node.title}</caption>)
260
+ end
250
261
  if node.attr("rowcount").positive? && node.attr?("cols")
251
262
  result << "<colgroup>"
252
263
  if autowidth
@@ -259,7 +270,8 @@ module Asciidoctor
259
270
  result << "</colgroup>"
260
271
  end
261
272
  result << "#{Table.display_rows(node)}</table>"
262
- Utils.wrap_id_classes result.join("\n"), nil, "table-responsive"
273
+ wrap_classes = "table-responsive#{" #{node.role}" if node.role}"
274
+ Utils.wrap_live Utils.wrap_id_classes(result.join("\n"), nil, wrap_classes), node.attr("live")
263
275
  end
264
276
  end
265
277
  end
@@ -6,7 +6,6 @@ module Asciidoctor
6
6
  module Flip
7
7
  FLIP = <<~JS
8
8
  (function() {
9
- const sectSelector = '.content-container > .section';
10
9
  // Holds replaced pagination links to prev/next chapter
11
10
  const chapPagination = {
12
11
  prevChap: null,
@@ -25,11 +24,7 @@ module Asciidoctor
25
24
  const nav = document.querySelectorAll('#sidebar nav > ul > li.active > ul > li');
26
25
 
27
26
  let currentId = "page";
28
- document.querySelectorAll('.content-container > .chaphead, .content-container > .preamble').forEach(el => {
29
- sectsById[currentId].push(el);
30
- el.dataset.withSect = currentId;
31
- });
32
- document.querySelectorAll(sectSelector).forEach(el => {
27
+ focusEl.querySelectorAll(':scope > .flip').forEach(el => {
33
28
  if(el.id) {
34
29
  el.dataset.prevPage = currentId;
35
30
  currentId = el.id;
@@ -112,7 +107,7 @@ module Asciidoctor
112
107
  document.querySelector('.breadcrumb').classList.toggle('d-block', id != 'page' && !isPresentation);
113
108
  document.querySelector('.chapauthors').classList.toggle('d-block', isPresentation);
114
109
 
115
- let section = target && target.closest(sectSelector);
110
+ let section = target && target.closest('.content-container > .section');
116
111
  if(section) {
117
112
  id = section.dataset.withSect;
118
113
  section = sectsById[id][0];
@@ -151,6 +146,7 @@ module Asciidoctor
151
146
  }
152
147
 
153
148
  focusOnLoad();
149
+ if(isPresentation) focusEl.dataset.flip = id;
154
150
  }
155
151
  flip();
156
152
 
@@ -183,7 +179,7 @@ module Asciidoctor
183
179
  switch(e.key) {
184
180
  case 'ArrowLeft': move('left'); break;
185
181
  case 'ArrowRight': move('right'); break;
186
- case 'e':
182
+ case 'q':
187
183
  case 'Escape':
188
184
  page.classList.contains('presentation') && changeViewmode('multi');
189
185
  }
@@ -196,6 +192,7 @@ module Asciidoctor
196
192
  }));
197
193
 
198
194
  page.classList.add('loaded');
195
+ ADHT.move = move;
199
196
  })();
200
197
  JS
201
198
  end
@@ -6,6 +6,8 @@ module Asciidoctor
6
6
  module Highlightjs
7
7
  CDN_PATH = "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build"
8
8
 
9
+ LN_PLUGIN_PATH = "https://cdn.jsdelivr.net/npm/highlightjs-line-numbers.js@2.9.0/dist/highlightjs-line-numbers.min.js"
10
+
9
11
  INCLUDED_LANGS = {
10
12
  "bash" => true,
11
13
  "c" => true,
@@ -106,6 +108,10 @@ module Asciidoctor
106
108
  }
107
109
  });
108
110
  hljs.highlightAll();
111
+
112
+ if(Object.hasOwn(hljs, "lineNumbersBlock")) {
113
+ document.querySelectorAll('code.ln-plugin').forEach(block => hljs.lineNumbersBlock(block));
114
+ }
109
115
  })();
110
116
  JS
111
117
  end
@@ -26,13 +26,14 @@ module Asciidoctor
26
26
  end
27
27
  result << %(</#{tag_name}> <!-- .level-#{level} -->\n)
28
28
  wrap_classes = %(list-wrapper#{" #{node.role}" if node.title? && node.role})
29
- Utils.wrap_id_classes_with_title result.join("\n"), node, node.id, wrap_classes
29
+ Utils.wrap_live Utils.wrap_id_classes_with_title(result.join("\n"), node, node.id, wrap_classes), node.attr("live")
30
30
  end
31
31
 
32
32
  def self.display_list_item(item, inside: false)
33
33
  result = []
34
+ lineno_attr = Utils.line_number_attr item.attr("line-number"), live: item.attr?("line-number")
34
35
  inside_mark = %(<span class="li-mark-inside">#{item.attr "mark"} </span>) if inside
35
- result << %(<li#{Utils.id_class_attr_str item.id, item.role}>)
36
+ result << %(<li#{Utils.id_class_attr_str item.id, item.role}#{lineno_attr}>)
36
37
  result << %(<div class="li-mark">#{item.attr "mark"}</div><div class="li-content">) unless inside
37
38
  result << %(<p>#{inside_mark}#{item.text}</p>) unless item.text.empty?
38
39
  result << "\n#{item.content}" if item.blocks?
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asciidoctor
4
+ module Html
5
+ # Script for live presentations
6
+ module Live
7
+ LIVE = <<~JS
8
+ (function() {
9
+ const page = document.getElementById('page');
10
+ const container = document.getElementById('content-container');
11
+ const liveBlocksSelector = ':scope > .flip.d-block .live';
12
+
13
+ let liveBlocks = container.querySelectorAll(liveBlocksSelector);
14
+ let liveBlockIdx = 0;
15
+ selectBlock();
16
+
17
+ const observer = new MutationObserver(() => {
18
+ liveBlocks = container.querySelectorAll(liveBlocksSelector);
19
+ liveBlockIdx = 0;
20
+ selectBlock();
21
+ });
22
+
23
+ function selectBlock() {
24
+ liveBlocks.forEach((block, idx) => {
25
+ block.firstElementChild.classList.toggle('selected', idx === liveBlockIdx);
26
+ });
27
+ }
28
+
29
+ function getLines(block, lineNumber = -1, selected) {
30
+ let selector = '[data-line-number]';
31
+ if(lineNumber > -1) selector = `[data-line-number="${lineNumber}"]`;
32
+ if(selected === false) selector += ':not(.emph)';
33
+ if(selected === true) selector += '.emph';
34
+ return block.querySelectorAll(selector);
35
+ }
36
+
37
+ function getNextLines() {
38
+ while(liveBlocks.length > 0 && liveBlockIdx < liveBlocks.length) {
39
+ selectBlock();
40
+ const currentBlock = liveBlocks[liveBlockIdx];
41
+ if(currentBlock) {
42
+ toggleDefault(currentBlock);
43
+ let lines = getLines(currentBlock, -1, true);
44
+ if(lines.length > 0) {
45
+ const lineNumber = parseInt(lines[lines.length - 1].dataset.lineNumber) + 1;
46
+ lines = getLines(currentBlock, lineNumber);
47
+ if(lines.length > 0) return lines;
48
+ } else {
49
+ lines = getLines(currentBlock, -1, false);
50
+ if(lines.length > 0) return getLines(currentBlock, lines[0].dataset.lineNumber);
51
+ }
52
+ }
53
+ liveBlockIdx++;
54
+ }
55
+ ADHT.move('right');
56
+ return [];
57
+ }
58
+
59
+ function nextBlock() {
60
+ if(liveBlockIdx < liveBlocks.length - 1) {
61
+ liveBlockIdx++;
62
+ selectBlock();
63
+ } else {
64
+ ADHT.move('right');
65
+ }
66
+ }
67
+
68
+ function prevBlock() {
69
+ if(liveBlockIdx > 0) {
70
+ liveBlockIdx--;
71
+ selectBlock();
72
+ } else {
73
+ ADHT.move('left');
74
+ }
75
+ }
76
+
77
+ function getPrevLines() {
78
+ while(liveBlocks.length > 0 && liveBlockIdx > -1) {
79
+ selectBlock();
80
+ const currentBlock = liveBlocks[liveBlockIdx];
81
+ if(currentBlock) {
82
+ const lines = getLines(currentBlock, -1, true);
83
+ if(lines.length > 0) {
84
+ const line = lines[lines.length - 1];
85
+ return getLines(currentBlock, line.dataset.lineNumber);
86
+ }
87
+ }
88
+ liveBlockIdx--;
89
+ }
90
+ ADHT.move('left');
91
+ return [];
92
+ }
93
+
94
+ function toggleDefault(block, reset = false) {
95
+ const token = block.dataset.reset;
96
+ if(reset) {
97
+ block.classList.add(token);
98
+ getLines(block).forEach(el => el.classList.remove('emph'));
99
+ } else {
100
+ block.classList.remove(token);
101
+ }
102
+ }
103
+
104
+ observer.observe(container, { attributes: true, attributeFilter: ['data-flip'] });
105
+ addEventListener('keyup', function(e) {
106
+ if(!page.classList.contains('presentation')) return;
107
+
108
+ const currentBlock = liveBlocks[liveBlockIdx];
109
+ if(/^\\d$/.test(e.key)) {
110
+ toggleDefault(currentBlock);
111
+ const key = e.key === "0" ? "10" : e.key;
112
+ getLines(currentBlock, e.key).forEach(el => el.classList.toggle('emph'));
113
+ } else {
114
+ switch(e.key) {
115
+ case 'r':
116
+ toggleDefault(currentBlock, true);
117
+ break;
118
+ case 'R':
119
+ liveBlocks.forEach(block => toggleDefault(block, true));
120
+ break;
121
+ case 'n':
122
+ getNextLines().forEach(el => el.classList.add('emph'));
123
+ break;
124
+ case 'b':
125
+ getPrevLines().forEach(el => el.classList.remove('emph'));
126
+ break;
127
+ case 'N':
128
+ nextBlock();
129
+ break;
130
+ case 'B':
131
+ prevBlock();
132
+ break;
133
+ }
134
+ }
135
+ });
136
+ })();
137
+ JS
138
+ end
139
+ end
140
+ end
@@ -69,6 +69,8 @@ module Asciidoctor
69
69
  block.style == "figlist"
70
70
  when :stem, :listing
71
71
  block.option? "numbered"
72
+ when :table
73
+ !block.option? "nocaption"
72
74
  else
73
75
  NUMBERED_CONTEXTS.include? context
74
76
  end
@@ -152,7 +154,7 @@ module Asciidoctor
152
154
  end
153
155
  block.set_attr "list-depth", depth
154
156
  if flat_style
155
- block.set_attr("flat-style", true)
157
+ block.set_attr "flat-style", true
156
158
  else
157
159
  offset = offset block
158
160
  style = block.style
@@ -194,8 +196,9 @@ module Asciidoctor
194
196
  register_reftext! item, mark
195
197
  end
196
198
 
197
- def process_source_code!(document, lang)
199
+ def process_source_code!(document, lang, linenums: false)
198
200
  document.set_attr("source-langs", {}) unless document.attr?("source-langs")
201
+ document.set_attr("linenumbering", true) if !document.attr?("linenumbering") && linenums
199
202
  langs = document.attr "source-langs"
200
203
  langs[lang] = true unless Highlightjs::INCLUDED_LANGS.include?(lang)
201
204
  end
@@ -225,29 +228,39 @@ module Asciidoctor
225
228
  end
226
229
 
227
230
  def process(document)
228
- listdepth = 0
229
- bulletdepth = 0
230
- flat_style = false
231
- flat_idx = 0 # flat index for (pseudocode) list
231
+ listdepth = bulletdepth = flat_idx = 0
232
+ flat_style = live = false
233
+ line_number = 1
232
234
  tw = TreeWalker.new document
233
235
  while (block = tw.next_block)
234
236
  unless block.attr? "refprocessed"
235
237
  process_numbered_block!(block, document) if process_numbered_block?(block)
236
238
  if colist? block
237
239
  process_colist! block
238
- elsif olist? block
239
- if listdepth.zero?
240
+ elsif (is_olist = olist? block) || (is_ulist = ulist? block)
241
+ if is_olist && listdepth.zero?
240
242
  flat_style = (block.style == "pseudocode")
241
243
  flat_idx = offset block
242
244
  end
243
- process_olist! block, listdepth, flat_style:
244
- elsif olist_item?(block) && flat_style
245
- process_flat_item! block, flat_idx
246
- flat_idx += 1
245
+ process_olist!(block, listdepth, flat_style:) if is_olist
246
+ process_ulist!(block, bulletdepth) if is_ulist
247
+ if (listdepth + bulletdepth).zero?
248
+ live = block.attr?("live")
249
+ document.set_attr "live", true if live && !document.attr?("live")
250
+ line_number = offset(block) + 1
251
+ end
252
+ elsif (is_olist_item = olist_item? block) || ulist_item?(block)
253
+ if is_olist_item && flat_style
254
+ process_flat_item! block, flat_idx
255
+ flat_idx += 1
256
+ end
257
+ if live
258
+ block.set_attr "line-number", line_number
259
+ line_number += 1
260
+ end
247
261
  elsif source_code? block
248
- process_source_code! document, block.attr("language")
249
- elsif ulist? block
250
- process_ulist! block, bulletdepth
262
+ linenums = block.option?("linenums") || block.attr?("live")
263
+ process_source_code! document, block.attr("language"), linenums:
251
264
  end
252
265
  block.set_attr "refprocessed", true
253
266
  end
@@ -27,6 +27,7 @@ module Asciidoctor
27
27
  </nav>
28
28
  </div>
29
29
  HTML
30
+ content = Utils.wrap_live content, attrs["live"]
30
31
  create_pass_block parent, content, attrs, subs: nil
31
32
  end
32
33
  end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "utils"
4
+
3
5
  module Asciidoctor
4
6
  module Html
5
7
  # Helpers for the table conversion
6
8
  module Table
7
- def self.display_row(tsec, row)
8
- result = ["<tr>"]
9
+ def self.display_row(tsec, row, line_number, live: false)
10
+ result = ["<tr#{Utils.line_number_attr line_number, live:}>"]
9
11
  row.each do |cell|
10
12
  cell_content = if tsec == :head
11
13
  cell.text
@@ -31,10 +33,17 @@ module Asciidoctor
31
33
  end
32
34
 
33
35
  def self.display_rows(node)
36
+ line_number = 0
37
+ live = node.attr? "live"
34
38
  node.rows.to_h.map do |tsec, rows|
35
39
  next if rows.empty?
36
40
 
37
- "<t#{tsec}>\n#{rows.map { |row| display_row(tsec, row) }.join("\n")}\n</t#{tsec}>"
41
+ result = rows.map do |row|
42
+ show_lineno = live && tsec != :head
43
+ line_number += 1 if show_lineno
44
+ display_row(tsec, row, line_number, live: show_lineno)
45
+ end
46
+ "<t#{tsec}>\n#{result.join "\n"}\n</t#{tsec}>"
38
47
  end.join("\n")
39
48
  end
40
49
  end
@@ -85,6 +85,24 @@ module Asciidoctor
85
85
  end
86
86
  %(<span class="title-mark">#{sectnum}</span>)
87
87
  end
88
+
89
+ def self.wrap_live(content, live_attr)
90
+ return content unless live_attr
91
+
92
+ /\A(?<default>normal|faded|covered)-(?<live>faded|covered)\Z/ =~ live_attr
93
+
94
+ live_class = %(live-#{live || "faded"})
95
+ default_class = %(live-default-#{default || "normal"})
96
+ <<~HTML
97
+ <div class="live #{default_class} #{live_class}" data-reset="#{default_class}">
98
+ <div class="live-select"><i class="bi bi-eye"></i></div>
99
+ #{content}</div> <!-- .live -->
100
+ HTML
101
+ end
102
+
103
+ def self.line_number_attr(line_number, live: false)
104
+ live ? %( data-line-number="#{line_number}") : ""
105
+ end
88
106
  end
89
107
  end
90
108
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Asciidoctor
4
4
  module Html
5
- VERSION = "2.0.3"
5
+ VERSION = "2.1.0"
6
6
  end
7
7
  end
@@ -35,7 +35,7 @@ module Minitest
35
35
  color = failed ? "danger" : "success"
36
36
  id = "test-#{name.tr "_", "-"}"
37
37
  title = display_result_title name, failed, color
38
- pre = %([source,asciidoc]\n------\n#{adoc}\n------\n)
38
+ pre = %([live=normal-faded,asciidoc]\n------\n#{adoc}\n------\n)
39
39
  fail = failed ? display_failure(@results[key].join("\n")) : ""
40
40
  %([##{id}]\n== #{title}\n\n#{pre}#{fail}\n\n#{adoc}\n\n)
41
41
  end