draftjs_html 0.21.0 → 0.22.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 662afcc40856fc071f8933862c939e45da447bfcf089287ef486c20eaa9ad9ea
4
- data.tar.gz: e3c86eb5fd81a6daf446d3cd6a2a8394d4db8c2a79a4d7e3dfc9aa0ff1524514
3
+ metadata.gz: 84ac3de7f21860685dbf39a24fb921227a1ccce6d97940acd279976daab15970
4
+ data.tar.gz: 2d3511eef3134a6cc01cd3da0147b3c347209c6c90ef4f5aaea90c681af979b1
5
5
  SHA512:
6
- metadata.gz: 91d4da6dd384c6b38fda6edaa8653af2a968b04e53ab3b0bdc4b1305242043f816c25f906508fb6800ab306bbe8967d6608e3a411294152b53c22f375b0cf6dd
7
- data.tar.gz: 8840930b9e1d680477ca458b7b44faff3343dfd1d4129b4e28c931e62c2318dddee8d8c4f8c32bf1f5eaa13e8b542f6704b09ee766c95c9049b9bfa4d37441cf
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
- @style_stack = StyleStack.new
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, @style_stack)
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
- range = range.begin..(range.end+1)
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
- @style_stack.track_start(tagname, current_character_offset + 1)
71
+ @active_styles += [DraftjsHtml::HtmlDefaults::HTML_STYLE_TAGS_TO_STYLE[tagname]]
71
72
  end
72
73
 
73
74
  def style_end(tagname)
74
- @style_stack.track_end(tagname, current_character_offset)
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, @style_stack)
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 << chars unless chars.empty?
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.join
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.join.length - 1
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, styles)
35
+ def flush_to(draftjs)
40
36
  if text_buffer.any?
41
- chars.join.lines.each do |line|
42
- draftjs.typed_block(block_name, line.chomp, depth: [depth, 0].max)
43
- end
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
- clear_text_buffer
52
- styles.clear_finished
53
- end
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
- def apply_entities_to(draftjs)
56
- Array(entities).each do |entity|
57
- range = entity[:start]..entity[:finish]
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DraftjsHtml
4
- VERSION = "0.21.0"
4
+ VERSION = "0.22.0"
5
5
  end
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.21.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-20 00:00:00.000000000 Z
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.1.6
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