coradoc-html 1.1.13 → 1.1.15

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.
@@ -5,11 +5,7 @@ module Coradoc
5
5
  module Drop
6
6
  class ListBlockDrop < Base
7
7
  def html_tag
8
- case @model.marker_type
9
- when 'ordered' then 'ol'
10
- when 'definition' then 'dl'
11
- else 'ul'
12
- end
8
+ TagMapping.tag_for(@model.marker_type)
13
9
  end
14
10
 
15
11
  def items
@@ -9,7 +9,7 @@ module Coradoc
9
9
  end
10
10
 
11
11
  def html_tag
12
- header? ? 'th' : 'td'
12
+ TagMapping.tag_for(header? ? :table_header : :table_cell)
13
13
  end
14
14
 
15
15
  def colspan
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Drop namespace — manages Liquid drop layer for template rendering.
4
+ #
5
+ # Loading order matters: Base must load before DropFactory, and all
6
+ # concrete drops must load after DropFactory (they self-register).
7
+ # Each drop calls DropFactory.register at load time.
8
+ module Coradoc
9
+ module Html
10
+ module Drop
11
+ end
12
+ end
13
+ end
14
+
15
+ # Base must load first (DropFactory depends on it)
16
+ require 'coradoc/html/drop/base'
17
+ # DropFactory loads next
18
+ require 'coradoc/html/drop/drop_factory'
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'media_base'
4
-
5
3
  module Coradoc
6
4
  module Input
7
5
  module Html
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'positional_formatting'
4
-
5
3
  module Coradoc
6
4
  module Input
7
5
  module Html
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'positional_formatting'
4
-
5
3
  module Coradoc
6
4
  module Input
7
5
  module Html
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'media_base'
4
-
5
3
  module Coradoc
6
4
  module Input
7
5
  module Html
@@ -4,43 +4,51 @@ module Coradoc
4
4
  module Input
5
5
  module Html
6
6
  module Converters
7
- # Autoload converter classes - they will register themselves when first accessed
8
- autoload :Base, 'coradoc/html/input/converters/base'
9
- autoload :Markup, 'coradoc/html/input/converters/markup'
10
- autoload :A, 'coradoc/html/input/converters/a'
11
- autoload :Aside, 'coradoc/html/input/converters/aside'
12
- autoload :Audio, 'coradoc/html/input/converters/audio'
13
- autoload :Blockquote, 'coradoc/html/input/converters/blockquote'
14
- autoload :Br, 'coradoc/html/input/converters/br'
15
- autoload :Bypass, 'coradoc/html/input/converters/bypass'
16
- autoload :Code, 'coradoc/html/input/converters/code'
17
- autoload :Div, 'coradoc/html/input/converters/div'
18
- autoload :Dl, 'coradoc/html/input/converters/dl'
19
- autoload :Skip, 'coradoc/html/input/converters/drop'
20
- autoload :Em, 'coradoc/html/input/converters/em'
21
- autoload :Figure, 'coradoc/html/input/converters/figure'
22
- autoload :H, 'coradoc/html/input/converters/h'
23
- autoload :Head, 'coradoc/html/input/converters/head'
24
- autoload :Hr, 'coradoc/html/input/converters/hr'
25
- autoload :Img, 'coradoc/html/input/converters/img'
26
- autoload :Li, 'coradoc/html/input/converters/li'
27
- autoload :Mark, 'coradoc/html/input/converters/mark'
28
- autoload :Math, 'coradoc/html/input/converters/math'
29
- autoload :MediaBase, 'coradoc/html/input/converters/media_base'
30
- autoload :Ol, 'coradoc/html/input/converters/ol'
31
- autoload :P, 'coradoc/html/input/converters/p'
32
- autoload :PassThrough, 'coradoc/html/input/converters/pass_through'
33
- autoload :PositionalFormatting, 'coradoc/html/input/converters/positional_formatting'
34
- autoload :Pre, 'coradoc/html/input/converters/pre'
35
- autoload :Q, 'coradoc/html/input/converters/q'
36
- autoload :Strong, 'coradoc/html/input/converters/strong'
37
- autoload :Sup, 'coradoc/html/input/converters/sup'
38
- autoload :Sub, 'coradoc/html/input/converters/sub'
39
- autoload :Table, 'coradoc/html/input/converters/table'
40
- autoload :Td, 'coradoc/html/input/converters/td'
41
- autoload :Text, 'coradoc/html/input/converters/text'
42
- autoload :Tr, 'coradoc/html/input/converters/tr'
43
- autoload :Video, 'coradoc/html/input/converters/video'
7
+ # Autoload converter classes they self-register when loaded.
8
+ # Adding a new converter requires only adding one entry here.
9
+ CONVERTERS = {
10
+ Base: 'coradoc/html/input/converters/base',
11
+ Markup: 'coradoc/html/input/converters/markup',
12
+ A: 'coradoc/html/input/converters/a',
13
+ Aside: 'coradoc/html/input/converters/aside',
14
+ Audio: 'coradoc/html/input/converters/audio',
15
+ Blockquote: 'coradoc/html/input/converters/blockquote',
16
+ Br: 'coradoc/html/input/converters/br',
17
+ Bypass: 'coradoc/html/input/converters/bypass',
18
+ Code: 'coradoc/html/input/converters/code',
19
+ Div: 'coradoc/html/input/converters/div',
20
+ Dl: 'coradoc/html/input/converters/dl',
21
+ Skip: 'coradoc/html/input/converters/drop',
22
+ Em: 'coradoc/html/input/converters/em',
23
+ Figure: 'coradoc/html/input/converters/figure',
24
+ H: 'coradoc/html/input/converters/h',
25
+ Head: 'coradoc/html/input/converters/head',
26
+ Hr: 'coradoc/html/input/converters/hr',
27
+ Img: 'coradoc/html/input/converters/img',
28
+ Li: 'coradoc/html/input/converters/li',
29
+ Mark: 'coradoc/html/input/converters/mark',
30
+ Math: 'coradoc/html/input/converters/math',
31
+ MediaBase: 'coradoc/html/input/converters/media_base',
32
+ Ol: 'coradoc/html/input/converters/ol',
33
+ P: 'coradoc/html/input/converters/p',
34
+ PassThrough: 'coradoc/html/input/converters/pass_through',
35
+ PositionalFormatting: 'coradoc/html/input/converters/positional_formatting',
36
+ Pre: 'coradoc/html/input/converters/pre',
37
+ Q: 'coradoc/html/input/converters/q',
38
+ Strong: 'coradoc/html/input/converters/strong',
39
+ Sup: 'coradoc/html/input/converters/sup',
40
+ Sub: 'coradoc/html/input/converters/sub',
41
+ Table: 'coradoc/html/input/converters/table',
42
+ Td: 'coradoc/html/input/converters/td',
43
+ Text: 'coradoc/html/input/converters/text',
44
+ Tr: 'coradoc/html/input/converters/tr',
45
+ Video: 'coradoc/html/input/converters/video'
46
+ }.freeze
47
+ private_constant :CONVERTERS
48
+
49
+ CONVERTERS.each do |name, path|
50
+ autoload name, path
51
+ end
44
52
 
45
53
  @converters = {}
46
54
  @converters_loaded = false
@@ -57,12 +65,7 @@ module Coradoc
57
65
  return if @converters_loaded
58
66
 
59
67
  @converters_loaded = true
60
-
61
- [
62
- Base, Markup, A, Aside, Audio, Blockquote, Br, Bypass, Code, Div, Dl,
63
- Skip, Em, Figure, H, Head, Hr, Img, Li, Mark, Math, Ol, P,
64
- PassThrough, Pre, Q, Strong, Sup, Sub, Table, Td, Text, Tr, Video
65
- ].each { |converter| _ = converter }
68
+ CONVERTERS.each_key { |name| const_get(name) }
66
69
  end
67
70
 
68
71
  def self.lookup(tag_name)
@@ -65,13 +65,6 @@ module Coradoc
65
65
  Coradoc::Html::Input::Converters.process_coradoc(tree, state)
66
66
  end
67
67
 
68
- def html_tree_preview
69
- Tempfile.open(%w[coradoc .html]) do |i|
70
- i << html_tree.to_html
71
- system 'chromium-browser', '--no-sandbox', i.path
72
- end
73
- end
74
-
75
68
  # define preprocess_html_tree to process HTML trees
76
69
 
77
70
  # Creates a hook to be called instead of converting an element
@@ -15,25 +15,27 @@ module Coradoc
15
15
  @dist_assets_cache = {}
16
16
  end
17
17
 
18
- def render_static(document, body_html, options)
18
+ def render_static(document, body_html, opts)
19
+ opts = RenderOptions.new(**opts) unless opts.is_a?(RenderOptions)
19
20
  layout_template = load_layout('default')
20
- return build_static_fallback(document, body_html, options) unless layout_template
21
+ return build_static_fallback(document, body_html, opts) unless layout_template
21
22
 
22
- layout_template.render(build_static_layout_data(document, body_html, options)).strip
23
+ layout_template.render(build_static_layout_data(document, body_html, opts)).strip
23
24
  end
24
25
 
25
- def render_spa(document, options, body_html, toc_data)
26
- dist_dir = options[:dist_dir] || File.expand_path('../../../frontend/dist', __dir__)
26
+ def render_spa(document, opts, body_html, toc_data)
27
+ opts = RenderOptions.new(**opts) unless opts.is_a?(RenderOptions)
28
+ dist_dir = opts.dist_dir || File.expand_path('../../../frontend/dist', __dir__)
27
29
  assets = load_dist_assets(dist_dir)
28
30
 
29
- content_data = build_spa_content_data(document, body_html, options, toc_data)
31
+ content_data = build_spa_content_data(document, body_html, opts, toc_data)
30
32
  safe_json = Escape.safe_json(content_data)
31
33
 
32
34
  layout_template = load_layout('spa')
33
35
  if layout_template
34
- layout_template.render(build_spa_layout_data(document, options, assets, safe_json)).strip
36
+ layout_template.render(build_spa_layout_data(document, opts, assets, safe_json)).strip
35
37
  else
36
- build_spa_fallback(document, options, assets, safe_json)
38
+ build_spa_fallback(document, opts, assets, safe_json)
37
39
  end
38
40
  end
39
41
 
@@ -47,25 +49,25 @@ module Coradoc
47
49
  TitleText.escape(document&.title) || Config::DEFAULT_TITLE
48
50
  end
49
51
 
50
- def resolve_lang(options)
51
- options[:lang] || Config::DEFAULT_LANG
52
+ def resolve_lang(opts)
53
+ opts.lang || Config::DEFAULT_LANG
52
54
  end
53
55
 
54
- def build_static_layout_data(document, body_html, options)
56
+ def build_static_layout_data(document, body_html, opts)
55
57
  {
56
- 'lang' => resolve_lang(options),
58
+ 'lang' => resolve_lang(opts),
57
59
  'title' => resolve_escaped_title(document),
58
- 'author' => options[:author],
59
- 'description' => options[:description],
60
+ 'author' => opts.author,
61
+ 'description' => opts.description,
60
62
  'generator_version' => Coradoc::VERSION.to_s,
61
63
  'body' => body_html,
62
- 'custom_css' => options[:custom_css]
64
+ 'custom_css' => opts.custom_css
63
65
  }
64
66
  end
65
67
 
66
- def build_static_fallback(document, body_html, options)
68
+ def build_static_fallback(document, body_html, opts)
67
69
  Nokogiri::HTML::Builder.new do |doc|
68
- doc.html(lang: resolve_lang(options)) do
70
+ doc.html(lang: resolve_lang(opts)) do
69
71
  doc.head do
70
72
  doc.meta(charset: 'UTF-8')
71
73
  doc.meta(name: 'viewport', content: 'width=device-width, initial-scale=1.0')
@@ -76,42 +78,42 @@ module Coradoc
76
78
  end.to_html
77
79
  end
78
80
 
79
- def build_spa_content_data(document, body_html, options, toc_data)
81
+ def build_spa_content_data(document, body_html, opts, toc_data)
80
82
  {
81
83
  mode: 'classic',
82
84
  contentHtml: body_html,
83
85
  toc: toc_data,
84
- meta: build_spa_meta(document, options),
85
- options: build_spa_options(options)
86
+ meta: build_spa_meta(document, opts),
87
+ options: build_spa_options(opts)
86
88
  }
87
89
  end
88
90
 
89
- def build_spa_meta(document, options)
91
+ def build_spa_meta(document, _opts)
90
92
  {
91
93
  title: TitleText.resolve(document&.title) || Config::DEFAULT_TITLE,
92
- author: options[:author],
93
- date: options[:revdate],
94
+ author: _opts.author,
95
+ date: nil,
94
96
  generator: "Coradoc #{Coradoc::VERSION}"
95
97
  }
96
98
  end
97
99
 
98
- def build_spa_options(options)
100
+ def build_spa_options(opts)
99
101
  {
100
- toc: options[:toc] ? true : false,
101
- tocPlacement: (options[:toc_placement] || :auto).to_s,
102
- sectnums: options[:section_numbers] == true,
103
- themeToggle: options[:theme_toggle] != false,
104
- readingProgress: options[:reading_progress] != false,
105
- lang: resolve_lang(options)
102
+ toc: opts.toc ? true : false,
103
+ tocPlacement: (opts.toc_placement || :auto).to_s,
104
+ sectnums: opts.section_numbers == true,
105
+ themeToggle: opts.theme_toggle != false,
106
+ readingProgress: opts.reading_progress != false,
107
+ lang: resolve_lang(opts)
106
108
  }
107
109
  end
108
110
 
109
- def build_spa_layout_data(document, options, assets, safe_json)
111
+ def build_spa_layout_data(document, opts, assets, safe_json)
110
112
  {
111
- 'lang' => resolve_lang(options),
113
+ 'lang' => resolve_lang(opts),
112
114
  'title' => resolve_escaped_title(document),
113
- 'author' => options[:author],
114
- 'description' => options[:description],
115
+ 'author' => opts.author,
116
+ 'description' => opts.description,
115
117
  'generator_version' => Coradoc::VERSION.to_s,
116
118
  'css' => assets[:css],
117
119
  'js' => assets[:js],
@@ -119,9 +121,9 @@ module Coradoc
119
121
  }
120
122
  end
121
123
 
122
- def build_spa_fallback(document, options, assets, safe_json)
124
+ def build_spa_fallback(document, opts, assets, safe_json)
123
125
  Nokogiri::HTML::Builder.new do |doc|
124
- doc.html(lang: resolve_lang(options)) do
126
+ doc.html(lang: resolve_lang(opts)) do
125
127
  doc.head do
126
128
  doc.meta(charset: 'UTF-8')
127
129
  doc.meta(name: 'viewport', content: 'width=device-width, initial-scale=1.0')
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Html
5
+ class RenderOptions
6
+ attr_reader :layout, :lang, :toc, :toc_levels, :section_numbers,
7
+ :section_number_levels, :author, :description, :custom_css,
8
+ :dist_dir, :theme_toggle, :reading_progress, :toc_placement
9
+
10
+ def initialize(layout: :static, lang: 'en', toc: false, toc_levels: 2,
11
+ section_numbers: false, section_number_levels: 3,
12
+ author: nil, description: nil, custom_css: nil,
13
+ dist_dir: nil, theme_toggle: true, reading_progress: true,
14
+ toc_placement: :auto)
15
+ @layout = layout
16
+ @lang = lang
17
+ @toc = toc
18
+ @toc_levels = toc_levels
19
+ @section_numbers = section_numbers
20
+ @section_number_levels = section_number_levels
21
+ @author = author
22
+ @description = description
23
+ @custom_css = custom_css
24
+ @dist_dir = dist_dir
25
+ @theme_toggle = theme_toggle
26
+ @reading_progress = reading_progress
27
+ @toc_placement = toc_placement
28
+ freeze
29
+ end
30
+
31
+ def spa?
32
+ @layout == :spa
33
+ end
34
+
35
+ def static?
36
+ @layout == :static
37
+ end
38
+ end
39
+ end
40
+ end
@@ -48,14 +48,21 @@ module Coradoc
48
48
  template.render(assigns, registers: { renderer: self, section_numbers: @section_numbers }).strip
49
49
  end
50
50
 
51
- def render_html5(document, **options)
52
- @section_numbers = compute_section_numbers(document, options)
51
+ def render_html5(document, **)
52
+ opts = RenderOptions.new(**)
53
+ builder = TocBuilder.from_options(opts)
54
+ @toc, @section_numbers = if document.is_a?(CoreModel::StructuralElement)
55
+ builder.build_with_numbers(document)
56
+ else
57
+ [nil, {}]
58
+ end
59
+
53
60
  body_html = render(document)
54
61
 
55
- if options[:layout] == :spa
56
- render_spa_layout(document, body_html, options)
62
+ if opts.spa?
63
+ render_spa_layout(document, body_html, opts)
57
64
  else
58
- render_static_layout(document, body_html, options)
65
+ render_static_layout(document, body_html, opts)
59
66
  end
60
67
  end
61
68
 
@@ -73,34 +80,22 @@ module Coradoc
73
80
 
74
81
  private
75
82
 
76
- def build_toc(_document, options)
77
- TocBuilder.from_options(options)
78
- end
79
-
80
- def compute_section_numbers(document, options)
81
- if options[:section_numbers] && document.is_a?(CoreModel::StructuralElement)
82
- build_toc(document, options).section_number_map(document)
83
- else
84
- {}
85
- end
86
- end
87
-
88
- def render_toc_for(document, options)
89
- toc = build_toc(document, options).build(document)
90
- render(toc)
91
- end
92
-
93
- def render_static_layout(document, body_html, options)
94
- if options[:toc] && document.is_a?(CoreModel::StructuralElement)
95
- toc_html = render_toc_for(document, options)
83
+ def render_static_layout(document, body_html, opts)
84
+ if opts.toc && @toc
85
+ toc_html = render(@toc)
96
86
  body_html = "#{toc_html}\n#{body_html}" unless toc_html.empty?
97
87
  end
98
- @layout_renderer.render_static(document, body_html, options)
88
+ @layout_renderer.render_static(document, body_html, opts)
99
89
  end
100
90
 
101
- def render_spa_layout(document, body_html, options)
102
- toc_data = TocSerializer.new.build_json(document, options)
103
- @layout_renderer.render_spa(document, options, body_html, toc_data)
91
+ def render_spa_layout(document, body_html, opts)
92
+ numbered = opts.section_numbers == true
93
+ toc_data = if @toc
94
+ { entries: TocSerializer.new.serialize_entries(@toc.entries), numbered: numbered }
95
+ else
96
+ { entries: [], numbered: false }
97
+ end
98
+ @layout_renderer.render_spa(document, opts, body_html, toc_data)
104
99
  end
105
100
 
106
101
  def find_and_load_template(type_name)
@@ -123,11 +118,12 @@ module Coradoc
123
118
  resolved = TitleText.resolve(drop.model)
124
119
  text = resolved ? Escape.escape_html(resolved) : ''
125
120
 
126
- doc = Nokogiri::HTML::Document.new
127
- fragment = Nokogiri::HTML::Builder.with(doc) do |builder|
128
- builder.div(class: "element element-#{type}") { builder.text text }
129
- end
130
- fragment.at_css('div').to_html
121
+ fragment = Nokogiri::HTML.fragment
122
+ div = Nokogiri::XML::Node.new('div', fragment.document)
123
+ div['class'] = "element element-#{type}"
124
+ div.content = text
125
+ fragment.add_child(div)
126
+ fragment.to_html
131
127
  end
132
128
 
133
129
  def normalize_dirs(dirs)
@@ -12,33 +12,16 @@ module Coradoc
12
12
  # Uses the unified Liquid template pipeline.
13
13
  class Spa < ConverterBase
14
14
  class Configuration < ConverterBase::ConfigurationBase
15
- attr_accessor :theme_toggle, :reading_progress,
16
- :toc_sticky, :toc_levels, :lang,
17
- :template_dirs, :dist_dir
18
-
19
- def initialize(**options)
20
- @theme_toggle = options.fetch(:theme_toggle, true)
21
- @reading_progress = options.fetch(:reading_progress, true)
22
- @toc_sticky = options.fetch(:toc_sticky, true)
23
- @toc_levels = options[:toc_levels] || 2
24
- @lang = options[:lang] || 'en'
25
- @template_dirs = options[:template_dirs]
26
- @dist_dir = options[:dist_dir]
27
- end
28
-
29
- def to_h
30
- {
31
- theme_toggle: @theme_toggle, reading_progress: @reading_progress,
32
- toc_sticky: @toc_sticky, toc_levels: @toc_levels,
33
- lang: @lang, template_dirs: @template_dirs, dist_dir: @dist_dir
34
- }
35
- end
15
+ attribute :theme_toggle, default: true
16
+ attribute :reading_progress, default: true
17
+ attribute :toc_sticky, default: true
18
+ attribute :toc_levels, default: 2
19
+ attribute :lang, default: 'en'
20
+ attribute :template_dirs
21
+ attribute :dist_dir
36
22
 
37
23
  def validate!
38
- return if @toc_levels.is_a?(Integer) && @toc_levels.between?(1, 5)
39
-
40
- raise ConverterBase::ValidationError,
41
- 'TOC levels must be an integer between 1 and 5'
24
+ range_check(:toc_levels, 1, 5, label: 'TOC levels')
42
25
  end
43
26
  end
44
27
 
@@ -12,44 +12,19 @@ module Coradoc
12
12
  # the unified Liquid template pipeline.
13
13
  class Static < ConverterBase
14
14
  class Configuration < ConverterBase::ConfigurationBase
15
- attr_accessor :include_toc, :toc_levels,
16
- :section_numbering, :section_numbering_levels,
17
- :lang, :meta_tags, :custom_css, :embedded,
18
- :template_dirs
19
-
20
- def initialize(**options)
21
- @include_toc = options.fetch(:include_toc, false)
22
- @toc_levels = options[:toc_levels] || 2
23
- @section_numbering = options.fetch(:section_numbering, false)
24
- @section_numbering_levels = options[:section_numbering_levels] || 3
25
- @lang = options[:lang] || 'en'
26
- @meta_tags = options[:meta_tags] || {}
27
- @custom_css = options[:custom_css]
28
- @embedded = options.fetch(:embedded, false)
29
- @template_dirs = options[:template_dirs]
30
- end
31
-
32
- def to_h
33
- {
34
- include_toc: @include_toc, toc_levels: @toc_levels,
35
- section_numbering: @section_numbering,
36
- section_numbering_levels: @section_numbering_levels,
37
- lang: @lang, meta_tags: @meta_tags, custom_css: @custom_css,
38
- embedded: @embedded, template_dirs: @template_dirs
39
- }
40
- end
15
+ attribute :include_toc, default: false
16
+ attribute :toc_levels, default: 2
17
+ attribute :section_numbering, default: false
18
+ attribute :section_numbering_levels, default: 3
19
+ attribute :lang, default: 'en'
20
+ attribute :meta_tags, default: {}
21
+ attribute :custom_css
22
+ attribute :embedded, default: false
23
+ attribute :template_dirs
41
24
 
42
25
  def validate!
43
- unless @toc_levels.is_a?(Integer) && @toc_levels.between?(1, 5)
44
- raise ConverterBase::ValidationError,
45
- 'TOC levels must be an integer between 1 and 5'
46
- end
47
-
48
- unless @section_numbering_levels.is_a?(Integer) &&
49
- @section_numbering_levels.between?(1, 6)
50
- raise ConverterBase::ValidationError,
51
- 'Section numbering levels must be an integer between 1 and 6'
52
- end
26
+ range_check(:toc_levels, 1, 5, label: 'TOC levels')
27
+ range_check(:section_numbering_levels, 1, 6, label: 'Section numbering levels')
53
28
  end
54
29
  end
55
30