draftjs_html 0.4.0 → 0.5.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: 6e7da064863d6ce77e04957db9f87f957db61dbbc622bf853c43b7ee6f61b970
4
- data.tar.gz: 985ea5ed6ec84cb10a62065858b9fa100ff444a8b0d27f181685ea5f1de609a1
3
+ metadata.gz: 947546094b93886a5533fe19720c44671066c08ebe079dd9a628cead629e880e
4
+ data.tar.gz: 280b8abcae0942d236f2352ef8f6aac6fb4a34d51323ed9cb15b8667c8e24d41
5
5
  SHA512:
6
- metadata.gz: 991ad75b614edd4c5a46290ded23422ed8a7691ef2e1c79328daa5e41d96b43595e8ea025709ced743a3fbe44144efbc6032baad0d09ee165cc1527fa173fb1a
7
- data.tar.gz: 15d168a71001ac7fc6dd2505f8fc716c22fb221f5cf25a0b1fc12cc34bd3a9b46c21de5bde8c47d5f4cd643b40c07c219b7b86852cdda7536ce9228c7fc18fb6
6
+ metadata.gz: c0b41f31e0c06a0c9dd6ee08ad7c4cbfd534875130503439ff21777a3145551fd15ac01496e96ec144a96c69ab4e137b4213eefe5f14c1b0b920bf6759e1d1a9
7
+ data.tar.gz: 991cabc0dd8a3ccb1785a34f9c629be7b5bc0ee0ec83498ba0e494a234eb3175c5089d3b44a533caa271dd115a499e710cf2bddc5d6442d181ebf2b980438f6c
data/README.md CHANGED
@@ -59,8 +59,8 @@ raw_draftjs = {
59
59
 
60
60
  DraftjsHtml.to_html(raw_draftjs, options: {
61
61
  entity_style_mappings: {
62
- abc: ->(entity, content) {
63
- %Q{<a href="https://example.com/?id=#{entity.data['user_id']}">#{content}</a>}
62
+ abc: ->(entity, content, *) {
63
+ DraftjsHtml::Node.new('a', { href: "https://example.com/?id=#{entity.data['user_id']}" }, content)
64
64
  },
65
65
  },
66
66
  }) # => <p>Hello <a href="https://example.com/?id=123">@Arya</a></p>
@@ -81,8 +81,9 @@ Defaults to `UTF-8`.
81
81
 
82
82
  Allows the author to specify special mapping functions for entities.
83
83
  By default, we render `LINK` and `IMAGE` entities using the standard `<a>` and `<img>` tags, respectively.
84
- The author may supply a Proc object that returns any object Nokogiri can consume for adding to HTML.
85
- This includes `String`, `Nokogiri::Document::Fragment`, and `Nokogiri::Document` objects.
84
+ The author may supply a `call`-able object that returns a `DraftjsHtml::Node`-able (or similar).
85
+ If returned a String, it's assumed this content is plaintext (or otherwise unsafe) and its content will be coerced to plaintext.
86
+ See the section on HTML Injection protection for more details.
86
87
 
87
88
  #### `:block_type_mapping`
88
89
 
@@ -128,13 +129,44 @@ However, if you "return" `nil` (or `false-y`) from the proc, it will fallback to
128
129
  DraftjsHtml.to_html(raw_draftjs, options: {
129
130
  inline_style_renderer: ->(style_names, content) {
130
131
  next if style_names != ['CUSTOM']
131
- "<pre>#{content}</pre>"
132
+ Nokogiri::XML::Node.new('pre', document).tap do |node|
133
+ node.content = content
134
+ end
132
135
  },
133
136
  })
134
137
 
135
138
  # This would use the default inline style rendering UNLESS the *only* applied style for this range was "CUSTOM"
136
139
  ```
137
140
 
141
+ #### HTML Injection protection
142
+
143
+ Working with user-generated content can be a dangerous thing.
144
+ While it allows for a lot of flexibility, it also creates some potential attack vectors that you need to be aware of.
145
+ We try to take a "safe by default" stance with this library, and not generate HTML that could be dangerous when we know better.
146
+
147
+ To facilitate this, we require a little work from you, dear programmer.
148
+ Namely, when specifying special algorithms for generating entities or inline styles, you need to help us keep you safe.
149
+ You can do this by returning a `Nokogiri::XML::Node` or `DraftjsHtml::Node` from any functions you provide that generate HTML.
150
+ This is similar to Ruby on Rails' `#to_html` method, but rather than a monkeypatch, we chose to provide a "marker class" (classes) that we know are safe.
151
+ These classes will handle escaping, encoding, and otherwise "safe generation" for you.
152
+ If you, on the other hand, return a bare `String` from one of the custom render functions, we assume it's _unsafe_ and encode it.
153
+
154
+ That is, a function like this:
155
+ ```ruby
156
+ ->(entity, content, document) do
157
+ "<p>hi!</p>"
158
+ end
159
+ # will become an HTML-entity escaped string (e.g. "&lt;p&gt;hi!&lt;/p&gt;")
160
+ ```
161
+
162
+ Where, a function like this:
163
+ ```ruby
164
+ ->(entity, content, document) do
165
+ DraftjsHtml::Node.new('p', {}, 'hi!')
166
+ end
167
+ # will nest HTML nodes as you probably want (e.g. "<p>hi!</p>")
168
+ ```
169
+
138
170
  ## Development
139
171
 
140
172
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
@@ -0,0 +1,32 @@
1
+ module DraftjsHtml
2
+ NokogiriNode = Struct.new(:node) do
3
+ def to_nokogiri(_document)
4
+ node
5
+ end
6
+ end
7
+
8
+ StringNode = Struct.new(:raw) do
9
+ def to_nokogiri(document)
10
+ raw
11
+ Nokogiri::XML::Text.new(raw, document)
12
+ end
13
+ end
14
+
15
+ Node = Struct.new(:element_name, :attributes, :content) do
16
+ def self.of(thing)
17
+ case thing
18
+ when Nokogiri::XML::Node then NokogiriNode.new(thing)
19
+ when self.class then thing
20
+ when String then StringNode.new(thing)
21
+ else thing
22
+ end
23
+ end
24
+
25
+ def to_nokogiri(document)
26
+ Nokogiri::XML::Node.new(element_name, document).tap do |node|
27
+ node.content = content
28
+ (attributes || {}).each { |k, v| node[k] = v }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,3 +1,5 @@
1
+ require_relative 'node'
2
+
1
3
  module DraftjsHtml
2
4
  class ToHtml
3
5
  BLOCK_TYPE_TO_HTML = {
@@ -27,28 +29,25 @@ module DraftjsHtml
27
29
  'UNDERLINE' => 'u',
28
30
  }.freeze
29
31
 
30
- DEFAULT_ENTITY_STYLE_FN = ->(_entity, chars) { chars }
32
+ DEFAULT_ENTITY_STYLE_FN = ->(_entity, chars, _doc) { chars }
31
33
  ENTITY_ATTRIBUTE_NAME_MAP = {
32
34
  'className' => 'class',
33
35
  'url' => 'href',
34
36
  }.freeze
35
37
  ENTITY_CONVERSION_MAP = {
36
- 'LINK' => ->(entity, content) {
37
- node = Nokogiri::HTML::DocumentFragment.parse('<a>').children.first
38
- node.content = content
39
- entity.data.slice('url', 'rel', 'target', 'title', 'className').each do |attr, value|
40
- node[ENTITY_ATTRIBUTE_NAME_MAP.fetch(attr, attr)] = value
38
+ 'LINK' => ->(entity, content, *) {
39
+ attributes = entity.data.slice('url', 'rel', 'target', 'title', 'className').each_with_object({}) do |(attr, value), h|
40
+ h[ENTITY_ATTRIBUTE_NAME_MAP.fetch(attr, attr)] = value
41
41
  end
42
42
 
43
- node
43
+ DraftjsHtml::Node.new('a', attributes, content)
44
44
  },
45
- 'IMAGE' => ->(entity, _content) {
46
- node = Nokogiri::HTML::DocumentFragment.parse('<img>').children.first
47
- entity.data.slice('src', 'alt', 'className', 'width', 'height').each do |attr, value|
48
- node[ENTITY_ATTRIBUTE_NAME_MAP.fetch(attr, attr)] = value
45
+ 'IMAGE' => ->(entity, *) {
46
+ attributes = entity.data.slice('src', 'alt', 'className', 'width', 'height').each_with_object({}) do |(attr, value), h|
47
+ h[ENTITY_ATTRIBUTE_NAME_MAP.fetch(attr, attr)] = value
49
48
  end
50
49
 
51
- node
50
+ DraftjsHtml::Node.new('img', attributes)
52
51
  }
53
52
  }.freeze
54
53
 
@@ -94,18 +93,22 @@ module DraftjsHtml
94
93
  end
95
94
  end
96
95
 
97
- def apply_styles_to(html, style_names, text)
98
- return html.parent << text if style_names.empty?
96
+ def apply_styles_to(html, style_names, child)
97
+ return append_child(html, child) if style_names.empty?
99
98
 
100
- custom_render_content = @options[:inline_style_renderer].call(style_names, text)
101
- return html.parent << custom_render_content if custom_render_content
99
+ custom_render_content = @options[:inline_style_renderer].call(style_names, child, @document.parent)
100
+ return append_child(html, custom_render_content) if custom_render_content
102
101
 
103
102
  style, *rest = style_names
104
- html.public_send(style_element_for(style)) do
105
- apply_styles_to(html, rest, text)
103
+ html.public_send(style_element_for(style)) do |builder|
104
+ apply_styles_to(builder, rest, child)
106
105
  end
107
106
  end
108
107
 
108
+ def append_child(nokogiri, child)
109
+ nokogiri.parent.add_child(DraftjsHtml::Node.of(child).to_nokogiri(@document.parent))
110
+ end
111
+
109
112
  def block_element_for(block)
110
113
  return 'br' if block.blank?
111
114
 
@@ -119,7 +122,11 @@ module DraftjsHtml
119
122
  def try_apply_entity_to(draftjs, char_range)
120
123
  entity = draftjs.find_entity(char_range.entity_key)
121
124
  content = char_range.text
122
- content = (@options[:entity_style_mappings][entity.type] || DEFAULT_ENTITY_STYLE_FN).call(entity, content) if entity
125
+ if entity
126
+ style_fn = (@options[:entity_style_mappings][entity.type] || DEFAULT_ENTITY_STYLE_FN)
127
+ content = style_fn.call(entity, content, @document.parent)
128
+ end
129
+
123
130
  content
124
131
  end
125
132
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DraftjsHtml
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: draftjs_html
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - TJ Taylor
@@ -65,6 +65,7 @@ files:
65
65
  - lib/draftjs_html/draftjs/entity.rb
66
66
  - lib/draftjs_html/draftjs/entity_map.rb
67
67
  - lib/draftjs_html/draftjs/to_raw.rb
68
+ - lib/draftjs_html/node.rb
68
69
  - lib/draftjs_html/to_html.rb
69
70
  - lib/draftjs_html/version.rb
70
71
  homepage: https://github.com/dugancathal/draftjs_html