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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 662afcc40856fc071f8933862c939e45da447bfcf089287ef486c20eaa9ad9ea
4
- data.tar.gz: e3c86eb5fd81a6daf446d3cd6a2a8394d4db8c2a79a4d7e3dfc9aa0ff1524514
3
+ metadata.gz: c01b8b4b8ca17c90c9d53f7d85caebc7fe6c18aab14a79ff635110d8a0fb81dc
4
+ data.tar.gz: 6e0d85454c9d8ee4e6998e8e398c72c01f6097fc3c5e9b61c165fb07ac01af0f
5
5
  SHA512:
6
- metadata.gz: 91d4da6dd384c6b38fda6edaa8653af2a968b04e53ab3b0bdc4b1305242043f816c25f906508fb6800ab306bbe8967d6608e3a411294152b53c22f375b0cf6dd
7
- data.tar.gz: 8840930b9e1d680477ca458b7b44faff3343dfd1d4129b4e28c931e62c2318dddee8d8c4f8c32bf1f5eaa13e8b542f6704b09ee766c95c9049b9bfa4d37441cf
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
- @style_stack = StyleStack.new
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, @style_stack)
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
- range = range.begin..(range.end+1)
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
- @style_stack.track_start(tagname, current_character_offset + 1)
72
+ @active_styles += [DraftjsHtml::HtmlDefaults::HTML_STYLE_TAGS_TO_STYLE[tagname]]
71
73
  end
72
74
 
73
75
  def style_end(tagname)
74
- @style_stack.track_end(tagname, current_character_offset)
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, @style_stack)
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 << chars unless chars.empty?
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.join
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.join.length - 1
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, styles)
37
+ def flush_to(draftjs)
40
38
  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
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
- clear_text_buffer
52
- styles.clear_finished
53
- end
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
- 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
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DraftjsHtml
4
- VERSION = "0.21.0"
4
+ VERSION = "0.23.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.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-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