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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/lib/coradoc/html/base.rb +157 -0
- data/lib/coradoc/html/config.rb +467 -0
- data/lib/coradoc/html/converter_base.rb +177 -0
- data/lib/coradoc/html/converters/admonition.rb +180 -0
- data/lib/coradoc/html/converters/attribute.rb +68 -0
- data/lib/coradoc/html/converters/attribute_reference.rb +60 -0
- data/lib/coradoc/html/converters/audio.rb +165 -0
- data/lib/coradoc/html/converters/base.rb +615 -0
- data/lib/coradoc/html/converters/bibliography.rb +82 -0
- data/lib/coradoc/html/converters/bibliography_entry.rb +108 -0
- data/lib/coradoc/html/converters/block_image.rb +72 -0
- data/lib/coradoc/html/converters/bold.rb +34 -0
- data/lib/coradoc/html/converters/break.rb +32 -0
- data/lib/coradoc/html/converters/comment_block.rb +42 -0
- data/lib/coradoc/html/converters/comment_line.rb +54 -0
- data/lib/coradoc/html/converters/cross_reference.rb +59 -0
- data/lib/coradoc/html/converters/document.rb +108 -0
- data/lib/coradoc/html/converters/example.rb +114 -0
- data/lib/coradoc/html/converters/highlight.rb +34 -0
- data/lib/coradoc/html/converters/include.rb +68 -0
- data/lib/coradoc/html/converters/inline_image.rb +41 -0
- data/lib/coradoc/html/converters/italic.rb +34 -0
- data/lib/coradoc/html/converters/line_break.rb +31 -0
- data/lib/coradoc/html/converters/link.rb +46 -0
- data/lib/coradoc/html/converters/list_item.rb +75 -0
- data/lib/coradoc/html/converters/listing.rb +99 -0
- data/lib/coradoc/html/converters/literal.rb +102 -0
- data/lib/coradoc/html/converters/monospace.rb +34 -0
- data/lib/coradoc/html/converters/open.rb +78 -0
- data/lib/coradoc/html/converters/ordered.rb +53 -0
- data/lib/coradoc/html/converters/paragraph.rb +46 -0
- data/lib/coradoc/html/converters/quote.rb +113 -0
- data/lib/coradoc/html/converters/reviewer_comment.rb +74 -0
- data/lib/coradoc/html/converters/reviewer_note.rb +134 -0
- data/lib/coradoc/html/converters/section.rb +90 -0
- data/lib/coradoc/html/converters/sidebar.rb +113 -0
- data/lib/coradoc/html/converters/source.rb +137 -0
- data/lib/coradoc/html/converters/source_code.rb +16 -0
- data/lib/coradoc/html/converters/span.rb +61 -0
- data/lib/coradoc/html/converters/strikethrough.rb +34 -0
- data/lib/coradoc/html/converters/subscript.rb +34 -0
- data/lib/coradoc/html/converters/superscript.rb +34 -0
- data/lib/coradoc/html/converters/table.rb +85 -0
- data/lib/coradoc/html/converters/table_cell.rb +203 -0
- data/lib/coradoc/html/converters/table_row.rb +45 -0
- data/lib/coradoc/html/converters/template_html_converter.rb +105 -0
- data/lib/coradoc/html/converters/term.rb +58 -0
- data/lib/coradoc/html/converters/text_element.rb +44 -0
- data/lib/coradoc/html/converters/underline.rb +34 -0
- data/lib/coradoc/html/converters/unordered.rb +47 -0
- data/lib/coradoc/html/converters/verse.rb +105 -0
- data/lib/coradoc/html/converters/video.rb +179 -0
- data/lib/coradoc/html/element_mapping.rb +210 -0
- data/lib/coradoc/html/entity.rb +137 -0
- data/lib/coradoc/html/input/cleaner.rb +163 -0
- data/lib/coradoc/html/input/config.rb +79 -0
- data/lib/coradoc/html/input/converters/a.rb +90 -0
- data/lib/coradoc/html/input/converters/aside.rb +23 -0
- data/lib/coradoc/html/input/converters/audio.rb +50 -0
- data/lib/coradoc/html/input/converters/base.rb +116 -0
- data/lib/coradoc/html/input/converters/blockquote.rb +25 -0
- data/lib/coradoc/html/input/converters/br.rb +19 -0
- data/lib/coradoc/html/input/converters/bypass.rb +83 -0
- data/lib/coradoc/html/input/converters/code.rb +25 -0
- data/lib/coradoc/html/input/converters/div.rb +25 -0
- data/lib/coradoc/html/input/converters/dl.rb +106 -0
- data/lib/coradoc/html/input/converters/drop.rb +28 -0
- data/lib/coradoc/html/input/converters/em.rb +23 -0
- data/lib/coradoc/html/input/converters/figure.rb +58 -0
- data/lib/coradoc/html/input/converters/h.rb +76 -0
- data/lib/coradoc/html/input/converters/head.rb +30 -0
- data/lib/coradoc/html/input/converters/hr.rb +20 -0
- data/lib/coradoc/html/input/converters/ignore.rb +22 -0
- data/lib/coradoc/html/input/converters/img.rb +110 -0
- data/lib/coradoc/html/input/converters/li.rb +35 -0
- data/lib/coradoc/html/input/converters/mark.rb +21 -0
- data/lib/coradoc/html/input/converters/markup.rb +107 -0
- data/lib/coradoc/html/input/converters/math.rb +46 -0
- data/lib/coradoc/html/input/converters/ol.rb +46 -0
- data/lib/coradoc/html/input/converters/p.rb +81 -0
- data/lib/coradoc/html/input/converters/pass_through.rb +19 -0
- data/lib/coradoc/html/input/converters/pre.rb +59 -0
- data/lib/coradoc/html/input/converters/q.rb +24 -0
- data/lib/coradoc/html/input/converters/strong.rb +22 -0
- data/lib/coradoc/html/input/converters/sub.rb +40 -0
- data/lib/coradoc/html/input/converters/sup.rb +40 -0
- data/lib/coradoc/html/input/converters/table.rb +64 -0
- data/lib/coradoc/html/input/converters/td.rb +70 -0
- data/lib/coradoc/html/input/converters/text.rb +67 -0
- data/lib/coradoc/html/input/converters/th.rb +20 -0
- data/lib/coradoc/html/input/converters/tr.rb +28 -0
- data/lib/coradoc/html/input/converters/video.rb +53 -0
- data/lib/coradoc/html/input/converters.rb +122 -0
- data/lib/coradoc/html/input/errors.rb +22 -0
- data/lib/coradoc/html/input/html_converter.rb +170 -0
- data/lib/coradoc/html/input/plugin.rb +169 -0
- data/lib/coradoc/html/input/plugins/plateau.rb +229 -0
- data/lib/coradoc/html/input/postprocessor.rb +31 -0
- data/lib/coradoc/html/input.rb +68 -0
- data/lib/coradoc/html/output.rb +95 -0
- data/lib/coradoc/html/renderer.rb +409 -0
- data/lib/coradoc/html/spa.rb +309 -0
- data/lib/coradoc/html/static.rb +293 -0
- data/lib/coradoc/html/template_config.rb +151 -0
- data/lib/coradoc/html/template_helpers.rb +58 -0
- data/lib/coradoc/html/template_locator.rb +114 -0
- data/lib/coradoc/html/theme/base.rb +231 -0
- data/lib/coradoc/html/theme/classic_renderer.rb +390 -0
- data/lib/coradoc/html/theme/modern/components/ui_components.rb +344 -0
- data/lib/coradoc/html/theme/modern/css_generator.rb +311 -0
- data/lib/coradoc/html/theme/modern/javascript_generator.rb +314 -0
- data/lib/coradoc/html/theme/modern/serializers/document_serializer.rb +382 -0
- data/lib/coradoc/html/theme/modern/tailwind_config_builder.rb +164 -0
- data/lib/coradoc/html/theme/modern/vue_template_generator.rb +374 -0
- data/lib/coradoc/html/theme/modern_renderer.rb +250 -0
- data/lib/coradoc/html/theme/registry.rb +153 -0
- data/lib/coradoc/html/theme.rb +13 -0
- data/lib/coradoc/html/transform/from_core_model.rb +32 -0
- data/lib/coradoc/html/transform/to_core_model.rb +39 -0
- data/lib/coradoc/html/version.rb +7 -0
- data/lib/coradoc/html.rb +255 -0
- 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
|