draftjs_exporter 0.0.2 → 0.0.3
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_exporter/entity_state.rb +4 -1
- data/lib/draftjs_exporter/error.rb +3 -0
- data/lib/draftjs_exporter/html.rb +8 -25
- data/lib/draftjs_exporter/version.rb +1 -1
- data/lib/draftjs_exporter/wrapper_state.rb +70 -0
- data/spec/integrations/html_spec.rb +169 -43
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 09a55e5243dc77a0e6b9711abced8fd26cab0776
|
4
|
+
data.tar.gz: d0f789f42a140cfcc4bd92bd8f1aa653bd2e033a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5bf865cb2d2a3a1305e13698800d028c38695a694212cdd5534eb1bdeef4a40c04dadbaa5f1bfbc913f022cb64555a9ef864aa26e06a346b78afcb7be275061a
|
7
|
+
data.tar.gz: 565b824cbd6355dd65bef816aa553dca4ca9562b9981557594145bb8f797fb31b67221b97d0eefeeeb12370f322ece790e4538374d707af852a0ac8913b0f754
|
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'draftjs_exporter/entities/null'
|
3
|
+
require 'draftjs_exporter/error'
|
3
4
|
|
4
5
|
module DraftjsExporter
|
6
|
+
class InvalidEntity < DraftjsExporter::Error; end
|
7
|
+
|
5
8
|
class EntityState
|
6
9
|
attr_reader :entity_decorators, :entity_map, :entity_stack, :root_element
|
7
10
|
|
@@ -40,7 +43,7 @@ module DraftjsExporter
|
|
40
43
|
_element, expected_entity_details = entity_stack.last
|
41
44
|
|
42
45
|
if expected_entity_details != entity_details
|
43
|
-
raise "
|
46
|
+
raise InvalidEntity, "Expected #{expected_entity_details.inspect} got #{entity_details.inspect}"
|
44
47
|
end
|
45
48
|
|
46
49
|
entity_stack.pop
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'nokogiri'
|
3
|
+
require 'draftjs_exporter/wrapper_state'
|
3
4
|
require 'draftjs_exporter/entity_state'
|
4
5
|
require 'draftjs_exporter/style_state'
|
5
6
|
require 'draftjs_exporter/command'
|
@@ -15,23 +16,17 @@ module DraftjsExporter
|
|
15
16
|
end
|
16
17
|
|
17
18
|
def call(content_state)
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
wrapper_state = WrapperState.new(block_map)
|
20
|
+
content_state.fetch(:blocks, []).each do |block|
|
21
|
+
element = wrapper_state.element_for(block)
|
22
|
+
entity_map = content_state.fetch(:entityMap, {})
|
23
|
+
block_contents(element, block, entity_map)
|
24
|
+
end
|
25
|
+
wrapper_state.to_s
|
21
26
|
end
|
22
27
|
|
23
28
|
private
|
24
29
|
|
25
|
-
def content_state_block(block, entity_map)
|
26
|
-
document = Nokogiri::HTML::Document.new
|
27
|
-
fragment = Nokogiri::HTML::DocumentFragment.new(document)
|
28
|
-
type = block.fetch(:type, 'unstyled')
|
29
|
-
element = document.create_element(*block_options(type)) { |e|
|
30
|
-
block_contents(e, block, entity_map)
|
31
|
-
}
|
32
|
-
fragment.add_child(element).to_s
|
33
|
-
end
|
34
|
-
|
35
30
|
def block_contents(element, block, entity_map)
|
36
31
|
style_state = StyleState.new(style_map)
|
37
32
|
entity_state = EntityState.new(element, entity_decorators, entity_map)
|
@@ -45,18 +40,6 @@ module DraftjsExporter
|
|
45
40
|
end
|
46
41
|
end
|
47
42
|
|
48
|
-
def block_options(type)
|
49
|
-
options = block_map.fetch(type)
|
50
|
-
return [options.fetch(:element)] unless options.key?(:wrapper)
|
51
|
-
|
52
|
-
wrapper = options.fetch(:wrapper)
|
53
|
-
name = wrapper[0]
|
54
|
-
config = wrapper[1] || {}
|
55
|
-
options = {}
|
56
|
-
options[:class] = config.fetch(:className) if config.key?(:className)
|
57
|
-
[name, options]
|
58
|
-
end
|
59
|
-
|
60
43
|
def add_node(element, text, state)
|
61
44
|
document = element.document
|
62
45
|
node = if state.text?
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module DraftjsExporter
|
2
|
+
class WrapperState
|
3
|
+
def initialize(block_map)
|
4
|
+
@block_map = block_map
|
5
|
+
@document = Nokogiri::HTML::Document.new
|
6
|
+
@fragment = Nokogiri::HTML::DocumentFragment.new(document)
|
7
|
+
reset_wrapper
|
8
|
+
end
|
9
|
+
|
10
|
+
def element_for(block)
|
11
|
+
type = block.fetch(:type, 'unstyled')
|
12
|
+
document.create_element(block_options(type)).tap do |e|
|
13
|
+
parent_for(type).add_child(e)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
fragment.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :fragment, :document, :block_map, :wrapper
|
24
|
+
|
25
|
+
def set_wrapper(element, options = {})
|
26
|
+
@wrapper = [element, options]
|
27
|
+
end
|
28
|
+
|
29
|
+
def wrapper_element
|
30
|
+
@wrapper[0] || fragment
|
31
|
+
end
|
32
|
+
|
33
|
+
def wrapper_options
|
34
|
+
@wrapper[1]
|
35
|
+
end
|
36
|
+
|
37
|
+
def parent_for(type)
|
38
|
+
options = block_map.fetch(type)
|
39
|
+
return reset_wrapper unless options.key?(:wrapper)
|
40
|
+
|
41
|
+
new_options = nokogiri_options(*options.fetch(:wrapper))
|
42
|
+
return wrapper_element if new_options == wrapper_options
|
43
|
+
|
44
|
+
create_wrapper(new_options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def reset_wrapper
|
48
|
+
set_wrapper(fragment)
|
49
|
+
wrapper_element
|
50
|
+
end
|
51
|
+
|
52
|
+
def nokogiri_options(element_name, element_attributes)
|
53
|
+
config = element_attributes || {}
|
54
|
+
options = {}
|
55
|
+
options[:class] = config.fetch(:className) if config.key?(:className)
|
56
|
+
[element_name, options]
|
57
|
+
end
|
58
|
+
|
59
|
+
def block_options(type)
|
60
|
+
block_map.fetch(type).fetch(:element)
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_wrapper(options)
|
64
|
+
document.create_element(*options).tap do |new_element|
|
65
|
+
reset_wrapper.add_child(new_element)
|
66
|
+
set_wrapper(new_element, options)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -24,55 +24,181 @@ RSpec.describe DraftjsExporter::HTML do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
describe '#call' do
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
27
|
+
context 'with different blocks' do
|
28
|
+
it 'decodes the content_state to html' do
|
29
|
+
input = {
|
30
|
+
entityMap: {},
|
31
|
+
blocks: [
|
32
|
+
{
|
33
|
+
key: '5s7g9',
|
34
|
+
text: 'Header',
|
35
|
+
type: 'header-one',
|
36
|
+
depth: 0,
|
37
|
+
inlineStyleRanges: [],
|
38
|
+
entityRanges: []
|
39
|
+
},
|
40
|
+
{
|
41
|
+
key: 'dem5p',
|
42
|
+
text: 'some paragraph text',
|
43
|
+
type: 'unstyled',
|
44
|
+
depth: 0,
|
45
|
+
inlineStyleRanges: [],
|
46
|
+
entityRanges: []
|
47
|
+
}
|
48
|
+
]
|
49
|
+
}
|
50
|
+
|
51
|
+
expected_output = <<-OUTPUT.strip
|
52
|
+
<h1>Header</h1><div>some paragraph text</div>
|
53
|
+
OUTPUT
|
54
|
+
|
55
|
+
expect(mapper.call(input)).to eq(expected_output)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'with inline styles' do
|
60
|
+
it 'decodes the content_state to html' do
|
61
|
+
input = {
|
62
|
+
entityMap: {},
|
63
|
+
blocks: [
|
64
|
+
{
|
65
|
+
key: 'dem5p',
|
66
|
+
text: 'some paragraph text',
|
67
|
+
type: 'unstyled',
|
68
|
+
depth: 0,
|
69
|
+
inlineStyleRanges: [
|
70
|
+
{
|
71
|
+
offset: 0,
|
72
|
+
length: 4,
|
73
|
+
style: 'ITALIC'
|
74
|
+
}
|
75
|
+
],
|
76
|
+
entityRanges: []
|
77
|
+
}
|
78
|
+
]
|
79
|
+
}
|
80
|
+
|
81
|
+
expected_output = <<-OUTPUT.strip
|
82
|
+
<div>
|
83
|
+
<span style="fontStyle: italic;">some</span> paragraph text</div>
|
84
|
+
OUTPUT
|
85
|
+
|
86
|
+
expect(mapper.call(input)).to eq(expected_output)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'with entities' do
|
91
|
+
it 'decodes the content_state to html' do
|
92
|
+
input = {
|
93
|
+
entityMap: {
|
94
|
+
'0' => {
|
95
|
+
type: 'LINK',
|
96
|
+
mutability: 'MUTABLE',
|
97
|
+
data: {
|
98
|
+
url: 'http://example.com'
|
99
|
+
}
|
35
100
|
}
|
36
|
-
}
|
37
|
-
},
|
38
|
-
blocks: [
|
39
|
-
{
|
40
|
-
key: '5s7g9',
|
41
|
-
text: 'Header',
|
42
|
-
type: 'header-one',
|
43
|
-
depth: 0,
|
44
|
-
inlineStyleRanges: [],
|
45
|
-
entityRanges: []
|
46
101
|
},
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
102
|
+
blocks: [
|
103
|
+
{
|
104
|
+
key: 'dem5p',
|
105
|
+
text: 'some paragraph text',
|
106
|
+
type: 'unstyled',
|
107
|
+
depth: 0,
|
108
|
+
inlineStyleRanges: [],
|
109
|
+
entityRanges: [
|
110
|
+
{
|
111
|
+
offset: 5,
|
112
|
+
length: 9,
|
113
|
+
key: 0
|
114
|
+
}
|
115
|
+
]
|
116
|
+
}
|
117
|
+
]
|
118
|
+
}
|
119
|
+
|
120
|
+
expected_output = <<-OUTPUT.strip
|
121
|
+
<div>some <a href="http://example.com">paragraph</a> text</div>
|
122
|
+
OUTPUT
|
123
|
+
|
124
|
+
expect(mapper.call(input)).to eq(expected_output)
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'throws an error if entities cross over' do
|
128
|
+
input = {
|
129
|
+
entityMap: {
|
130
|
+
'0' => {
|
131
|
+
type: 'LINK',
|
132
|
+
mutability: 'MUTABLE',
|
133
|
+
data: {
|
134
|
+
url: 'http://foo.example.com'
|
57
135
|
}
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
136
|
+
},
|
137
|
+
'1' => {
|
138
|
+
type: 'LINK',
|
139
|
+
mutability: 'MUTABLE',
|
140
|
+
data: {
|
141
|
+
url: 'http://bar.example.com'
|
64
142
|
}
|
65
|
-
|
66
|
-
}
|
67
|
-
|
68
|
-
|
143
|
+
}
|
144
|
+
},
|
145
|
+
blocks: [
|
146
|
+
{
|
147
|
+
key: 'dem5p',
|
148
|
+
text: 'some paragraph text',
|
149
|
+
type: 'unstyled',
|
150
|
+
depth: 0,
|
151
|
+
inlineStyleRanges: [],
|
152
|
+
entityRanges: [
|
153
|
+
{
|
154
|
+
offset: 5,
|
155
|
+
length: 9,
|
156
|
+
key: 0
|
157
|
+
},
|
158
|
+
{
|
159
|
+
offset: 2,
|
160
|
+
length: 9,
|
161
|
+
key: 1
|
162
|
+
}
|
163
|
+
]
|
164
|
+
}
|
165
|
+
]
|
166
|
+
}
|
167
|
+
|
168
|
+
expect { mapper.call(input) }.to raise_error(DraftjsExporter::InvalidEntity)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context 'with wrapped blocks' do
|
173
|
+
it 'decodes the content_state to html' do
|
174
|
+
input = {
|
175
|
+
entityMap: {},
|
176
|
+
blocks: [
|
177
|
+
{
|
178
|
+
key: 'dem5p',
|
179
|
+
text: 'item1',
|
180
|
+
type: 'unordered-list-item',
|
181
|
+
depth: 0,
|
182
|
+
inlineStyleRanges: [],
|
183
|
+
entityRanges: []
|
184
|
+
},
|
185
|
+
{
|
186
|
+
key: 'dem5p',
|
187
|
+
text: 'item2',
|
188
|
+
type: 'unordered-list-item',
|
189
|
+
depth: 0,
|
190
|
+
inlineStyleRanges: [],
|
191
|
+
entityRanges: []
|
192
|
+
}
|
193
|
+
]
|
194
|
+
}
|
69
195
|
|
70
|
-
|
71
|
-
<
|
72
|
-
|
73
|
-
OUTPUT
|
196
|
+
expected_output = <<-EOS.strip
|
197
|
+
<ul class="public-DraftStyleDefault-ul">\n<li>item1</li>\n<li>item2</li>\n</ul>
|
198
|
+
EOS
|
74
199
|
|
75
|
-
|
200
|
+
expect(mapper.call(input)).to eq(expected_output)
|
201
|
+
end
|
76
202
|
end
|
77
203
|
end
|
78
204
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: draftjs_exporter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Theo Cushion
|
@@ -126,9 +126,11 @@ files:
|
|
126
126
|
- lib/draftjs_exporter/entities/link.rb
|
127
127
|
- lib/draftjs_exporter/entities/null.rb
|
128
128
|
- lib/draftjs_exporter/entity_state.rb
|
129
|
+
- lib/draftjs_exporter/error.rb
|
129
130
|
- lib/draftjs_exporter/html.rb
|
130
131
|
- lib/draftjs_exporter/style_state.rb
|
131
132
|
- lib/draftjs_exporter/version.rb
|
133
|
+
- lib/draftjs_exporter/wrapper_state.rb
|
132
134
|
- spec/integrations/html_spec.rb
|
133
135
|
- spec/integrations/requires_spec.rb
|
134
136
|
- spec/spec_helper.rb
|
@@ -152,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
154
|
version: '0'
|
153
155
|
requirements: []
|
154
156
|
rubyforge_project:
|
155
|
-
rubygems_version: 2.5
|
157
|
+
rubygems_version: 2.2.5
|
156
158
|
signing_key:
|
157
159
|
specification_version: 4
|
158
160
|
summary: Export Draft.js content state into HTML
|