coradoc-mirror 0.1.8 → 0.1.10

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: 0a1079e6734d1795dd52788ca44ac74475590dbf6ccb9a576a5fcf4ef92f020d
4
- data.tar.gz: ab04156585e16f2b4fa61337e3a9eca6b0f758ea82db4233c38f4422686fe611
3
+ metadata.gz: 4d7f1f75e597c4f6a4afe4b9d290d219ee3563b425dc31583918623f8228d048
4
+ data.tar.gz: 408570af8c07418f349e96c129d07f0f21b8ccb9100f7f3aee6a9bae5727443f
5
5
  SHA512:
6
- metadata.gz: d3e532a097d574ac76f76c3ba863ad82fefc8c4007572a30c7d4c4bcad66f14008236a0ef9e4aca550c4f55b91f5dbc75740afac67aa2bcc18bce5c9aae0f0b9
7
- data.tar.gz: 7bf8e4d4c71e20cb6cb1f71ef4a6e9c5cb1109a81c4fc1b5e85a2b462af197e3f5801827ceb30bce5ed7ce5f897ca3c2300d9fa27db699ee5a9eb239da9d4272
6
+ metadata.gz: f2091e8e24d6906ed7b2e40ad53538334d7cabc80a4f480f667b8e343c0e2670866ae50c13550e95bf2b08aa1c79bf629387195503688a16b8ee064b7f3994e3
7
+ data.tar.gz: 7e970c3b58e0a6824e1b9eb967cbb5bf8383ac88f9833334874bbf131771a1a571ab9bda4d38bd70a99f8ab8e3292e2c6a1f0cc6f082187c3e76683b37e90236
@@ -139,6 +139,24 @@ module Coradoc
139
139
  Node::Footnotes.new(content: entries)
140
140
  end
141
141
 
142
+ # Public entrypoint for handlers that need to dispatch a nested
143
+ # CoreModel element through the same registry path as a top-level
144
+ # walk (e.g., `+`-continuation blocks attached to a dlist dd must
145
+ # be transformed by their own registered handler rather than
146
+ # inlined as text). Returns an Array of Mirror nodes (possibly
147
+ # empty). Single source of truth for "transform one element" so
148
+ # handlers don't bypass source-line propagation.
149
+ def transform_element(element)
150
+ result = @registry.handle(element, context: self)
151
+ return [] unless result
152
+
153
+ value, = result
154
+ return [] unless value
155
+
156
+ propagate_source_line(value, element)
157
+ Array(value)
158
+ end
159
+
142
160
  private
143
161
 
144
162
  def element_has_text_content?(element)
@@ -19,19 +19,45 @@ module Coradoc
19
19
  class << self
20
20
  private
21
21
 
22
+ # One `<dt>` per term + one `<dd>` per item. Multi-term `<dt>`'s
23
+ # (AsciiDoc `term1::\nterm2::\ndef`) emit two `<dt>` elements
24
+ # sharing one `<dd>`. Single-term items — the common case —
25
+ # emit one `<dt>` + one `<dd>`.
22
26
  def definition_entry(item, context)
23
- term_node = build_term(item, context)
27
+ term_nodes = build_terms(item, context)
24
28
  desc_node = build_description(item, context)
25
- return nil unless term_node || desc_node
29
+ return nil if term_nodes.empty? && desc_node.nil?
26
30
 
27
- [term_node, desc_node].compact
31
+ term_nodes + [desc_node].compact
28
32
  end
29
33
 
30
- def build_term(item, context)
31
- term_text = item.term
32
- return nil unless term_text && !term_text.to_s.empty?
34
+ # Emit one DefinitionTerm node per term in `item.terms`.
35
+ # Falls back to `[term]` for items populated via paths that
36
+ # only set the singular `term` accessor (backward compat).
37
+ def build_terms(item, context)
38
+ term_list = terms_for(item)
39
+ return [] if term_list.empty?
33
40
 
34
- term_children = item.term_children if item.is_a?(CoreModel::DefinitionItem)
41
+ term_list.map { |term| build_term_node(term, item, context) }
42
+ end
43
+
44
+ # Source of truth for "the terms on this `<dt>`": the `terms`
45
+ # collection when populated, else `[term]` for legacy callers.
46
+ def terms_for(item)
47
+ collection = item.terms if item.is_a?(CoreModel::DefinitionItem)
48
+ return Array(collection) unless collection.nil? || collection.empty?
49
+
50
+ primary = item.term
51
+ primary.to_s.empty? ? [] : [primary.to_s]
52
+ end
53
+
54
+ # Build a single DefinitionTerm node for one term string,
55
+ # carrying inline children when the source had them. Children
56
+ # map 1:1 to the FIRST term only (multi-term `<dt>`'s with
57
+ # inline markup on later terms is rare; the parser doesn't
58
+ # capture per-term children today).
59
+ def build_term_node(term_text, item, context)
60
+ term_children = (item.term_children if (term_text == terms_for(item).first) && item.is_a?(CoreModel::DefinitionItem))
35
61
  if term_children && !term_children.empty?
36
62
  inline_nodes = term_children.flat_map do |child|
37
63
  Handlers::Inline.process_child(child, context)
@@ -44,9 +70,43 @@ module Coradoc
44
70
 
45
71
  def build_description(item, context)
46
72
  desc_nodes = description_nodes(item, context)
47
- return nil if desc_nodes.empty?
73
+ attached_nodes = attached_block_nodes(item, context)
74
+ nested_nodes = nested_dl_nodes(item, context)
75
+ all_nodes = desc_nodes.concat(attached_nodes).concat(nested_nodes)
76
+ return nil if all_nodes.empty?
77
+
78
+ Node::DefinitionDescription.new(content: all_nodes)
79
+ end
48
80
 
49
- Node::DefinitionDescription.new(content: desc_nodes)
81
+ # `+`-continuation blocks attached to this dd in AsciiDoc source
82
+ # become children of the same DefinitionDescription node so they
83
+ # render inside the same <dd> element. Dispatch each through
84
+ # `context.transform_element` — same registry path as a top-level
85
+ # walk, so every registered block handler (paragraph, admonition,
86
+ # source, etc.) applies.
87
+ def attached_block_nodes(item, context)
88
+ return [] unless item.is_a?(CoreModel::DefinitionItem)
89
+ return [] if item.attached_children.empty?
90
+
91
+ item.attached_children.flat_map do |block|
92
+ context.transform_element(block)
93
+ end
94
+ end
95
+
96
+ # Multi-level dlist: AsciiDoc `::`/`:::`/`::::` delimiters
97
+ # express nesting depth. The parser builds the tree
98
+ # (CoreModel::DefinitionItem#nested); the mirror handler must
99
+ # recurse so the nested <dl> renders INSIDE the parent <dd>.
100
+ # Single source of truth: this method recurses through
101
+ # `DefinitionList.call`, so nested items use the same dispatch
102
+ # path as top-level items (DRY).
103
+ def nested_dl_nodes(item, context)
104
+ return [] unless item.is_a?(CoreModel::DefinitionItem)
105
+
106
+ nested = item.nested
107
+ return [] unless nested && nested.items.any?
108
+
109
+ [DefinitionList.call(nested, context: context)].compact
50
110
  end
51
111
 
52
112
  # Emit one text node per source — never both. Rich
@@ -56,7 +116,9 @@ module Coradoc
56
116
  # instances populated by paths that don't build inline children.
57
117
  def description_nodes(item, context)
58
118
  unless item.is_a?(CoreModel::DefinitionItem)
59
- return Array(item.definitions).map { |defn| context.text_node(defn.to_s) }
119
+ return Array(item.definitions).map do |defn|
120
+ context.text_node(defn.to_s)
121
+ end
60
122
  end
61
123
 
62
124
  children = item.definition_children
@@ -123,10 +123,59 @@ module Coradoc
123
123
  end
124
124
 
125
125
  def build_simple_mark(element, context, mark_class)
126
- text = extract_inline_text(element)
127
- return nil if text.empty?
126
+ # InlineElement always has children after the dual-shape fix
127
+ # (Bug 16A follow-up). Walk the children and apply this mark
128
+ # to each text leaf. Inner mark elements get the outer mark
129
+ # added to their marks list, producing ProseMirror's flat
130
+ # text-node-with-marks shape.
131
+ #
132
+ # Fallback: programmatically constructed elements (e.g. in
133
+ # specs or other format gems) may not have children. Fall
134
+ # back to the content string in that case.
135
+ children = element.children
136
+ if children.nil? || children.empty?
137
+ text = extract_inline_text(element)
138
+ return nil if text.empty?
139
+
140
+ return context.text_node(text, marks: [mark_class.new])
141
+ end
128
142
 
129
- context.text_node(text, marks: [mark_class.new])
143
+ flatten_marked_children(children, [mark_class.new], context)
144
+ end
145
+
146
+ # Walk a list of children, attaching the active set of marks
147
+ # to each text leaf. When a child is itself an InlineElement
148
+ # with its own mark (e.g. CodeElement inside BoldElement),
149
+ # the inner mark is added to the active set and recursion
150
+ # proceeds into its children. Produces the conjunction of
151
+ # marks for each text run that ProseMirror expects:
152
+ # **a `b` c** → text("a ", [bold]), text("b", [bold, code]), text(" c", [bold])
153
+ def flatten_marked_children(children, active_marks, context)
154
+ result = []
155
+ children.each do |child|
156
+ case child
157
+ when CoreModel::TextContent
158
+ next if child.text.nil? || child.text.empty?
159
+
160
+ result << context.text_node(child.text, marks: active_marks.dup)
161
+ when CoreModel::InlineElement
162
+ inner_mark_class = SIMPLE_MARK_TYPES[child.class]
163
+ if inner_mark_class && child.children&.any?
164
+ combined = active_marks + [inner_mark_class.new]
165
+ result.concat(flatten_marked_children(child.children, combined, context))
166
+ elsif inner_mark_class
167
+ text = extract_inline_text(child)
168
+ next if text.empty?
169
+
170
+ combined = active_marks + [inner_mark_class.new]
171
+ result << context.text_node(text, marks: combined)
172
+ else
173
+ nested = dispatch_inline(child, context)
174
+ result << nested if nested
175
+ end
176
+ end
177
+ end
178
+ result
130
179
  end
131
180
 
132
181
  def build_link_mark(element, context)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Coradoc
4
4
  module Mirror
5
- VERSION = '0.1.8'
5
+ VERSION = '0.1.10'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coradoc-mirror
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.