asciidoctor-rhrev 1.9.2 → 1.11.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.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +9 -0
- data/lib/asciidoctor/rhrev/change_bars.rb +113 -9
- data/lib/asciidoctor/rhrev/converter.rb +12 -8
- data/lib/asciidoctor/rhrev/renderer.rb +20 -5
- data/lib/asciidoctor/rhrev/rhrev_html.rb +121 -6
- data/lib/asciidoctor/rhrev/table_cell_change_bar_patch.rb +5 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 88e8d8ff30a9b7b8170298f2c5a8099f6a4ded89dcbceecd3557b4a18bf37344
|
|
4
|
+
data.tar.gz: dfb17de4a7e0fb23e98aa6ffd648172a0e74135fbb5b1d4c6d7f4b52569353bc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d2b30034dba30dddef36b20ee6660a689ed61a2577b2c3b4f1e503d43bd88a018f8b22c2885962b2e90e11e6ac2801b2d790f4afb11f32c694ba1c26411514bb
|
|
7
|
+
data.tar.gz: 93b412dc36911718747efd0161dd1e97685a4b1161795c315c20c09168b5dbcc8c74066e89b254d834d225542ab0098a0326f8a1f1954ee18fb1dbdb74b2d4a8
|
data/CHANGELOG.adoc
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
= Changelog
|
|
2
2
|
|
|
3
|
+
== 1.11.0 (2026-10-28)
|
|
4
|
+
|
|
5
|
+
* Change bar links in HTML output, with `rhrev-change-bars-link` (gem and npm). The HTML backend and the Antora extension give each revision history row the id the PDF backend uses for its named destination, and put a transparent link inside every element tagged `rhrev-changed`, to the row of its change, with the same row choice as the PDF backend. A stylesheet carried by the first link of a page places each link over the bar and the gap beside it. A table's link sits in its caption, and a changed cell's link in its own `<td>` or `<th>`. In an Antora site, a page without a revision history table of its own links to the table page of its component, or else to a table page with `rhrev-scope: site`, which `antora-rhrev-html.js` now records. New `antora-extension/change-bar-links.js`; new regression files `tests/regression/html-change-bar-links.rb` and `tests/regression/js-change-bar-links.js`
|
|
6
|
+
|
|
7
|
+
== 1.10.0 (2026-10-28)
|
|
8
|
+
|
|
9
|
+
* Change bar links, enabled with `rhrev-change-bars-link` alongside `rhrev-change-bars` (PDF backend). Every change bar links to the revision history row of its change, and each filled column of a change bar table links to the row of its own revision. A block that rolls up into its section's row or into the `all` entry links to that row, a table cell links to its table's row, and a block without a row of its own links to its revision's header row. The link of a bar also covers the gap between the text and the bar. Links of nested blocks sit above their section's link, so a click follows the innermost change. The revision history table carries one named destination per row, header, and `all` entry. New regression file `tests/regression/change-bar-links.rb` (gem)
|
|
10
|
+
* Note: gem-only; the Antora extension is unaffected, npm still bumps concurrently per the standing 1.5.1 policy
|
|
11
|
+
|
|
3
12
|
== 1.9.2 (2026-10-27)
|
|
4
13
|
|
|
5
14
|
* Change bar table labels stand upright, one character per line, read from top to bottom, with the labels of all columns sharing one bottom line; labels no longer turn 90 degrees. `ink_change_bar_table_label` draws each label (gem)
|
|
@@ -22,6 +22,12 @@ module Asciidoctor
|
|
|
22
22
|
# filled, and every page with a box labels each column at the top of
|
|
23
23
|
# its body, so an isolated change still shows which revision it is.
|
|
24
24
|
# Without lanes, as in collapse mode, table draws the bar too.
|
|
25
|
+
#
|
|
26
|
+
# With rhrev-change-bars-link set, every bar and every filled table
|
|
27
|
+
# column links to the revision history row of the change it marks:
|
|
28
|
+
# each bar records the marked node it inks for, and once the
|
|
29
|
+
# revision history has rendered with a named destination on every
|
|
30
|
+
# row, ink_change_bar_links annotates the recorded areas.
|
|
25
31
|
module ChangeBars
|
|
26
32
|
CHANGE_BAR_STYLES = %w(bar table).freeze
|
|
27
33
|
|
|
@@ -48,6 +54,12 @@ module Asciidoctor
|
|
|
48
54
|
@change_bar_section_stack = []
|
|
49
55
|
@change_bar_grid_labeled_pages = {}
|
|
50
56
|
@change_bar_margin_warned = false
|
|
57
|
+
if doc.attr? 'rhrev-change-bars-link'
|
|
58
|
+
@change_bar_links = []
|
|
59
|
+
@change_bar_link_dests = ::Set.new
|
|
60
|
+
else
|
|
61
|
+
@change_bar_links = @change_bar_link_dests = nil
|
|
62
|
+
end
|
|
51
63
|
end
|
|
52
64
|
|
|
53
65
|
# Resolved lazily on first ink: the theme (and @media/@folio_placement)
|
|
@@ -129,7 +141,7 @@ module Asciidoctor
|
|
|
129
141
|
end
|
|
130
142
|
|
|
131
143
|
def record_change_bar_start node
|
|
132
|
-
@change_bar_starts[node] = { page: page_number, cursor: cursor, lanes: (node_change_bar_lanes node) }
|
|
144
|
+
@change_bar_starts[node] = { page: page_number, cursor: cursor, lanes: (node_change_bar_lanes node), node: node }
|
|
133
145
|
end
|
|
134
146
|
|
|
135
147
|
def take_change_bar_start node
|
|
@@ -254,7 +266,7 @@ module Asciidoctor
|
|
|
254
266
|
# Inks one page's share of a table box between top and bottom: the
|
|
255
267
|
# columns from change_bar_table_columns, the columns of lanes
|
|
256
268
|
# filled, the first column nearest the text.
|
|
257
|
-
def ink_change_bar_table pgnum, top, bottom, lanes, settings
|
|
269
|
+
def ink_change_bar_table pgnum, top, bottom, lanes, settings, from = nil
|
|
258
270
|
width = settings[:table_column_width]
|
|
259
271
|
on_right = change_bar_on_right? pgnum, settings
|
|
260
272
|
column_of = change_bar_table_columns @change_bar_lane_index.size, settings[:table_max_columns]
|
|
@@ -262,8 +274,9 @@ module Asciidoctor
|
|
|
262
274
|
left = on_right ? bounds.right + settings[:offset] : bounds.left - settings[:offset] - count * width
|
|
263
275
|
left = change_bar_inside_page left, count * width, settings[:table_border_width] / 2.0, on_right, settings
|
|
264
276
|
column_x = ->(column) { on_right ? left + column * width : left + (count - 1 - column) * width }
|
|
265
|
-
lanes.
|
|
277
|
+
lanes.group_by {|lane| column_of[lane] }.each do |column, column_lanes|
|
|
266
278
|
ink_change_bar_fill (column_x.call column), top, bottom, width, settings[:color]
|
|
279
|
+
record_change_bar_link from, column_lanes, (column_x.call column), width, top, bottom if from
|
|
267
280
|
end
|
|
268
281
|
save_graphics_state do
|
|
269
282
|
if settings[:table_grid_width] > 0
|
|
@@ -316,6 +329,7 @@ module Asciidoctor
|
|
|
316
329
|
|
|
317
330
|
# Paints the bar page by page; from/to are { page:, cursor: }, and
|
|
318
331
|
# from[:lanes] lists the table columns to fill, the first when absent.
|
|
332
|
+
# from[:node] names the marked node a link resolves against.
|
|
319
333
|
# Called immediately after the block is rendered, so all spanned pages exist.
|
|
320
334
|
def ink_change_bar from, to
|
|
321
335
|
return if scratch? || from.nil? || to.nil?
|
|
@@ -329,15 +343,21 @@ module Asciidoctor
|
|
|
329
343
|
top = pgnum == from[:page] ? from[:cursor] : bounds.top
|
|
330
344
|
bottom = pgnum == to[:page] ? to[:cursor] : 0
|
|
331
345
|
next if (height = top - bottom) <= 0
|
|
332
|
-
next ink_change_bar_table pgnum, top, bottom, lanes, settings if change_bar_table? settings
|
|
333
|
-
|
|
346
|
+
next ink_change_bar_table pgnum, top, bottom, lanes, settings, from if change_bar_table? settings
|
|
347
|
+
x = change_bar_x pgnum, settings
|
|
348
|
+
ink_change_bar_fill x, top, bottom, settings[:width], settings[:color]
|
|
349
|
+
# A bar a few points wide makes a poor click target, so its
|
|
350
|
+
# link also covers the gap between the text and the bar
|
|
351
|
+
edge = (change_bar_on_right? pgnum, settings) ? bounds.right : bounds.left
|
|
352
|
+
left = [x, edge].min
|
|
353
|
+
record_change_bar_link from, lanes, left, ([x + settings[:width], edge].max - left), top, bottom
|
|
334
354
|
end
|
|
335
355
|
end
|
|
336
356
|
end
|
|
337
357
|
|
|
338
|
-
def ink_change_bar_for_extent extent, lanes = nil
|
|
358
|
+
def ink_change_bar_for_extent extent, lanes = nil, node = nil
|
|
339
359
|
return unless extent
|
|
340
|
-
ink_change_bar({ page: extent.from.page, cursor: extent.from.cursor, lanes: lanes },
|
|
360
|
+
ink_change_bar({ page: extent.from.page, cursor: extent.from.cursor, lanes: lanes, node: node },
|
|
341
361
|
{ page: extent.to.page, cursor: extent.to.cursor })
|
|
342
362
|
end
|
|
343
363
|
|
|
@@ -365,7 +385,7 @@ module Asciidoctor
|
|
|
365
385
|
instance_exec(&block)
|
|
366
386
|
else
|
|
367
387
|
block.call extent
|
|
368
|
-
ink_change_bar_for_extent extent, lanes
|
|
388
|
+
ink_change_bar_for_extent extent, lanes, node
|
|
369
389
|
end
|
|
370
390
|
end
|
|
371
391
|
else
|
|
@@ -382,13 +402,97 @@ module Asciidoctor
|
|
|
382
402
|
# casing the others don't.
|
|
383
403
|
def bracket_change_bar node
|
|
384
404
|
if @change_bar_attr && !scratch? && (lanes = node_change_bar_lanes node)
|
|
385
|
-
bar_from = { page: page_number, cursor: cursor, lanes: lanes }
|
|
405
|
+
bar_from = { page: page_number, cursor: cursor, lanes: lanes, node: node }
|
|
386
406
|
end
|
|
387
407
|
result = yield
|
|
388
408
|
ink_change_bar bar_from, { page: page_number, cursor: cursor } if bar_from
|
|
389
409
|
result
|
|
390
410
|
end
|
|
391
411
|
|
|
412
|
+
# Records the area of a bar or table column on the current page, at
|
|
413
|
+
# x and width relative to the current bounds, for the link
|
|
414
|
+
# ink_change_bar_links adds once the revision history has rendered.
|
|
415
|
+
def record_change_bar_link from, lanes, x, width, top, bottom
|
|
416
|
+
return unless @change_bar_links && (node = from[:node])
|
|
417
|
+
left = bounds.absolute_left + x
|
|
418
|
+
@change_bar_links << {
|
|
419
|
+
page: page_number,
|
|
420
|
+
rect: [left, bounds.absolute_bottom + bottom, left + width, bounds.absolute_bottom + top],
|
|
421
|
+
node: node,
|
|
422
|
+
lanes: lanes,
|
|
423
|
+
}
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# Contexts that carry a revision history row of their own when
|
|
427
|
+
# they have an id (Converter#prescan_document).
|
|
428
|
+
OWN_ENTRY_CONTEXTS = [:section, :floating_title, :example, :listing, :table, :image].freeze
|
|
429
|
+
|
|
430
|
+
# The anchor of the revision history row a marked node's change
|
|
431
|
+
# text lands in, :all for the revision's All row, or nil when the
|
|
432
|
+
# node has no row: the node's own id for the contexts with rows of
|
|
433
|
+
# their own, the enclosing table's id for a table cell, and
|
|
434
|
+
# otherwise the enclosing section's id, as rollup_child_revision_
|
|
435
|
+
# entries and rollup_table_cell_revision_entries file them.
|
|
436
|
+
def change_bar_entry_anchor node
|
|
437
|
+
if node.context == :table_cell
|
|
438
|
+
table = node.parent
|
|
439
|
+
table = table.parent until table.nil? || table.context == :table
|
|
440
|
+
return table.id if table&.id
|
|
441
|
+
node = table
|
|
442
|
+
elsif OWN_ENTRY_CONTEXTS.include? node.context
|
|
443
|
+
return node.id
|
|
444
|
+
elsif !(Converter::ROLLUP_CONTEXTS.include? node.context)
|
|
445
|
+
return nil
|
|
446
|
+
end
|
|
447
|
+
(section = enclosing_section node) && section.id ? section.id : :all
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# Destination names for a revision history row: the row of anchor,
|
|
451
|
+
# the All row, and the revision's header row, for a revision group
|
|
452
|
+
# key such as 1-3.
|
|
453
|
+
def change_bar_entry_dest group, anchor
|
|
454
|
+
%(_rhrev_entry_#{group}_#{anchor})
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def change_bar_all_dest group
|
|
458
|
+
%(_rhrev_all_#{group})
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def change_bar_revision_dest group
|
|
462
|
+
%(_rhrev_revision_#{group})
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
# The destination a recorded link points at: the row of the newest
|
|
466
|
+
# revision among the link's lanes that the node marks, or that
|
|
467
|
+
# revision's header row when the revision history has no row for
|
|
468
|
+
# the node. Nil when neither rendered.
|
|
469
|
+
def change_bar_link_dest link
|
|
470
|
+
lane_of = ->(z) { @change_bar_lane_index&.[](z) || 0 }
|
|
471
|
+
marks = @change_bar_matcher.marks link[:node].attributes
|
|
472
|
+
mark, = marks.select {|_, z| link[:lanes].include? lane_of.call z }.max_by {|_, z| lane_of.call z }
|
|
473
|
+
return unless mark
|
|
474
|
+
group = revision_history.revision_key mark.to_s.delete_prefix @revision_prefix
|
|
475
|
+
anchor = change_bar_entry_anchor link[:node]
|
|
476
|
+
row = anchor == :all ? (change_bar_all_dest group) : (change_bar_entry_dest group, anchor if anchor)
|
|
477
|
+
[row, (change_bar_revision_dest group)].find {|name| name && (@change_bar_link_dests.include? name) }
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# Adds a link annotation over every recorded bar and table column
|
|
481
|
+
# whose revision history row rendered. Viewers hit the annotation
|
|
482
|
+
# added last, and a section's bar, recorded once the section ends,
|
|
483
|
+
# covers the bars of the blocks inside it, so the tallest areas go
|
|
484
|
+
# first and every nested block keeps a link of its own on top.
|
|
485
|
+
def ink_change_bar_links
|
|
486
|
+
return if scratch? || @change_bar_links.nil? || @change_bar_links.empty?
|
|
487
|
+
current = page_number
|
|
488
|
+
@change_bar_links.sort_by.with_index {|link, i| [-(link[:rect][3] - link[:rect][1]), i] }.each do |link|
|
|
489
|
+
next unless (dest = change_bar_link_dest link)
|
|
490
|
+
go_to_page link[:page] unless page_number == link[:page]
|
|
491
|
+
link_annotation link[:rect], Border: [0, 0, 0], Dest: dest
|
|
492
|
+
end
|
|
493
|
+
go_to_page current unless page_number == current
|
|
494
|
+
end
|
|
495
|
+
|
|
392
496
|
# The heading ink methods run after arrange_heading / start_new_chapter
|
|
393
497
|
# have settled the page, so this is the first accurate start position.
|
|
394
498
|
def ink_general_heading node, title, opts = {}
|
|
@@ -193,6 +193,9 @@ module Asciidoctor
|
|
|
193
193
|
if @revision_history_extent && @catalog
|
|
194
194
|
ink_revision_history node, @revision_history_extent
|
|
195
195
|
end
|
|
196
|
+
|
|
197
|
+
# After the revision history, whose rows carry the destinations
|
|
198
|
+
ink_change_bar_links
|
|
196
199
|
|
|
197
200
|
# Re-render tables with pagerhrefs (only in manual mode)
|
|
198
201
|
ink_pagerhref_tables if @manual_mode && @pagerhref_tables && !@pagerhref_tables.empty?
|
|
@@ -314,7 +317,7 @@ module Asciidoctor
|
|
|
314
317
|
if @change_bar_attr && !scratch? && (lanes = node_change_bar_lanes node) &&
|
|
315
318
|
!(!at_page_top? && ((node.option? 'unbreakable') ||
|
|
316
319
|
((node.option? 'breakable') && (node.id || node.title?))))
|
|
317
|
-
bar_from = { page: page_number, cursor: cursor, lanes: lanes }
|
|
320
|
+
bar_from = { page: page_number, cursor: cursor, lanes: lanes, node: node }
|
|
318
321
|
end
|
|
319
322
|
result = super
|
|
320
323
|
catalog_block_anchor node
|
|
@@ -332,14 +335,14 @@ module Asciidoctor
|
|
|
332
335
|
# PDF targets import whole pages; there is no cursor flow to mark
|
|
333
336
|
if @change_bar_attr && !scratch? && (lanes = node_change_bar_lanes node) &&
|
|
334
337
|
!((node.attr 'target').to_s.downcase.end_with? '.pdf')
|
|
335
|
-
bar_from = { page: page_number, cursor: cursor, lanes: lanes }
|
|
338
|
+
bar_from = { page: page_number, cursor: cursor, lanes: lanes, node: node }
|
|
336
339
|
end
|
|
337
340
|
result = super
|
|
338
341
|
catalog_block_anchor node
|
|
339
342
|
if bar_from
|
|
340
343
|
bar_to = { page: page_number, cursor: cursor }
|
|
341
344
|
# Images never split; a page change means the image moved wholesale
|
|
342
|
-
bar_from =
|
|
345
|
+
bar_from = bar_from.merge page: bar_to[:page], cursor: bounds.top if bar_to[:page] > bar_from[:page]
|
|
343
346
|
ink_change_bar bar_from, bar_to
|
|
344
347
|
end
|
|
345
348
|
result
|
|
@@ -469,9 +472,10 @@ module Asciidoctor
|
|
|
469
472
|
# trying to call super itself.
|
|
470
473
|
def convert_targeted_list node, targets = (list_item_change_bar_targets node)
|
|
471
474
|
@list_change_bar_target_stack ||= []
|
|
472
|
-
|
|
475
|
+
# The list rides along so an item's bar links to the list's row
|
|
476
|
+
@list_change_bar_target_stack.push(targets && (targets.merge node: node))
|
|
473
477
|
if targets && !targets[:whole].empty? && !scratch?
|
|
474
|
-
bar_from = { page: page_number, cursor: cursor, lanes: targets[:whole] }
|
|
478
|
+
bar_from = { page: page_number, cursor: cursor, lanes: targets[:whole], node: node }
|
|
475
479
|
result = yield
|
|
476
480
|
ink_change_bar bar_from, { page: page_number, cursor: cursor }
|
|
477
481
|
result
|
|
@@ -559,14 +563,14 @@ module Asciidoctor
|
|
|
559
563
|
def convert_list_item node, list, opts = {}
|
|
560
564
|
targets = @list_change_bar_target_stack&.last
|
|
561
565
|
if targets && (idx = list.items.index node) && (lanes = targets[:items][idx + 1])
|
|
562
|
-
ink_targeted_list_item_change_bar(lanes) { super }
|
|
566
|
+
ink_targeted_list_item_change_bar(lanes, targets[:node]) { super }
|
|
563
567
|
else
|
|
564
568
|
super
|
|
565
569
|
end
|
|
566
570
|
end
|
|
567
571
|
|
|
568
|
-
def ink_targeted_list_item_change_bar lanes
|
|
569
|
-
bar_from = { page: page_number, cursor: cursor, lanes: lanes } if @change_bar_attr && !scratch?
|
|
572
|
+
def ink_targeted_list_item_change_bar lanes, list
|
|
573
|
+
bar_from = { page: page_number, cursor: cursor, lanes: lanes, node: list } if @change_bar_attr && !scratch?
|
|
570
574
|
result = yield
|
|
571
575
|
ink_change_bar bar_from, { page: page_number, cursor: cursor } if bar_from
|
|
572
576
|
result
|
|
@@ -449,12 +449,12 @@ module Asciidoctor
|
|
|
449
449
|
header_text = "*#{major_changes_text} #{revision_label} #{prev_rev_text}*"
|
|
450
450
|
end
|
|
451
451
|
|
|
452
|
-
page_cell = build_text_cell table, 0, "*#{page_label}*"
|
|
452
|
+
page_cell = build_text_cell table, 0, "#{change_bar_link_anchor change_bar_revision_dest revision}*#{page_label}*"
|
|
453
453
|
header_cell = build_text_cell table, 1, header_text
|
|
454
454
|
table.rows.body << [page_cell, header_cell]
|
|
455
455
|
|
|
456
456
|
if (all_change = revision_history.instance_variable_get(:@all_entries)[revision])
|
|
457
|
-
row = build_list_row table, all_text, all_change, nil, nil, template_location_cell, template_asciidoc_cell, doc, is_all_or_cover: true
|
|
457
|
+
row = build_list_row table, "#{change_bar_link_anchor change_bar_all_dest revision}#{all_text}", all_change, nil, nil, template_location_cell, template_asciidoc_cell, doc, is_all_or_cover: true
|
|
458
458
|
table.rows.body << row
|
|
459
459
|
end
|
|
460
460
|
|
|
@@ -475,7 +475,8 @@ module Asciidoctor
|
|
|
475
475
|
|
|
476
476
|
entries.each do |entry|
|
|
477
477
|
location = build_location_text entry, doc
|
|
478
|
-
|
|
478
|
+
dest = change_bar_entry_dest revision, entry[:anchor] if @change_bar_link_dests && entry[:anchor]
|
|
479
|
+
row = build_list_row table, location, entry[:change], entry[:anchor], entry, template_location_cell, template_asciidoc_cell, doc, dest: dest
|
|
479
480
|
table.rows.body << row
|
|
480
481
|
end
|
|
481
482
|
end
|
|
@@ -518,15 +519,21 @@ module Asciidoctor
|
|
|
518
519
|
lines.join("\n")
|
|
519
520
|
end
|
|
520
521
|
|
|
521
|
-
def build_list_row table, location_text, change_text, anchor, entry, template_location_cell, template_asciidoc_cell, doc, is_all_or_cover: false
|
|
522
|
+
def build_list_row table, location_text, change_text, anchor, entry, template_location_cell, template_asciidoc_cell, doc, is_all_or_cover: false, dest: nil
|
|
523
|
+
if dest
|
|
524
|
+
@change_bar_link_dests << dest
|
|
525
|
+
# Raw formatter markup, as asciidoctor-pdf converts an inline anchor
|
|
526
|
+
dest_marker = %(<a id="#{dest}">#{::Asciidoctor::PDF::Converter::DummyText}</a>)
|
|
527
|
+
end
|
|
522
528
|
if anchor && location_text != '???'
|
|
523
529
|
location_cell = ::Asciidoctor::Table::Cell.new(template_location_cell.column, "", template_asciidoc_cell.attributes.dup)
|
|
524
530
|
location_cell.inner_document.blocks.clear
|
|
525
531
|
para_block = ::Asciidoctor::Block.new(location_cell.inner_document, :paragraph)
|
|
526
532
|
formatted_location = format_with_role(location_text, 'rhrevpage')
|
|
527
|
-
para_block.lines = ["<a anchor=\"#{anchor}\">#{formatted_location}</a>"]
|
|
533
|
+
para_block.lines = ["#{dest_marker}<a anchor=\"#{anchor}\">#{formatted_location}</a>"]
|
|
528
534
|
location_cell.inner_document.blocks << para_block
|
|
529
535
|
else
|
|
536
|
+
location_text = %([[#{dest}]]#{location_text}) if dest
|
|
530
537
|
location_cell = ::Asciidoctor::Table::Cell.new(template_location_cell.column, location_text, template_location_cell.attributes.dup)
|
|
531
538
|
end
|
|
532
539
|
|
|
@@ -585,6 +592,14 @@ module Asciidoctor
|
|
|
585
592
|
[location_cell, change_cell]
|
|
586
593
|
end
|
|
587
594
|
|
|
595
|
+
# An inline anchor naming dest, which also records it as rendered,
|
|
596
|
+
# or an empty string without rhrev-change-bars-link.
|
|
597
|
+
def change_bar_link_anchor dest
|
|
598
|
+
return '' unless @change_bar_link_dests
|
|
599
|
+
@change_bar_link_dests << dest
|
|
600
|
+
%([[#{dest}]])
|
|
601
|
+
end
|
|
602
|
+
|
|
588
603
|
def build_cell table, col_idx, text
|
|
589
604
|
column = table.columns[col_idx]
|
|
590
605
|
::Asciidoctor::Table::Cell.new column, text
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'asciidoctor'
|
|
4
4
|
require 'asciidoctor/converter/html5'
|
|
5
|
+
require 'set'
|
|
5
6
|
|
|
6
7
|
module Asciidoctor
|
|
7
8
|
module Rhrev
|
|
@@ -69,6 +70,74 @@ module Asciidoctor
|
|
|
69
70
|
end
|
|
70
71
|
end
|
|
71
72
|
|
|
73
|
+
# Change bar links in HTML output, enabled by rhrev-change-bars-link:
|
|
74
|
+
# each changed element carries a transparent link over its bar and the
|
|
75
|
+
# gap beside it, to the revision history row of its change. The row
|
|
76
|
+
# choice matches the PDF backend's ChangeBars#change_bar_link_dest, and
|
|
77
|
+
# the row ids match its named destinations.
|
|
78
|
+
module HtmlChangeLinks
|
|
79
|
+
OWN_ENTRY_CONTEXTS = [:section, :floating_title, :example, :listing, :table, :image].freeze
|
|
80
|
+
|
|
81
|
+
# Positions each link over the bar of its element. The bar is the
|
|
82
|
+
# element's right border, set by the site stylesheet, so the link
|
|
83
|
+
# spans the border and the padding beside it. It rides on the first
|
|
84
|
+
# link of a document, so a document without links gets none.
|
|
85
|
+
STYLE = '<style>.rhrev-changed { position: relative; } ' \
|
|
86
|
+
'.rhrev-change-link { position: absolute; top: 0; bottom: 0; ' \
|
|
87
|
+
'right: calc(-1 * var(--rhrev-change-bar-width, 2px)); ' \
|
|
88
|
+
'width: calc(var(--rhrev-change-bar-offset, 16px) + var(--rhrev-change-bar-width, 2px)); } ' \
|
|
89
|
+
'caption.rhrev-change-link-host { padding: 0; margin: 0; height: 0; }</style>'
|
|
90
|
+
|
|
91
|
+
module_function
|
|
92
|
+
|
|
93
|
+
def entry_id group, anchor
|
|
94
|
+
%(_rhrev_entry_#{group}_#{anchor})
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def all_id group
|
|
98
|
+
%(_rhrev_all_#{group})
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def revision_id group
|
|
102
|
+
%(_rhrev_revision_#{group})
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# The row ids a node's link tries, best first: the row of its change,
|
|
106
|
+
# then the header of its revision. The change is the node's newest
|
|
107
|
+
# mark that draws a bar.
|
|
108
|
+
def candidates node, matcher, prefix
|
|
109
|
+
marks = matcher.marks node.attributes
|
|
110
|
+
return [] if marks.empty?
|
|
111
|
+
key, = ThirdLevel.sort_marks(marks, prefix, matcher.kind).last
|
|
112
|
+
group = ThirdLevel.group_key (ThirdLevel.revision_of key, prefix), matcher.mode
|
|
113
|
+
anchor = entry_anchor node
|
|
114
|
+
row = anchor == :all ? (all_id group) : (entry_id group, anchor if anchor)
|
|
115
|
+
[row, (revision_id group)].compact
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# The anchor of the row a node's change text lands in, :all for the
|
|
119
|
+
# All row, or nil when the node has no row.
|
|
120
|
+
def entry_anchor node
|
|
121
|
+
if node.context == :table_cell
|
|
122
|
+
table = node.parent
|
|
123
|
+
table = table.parent until table.nil? || table.context == :table
|
|
124
|
+
return table.id if table&.id
|
|
125
|
+
node = table
|
|
126
|
+
elsif OWN_ENTRY_CONTEXTS.include? node.context
|
|
127
|
+
return node.id
|
|
128
|
+
elsif !(Html5Converter::ROLLUP_CONTEXTS.include? node.context)
|
|
129
|
+
return nil
|
|
130
|
+
end
|
|
131
|
+
section = node.parent
|
|
132
|
+
section = section.parent until section.nil? || section.context == :section
|
|
133
|
+
section&.id ? section.id : :all
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def link_html href, doc
|
|
137
|
+
%(<a class="rhrev-change-link" href="#{href}" aria-label="#{doc.attr 'version-label', 'Revision'} History"></a>)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
72
141
|
# Tags blocks marked with the current revision (revnumber 1.2 => attribute
|
|
73
142
|
# rhrev1-2) with the rhrev-changed role so a stylesheet can draw an HTML
|
|
74
143
|
# change bar (border on the right side). Enabled by the rhrev-change-bars
|
|
@@ -95,11 +164,13 @@ module Asciidoctor
|
|
|
95
164
|
prefix = document.attr 'revhistoryprefix', 'rhrev'
|
|
96
165
|
matcher = ThirdLevel.matcher document, prefix, revnumber
|
|
97
166
|
|
|
167
|
+
link = document.attr? 'rhrev-change-bars-link'
|
|
98
168
|
tagged = false
|
|
99
169
|
document.find_by { |b| TRACKED_CONTEXTS.include? b.context }.each do |block|
|
|
100
170
|
next unless block.attributes && (matcher.match? block.attributes)
|
|
101
171
|
roles = (block.attr 'role')
|
|
102
172
|
block.set_attr 'role', [roles, 'rhrev-changed'].compact.join(' ')
|
|
173
|
+
block.instance_variable_set :@rhrev_change_link, (HtmlChangeLinks.candidates block, matcher, prefix) if link
|
|
103
174
|
tagged = true
|
|
104
175
|
end
|
|
105
176
|
return document unless tagged
|
|
@@ -144,11 +215,41 @@ module Asciidoctor
|
|
|
144
215
|
def convert node, transform = node.node_name, opts = {}
|
|
145
216
|
if node.context == :rhrev
|
|
146
217
|
convert_rhrev_block node
|
|
218
|
+
elsif node.context != :table && transform == node.node_name && (candidates = node.instance_variable_get :@rhrev_change_link)
|
|
219
|
+
(link = change_link node.document, candidates) ? (super.sub(/\A\s*<[^>]*>/) { %(#{$&}#{link}) }) : super
|
|
147
220
|
else
|
|
148
221
|
super
|
|
149
222
|
end
|
|
150
223
|
end
|
|
151
224
|
|
|
225
|
+
# The link to the first candidate row the revision history table
|
|
226
|
+
# renders, or nil when it renders none of them. The first link of the
|
|
227
|
+
# document carries the stylesheet.
|
|
228
|
+
def change_link doc, candidates
|
|
229
|
+
return unless (id = candidates.find { |candidate| rhrev_row_ids(doc).include? candidate })
|
|
230
|
+
style = ((@rhrev_styled ||= ::Set.new).add? doc.object_id) ? HtmlChangeLinks::STYLE : ''
|
|
231
|
+
%(#{style}#{HtmlChangeLinks.link_html %(##{id}), doc})
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# The ids of the revision history rows the document renders, the same
|
|
235
|
+
# set generate_html_table assigns. Empty without a revision history
|
|
236
|
+
# table in the document.
|
|
237
|
+
def rhrev_row_ids doc
|
|
238
|
+
(@rhrev_row_ids ||= {})[doc.object_id] ||= begin
|
|
239
|
+
ids = ::Set.new
|
|
240
|
+
revnumber = doc.attr 'revnumber', '1.0'
|
|
241
|
+
if (doc.find_by(context: :rhrev).any?) && (doc.attr 'rhrev') != 'manual' && !(is_initial_release? revnumber) &&
|
|
242
|
+
(catalog = collect_all_entries doc)
|
|
243
|
+
catalog.sorted_revisions.each do |revision|
|
|
244
|
+
ids << (HtmlChangeLinks.revision_id revision)
|
|
245
|
+
ids << (HtmlChangeLinks.all_id revision) if catalog.all_entries[revision]
|
|
246
|
+
(catalog.entries[revision] || []).each { |entry| ids << (HtmlChangeLinks.entry_id revision, entry[:anchor]) }
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
ids
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
152
253
|
# Marks an individual changed table cell with the rhrev-changed role
|
|
153
254
|
# directly on its <td>/<th>, the requested mechanism: no bundled
|
|
154
255
|
# styling, a stylesheet decides how a changed cell looks, same as
|
|
@@ -199,7 +300,13 @@ module Asciidoctor
|
|
|
199
300
|
class_attribute = %( class="#{classes.join ' '}")
|
|
200
301
|
|
|
201
302
|
result << %(<table#{id_attribute}#{class_attribute}#{width_attribute}>)
|
|
202
|
-
|
|
303
|
+
table_link = (candidates = node.instance_variable_get :@rhrev_change_link) && (change_link doc, candidates)
|
|
304
|
+
if node.title?
|
|
305
|
+
result << %(<caption class="title">#{table_link}#{node.captioned_title}</caption>)
|
|
306
|
+
elsif table_link
|
|
307
|
+
result << %(<caption class="rhrev-change-link-host">#{table_link}</caption>)
|
|
308
|
+
end
|
|
309
|
+
link = doc.attr? 'rhrev-change-bars-link'
|
|
203
310
|
if (node.attr 'rowcount') > 0
|
|
204
311
|
slash = @void_element_slash
|
|
205
312
|
result << '<colgroup>'
|
|
@@ -233,12 +340,16 @@ module Asciidoctor
|
|
|
233
340
|
|
|
234
341
|
cell_tag_name = (tsec == :head || cell.style == :header ? 'th' : 'td')
|
|
235
342
|
cell_classes = ['tableblock', %(halign-#{cell.attr 'halign'}), %(valign-#{cell.attr 'valign'})]
|
|
236
|
-
|
|
343
|
+
cell_link = nil
|
|
344
|
+
if cell.attributes && (matcher.match? cell.attributes)
|
|
345
|
+
cell_classes << 'rhrev-changed'
|
|
346
|
+
cell_link = change_link doc, (HtmlChangeLinks.candidates cell, matcher, prefix) if link
|
|
347
|
+
end
|
|
237
348
|
cell_class_attribute = %( class="#{cell_classes.join ' '}")
|
|
238
349
|
cell_colspan_attribute = cell.colspan ? %( colspan="#{cell.colspan}") : ''
|
|
239
350
|
cell_rowspan_attribute = cell.rowspan ? %( rowspan="#{cell.rowspan}") : ''
|
|
240
351
|
cell_style_attribute = (node.document.attr? 'cellbgcolor') ? %( style="background-color: #{node.document.attr 'cellbgcolor'};") : ''
|
|
241
|
-
result << %(<#{cell_tag_name}#{cell_class_attribute}#{cell_colspan_attribute}#{cell_rowspan_attribute}#{cell_style_attribute}>#{cell_content}</#{cell_tag_name}>)
|
|
352
|
+
result << %(<#{cell_tag_name}#{cell_class_attribute}#{cell_colspan_attribute}#{cell_rowspan_attribute}#{cell_style_attribute}>#{cell_link}#{cell_content}</#{cell_tag_name}>)
|
|
242
353
|
end
|
|
243
354
|
result << '</tr>'
|
|
244
355
|
end
|
|
@@ -607,6 +718,9 @@ module Asciidoctor
|
|
|
607
718
|
major_changes_text = doc.attr 'rhrev-localization-major-changes', 'Major changes since'
|
|
608
719
|
all_text = doc.attr 'rhrev-localization-all', 'All'
|
|
609
720
|
|
|
721
|
+
link = doc.attr? 'rhrev-change-bars-link'
|
|
722
|
+
row_id = ->(id) { link ? %( id="#{id}") : '' }
|
|
723
|
+
entry_ids = ::Set.new
|
|
610
724
|
catalog.sorted_revisions.each do |revision|
|
|
611
725
|
third_level = ThirdLevel.mode doc
|
|
612
726
|
revision_label = doc.attr('version-label', 'Revision')
|
|
@@ -622,14 +736,14 @@ module Asciidoctor
|
|
|
622
736
|
end
|
|
623
737
|
|
|
624
738
|
# Header row for this revision
|
|
625
|
-
html <<
|
|
739
|
+
html << %(<tr class="rhrev-header"#{row_id.call HtmlChangeLinks.revision_id revision}>)
|
|
626
740
|
html << %(<th class="tableblock halign-left valign-top">#{section_label}</th>)
|
|
627
741
|
html << %(<th class="tableblock halign-left valign-top"><strong>#{header_text}</strong></th>)
|
|
628
742
|
html << '</tr>'
|
|
629
743
|
|
|
630
744
|
# All entry
|
|
631
745
|
if (all_change = catalog.instance_variable_get(:@all_entries)[revision])
|
|
632
|
-
html <<
|
|
746
|
+
html << %(<tr#{row_id.call HtmlChangeLinks.all_id revision}>)
|
|
633
747
|
html << %(<td>#{all_text}</td>)
|
|
634
748
|
html << %(<td>#{format_change_as_html_list(all_change)}</td>)
|
|
635
749
|
html << '</tr>'
|
|
@@ -642,8 +756,9 @@ module Asciidoctor
|
|
|
642
756
|
entries.each do |entry|
|
|
643
757
|
anchor = entry[:anchor]
|
|
644
758
|
title = entry[:title] || anchor.tr('_-', ' ').split.map(&:capitalize).join(' ')
|
|
759
|
+
id = HtmlChangeLinks.entry_id revision, anchor
|
|
645
760
|
|
|
646
|
-
html <<
|
|
761
|
+
html << %(<tr#{(entry_ids.add? id) ? (row_id.call id) : ''}>)
|
|
647
762
|
html << %(<td><a href="##{anchor}">#{title}</a></td>)
|
|
648
763
|
html << %(<td>#{format_change_as_html_list(entry[:change])}</td>)
|
|
649
764
|
html << '</tr>'
|
|
@@ -49,14 +49,14 @@ if defined? ::Prawn::Table
|
|
|
49
49
|
module Rhrev
|
|
50
50
|
module TableCellChangeBarPatch
|
|
51
51
|
# The change-bar lanes the cell draws (ChangeBars#change_bar_lanes),
|
|
52
|
-
# or nil for an unmarked cell.
|
|
53
|
-
attr_accessor :rhrev_marked
|
|
52
|
+
# or nil for an unmarked cell, and the marked Asciidoctor cell.
|
|
53
|
+
attr_accessor :rhrev_marked, :rhrev_marked_node
|
|
54
54
|
|
|
55
55
|
def draw_background pt
|
|
56
56
|
if @rhrev_marked && @pdf.respond_to?(:ink_change_bar)
|
|
57
57
|
x, y = pt
|
|
58
58
|
page = @pdf.page_number
|
|
59
|
-
@pdf.ink_change_bar({ page: page, cursor: y, lanes: @rhrev_marked }, { page: page, cursor: y - height })
|
|
59
|
+
@pdf.ink_change_bar({ page: page, cursor: y, lanes: @rhrev_marked, node: @rhrev_marked_node }, { page: page, cursor: y - height })
|
|
60
60
|
end
|
|
61
61
|
super
|
|
62
62
|
end
|
|
@@ -82,8 +82,10 @@ if defined? ::Prawn::Table
|
|
|
82
82
|
next unless (cell_data = data[r] && data[r][c]).is_a?(::Hash)
|
|
83
83
|
if (content = cell_data[:content]).is_a? ::Prawn::Table::Cell
|
|
84
84
|
content.rhrev_marked = lanes
|
|
85
|
+
content.rhrev_marked_node = cell
|
|
85
86
|
else
|
|
86
87
|
cell_data[:rhrev_marked] = lanes
|
|
88
|
+
cell_data[:rhrev_marked_node] = cell
|
|
87
89
|
end
|
|
88
90
|
end
|
|
89
91
|
end
|