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.
- checksums.yaml +4 -4
- data/lib/coradoc/html/config.rb +36 -12
- data/lib/coradoc/html/converter_base.rb +26 -68
- data/lib/coradoc/html/drop/annotation_drop.rb +31 -0
- data/lib/coradoc/html/drop/base.rb +77 -0
- data/lib/coradoc/html/drop/bibliography_drop.rb +15 -0
- data/lib/coradoc/html/drop/bibliography_entry_drop.rb +24 -0
- data/lib/coradoc/html/drop/block_drop.rb +69 -0
- data/lib/coradoc/html/drop/definition_item_drop.rb +36 -0
- data/lib/coradoc/html/drop/definition_list_drop.rb +15 -0
- data/lib/coradoc/html/drop/document_drop.rb +52 -0
- data/lib/coradoc/html/drop/drop_factory.rb +73 -0
- data/lib/coradoc/html/drop/footnote_drop.rb +24 -0
- data/lib/coradoc/html/drop/image_drop.rb +35 -0
- data/lib/coradoc/html/drop/inline_element_drop.rb +64 -0
- data/lib/coradoc/html/drop/list_block_drop.rb +23 -0
- data/lib/coradoc/html/drop/list_item_drop.rb +20 -0
- data/lib/coradoc/html/drop/table_cell_drop.rb +35 -0
- data/lib/coradoc/html/drop/table_drop.rb +15 -0
- data/lib/coradoc/html/drop/table_row_drop.rb +23 -0
- data/lib/coradoc/html/drop/term_drop.rb +24 -0
- data/lib/coradoc/html/drop/text_content_drop.rb +15 -0
- data/lib/coradoc/html/drop/toc_drop.rb +15 -0
- data/lib/coradoc/html/drop/toc_entry_drop.rb +32 -0
- data/lib/coradoc/html/escape.rb +29 -0
- data/lib/coradoc/html/input/cleaner.rb +4 -33
- data/lib/coradoc/html/input/config.rb +4 -3
- data/lib/coradoc/html/input/converters/a.rb +8 -19
- data/lib/coradoc/html/input/converters/aside.rb +4 -5
- data/lib/coradoc/html/input/converters/audio.rb +8 -35
- data/lib/coradoc/html/input/converters/base.rb +29 -27
- data/lib/coradoc/html/input/converters/blockquote.rb +4 -2
- data/lib/coradoc/html/input/converters/br.rb +4 -4
- data/lib/coradoc/html/input/converters/bypass.rb +68 -67
- data/lib/coradoc/html/input/converters/code.rb +7 -5
- data/lib/coradoc/html/input/converters/div.rb +4 -4
- data/lib/coradoc/html/input/converters/dl.rb +3 -25
- data/lib/coradoc/html/input/converters/drop.rb +13 -13
- data/lib/coradoc/html/input/converters/em.rb +5 -3
- data/lib/coradoc/html/input/converters/figure.rb +3 -26
- data/lib/coradoc/html/input/converters/h.rb +9 -11
- data/lib/coradoc/html/input/converters/head.rb +5 -4
- data/lib/coradoc/html/input/converters/hr.rb +4 -5
- data/lib/coradoc/html/input/converters/img.rb +4 -9
- data/lib/coradoc/html/input/converters/li.rb +3 -1
- data/lib/coradoc/html/input/converters/mark.rb +3 -1
- data/lib/coradoc/html/input/converters/markup.rb +4 -8
- data/lib/coradoc/html/input/converters/math.rb +7 -14
- data/lib/coradoc/html/input/converters/media_base.rb +50 -0
- data/lib/coradoc/html/input/converters/ol.rb +6 -8
- data/lib/coradoc/html/input/converters/p.rb +43 -34
- data/lib/coradoc/html/input/converters/pass_through.rb +2 -4
- data/lib/coradoc/html/input/converters/positional_formatting.rb +37 -0
- data/lib/coradoc/html/input/converters/pre.rb +3 -3
- data/lib/coradoc/html/input/converters/q.rb +6 -3
- data/lib/coradoc/html/input/converters/strong.rb +4 -2
- data/lib/coradoc/html/input/converters/sub.rb +7 -23
- data/lib/coradoc/html/input/converters/sup.rb +7 -23
- data/lib/coradoc/html/input/converters/table.rb +3 -1
- data/lib/coradoc/html/input/converters/td.rb +4 -30
- data/lib/coradoc/html/input/converters/text.rb +4 -3
- data/lib/coradoc/html/input/converters/tr.rb +3 -2
- data/lib/coradoc/html/input/converters/video.rb +14 -36
- data/lib/coradoc/html/input/converters.rb +17 -35
- data/lib/coradoc/html/input/html_converter.rb +2 -74
- data/lib/coradoc/html/input/plugin.rb +8 -50
- data/lib/coradoc/html/input/plugins/plateau.rb +4 -19
- data/lib/coradoc/html/input/postprocessor.rb +3 -9
- data/lib/coradoc/html/input.rb +26 -8
- data/lib/coradoc/html/layout_renderer.rb +163 -0
- data/lib/coradoc/html/output.rb +6 -12
- data/lib/coradoc/html/renderer.rb +84 -350
- data/lib/coradoc/html/section_numberable.rb +9 -0
- data/lib/coradoc/html/spa.rb +29 -270
- data/lib/coradoc/html/static.rb +29 -238
- data/lib/coradoc/html/template_caching.rb +31 -0
- data/lib/coradoc/html/template_config.rb +11 -70
- data/lib/coradoc/html/template_helpers.rb +39 -31
- data/lib/coradoc/html/template_locator.rb +17 -11
- data/lib/coradoc/html/theme.rb +1 -7
- data/lib/coradoc/html/title_text.rb +57 -0
- data/lib/coradoc/html/toc_builder.rb +105 -0
- data/lib/coradoc/html/toc_serializer.rb +33 -0
- data/lib/coradoc/html/transform/from_core_model.rb +13 -12
- data/lib/coradoc/html/transform/to_core_model.rb +10 -12
- data/lib/coradoc/html/version.rb +1 -1
- data/lib/coradoc/html.rb +43 -88
- metadata +37 -70
- data/lib/coradoc/html/base.rb +0 -157
- data/lib/coradoc/html/converters/admonition.rb +0 -180
- data/lib/coradoc/html/converters/attribute.rb +0 -68
- data/lib/coradoc/html/converters/attribute_reference.rb +0 -60
- data/lib/coradoc/html/converters/audio.rb +0 -165
- data/lib/coradoc/html/converters/base.rb +0 -615
- data/lib/coradoc/html/converters/bibliography.rb +0 -82
- data/lib/coradoc/html/converters/bibliography_entry.rb +0 -108
- data/lib/coradoc/html/converters/block_image.rb +0 -72
- data/lib/coradoc/html/converters/bold.rb +0 -34
- data/lib/coradoc/html/converters/break.rb +0 -32
- data/lib/coradoc/html/converters/comment_block.rb +0 -42
- data/lib/coradoc/html/converters/comment_line.rb +0 -54
- data/lib/coradoc/html/converters/cross_reference.rb +0 -59
- data/lib/coradoc/html/converters/document.rb +0 -108
- data/lib/coradoc/html/converters/example.rb +0 -114
- data/lib/coradoc/html/converters/highlight.rb +0 -34
- data/lib/coradoc/html/converters/include.rb +0 -68
- data/lib/coradoc/html/converters/inline_image.rb +0 -41
- data/lib/coradoc/html/converters/italic.rb +0 -34
- data/lib/coradoc/html/converters/line_break.rb +0 -31
- data/lib/coradoc/html/converters/link.rb +0 -46
- data/lib/coradoc/html/converters/list_item.rb +0 -75
- data/lib/coradoc/html/converters/listing.rb +0 -99
- data/lib/coradoc/html/converters/literal.rb +0 -102
- data/lib/coradoc/html/converters/monospace.rb +0 -34
- data/lib/coradoc/html/converters/open.rb +0 -78
- data/lib/coradoc/html/converters/ordered.rb +0 -53
- data/lib/coradoc/html/converters/paragraph.rb +0 -46
- data/lib/coradoc/html/converters/quote.rb +0 -113
- data/lib/coradoc/html/converters/reviewer_comment.rb +0 -74
- data/lib/coradoc/html/converters/reviewer_note.rb +0 -134
- data/lib/coradoc/html/converters/section.rb +0 -90
- data/lib/coradoc/html/converters/sidebar.rb +0 -113
- data/lib/coradoc/html/converters/source.rb +0 -137
- data/lib/coradoc/html/converters/source_code.rb +0 -16
- data/lib/coradoc/html/converters/span.rb +0 -61
- data/lib/coradoc/html/converters/strikethrough.rb +0 -34
- data/lib/coradoc/html/converters/subscript.rb +0 -34
- data/lib/coradoc/html/converters/superscript.rb +0 -34
- data/lib/coradoc/html/converters/table.rb +0 -85
- data/lib/coradoc/html/converters/table_cell.rb +0 -203
- data/lib/coradoc/html/converters/table_row.rb +0 -45
- data/lib/coradoc/html/converters/template_html_converter.rb +0 -105
- data/lib/coradoc/html/converters/term.rb +0 -58
- data/lib/coradoc/html/converters/text_element.rb +0 -44
- data/lib/coradoc/html/converters/underline.rb +0 -34
- data/lib/coradoc/html/converters/unordered.rb +0 -47
- data/lib/coradoc/html/converters/verse.rb +0 -105
- data/lib/coradoc/html/converters/video.rb +0 -179
- data/lib/coradoc/html/element_mapping.rb +0 -210
- data/lib/coradoc/html/entity.rb +0 -137
- data/lib/coradoc/html/input/converters/ignore.rb +0 -22
- data/lib/coradoc/html/input/converters/th.rb +0 -20
- data/lib/coradoc/html/theme/base.rb +0 -231
- data/lib/coradoc/html/theme/classic_renderer.rb +0 -390
- data/lib/coradoc/html/theme/modern/components/ui_components.rb +0 -344
- data/lib/coradoc/html/theme/modern/css_generator.rb +0 -311
- data/lib/coradoc/html/theme/modern/javascript_generator.rb +0 -314
- data/lib/coradoc/html/theme/modern/serializers/document_serializer.rb +0 -382
- data/lib/coradoc/html/theme/modern/tailwind_config_builder.rb +0 -164
- data/lib/coradoc/html/theme/modern/vue_template_generator.rb +0 -374
- data/lib/coradoc/html/theme/modern_renderer.rb +0 -250
- data/lib/coradoc/html/theme/registry.rb +0 -153
|
@@ -4,45 +4,32 @@ require 'pathname'
|
|
|
4
4
|
|
|
5
5
|
module Coradoc
|
|
6
6
|
module Html
|
|
7
|
-
# Configuration for the Liquid template system
|
|
7
|
+
# Configuration for the Liquid template system.
|
|
8
8
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# Delegates template lookup to TemplateLocator for consistent behavior
|
|
10
|
+
# across global configuration and per-renderer usage.
|
|
11
11
|
#
|
|
12
12
|
# @example Global configuration
|
|
13
13
|
# Coradoc::Html.configure do |config|
|
|
14
14
|
# config.template_dirs = ["/path/to/custom/templates"]
|
|
15
15
|
# end
|
|
16
|
-
#
|
|
17
|
-
# @example Per-render configuration
|
|
18
|
-
# Coradoc::Html.serialize(document, template_dirs: ["/custom/templates"])
|
|
19
|
-
#
|
|
20
16
|
class TemplateConfig
|
|
21
|
-
|
|
22
|
-
DEFAULT_TEMPLATE_DIR = Pathname.new(File.join(
|
|
23
|
-
File.dirname(__FILE__), 'templates', 'core_model'
|
|
24
|
-
)).freeze
|
|
17
|
+
DEFAULT_TEMPLATE_DIR = TemplateLocator::DEFAULT_TEMPLATE_DIR
|
|
25
18
|
|
|
26
|
-
# @return [Array<Pathname>] List of user-provided template directories
|
|
27
19
|
attr_accessor :template_dirs
|
|
28
20
|
|
|
29
|
-
# Initialize a new configuration
|
|
30
|
-
#
|
|
31
|
-
# @param template_dirs [Array<String, Pathname>] Custom template directories
|
|
32
21
|
def initialize(template_dirs: [])
|
|
33
22
|
@template_dirs = Array(template_dirs).map { |dir| Pathname.new(dir) }
|
|
34
23
|
end
|
|
35
24
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
def locator
|
|
26
|
+
@locator ||= TemplateLocator.new(user_dirs: @template_dirs)
|
|
27
|
+
end
|
|
28
|
+
|
|
39
29
|
def all_template_dirs
|
|
40
30
|
@template_dirs + [DEFAULT_TEMPLATE_DIR]
|
|
41
31
|
end
|
|
42
32
|
|
|
43
|
-
# List all available default templates
|
|
44
|
-
#
|
|
45
|
-
# @return [Array<Symbol>] List of template names (without .liquid extension)
|
|
46
33
|
def self.available_templates
|
|
47
34
|
@available_templates ||= begin
|
|
48
35
|
return [] unless DEFAULT_TEMPLATE_DIR.exist?
|
|
@@ -54,48 +41,24 @@ module Coradoc
|
|
|
54
41
|
end
|
|
55
42
|
end
|
|
56
43
|
|
|
57
|
-
# Get the path to a specific default template
|
|
58
|
-
#
|
|
59
|
-
# @param name [Symbol, String] Template name (e.g., :bibliography)
|
|
60
|
-
# @return [Pathname, nil] Path to the template file, or nil if not found
|
|
61
44
|
def self.template_path_for(name)
|
|
62
45
|
path = DEFAULT_TEMPLATE_DIR.join("#{name}.liquid")
|
|
63
46
|
path.exist? ? path : nil
|
|
64
47
|
end
|
|
65
48
|
|
|
66
|
-
# Check if a template exists
|
|
67
|
-
#
|
|
68
|
-
# @param name [Symbol, String] Template name
|
|
69
|
-
# @return [Boolean] True if template exists in any directory
|
|
70
49
|
def template_exists?(name)
|
|
71
|
-
|
|
72
|
-
dir.join("#{name}.liquid").exist?
|
|
73
|
-
end
|
|
50
|
+
locator.exists?(name.to_s)
|
|
74
51
|
end
|
|
75
52
|
|
|
76
|
-
# Find a template by name
|
|
77
|
-
#
|
|
78
|
-
# @param name [Symbol, String] Template name
|
|
79
|
-
# @return [Pathname, nil] Path to the first matching template
|
|
80
53
|
def find_template(name)
|
|
81
|
-
|
|
82
|
-
path = dir.join("#{name}.liquid")
|
|
83
|
-
return path if path.exist?
|
|
84
|
-
end
|
|
85
|
-
nil
|
|
54
|
+
locator.find(name.to_s)
|
|
86
55
|
end
|
|
87
56
|
|
|
88
|
-
# Reset configuration to defaults
|
|
89
|
-
#
|
|
90
|
-
# @return [void]
|
|
91
57
|
def reset!
|
|
92
58
|
@template_dirs = []
|
|
59
|
+
@locator = nil
|
|
93
60
|
end
|
|
94
61
|
|
|
95
|
-
# Create a copy of this configuration with additional directories
|
|
96
|
-
#
|
|
97
|
-
# @param additional_dirs [Array<String, Pathname>] Extra directories
|
|
98
|
-
# @return [TemplateConfig] New configuration with merged directories
|
|
99
62
|
def with_dirs(additional_dirs)
|
|
100
63
|
self.class.new(
|
|
101
64
|
template_dirs: @template_dirs + Array(additional_dirs).map { |d| Pathname.new(d) }
|
|
@@ -105,44 +68,22 @@ module Coradoc
|
|
|
105
68
|
|
|
106
69
|
# Module-level configuration storage
|
|
107
70
|
class << self
|
|
108
|
-
# Get the global configuration
|
|
109
|
-
#
|
|
110
|
-
# @return [TemplateConfig] The global configuration
|
|
111
71
|
def configuration
|
|
112
72
|
@configuration ||= TemplateConfig.new
|
|
113
73
|
end
|
|
114
74
|
|
|
115
|
-
# Configure the template system
|
|
116
|
-
#
|
|
117
|
-
# @yield [TemplateConfig] Yields the configuration object
|
|
118
|
-
# @return [void]
|
|
119
|
-
#
|
|
120
|
-
# @example
|
|
121
|
-
# Coradoc::Html.configure do |config|
|
|
122
|
-
# config.template_dirs = ["/path/to/templates"]
|
|
123
|
-
# end
|
|
124
75
|
def configure
|
|
125
76
|
yield(configuration) if block_given?
|
|
126
77
|
end
|
|
127
78
|
|
|
128
|
-
# Reset configuration to defaults
|
|
129
|
-
#
|
|
130
|
-
# @return [void]
|
|
131
79
|
def reset_configuration!
|
|
132
80
|
@configuration = nil
|
|
133
81
|
end
|
|
134
82
|
|
|
135
|
-
# List all available default templates
|
|
136
|
-
#
|
|
137
|
-
# @return [Array<Symbol>] List of template names
|
|
138
83
|
def available_templates
|
|
139
84
|
TemplateConfig.available_templates
|
|
140
85
|
end
|
|
141
86
|
|
|
142
|
-
# Get the path to a default template
|
|
143
|
-
#
|
|
144
|
-
# @param name [Symbol, String] Template name
|
|
145
|
-
# @return [Pathname, nil] Path to template or nil
|
|
146
87
|
def template_path_for(name)
|
|
147
88
|
TemplateConfig.template_path_for(name)
|
|
148
89
|
end
|
|
@@ -6,53 +6,61 @@ module Coradoc
|
|
|
6
6
|
module Html
|
|
7
7
|
# Liquid filters for template rendering
|
|
8
8
|
module TemplateFilters
|
|
9
|
-
# Render a CoreModel element by looking up its template
|
|
9
|
+
# Render a CoreModel element or Drop by looking up its template.
|
|
10
10
|
#
|
|
11
11
|
# Usage in templates:
|
|
12
12
|
# {{ child | render_element }}
|
|
13
13
|
# {% for item in children %}{{ item | render_element }}{% endfor %}
|
|
14
14
|
#
|
|
15
|
-
def render_element(input
|
|
15
|
+
def render_element(input)
|
|
16
16
|
return '' if input.nil?
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
|
|
18
|
+
renderer = @context.registers[:renderer]
|
|
19
|
+
return '' unless renderer
|
|
20
|
+
|
|
21
|
+
case input
|
|
22
|
+
when Drop::Base
|
|
23
|
+
renderer.render_drop(input)
|
|
24
|
+
when Array
|
|
25
|
+
input.map { |i| render_element(i) }.join("\n")
|
|
26
|
+
when Hash
|
|
27
|
+
render_hash_data(input, renderer)
|
|
28
|
+
when String
|
|
29
|
+
input
|
|
30
|
+
else
|
|
31
|
+
drop = Drop::DropFactory.create(input)
|
|
32
|
+
drop.is_a?(Drop::Base) ? renderer.render_drop(drop) : drop.to_s
|
|
33
|
+
end
|
|
33
34
|
end
|
|
34
35
|
|
|
35
|
-
# Escape HTML entities
|
|
36
36
|
def escape_html(input)
|
|
37
|
-
input
|
|
38
|
-
.gsub(/&/, '&')
|
|
39
|
-
.gsub(/</, '<')
|
|
40
|
-
.gsub(/>/, '>')
|
|
41
|
-
.gsub(/"/, '"')
|
|
42
|
-
.gsub(/'/, ''')
|
|
37
|
+
Escape.escape_html(input)
|
|
43
38
|
end
|
|
44
39
|
|
|
45
40
|
# Escape HTML attribute values
|
|
46
41
|
def escape_attr(input)
|
|
47
|
-
input
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
Escape.escape_attr(input)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# JSON-encode with </script protection for inline JS
|
|
46
|
+
def safe_json(input)
|
|
47
|
+
Escape.safe_json(input)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def render_hash_data(data, renderer)
|
|
53
|
+
drop_type = data['drop_type']
|
|
54
|
+
return data.to_s unless drop_type
|
|
55
|
+
|
|
56
|
+
template = renderer.find_template(drop_type)
|
|
57
|
+
return data.to_s unless template
|
|
58
|
+
|
|
59
|
+
template.render(data, registers: { renderer: renderer }).strip
|
|
52
60
|
end
|
|
53
61
|
end
|
|
54
62
|
end
|
|
55
63
|
end
|
|
56
64
|
|
|
57
|
-
# Register filters with Liquid
|
|
65
|
+
# Register filters with Liquid
|
|
58
66
|
Liquid::Environment.default.register_filter(Coradoc::Html::TemplateFilters)
|
|
@@ -23,6 +23,9 @@ module Coradoc
|
|
|
23
23
|
# Default template subdirectory within each template root
|
|
24
24
|
CORE_MODEL_DIR = 'core_model'
|
|
25
25
|
|
|
26
|
+
# Canonical default template directory within the gem
|
|
27
|
+
DEFAULT_TEMPLATE_DIR = Pathname.new(File.join(File.dirname(__FILE__), 'templates', CORE_MODEL_DIR)).freeze
|
|
28
|
+
|
|
26
29
|
attr_reader :user_dirs, :default_dir
|
|
27
30
|
|
|
28
31
|
# Initialize the locator
|
|
@@ -42,12 +45,14 @@ module Coradoc
|
|
|
42
45
|
def find(type_name)
|
|
43
46
|
return @cache[type_name] if @cache.key?(type_name)
|
|
44
47
|
|
|
45
|
-
# First check user directories
|
|
48
|
+
# First check user directories (core_model/ subdir, then root)
|
|
46
49
|
@user_dirs.each do |dir|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
%W[#{dir}/#{CORE_MODEL_DIR}/#{type_name}.liquid #{dir}/#{type_name}.liquid].each do |template_path|
|
|
51
|
+
path = Pathname.new(template_path)
|
|
52
|
+
if path.exist?
|
|
53
|
+
@cache[type_name] = path
|
|
54
|
+
return path
|
|
55
|
+
end
|
|
51
56
|
end
|
|
52
57
|
end
|
|
53
58
|
|
|
@@ -78,13 +83,14 @@ module Coradoc
|
|
|
78
83
|
def available_templates
|
|
79
84
|
types = Set.new
|
|
80
85
|
|
|
81
|
-
# Collect from user directories
|
|
86
|
+
# Collect from user directories (root and core_model/ subdir)
|
|
82
87
|
@user_dirs.each do |dir|
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
[dir, dir / CORE_MODEL_DIR].each do |scan_dir|
|
|
89
|
+
next unless scan_dir.exist? && scan_dir.directory?
|
|
85
90
|
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
scan_dir.glob('*.liquid') do |f|
|
|
92
|
+
types.add(f.basename('.liquid').to_s)
|
|
93
|
+
end
|
|
88
94
|
end
|
|
89
95
|
end
|
|
90
96
|
|
|
@@ -102,7 +108,7 @@ module Coradoc
|
|
|
102
108
|
#
|
|
103
109
|
# @return [Pathname] Path to default templates
|
|
104
110
|
def default_template_dir
|
|
105
|
-
|
|
111
|
+
DEFAULT_TEMPLATE_DIR
|
|
106
112
|
end
|
|
107
113
|
|
|
108
114
|
# Clear the template cache (useful when template directories change)
|
data/lib/coradoc/html/theme.rb
CHANGED
|
@@ -2,12 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
module Coradoc
|
|
4
4
|
module Html
|
|
5
|
-
module Theme
|
|
6
|
-
# Autoload Theme components
|
|
7
|
-
autoload :Base, 'coradoc/html/theme/base'
|
|
8
|
-
autoload :Registry, 'coradoc/html/theme/registry'
|
|
9
|
-
autoload :ClassicRenderer, 'coradoc/html/theme/classic_renderer'
|
|
10
|
-
autoload :ModernRenderer, 'coradoc/html/theme/modern_renderer'
|
|
11
|
-
end
|
|
5
|
+
module Theme; end
|
|
12
6
|
end
|
|
13
7
|
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Html
|
|
5
|
+
# Resolves CoreModel title attributes to plain-text strings.
|
|
6
|
+
#
|
|
7
|
+
# CoreModel titles can be String, CoreModel::Base (with .text),
|
|
8
|
+
# Array of mixed types, or nil. This utility provides a single
|
|
9
|
+
# resolution path used by TocBuilder, TocSerializer, Renderer,
|
|
10
|
+
# LayoutRenderer, and Drop classes.
|
|
11
|
+
module TitleText
|
|
12
|
+
TEXT_TYPES = [CoreModel::TextContent, CoreModel::Term].freeze
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def resolve(title)
|
|
17
|
+
case title
|
|
18
|
+
when nil then nil
|
|
19
|
+
when String then title
|
|
20
|
+
when CoreModel::Base then resolve_model(title)
|
|
21
|
+
when Array then title.map { |t| resolve_element(t) }.join
|
|
22
|
+
else title.to_s
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def escape(title)
|
|
27
|
+
resolved = resolve(title)
|
|
28
|
+
resolved ? Escape.escape_html(resolved) : nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def resolve_model(model)
|
|
32
|
+
if text_type?(model) && model.text
|
|
33
|
+
model.text
|
|
34
|
+
elsif content_type?(model) && model.content
|
|
35
|
+
model.content.to_s
|
|
36
|
+
else
|
|
37
|
+
model.to_s
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def resolve_element(element)
|
|
42
|
+
case element
|
|
43
|
+
when CoreModel::Base then resolve_model(element)
|
|
44
|
+
else element.to_s
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def text_type?(model)
|
|
49
|
+
TEXT_TYPES.any? { |t| model.is_a?(t) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def content_type?(model)
|
|
53
|
+
model.is_a?(CoreModel::InlineElement) || model.is_a?(CoreModel::StructuralElement)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Html
|
|
5
|
+
# Builds a CoreModel::Toc from a document's StructuralElement tree.
|
|
6
|
+
#
|
|
7
|
+
# Walks the section hierarchy, assigns section numbers, and returns
|
|
8
|
+
# a Toc model with nested TocEntry children. Section numbers are
|
|
9
|
+
# derived from tree position — single source of truth for both
|
|
10
|
+
# TOC rendering and heading numbering.
|
|
11
|
+
class TocBuilder
|
|
12
|
+
def initialize(max_level: 6, numbered: false, section_number_levels: 3)
|
|
13
|
+
@max_level = max_level
|
|
14
|
+
@numbered = numbered
|
|
15
|
+
@section_number_levels = section_number_levels
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Build a TocBuilder from renderer-style options hash.
|
|
19
|
+
#
|
|
20
|
+
# @param options [Hash] options with :section_number_levels, :toc_levels, :section_numbers
|
|
21
|
+
# @return [TocBuilder]
|
|
22
|
+
def self.from_options(options)
|
|
23
|
+
section_number_levels = options[:section_number_levels] || 3
|
|
24
|
+
toc_levels = options[:toc_levels] || 2
|
|
25
|
+
max_level = [toc_levels, section_number_levels].min
|
|
26
|
+
new(max_level: max_level, numbered: options[:section_numbers] == true, section_number_levels: section_number_levels)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Build a Toc model from a document.
|
|
30
|
+
#
|
|
31
|
+
# @param document [CoreModel::StructuralElement] the root document
|
|
32
|
+
# @return [CoreModel::Toc] the built TOC with entries and section numbers
|
|
33
|
+
def build(document)
|
|
34
|
+
entries = []
|
|
35
|
+
counters = [0]
|
|
36
|
+
collect_entries(document.children, entries, 1, counters) if document.children
|
|
37
|
+
|
|
38
|
+
CoreModel::Toc.new(
|
|
39
|
+
entries: entries,
|
|
40
|
+
min_level: 1,
|
|
41
|
+
max_level: @max_level,
|
|
42
|
+
numbered: @numbered
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Compute a mapping of section_id => section_number_string.
|
|
47
|
+
# Always computes numbers regardless of the +numbered+ flag,
|
|
48
|
+
# since this is used for heading annotation in the body.
|
|
49
|
+
#
|
|
50
|
+
# @param document [CoreModel::StructuralElement] the root document
|
|
51
|
+
# @return [Hash{String => String}] mapping of section ID to number (e.g., "2.1")
|
|
52
|
+
def section_number_map(document)
|
|
53
|
+
map = {}
|
|
54
|
+
entries = []
|
|
55
|
+
counters = [0]
|
|
56
|
+
collect_entries(document.children, entries, 1, counters, always_number: true) if document.children
|
|
57
|
+
flatten_numbers(entries, map)
|
|
58
|
+
map
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def collect_entries(items, entries, level, counters, always_number: false)
|
|
64
|
+
return unless items && level <= @max_level
|
|
65
|
+
|
|
66
|
+
items.each do |item|
|
|
67
|
+
next unless item.is_a?(CoreModel::StructuralElement)
|
|
68
|
+
next unless item.section? || item.header?
|
|
69
|
+
|
|
70
|
+
counters[level] = (counters[level] || 0) + 1
|
|
71
|
+
((level + 1)..@section_number_levels).each { |i| counters[i] = 0 }
|
|
72
|
+
|
|
73
|
+
use_number = always_number || @numbered
|
|
74
|
+
number = use_number && level <= @section_number_levels ? counters[1..level].join('.') : nil
|
|
75
|
+
|
|
76
|
+
children = []
|
|
77
|
+
collect_entries(item.children, children, level + 1, counters, always_number: always_number) if item.children
|
|
78
|
+
|
|
79
|
+
entries << CoreModel::TocEntry.new(
|
|
80
|
+
id: entry_id(item),
|
|
81
|
+
title: entry_title(item),
|
|
82
|
+
level: level,
|
|
83
|
+
number: number,
|
|
84
|
+
children: children
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def flatten_numbers(entries, map)
|
|
90
|
+
entries.each do |entry|
|
|
91
|
+
map[entry.id] = entry.number if entry.id && entry.number
|
|
92
|
+
flatten_numbers(entry.children, map) if entry.children
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def entry_title(section)
|
|
97
|
+
TitleText.resolve(section.title)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def entry_id(section)
|
|
101
|
+
section.id
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Html
|
|
5
|
+
# Serializes a document's TOC structure to JSON for inline embedding.
|
|
6
|
+
#
|
|
7
|
+
# Used by the SPA layout to provide client-side navigation data.
|
|
8
|
+
class TocSerializer
|
|
9
|
+
def build_json(document, options)
|
|
10
|
+
return { entries: [], numbered: false } unless document.is_a?(CoreModel::StructuralElement)
|
|
11
|
+
|
|
12
|
+
numbered = options[:section_numbers] == true
|
|
13
|
+
builder = TocBuilder.from_options(options)
|
|
14
|
+
toc = builder.build(document)
|
|
15
|
+
{ entries: serialize_entries(toc.entries), numbered: numbered }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def serialize_entries(entries)
|
|
21
|
+
entries.map do |entry|
|
|
22
|
+
{
|
|
23
|
+
id: entry.id,
|
|
24
|
+
title: TitleText.resolve(entry.title),
|
|
25
|
+
number: entry.number,
|
|
26
|
+
level: entry.level,
|
|
27
|
+
children: entry.children.any? ? serialize_entries(entry.children) : []
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'coradoc/core_model'
|
|
4
|
+
|
|
3
5
|
module Coradoc
|
|
4
6
|
module Html
|
|
5
7
|
module Transform
|
|
6
|
-
# Transforms CoreModel
|
|
8
|
+
# Transforms CoreModel to HTML output
|
|
7
9
|
#
|
|
8
|
-
# This transformer converts CoreModel to
|
|
9
|
-
#
|
|
10
|
-
# directly, so this transformer primarily passes through the CoreModel.
|
|
10
|
+
# This transformer converts CoreModel to HTML strings by delegating
|
|
11
|
+
# to the existing theme/renderer pipeline.
|
|
11
12
|
class FromCoreModel
|
|
12
13
|
class << self
|
|
13
|
-
# Transform a CoreModel to HTML
|
|
14
|
+
# Transform a CoreModel to HTML string
|
|
14
15
|
#
|
|
15
|
-
# @param model [Coradoc::CoreModel::Base] CoreModel to transform
|
|
16
|
-
# @
|
|
17
|
-
|
|
16
|
+
# @param model [Coradoc::CoreModel::Base, Array] CoreModel to transform
|
|
17
|
+
# @param options [Hash] Renderer options (e.g., theme)
|
|
18
|
+
# @return [String] HTML output
|
|
19
|
+
def transform(model, options = {})
|
|
18
20
|
case model
|
|
19
21
|
when Coradoc::CoreModel::Base
|
|
20
|
-
|
|
21
|
-
model
|
|
22
|
+
Html.serialize(model, options)
|
|
22
23
|
when Array
|
|
23
|
-
model.map { |item| transform(item) }
|
|
24
|
+
model.map { |item| transform(item, options) }.join("\n")
|
|
24
25
|
else
|
|
25
|
-
model
|
|
26
|
+
model.to_s
|
|
26
27
|
end
|
|
27
28
|
end
|
|
28
29
|
end
|
|
@@ -1,31 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'nokogiri'
|
|
3
4
|
require 'coradoc/core_model'
|
|
4
5
|
|
|
5
6
|
module Coradoc
|
|
6
7
|
module Html
|
|
7
8
|
module Transform
|
|
8
|
-
# Transforms HTML
|
|
9
|
+
# Transforms Nokogiri HTML nodes to CoreModel
|
|
9
10
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
11
|
+
# Nokogiri serves as the HTML model layer. This transformer converts
|
|
12
|
+
# Nokogiri::XML::Document or Nokogiri::XML::Node objects into CoreModel
|
|
13
|
+
# by delegating to the existing input converter pipeline.
|
|
12
14
|
class ToCoreModel
|
|
13
15
|
class << self
|
|
14
|
-
# Transform an HTML
|
|
16
|
+
# Transform an HTML model (Nokogiri node) to CoreModel
|
|
15
17
|
#
|
|
16
|
-
# @param model [
|
|
18
|
+
# @param model [Nokogiri::XML::Document, Nokogiri::XML::Node, Coradoc::CoreModel::Base]
|
|
19
|
+
# HTML input model to transform
|
|
17
20
|
# @return [Coradoc::CoreModel::Base] CoreModel equivalent
|
|
18
21
|
def transform(model)
|
|
19
|
-
# HTML input now produces CoreModel directly
|
|
20
|
-
transform_direct(model)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
private
|
|
24
|
-
|
|
25
|
-
def transform_direct(model)
|
|
26
22
|
case model
|
|
27
23
|
when Coradoc::CoreModel::Base
|
|
28
24
|
model
|
|
25
|
+
when Nokogiri::XML::Document, Nokogiri::XML::Node
|
|
26
|
+
::Coradoc::Input::Html::HtmlConverter.to_core_model(model)
|
|
29
27
|
when Array
|
|
30
28
|
model.map { |item| transform(item) }
|
|
31
29
|
else
|