asciidoctor-rhrev 1.9.2 → 1.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1da65b61e67581cc903950852a590dbd89c965414c2abaa40059bb8f445e4c6a
4
- data.tar.gz: 2d6fa89231f2724df9152df5346318a05dfdf1465eb64dfd9d29df114ab6e9fa
3
+ metadata.gz: 98b0e2be6dfcba7e7865f380b4bcffe103bc94114ef1e7c4164cb90e2ecc463d
4
+ data.tar.gz: abbd012a8186d9f28cb9e438467dd1dfa741cbb9cf9f7dee83ad777765ad8a73
5
5
  SHA512:
6
- metadata.gz: 3eb4539c96fbdfb34d70335963d9312121ef2a5c93af4f049731822d0ee236b9c8d2feaf296c225e5e50b12486a599da539ff98e6c79948ff0e573991535cf41
7
- data.tar.gz: 9fe21ece1dd48a8d1c4acc605ad1b4fb9872296ff56a2438f9ccf9689d218c392899ea4c243a879957754b9bc537fd807a8b5e279b967f2d25b77c6aca6372ab
6
+ metadata.gz: 536952505824ad5d26f9338bd1e8ee77c43da3e585b6590845fd56bb76014ccec742a35d25c51ab1d908b2a79775c5feb9c1877d39cedd7af2e9f7418224e23a
7
+ data.tar.gz: 6508ab015c404efb30b4f4f841d09d4204289c9979eca9f178ae8ca2e9c4f1431c65ebd0519af2cac7374515efe056eb3381d1eca5a532c1a8ac8bd5f1bdad4d
data/CHANGELOG.adoc CHANGED
@@ -1,5 +1,10 @@
1
1
  = Changelog
2
2
 
3
+ == 1.10.0 (2026-10-28)
4
+
5
+ * 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)
6
+ * Note: gem-only; the Antora extension is unaffected, npm still bumps concurrently per the standing 1.5.1 policy
7
+
3
8
  == 1.9.2 (2026-10-27)
4
9
 
5
10
  * 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.map {|lane| column_of[lane] }.uniq.each do |column|
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
- ink_change_bar_fill (change_bar_x pgnum, settings), top, bottom, settings[:width], settings[:color]
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 = { page: bar_to[:page], cursor: bounds.top, lanes: bar_from[:lanes] } if bar_to[:page] > bar_from[:page]
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
- @list_change_bar_target_stack.push targets
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
- row = build_list_row table, location, entry[:change], entry[:anchor], entry, template_location_cell, template_asciidoc_cell, doc
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
@@ -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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asciidoctor-rhrev
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.2
4
+ version: 1.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - 白一百 baiyibai