coradoc-html 1.1.7

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 (124) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/lib/coradoc/html/base.rb +157 -0
  4. data/lib/coradoc/html/config.rb +467 -0
  5. data/lib/coradoc/html/converter_base.rb +177 -0
  6. data/lib/coradoc/html/converters/admonition.rb +180 -0
  7. data/lib/coradoc/html/converters/attribute.rb +68 -0
  8. data/lib/coradoc/html/converters/attribute_reference.rb +60 -0
  9. data/lib/coradoc/html/converters/audio.rb +165 -0
  10. data/lib/coradoc/html/converters/base.rb +615 -0
  11. data/lib/coradoc/html/converters/bibliography.rb +82 -0
  12. data/lib/coradoc/html/converters/bibliography_entry.rb +108 -0
  13. data/lib/coradoc/html/converters/block_image.rb +72 -0
  14. data/lib/coradoc/html/converters/bold.rb +34 -0
  15. data/lib/coradoc/html/converters/break.rb +32 -0
  16. data/lib/coradoc/html/converters/comment_block.rb +42 -0
  17. data/lib/coradoc/html/converters/comment_line.rb +54 -0
  18. data/lib/coradoc/html/converters/cross_reference.rb +59 -0
  19. data/lib/coradoc/html/converters/document.rb +108 -0
  20. data/lib/coradoc/html/converters/example.rb +114 -0
  21. data/lib/coradoc/html/converters/highlight.rb +34 -0
  22. data/lib/coradoc/html/converters/include.rb +68 -0
  23. data/lib/coradoc/html/converters/inline_image.rb +41 -0
  24. data/lib/coradoc/html/converters/italic.rb +34 -0
  25. data/lib/coradoc/html/converters/line_break.rb +31 -0
  26. data/lib/coradoc/html/converters/link.rb +46 -0
  27. data/lib/coradoc/html/converters/list_item.rb +75 -0
  28. data/lib/coradoc/html/converters/listing.rb +99 -0
  29. data/lib/coradoc/html/converters/literal.rb +102 -0
  30. data/lib/coradoc/html/converters/monospace.rb +34 -0
  31. data/lib/coradoc/html/converters/open.rb +78 -0
  32. data/lib/coradoc/html/converters/ordered.rb +53 -0
  33. data/lib/coradoc/html/converters/paragraph.rb +46 -0
  34. data/lib/coradoc/html/converters/quote.rb +113 -0
  35. data/lib/coradoc/html/converters/reviewer_comment.rb +74 -0
  36. data/lib/coradoc/html/converters/reviewer_note.rb +134 -0
  37. data/lib/coradoc/html/converters/section.rb +90 -0
  38. data/lib/coradoc/html/converters/sidebar.rb +113 -0
  39. data/lib/coradoc/html/converters/source.rb +137 -0
  40. data/lib/coradoc/html/converters/source_code.rb +16 -0
  41. data/lib/coradoc/html/converters/span.rb +61 -0
  42. data/lib/coradoc/html/converters/strikethrough.rb +34 -0
  43. data/lib/coradoc/html/converters/subscript.rb +34 -0
  44. data/lib/coradoc/html/converters/superscript.rb +34 -0
  45. data/lib/coradoc/html/converters/table.rb +85 -0
  46. data/lib/coradoc/html/converters/table_cell.rb +203 -0
  47. data/lib/coradoc/html/converters/table_row.rb +45 -0
  48. data/lib/coradoc/html/converters/template_html_converter.rb +105 -0
  49. data/lib/coradoc/html/converters/term.rb +58 -0
  50. data/lib/coradoc/html/converters/text_element.rb +44 -0
  51. data/lib/coradoc/html/converters/underline.rb +34 -0
  52. data/lib/coradoc/html/converters/unordered.rb +47 -0
  53. data/lib/coradoc/html/converters/verse.rb +105 -0
  54. data/lib/coradoc/html/converters/video.rb +179 -0
  55. data/lib/coradoc/html/element_mapping.rb +210 -0
  56. data/lib/coradoc/html/entity.rb +137 -0
  57. data/lib/coradoc/html/input/cleaner.rb +163 -0
  58. data/lib/coradoc/html/input/config.rb +79 -0
  59. data/lib/coradoc/html/input/converters/a.rb +90 -0
  60. data/lib/coradoc/html/input/converters/aside.rb +23 -0
  61. data/lib/coradoc/html/input/converters/audio.rb +50 -0
  62. data/lib/coradoc/html/input/converters/base.rb +116 -0
  63. data/lib/coradoc/html/input/converters/blockquote.rb +25 -0
  64. data/lib/coradoc/html/input/converters/br.rb +19 -0
  65. data/lib/coradoc/html/input/converters/bypass.rb +83 -0
  66. data/lib/coradoc/html/input/converters/code.rb +25 -0
  67. data/lib/coradoc/html/input/converters/div.rb +25 -0
  68. data/lib/coradoc/html/input/converters/dl.rb +106 -0
  69. data/lib/coradoc/html/input/converters/drop.rb +28 -0
  70. data/lib/coradoc/html/input/converters/em.rb +23 -0
  71. data/lib/coradoc/html/input/converters/figure.rb +58 -0
  72. data/lib/coradoc/html/input/converters/h.rb +76 -0
  73. data/lib/coradoc/html/input/converters/head.rb +30 -0
  74. data/lib/coradoc/html/input/converters/hr.rb +20 -0
  75. data/lib/coradoc/html/input/converters/ignore.rb +22 -0
  76. data/lib/coradoc/html/input/converters/img.rb +110 -0
  77. data/lib/coradoc/html/input/converters/li.rb +35 -0
  78. data/lib/coradoc/html/input/converters/mark.rb +21 -0
  79. data/lib/coradoc/html/input/converters/markup.rb +107 -0
  80. data/lib/coradoc/html/input/converters/math.rb +46 -0
  81. data/lib/coradoc/html/input/converters/ol.rb +46 -0
  82. data/lib/coradoc/html/input/converters/p.rb +81 -0
  83. data/lib/coradoc/html/input/converters/pass_through.rb +19 -0
  84. data/lib/coradoc/html/input/converters/pre.rb +59 -0
  85. data/lib/coradoc/html/input/converters/q.rb +24 -0
  86. data/lib/coradoc/html/input/converters/strong.rb +22 -0
  87. data/lib/coradoc/html/input/converters/sub.rb +40 -0
  88. data/lib/coradoc/html/input/converters/sup.rb +40 -0
  89. data/lib/coradoc/html/input/converters/table.rb +64 -0
  90. data/lib/coradoc/html/input/converters/td.rb +70 -0
  91. data/lib/coradoc/html/input/converters/text.rb +67 -0
  92. data/lib/coradoc/html/input/converters/th.rb +20 -0
  93. data/lib/coradoc/html/input/converters/tr.rb +28 -0
  94. data/lib/coradoc/html/input/converters/video.rb +53 -0
  95. data/lib/coradoc/html/input/converters.rb +122 -0
  96. data/lib/coradoc/html/input/errors.rb +22 -0
  97. data/lib/coradoc/html/input/html_converter.rb +170 -0
  98. data/lib/coradoc/html/input/plugin.rb +169 -0
  99. data/lib/coradoc/html/input/plugins/plateau.rb +229 -0
  100. data/lib/coradoc/html/input/postprocessor.rb +31 -0
  101. data/lib/coradoc/html/input.rb +68 -0
  102. data/lib/coradoc/html/output.rb +95 -0
  103. data/lib/coradoc/html/renderer.rb +409 -0
  104. data/lib/coradoc/html/spa.rb +309 -0
  105. data/lib/coradoc/html/static.rb +293 -0
  106. data/lib/coradoc/html/template_config.rb +151 -0
  107. data/lib/coradoc/html/template_helpers.rb +58 -0
  108. data/lib/coradoc/html/template_locator.rb +114 -0
  109. data/lib/coradoc/html/theme/base.rb +231 -0
  110. data/lib/coradoc/html/theme/classic_renderer.rb +390 -0
  111. data/lib/coradoc/html/theme/modern/components/ui_components.rb +344 -0
  112. data/lib/coradoc/html/theme/modern/css_generator.rb +311 -0
  113. data/lib/coradoc/html/theme/modern/javascript_generator.rb +314 -0
  114. data/lib/coradoc/html/theme/modern/serializers/document_serializer.rb +382 -0
  115. data/lib/coradoc/html/theme/modern/tailwind_config_builder.rb +164 -0
  116. data/lib/coradoc/html/theme/modern/vue_template_generator.rb +374 -0
  117. data/lib/coradoc/html/theme/modern_renderer.rb +250 -0
  118. data/lib/coradoc/html/theme/registry.rb +153 -0
  119. data/lib/coradoc/html/theme.rb +13 -0
  120. data/lib/coradoc/html/transform/from_core_model.rb +32 -0
  121. data/lib/coradoc/html/transform/to_core_model.rb +39 -0
  122. data/lib/coradoc/html/version.rb +7 -0
  123. data/lib/coradoc/html.rb +255 -0
  124. metadata +264 -0
@@ -0,0 +1,390 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'coradoc/html'
4
+ require 'coradoc/html/config'
5
+ require 'coradoc/html/converters/base'
6
+
7
+ module Coradoc
8
+ module Html
9
+ module Theme
10
+ # Classic theme renderer
11
+ #
12
+ # This renderer wraps the existing Coradoc HTML generation system.
13
+ # It maintains backward compatibility with the classic theme while
14
+ # following the new theme system architecture.
15
+ #
16
+ # The classic theme is the default theme and provides the same output
17
+ # as the original Coradoc HTML converter.
18
+ class ClassicRenderer < Base
19
+ # Register this theme automatically
20
+ Registry.auto_register(self)
21
+
22
+ # Check if template rendering is enabled
23
+ #
24
+ # @return [Boolean]
25
+ def use_templates?
26
+ @options[:use_templates] == true
27
+ end
28
+
29
+ # Get the template renderer when templates are enabled
30
+ #
31
+ # @return [Coradoc::Html::Renderer, nil]
32
+ def template_renderer
33
+ return nil unless use_templates?
34
+
35
+ @template_renderer ||= begin
36
+ dirs = @options[:template_dirs] || global_template_dirs
37
+ Renderer.new(template_dirs: dirs)
38
+ end
39
+ end
40
+
41
+ # Supported features for classic theme
42
+ #
43
+ # @return [Array<Symbol>] Supported features
44
+ def supported_features
45
+ features = %i[
46
+ dark_mode
47
+ theme_toggle
48
+ syntax_highlighting
49
+ table_of_contents
50
+ section_numbering
51
+ ]
52
+ features << :template_rendering if use_templates?
53
+ features
54
+ end
55
+
56
+ # Render document to HTML
57
+ #
58
+ # Generates HTML body content from the document.
59
+ # Uses the HTML Converters directly to avoid circular dependency.
60
+ # When templates are enabled, uses the template renderer instead.
61
+ #
62
+ # @return [String] HTML string
63
+ def render
64
+ return template_renderer.render(@document) if use_templates? && template_renderer
65
+
66
+ # Use HTML converters directly to convert document model to HTML
67
+ # This avoids circular dependency with Coradoc::Output::Html
68
+ Coradoc::Html::Converters::Document.to_html(@document, @options)
69
+ end
70
+
71
+ # Render complete HTML5 document
72
+ #
73
+ # Builds the complete HTML5 document with head, body, and all assets.
74
+ # @return [String] Complete HTML5 document
75
+ def render_html5
76
+ html_body = render
77
+
78
+ lang = @options[:lang] || 'en'
79
+ body_classes = build_body_classes
80
+
81
+ # Apply section numbering if enabled
82
+ final_body = if @options[:sectnums]
83
+ apply_section_numbering(html_body)
84
+ else
85
+ html_body
86
+ end
87
+
88
+ # Build TOC if enabled
89
+ toc_html = build_toc
90
+
91
+ # Insert TOC based on placement
92
+ final_body = insert_toc(final_body, toc_html)
93
+
94
+ # Add theme toggle button if enabled
95
+ theme_button = build_theme_toggle_button
96
+
97
+ <<~HTML
98
+ <!DOCTYPE html>
99
+ <html lang="#{lang}">
100
+ <head>
101
+ #{build_head_content}
102
+ </head>
103
+ <body#{body_classes}>
104
+ #{final_body}
105
+ #{theme_button}
106
+ </body>
107
+ </html>
108
+ HTML
109
+ end
110
+
111
+ protected
112
+
113
+ # Get global template directories from configuration
114
+ def global_template_dirs
115
+ Coradoc::Html.configuration.template_dirs.map(&:to_s)
116
+ end
117
+
118
+ # Build body classes
119
+ #
120
+ # @return [String] Body class attribute
121
+ def build_body_classes
122
+ classes = []
123
+
124
+ # Add TOC placement class
125
+ if @options[:toc]
126
+ placement = @options[:toc_placement] || :auto
127
+ classes << "toc-#{placement}" unless placement == :auto
128
+ end
129
+
130
+ classes.empty? ? '' : %( class="#{classes.join(' ')}")
131
+ end
132
+
133
+ # Build CSS tags
134
+ #
135
+ # @return [String] CSS link or style tags
136
+ def build_css_tags
137
+ Coradoc::Html::Config.css_tags(@options).split("\n")
138
+ .map { |line| " #{line}" }.join("\n")
139
+ end
140
+
141
+ # Build script tags
142
+ #
143
+ # @return [String] Script tags
144
+ def build_script_tags
145
+ js = Coradoc::Html::Config.js_tags(@options)
146
+ return '' if js.empty?
147
+
148
+ js.split("\n").map { |line| " #{line}" }.join("\n")
149
+ end
150
+
151
+ # Build syntax highlighter tags
152
+ #
153
+ # @return [String] Syntax highlighter tags HTML
154
+ def build_head_content
155
+ parts = []
156
+ parts << build_meta_tags
157
+ parts << build_title_tag
158
+ parts << build_css_tags
159
+ parts << build_script_tags
160
+ parts << build_syntax_highlighter_tags
161
+ parts.compact.reject(&:empty?).join("\n")
162
+ end
163
+
164
+ # Build syntax highlighter tags
165
+ #
166
+ # @return [String] Syntax highlighter tags HTML
167
+ def build_syntax_highlighter_tags
168
+ tags = Coradoc::Html::Config.syntax_highlighter_tags(@options)
169
+ return '' if tags.empty?
170
+
171
+ tags.split("\n").map { |line| " #{line}" }.join("\n")
172
+ end
173
+
174
+ # Insert TOC into body HTML based on placement
175
+ #
176
+ # @param body_html [String] Body HTML content
177
+ # @param toc_html [String] TOC HTML
178
+ # @return [String] Combined HTML
179
+ def insert_toc(body_html, toc_html)
180
+ return body_html if toc_html.empty?
181
+
182
+ placement = @options[:toc_placement] || :auto
183
+
184
+ case placement
185
+ when :left, :right
186
+ # For sidebar placements, TOC is positioned via CSS
187
+ "#{toc_html}\n#{body_html}"
188
+ when :auto, :preamble
189
+ # Insert after header, before main content
190
+ if body_html =~ %r{(</h1>)}
191
+ body_html.sub(::Regexp.last_match(1), "#{::Regexp.last_match(1)}\n#{toc_html}")
192
+ else
193
+ "#{toc_html}\n#{body_html}"
194
+ end
195
+ else
196
+ # Default: prepend to body
197
+ "#{toc_html}\n#{body_html}"
198
+ end
199
+ end
200
+
201
+ # Build theme toggle button HTML
202
+ #
203
+ # @return [String] Theme toggle button HTML or empty string
204
+ def build_theme_toggle_button
205
+ return '' unless @options[:theme_toggle]
206
+
207
+ <<~HTML
208
+ <button id="theme-toggle" aria-label="Toggle dark mode" title="Toggle dark/light mode">
209
+ <span class="theme-toggle-icon">☀️</span>
210
+ </button>
211
+ HTML
212
+ end
213
+
214
+ # Build table of contents
215
+ #
216
+ # @return [String] TOC HTML or empty string
217
+ def build_toc
218
+ return '' unless @options[:toc]
219
+
220
+ toc_placement = @options[:toc_placement] || :auto
221
+ toc_class = toc_placement == :auto ? 'toc' : "toc toc-#{toc_placement}"
222
+
223
+ sections = extract_sections(@document)
224
+ return '' if sections.empty?
225
+
226
+ toc_title = @options[:toc_title] || 'Table of Contents'
227
+ toc_levels = @options[:toclevels] || 2
228
+
229
+ build_toc_html(sections, toc_levels, toc_class, toc_title)
230
+ end
231
+
232
+ # Extract sections from document
233
+ #
234
+ # @param doc [Coradoc::CoreModel::StructuralElement] Document to extract sections from
235
+ # @return [Array] Array of section objects
236
+ def extract_sections(doc)
237
+ sections = []
238
+ return sections unless doc.is_a?(Coradoc::CoreModel::StructuralElement)
239
+
240
+ collect_sections(doc.children, sections, 1)
241
+ sections
242
+ end
243
+
244
+ # Recursively collect sections
245
+ #
246
+ # @param items [Array] Items to process
247
+ # @param sections [Array] Accumulated sections
248
+ # @param level [Integer] Current section level
249
+ def collect_sections(items, sections, level)
250
+ return unless items
251
+
252
+ items.each do |item|
253
+ next unless item.is_a?(Coradoc::CoreModel::StructuralElement)
254
+
255
+ section_data = {
256
+ title: extract_section_title(item),
257
+ id: extract_section_id(item),
258
+ level: level,
259
+ children: []
260
+ }
261
+
262
+ # Recursively collect subsections
263
+ collect_sections(item.children, section_data[:children], level + 1)
264
+
265
+ sections << section_data
266
+ end
267
+ end
268
+
269
+ # Extract section title
270
+ #
271
+ # @param section [Coradoc::CoreModel::StructuralElement] Section to extract title from
272
+ # @return [String] Section title
273
+ def extract_section_title(section)
274
+ title = section.title
275
+ if title
276
+ if title.is_a?(Coradoc::CoreModel::Base) && title.text
277
+ title.text
278
+ elsif title.is_a?(Coradoc::CoreModel::Base) && title.content
279
+ Coradoc::Html::Converters::Base.get_text_content(title)
280
+ elsif title.is_a?(Array)
281
+ title.map { |t| t.is_a?(Coradoc::CoreModel::Base) && t.text ? t.text : t.to_s }.join
282
+ else
283
+ title.to_s
284
+ end
285
+ else
286
+ 'Untitled Section'
287
+ end
288
+ end
289
+
290
+ # Extract section ID
291
+ #
292
+ # @param section [Coradoc::CoreModel::StructuralElement] Section to extract ID from
293
+ # @return [String] Section ID
294
+ def extract_section_id(section)
295
+ if section.id
296
+ section.id
297
+ else
298
+ # Generate ID from title
299
+ title = extract_section_title(section)
300
+ "_#{title.to_s.downcase.gsub(/[^a-z0-9]+/, '_').gsub(/^_+|_+$/, '')}"
301
+ end
302
+ end
303
+
304
+ # Build TOC HTML
305
+ #
306
+ # @param sections [Array] Section data
307
+ # @param max_level [Integer] Maximum level to include
308
+ # @param toc_class [String] CSS class for TOC
309
+ # @param toc_title [String] TOC title
310
+ # @return [String] TOC HTML
311
+ def build_toc_html(sections, max_level, toc_class, toc_title)
312
+ return '' if sections.empty?
313
+
314
+ html = []
315
+ html << %(<div id="toc" class="#{toc_class}">)
316
+ html << %( <div class="toc-title">#{escape_html(toc_title)}</div>)
317
+ html << build_toc_list(sections, 1, max_level)
318
+ html << %(</div>)
319
+ html.join("\n")
320
+ end
321
+
322
+ # Build TOC list recursively
323
+ #
324
+ # @param sections [Array] Section data
325
+ # @param current_level [Integer] Current level
326
+ # @param max_level [Integer] Maximum level
327
+ # @return [String] List HTML
328
+ def build_toc_list(sections, current_level, max_level)
329
+ return '' if sections.empty? || current_level > max_level
330
+
331
+ html = []
332
+ html << %( <ul class="sectlevel#{current_level}">)
333
+
334
+ sections.each do |section|
335
+ html << %( <li>)
336
+ html << %( <a href="##{section[:id]}">#{escape_html(section[:title])}</a>)
337
+
338
+ # Add nested list for children
339
+ if section[:children] && !section[:children].empty? && current_level < max_level
340
+ nested_html = build_toc_list(section[:children], current_level + 1, max_level)
341
+ html << nested_html if nested_html && !nested_html.empty?
342
+ end
343
+
344
+ html << %( </li>)
345
+ end
346
+
347
+ html << %( </ul>)
348
+ html.join("\n")
349
+ end
350
+
351
+ # Apply section numbering to HTML
352
+ #
353
+ # @param html [String] HTML content
354
+ # @return [String] HTML with section numbers
355
+ def apply_section_numbering(html)
356
+ require 'nokogiri'
357
+
358
+ max_level = @options[:sectnumlevels] || 3
359
+ doc = Nokogiri::HTML::DocumentFragment.parse(html)
360
+
361
+ # Track section numbers at each level
362
+ counters = Array.new(max_level + 1, 0)
363
+
364
+ # Find all headings (h2-h6, h1 is document title)
365
+ (2..6).each do |level|
366
+ doc.css("h#{level}").each do |heading|
367
+ section_level = level - 1 # h2 = level 1, h3 = level 2, etc.
368
+ next if section_level > max_level
369
+
370
+ # Increment counter for this level
371
+ counters[section_level] += 1
372
+
373
+ # Reset deeper level counters
374
+ ((section_level + 1)..max_level).each { |i| counters[i] = 0 }
375
+
376
+ # Build section number (e.g., "1.2.3")
377
+ section_number = counters[1..section_level].join('.')
378
+
379
+ # Add section number to heading
380
+ number_span = %(<span class="sectnum">#{section_number}. </span>)
381
+ heading.inner_html = number_span + heading.inner_html
382
+ end
383
+ end
384
+
385
+ doc.to_html
386
+ end
387
+ end
388
+ end
389
+ end
390
+ end