draftjs_html 0.8.0 → 0.9.0
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/draftjs_html/draftjs/block.rb +2 -1
- data/lib/draftjs_html/html_defaults.rb +50 -0
- data/lib/draftjs_html/html_depth.rb +65 -0
- data/lib/draftjs_html/node.rb +1 -1
- data/lib/draftjs_html/overrideable_map.rb +25 -0
- data/lib/draftjs_html/to_html.rb +17 -83
- data/lib/draftjs_html/version.rb +1 -1
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4955cd2cbaade2ea9ae659cb65028e4097fb1d8d95d5058bc165e758f40e7794
|
4
|
+
data.tar.gz: 5bf0f0fbf4e6e73eb3da8b069fe536505a56db67cfed802c04baa2079a8641ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a36d06cdc9383a8c27d3d93c08fc59c4a022120aa9951cdf6e9774f0fc77f3e69439ea761a6765469b0f9a4c4879b76b216b465473894b35fbd7e57b5e5ff740
|
7
|
+
data.tar.gz: 16c84f495363d08c997b55de7f53b496d8f92760c936a755c1c1960d024bae7e9fb3235bcbcdb32d05649dc2668c16d4833577fce9b9861273a32f7cae33082a
|
@@ -2,12 +2,13 @@
|
|
2
2
|
|
3
3
|
module DraftjsHtml
|
4
4
|
module Draftjs
|
5
|
-
Block = Struct.new(:key, :text, :type, :inline_style_ranges, :raw_entity_ranges, keyword_init: true) do
|
5
|
+
Block = Struct.new(:key, :text, :type, :depth, :inline_style_ranges, :raw_entity_ranges, keyword_init: true) do
|
6
6
|
def self.parse(raw)
|
7
7
|
new(
|
8
8
|
key: raw['key'],
|
9
9
|
text: raw['text'],
|
10
10
|
type: raw['type'],
|
11
|
+
depth: raw['depth'],
|
11
12
|
inline_style_ranges: Array(raw['inlineStyleRanges']),
|
12
13
|
raw_entity_ranges: Array(raw['entityRanges']),
|
13
14
|
)
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module DraftjsHtml
|
2
|
+
module HtmlDefaults
|
3
|
+
BLOCK_TYPE_TO_HTML = {
|
4
|
+
'unstyled' => 'p',
|
5
|
+
'paragraph' => 'p',
|
6
|
+
'header-one' => 'h1',
|
7
|
+
'header-two' => 'h2',
|
8
|
+
'header-three' => 'h3',
|
9
|
+
'header-four' => 'h4',
|
10
|
+
'header-five' => 'h5',
|
11
|
+
'header-six' => 'h6',
|
12
|
+
'blockquote' => 'blockquote',
|
13
|
+
'code-block' => 'code',
|
14
|
+
'ordered-list-item' => 'li',
|
15
|
+
'unordered-list-item' => 'li',
|
16
|
+
'atomic' => 'figure',
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
STYLE_MAP = {
|
20
|
+
'BOLD' => 'b',
|
21
|
+
'ITALIC' => 'i',
|
22
|
+
'STRIKETHROUGH' => 'del',
|
23
|
+
'UNDERLINE' => 'u',
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
ENTITY_ATTRIBUTE_NAME_MAP = {
|
27
|
+
'className' => 'class',
|
28
|
+
'url' => 'href',
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
DEFAULT_ENTITY_STYLE_FN = ->(_entity, chars, _doc) { chars }
|
32
|
+
|
33
|
+
ENTITY_CONVERSION_MAP = {
|
34
|
+
'LINK' => ->(entity, content, *) {
|
35
|
+
attributes = entity.data.slice('url', 'rel', 'target', 'title', 'className').each_with_object({}) do |(attr, value), h|
|
36
|
+
h[ENTITY_ATTRIBUTE_NAME_MAP.fetch(attr, attr)] = value
|
37
|
+
end
|
38
|
+
|
39
|
+
DraftjsHtml::Node.new('a', attributes, content)
|
40
|
+
},
|
41
|
+
'IMAGE' => ->(entity, *) {
|
42
|
+
attributes = entity.data.slice('src', 'alt', 'className', 'width', 'height').each_with_object({}) do |(attr, value), h|
|
43
|
+
h[ENTITY_ATTRIBUTE_NAME_MAP.fetch(attr, attr)] = value
|
44
|
+
end
|
45
|
+
|
46
|
+
DraftjsHtml::Node.new('img', attributes)
|
47
|
+
}
|
48
|
+
}.freeze
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module DraftjsHtml
|
2
|
+
# This class manages the depth and nesting of the myriad HTML tags generated by DraftjsHtml::ToHtml.
|
3
|
+
# It is intended to be a private implementation detail.
|
4
|
+
class HtmlDepth # :nodoc:
|
5
|
+
BLOCK_TYPE_TO_HTML_WRAPPER = {
|
6
|
+
'code-block' => 'pre',
|
7
|
+
'ordered-list-item' => 'ol',
|
8
|
+
'unordered-list-item' => 'ul',
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
attr_reader :body
|
12
|
+
|
13
|
+
def initialize(body)
|
14
|
+
@current_depth = 0
|
15
|
+
@body = body
|
16
|
+
@previous_parents = [body.parent]
|
17
|
+
end
|
18
|
+
|
19
|
+
def apply(block)
|
20
|
+
new_wrapper_tag = BLOCK_TYPE_TO_HTML_WRAPPER[block.type]
|
21
|
+
if body.parent.name != new_wrapper_tag || block.depth != @current_depth
|
22
|
+
if @current_depth < block.depth
|
23
|
+
push_depth(body, new_wrapper_tag)
|
24
|
+
elsif @current_depth > block.depth
|
25
|
+
pop_depth(body, times: @current_depth - block.depth)
|
26
|
+
pop_nesting(body) unless new_wrapper_tag
|
27
|
+
elsif new_wrapper_tag
|
28
|
+
push_nesting(body, new_wrapper_tag)
|
29
|
+
elsif @previous_parents.size > 1
|
30
|
+
pop_nesting(body)
|
31
|
+
end
|
32
|
+
@current_depth = block.depth
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def push_depth(builder, tagname)
|
39
|
+
@previous_parents << builder.parent
|
40
|
+
builder.parent = builder.parent.last_element_child
|
41
|
+
push_nesting(builder, tagname)
|
42
|
+
end
|
43
|
+
|
44
|
+
def push_nesting(builder, tagname)
|
45
|
+
node = create_child(builder, tagname)
|
46
|
+
@previous_parents << builder.parent
|
47
|
+
builder.parent = node
|
48
|
+
end
|
49
|
+
|
50
|
+
def pop_depth(builder, times:)
|
51
|
+
times.times do
|
52
|
+
pop_nesting(builder)
|
53
|
+
pop_nesting(builder)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def pop_nesting(builder)
|
58
|
+
builder.parent = @previous_parents.pop
|
59
|
+
end
|
60
|
+
|
61
|
+
def create_child(builder, tagname)
|
62
|
+
builder.parent.add_child(builder.doc.create_element(tagname))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/draftjs_html/node.rb
CHANGED
@@ -18,7 +18,7 @@ module DraftjsHtml
|
|
18
18
|
lines = raw.lines
|
19
19
|
text_nodes = lines.flat_map.with_index do |text, i|
|
20
20
|
nodes = [Nokogiri::XML::Text.new(text.chomp, document)]
|
21
|
-
nodes << Nokogiri::XML::Node.new('br', document) if
|
21
|
+
nodes << Nokogiri::XML::Node.new('br', document) if text.end_with?("\n")
|
22
22
|
nodes
|
23
23
|
end
|
24
24
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module DraftjsHtml
|
2
|
+
class OverrideableMap
|
3
|
+
def initialize(defaults = {})
|
4
|
+
@map = defaults.dup.transform_keys(&:to_s)
|
5
|
+
end
|
6
|
+
|
7
|
+
def with_overrides(overrides)
|
8
|
+
@map.merge!((overrides || {}).transform_keys(&:to_s))
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def with_default(default)
|
13
|
+
@default = default
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def value_of!(key)
|
18
|
+
@map.fetch(key.to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
def value_of(key)
|
22
|
+
@map.fetch(key.to_s, @default)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/draftjs_html/to_html.rb
CHANGED
@@ -1,59 +1,14 @@
|
|
1
1
|
require_relative 'node'
|
2
|
+
require_relative 'html_depth'
|
3
|
+
require_relative 'html_defaults'
|
4
|
+
require_relative 'overrideable_map'
|
2
5
|
|
3
6
|
module DraftjsHtml
|
4
7
|
class ToHtml
|
5
|
-
BLOCK_TYPE_TO_HTML = {
|
6
|
-
'unstyled' => 'p',
|
7
|
-
'paragraph' => 'p',
|
8
|
-
'header-one' => 'h1',
|
9
|
-
'header-two' => 'h2',
|
10
|
-
'header-three' => 'h3',
|
11
|
-
'header-four' => 'h4',
|
12
|
-
'header-five' => 'h5',
|
13
|
-
'header-six' => 'h6',
|
14
|
-
'blockquote' => 'blockquote',
|
15
|
-
'code-block' => 'code',
|
16
|
-
'ordered-list-item' => 'li',
|
17
|
-
'unordered-list-item' => 'li',
|
18
|
-
'atomic' => 'figure',
|
19
|
-
}.freeze
|
20
|
-
BLOCK_TYPE_TO_HTML_WRAPPER = {
|
21
|
-
'code-block' => 'pre',
|
22
|
-
'ordered-list-item' => 'ol',
|
23
|
-
'unordered-list-item' => 'ul',
|
24
|
-
}.freeze
|
25
|
-
STYLE_MAP = {
|
26
|
-
'BOLD' => 'b',
|
27
|
-
'ITALIC' => 'i',
|
28
|
-
'STRIKETHROUGH' => 'del',
|
29
|
-
'UNDERLINE' => 'u',
|
30
|
-
}.freeze
|
31
|
-
|
32
|
-
DEFAULT_ENTITY_STYLE_FN = ->(_entity, chars, _doc) { chars }
|
33
|
-
ENTITY_ATTRIBUTE_NAME_MAP = {
|
34
|
-
'className' => 'class',
|
35
|
-
'url' => 'href',
|
36
|
-
}.freeze
|
37
|
-
ENTITY_CONVERSION_MAP = {
|
38
|
-
'LINK' => ->(entity, content, *) {
|
39
|
-
attributes = entity.data.slice('url', 'rel', 'target', 'title', 'className').each_with_object({}) do |(attr, value), h|
|
40
|
-
h[ENTITY_ATTRIBUTE_NAME_MAP.fetch(attr, attr)] = value
|
41
|
-
end
|
42
|
-
|
43
|
-
DraftjsHtml::Node.new('a', attributes, content)
|
44
|
-
},
|
45
|
-
'IMAGE' => ->(entity, *) {
|
46
|
-
attributes = entity.data.slice('src', 'alt', 'className', 'width', 'height').each_with_object({}) do |(attr, value), h|
|
47
|
-
h[ENTITY_ATTRIBUTE_NAME_MAP.fetch(attr, attr)] = value
|
48
|
-
end
|
49
|
-
|
50
|
-
DraftjsHtml::Node.new('img', attributes)
|
51
|
-
}
|
52
|
-
}.freeze
|
53
|
-
|
54
8
|
def initialize(options)
|
55
9
|
@options = ensure_options!(options)
|
56
10
|
@document = Nokogiri::HTML::Builder.new(encoding: @options.fetch(:encoding, 'UTF-8'))
|
11
|
+
@html_depth = HtmlDepth.new(@document)
|
57
12
|
end
|
58
13
|
|
59
14
|
def convert(raw_draftjs)
|
@@ -61,10 +16,10 @@ module DraftjsHtml
|
|
61
16
|
|
62
17
|
@document.html do |html|
|
63
18
|
html.body do |body|
|
64
|
-
@
|
19
|
+
@previous_parents = [body.parent]
|
65
20
|
|
66
21
|
draftjs.blocks.each do |block|
|
67
|
-
|
22
|
+
@html_depth.apply(block)
|
68
23
|
|
69
24
|
body.public_send(block_element_for(block)) do |block_body|
|
70
25
|
block.each_range do |char_range|
|
@@ -84,18 +39,7 @@ module DraftjsHtml
|
|
84
39
|
private
|
85
40
|
|
86
41
|
def squeeze_newlines(char_range)
|
87
|
-
char_range.text = @options[:newline_squeezer].call(char_range.text)
|
88
|
-
end
|
89
|
-
|
90
|
-
def ensure_nesting_depth(block, body)
|
91
|
-
new_wrapper_tag = BLOCK_TYPE_TO_HTML_WRAPPER[block.type]
|
92
|
-
if body.parent.name != new_wrapper_tag
|
93
|
-
if new_wrapper_tag
|
94
|
-
push_nesting(body, new_wrapper_tag)
|
95
|
-
else
|
96
|
-
pop_nesting(body)
|
97
|
-
end
|
98
|
-
end
|
42
|
+
char_range.text = @options[:newline_squeezer].call(char_range.text.chomp)
|
99
43
|
end
|
100
44
|
|
101
45
|
def apply_styles_to(html, style_names, child)
|
@@ -117,43 +61,33 @@ module DraftjsHtml
|
|
117
61
|
def block_element_for(block)
|
118
62
|
return 'br' if block.blank?
|
119
63
|
|
120
|
-
@options[:block_type_mapping].
|
64
|
+
@options[:block_type_mapping].value_of!(block.type)
|
121
65
|
end
|
122
66
|
|
123
67
|
def style_element_for(style)
|
124
|
-
@options[:inline_style_mapping]
|
68
|
+
@options[:inline_style_mapping].value_of!(style)
|
125
69
|
end
|
126
70
|
|
127
71
|
def try_apply_entity_to(draftjs, char_range)
|
128
72
|
entity = draftjs.find_entity(char_range.entity_key)
|
129
73
|
content = char_range.text
|
130
74
|
if entity
|
131
|
-
style_fn =
|
75
|
+
style_fn = @options[:entity_style_mappings].value_of(entity.type)
|
132
76
|
content = style_fn.call(entity, Node.of(content), @document.parent)
|
133
77
|
end
|
134
78
|
|
135
79
|
content
|
136
80
|
end
|
137
81
|
|
138
|
-
def push_nesting(builder, tagname)
|
139
|
-
node = create_child(builder, tagname)
|
140
|
-
@previous_parent = builder.parent
|
141
|
-
builder.parent = node
|
142
|
-
end
|
143
|
-
|
144
|
-
def pop_nesting(builder)
|
145
|
-
builder.parent = @previous_parent
|
146
|
-
end
|
147
|
-
|
148
|
-
def create_child(builder, tagname)
|
149
|
-
builder.parent.add_child(builder.doc.create_element(tagname))
|
150
|
-
end
|
151
|
-
|
152
82
|
def ensure_options!(opts)
|
153
|
-
opts[:entity_style_mappings] =
|
154
|
-
|
83
|
+
opts[:entity_style_mappings] = OverrideableMap.new(HtmlDefaults::ENTITY_CONVERSION_MAP)
|
84
|
+
.with_overrides(opts[:entity_style_mappings])
|
85
|
+
.with_default(HtmlDefaults::DEFAULT_ENTITY_STYLE_FN)
|
86
|
+
opts[:block_type_mapping] = OverrideableMap.new(HtmlDefaults::BLOCK_TYPE_TO_HTML)
|
87
|
+
.with_overrides(opts[:block_type_mapping])
|
155
88
|
opts[:newline_squeezer] = opts[:squeeze_newlines] ? ->(text) { text.gsub(/(\n|\r\n)+/, "\n") } : ->(text) { text }
|
156
|
-
opts[:inline_style_mapping] =
|
89
|
+
opts[:inline_style_mapping] = OverrideableMap.new(HtmlDefaults::STYLE_MAP)
|
90
|
+
.with_overrides(opts[:inline_style_mapping])
|
157
91
|
opts[:inline_style_renderer] ||= ->(*) { nil }
|
158
92
|
opts
|
159
93
|
end
|
data/lib/draftjs_html/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: draftjs_html
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- TJ Taylor
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-10-
|
11
|
+
date: 2022-10-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -65,7 +65,10 @@ files:
|
|
65
65
|
- lib/draftjs_html/draftjs/entity.rb
|
66
66
|
- lib/draftjs_html/draftjs/entity_map.rb
|
67
67
|
- lib/draftjs_html/draftjs/to_raw.rb
|
68
|
+
- lib/draftjs_html/html_defaults.rb
|
69
|
+
- lib/draftjs_html/html_depth.rb
|
68
70
|
- lib/draftjs_html/node.rb
|
71
|
+
- lib/draftjs_html/overrideable_map.rb
|
69
72
|
- lib/draftjs_html/to_html.rb
|
70
73
|
- lib/draftjs_html/version.rb
|
71
74
|
homepage: https://github.com/dugancathal/draftjs_html
|
@@ -75,7 +78,7 @@ metadata:
|
|
75
78
|
homepage_uri: https://github.com/dugancathal/draftjs_html
|
76
79
|
source_code_uri: https://github.com/dugancathal/draftjs_html
|
77
80
|
changelog_uri: https://github.com/dugancathal/draftjs_html/tree/main/CHANGELOG.md
|
78
|
-
post_install_message:
|
81
|
+
post_install_message:
|
79
82
|
rdoc_options: []
|
80
83
|
require_paths:
|
81
84
|
- lib
|
@@ -90,8 +93,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
93
|
- !ruby/object:Gem::Version
|
91
94
|
version: '0'
|
92
95
|
requirements: []
|
93
|
-
rubygems_version: 3.1.
|
94
|
-
signing_key:
|
96
|
+
rubygems_version: 3.1.2
|
97
|
+
signing_key:
|
95
98
|
specification_version: 4
|
96
99
|
summary: A tool for converting DraftJS JSON to HTML (and back again)
|
97
100
|
test_files: []
|