coradoc-html 1.1.16 → 1.1.17

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 969890f3d4d45ed9363c1844aebe549fe0ac92df4d39af29c7ccdb46fce8048b
4
- data.tar.gz: 9892092a47521897e7b5d008673cd5b25daa324022b34ddc9558c8e462945b7b
3
+ metadata.gz: d5c1e5cd6e38250709afed5441270974421958870420434743ec2dcc2080ef5d
4
+ data.tar.gz: 1b9c0d24cc15f6b535c85c993c0d8498ccaa572bec23be5e7f0bed4cd8661c93
5
5
  SHA512:
6
- metadata.gz: ced9fbc9f7325b093b3d84e463b9a91df96b95c3f1eecbdbb34d2d6a63bcd4002d3f36bfff950242a2d6c7ea7d66e8cbb880d04a7433d63ce81771b4bc9d3b89
7
- data.tar.gz: 9252a74bee993a960700efbc00733fb4bfb2d6500751014d462120aa0009da045ae85de4d943e1f01a78ffbe67b530c8c191ad1e1f4e37fe9e1321bdee25341b
6
+ metadata.gz: 86c7504af66e3a2933bc595eb1d6f6f628cfd07377a290927460ba94b96b44f4b2adf3b52bb26a2915c93527f6414ef0a35809810bfa2b1f8bd208633ea4c257
7
+ data.tar.gz: e0bcaf88a1d89d6b9e23ed71a7b7e8bd4f0a467780051db981e8d833a6e43fa3cf002d96efb5e7e6cdc17f368e0f8e5773f7df4af2d06f40bb85c94fde1c53b0
@@ -49,7 +49,7 @@ module Coradoc
49
49
 
50
50
  def initialize(**options)
51
51
  self.class.configuration_attributes.each do |name, default|
52
- value = options.fetch(name) { default.respond_to?(:call) ? default.call : default }
52
+ value = options.fetch(name) { default.is_a?(Proc) ? default.call : default }
53
53
  public_send(:"#{name}=", value)
54
54
  end
55
55
  end
@@ -26,13 +26,25 @@ module Coradoc
26
26
  end
27
27
 
28
28
  def text
29
- if %i[source_code literal listing].include?(resolved_semantic_type)
30
- Escape.escape_html(@model.flat_text)
29
+ if verbatim?
30
+ Escape.escape_html(stripped_text)
31
31
  elsif resolved_semantic_type == :pass
32
32
  @model.flat_text.to_s
33
33
  end
34
34
  end
35
35
 
36
+ def callouts
37
+ return [] unless verbatim?
38
+
39
+ @callouts ||= CoreModel::CalloutText.ordered(@model.callouts).map do |callout|
40
+ { 'index' => callout.index, 'content' => Escape.escape_html(callout.content.to_s) }
41
+ end
42
+ end
43
+
44
+ def callouts?
45
+ !callouts.empty?
46
+ end
47
+
36
48
  def hidden?
37
49
  %i[comment reviewer].include?(resolved_semantic_type)
38
50
  end
@@ -50,6 +62,14 @@ module Coradoc
50
62
  def resolved_semantic_type
51
63
  @resolved_semantic_type ||= @model.resolve_semantic_type || :paragraph
52
64
  end
65
+
66
+ def verbatim?
67
+ %i[source_code literal listing].include?(resolved_semantic_type)
68
+ end
69
+
70
+ def stripped_text
71
+ CoreModel::CalloutText.strip_markers(@model.flat_text.to_s, @model.callouts)
72
+ end
53
73
  end
54
74
 
55
75
  DropFactory.register(CoreModel::Block, BlockDrop)
@@ -51,22 +51,22 @@ end
51
51
 
52
52
  # Load all drops — each self-registers with DropFactory.
53
53
  # Registration order doesn't matter (sorted by ancestor depth).
54
- require 'coradoc/html/drop/annotation_drop'
55
- require 'coradoc/html/drop/block_drop'
56
- require 'coradoc/html/drop/list_block_drop'
57
- require 'coradoc/html/drop/list_item_drop'
58
- require 'coradoc/html/drop/table_drop'
59
- require 'coradoc/html/drop/table_row_drop'
60
- require 'coradoc/html/drop/table_cell_drop'
61
- require 'coradoc/html/drop/image_drop'
62
- require 'coradoc/html/drop/inline_element_drop'
63
- require 'coradoc/html/drop/bibliography_entry_drop'
64
- require 'coradoc/html/drop/bibliography_drop'
65
- require 'coradoc/html/drop/toc_entry_drop'
66
- require 'coradoc/html/drop/toc_drop'
67
- require 'coradoc/html/drop/definition_item_drop'
68
- require 'coradoc/html/drop/definition_list_drop'
69
- require 'coradoc/html/drop/term_drop'
70
- require 'coradoc/html/drop/footnote_drop'
71
- require 'coradoc/html/drop/text_content_drop'
72
- require 'coradoc/html/drop/document_drop'
54
+ require_relative 'annotation_drop'
55
+ require_relative 'block_drop'
56
+ require_relative 'list_block_drop'
57
+ require_relative 'list_item_drop'
58
+ require_relative 'table_drop'
59
+ require_relative 'table_row_drop'
60
+ require_relative 'table_cell_drop'
61
+ require_relative 'image_drop'
62
+ require_relative 'inline_element_drop'
63
+ require_relative 'bibliography_entry_drop'
64
+ require_relative 'bibliography_drop'
65
+ require_relative 'toc_entry_drop'
66
+ require_relative 'toc_drop'
67
+ require_relative 'definition_item_drop'
68
+ require_relative 'definition_list_drop'
69
+ require_relative 'term_drop'
70
+ require_relative 'footnote_drop'
71
+ require_relative 'text_content_drop'
72
+ require_relative 'document_drop'
@@ -13,6 +13,6 @@ module Coradoc
13
13
  end
14
14
 
15
15
  # Base must load first (DropFactory depends on it)
16
- require 'coradoc/html/drop/base'
16
+ require_relative 'drop/base'
17
17
  # DropFactory loads next
18
- require 'coradoc/html/drop/drop_factory'
18
+ require_relative 'drop/drop_factory'
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+
5
+ module Coradoc
6
+ module Html
7
+ # Single source of truth for FrontmatterBlock -> HTML `<meta>` and
8
+ # `<link>` tag mapping. (MECE: HTML-specific concerns live in the
9
+ # HTML gem; CoreModel has no knowledge of HTML.)
10
+ #
11
+ # The module produces a small data structure that layout templates
12
+ # and Nokogiri::HTML::Builder fallbacks both consume, so we never
13
+ # duplicate the mapping rule across output paths (DRY).
14
+ #
15
+ # Mapping table (single source of truth — extend here only):
16
+ #
17
+ # | Frontmatter key | Output |
18
+ # |-----------------------|-----------------------------------------|
19
+ # | title | <title> (caller decides title priority) |
20
+ # | author | <meta name="author"> |
21
+ # | description, excerpt | <meta name="description"> |
22
+ # | date | <meta name="date"> (ISO 8601) |
23
+ # | subject | <meta name="subject"> |
24
+ # | tags, categories | <meta name="keywords"> (comma-joined) |
25
+ # | $schema | <link rel="schema.X" href="..."> |
26
+ # | (any other scalar) | <meta name="<key>" content="<value>"> |
27
+ module FrontmatterMeta
28
+ Meta = Struct.new(:name, :content, keyword_init: true)
29
+ LinkTag = Struct.new(:rel, :href, keyword_init: true)
30
+
31
+ DESCRIPTION_KEYS = %w[description excerpt].freeze
32
+ KEYWORDS_KEYS = %w[tags categories].freeze
33
+
34
+ class << self
35
+ # Extract a meta-tag list and link-tag list from a
36
+ # FrontmatterBlock. `title` is returned separately so the caller
37
+ # can decide precedence (e.g., document title vs. frontmatter).
38
+ #
39
+ # @param block [Coradoc::CoreModel::FrontmatterBlock, nil]
40
+ # @return [Hash{Symbol=>Array<Meta>,Array<LinkTag>,String,nil}]
41
+ # { metas:, links:, title: }
42
+ def extract(block)
43
+ return empty_result unless block.is_a?(Coradoc::CoreModel::FrontmatterBlock)
44
+
45
+ data = block.data || {}
46
+ {
47
+ metas: build_metas(data),
48
+ links: build_links(block.schema),
49
+ title: scalar_value(data['title'])
50
+ }
51
+ end
52
+
53
+ # Emit meta + link tags into a Nokogiri head builder context.
54
+ def emit_into_builder(builder_doc, block)
55
+ data = extract(block)
56
+ Array(data[:metas]).each do |meta|
57
+ builder_doc.meta(name: meta.name, content: meta.content)
58
+ end
59
+ Array(data[:links]).each do |link|
60
+ builder_doc.link(rel: link.rel, href: link.href)
61
+ end
62
+ data
63
+ end
64
+
65
+ private
66
+
67
+ def empty_result
68
+ { metas: [], links: [], title: nil }
69
+ end
70
+
71
+ def build_metas(data)
72
+ metas = []
73
+ consumed = []
74
+
75
+ if (v = find_first_scalar(data, DESCRIPTION_KEYS))
76
+ metas << Meta.new(name: 'description', content: v)
77
+ consumed += DESCRIPTION_KEYS
78
+ end
79
+ if (v = find_first_array_as_csv(data, KEYWORDS_KEYS))
80
+ metas << Meta.new(name: 'keywords', content: v)
81
+ consumed += KEYWORDS_KEYS
82
+ end
83
+ if (v = find_first_scalar(data, %w[date]))
84
+ metas << Meta.new(name: 'date', content: v)
85
+ consumed << 'date'
86
+ end
87
+ if (v = find_first_scalar(data, %w[author]))
88
+ metas << Meta.new(name: 'author', content: v)
89
+ consumed << 'author'
90
+ end
91
+ if (v = find_first_scalar(data, %w[subject]))
92
+ metas << Meta.new(name: 'subject', content: v)
93
+ consumed << 'subject'
94
+ end
95
+
96
+ data.each do |key, value|
97
+ next if consumed.include?(key) || key == 'title'
98
+
99
+ content = scalar_value(value)
100
+ next if content.nil? || content.empty?
101
+
102
+ metas << Meta.new(name: key, content: content)
103
+ end
104
+
105
+ metas
106
+ end
107
+
108
+ def build_links(schema)
109
+ return [] unless schema && !schema.to_s.strip.empty?
110
+
111
+ [LinkTag.new(rel: 'schema.dublin_core', href: schema.to_s)]
112
+ end
113
+
114
+ def find_first_scalar(data, keys)
115
+ keys.each do |k|
116
+ value = data[k]
117
+ next if value.nil?
118
+
119
+ str = scalar_value(value)
120
+ return str if str && !str.empty?
121
+ end
122
+ nil
123
+ end
124
+
125
+ def find_first_array_as_csv(data, keys)
126
+ keys.each do |k|
127
+ value = data[k]
128
+ next if value.nil?
129
+
130
+ csv = array_to_csv(value)
131
+ return csv if csv && !csv.empty?
132
+ end
133
+ nil
134
+ end
135
+
136
+ def scalar_value(value)
137
+ return nil if value.nil?
138
+
139
+ case value
140
+ when String then value
141
+ when Integer, Float, TrueClass, FalseClass then value.to_s
142
+ when Date, Time, DateTime then value.iso8601
143
+ when Symbol then value.to_s
144
+ when Array then array_to_csv(value)
145
+ when Hash then nil
146
+ else value.to_s
147
+ end
148
+ end
149
+
150
+ def array_to_csv(value)
151
+ return nil unless value.is_a?(Array)
152
+
153
+ strings = value.map { |i| scalar_value(i) }.compact
154
+ strings.any? ? strings.join(', ') : nil
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'digest'
4
4
  require 'nokogiri'
5
- require 'coradoc/input'
5
+ require 'coradoc'
6
6
 
7
7
  module Coradoc
8
8
  module Input
@@ -53,7 +53,28 @@ module Coradoc
53
53
  opts.lang || Config::DEFAULT_LANG
54
54
  end
55
55
 
56
+ # Pull a leading FrontmatterBlock out of the document's children,
57
+ # if present. Returns nil when there isn't one.
58
+ def extract_frontmatter(document)
59
+ return nil unless document.is_a?(Coradoc::CoreModel::DocumentElement)
60
+
61
+ Array(document.children).find { |c| c.is_a?(Coradoc::CoreModel::FrontmatterBlock) }
62
+ end
63
+
64
+ # Filter meta_tags so caller-supplied opts (author/description)
65
+ # take precedence and we never emit two <meta> tags with the same
66
+ # name. Single source of truth for the dedup rule (DRY): both the
67
+ # static and SPA layout paths route through here.
68
+ def dedup_meta_tags(metas, opts)
69
+ overridden = %w[author description].each_with_object([]) do |name, acc|
70
+ acc << name if opts.public_send(name)
71
+ end
72
+ metas.reject { |m| overridden.include?(m.name) }
73
+ end
74
+
56
75
  def build_static_layout_data(document, body_html, opts)
76
+ frontmatter = extract_frontmatter(document)
77
+ fm_metas, fm_links = FrontmatterMeta.extract(frontmatter).values_at(:metas, :links)
57
78
  {
58
79
  'lang' => resolve_lang(opts),
59
80
  'title' => resolve_escaped_title(document),
@@ -61,17 +82,21 @@ module Coradoc
61
82
  'description' => opts.description,
62
83
  'generator_version' => Coradoc::VERSION.to_s,
63
84
  'body' => body_html,
64
- 'custom_css' => opts.custom_css
85
+ 'custom_css' => opts.custom_css,
86
+ 'meta_tags' => dedup_meta_tags(fm_metas, opts).map { |m| { 'name' => m.name, 'content' => m.content } },
87
+ 'link_tags' => fm_links.map { |l| { 'rel' => l.rel, 'href' => l.href } }
65
88
  }
66
89
  end
67
90
 
68
91
  def build_static_fallback(document, body_html, opts)
92
+ frontmatter = extract_frontmatter(document)
69
93
  Nokogiri::HTML::Builder.new do |doc|
70
94
  doc.html(lang: resolve_lang(opts)) do
71
95
  doc.head do
72
96
  doc.meta(charset: 'UTF-8')
73
97
  doc.meta(name: 'viewport', content: 'width=device-width, initial-scale=1.0')
74
98
  doc.title resolve_title(document)
99
+ FrontmatterMeta.emit_into_builder(doc, frontmatter)
75
100
  end
76
101
  doc.body { doc << body_html }
77
102
  end
@@ -109,6 +134,8 @@ module Coradoc
109
134
  end
110
135
 
111
136
  def build_spa_layout_data(document, opts, assets, safe_json)
137
+ frontmatter = extract_frontmatter(document)
138
+ fm_metas, fm_links = FrontmatterMeta.extract(frontmatter).values_at(:metas, :links)
112
139
  {
113
140
  'lang' => resolve_lang(opts),
114
141
  'title' => resolve_escaped_title(document),
@@ -117,11 +144,14 @@ module Coradoc
117
144
  'generator_version' => Coradoc::VERSION.to_s,
118
145
  'css' => assets[:css],
119
146
  'js' => assets[:js],
120
- 'data' => safe_json
147
+ 'data' => safe_json,
148
+ 'meta_tags' => dedup_meta_tags(fm_metas, opts).map { |m| { 'name' => m.name, 'content' => m.content } },
149
+ 'link_tags' => fm_links.map { |l| { 'rel' => l.rel, 'href' => l.href } }
121
150
  }
122
151
  end
123
152
 
124
153
  def build_spa_fallback(document, opts, assets, safe_json)
154
+ frontmatter = extract_frontmatter(document)
125
155
  Nokogiri::HTML::Builder.new do |doc|
126
156
  doc.html(lang: resolve_lang(opts)) do
127
157
  doc.head do
@@ -129,6 +159,7 @@ module Coradoc
129
159
  doc.meta(name: 'viewport', content: 'width=device-width, initial-scale=1.0')
130
160
  doc.meta(name: 'generator', content: "Coradoc #{Coradoc::VERSION}")
131
161
  doc.title resolve_title(document)
162
+ FrontmatterMeta.emit_into_builder(doc, frontmatter)
132
163
  doc.style { doc.text assets[:css] } if assets[:css]
133
164
  end
134
165
  doc.body do
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'coradoc/output'
3
+ require 'coradoc'
4
4
 
5
5
  module Coradoc
6
6
  module Output
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'coradoc/html/renderer'
3
+ require_relative 'renderer'
4
4
 
5
5
  module Coradoc
6
6
  module Html
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'coradoc/html/renderer'
3
+ require_relative 'renderer'
4
4
 
5
5
  module Coradoc
6
6
  module Html
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'coradoc/core_model'
3
+ require 'coradoc'
4
4
 
5
5
  module Coradoc
6
6
  module Html
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'nokogiri'
4
- require 'coradoc/core_model'
4
+ require 'coradoc'
5
5
 
6
6
  module Coradoc
7
7
  module Html
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Coradoc
4
4
  module Html
5
- VERSION = '1.1.16'
5
+ VERSION = '1.1.17'
6
6
  end
7
7
  end
data/lib/coradoc/html.rb CHANGED
@@ -13,9 +13,9 @@ module Coradoc
13
13
  end
14
14
 
15
15
  # Load HTML input module to register with Coradoc::Input
16
- require 'coradoc/html/input'
16
+ require_relative 'html/input'
17
17
  # Load HTML output module to register with Coradoc::Output
18
- require 'coradoc/html/output'
18
+ require_relative 'html/output'
19
19
 
20
20
  module Coradoc
21
21
  module Html
@@ -29,7 +29,10 @@ module Coradoc
29
29
  autoload :TitleText, 'coradoc/html/title_text'
30
30
 
31
31
  # Drop layer — self-registering drops loaded via parent namespace file
32
- require 'coradoc/html/drop'
32
+ require_relative 'html/drop'
33
+
34
+ # FrontmatterBlock -> <meta> tag mapping (HTML-specific concern)
35
+ autoload :FrontmatterMeta, 'coradoc/html/frontmatter_meta'
33
36
 
34
37
  # Autoload HTML output converters
35
38
  autoload :ConverterBase, 'coradoc/html/converter_base'
@@ -42,7 +45,7 @@ module Coradoc
42
45
  autoload :TemplateLocator, 'coradoc/html/template_locator'
43
46
  autoload :TemplateConfig, 'coradoc/html/template_config'
44
47
  # Side-effect: registers Liquid filters on load
45
- require 'coradoc/html/template_helpers'
48
+ require_relative 'html/template_helpers'
46
49
  autoload :Renderer, 'coradoc/html/renderer'
47
50
  autoload :LayoutRenderer, 'coradoc/html/layout_renderer'
48
51
  autoload :RenderOptions, 'coradoc/html/render_options'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coradoc-html
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.16
4
+ version: 1.1.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -143,6 +143,7 @@ files:
143
143
  - lib/coradoc/html/drop/toc_drop.rb
144
144
  - lib/coradoc/html/drop/toc_entry_drop.rb
145
145
  - lib/coradoc/html/escape.rb
146
+ - lib/coradoc/html/frontmatter_meta.rb
146
147
  - lib/coradoc/html/input.rb
147
148
  - lib/coradoc/html/input/cleaner.rb
148
149
  - lib/coradoc/html/input/config.rb