asciidoctor-rhrev 1.7.0 → 1.8.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: 7ff948e637bb99f9d64376d94ad293310988680a558ef3796aa797911210ad48
4
- data.tar.gz: f47c7b2ecf6aa55d7e396b44221e8d7af6445c9c6085a71182e8b0a457e09226
3
+ metadata.gz: f150c21ea6f2c3fe871bc18d4e91cae846279104ddc72d72e9b7f06e4a009a20
4
+ data.tar.gz: 5f1ef686a5575db2d83e818577310e9f81060beb891ec800abcad29f19327c2b
5
5
  SHA512:
6
- metadata.gz: a908055a506c6d32629d381a800c2c8911e649e0d4dfcd51ea2b5bb64a7d70e6bd4bb45229541947b9e840df95a9c3335d03179634cfe8efd99d51bedc8a5cb5
7
- data.tar.gz: 8f5442540754978416479cf2b90f01d207993659b9e243277cdc1d645475dec294379691b62b88d1b7ad55ef3748f4af318486ea3bc7fc3ee6d6e08e2cf3d74b
6
+ metadata.gz: f6de41ae3d96fdf56c8eeda288f1bc2650d365019e641ccc5259755107e8f81af2be29715b6ef4a0c9f42e8eb22d53f7db73bfe38e2d447b19e7d1382f3e8047
7
+ data.tar.gz: 68251c6a35b808bd9e138cc6cfd3996969d052c20fac88e3bb8b17f5efd8b9a388a243a4c9a50471ba1e54fd95e126ca0acab584abd4c627c9a0067c84bf9d89
data/CHANGELOG.adoc CHANGED
@@ -1,5 +1,15 @@
1
1
  = Changelog
2
2
 
3
+ == 1.8.0 (2026-09-08)
4
+
5
+ * Table-cell marks no longer require the AsciiDoc-style cell prefix `a|`: an rhrev attribute list alone on the first line of any cell's content marks the cell, for every style prefix and none, and for cells in a header row, which no earlier release could mark at all. `Asciidoctor::Rhrev::CellTextMarks` reads the attribute list off the cell's raw text, sets the attributes on the cell, and strips the line, before the existing `a|` propagation runs; the PDF prescan, the HTML treeprocessor, and both Antora extension files use it. In Antora output the lift wraps a marked plain or header cell's text in an `rhrev-changed` role span so the existing table post-processor tags the cell's own `<td>`/`<th>`; a literal cell renders verbatim and stays untagged there, its mark still rolls up. Foot rows now join every table-cell walk. New fixture `tests/features/test-change-bars-table-header-cells.adoc` and regression files `tests/regression/table-header-cell-marks.rb`, `html-table-header-cell-marks.rb`, and `js-table-header-cell-marks.js`
6
+ * Multi-row table headers: rows promoted into the header with the `hrows` tree processor carry their marks the same way. asciidoctor-pdf drops header-cell rowspan (asciidoctor/asciidoctor-pdf#2631); the `multirow_table_header.rb` converter in baiyibai-antora-collector-assembler-pdf-themes restores it together with the `hrows` promotion
7
+
8
+ == 1.7.1 (2026-09-02)
9
+
10
+ * Fix two revision-history entries sharing the same start page sorting in the wrong relative order whenever one of them has body content that spans a page break (PDF backend). `Converter#convert_section` correctly reads a section's own starting page via `pdf-page-start`, set by asciidoctor-pdf's own `convert_section` right before the heading itself is inked, but paired it with a `:dest][:y]` value read from `cursor` after `result = super` returns, once the section's whole body (nested content, a large table, anything that can span several physical pages of its own) had already rendered. For a page-spanning section that cursor belongs to whatever page the body finished on, not the section's own start page `pdf-page-start` names, so the two stopped describing the same physical location at all; two entries sharing that start page are ordered by this `y` value alone (`renderer.rb`'s own `entries.sort_by`), and a wrong one can reverse their true top-to-bottom order. Reported directly, with real captured values from a real document, confirming exactly this: a page-spanning section's own `y` secretly belonging to the next physical page, sorting it ahead of another entry that genuinely was higher on their shared page. `ChangeBars#record_section_heading_position`, called unconditionally, not gated on change bars being enabled at all, from `ink_general_heading`/`ink_chapter_title`/`ink_part_title`, the same moment upstream itself captures `pdf-page-start`, right before the heading is actually inked. `Converter#convert_section` reads it back instead of a live post-super cursor, so `page_num` and `y` always describe the section's own start consistently. New regression file, `tests/regression/section-dest-y-page-mismatch.rb`, forces the page-spanning body with an explicit `<<<` page break rather than tuning paragraph-repeat counts against exact font metrics, deterministic regardless of theme or font; confirmed to fail (reversed order) without the fix and pass with it
11
+ * Note: gem-only; the Antora extension (HTML output, no PDF page/cursor concept) is unaffected, npm still bumps concurrently per the standing 1.5.1 policy
12
+
3
13
  == 1.7.0 (2026-09-02)
4
14
 
5
15
  * Replace 1.6.0's per-item list change bars with a different design, after real user feedback that the syntax it needed was too heavy: marking one item required a `+` continuation, an attribute line, and a two-line empty open block, four lines to mark a single bullet, far more than every other rhrev-markable construct's own single attribute line. 1.6.0's mechanism (`Converter#propagate_list_item_content_marks`, the attached-continuation-block workaround, `list_item` added to the roll-up contexts) is fully removed, not just superseded
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asciidoctor
4
+ module Rhrev
5
+ # Lifts an rhrev attribute list written as the first line of a table
6
+ # cell's content onto the cell itself, then removes that line from the
7
+ # cell's raw text, so the mark reaches the cell for every cell style,
8
+ # including cells in a header row.
9
+ #
10
+ # Asciidoctor parses a header-row cell without any style at all:
11
+ # Table::Cell#initialize forces the style to nil whenever the table's
12
+ # header option is in effect, so the a| prefix is consumed and ignored
13
+ # there, and the cell's content is never parsed as blocks. Rows promoted
14
+ # into the header after parsing (the hrows tree processor from
15
+ # asciidoctor-extensions-lab) keep their parsed style, but asciidoctor-pdf
16
+ # and Asciidoctor's own HTML5 converter render every head cell through
17
+ # the plain-text path, which returns an AsciiDoc-style cell's raw source,
18
+ # attribute line included. Both cases leave the attribute list in the
19
+ # cell's raw text. Reading that raw text directly, before any converter
20
+ # touches it, covers every style, every row kind, and every promotion
21
+ # mechanism with one rule: an rhrev attribute list alone on the first
22
+ # line of the cell's content marks the cell.
23
+ #
24
+ # Reads and writes only the cell's raw @text. Cell#text applies
25
+ # substitutions, and the PDF converter's prescan runs with attribute
26
+ # substitution deliberately suppressed because counter attributes
27
+ # increment on every evaluation; table_contains_pagerhref? already takes
28
+ # the same care. Idempotent: once the line is stripped a second pass
29
+ # finds nothing to lift, which matters for asciidoctor-pdf's scratch
30
+ # passes and rhrev's own deferred pagerhref re-render, both of which
31
+ # reuse the same node objects.
32
+ #
33
+ # For an AsciiDoc-style cell the inner document was already parsed from
34
+ # the same raw text, with the attribute list attached to its first nested
35
+ # block; propagate_cell_content_marks still moves that mark onto the cell
36
+ # and strips it from the nested block, so the two mechanisms agree on the
37
+ # cell's attributes and the strip here only affects the raw text a head
38
+ # cell renders from.
39
+ #
40
+ # An attribute list without any rhrev-prefixed key is left untouched: it
41
+ # belongs to the writer's own content.
42
+ module CellTextMarks
43
+ LEADING_ATTRIBUTE_LIST_RX = /\A\[([^\[\]\n]+)\]\n/
44
+
45
+ module_function
46
+
47
+ # Lifts marks from every cell of every table in doc. Returns the number
48
+ # of cells changed.
49
+ def lift doc, prefix
50
+ lifted = 0
51
+ doc.find_by(context: :table).each do |table|
52
+ rows = table.rows
53
+ (rows.head + rows.body + rows.foot).flatten.each do |cell|
54
+ lifted += 1 if lift_cell cell, prefix
55
+ end
56
+ end
57
+ lifted
58
+ end
59
+
60
+ # Lifts a mark from one cell. Returns true when the cell changed.
61
+ def lift_cell cell, prefix
62
+ raw = cell.instance_variable_get :@text
63
+ return false unless raw.is_a?(::String) && (m = LEADING_ATTRIBUTE_LIST_RX.match raw)
64
+ attrs = (::Asciidoctor::AttributeList.new m[1]).parse
65
+ marks = attrs.select { |key, _| key.to_s.start_with? prefix }
66
+ return false if marks.empty?
67
+ marks.each { |key, value| cell.set_attr key.to_s, value }
68
+ cell.instance_variable_set :@text, (raw.slice m[0].length, raw.length)
69
+ true
70
+ end
71
+ end
72
+ end
73
+ end
@@ -62,6 +62,29 @@ module Asciidoctor
62
62
  @change_bar_starts&.delete node
63
63
  end
64
64
 
65
+ # A section's own cursor position at heading-ink time, before its
66
+ # body (potentially spanning several pages) renders. Unconditional,
67
+ # not gated on change bars being enabled at all: Converter#
68
+ # convert_section's own revision-history dest needs this, pairing
69
+ # it with pdf-page-start (also captured at this same moment,
70
+ # upstream's own convert_section, right before the heading ink
71
+ # call this method wraps) so both fields describe the section's
72
+ # own start consistently. A page-spanning section's cursor read
73
+ # after the whole body renders belongs to wherever that body
74
+ # finished, not the section's own start page; two entries sharing
75
+ # the same start page then sort by a y value that was never on
76
+ # that page at all, reversing their true top-to-bottom order,
77
+ # confirmed directly against a real document.
78
+ def record_section_heading_position node
79
+ return unless node.context == :section
80
+ @section_heading_positions ||= {}
81
+ @section_heading_positions[node] = cursor
82
+ end
83
+
84
+ def take_section_heading_position node
85
+ @section_heading_positions&.delete node
86
+ end
87
+
65
88
  # A section's bar covers only its own directly-owned content, not any
66
89
  # nested child sections/chapters: as soon as a child section begins
67
90
  # rendering, close out the bar of every still-open ancestor section
@@ -177,11 +200,13 @@ module Asciidoctor
177
200
  # have settled the page, so this is the first accurate start position.
178
201
  def ink_general_heading node, title, opts = {}
179
202
  record_change_bar_start node if @change_bar_attr && !scratch? && (change_bar? node)
203
+ record_section_heading_position node unless scratch?
180
204
  super
181
205
  end
182
206
 
183
207
  def ink_chapter_title node, title, opts = {}
184
208
  record_change_bar_start node if @change_bar_attr && !scratch? && (change_bar? node)
209
+ record_section_heading_position node unless scratch?
185
210
  super
186
211
  end
187
212
 
@@ -189,6 +214,7 @@ module Asciidoctor
189
214
  # so it must be overridden separately
190
215
  def ink_part_title node, title, opts = {}
191
216
  record_change_bar_start node if @change_bar_attr && !scratch? && (change_bar? node)
217
+ record_section_heading_position node unless scratch?
192
218
  super
193
219
  end
194
220
  end
@@ -227,6 +227,27 @@ module Asciidoctor
227
227
  # page break. A pre-super page_number would be the page before that
228
228
  # break, one page too low for any chapter-starting section.
229
229
  page_num = ((node.attr 'pdf-page-start') || page_number).to_i
230
+
231
+ # y must describe the same physical location page_num does: the
232
+ # section's own start, not wherever cursor happens to sit once
233
+ # this whole call returns. A section's body can span several
234
+ # pages of its own (nested content, a large table, and so on);
235
+ # a post-super cursor read then belongs to whatever page that
236
+ # body finished on, not page_num's page, and the two stop
237
+ # describing the same point at all. take_section_heading_
238
+ # position (change_bars.rb) captured cursor at the same moment
239
+ # upstream captured pdf-page-start, right before the heading
240
+ # itself was inked, so pairing the two here always describes
241
+ # the section's own start consistently. Reported directly
242
+ # against a real document: two entries sharing the same
243
+ # page_sortable sorted in the wrong relative order, one of
244
+ # them a page-spanning section whose old post-super y actually
245
+ # belonged to the page after the one it was paired with.
246
+ # Falls back to the old post-super cursor only for a section
247
+ # with no heading ink call at all (a hidden title), no worse
248
+ # than before for that one remaining case.
249
+ y_pos = take_section_heading_position(node) || cursor
250
+
230
251
  @anchor_catalog ||= {}
231
252
  @anchor_catalog[node.id] ||= {
232
253
  title: node.title,
@@ -235,10 +256,10 @@ module Asciidoctor
235
256
  context: node.context,
236
257
  level: node.level
237
258
  }
238
- @anchor_catalog[node.id][:dest] = { page: (rhrev_display_page page_num), physical_page: page_num, y: cursor }
259
+ @anchor_catalog[node.id][:dest] = { page: (rhrev_display_page page_num), physical_page: page_num, y: y_pos }
239
260
 
240
261
  # Link destination to page for revision history
241
- revision_history.link_dest_to_page node.id, page_num, (@rhrev_body_start_page || 1), y: cursor
262
+ revision_history.link_dest_to_page node.id, page_num, (@rhrev_body_start_page || 1), y: y_pos
242
263
  end
243
264
 
244
265
  # Start position was captured at heading-ink time (after any page advance)
@@ -262,7 +283,7 @@ module Asciidoctor
262
283
  # way, only the bar geometry was wrong, and bars are graphical.
263
284
  def marked_table_cell_bar_rows node
264
285
  marked_rows = []
265
- (node.rows[:head] + node.rows[:body]).each_with_index do |row, row_idx|
286
+ (node.rows[:head] + node.rows[:body] + node.rows[:foot]).each_with_index do |row, row_idx|
266
287
  marked_rows << row_idx if row.any? do |cell|
267
288
  cell.respond_to?(:attributes) && cell.attributes && (cell.attributes.key? @change_bar_attr)
268
289
  end
@@ -845,9 +866,16 @@ module Asciidoctor
845
866
  # Must run before rollup_table_cell_revision_entries and before real
846
867
  # rendering, so the cell's own attributes are already in place by
847
868
  # the time either reads them.
869
+ #
870
+ # CellTextMarks.lift (cell_text_marks.rb) runs right before this: it
871
+ # reads the same attribute list off the cell's raw text, which is
872
+ # the only place it survives for a plain cell or any header-row
873
+ # cell, so a mark no longer needs the a| form at all. Both paths
874
+ # set the same attributes on the cell; this one still strips the
875
+ # nested block for the double-bar reason described here.
848
876
  def propagate_cell_content_marks doc
849
877
  doc.find_by(context: :table).each do |table|
850
- cells = (table.rows[:head] + table.rows[:body]).flatten
878
+ cells = (table.rows[:head] + table.rows[:body] + table.rows[:foot]).flatten
851
879
  cells.each do |cell|
852
880
  next unless cell.style == :asciidoc
853
881
  inner = (cell.inner_document rescue nil)
@@ -870,7 +898,7 @@ module Asciidoctor
870
898
 
871
899
  def rollup_table_cell_revision_entries doc
872
900
  doc.find_by(context: :table).each do |table|
873
- cells = (table.rows[:head] + table.rows[:body]).flatten
901
+ cells = (table.rows[:head] + table.rows[:body] + table.rows[:foot]).flatten
874
902
  cells.each do |cell|
875
903
  attr_entries = (cell.instance_variable_get(:@attributes) rescue nil)
876
904
  next if attr_entries.nil? || attr_entries.empty?
@@ -1099,6 +1127,7 @@ module Asciidoctor
1099
1127
  end
1100
1128
  end
1101
1129
 
1130
+ ::Asciidoctor::Rhrev::CellTextMarks.lift doc, @revision_prefix
1102
1131
  propagate_cell_content_marks doc
1103
1132
  rollup_child_revision_entries doc
1104
1133
  rollup_table_cell_revision_entries doc
@@ -31,13 +31,20 @@ module Asciidoctor
31
31
  # as the PDF side: leaving it in place would tag both the cell and the
32
32
  # nested paragraph independently, a redundant double border for what is
33
33
  # really one mark on one piece of content.
34
+ #
35
+ # CellTextMarks.lift (cell_text_marks.rb) runs first inside this
36
+ # treeprocessor: it reads the same attribute list off the cell's raw
37
+ # text, which is the only place it survives for a plain cell or any
38
+ # header-row cell, so a mark no longer needs the a| form at all.
34
39
  class TableCellMarkPropagationHtmlTreeprocessor < Asciidoctor::Extensions::Treeprocessor
35
40
  def process document
36
41
  return document unless document.basebackend? 'html'
37
42
  prefix = document.attr 'revhistoryprefix', 'rhrev'
38
43
 
44
+ ::Asciidoctor::Rhrev::CellTextMarks.lift document, prefix
45
+
39
46
  document.find_by(context: :table).each do |table|
40
- cells = (table.rows[:head] + table.rows[:body]).flatten
47
+ cells = (table.rows[:head] + table.rows[:body] + table.rows[:foot]).flatten
41
48
  cells.each do |cell|
42
49
  next unless cell.style == :asciidoc
43
50
  inner = (cell.inner_document rescue nil)
@@ -469,7 +476,7 @@ module Asciidoctor
469
476
  # treeprocessor phase, before this (a converter method) ever runs.
470
477
  def rollup_table_cell_revision_entries catalog, doc, prefix
471
478
  doc.find_by(context: :table).each do |table|
472
- cells = (table.rows[:head] + table.rows[:body]).flatten
479
+ cells = (table.rows[:head] + table.rows[:body] + table.rows[:foot]).flatten
473
480
  cells.each do |cell|
474
481
  attr_entries = (cell.instance_variable_get(:@attributes) rescue nil)
475
482
  next if attr_entries.nil? || attr_entries.empty?
@@ -6,6 +6,7 @@ require 'asciidoctor' unless defined? Asciidoctor
6
6
  # Load shared components (processors, helpers, catalog)
7
7
  require_relative 'rhrev/processors'
8
8
  require_relative 'rhrev/helpers'
9
+ require_relative 'rhrev/cell_text_marks'
9
10
  require_relative 'rhrev/catalog'
10
11
 
11
12
  # Try to load the PDF converter support if asciidoctor-pdf is available
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.7.0
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - 白一百 baiyibai
@@ -38,6 +38,7 @@ files:
38
38
  - lib/asciidoctor-rhrev.rb
39
39
  - lib/asciidoctor/rhrev.rb
40
40
  - lib/asciidoctor/rhrev/catalog.rb
41
+ - lib/asciidoctor/rhrev/cell_text_marks.rb
41
42
  - lib/asciidoctor/rhrev/change_bars.rb
42
43
  - lib/asciidoctor/rhrev/converter.rb
43
44
  - lib/asciidoctor/rhrev/exporter.rb