asciidoctor-html 2.0.3 → 2.1.1

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.
@@ -0,0 +1,12 @@
1
+ {
2
+ "jupyter-lite-schema-version": 0,
3
+ "jupyter-config-data": {
4
+ "showLoadingIndicator": true,
5
+ "disabledExtensions": [
6
+ "@jupyterlab/drawio-extension",
7
+ "jupyterlab-kernel-spy",
8
+ "jupyterlab-tour"
9
+ ]
10
+ }
11
+ }
12
+
@@ -0,0 +1,7 @@
1
+ # Core modules (mandatory)
2
+ jupyterlite-core==0.8.0
3
+ jupyterlab~=4.6.0
4
+ notebook~=7.6.0
5
+
6
+ # Python kernel (optional)
7
+ jupyterlite-pyodide-kernel==0.8.0
@@ -31,6 +31,7 @@ module Asciidoctor
31
31
 
32
32
  DOCATTRS = {
33
33
  "sectids" => false,
34
+ "xrefstyle" => "short",
34
35
  "stem" => "latexmath",
35
36
  "hide-uri-scheme" => true,
36
37
  "source-highlighter" => "highlight.js",
@@ -59,6 +60,7 @@ module Asciidoctor
59
60
  # - short_title
60
61
  # - authors
61
62
  # - chapname
63
+ # - base_url
62
64
  def initialize(opts = {})
63
65
  opts = DEFAULT_OPTS.merge opts
64
66
  @title = ERB::Escape.html_escape opts[:title]
@@ -116,7 +118,7 @@ module Asciidoctor
116
118
  end
117
119
 
118
120
  def langs(doc)
119
- doc.attr?("source-langs") ? doc.attr("source-langs").keys : []
121
+ doc.attr("source-langs").keys if doc.attr?("source-langs")
120
122
  end
121
123
 
122
124
  def doctitle(doc)
@@ -207,12 +209,15 @@ module Asciidoctor
207
209
 
208
210
  def outline(key, doc, absolute: true)
209
211
  items = []
212
+ line_number = 0
210
213
  doc.sections.each do |section|
214
+ next if section.option? "skipnav"
211
215
  next unless section.id && section.level == 1
212
216
 
213
217
  prefix = Utils.display_sectnum(section) if section.numbered
214
218
  url = "#{"#{key}.html" if absolute}##{section.id}"
215
- items << BookTemplate.nav_item(url, "#{prefix}#{section.title}")
219
+ line_number += 1
220
+ items << BookTemplate.nav_item(url, "#{prefix}#{section.title}", line_number:)
216
221
  end
217
222
  return "" unless items.size > 1
218
223
 
@@ -256,6 +261,7 @@ module Asciidoctor
256
261
  content,
257
262
  nav_items,
258
263
  has_subnav: !outline.empty?,
264
+ has_live: doc.attr?("live"),
259
265
  title: @title,
260
266
  short_title: @short_title,
261
267
  authors: display_authors(doc),
@@ -264,6 +270,7 @@ module Asciidoctor
264
270
  chapheading: tdata.chapheading,
265
271
  chapsubheading: tdata.chapsubheading,
266
272
  langs: langs(doc),
273
+ linenumbering: doc.attr?("linenumbering"),
267
274
  paginator: pagination(key),
268
275
  pagestyle: doc.attr?("pagestyle") ? doc.attr("pagestyle").to_sym : :single
269
276
  )
@@ -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>
@@ -8,6 +8,7 @@ require "psych"
8
8
  require_relative "book"
9
9
  require_relative "webmanifest"
10
10
  require_relative "version"
11
+ require_relative "jupyterlite"
11
12
 
12
13
  module Asciidoctor
13
14
  module Html
@@ -53,10 +54,11 @@ module Asciidoctor
53
54
  "#{config["srcdir"]}/#{f}"
54
55
  end
55
56
  end
57
+ config["jupyterlite"] = config["jupyterlite"] || false
56
58
  config
57
59
  end
58
60
 
59
- def self.setup_outdir(srcdir, outdir)
61
+ def self.setup_outdir(srcdir, outdir, jupyterlite: false)
60
62
  assets_out = "#{outdir}/#{ASSETS_PATH}"
61
63
  FileUtils.mkdir_p assets_out unless File.directory?(assets_out)
62
64
  %W[#{IMG_PATH} #{CSS_PATH} #{FAVICON_PATH}].each do |p|
@@ -76,6 +78,7 @@ module Asciidoctor
76
78
  puts
77
79
  FileUtils.cp_r "#{rootdir}/#{p}", assets_out
78
80
  end
81
+ build_jupyterlite(rootdir, outdir) if jupyterlite
79
82
  end
80
83
 
81
84
  def self.generate_webmanifest(outdir, name, short_name)
@@ -95,6 +98,13 @@ module Asciidoctor
95
98
  book_opts
96
99
  end
97
100
 
101
+ def self.build_jupyterlite(rootdir, outdir)
102
+ jupyterlite_outdir = "#{outdir}/#{Jupyterlite::PATH}"
103
+ return if File.directory?(jupyterlite_outdir)
104
+
105
+ system %(#{rootdir}/bin/build-jupyterlite -o "#{jupyterlite_outdir}" "#{rootdir}/#{Jupyterlite::PATH}")
106
+ end
107
+
98
108
  def self.run(opts = nil)
99
109
  opts ||= parse_opts
100
110
  if opts[:version]
@@ -105,7 +115,7 @@ module Asciidoctor
105
115
  outdir = config["outdir"]
106
116
  srcdir = config["srcdir"]
107
117
  book_opts = generate_bookopts config
108
- setup_outdir srcdir, outdir
118
+ setup_outdir srcdir, outdir, jupyterlite: config["jupyterlite"]
109
119
  generate_webmanifest outdir, book_opts[:title], book_opts[:short_title]
110
120
  book = Book.new book_opts
111
121
  puts "Writing book to\n #{outdir}"
@@ -5,6 +5,7 @@ require_relative "list"
5
5
  require_relative "utils"
6
6
  require_relative "figure"
7
7
  require_relative "table"
8
+ require_relative "jupyterlite"
8
9
 
9
10
  module Asciidoctor
10
11
  module Html
@@ -29,7 +30,7 @@ module Asciidoctor
29
30
  end
30
31
 
31
32
  def convert_preamble(node)
32
- %(<div class="preamble d-block">\n#{node.content}</div> <!-- .preamble -->\n)
33
+ %(<div class="preamble flip d-block">\n#{node.content}</div> <!-- .preamble.flip -->\n)
33
34
  end
34
35
 
35
36
  def convert_section(node)
@@ -38,9 +39,10 @@ module Asciidoctor
38
39
  show_sectnum = node.numbered && level <= (document.attr("sectnumlevels") || 1).to_i
39
40
  tag_level = [level == 1 ? level + 1 : level + 2, 6].min
40
41
  tag_name = %(h#{tag_level})
42
+ classes = "section#{" flip" if level == 1}#{" #{node.role}" if node.role}"
41
43
  display_sectnum = Utils.display_sectnum(node, level) if show_sectnum
42
44
  content = %(<#{tag_name}>#{display_sectnum}#{node.title}</#{tag_name}>\n#{node.content})
43
- Utils.wrap_node content, node, :section
45
+ Utils.wrap_id_classes content, node.id, classes, :section
44
46
  end
45
47
 
46
48
  def convert_paragraph(node)
@@ -118,19 +120,30 @@ module Asciidoctor
118
120
  end
119
121
 
120
122
  def convert_listing(node)
123
+ if node.option? "jupyter"
124
+ return Utils.wrap_id_classes(
125
+ "#{Utils.display_title node}#{Jupyterlite.html node}",
126
+ node.id,
127
+ "jupyter-cell-wrapper"
128
+ )
129
+ end
130
+
121
131
  nowrap = (node.option? "nowrap") || !(node.document.attr? "prewrap")
122
132
  if node.style == "source"
123
- lang = node.attr "language"
124
- code_open = %(<code#{%( class="language-#{lang}") if lang}>)
133
+ classes = []
134
+ classes << "language-#{node.attr "language"}" if node.attr?("language")
135
+ classes << "linenums" if (has_linenums = node.option? "linenums")
136
+ classes << "ln-plugin" if has_linenums || node.attr?("live")
137
+ code_open = %(<code#{%( class="#{classes.join " "}") unless classes.empty?}>)
125
138
  pre_open = %(<pre#{%( class="nowrap") if nowrap}>#{code_open})
126
139
  pre_close = "</code></pre>"
127
140
  else
128
141
  pre_open = %(<pre#{%( class="nowrap") if nowrap}>)
129
142
  pre_close = "</pre>"
130
143
  end
131
- title = Utils.display_title(node)
144
+ title = Utils.display_title node
132
145
  content = title + pre_open + node.content + pre_close
133
- Utils.wrap_node content, node
146
+ Utils.wrap_live Utils.wrap_node(content, node), node.attr("live")
134
147
  end
135
148
 
136
149
  def convert_literal(node)
@@ -192,19 +205,24 @@ module Asciidoctor
192
205
  def convert_dlist(node)
193
206
  classes = ["dlist", node.style, node.role].compact.join(" ")
194
207
  result = [%(<dl#{Utils.dyn_id_class_attr_str node, classes}>)]
208
+ live = node.attr? "live"
209
+ line_number = 1
195
210
  node.items.each do |terms, dd|
196
211
  terms.each do |dt|
197
- result << %(<dt>#{dt.text}</dt>)
212
+ result << %(<dt class="dterm"#{Utils.line_number_attr line_number, live:}>#{dt.text}</dt>)
213
+ line_number += 1
198
214
  end
199
215
  next unless dd
200
216
 
201
- result << "<dd>"
217
+ result << "<dd#{Utils.line_number_attr line_number, live:}>"
218
+ line_number += 1
202
219
  result << %(<p>#{dd.text}</p>) if dd.text?
203
220
  result << dd.content if dd.blocks?
204
221
  result << "</dd>"
205
222
  end
206
223
  result << "</dl>\n"
207
- Utils.wrap_id_classes_with_title result.join("\n"), node, node.id, "dlist-wrapper"
224
+ Utils.wrap_live(Utils.wrap_id_classes_with_title(result.join("\n"), node, node.id, "dlist-wrapper"),
225
+ node.attr("live"))
208
226
  end
209
227
 
210
228
  def convert_inline_anchor(node)
@@ -233,7 +251,7 @@ module Asciidoctor
233
251
  end
234
252
 
235
253
  def convert_table(node)
236
- classes = ["table", node.role].compact
254
+ classes = ["table"]
237
255
  classes << "table-striped" if node.option? "striped"
238
256
  classes << "table-bordered" if node.option? "bordered"
239
257
  classes << "table-sm" if node.option? "compact"
@@ -246,7 +264,9 @@ module Asciidoctor
246
264
  width_attribute = %( width="#{tablewidth}%")
247
265
  end
248
266
  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>)
267
+ unless node.option? "nocaption"
268
+ result << %(<caption class="table-title">#{Utils.display_title_prefix node}#{node.title}</caption>)
269
+ end
250
270
  if node.attr("rowcount").positive? && node.attr?("cols")
251
271
  result << "<colgroup>"
252
272
  if autowidth
@@ -259,7 +279,8 @@ module Asciidoctor
259
279
  result << "</colgroup>"
260
280
  end
261
281
  result << "#{Table.display_rows(node)}</table>"
262
- Utils.wrap_id_classes result.join("\n"), nil, "table-responsive"
282
+ wrap_classes = "table-responsive#{" #{node.role}" if node.role}"
283
+ Utils.wrap_live Utils.wrap_id_classes(result.join("\n"), nil, wrap_classes), node.attr("live")
263
284
  end
264
285
  end
265
286
  end
@@ -28,8 +28,8 @@ module Asciidoctor
28
28
  tag = path_tag.size > 1 ? path_tag[1] : "chapref"
29
29
  doc_key = Pathname(path).sub_ext ""
30
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}"
31
+ hashtag = "##{path_tag[1]}" if path_tag.size > 1
32
+ href = "#{Pathname(path).sub_ext ".html"}#{hashtag}"
33
33
  create_anchor parent, text, type: :link, target: href
34
34
  end
35
35
  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
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asciidoctor
4
+ module Html
5
+ # Constants for the jupyterlite library
6
+ module Jupyterlite
7
+ class << self
8
+ attr_accessor :cell_id
9
+ end
10
+
11
+ PATH = "jupyterlite"
12
+ REPL_PATH = "repl/index.html"
13
+
14
+ CONFIG = {
15
+ toolbar: true,
16
+ kernel: "python",
17
+ promptCellPosition: "left",
18
+ clearCellsOnExecute: true,
19
+ hideCodeInput: true,
20
+ clearCodeContentOnExecute: false,
21
+ showBanner: false,
22
+ execute: false
23
+ }.freeze
24
+
25
+ def self.repl_path_with_options(node)
26
+ query_string = ["code=#{URI.encode_uri_component node.content}"]
27
+ query_string << CONFIG.merge(
28
+ { promptCellPosition: node.attr("prompt-position", "left") }
29
+ ).map do |k, v|
30
+ value = if v == true
31
+ 1
32
+ elsif v == false
33
+ 0
34
+ else
35
+ v
36
+ end
37
+ "#{k}=#{URI.encode_uri_component value}"
38
+ end
39
+ "#{REPL_PATH}?#{query_string.join "&"}"
40
+ end
41
+
42
+ def self.html(node)
43
+ @cell_id += 1
44
+ cell_id = "jupyter-cell-frame-#{@cell_id}"
45
+ height_attr = %( style="height: #{node.attr("height")}px;") if node.attr?("height")
46
+ <<~HTML
47
+ <div id="#{cell_id}"#{height_attr} class="jupyter-cell"></div>
48
+ <script>
49
+ (function(){
50
+ const cell = document.getElementById('#{cell_id}');
51
+ addEventListener('load', () => {
52
+ const frame = document.createElement('iframe');
53
+ frame.src = '#{PATH}/#{repl_path_with_options node}';
54
+ frame.width = '100%';
55
+ frame.height = '100%';
56
+ cell.appendChild(frame);
57
+ });
58
+ })();
59
+ </script>
60
+ HTML
61
+ end
62
+ end
63
+ end
64
+ 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