asciidoctor-rhrev 1.6.0 → 1.7.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: d50d58f6581222d194cf031114bf6aeb82525b1662e6765c4e24c5c4f0e530cd
4
- data.tar.gz: b45e1e49cc09002e1267c1e31221658ec9aa26a5ca49d4ef9dc8ea3ba857e0d4
3
+ metadata.gz: 7ff948e637bb99f9d64376d94ad293310988680a558ef3796aa797911210ad48
4
+ data.tar.gz: f47c7b2ecf6aa55d7e396b44221e8d7af6445c9c6085a71182e8b0a457e09226
5
5
  SHA512:
6
- metadata.gz: 0cf35ff6b4b52a1b9851b8eb103efd5eafc0922f59045d4830b5a390a2a62607060ff79442d5c26e129512efaf06cbd0c4dfc06808f992b14fe3203f94a2e3cf
7
- data.tar.gz: 39ac612cba2f168c9cf4b26eddaea213246d18a90a3b25283b38f82c035756ae78597b01090466ae97ffe644f53f813e8f6b907a7957f5bd4f470d8ecc607e73
6
+ metadata.gz: a908055a506c6d32629d381a800c2c8911e649e0d4dfcd51ea2b5bb64a7d70e6bd4bb45229541947b9e840df95a9c3335d03179634cfe8efd99d51bedc8a5cb5
7
+ data.tar.gz: 8f5442540754978416479cf2b90f01d207993659b9e243277cdc1d645475dec294379691b62b88d1b7ad55ef3748f4af318486ea3bc7fc3ee6d6e08e2cf3d74b
data/CHANGELOG.adoc CHANGED
@@ -1,5 +1,13 @@
1
1
  = Changelog
2
2
 
3
+ == 1.7.0 (2026-09-02)
4
+
5
+ * 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
6
+ * Add per-item targeting instead, staying on the list's own existing attribute line: one or more trailing positional attributes name which item(s), by 1-based position, get the bar, `[rhrev1-2="Reworded step two",2]`. Several items and a dash range both work, mixed freely, `[rhrev1-2="Reworded the middle steps",2,4-6]`. No positional attribute at all keeps the exact behavior from every release before 1.6.0: one bar over the whole list. Confirmed directly before this design was picked, not assumed: a bare positional attribute cannot collide with anything AsciiDoc already claims on a list (style, and so on), since Asciidoctor tracks positional index by each entry's own place in the bracket, not by counting unnamed entries only, so a named `rhrev1-2="..."` occupying position 1 never contests a trailing bare position. The revision-history roll-up needs no new code at all: a targeted list is still just a marked list, already one of the existing roll-up contexts, already rolling up to its enclosing section correctly
7
+ * `Converter#convert_list_item` reads the innermost enclosing list's own target set off a stack `Converter#convert_targeted_list` maintains (the same shape `@change_bar_section_stack` already uses for nested sections), not off the list argument directly: for a `dlist` using the `unordered`, `ordered`, or `qanda` style, asciidoctor-pdf synthesizes a brand-new `List` object with no attributes of its own to render through, never the real, marked `dlist` node, so the stack is what carries the real target set down correctly regardless of which path produced the call. A `dlist` using the default or horizontal style has no per-item mechanism to target at all (asciidoctor-pdf renders those inline inside `convert_dlist` itself, no per-entry call to override, the same structural gap table-row-level marking has); a positional entry on one of those two styles falls back to marking the whole block, with a warning, rather than silently doing nothing
8
+ * Fix a bug caught during development, before this ever shipped: `bracket_change_bar`, the shared helper every other bar-inking call site already uses, gates on `change_bar? node`, checking the bracketed node's OWN attributes. A targeted item never carries the mark itself, only the enclosing list does, so reusing `bracket_change_bar` unmodified silently swallowed every targeted item's bar, real rendering showed no bar at all despite the target set correctly including the item's index. `Converter#ink_targeted_list_item_change_bar` is the fix, the same shape minus that gate, since membership in the already-resolved target set answers the "should this bar" question on its own. New regression file, `tests/regression/list-item-target-change-bars.rb`, includes a spy on `ink_change_bar` (the same technique `table-cell-change-bar.rb`'s own manual-mode pagerhref check already establishes) specifically because this exact bug would have passed every other check in the file: the revision-history roll-up is a separate mechanism from bar-inking and stayed correct the whole time the bar itself was silently missing
9
+ * Note: gem-only; the Antora extension (HTML output, no page-margin bar geometry the same way) is unaffected, npm still bumps concurrently per the standing 1.5.1 policy
10
+
3
11
  == 1.6.0 (2026-09-02)
4
12
 
5
13
  * Add per-item change bars and revision-history roll-up for individual items in an unordered or ordered list (PDF backend), previously only possible for a whole marked list. `Converter#convert_list_item` brackets asciidoctor-pdf's own per-item render call (`convert_list` calls it once per item directly, a real granular hook, unlike table rows) the same way the existing paragraph/ulist/olist/dlist overrides already bracket their own whole-block calls. No AsciiDoc syntax attaches a block attribute list directly to a list item's own bullet line, confirmed directly against a real parse: the attribute list is silently dropped, not attached to the item, the list, or anywhere else. The working construct, the same shape as an `a|` table cell's own workaround, is a block attribute list on content the item already legitimately attaches via a `+` continuation. `Converter#propagate_list_item_content_marks` copies a marked carrier block's rhrev attribute onto the enclosing item and strips it from the carrier, at prescan time, before real rendering: the strip matters because `convert_list_item`'s own `traverse_list_item` call renders an item's attached blocks through this same converter, so a left-marked carrier would independently trigger its own bar-inking hook too, a second bad bar. `list_item` is now one of the roll-up contexts, resolved via the same nearest-enclosing-section walk every other roll-up context already uses, reusing that mechanism unchanged. A marked sub-list attached to an item keeps its own independent mark; the new marker-carrier context set deliberately excludes ulist/olist/dlist/list_item so a compound item's own nested sub-list is never swallowed into the parent item's mark. A `dlist` using the `unordered`, `ordered`, or `qanda` style gets the same per-item support, since asciidoctor-pdf itself renders those through synthesized list items routed through the same hook; the default and `horizontal` `dlist` styles render each term and description pair inline with no per-item method call to override, the same category of structural gap table-row-level marking has, not attempted here. Verified directly: a real fixture rendered to PNG shows exactly one bar, spanning only the marked item, not the whole list and not bleeding into neighboring items; the revision-history table shows exactly one bullet for the marked item, nothing from its unmarked siblings. New regression file, `tests/regression/list-item-change-bars.rb`, confirmed to fail (a raised `NoMethodError`) without the fix and pass with it
@@ -1,5 +1,6 @@
1
1
  require 'asciidoctor/pdf' unless defined? Asciidoctor::PDF
2
2
  require 'fileutils'
3
+ require 'set'
3
4
  require_relative 'helpers'
4
5
  require_relative 'catalog'
5
6
  require_relative 'renderer'
@@ -399,40 +400,151 @@ module Asciidoctor
399
400
  bracket_change_bar(node) { super }
400
401
  end
401
402
 
403
+ # A list marked with only the change-text attribute, [rhrev1-2="..."],
404
+ # is unchanged from every prior release: bracket_change_bar covers
405
+ # the whole list, one bar, one roll-up bullet.
406
+ #
407
+ # A list marked with one or more trailing positional attributes,
408
+ # [rhrev1-2="...",2] or [rhrev1-2="...",2,4-6], instead targets
409
+ # specific items by their 1-based position: no bar over the whole
410
+ # list, a separate bar for just the named item(s), inked by
411
+ # convert_list_item below. The roll-up bullet is unaffected either
412
+ # way, still the list's own single entry, [rhrev1-2="..."] is read
413
+ # exactly as before.
414
+ #
415
+ # A bare positional attribute cannot collide with a list's own
416
+ # style (ulist [square], olist numbering, and so on): confirmed
417
+ # directly, Asciidoctor tracks positional index by each entry's own
418
+ # place in the bracket, not by counting unnamed entries only, so a
419
+ # named rhrev1-2="..." occupying position 1 never contests the
420
+ # trailing bare position(s) after it.
402
421
  def convert_ulist node
403
- bracket_change_bar(node) { super }
422
+ convert_targeted_list(node) { super }
404
423
  end
405
424
 
406
425
  def convert_olist node
407
- bracket_change_bar(node) { super }
426
+ convert_targeted_list(node) { super }
408
427
  end
409
428
 
429
+ # dlist's unordered/ordered/qanda styles synthesize real ListItem
430
+ # objects and route them through convert_list_item the same as
431
+ # ulist/olist, confirmed directly against asciidoctor-pdf's own
432
+ # source (convert_dlist's own case statement, then convert_list),
433
+ # so per-item targeting reaches them too. The default and
434
+ # horizontal styles render every term/description pair inline
435
+ # inside convert_dlist itself, with no per-entry call to override
436
+ # at all, the same structural gap table-row-level marking has;
437
+ # per-item targets on one of those two styles fall back to marking
438
+ # the whole block instead, with a warning, rather than silently
439
+ # doing nothing.
440
+ DLIST_STYLES_ROUTING_THROUGH_LIST_ITEM = %w(unordered ordered qanda).freeze
441
+
410
442
  def convert_dlist node
411
- bracket_change_bar(node) { super }
443
+ targets = list_item_change_bar_targets node
444
+ if (::Set === targets) && !(DLIST_STYLES_ROUTING_THROUGH_LIST_ITEM.include? node.style)
445
+ log :warn, %(rhrev per-item list marking is not supported for a dlist using the '#{node.style || 'default'}' style; marking the whole block instead)
446
+ targets = :all
447
+ end
448
+ convert_targeted_list(node, targets) { super }
449
+ end
450
+
451
+ # Shared by convert_ulist/convert_olist/convert_dlist: computes the
452
+ # target set (unless the caller, convert_dlist, already resolved
453
+ # it), pushes it so convert_list_item can read the innermost
454
+ # enclosing list's own targets, and either brackets the whole list
455
+ # (targets == :all) or leaves bar-inking to convert_list_item
456
+ # entirely (targets is a real index Set, or nil, not marked at
457
+ # all). The stack, not a single ivar, matters for a list nested
458
+ # inside a marked item's own content: the same shape
459
+ # @change_bar_section_stack already uses for nested sections.
460
+ #
461
+ # Takes the real conversion as a block, not a bare super call
462
+ # inside this method: super here would look for
463
+ # Converter#convert_targeted_list up the ancestor chain, which
464
+ # does not exist, since this method's own name differs from the
465
+ # three real converter entry points calling it. The block, called
466
+ # from inside convert_ulist/convert_olist/convert_dlist
467
+ # themselves, keeps super resolving to the right upstream method,
468
+ # the same reason bracket_change_bar takes a block rather than
469
+ # trying to call super itself.
470
+ def convert_targeted_list node, targets = (list_item_change_bar_targets node)
471
+ @list_change_bar_target_stack ||= []
472
+ @list_change_bar_target_stack.push targets
473
+ if targets == :all
474
+ bracket_change_bar(node) { yield }
475
+ else
476
+ yield
477
+ end
478
+ ensure
479
+ @list_change_bar_target_stack.pop
480
+ end
481
+
482
+ # The target set for the list currently being converted: :all
483
+ # (bar the whole list, the only behavior before this feature
484
+ # existed), nil (not marked for the current revision at all), or a
485
+ # Set of 1-based item positions parsed from one or more trailing
486
+ # positional attributes, expanding an N-M token into a range.
487
+ # Reuses change_bar? for the marked/not-marked check, the same
488
+ # predicate every other bar-inking decision in this file already
489
+ # uses, so this only ever answers for the current revnumber.
490
+ def list_item_change_bar_targets node
491
+ return nil unless change_bar? node
492
+ raw = node.instance_variable_get(:@attributes)
493
+ positional_keys = raw.keys.select {|k| ::Integer === k }.sort
494
+ return :all if positional_keys.empty?
495
+ targets = ::Set.new
496
+ positional_keys.each do |key|
497
+ token = raw[key].to_s
498
+ if (m = /\A(\d+)-(\d+)\z/.match token)
499
+ (m[1].to_i..m[2].to_i).each {|i| targets << i }
500
+ elsif /\A\d+\z/.match? token
501
+ targets << token.to_i
502
+ end
503
+ end
504
+ targets.empty? ? :all : targets
412
505
  end
413
506
 
414
507
  # A single item within a ulist/olist (and a dlist using the
415
- # unordered/ordered/qanda styles, which asciidoctor-pdf itself
416
- # synthesizes into real ListItem objects routed through here the
417
- # same way), rather than the whole list. asciidoctor-pdf's own
418
- # convert_list calls this once per item directly (not through the
419
- # normal node.convert dispatch), a real granular hook, so the same
420
- # bracket_change_bar shape as the nine block types above applies
421
- # unchanged: bare super forwards list/opts along with node
422
- # automatically. The bracket spans traverse_list_item's own call at
423
- # the end of the real convert_list_item, which covers the item's
424
- # marker, its principal text, and any attached continuation content
425
- # together, correct even for a compound, multi-block item.
508
+ # unordered/ordered/qanda styles, real ListItem objects
509
+ # asciidoctor-pdf itself synthesizes and routes through here the
510
+ # same way). Reads the innermost enclosing list's own target set
511
+ # off the top of the stack convert_targeted_list maintains, not
512
+ # off list itself: for a dlist's synthesized styles, list here is
513
+ # a brand-new List object asciidoctor-pdf builds on the fly with
514
+ # no attributes of its own, never the real, marked dlist node, so
515
+ # reading list's own attributes directly would never see the
516
+ # mark. The stack is what carries the real target set down
517
+ # correctly regardless of which path produced this call.
518
+ #
519
+ # list.items.index node relies on plain object identity, correct
520
+ # here: asciidoctor-pdf's own convert_list passes each item
521
+ # straight from list.items into this method, no copying.
426
522
  #
427
- # A dlist using its default or horizontal style is NOT covered:
428
- # asciidoctor-pdf renders those two styles' term/description pairs
429
- # entirely inline inside convert_dlist itself, with no per-entry
430
- # method call to override, the same category of structural gap
431
- # table-row-level marking has (see LIST_ITEM_MARKER_CARRIER_
432
- # CONTEXTS's own docblock and the project's declined-features
433
- # notes); a bigger, riskier lift, not attempted here.
523
+ # Deliberately not bracket_change_bar: its own change_bar? node
524
+ # gate checks the item's OWN attributes, always false here, the
525
+ # mark lives on the enclosing list, never the item. That gate is
526
+ # exactly right for every other bracket_change_bar caller (the
527
+ # node being bracketed is the node that was actually marked); it
528
+ # would silently swallow every targeted item's bar here, caught
529
+ # directly by real rendering showing no bar at all despite the
530
+ # target set correctly including this item's index. ink_targeted_
531
+ # list_item_change_bar mirrors bracket_change_bar's own shape
532
+ # without that gate, since membership in targets already answers
533
+ # the "should this bar" question on its own.
434
534
  def convert_list_item node, list, opts = {}
435
- bracket_change_bar(node) { super }
535
+ targets = @list_change_bar_target_stack&.last
536
+ if (::Set === targets) && (idx = list.items.index node) && (targets.include? idx + 1)
537
+ ink_targeted_list_item_change_bar { super }
538
+ else
539
+ super
540
+ end
541
+ end
542
+
543
+ def ink_targeted_list_item_change_bar
544
+ bar_from = { page: page_number, cursor: cursor } if @change_bar_attr && !scratch?
545
+ result = yield
546
+ ink_change_bar bar_from, { page: page_number, cursor: cursor } if bar_from
547
+ result
436
548
  end
437
549
 
438
550
  def convert_rhrev node
@@ -552,24 +664,7 @@ module Asciidoctor
552
664
  # none yet for the matching revision), rather than needing its own
553
665
  # id, its own destination, or a generated label. See
554
666
  # rollup_child_revision_entries.
555
- #
556
- # list_item is included: enclosing_section's own node.parent walk
557
- # already skips past a marked item's containing ulist/olist/dlist to
558
- # the nearest section correctly, no special-casing needed there. Its
559
- # own mark, however, never arrives via the item's own attributes
560
- # directly, see propagate_list_item_content_marks below.
561
- ROLLUP_CONTEXTS = [:paragraph, :ulist, :olist, :dlist, :list_item, :admonition, :open, :quote, :verse, :sidebar].freeze
562
-
563
- # Contexts propagate_list_item_content_marks accepts as a list
564
- # item's own marker carrier: everything ROLLUP_CONTEXTS covers
565
- # except list_item itself (the item is the propagation target, not
566
- # a valid source) and ulist/olist/dlist (a nested sub-list attached
567
- # to an item already has its own independent, correct mark-and-
568
- # roll-up path via convert_ulist/convert_olist/convert_dlist and
569
- # this same ROLLUP_CONTEXTS walk; swallowing its mark into the
570
- # parent item here would both steal it from the sub-list and give
571
- # the item a mark it never earned).
572
- LIST_ITEM_MARKER_CARRIER_CONTEXTS = (ROLLUP_CONTEXTS - [:list_item, :ulist, :olist, :dlist]).freeze
667
+ ROLLUP_CONTEXTS = [:paragraph, :ulist, :olist, :dlist, :admonition, :open, :quote, :verse, :sidebar].freeze
573
668
 
574
669
  # Walks up from node to its nearest enclosing :section, or nil if
575
670
  # none exists, i.e. node sits in the document preamble. Shared by
@@ -773,60 +868,6 @@ module Asciidoctor
773
868
  end
774
869
  end
775
870
 
776
- # No canonical AsciiDoc syntax attaches a block attribute list
777
- # directly to one list item's own bullet line; confirmed directly
778
- # (Asciidoctor.load, a real fixture): the attribute list is silently
779
- # dropped, not attached to the item, the list, or anywhere else. The
780
- # working construct, the same shape as an a| table cell's own
781
- # workaround, is a block attribute list on content the item already
782
- # legitimately attaches via a `+` continuation:
783
- #
784
- # * Item two
785
- # +
786
- # [rhrev1-2="Added item two"]
787
- # --
788
- # --
789
- #
790
- # or with real visible continuation text instead of an empty open
791
- # block. Copies any rhrev-prefixed attribute from the item's own
792
- # directly-attached marker block onto the item itself, then strips
793
- # it from that block, the same two reasons propagate_cell_content_
794
- # marks strips its own source: convert_list_item's traverse_list_
795
- # item call renders an item's attached blocks through this same
796
- # converter, so a left-marked carrier would independently trigger
797
- # its own bar-inking hook too, and the strip keeps
798
- # node_carries_own_rhrev?/rollup_child_revision_entries from later
799
- # finding and rolling up the same mark a second time from the
800
- # carrier's own now-redundant position in the tree.
801
- #
802
- # Deliberately only the item's own direct children (item.blocks),
803
- # not a recursive descent: a compound item nesting a sub-list of
804
- # its own must not have that sub-list's independent marks stolen
805
- # and reattributed to the outer item, LIST_ITEM_MARKER_CARRIER_
806
- # CONTEXTS excludes ulist/olist/dlist/list_item for exactly this
807
- # reason, restricting a valid carrier to paragraph/admonition/open/
808
- # quote/verse/sidebar.
809
- #
810
- # Must run before rollup_child_revision_entries and before real
811
- # rendering, so the item's own attributes are already in place by
812
- # the time either reads them.
813
- def propagate_list_item_content_marks doc
814
- doc.find_by(context: :list_item).each do |item|
815
- item.blocks.each do |node|
816
- next unless LIST_ITEM_MARKER_CARRIER_CONTEXTS.include? node.context
817
- attr_entries = (node.instance_variable_get(:@attributes) rescue nil)
818
- next if attr_entries.nil? || attr_entries.empty?
819
-
820
- attr_entries.keys.each do |key|
821
- key_str = key.to_s
822
- next unless key_str.start_with?(@revision_prefix)
823
- item.set_attr key_str, attr_entries[key]
824
- node.remove_attr key_str
825
- end
826
- end
827
- end
828
- end
829
-
830
871
  def rollup_table_cell_revision_entries doc
831
872
  doc.find_by(context: :table).each do |table|
832
873
  cells = (table.rows[:head] + table.rows[:body]).flatten
@@ -1059,7 +1100,6 @@ module Asciidoctor
1059
1100
  end
1060
1101
 
1061
1102
  propagate_cell_content_marks doc
1062
- propagate_list_item_content_marks doc
1063
1103
  rollup_child_revision_entries doc
1064
1104
  rollup_table_cell_revision_entries doc
1065
1105
 
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.6.0
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - 白一百 baiyibai