draftjs_html 0.21.0 → 0.23.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|