coradoc-html 1.1.14 → 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.
- checksums.yaml +4 -4
- data/lib/coradoc/html/asset_resolver.rb +193 -0
- data/lib/coradoc/html/config.rb +18 -358
- data/lib/coradoc/html/converter_base.rb +41 -2
- data/lib/coradoc/html/drop/block_drop.rb +2 -13
- data/lib/coradoc/html/drop/inline_element_drop.rb +1 -20
- data/lib/coradoc/html/drop/list_block_drop.rb +1 -5
- data/lib/coradoc/html/drop/table_cell_drop.rb +1 -1
- data/lib/coradoc/html/layout_renderer.rb +38 -36
- data/lib/coradoc/html/render_options.rb +40 -0
- data/lib/coradoc/html/renderer.rb +12 -11
- data/lib/coradoc/html/spa.rb +8 -25
- data/lib/coradoc/html/static.rb +11 -36
- data/lib/coradoc/html/tag_mapping.rb +104 -0
- data/lib/coradoc/html/toc_builder.rb +5 -4
- data/lib/coradoc/html/toc_serializer.rb +4 -3
- data/lib/coradoc/html/transform/from_core_model.rb +2 -0
- data/lib/coradoc/html/transform/to_core_model.rb +2 -0
- data/lib/coradoc/html/version.rb +1 -1
- data/lib/coradoc/html.rb +3 -0
- metadata +4 -1
data/lib/coradoc/html/config.rb
CHANGED
|
@@ -1,116 +1,73 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'nokogiri'
|
|
4
|
-
|
|
5
3
|
module Coradoc
|
|
6
4
|
module Html
|
|
7
5
|
module Config
|
|
8
6
|
DEFAULT_LANG = 'en'
|
|
9
7
|
DEFAULT_TITLE = 'Untitled'
|
|
10
8
|
|
|
11
|
-
# Default HTML output options
|
|
12
9
|
DEFAULT_OPTIONS = {
|
|
13
|
-
# Theme system options
|
|
14
10
|
theme: :classic,
|
|
15
11
|
modern: {
|
|
16
|
-
# Appearance
|
|
17
12
|
color_scheme: :glass,
|
|
18
13
|
primary_color: '#6366f1',
|
|
19
14
|
accent_color: '#8b5cf6',
|
|
20
|
-
|
|
21
|
-
# Layout
|
|
22
15
|
max_width: '1200px',
|
|
23
16
|
content_width: '65ch',
|
|
24
17
|
sidebar_width: '280px',
|
|
25
|
-
|
|
26
|
-
# Features
|
|
27
18
|
theme_toggle: true,
|
|
28
19
|
reading_progress: true,
|
|
29
20
|
back_to_top: true,
|
|
30
21
|
toc_sticky: true,
|
|
31
22
|
copy_code_buttons: true,
|
|
32
|
-
|
|
33
|
-
# Animation
|
|
34
23
|
enable_animations: true,
|
|
35
24
|
animation_duration: '300ms',
|
|
36
|
-
|
|
37
|
-
# Performance
|
|
38
25
|
lazy_load_images: true
|
|
39
26
|
}.freeze,
|
|
40
|
-
|
|
41
|
-
# HTML version
|
|
42
27
|
html_version: :html5,
|
|
43
|
-
|
|
44
|
-
# Formatting options
|
|
45
28
|
pretty_print: false,
|
|
46
29
|
indent: ' ',
|
|
47
30
|
line_wrap: 0,
|
|
48
|
-
|
|
49
|
-
# Content options
|
|
50
31
|
escape_content: true,
|
|
51
32
|
preserve_whitespace: false,
|
|
52
33
|
convert_line_breaks: true,
|
|
53
34
|
preserve_comments: false,
|
|
54
|
-
|
|
55
|
-
# Element options
|
|
56
35
|
use_semantic_elements: true,
|
|
57
36
|
add_css_classes: true,
|
|
58
37
|
add_data_attributes: false,
|
|
59
|
-
|
|
60
|
-
# Link options
|
|
61
38
|
external_link_target: nil,
|
|
62
39
|
link_rel: nil,
|
|
63
|
-
|
|
64
|
-
# Image options
|
|
65
40
|
image_loading: nil,
|
|
66
41
|
image_decoding: nil,
|
|
67
|
-
|
|
68
|
-
# Code block options
|
|
69
42
|
syntax_highlighter: nil,
|
|
70
43
|
syntax_highlighter_opts: {},
|
|
71
|
-
|
|
72
|
-
# Table options
|
|
73
44
|
table_border: false,
|
|
74
45
|
table_stripes: false,
|
|
75
|
-
|
|
76
|
-
# Attribute options
|
|
77
46
|
preserve_custom_attributes: true,
|
|
78
47
|
attribute_prefix: 'data-',
|
|
79
|
-
|
|
80
|
-
# CSS & Styling options
|
|
81
48
|
stylesheet: 'coradoc.css',
|
|
82
49
|
stylesdir: './css',
|
|
83
50
|
linkcss: false,
|
|
84
51
|
copycss: true,
|
|
85
52
|
css_theme: 'professional',
|
|
86
53
|
custom_css: nil,
|
|
87
|
-
|
|
88
|
-
# JavaScript options
|
|
89
54
|
javascript: 'coradoc.js',
|
|
90
55
|
jsdir: './js',
|
|
91
56
|
linkjs: false,
|
|
92
57
|
theme_toggle: true,
|
|
93
58
|
toc_interactive: nil,
|
|
94
|
-
|
|
95
|
-
# Document metadata options
|
|
96
59
|
author: nil,
|
|
97
60
|
description: nil,
|
|
98
61
|
keywords: nil,
|
|
99
62
|
lang: 'en',
|
|
100
63
|
embedded: false,
|
|
101
64
|
meta_tags: {},
|
|
102
|
-
|
|
103
|
-
# Table of contents options
|
|
104
65
|
toc: false,
|
|
105
66
|
toclevels: 2,
|
|
106
67
|
toc_title: 'Table of Contents',
|
|
107
68
|
toc_placement: :auto,
|
|
108
|
-
|
|
109
|
-
# Section numbering options
|
|
110
69
|
sectnums: false,
|
|
111
70
|
sectnumlevels: 3,
|
|
112
|
-
|
|
113
|
-
# Syntax highlighting options
|
|
114
71
|
source_highlighter: nil,
|
|
115
72
|
highlightjs_theme: 'github',
|
|
116
73
|
pygments_style: 'default',
|
|
@@ -118,17 +75,14 @@ module Coradoc
|
|
|
118
75
|
}.freeze
|
|
119
76
|
|
|
120
77
|
class << self
|
|
121
|
-
# Get default options
|
|
122
78
|
def default_options
|
|
123
79
|
DEFAULT_OPTIONS.dup
|
|
124
80
|
end
|
|
125
81
|
|
|
126
|
-
# Merge user options with defaults
|
|
127
82
|
def merge_options(user_options = {})
|
|
128
83
|
default_options.merge(user_options)
|
|
129
84
|
end
|
|
130
85
|
|
|
131
|
-
# Validate options
|
|
132
86
|
def validate_options(options)
|
|
133
87
|
valid_keys = DEFAULT_OPTIONS.keys
|
|
134
88
|
invalid_keys = options.keys - valid_keys
|
|
@@ -138,19 +92,16 @@ module Coradoc
|
|
|
138
92
|
options
|
|
139
93
|
end
|
|
140
94
|
|
|
141
|
-
# Get CSS class for element type
|
|
142
95
|
def css_class_for(element_type, role = nil)
|
|
143
96
|
classes = [element_type.to_s.tr('_', '-')]
|
|
144
97
|
classes << role if role
|
|
145
98
|
classes.join(' ')
|
|
146
99
|
end
|
|
147
100
|
|
|
148
|
-
# Get data attribute name
|
|
149
101
|
def data_attribute_name(name, prefix: 'data-')
|
|
150
102
|
"#{prefix}#{name.to_s.tr('_', '-')}"
|
|
151
103
|
end
|
|
152
104
|
|
|
153
|
-
# Build element configuration
|
|
154
105
|
def element_config(element_type, options = {})
|
|
155
106
|
{
|
|
156
107
|
tag: html_tag_for(element_type),
|
|
@@ -159,333 +110,42 @@ module Coradoc
|
|
|
159
110
|
}
|
|
160
111
|
end
|
|
161
112
|
|
|
162
|
-
# Map element type to HTML tag
|
|
163
113
|
def html_tag_for(element_type)
|
|
164
|
-
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Get stylesheet path
|
|
168
|
-
def stylesheet_path(options = {})
|
|
169
|
-
# When linking, use css_theme-based filename, not the stylesheet option
|
|
170
|
-
css_theme = options[:css_theme] || DEFAULT_OPTIONS[:css_theme]
|
|
171
|
-
stylesheet = "#{css_theme}.css"
|
|
172
|
-
stylesdir = options[:stylesdir] || DEFAULT_OPTIONS[:stylesdir]
|
|
173
|
-
|
|
174
|
-
if stylesdir && stylesdir != '.'
|
|
175
|
-
File.join(stylesdir, stylesheet)
|
|
176
|
-
else
|
|
177
|
-
stylesheet
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
# Get embedded stylesheet content
|
|
182
|
-
def embedded_stylesheet(options = {})
|
|
183
|
-
css_theme = options[:css_theme] || DEFAULT_OPTIONS[:css_theme]
|
|
184
|
-
stylesheet_name = "#{css_theme}.css"
|
|
185
|
-
|
|
186
|
-
# Try themes directory first
|
|
187
|
-
themes_path = File.join(__dir__, 'assets', 'themes', stylesheet_name)
|
|
188
|
-
asset_path = if File.exist?(themes_path)
|
|
189
|
-
themes_path
|
|
190
|
-
else
|
|
191
|
-
# Fall back to assets directory for backward compatibility
|
|
192
|
-
File.join(__dir__, 'assets', stylesheet_name)
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
css_content = if File.exist?(asset_path)
|
|
196
|
-
File.read(asset_path)
|
|
197
|
-
else
|
|
198
|
-
# Fallback to default coradoc.css
|
|
199
|
-
default_path = File.join(__dir__, 'assets', 'coradoc.css')
|
|
200
|
-
File.exist?(default_path) ? File.read(default_path) : ''
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
# Resolve @import statements for embedded CSS
|
|
204
|
-
# @import doesn't work in inline <style> tags
|
|
205
|
-
resolve_css_imports(css_content, File.dirname(asset_path))
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
# Resolve @import statements in CSS content
|
|
209
|
-
# @param css_content [String] CSS content with potential @import statements
|
|
210
|
-
# @param base_dir [String] Base directory for resolving relative imports
|
|
211
|
-
# @return [String] CSS content with imports resolved
|
|
212
|
-
def resolve_css_imports(css_content, base_dir)
|
|
213
|
-
# Match @import url('...') or @import url("...") or @import '...' or @import "..."
|
|
214
|
-
css_content.gsub(/@import\s+(?:url\()?['"]([^'"]+)['"]\)?;?/) do
|
|
215
|
-
import_path = ::Regexp.last_match(1)
|
|
216
|
-
full_path = File.join(base_dir, import_path)
|
|
217
|
-
|
|
218
|
-
if File.exist?(full_path)
|
|
219
|
-
# Read the imported file and recursively resolve its imports
|
|
220
|
-
imported_content = File.read(full_path)
|
|
221
|
-
resolve_css_imports(imported_content, File.dirname(full_path))
|
|
222
|
-
else
|
|
223
|
-
# Keep the original import if file not found
|
|
224
|
-
::Regexp.last_match(0)
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
# Build CSS link tag
|
|
230
|
-
def css_link_tag(options = {})
|
|
231
|
-
href = stylesheet_path(options)
|
|
232
|
-
doc = Nokogiri::HTML::Document.new
|
|
233
|
-
node = Nokogiri::XML::Node.new('link', doc)
|
|
234
|
-
node['rel'] = 'stylesheet'
|
|
235
|
-
node['href'] = href
|
|
236
|
-
node.to_html
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
# Build CSS style tag with embedded content
|
|
240
|
-
def css_style_tag(options = {})
|
|
241
|
-
css_content = embedded_stylesheet(options)
|
|
242
|
-
custom_css = options[:custom_css]
|
|
243
|
-
|
|
244
|
-
content = css_content
|
|
245
|
-
content += "\n\n#{custom_css}" if custom_css && !custom_css.empty?
|
|
246
|
-
|
|
247
|
-
build_text_element('style', content)
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
# Build custom CSS style tag
|
|
251
|
-
def custom_css_tag(custom_css)
|
|
252
|
-
return '' unless custom_css && !custom_css.empty?
|
|
253
|
-
|
|
254
|
-
build_text_element('style', custom_css)
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
# Determine whether to embed or link CSS
|
|
258
|
-
def embed_css?(options = {})
|
|
259
|
-
# Embed if linkcss is false or embedded mode is true
|
|
260
|
-
!options.fetch(:linkcss, DEFAULT_OPTIONS[:linkcss]) ||
|
|
261
|
-
options.fetch(:embedded, DEFAULT_OPTIONS[:embedded])
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
# Build complete CSS tags (link or embedded, plus custom)
|
|
265
|
-
def css_tags(options = {})
|
|
266
|
-
tags = []
|
|
267
|
-
|
|
268
|
-
if embed_css?(options)
|
|
269
|
-
# Embedded mode: include full stylesheet in style tag
|
|
270
|
-
tags << css_style_tag(options)
|
|
271
|
-
else
|
|
272
|
-
# Linked mode: link to external stylesheet
|
|
273
|
-
tags << css_link_tag(options)
|
|
274
|
-
# Add custom CSS separately if provided
|
|
275
|
-
tags << custom_css_tag(options[:custom_css]) if options[:custom_css]
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
tags.join("\n")
|
|
114
|
+
TagMapping.tag_for(element_type)
|
|
279
115
|
end
|
|
280
116
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
!options.fetch(:linkjs, DEFAULT_OPTIONS[:linkjs]) ||
|
|
285
|
-
options.fetch(:embedded, DEFAULT_OPTIONS[:embedded]) ||
|
|
286
|
-
!options.fetch(:linkcss, DEFAULT_OPTIONS[:linkcss])
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
# Get JavaScript file path
|
|
290
|
-
def javascript_path(options = {})
|
|
291
|
-
javascript = options[:javascript] || DEFAULT_OPTIONS[:javascript]
|
|
292
|
-
jsdir = options[:jsdir] || DEFAULT_OPTIONS[:jsdir]
|
|
293
|
-
|
|
294
|
-
if jsdir && jsdir != '.'
|
|
295
|
-
File.join(jsdir, javascript)
|
|
296
|
-
else
|
|
297
|
-
javascript
|
|
298
|
-
end
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
# Get embedded JavaScript content
|
|
302
|
-
def embedded_javascript(options = {})
|
|
303
|
-
javascript_name = options[:javascript] || DEFAULT_OPTIONS[:javascript]
|
|
304
|
-
asset_path = File.join(__dir__, 'assets', 'js', javascript_name)
|
|
305
|
-
|
|
306
|
-
if File.exist?(asset_path)
|
|
307
|
-
File.read(asset_path)
|
|
308
|
-
else
|
|
309
|
-
''
|
|
310
|
-
end
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
# Build JavaScript link tag
|
|
314
|
-
def js_link_tag(options = {})
|
|
315
|
-
src = javascript_path(options)
|
|
316
|
-
doc = Nokogiri::HTML::Document.new
|
|
317
|
-
node = Nokogiri::XML::Node.new('script', doc)
|
|
318
|
-
node['src'] = src
|
|
319
|
-
node['defer'] = ''
|
|
320
|
-
node.to_html
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
# Build JavaScript script tag with embedded content
|
|
324
|
-
def js_script_tag(options = {})
|
|
325
|
-
js_content = embedded_javascript(options)
|
|
326
|
-
return '' if js_content.empty?
|
|
327
|
-
|
|
328
|
-
build_text_element('script', js_content)
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
# Build complete JavaScript tags (link or embedded)
|
|
332
|
-
def js_tags(options = {})
|
|
333
|
-
return '' if options[:javascript] == false
|
|
334
|
-
|
|
335
|
-
tags = []
|
|
117
|
+
def code_block_attributes(language, options = {})
|
|
118
|
+
attrs = {}
|
|
119
|
+
attrs[:class] = "language-#{language}" if language && !language.empty?
|
|
336
120
|
|
|
337
|
-
|
|
338
|
-
# Embedded mode: include full JavaScript in script tag
|
|
339
|
-
js_script_tag(options)
|
|
340
|
-
else
|
|
341
|
-
# Linked mode: link to external JavaScript file
|
|
342
|
-
js_link_tag(options)
|
|
343
|
-
end
|
|
121
|
+
attrs[:class] = [attrs[:class], 'line-numbers'].compact.join(' ') if options[:linenums] || options[:line_numbers]
|
|
344
122
|
|
|
345
|
-
|
|
123
|
+
attrs
|
|
346
124
|
end
|
|
347
125
|
|
|
348
|
-
# Check if theme toggle should be enabled
|
|
349
126
|
def theme_toggle?(options = {})
|
|
350
127
|
options.fetch(:theme_toggle, DEFAULT_OPTIONS[:theme_toggle])
|
|
351
128
|
end
|
|
352
129
|
|
|
353
|
-
# Check if interactive TOC should be enabled
|
|
354
130
|
def toc_interactive?(options = {})
|
|
355
|
-
# Default to true if TOC is enabled and toc_interactive is not explicitly set to false
|
|
356
131
|
toc_enabled = options.fetch(:toc, DEFAULT_OPTIONS[:toc])
|
|
357
132
|
toc_interactive = options[:toc_interactive]
|
|
358
|
-
|
|
359
|
-
# If toc_interactive is nil, default to true when TOC is enabled
|
|
360
|
-
if toc_interactive.nil?
|
|
361
|
-
toc_enabled
|
|
362
|
-
else
|
|
363
|
-
toc_interactive
|
|
364
|
-
end
|
|
365
|
-
end
|
|
366
|
-
|
|
367
|
-
# Build syntax highlighter tags (CSS and JS)
|
|
368
|
-
# @param options [Hash] Configuration options
|
|
369
|
-
# @return [String] HTML tags for syntax highlighting
|
|
370
|
-
def syntax_highlighter_tags(options = {})
|
|
371
|
-
highlighter = options[:source_highlighter]
|
|
372
|
-
return '' unless highlighter
|
|
373
|
-
|
|
374
|
-
case highlighter.to_sym
|
|
375
|
-
when :highlightjs, :highlight_js, :'highlight.js'
|
|
376
|
-
highlightjs_tags(options)
|
|
377
|
-
when :pygments
|
|
378
|
-
# Pygments requires server-side processing, not implemented for client-side HTML
|
|
379
|
-
''
|
|
380
|
-
when :rouge
|
|
381
|
-
# Rouge requires server-side processing, not implemented for client-side HTML
|
|
382
|
-
''
|
|
383
|
-
else
|
|
384
|
-
''
|
|
385
|
-
end
|
|
386
|
-
end
|
|
387
|
-
|
|
388
|
-
# Build Highlight.js tags
|
|
389
|
-
# @param options [Hash] Configuration options
|
|
390
|
-
# @return [String] HTML tags for Highlight.js
|
|
391
|
-
def highlightjs_tags(options = {})
|
|
392
|
-
theme = options[:highlightjs_theme] || DEFAULT_OPTIONS[:highlightjs_theme]
|
|
393
|
-
doc = Nokogiri::HTML::Document.new
|
|
394
|
-
|
|
395
|
-
link_node = Nokogiri::XML::Node.new('link', doc)
|
|
396
|
-
link_node['rel'] = 'stylesheet'
|
|
397
|
-
link_node['href'] = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/#{theme}.min.css"
|
|
398
|
-
|
|
399
|
-
script_node = Nokogiri::XML::Node.new('script', doc)
|
|
400
|
-
script_node['src'] = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js'
|
|
401
|
-
|
|
402
|
-
init_node = Nokogiri::XML::Node.new('script', doc)
|
|
403
|
-
init_node.content = 'hljs.highlightAll();'
|
|
404
|
-
|
|
405
|
-
[link_node.to_html, script_node.to_html, init_node.to_html].join("\n")
|
|
133
|
+
toc_interactive.nil? ? toc_enabled : toc_interactive
|
|
406
134
|
end
|
|
135
|
+
end
|
|
407
136
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
attrs[:class] = "language-#{language}" if language && !language.empty?
|
|
416
|
-
|
|
417
|
-
if options[:linenums] || options[:line_numbers]
|
|
418
|
-
attrs[:class] =
|
|
419
|
-
[attrs[:class], 'line-numbers'].compact.join(' ')
|
|
420
|
-
end
|
|
421
|
-
|
|
422
|
-
attrs
|
|
423
|
-
end
|
|
424
|
-
|
|
425
|
-
# Mapping of Coradoc elements to HTML tags
|
|
426
|
-
TAG_MAPPING = {
|
|
427
|
-
# Sections
|
|
428
|
-
section: 'section',
|
|
429
|
-
header: 'header',
|
|
430
|
-
|
|
431
|
-
# Blocks
|
|
432
|
-
paragraph: 'p',
|
|
433
|
-
example: 'div',
|
|
434
|
-
sidebar: 'aside',
|
|
435
|
-
quote: 'blockquote',
|
|
436
|
-
verse: 'div',
|
|
437
|
-
listing: 'pre',
|
|
438
|
-
literal: 'pre',
|
|
439
|
-
source: 'pre',
|
|
440
|
-
open: 'div',
|
|
441
|
-
|
|
442
|
-
# Lists
|
|
443
|
-
ordered_list: 'ol',
|
|
444
|
-
unordered_list: 'ul',
|
|
445
|
-
list_item: 'li',
|
|
446
|
-
description_list: 'dl',
|
|
447
|
-
description_term: 'dt',
|
|
448
|
-
description_detail: 'dd',
|
|
449
|
-
|
|
450
|
-
# Tables
|
|
451
|
-
table: 'table',
|
|
452
|
-
table_row: 'tr',
|
|
453
|
-
table_cell: 'td',
|
|
454
|
-
table_header: 'th',
|
|
455
|
-
|
|
456
|
-
# Inline
|
|
457
|
-
bold: 'strong',
|
|
458
|
-
italic: 'em',
|
|
459
|
-
monospace: 'code',
|
|
460
|
-
highlight: 'mark',
|
|
461
|
-
superscript: 'sup',
|
|
462
|
-
subscript: 'sub',
|
|
463
|
-
underline: 'u',
|
|
464
|
-
strikethrough: 'del',
|
|
465
|
-
small_caps: 'span',
|
|
466
|
-
|
|
467
|
-
# Links
|
|
468
|
-
anchor: 'a',
|
|
469
|
-
cross_reference: 'a',
|
|
470
|
-
|
|
471
|
-
# Media
|
|
472
|
-
image: 'img',
|
|
473
|
-
video: 'video',
|
|
474
|
-
audio: 'audio',
|
|
475
|
-
|
|
476
|
-
# Other
|
|
477
|
-
break: 'hr',
|
|
478
|
-
line_break: 'br',
|
|
479
|
-
admonition: 'div'
|
|
480
|
-
}.freeze
|
|
137
|
+
ASSET_METHODS = %i[
|
|
138
|
+
stylesheet_path embedded_stylesheet resolve_css_imports
|
|
139
|
+
css_link_tag css_style_tag custom_css_tag embed_css? css_tags
|
|
140
|
+
javascript_path embedded_javascript
|
|
141
|
+
js_link_tag js_script_tag embed_js? js_tags
|
|
142
|
+
syntax_highlighter_tags highlightjs_tags
|
|
143
|
+
].freeze
|
|
481
144
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
node = Nokogiri::XML::Node.new(tag_name, doc)
|
|
485
|
-
node.content = content
|
|
486
|
-
node.to_html
|
|
487
|
-
end
|
|
145
|
+
ASSET_METHODS.each do |method|
|
|
146
|
+
define_method(method) { |*args, **kwargs, &blk| AssetResolver.public_send(method, *args, **kwargs, &blk) }
|
|
488
147
|
end
|
|
148
|
+
module_function(*ASSET_METHODS)
|
|
489
149
|
end
|
|
490
150
|
end
|
|
491
151
|
end
|
|
@@ -28,14 +28,53 @@ module Coradoc
|
|
|
28
28
|
# Provides shared `merge` and `defaults` patterns.
|
|
29
29
|
# Subclasses define `initialize`, `to_h`, and `validate!`.
|
|
30
30
|
class ConfigurationBase
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
class << self
|
|
32
|
+
# Declare a configuration attribute with an optional default.
|
|
33
|
+
# Replaces manual attr_accessor + initialize + to_h boilerplate.
|
|
34
|
+
def attribute(name, default: nil)
|
|
35
|
+
attr_accessor name
|
|
36
|
+
|
|
37
|
+
configuration_attributes[name] = default
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Registry of declared attributes and their defaults
|
|
41
|
+
def configuration_attributes
|
|
42
|
+
@configuration_attributes ||= {}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def defaults
|
|
46
|
+
new
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def initialize(**options)
|
|
51
|
+
self.class.configuration_attributes.each do |name, default|
|
|
52
|
+
value = options.fetch(name) { default.respond_to?(:call) ? default.call : default }
|
|
53
|
+
public_send(:"#{name}=", value)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_h
|
|
58
|
+
self.class.configuration_attributes.each_with_object({}) do |(name, _), hash|
|
|
59
|
+
hash[name] = public_send(name)
|
|
60
|
+
end
|
|
33
61
|
end
|
|
34
62
|
|
|
35
63
|
def merge(other)
|
|
36
64
|
other_hash = other.is_a?(self.class) ? other.to_h : other.to_h.transform_keys(&:to_sym)
|
|
37
65
|
self.class.new(**to_h, **other_hash)
|
|
38
66
|
end
|
|
67
|
+
|
|
68
|
+
protected
|
|
69
|
+
|
|
70
|
+
def range_check(name, min, max, label: nil)
|
|
71
|
+
value = public_send(name)
|
|
72
|
+
return if value.is_a?(Integer) && value.between?(min, max)
|
|
73
|
+
|
|
74
|
+
display = label || name.to_s.tr('_', ' ').gsub(/\b\w/, &:upcase)
|
|
75
|
+
raise ConverterBase::ValidationError,
|
|
76
|
+
"#{display} must be an integer between #{min} and #{max}"
|
|
77
|
+
end
|
|
39
78
|
end
|
|
40
79
|
|
|
41
80
|
attr_reader :document, :config
|
|
@@ -4,23 +4,12 @@ module Coradoc
|
|
|
4
4
|
module Html
|
|
5
5
|
module Drop
|
|
6
6
|
class BlockDrop < Base
|
|
7
|
-
SEMANTIC_TAG_MAP = {
|
|
8
|
-
paragraph: 'p', source_code: 'pre', quote: 'blockquote',
|
|
9
|
-
verse: 'blockquote', example: 'div', sidebar: 'aside',
|
|
10
|
-
literal: 'pre', listing: 'pre', open: 'div',
|
|
11
|
-
horizontal_rule: 'hr'
|
|
12
|
-
}.freeze
|
|
13
|
-
|
|
14
|
-
SEMANTIC_CLASS_MAP = {
|
|
15
|
-
example: 'example', sidebar: 'sidebar', literal: 'literal'
|
|
16
|
-
}.freeze
|
|
17
|
-
|
|
18
7
|
def semantic_type
|
|
19
8
|
resolved_semantic_type.to_s
|
|
20
9
|
end
|
|
21
10
|
|
|
22
11
|
def html_tag
|
|
23
|
-
|
|
12
|
+
TagMapping.tag_for(resolved_semantic_type)
|
|
24
13
|
end
|
|
25
14
|
|
|
26
15
|
def language
|
|
@@ -28,7 +17,7 @@ module Coradoc
|
|
|
28
17
|
end
|
|
29
18
|
|
|
30
19
|
def css_class
|
|
31
|
-
cls =
|
|
20
|
+
cls = TagMapping.css_class_for(resolved_semantic_type)
|
|
32
21
|
cls ? "block-#{semantic_type} #{cls}" : "block-#{semantic_type}"
|
|
33
22
|
end
|
|
34
23
|
|
|
@@ -4,31 +4,12 @@ module Coradoc
|
|
|
4
4
|
module Html
|
|
5
5
|
module Drop
|
|
6
6
|
class InlineElementDrop < Base
|
|
7
|
-
FORMAT_TAG_MAP = {
|
|
8
|
-
'bold' => 'strong',
|
|
9
|
-
'italic' => 'em',
|
|
10
|
-
'monospace' => 'code',
|
|
11
|
-
'superscript' => 'sup',
|
|
12
|
-
'subscript' => 'sub',
|
|
13
|
-
'underline' => 'u',
|
|
14
|
-
'strikethrough' => 'del',
|
|
15
|
-
'highlight' => 'mark',
|
|
16
|
-
'quotation' => 'q',
|
|
17
|
-
'small' => 'small',
|
|
18
|
-
'stem' => 'code'
|
|
19
|
-
}.freeze
|
|
20
|
-
|
|
21
7
|
def format_type
|
|
22
8
|
@model.resolve_format_type
|
|
23
9
|
end
|
|
24
10
|
|
|
25
11
|
def html_tag
|
|
26
|
-
|
|
27
|
-
when 'link', 'xref' then 'a'
|
|
28
|
-
when 'footnote' then 'sup'
|
|
29
|
-
when 'span', 'term' then 'span'
|
|
30
|
-
else FORMAT_TAG_MAP[format_type]
|
|
31
|
-
end
|
|
12
|
+
TagMapping.tag_for(format_type)
|
|
32
13
|
end
|
|
33
14
|
|
|
34
15
|
def href
|