coradoc-html 1.1.7 → 1.1.13

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.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/lib/coradoc/html/config.rb +36 -12
  3. data/lib/coradoc/html/converter_base.rb +26 -68
  4. data/lib/coradoc/html/drop/annotation_drop.rb +31 -0
  5. data/lib/coradoc/html/drop/base.rb +77 -0
  6. data/lib/coradoc/html/drop/bibliography_drop.rb +15 -0
  7. data/lib/coradoc/html/drop/bibliography_entry_drop.rb +24 -0
  8. data/lib/coradoc/html/drop/block_drop.rb +69 -0
  9. data/lib/coradoc/html/drop/definition_item_drop.rb +36 -0
  10. data/lib/coradoc/html/drop/definition_list_drop.rb +15 -0
  11. data/lib/coradoc/html/drop/document_drop.rb +52 -0
  12. data/lib/coradoc/html/drop/drop_factory.rb +73 -0
  13. data/lib/coradoc/html/drop/footnote_drop.rb +24 -0
  14. data/lib/coradoc/html/drop/image_drop.rb +35 -0
  15. data/lib/coradoc/html/drop/inline_element_drop.rb +64 -0
  16. data/lib/coradoc/html/drop/list_block_drop.rb +23 -0
  17. data/lib/coradoc/html/drop/list_item_drop.rb +20 -0
  18. data/lib/coradoc/html/drop/table_cell_drop.rb +35 -0
  19. data/lib/coradoc/html/drop/table_drop.rb +15 -0
  20. data/lib/coradoc/html/drop/table_row_drop.rb +23 -0
  21. data/lib/coradoc/html/drop/term_drop.rb +24 -0
  22. data/lib/coradoc/html/drop/text_content_drop.rb +15 -0
  23. data/lib/coradoc/html/drop/toc_drop.rb +15 -0
  24. data/lib/coradoc/html/drop/toc_entry_drop.rb +32 -0
  25. data/lib/coradoc/html/escape.rb +29 -0
  26. data/lib/coradoc/html/input/cleaner.rb +4 -33
  27. data/lib/coradoc/html/input/config.rb +4 -3
  28. data/lib/coradoc/html/input/converters/a.rb +8 -19
  29. data/lib/coradoc/html/input/converters/aside.rb +4 -5
  30. data/lib/coradoc/html/input/converters/audio.rb +8 -35
  31. data/lib/coradoc/html/input/converters/base.rb +29 -27
  32. data/lib/coradoc/html/input/converters/blockquote.rb +4 -2
  33. data/lib/coradoc/html/input/converters/br.rb +4 -4
  34. data/lib/coradoc/html/input/converters/bypass.rb +68 -67
  35. data/lib/coradoc/html/input/converters/code.rb +7 -5
  36. data/lib/coradoc/html/input/converters/div.rb +4 -4
  37. data/lib/coradoc/html/input/converters/dl.rb +3 -25
  38. data/lib/coradoc/html/input/converters/drop.rb +13 -13
  39. data/lib/coradoc/html/input/converters/em.rb +5 -3
  40. data/lib/coradoc/html/input/converters/figure.rb +3 -26
  41. data/lib/coradoc/html/input/converters/h.rb +9 -11
  42. data/lib/coradoc/html/input/converters/head.rb +5 -4
  43. data/lib/coradoc/html/input/converters/hr.rb +4 -5
  44. data/lib/coradoc/html/input/converters/img.rb +4 -9
  45. data/lib/coradoc/html/input/converters/li.rb +3 -1
  46. data/lib/coradoc/html/input/converters/mark.rb +3 -1
  47. data/lib/coradoc/html/input/converters/markup.rb +4 -8
  48. data/lib/coradoc/html/input/converters/math.rb +7 -14
  49. data/lib/coradoc/html/input/converters/media_base.rb +50 -0
  50. data/lib/coradoc/html/input/converters/ol.rb +6 -8
  51. data/lib/coradoc/html/input/converters/p.rb +43 -34
  52. data/lib/coradoc/html/input/converters/pass_through.rb +2 -4
  53. data/lib/coradoc/html/input/converters/positional_formatting.rb +37 -0
  54. data/lib/coradoc/html/input/converters/pre.rb +3 -3
  55. data/lib/coradoc/html/input/converters/q.rb +6 -3
  56. data/lib/coradoc/html/input/converters/strong.rb +4 -2
  57. data/lib/coradoc/html/input/converters/sub.rb +7 -23
  58. data/lib/coradoc/html/input/converters/sup.rb +7 -23
  59. data/lib/coradoc/html/input/converters/table.rb +3 -1
  60. data/lib/coradoc/html/input/converters/td.rb +4 -30
  61. data/lib/coradoc/html/input/converters/text.rb +4 -3
  62. data/lib/coradoc/html/input/converters/tr.rb +3 -2
  63. data/lib/coradoc/html/input/converters/video.rb +14 -36
  64. data/lib/coradoc/html/input/converters.rb +17 -35
  65. data/lib/coradoc/html/input/html_converter.rb +2 -74
  66. data/lib/coradoc/html/input/plugin.rb +8 -50
  67. data/lib/coradoc/html/input/plugins/plateau.rb +4 -19
  68. data/lib/coradoc/html/input/postprocessor.rb +3 -9
  69. data/lib/coradoc/html/input.rb +26 -8
  70. data/lib/coradoc/html/layout_renderer.rb +163 -0
  71. data/lib/coradoc/html/output.rb +6 -12
  72. data/lib/coradoc/html/renderer.rb +84 -350
  73. data/lib/coradoc/html/section_numberable.rb +9 -0
  74. data/lib/coradoc/html/spa.rb +29 -270
  75. data/lib/coradoc/html/static.rb +29 -238
  76. data/lib/coradoc/html/template_caching.rb +31 -0
  77. data/lib/coradoc/html/template_config.rb +11 -70
  78. data/lib/coradoc/html/template_helpers.rb +39 -31
  79. data/lib/coradoc/html/template_locator.rb +17 -11
  80. data/lib/coradoc/html/theme.rb +1 -7
  81. data/lib/coradoc/html/title_text.rb +57 -0
  82. data/lib/coradoc/html/toc_builder.rb +105 -0
  83. data/lib/coradoc/html/toc_serializer.rb +33 -0
  84. data/lib/coradoc/html/transform/from_core_model.rb +13 -12
  85. data/lib/coradoc/html/transform/to_core_model.rb +10 -12
  86. data/lib/coradoc/html/version.rb +1 -1
  87. data/lib/coradoc/html.rb +43 -88
  88. metadata +37 -70
  89. data/lib/coradoc/html/base.rb +0 -157
  90. data/lib/coradoc/html/converters/admonition.rb +0 -180
  91. data/lib/coradoc/html/converters/attribute.rb +0 -68
  92. data/lib/coradoc/html/converters/attribute_reference.rb +0 -60
  93. data/lib/coradoc/html/converters/audio.rb +0 -165
  94. data/lib/coradoc/html/converters/base.rb +0 -615
  95. data/lib/coradoc/html/converters/bibliography.rb +0 -82
  96. data/lib/coradoc/html/converters/bibliography_entry.rb +0 -108
  97. data/lib/coradoc/html/converters/block_image.rb +0 -72
  98. data/lib/coradoc/html/converters/bold.rb +0 -34
  99. data/lib/coradoc/html/converters/break.rb +0 -32
  100. data/lib/coradoc/html/converters/comment_block.rb +0 -42
  101. data/lib/coradoc/html/converters/comment_line.rb +0 -54
  102. data/lib/coradoc/html/converters/cross_reference.rb +0 -59
  103. data/lib/coradoc/html/converters/document.rb +0 -108
  104. data/lib/coradoc/html/converters/example.rb +0 -114
  105. data/lib/coradoc/html/converters/highlight.rb +0 -34
  106. data/lib/coradoc/html/converters/include.rb +0 -68
  107. data/lib/coradoc/html/converters/inline_image.rb +0 -41
  108. data/lib/coradoc/html/converters/italic.rb +0 -34
  109. data/lib/coradoc/html/converters/line_break.rb +0 -31
  110. data/lib/coradoc/html/converters/link.rb +0 -46
  111. data/lib/coradoc/html/converters/list_item.rb +0 -75
  112. data/lib/coradoc/html/converters/listing.rb +0 -99
  113. data/lib/coradoc/html/converters/literal.rb +0 -102
  114. data/lib/coradoc/html/converters/monospace.rb +0 -34
  115. data/lib/coradoc/html/converters/open.rb +0 -78
  116. data/lib/coradoc/html/converters/ordered.rb +0 -53
  117. data/lib/coradoc/html/converters/paragraph.rb +0 -46
  118. data/lib/coradoc/html/converters/quote.rb +0 -113
  119. data/lib/coradoc/html/converters/reviewer_comment.rb +0 -74
  120. data/lib/coradoc/html/converters/reviewer_note.rb +0 -134
  121. data/lib/coradoc/html/converters/section.rb +0 -90
  122. data/lib/coradoc/html/converters/sidebar.rb +0 -113
  123. data/lib/coradoc/html/converters/source.rb +0 -137
  124. data/lib/coradoc/html/converters/source_code.rb +0 -16
  125. data/lib/coradoc/html/converters/span.rb +0 -61
  126. data/lib/coradoc/html/converters/strikethrough.rb +0 -34
  127. data/lib/coradoc/html/converters/subscript.rb +0 -34
  128. data/lib/coradoc/html/converters/superscript.rb +0 -34
  129. data/lib/coradoc/html/converters/table.rb +0 -85
  130. data/lib/coradoc/html/converters/table_cell.rb +0 -203
  131. data/lib/coradoc/html/converters/table_row.rb +0 -45
  132. data/lib/coradoc/html/converters/template_html_converter.rb +0 -105
  133. data/lib/coradoc/html/converters/term.rb +0 -58
  134. data/lib/coradoc/html/converters/text_element.rb +0 -44
  135. data/lib/coradoc/html/converters/underline.rb +0 -34
  136. data/lib/coradoc/html/converters/unordered.rb +0 -47
  137. data/lib/coradoc/html/converters/verse.rb +0 -105
  138. data/lib/coradoc/html/converters/video.rb +0 -179
  139. data/lib/coradoc/html/element_mapping.rb +0 -210
  140. data/lib/coradoc/html/entity.rb +0 -137
  141. data/lib/coradoc/html/input/converters/ignore.rb +0 -22
  142. data/lib/coradoc/html/input/converters/th.rb +0 -20
  143. data/lib/coradoc/html/theme/base.rb +0 -231
  144. data/lib/coradoc/html/theme/classic_renderer.rb +0 -390
  145. data/lib/coradoc/html/theme/modern/components/ui_components.rb +0 -344
  146. data/lib/coradoc/html/theme/modern/css_generator.rb +0 -311
  147. data/lib/coradoc/html/theme/modern/javascript_generator.rb +0 -314
  148. data/lib/coradoc/html/theme/modern/serializers/document_serializer.rb +0 -382
  149. data/lib/coradoc/html/theme/modern/tailwind_config_builder.rb +0 -164
  150. data/lib/coradoc/html/theme/modern/vue_template_generator.rb +0 -374
  151. data/lib/coradoc/html/theme/modern_renderer.rb +0 -250
  152. data/lib/coradoc/html/theme/registry.rb +0 -153
@@ -1,180 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coradoc
4
- module Html
5
- module Converters
6
- # Converter for CoreModel::AnnotationBlock to HTML admonition block
7
- class Admonition < Base
8
- # Convert CoreModel::AnnotationBlock to HTML admonition block
9
- def self.to_html(admonition, _options = {})
10
- return '' unless admonition
11
-
12
- # Get admonition type (NOTE, TIP, IMPORTANT, WARNING, CAUTION)
13
- type = admonition.annotation_type ? admonition.annotation_type.to_s.upcase : 'NOTE'
14
-
15
- # Build div attributes
16
- attrs = build_attributes(admonition, type)
17
-
18
- # Build title if present
19
- title_html = build_title(admonition)
20
-
21
- # Build admonition label
22
- label = build_label(type)
23
-
24
- # Process admonition content
25
- content = process_content(admonition.content)
26
-
27
- # Combine label, title, and content
28
- admonition_html = ''
29
- admonition_html += "#{label}\n"
30
- admonition_html += "#{title_html}\n" if title_html
31
- admonition_html += content
32
-
33
- %(<div#{attrs}>\n#{admonition_html}\n</div>)
34
- end
35
-
36
- # Convert HTML admonition div to CoreModel::AnnotationBlock
37
- def self.to_coradoc(element, _options = {})
38
- return nil unless element.name == 'div'
39
- return nil unless element['class']&.include?('admonition')
40
-
41
- # Extract admonition type from class
42
- type = extract_type(element)
43
- return nil unless type
44
-
45
- # Extract title if present
46
- title_elem = element.at_css('.admonition-title')
47
- title = title_elem&.text&.strip
48
-
49
- # Extract content - all children except label and title
50
- content_nodes = element.children.reject do |node|
51
- node == element.at_css('.admonition-label') ||
52
- node == title_elem ||
53
- (node.text? && node.text.strip.empty?)
54
- end
55
-
56
- content = extract_content(content_nodes)
57
-
58
- # Extract ID if present
59
- id = element['id']
60
-
61
- Coradoc::CoreModel::AnnotationBlock.new(
62
- annotation_type: type.downcase,
63
- content: content,
64
- title: title,
65
- id: id
66
- )
67
- end
68
-
69
- def self.build_attributes(admonition, type)
70
- attrs = [%( class="admonition admonition-#{type.downcase}")]
71
-
72
- # Add ID if present
73
- attrs << %( id="#{escape_attribute(admonition.id)}") if admonition.id
74
-
75
- attrs.join
76
- end
77
-
78
- def self.build_label(type)
79
- %(<div class="admonition-label">#{escape_html(type)}</div>)
80
- end
81
-
82
- def self.build_title(admonition)
83
- return nil unless admonition.title
84
-
85
- title_text = admonition.title.to_s
86
- return nil if title_text.empty?
87
-
88
- %(<div class="admonition-title">#{escape_html(title_text)}</div>)
89
- end
90
-
91
- def self.process_content(content)
92
- return '' if content.nil?
93
-
94
- if content.is_a?(Array)
95
- # Group consecutive lines into paragraphs, handling inline elements
96
- result = []
97
- current_para = []
98
-
99
- content.each do |item|
100
- case item
101
- when String
102
- current_para << item
103
- else
104
- # End current paragraph and start a new one
105
- if current_para.any?
106
- result << build_paragraph(current_para)
107
- current_para = []
108
- end
109
- result << convert_item(item)
110
- end
111
- end
112
-
113
- # Don't forget the last paragraph
114
- result << build_paragraph(current_para) if current_para.any?
115
-
116
- result.compact.join("\n")
117
- elsif content.is_a?(String)
118
- "<p>#{escape_html(content)}</p>"
119
- else
120
- convert_item(content)
121
- end
122
- end
123
-
124
- def self.build_paragraph(items)
125
- return nil if items.nil? || items.empty?
126
-
127
- # Convert all items to HTML and join them
128
- content_html = items.map do |item|
129
- case item
130
- when String
131
- escape_html(item)
132
- else
133
- convert_content_to_html(item)
134
- end
135
- end.join
136
-
137
- return nil if content_html.empty?
138
-
139
- "<p>#{content_html}</p>"
140
- end
141
-
142
- def self.convert_item(item)
143
- case item
144
- when String
145
- "<p>#{escape_html(item)}</p>"
146
- else
147
- convert_content_to_html(item)
148
- end
149
- end
150
-
151
- def self.extract_type(element)
152
- return nil unless element['class']
153
-
154
- # Extract type from class like "admonition-note", "admonition-warning", etc.
155
- classes = element['class'].split
156
- type_class = classes.find { |c| c.start_with?('admonition-') && c != 'admonition' }
157
- return nil unless type_class
158
-
159
- type_class.sub(/^admonition-/, '').upcase
160
- end
161
-
162
- def self.extract_content(nodes)
163
- # Extract and convert content nodes
164
- nodes.map do |node|
165
- if node.text? && !node.text.strip.empty?
166
- node.text.strip
167
- elsif node.element?
168
- case node.name
169
- when 'p'
170
- Paragraph.to_coradoc(node)
171
- else
172
- node.text.strip
173
- end
174
- end
175
- end.compact
176
- end
177
- end
178
- end
179
- end
180
- end
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'nokogiri'
4
-
5
- module Coradoc
6
- module Html
7
- module Converters
8
- # Converts document attributes to/from HTML
9
- #
10
- # Attributes are document-level directives (e.g., :author:, :toc:).
11
- # In HTML, we represent them as HTML comments to preserve the attribute
12
- # information without affecting the rendered output.
13
- #
14
- # Examples:
15
- # :author: John Doe => <!-- :author: John Doe -->
16
- # :toc: => <!-- :toc: -->
17
- class Attribute
18
- # Convert CoreModel::Block (attribute) to HTML comment
19
- def self.to_html(model, _options = {})
20
- key = model.metadata&.dig(:key).to_s
21
- key = escape_html(key)
22
-
23
- # Handle single value or array of values
24
- values = Array(model.metadata&.dig(:value)).compact
25
-
26
- if values.empty?
27
- # Attribute with no value (e.g., :toc:)
28
- "<!-- :#{key}: -->"
29
- else
30
- # Attribute with value(s)
31
- value_str = values.map { |v| escape_html(v.to_s) }.join(', ')
32
- "<!-- :#{key}: #{value_str} -->"
33
- end
34
- end
35
-
36
- # Convert HTML comment to CoreModel::Block (attribute)
37
- def self.to_coradoc(element, _options = {})
38
- return nil unless element.is_a?(Nokogiri::XML::Comment)
39
-
40
- content = element.content.strip
41
-
42
- # Match attribute pattern: :key: or :key: value
43
- return nil unless content.match?(/^:([^:]+):(.*)$/)
44
-
45
- match = content.match(/^:([^:]+):(.*)$/)
46
- key = match[1].strip
47
- value_part = match[2].strip
48
-
49
- # Parse value(s) - could be comma-separated
50
- values = if value_part.empty?
51
- []
52
- else
53
- value_part.split(',').map(&:strip)
54
- end
55
-
56
- Coradoc::CoreModel::Block.new(
57
- element_type: 'attribute',
58
- content: key,
59
- metadata: {
60
- key: key,
61
- value: values
62
- }
63
- )
64
- end
65
- end
66
- end
67
- end
68
- end
@@ -1,60 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coradoc
4
- module Html
5
- module Converters
6
- # Converts CoreModel::InlineElement with format_type "attribute_reference"
7
- #
8
- # Attribute references are inline placeholders that reference document attributes
9
- # (e.g., {author}, {docname}, {revnumber}).
10
- #
11
- # In HTML, we render them as-is with the curly braces to preserve the
12
- # reference syntax. Actual substitution of attribute values would happen
13
- # at a different processing layer if needed.
14
- #
15
- # Examples:
16
- # {author} => {author}
17
- # {docname} => {docname}
18
- class AttributeReference < Base
19
- # Convert CoreModel::InlineElement (attribute_reference) to HTML
20
- #
21
- # @param model [Coradoc::CoreModel::InlineElement] the attribute reference model
22
- # @param options [Hash] conversion options
23
- # @option options [Hash] :document_attributes Document attributes for substitution
24
- # @return [String] HTML representation of the attribute reference
25
- def self.to_html(model, options = {})
26
- name = model.target.to_s
27
-
28
- # Try to substitute with actual attribute value if document_attributes provided
29
- if options[:document_attributes]
30
- value = options[:document_attributes][name] || options[:document_attributes][name]
31
- return escape_html(value.to_s) if value
32
- end
33
-
34
- # Fallback: render as-is with curly braces
35
- escape_html("{#{name}}")
36
- end
37
-
38
- # Convert HTML text to CoreModel::InlineElement (attribute_reference)
39
- #
40
- # @param text [String] the HTML text
41
- # @param _options [Hash] conversion options (unused)
42
- # @return [Coradoc::CoreModel::InlineElement, nil] the attribute reference model or nil
43
- def self.to_coradoc(text, _options = {})
44
- return nil unless text.is_a?(String)
45
-
46
- # Match attribute reference pattern: {name}
47
- match = text.match(/^\{([^}]+)\}$/)
48
- return nil unless match
49
-
50
- name = match[1]
51
-
52
- Coradoc::CoreModel::InlineElement.new(
53
- format_type: 'attribute_reference',
54
- target: name
55
- )
56
- end
57
- end
58
- end
59
- end
60
- end
@@ -1,165 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coradoc
4
- module Html
5
- module Converters
6
- # Converter for audio elements
7
- class Audio < Base
8
- # Convert CoreModel::Block (audio) to HTML <audio>
9
- def self.to_html(audio, _options = {})
10
- return '' unless audio
11
-
12
- # Build audio attributes
13
- attrs = build_attributes(audio)
14
-
15
- # Get audio source from metadata or content
16
- src = audio.metadata&.dig(:src) || audio.content
17
-
18
- # Build source element
19
- source_tag = %(<source src="#{escape_attribute(src)}"#{build_type_attr(src)}>)
20
-
21
- # Build optional caption/title
22
- caption = audio.title
23
-
24
- # Determine if we need a wrapper (for block audio with caption)
25
- if caption
26
- <<~HTML.strip
27
- <figure#{build_figure_attrs(audio)}>
28
- <audio#{attrs}>
29
- #{source_tag}
30
- Your browser does not support the audio tag.
31
- </audio>
32
- <figcaption>#{escape_html(caption)}</figcaption>
33
- </figure>
34
- HTML
35
- else
36
- <<~HTML.strip
37
- <audio#{attrs}>
38
- #{source_tag}
39
- Your browser does not support the audio tag.
40
- </audio>
41
- HTML
42
- end
43
- end
44
-
45
- # Convert HTML <audio> to CoreModel::Block (audio)
46
- def self.to_coradoc(element, _options = {})
47
- # Handle both <audio> and <figure><audio> structures
48
- audio_elem = if element.name == 'figure'
49
- element.at_css('audio')
50
- elsif element.name == 'audio'
51
- element
52
- else
53
- return nil
54
- end
55
-
56
- return nil unless audio_elem
57
-
58
- # Extract source from <source> tag or src attribute
59
- src = extract_audio_src(audio_elem)
60
- return nil unless src
61
-
62
- # Extract caption if in figure
63
- caption = if element.name == 'figure'
64
- figcaption = element.at_css('figcaption')
65
- figcaption&.text&.strip
66
- end
67
-
68
- # Extract ID if present
69
- id = audio_elem['id'] || element['id']
70
-
71
- # Extract audio attributes
72
- metadata = extract_audio_metadata(audio_elem)
73
- metadata[:src] = src
74
-
75
- Coradoc::CoreModel::Block.new(
76
- element_type: 'audio',
77
- content: src,
78
- title: caption,
79
- id: id,
80
- metadata: metadata
81
- )
82
- end
83
-
84
- def self.build_attributes(audio)
85
- attrs = []
86
-
87
- # Extract options from metadata
88
- options = audio.metadata&.dig(:options) || []
89
-
90
- # Add controls by default (unless nocontrols option is set)
91
- has_controls = !options.include?('nocontrols')
92
- attrs << ' controls' if has_controls
93
-
94
- # Add autoplay if specified in options
95
- attrs << ' autoplay' if options.include?('autoplay')
96
-
97
- # Add loop if specified in options
98
- attrs << ' loop' if options.include?('loop')
99
-
100
- # Add muted if specified in options
101
- attrs << ' muted' if options.include?('muted')
102
-
103
- # Add ID if present
104
- attrs << %( id="#{escape_attribute(audio.id)}") if audio.id
105
-
106
- attrs.join
107
- end
108
-
109
- def self.build_type_attr(src)
110
- # Determine audio MIME type from extension
111
- ext = File.extname(src).downcase
112
- type = case ext
113
- when '.mp3'
114
- 'audio/mpeg'
115
- when '.ogg', '.oga'
116
- 'audio/ogg'
117
- when '.wav'
118
- 'audio/wav'
119
- when '.m4a'
120
- 'audio/mp4'
121
- when '.aac'
122
- 'audio/aac'
123
- when '.flac'
124
- 'audio/flac'
125
- end
126
-
127
- type ? %( type="#{type}") : ''
128
- end
129
-
130
- def self.build_figure_attrs(audio)
131
- attrs = []
132
-
133
- # Add ID to figure if present
134
- attrs << %( id="#{escape_attribute(audio.id)}-figure") if audio.id
135
-
136
- attrs.join
137
- end
138
-
139
- def self.extract_audio_src(element)
140
- # Try to get src from <source> tag first
141
- source = element.at_css('source')
142
- return source['src'] if source && source['src']
143
-
144
- # Fall back to src attribute on <audio>
145
- element['src']
146
- end
147
-
148
- def self.extract_audio_metadata(element)
149
- metadata = {}
150
- options = []
151
-
152
- # Extract boolean attributes
153
- options << 'controls' if element.has_attribute?('controls')
154
- options << 'autoplay' if element.has_attribute?('autoplay')
155
- options << 'loop' if element.has_attribute?('loop')
156
- options << 'muted' if element.has_attribute?('muted')
157
-
158
- metadata[:options] = options unless options.empty?
159
-
160
- metadata
161
- end
162
- end
163
- end
164
- end
165
- end