draftjs_html 0.21.0 → 0.22.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/draftjs_html/from_html/char_list.rb +152 -0
- data/lib/draftjs_html/from_html/depth_stack.rb +12 -12
- data/lib/draftjs_html/from_html/pending_block.rb +13 -27
- data/lib/draftjs_html/from_html.rb +1 -1
- 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: 84ac3de7f21860685dbf39a24fb921227a1ccce6d97940acd279976daab15970
|
4
|
+
data.tar.gz: 2d3511eef3134a6cc01cd3da0147b3c347209c6c90ef4f5aaea90c681af979b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2775eac524063149973d4908f9f30f5e9a4d6b734f35e44b459454a95cc6a415a25a1b36daf07af3b34b1bf08b2b96c2980e6be12a9b21ed23c162100688ad03
|
7
|
+
data.tar.gz: dd0c42f5271934a1a17452159e7dbad8b51bd4ad0637a9b87cf4c77b43c6ea690bdda838830d92b1fcd929e985d88958fa5bd5326af89dc3e9db76e7e84a5fe2
|
@@ -0,0 +1,152 @@
|
|
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
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def find_overlapping_styles(descriptors)
|
138
|
+
descriptors.select do |candidate_a|
|
139
|
+
candidate_range = candidate_a[:start]..candidate_a[:finish]
|
140
|
+
(descriptors - [candidate_a]).any? do |other|
|
141
|
+
other_range = other[:start]..other[:finish]
|
142
|
+
range_overlaps?(candidate_range, other_range)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def range_overlaps?(candidate_range, other_range)
|
148
|
+
other_range.begin == candidate_range.begin || candidate_range.cover?(other_range.begin) || other_range.cover?(candidate_range.begin)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -5,7 +5,7 @@ module DraftjsHtml
|
|
5
5
|
@stack = []
|
6
6
|
@nodes = []
|
7
7
|
@list_depth = -1
|
8
|
-
@
|
8
|
+
@active_styles = []
|
9
9
|
end
|
10
10
|
|
11
11
|
def push(tagname, attrs)
|
@@ -26,8 +26,7 @@ module DraftjsHtml
|
|
26
26
|
@nodes.pop
|
27
27
|
end
|
28
28
|
blocks.reverse_each do |pending_block|
|
29
|
-
pending_block.flush_to(draftjs
|
30
|
-
pending_block.apply_entities_to(draftjs)
|
29
|
+
pending_block.flush_to(draftjs)
|
31
30
|
end
|
32
31
|
@list_depth -= 1
|
33
32
|
end
|
@@ -59,34 +58,35 @@ module DraftjsHtml
|
|
59
58
|
next unless user_created_entity
|
60
59
|
|
61
60
|
if content == '' && !user_created_entity[:atomic]
|
62
|
-
current.text_buffer
|
63
|
-
|
61
|
+
current.text_buffer.append(' ', entity: user_created_entity)
|
62
|
+
elsif content == '' && user_created_entity[:atomic]
|
63
|
+
current.text_buffer.append_atomic_entity(user_created_entity)
|
64
|
+
else
|
65
|
+
current.text_buffer.apply_entity(range, user_created_entity)
|
64
66
|
end
|
65
|
-
current.entities << user_created_entity.merge(start: range.begin, finish: range.end)
|
66
67
|
end
|
67
68
|
end
|
68
69
|
|
69
70
|
def style_start(tagname)
|
70
|
-
@
|
71
|
+
@active_styles += [DraftjsHtml::HtmlDefaults::HTML_STYLE_TAGS_TO_STYLE[tagname]]
|
71
72
|
end
|
72
73
|
|
73
74
|
def style_end(tagname)
|
74
|
-
@
|
75
|
+
@active_styles.delete_at(@active_styles.index(DraftjsHtml::HtmlDefaults::HTML_STYLE_TAGS_TO_STYLE[tagname]))
|
75
76
|
end
|
76
77
|
|
77
78
|
def flush_to(draftjs)
|
78
|
-
current.flush_to(draftjs
|
79
|
-
current.apply_entities_to(draftjs)
|
79
|
+
current.flush_to(draftjs)
|
80
80
|
end
|
81
81
|
|
82
82
|
def append_text(chars)
|
83
|
-
current.text_buffer
|
83
|
+
current.text_buffer.append(chars, styles: @active_styles) unless chars.empty?
|
84
84
|
end
|
85
85
|
|
86
86
|
private
|
87
87
|
|
88
88
|
def current_text_buffer
|
89
|
-
current.text_buffer.
|
89
|
+
current.text_buffer.text
|
90
90
|
end
|
91
91
|
|
92
92
|
def current_character_offset
|
@@ -6,7 +6,7 @@ module DraftjsHtml
|
|
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,
|
@@ -17,12 +17,8 @@ module DraftjsHtml
|
|
17
17
|
self[:chars]
|
18
18
|
end
|
19
19
|
|
20
|
-
def clear_text_buffer
|
21
|
-
self[:chars] = []
|
22
|
-
end
|
23
|
-
|
24
20
|
def character_offset
|
25
|
-
text_buffer.
|
21
|
+
text_buffer.size - 1
|
26
22
|
end
|
27
23
|
|
28
24
|
def flushable?
|
@@ -36,31 +32,21 @@ module DraftjsHtml
|
|
36
32
|
self.entities += other_pending_block.entities
|
37
33
|
end
|
38
34
|
|
39
|
-
def flush_to(draftjs
|
35
|
+
def flush_to(draftjs)
|
40
36
|
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
|
37
|
+
text_buffer.each_line do |line|
|
38
|
+
block_type = line.atomic? ? 'atomic' : block_name
|
39
|
+
draftjs.typed_block(block_type, line.text, depth: [depth, 0].max)
|
50
40
|
|
51
|
-
|
52
|
-
|
53
|
-
|
41
|
+
line.entity_ranges.each do |entity_range|
|
42
|
+
entity = entity_range.entity
|
43
|
+
draftjs.apply_entity entity[:type], entity_range.range, data: entity[:data], mutability: entity.fetch(:mutability, 'IMMUTABLE')
|
44
|
+
end
|
54
45
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
if entity[:atomic]
|
59
|
-
draftjs.typed_block('atomic', ' ', depth: [depth, 0].max)
|
60
|
-
range = 0..0
|
46
|
+
line.style_ranges.each do |style_range|
|
47
|
+
draftjs.inline_style(style_range.style, style_range.range)
|
48
|
+
end
|
61
49
|
end
|
62
|
-
|
63
|
-
draftjs.apply_entity entity[:type], range, data: entity[:data], mutability: entity.fetch(:mutability, 'IMMUTABLE')
|
64
50
|
end
|
65
51
|
end
|
66
52
|
|
@@ -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
|
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.22.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
|