draftjs_html 0.21.0 → 0.23.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/README.md +9 -0
- data/lib/draftjs_html/from_html/char_list.rb +136 -0
- data/lib/draftjs_html/from_html/depth_stack.rb +15 -14
- data/lib/draftjs_html/from_html/pending_block.rb +18 -30
- data/lib/draftjs_html/from_html.rb +2 -2
- data/lib/draftjs_html/version.rb +1 -1
- metadata +4 -4
- data/lib/draftjs_html/from_html/style_stack.rb +0 -50
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c01b8b4b8ca17c90c9d53f7d85caebc7fe6c18aab14a79ff635110d8a0fb81dc
|
|
4
|
+
data.tar.gz: 6e0d85454c9d8ee4e6998e8e398c72c01f6097fc3c5e9b61c165fb07ac01af0f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 76cdd86c495a5900653f060f8d4f9702a29b1a7b3ebdb792b00e3f7071642c2fd7547d56aa1e3f6684ab6f873e8c377a09d8fe0c344e2ecf400db106076ebe66
|
|
7
|
+
data.tar.gz: 0c65eac79da80da23d667c62352542dc03717e3db1999fffb35f5a7ac19a1afe871345c1c7449dd29c0570fd780d17c1ea44f3c75b02c034e7cd224eb2c002bb
|
data/README.md
CHANGED
|
@@ -229,6 +229,15 @@ The callable should return a Hash with symbol keys. The supported values are:
|
|
|
229
229
|
- `data` (optional, default `{}`)
|
|
230
230
|
- an arbitrary data-bag (Hash) of entity data
|
|
231
231
|
|
|
232
|
+
#### `:is_semantic_markup:`
|
|
233
|
+
|
|
234
|
+
Defaults to `true`.
|
|
235
|
+
|
|
236
|
+
By setting to `false`, the user is stating they want to treat `div` tags as semantic,
|
|
237
|
+
block-level tags. In some markup (emails, for example), there are no semantic tags
|
|
238
|
+
(read, no `p` tags), so the only indications of whitespace and structure come from
|
|
239
|
+
`div` tags. This flag will flush content wrapped in a `div` as a DraftJS block.
|
|
240
|
+
|
|
232
241
|
## Development
|
|
233
242
|
|
|
234
243
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'set'
|
|
3
|
+
|
|
4
|
+
module DraftjsHtml
|
|
5
|
+
class FromHtml < Nokogiri::XML::SAX::Document
|
|
6
|
+
class CharList
|
|
7
|
+
module FinishableRange
|
|
8
|
+
def range
|
|
9
|
+
start..finish
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def try_finish(index)
|
|
13
|
+
self[:finish] ||= index
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
Char = Struct.new(:it, :styles, :entity, :atomic, keyword_init: true) do
|
|
18
|
+
def atomic?
|
|
19
|
+
self.atomic
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def styles
|
|
23
|
+
self[:styles] ||= Set.new
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
EntityRange = Struct.new(:entity, :start, :finish, keyword_init: true) do
|
|
28
|
+
include FinishableRange
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
StyleRange = Struct.new(:style, :start, :finish, keyword_init: true) do
|
|
32
|
+
include FinishableRange
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
attr_reader :chars
|
|
36
|
+
|
|
37
|
+
def initialize(initial = [])
|
|
38
|
+
@chars = initial.dup
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def append(str, styles: Set.new, entity: nil)
|
|
42
|
+
@chars << Char.new(it: "\n") if @chars.last&.atomic?
|
|
43
|
+
|
|
44
|
+
@chars += str.chars.map { Char.new(it: _1, styles: Set.new(styles), entity: entity) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def append_char(char)
|
|
48
|
+
@chars << char
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def text
|
|
52
|
+
@chars.map(&:it).join
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def size
|
|
56
|
+
@chars.size
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def any?
|
|
60
|
+
size > 0
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def atomic?
|
|
64
|
+
@chars.any? && @chars.all?(&:atomic?)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def each_line
|
|
68
|
+
return to_enum(:each_line) unless block_given?
|
|
69
|
+
|
|
70
|
+
line = self.class.new
|
|
71
|
+
chars.each do |c|
|
|
72
|
+
if c.it == "\n"
|
|
73
|
+
yield line
|
|
74
|
+
line = self.class.new
|
|
75
|
+
next
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
line.append_char(c)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
yield line if line.any?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def apply_entity(range, entity)
|
|
85
|
+
@chars[range].each { _1.entity = entity }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def append_atomic_entity(entity)
|
|
89
|
+
append("\n") if @chars.any?
|
|
90
|
+
append_char(Char.new(it: ' ', atomic: true, entity: entity))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def append_styles(range, styles)
|
|
94
|
+
@chars[range].each { _1.styles += Array(styles) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def +(other)
|
|
98
|
+
self.class.new(@chars + other.chars)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def entity_ranges
|
|
102
|
+
current_entity = nil
|
|
103
|
+
|
|
104
|
+
entity_ranges = @chars.each_with_object(Array.new).with_index do |(char, ranges), i|
|
|
105
|
+
next if char.entity == current_entity
|
|
106
|
+
|
|
107
|
+
current_entity = char.entity
|
|
108
|
+
ranges.last&.try_finish(i - 1)
|
|
109
|
+
ranges << EntityRange.new(entity: char.entity, start: i) if char.entity
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
entity_ranges.last&.try_finish(@chars.size - 1)
|
|
113
|
+
entity_ranges
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def style_ranges
|
|
117
|
+
style_ranges = @chars.each_with_object({}).with_index do |(char, ranges_by_style), i|
|
|
118
|
+
char.styles.each do |style|
|
|
119
|
+
ranges_by_style[style] ||= []
|
|
120
|
+
if i > 0 && @chars[i-1].styles.include?(style)
|
|
121
|
+
ranges_by_style[style].last << i
|
|
122
|
+
else
|
|
123
|
+
ranges_by_style[style] << [i]
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
style_ranges.flat_map do |style, ranges|
|
|
129
|
+
ranges.map do |range|
|
|
130
|
+
StyleRange.new(style: style, start: range.first, finish: range.last)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
module DraftjsHtml
|
|
2
2
|
class FromHtml < Nokogiri::XML::SAX::Document
|
|
3
3
|
class DepthStack
|
|
4
|
-
def initialize
|
|
4
|
+
def initialize(is_semantic_markup: true)
|
|
5
5
|
@stack = []
|
|
6
6
|
@nodes = []
|
|
7
7
|
@list_depth = -1
|
|
8
|
-
@
|
|
8
|
+
@active_styles = []
|
|
9
|
+
@is_semantic_markup = is_semantic_markup
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def push(tagname, attrs)
|
|
12
|
-
@stack << PendingBlock.from_tag(tagname, attrs, @nodes.dup, @list_depth)
|
|
13
|
+
@stack << PendingBlock.from_tag(tagname, attrs, @nodes.dup, @list_depth, is_semantic_markup: @is_semantic_markup)
|
|
13
14
|
track_block_node(tagname)
|
|
14
15
|
end
|
|
15
16
|
|
|
@@ -26,8 +27,7 @@ module DraftjsHtml
|
|
|
26
27
|
@nodes.pop
|
|
27
28
|
end
|
|
28
29
|
blocks.reverse_each do |pending_block|
|
|
29
|
-
pending_block.flush_to(draftjs
|
|
30
|
-
pending_block.apply_entities_to(draftjs)
|
|
30
|
+
pending_block.flush_to(draftjs)
|
|
31
31
|
end
|
|
32
32
|
@list_depth -= 1
|
|
33
33
|
end
|
|
@@ -59,34 +59,35 @@ module DraftjsHtml
|
|
|
59
59
|
next unless user_created_entity
|
|
60
60
|
|
|
61
61
|
if content == '' && !user_created_entity[:atomic]
|
|
62
|
-
current.text_buffer
|
|
63
|
-
|
|
62
|
+
current.text_buffer.append(' ', entity: user_created_entity)
|
|
63
|
+
elsif content == '' && user_created_entity[:atomic]
|
|
64
|
+
current.text_buffer.append_atomic_entity(user_created_entity)
|
|
65
|
+
else
|
|
66
|
+
current.text_buffer.apply_entity(range, user_created_entity)
|
|
64
67
|
end
|
|
65
|
-
current.entities << user_created_entity.merge(start: range.begin, finish: range.end)
|
|
66
68
|
end
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
def style_start(tagname)
|
|
70
|
-
@
|
|
72
|
+
@active_styles += [DraftjsHtml::HtmlDefaults::HTML_STYLE_TAGS_TO_STYLE[tagname]]
|
|
71
73
|
end
|
|
72
74
|
|
|
73
75
|
def style_end(tagname)
|
|
74
|
-
@
|
|
76
|
+
@active_styles.delete_at(@active_styles.index(DraftjsHtml::HtmlDefaults::HTML_STYLE_TAGS_TO_STYLE[tagname]))
|
|
75
77
|
end
|
|
76
78
|
|
|
77
79
|
def flush_to(draftjs)
|
|
78
|
-
current.flush_to(draftjs
|
|
79
|
-
current.apply_entities_to(draftjs)
|
|
80
|
+
current.flush_to(draftjs)
|
|
80
81
|
end
|
|
81
82
|
|
|
82
83
|
def append_text(chars)
|
|
83
|
-
current.text_buffer
|
|
84
|
+
current.text_buffer.append(chars, styles: @active_styles) unless chars.empty?
|
|
84
85
|
end
|
|
85
86
|
|
|
86
87
|
private
|
|
87
88
|
|
|
88
89
|
def current_text_buffer
|
|
89
|
-
current.text_buffer.
|
|
90
|
+
current.text_buffer.text
|
|
90
91
|
end
|
|
91
92
|
|
|
92
93
|
def current_character_offset
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
module DraftjsHtml
|
|
2
2
|
class FromHtml < Nokogiri::XML::SAX::Document
|
|
3
|
-
PendingBlock = Struct.new(:tagname, :attrs, :chars, :entities, :pending_entities, :parent_tagnames, :depth, keyword_init: true) do
|
|
4
|
-
def self.from_tag(name, attrs, parent_tagnames, depth)
|
|
3
|
+
PendingBlock = Struct.new(:tagname, :attrs, :chars, :entities, :pending_entities, :parent_tagnames, :depth, :is_semantic_markup, keyword_init: true) do
|
|
4
|
+
def self.from_tag(name, attrs, parent_tagnames, depth, is_semantic_markup: true)
|
|
5
5
|
self.new(
|
|
6
6
|
tagname: name,
|
|
7
7
|
attrs: attrs,
|
|
8
8
|
entities: [],
|
|
9
|
-
chars:
|
|
9
|
+
chars: CharList.new,
|
|
10
10
|
pending_entities: [],
|
|
11
11
|
depth: depth,
|
|
12
12
|
parent_tagnames: parent_tagnames,
|
|
13
|
+
is_semantic_markup: is_semantic_markup,
|
|
13
14
|
)
|
|
14
15
|
end
|
|
15
16
|
|
|
@@ -17,17 +18,14 @@ module DraftjsHtml
|
|
|
17
18
|
self[:chars]
|
|
18
19
|
end
|
|
19
20
|
|
|
20
|
-
def clear_text_buffer
|
|
21
|
-
self[:chars] = []
|
|
22
|
-
end
|
|
23
|
-
|
|
24
21
|
def character_offset
|
|
25
|
-
text_buffer.
|
|
22
|
+
text_buffer.size - 1
|
|
26
23
|
end
|
|
27
24
|
|
|
28
25
|
def flushable?
|
|
29
26
|
%w[OPENING ol ul li table].include?(parent_tagnames.last) ||
|
|
30
|
-
(parent_tagnames.last == 'div' && tagname != 'div')
|
|
27
|
+
(parent_tagnames.last == 'div' && tagname != 'div') ||
|
|
28
|
+
(!is_semantic_markup && tagname == 'div')
|
|
31
29
|
end
|
|
32
30
|
|
|
33
31
|
def consume(other_pending_block)
|
|
@@ -36,31 +34,21 @@ module DraftjsHtml
|
|
|
36
34
|
self.entities += other_pending_block.entities
|
|
37
35
|
end
|
|
38
36
|
|
|
39
|
-
def flush_to(draftjs
|
|
37
|
+
def flush_to(draftjs)
|
|
40
38
|
if text_buffer.any?
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
styles.each do |descriptor|
|
|
46
|
-
finish = descriptor[:finish] || character_offset
|
|
47
|
-
draftjs.inline_style(descriptor[:style], descriptor[:start]..finish)
|
|
48
|
-
end
|
|
49
|
-
end
|
|
39
|
+
text_buffer.each_line do |line|
|
|
40
|
+
block_type = line.atomic? ? 'atomic' : block_name
|
|
41
|
+
draftjs.typed_block(block_type, line.text, depth: [depth, 0].max)
|
|
50
42
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
line.entity_ranges.each do |entity_range|
|
|
44
|
+
entity = entity_range.entity
|
|
45
|
+
draftjs.apply_entity entity[:type], entity_range.range, data: entity[:data], mutability: entity.fetch(:mutability, 'IMMUTABLE')
|
|
46
|
+
end
|
|
54
47
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if entity[:atomic]
|
|
59
|
-
draftjs.typed_block('atomic', ' ', depth: [depth, 0].max)
|
|
60
|
-
range = 0..0
|
|
48
|
+
line.style_ranges.each do |style_range|
|
|
49
|
+
draftjs.inline_style(style_range.style, style_range.range)
|
|
50
|
+
end
|
|
61
51
|
end
|
|
62
|
-
|
|
63
|
-
draftjs.apply_entity entity[:type], range, data: entity[:data], mutability: entity.fetch(:mutability, 'IMMUTABLE')
|
|
64
52
|
end
|
|
65
53
|
end
|
|
66
54
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
require 'stringio'
|
|
2
2
|
require_relative 'html_defaults'
|
|
3
3
|
require_relative 'from_html/elements'
|
|
4
|
-
require_relative 'from_html/style_stack'
|
|
5
4
|
require_relative 'from_html/pending_block'
|
|
6
5
|
require_relative 'from_html/depth_stack'
|
|
6
|
+
require_relative 'from_html/char_list'
|
|
7
7
|
|
|
8
8
|
module DraftjsHtml
|
|
9
9
|
class FromHtml < Nokogiri::XML::SAX::Document
|
|
@@ -11,7 +11,7 @@ module DraftjsHtml
|
|
|
11
11
|
def initialize(options = {})
|
|
12
12
|
@draftjs = Draftjs::RawBuilder.new
|
|
13
13
|
@parser = Nokogiri::HTML4::SAX::Parser.new(self)
|
|
14
|
-
@depth_stack = DepthStack.new
|
|
14
|
+
@depth_stack = DepthStack.new(is_semantic_markup: options.fetch(:is_semantic_markup, true))
|
|
15
15
|
@options = ensure_options!(options.dup)
|
|
16
16
|
end
|
|
17
17
|
|
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.23.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- TJ Taylor
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-
|
|
11
|
+
date: 2022-12-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: nokogiri
|
|
@@ -68,10 +68,10 @@ files:
|
|
|
68
68
|
- lib/draftjs_html/draftjs/raw_builder.rb
|
|
69
69
|
- lib/draftjs_html/draftjs/to_raw.rb
|
|
70
70
|
- lib/draftjs_html/from_html.rb
|
|
71
|
+
- lib/draftjs_html/from_html/char_list.rb
|
|
71
72
|
- lib/draftjs_html/from_html/depth_stack.rb
|
|
72
73
|
- lib/draftjs_html/from_html/elements.rb
|
|
73
74
|
- lib/draftjs_html/from_html/pending_block.rb
|
|
74
|
-
- lib/draftjs_html/from_html/style_stack.rb
|
|
75
75
|
- lib/draftjs_html/html_defaults.rb
|
|
76
76
|
- lib/draftjs_html/html_depth.rb
|
|
77
77
|
- lib/draftjs_html/node.rb
|
|
@@ -100,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
100
100
|
- !ruby/object:Gem::Version
|
|
101
101
|
version: '0'
|
|
102
102
|
requirements: []
|
|
103
|
-
rubygems_version: 3.
|
|
103
|
+
rubygems_version: 3.3.26
|
|
104
104
|
signing_key:
|
|
105
105
|
specification_version: 4
|
|
106
106
|
summary: A tool for converting DraftJS JSON to HTML (and back again)
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
module DraftjsHtml
|
|
2
|
-
class FromHtml < Nokogiri::XML::SAX::Document
|
|
3
|
-
class StyleStack
|
|
4
|
-
def initialize
|
|
5
|
-
@stack = []
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def clear_finished
|
|
9
|
-
@stack.delete_if { !!_1[:finish] }
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def each(&block)
|
|
13
|
-
@stack.reverse_each.group_by { _1[:tagname] }.each do |_, descriptors|
|
|
14
|
-
overlapping_ranges = find_overlapping_styles(descriptors)
|
|
15
|
-
widest_descriptor = overlapping_ranges.max_by { (_1[:start].._1[:finish]).size }
|
|
16
|
-
|
|
17
|
-
applicable_styles = descriptors - overlapping_ranges + [widest_descriptor].compact
|
|
18
|
-
applicable_styles.each(&block)
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def track_start(tagname, current_character_offset)
|
|
23
|
-
style = DraftjsHtml::HtmlDefaults::HTML_STYLE_TAGS_TO_STYLE[tagname]
|
|
24
|
-
@stack.unshift({ tagname: tagname, style: style, start: current_character_offset })
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def track_end(tagname, current_character_offset)
|
|
28
|
-
descriptor_index = @stack.find_index { _1[:tagname] == tagname && !_1[:finish] }
|
|
29
|
-
descriptor = @stack[descriptor_index]
|
|
30
|
-
descriptor[:finish] = current_character_offset
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
private
|
|
34
|
-
|
|
35
|
-
def find_overlapping_styles(descriptors)
|
|
36
|
-
descriptors.select do |candidate_a|
|
|
37
|
-
candidate_range = candidate_a[:start]..candidate_a[:finish]
|
|
38
|
-
(descriptors - [candidate_a]).any? do |other|
|
|
39
|
-
other_range = other[:start]..other[:finish]
|
|
40
|
-
range_overlaps?(candidate_range, other_range)
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def range_overlaps?(candidate_range, other_range)
|
|
46
|
-
other_range.begin == candidate_range.begin || candidate_range.cover?(other_range.begin) || other_range.cover?(candidate_range.begin)
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|