draftjs_html 0.7.0 → 0.9.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: 0e6d6f1d7f1c978841fe029f93f5db47688459ec70f2b33de5ed7c9cfa6d2758
4
- data.tar.gz: 40ee34b8ba65efaf7c3c627046c06bd1e03c32547a17b111cfc661b7bd7a0a7f
3
+ metadata.gz: 4955cd2cbaade2ea9ae659cb65028e4097fb1d8d95d5058bc165e758f40e7794
4
+ data.tar.gz: 5bf0f0fbf4e6e73eb3da8b069fe536505a56db67cfed802c04baa2079a8641ff
5
5
  SHA512:
6
- metadata.gz: 4427355fcb9c1578edc43d67c7fd04ba9c64001dae800996c7124938c0fe2c28d5e4da29e35fda0f2b4659656f829c62dd16b9e482a0e4b6fc780cffe8a1223c
7
- data.tar.gz: 1277ff4825ce7a8bb972c4c0cbca2b3b0f440eceee3e6c00346b78b99d9bc900f8497f4a562b56d2cc8427b545418f578c64358d90618dcb7ce39fff5e196b43
6
+ metadata.gz: a36d06cdc9383a8c27d3d93c08fc59c4a022120aa9951cdf6e9774f0fc77f3e69439ea761a6765469b0f9a4c4879b76b216b465473894b35fbd7e57b5e5ff740
7
+ data.tar.gz: 16c84f495363d08c997b55de7f53b496d8f92760c936a755c1c1960d024bae7e9fb3235bcbcdb32d05649dc2668c16d4833577fce9b9861273a32f7cae33082a
data/README.md CHANGED
@@ -77,6 +77,17 @@ HTML, it's also possible to return `Nokogiri::Node` objects or String objects.
77
77
  Specify the HTML generation encoding.
78
78
  Defaults to `UTF-8`.
79
79
 
80
+ #### `squeeze_newlines`
81
+
82
+ Often times, we'll get text in our blocks that will generate unexpected HTML.
83
+ Most of this is caused by whitespace.
84
+ You can use the `squeeze_newlines` option to collapse consecutive newline/CRLF characters to one, resulting in a single `<br>` tag.
85
+ Defaults to `false`.
86
+
87
+ ```ruby
88
+
89
+ ```
90
+
80
91
  #### `:entity_style_mappings`
81
92
 
82
93
  Allows the author to specify special mapping functions for entities.
@@ -93,12 +104,11 @@ These may be overridden and appended to, like so:
93
104
 
94
105
  ```ruby
95
106
  DraftjsHtml.to_html(raw_draftjs, options: {
96
- block_type_mapping: {
97
- 'unstyled' => 'span',
98
- },
107
+ squeeze_newlines: true,
99
108
  })
100
109
 
101
- # This would generate <span> tags instead of <p> tags for "unstyled" DraftJS blocks.
110
+ # Given a DraftJS block like: `{ text: 'Hi!\n\n\nWelcome to Westeros!\n\n\n'}`
111
+ # This would generate `<p>Hi!<br>Welcome to Westeros!<br></p>`
102
112
  ```
103
113
 
104
114
  #### `:inline_style_mapping`
@@ -2,12 +2,13 @@
2
2
 
3
3
  module DraftjsHtml
4
4
  module Draftjs
5
- Block = Struct.new(:key, :text, :type, :inline_style_ranges, :raw_entity_ranges, keyword_init: true) do
5
+ Block = Struct.new(:key, :text, :type, :depth, :inline_style_ranges, :raw_entity_ranges, keyword_init: true) do
6
6
  def self.parse(raw)
7
7
  new(
8
8
  key: raw['key'],
9
9
  text: raw['text'],
10
10
  type: raw['type'],
11
+ depth: raw['depth'],
11
12
  inline_style_ranges: Array(raw['inlineStyleRanges']),
12
13
  raw_entity_ranges: Array(raw['entityRanges']),
13
14
  )
@@ -0,0 +1,50 @@
1
+ module DraftjsHtml
2
+ module HtmlDefaults
3
+ BLOCK_TYPE_TO_HTML = {
4
+ 'unstyled' => 'p',
5
+ 'paragraph' => 'p',
6
+ 'header-one' => 'h1',
7
+ 'header-two' => 'h2',
8
+ 'header-three' => 'h3',
9
+ 'header-four' => 'h4',
10
+ 'header-five' => 'h5',
11
+ 'header-six' => 'h6',
12
+ 'blockquote' => 'blockquote',
13
+ 'code-block' => 'code',
14
+ 'ordered-list-item' => 'li',
15
+ 'unordered-list-item' => 'li',
16
+ 'atomic' => 'figure',
17
+ }.freeze
18
+
19
+ STYLE_MAP = {
20
+ 'BOLD' => 'b',
21
+ 'ITALIC' => 'i',
22
+ 'STRIKETHROUGH' => 'del',
23
+ 'UNDERLINE' => 'u',
24
+ }.freeze
25
+
26
+ ENTITY_ATTRIBUTE_NAME_MAP = {
27
+ 'className' => 'class',
28
+ 'url' => 'href',
29
+ }.freeze
30
+
31
+ DEFAULT_ENTITY_STYLE_FN = ->(_entity, chars, _doc) { chars }
32
+
33
+ ENTITY_CONVERSION_MAP = {
34
+ 'LINK' => ->(entity, content, *) {
35
+ attributes = entity.data.slice('url', 'rel', 'target', 'title', 'className').each_with_object({}) do |(attr, value), h|
36
+ h[ENTITY_ATTRIBUTE_NAME_MAP.fetch(attr, attr)] = value
37
+ end
38
+
39
+ DraftjsHtml::Node.new('a', attributes, content)
40
+ },
41
+ 'IMAGE' => ->(entity, *) {
42
+ attributes = entity.data.slice('src', 'alt', 'className', 'width', 'height').each_with_object({}) do |(attr, value), h|
43
+ h[ENTITY_ATTRIBUTE_NAME_MAP.fetch(attr, attr)] = value
44
+ end
45
+
46
+ DraftjsHtml::Node.new('img', attributes)
47
+ }
48
+ }.freeze
49
+ end
50
+ end
@@ -0,0 +1,65 @@
1
+ module DraftjsHtml
2
+ # This class manages the depth and nesting of the myriad HTML tags generated by DraftjsHtml::ToHtml.
3
+ # It is intended to be a private implementation detail.
4
+ class HtmlDepth # :nodoc:
5
+ BLOCK_TYPE_TO_HTML_WRAPPER = {
6
+ 'code-block' => 'pre',
7
+ 'ordered-list-item' => 'ol',
8
+ 'unordered-list-item' => 'ul',
9
+ }.freeze
10
+
11
+ attr_reader :body
12
+
13
+ def initialize(body)
14
+ @current_depth = 0
15
+ @body = body
16
+ @previous_parents = [body.parent]
17
+ end
18
+
19
+ def apply(block)
20
+ new_wrapper_tag = BLOCK_TYPE_TO_HTML_WRAPPER[block.type]
21
+ if body.parent.name != new_wrapper_tag || block.depth != @current_depth
22
+ if @current_depth < block.depth
23
+ push_depth(body, new_wrapper_tag)
24
+ elsif @current_depth > block.depth
25
+ pop_depth(body, times: @current_depth - block.depth)
26
+ pop_nesting(body) unless new_wrapper_tag
27
+ elsif new_wrapper_tag
28
+ push_nesting(body, new_wrapper_tag)
29
+ elsif @previous_parents.size > 1
30
+ pop_nesting(body)
31
+ end
32
+ @current_depth = block.depth
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def push_depth(builder, tagname)
39
+ @previous_parents << builder.parent
40
+ builder.parent = builder.parent.last_element_child
41
+ push_nesting(builder, tagname)
42
+ end
43
+
44
+ def push_nesting(builder, tagname)
45
+ node = create_child(builder, tagname)
46
+ @previous_parents << builder.parent
47
+ builder.parent = node
48
+ end
49
+
50
+ def pop_depth(builder, times:)
51
+ times.times do
52
+ pop_nesting(builder)
53
+ pop_nesting(builder)
54
+ end
55
+ end
56
+
57
+ def pop_nesting(builder)
58
+ builder.parent = @previous_parents.pop
59
+ end
60
+
61
+ def create_child(builder, tagname)
62
+ builder.parent.add_child(builder.doc.create_element(tagname))
63
+ end
64
+ end
65
+ end
@@ -18,7 +18,7 @@ module DraftjsHtml
18
18
  lines = raw.lines
19
19
  text_nodes = lines.flat_map.with_index do |text, i|
20
20
  nodes = [Nokogiri::XML::Text.new(text.chomp, document)]
21
- nodes << Nokogiri::XML::Node.new('br', document) if i < lines.size - 1
21
+ nodes << Nokogiri::XML::Node.new('br', document) if text.end_with?("\n")
22
22
  nodes
23
23
  end
24
24
 
@@ -0,0 +1,25 @@
1
+ module DraftjsHtml
2
+ class OverrideableMap
3
+ def initialize(defaults = {})
4
+ @map = defaults.dup.transform_keys(&:to_s)
5
+ end
6
+
7
+ def with_overrides(overrides)
8
+ @map.merge!((overrides || {}).transform_keys(&:to_s))
9
+ self
10
+ end
11
+
12
+ def with_default(default)
13
+ @default = default
14
+ self
15
+ end
16
+
17
+ def value_of!(key)
18
+ @map.fetch(key.to_s)
19
+ end
20
+
21
+ def value_of(key)
22
+ @map.fetch(key.to_s, @default)
23
+ end
24
+ end
25
+ end
@@ -1,59 +1,14 @@
1
1
  require_relative 'node'
2
+ require_relative 'html_depth'
3
+ require_relative 'html_defaults'
4
+ require_relative 'overrideable_map'
2
5
 
3
6
  module DraftjsHtml
4
7
  class ToHtml
5
- BLOCK_TYPE_TO_HTML = {
6
- 'unstyled' => 'p',
7
- 'paragraph' => 'p',
8
- 'header-one' => 'h1',
9
- 'header-two' => 'h2',
10
- 'header-three' => 'h3',
11
- 'header-four' => 'h4',
12
- 'header-five' => 'h5',
13
- 'header-six' => 'h6',
14
- 'blockquote' => 'blockquote',
15
- 'code-block' => 'code',
16
- 'ordered-list-item' => 'li',
17
- 'unordered-list-item' => 'li',
18
- 'atomic' => 'figure',
19
- }.freeze
20
- BLOCK_TYPE_TO_HTML_WRAPPER = {
21
- 'code-block' => 'pre',
22
- 'ordered-list-item' => 'ol',
23
- 'unordered-list-item' => 'ul',
24
- }.freeze
25
- STYLE_MAP = {
26
- 'BOLD' => 'b',
27
- 'ITALIC' => 'i',
28
- 'STRIKETHROUGH' => 'del',
29
- 'UNDERLINE' => 'u',
30
- }.freeze
31
-
32
- DEFAULT_ENTITY_STYLE_FN = ->(_entity, chars, _doc) { chars }
33
- ENTITY_ATTRIBUTE_NAME_MAP = {
34
- 'className' => 'class',
35
- 'url' => 'href',
36
- }.freeze
37
- ENTITY_CONVERSION_MAP = {
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
- end
42
-
43
- DraftjsHtml::Node.new('a', attributes, content)
44
- },
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
48
- end
49
-
50
- DraftjsHtml::Node.new('img', attributes)
51
- }
52
- }.freeze
53
-
54
8
  def initialize(options)
55
9
  @options = ensure_options!(options)
56
10
  @document = Nokogiri::HTML::Builder.new(encoding: @options.fetch(:encoding, 'UTF-8'))
11
+ @html_depth = HtmlDepth.new(@document)
57
12
  end
58
13
 
59
14
  def convert(raw_draftjs)
@@ -61,13 +16,14 @@ module DraftjsHtml
61
16
 
62
17
  @document.html do |html|
63
18
  html.body do |body|
64
- @previous_parent = body.parent
19
+ @previous_parents = [body.parent]
65
20
 
66
21
  draftjs.blocks.each do |block|
67
- ensure_nesting_depth(block, body)
22
+ @html_depth.apply(block)
68
23
 
69
24
  body.public_send(block_element_for(block)) do |block_body|
70
25
  block.each_range do |char_range|
26
+ squeeze_newlines(char_range)
71
27
  content = try_apply_entity_to(draftjs, char_range)
72
28
 
73
29
  apply_styles_to(block_body, char_range.style_names, Node.of(content))
@@ -82,15 +38,8 @@ module DraftjsHtml
82
38
 
83
39
  private
84
40
 
85
- def ensure_nesting_depth(block, body)
86
- new_wrapper_tag = BLOCK_TYPE_TO_HTML_WRAPPER[block.type]
87
- if body.parent.name != new_wrapper_tag
88
- if new_wrapper_tag
89
- push_nesting(body, new_wrapper_tag)
90
- else
91
- pop_nesting(body)
92
- end
93
- end
41
+ def squeeze_newlines(char_range)
42
+ char_range.text = @options[:newline_squeezer].call(char_range.text.chomp)
94
43
  end
95
44
 
96
45
  def apply_styles_to(html, style_names, child)
@@ -112,42 +61,33 @@ module DraftjsHtml
112
61
  def block_element_for(block)
113
62
  return 'br' if block.blank?
114
63
 
115
- @options[:block_type_mapping].fetch(block.type)
64
+ @options[:block_type_mapping].value_of!(block.type)
116
65
  end
117
66
 
118
67
  def style_element_for(style)
119
- @options[:inline_style_mapping][style]
68
+ @options[:inline_style_mapping].value_of!(style)
120
69
  end
121
70
 
122
71
  def try_apply_entity_to(draftjs, char_range)
123
72
  entity = draftjs.find_entity(char_range.entity_key)
124
73
  content = char_range.text
125
74
  if entity
126
- style_fn = (@options[:entity_style_mappings][entity.type] || DEFAULT_ENTITY_STYLE_FN)
75
+ style_fn = @options[:entity_style_mappings].value_of(entity.type)
127
76
  content = style_fn.call(entity, Node.of(content), @document.parent)
128
77
  end
129
78
 
130
79
  content
131
80
  end
132
81
 
133
- def push_nesting(builder, tagname)
134
- node = create_child(builder, tagname)
135
- @previous_parent = builder.parent
136
- builder.parent = node
137
- end
138
-
139
- def pop_nesting(builder)
140
- builder.parent = @previous_parent
141
- end
142
-
143
- def create_child(builder, tagname)
144
- builder.parent.add_child(builder.doc.create_element(tagname))
145
- end
146
-
147
82
  def ensure_options!(opts)
148
- opts[:entity_style_mappings] = ENTITY_CONVERSION_MAP.merge(opts[:entity_style_mappings] || {}).transform_keys(&:to_s)
149
- opts[:block_type_mapping] = BLOCK_TYPE_TO_HTML.merge(opts[:block_type_mapping] || {})
150
- opts[:inline_style_mapping] = STYLE_MAP.merge(opts[:inline_style_mapping] || {}).transform_keys(&:to_s)
83
+ opts[:entity_style_mappings] = OverrideableMap.new(HtmlDefaults::ENTITY_CONVERSION_MAP)
84
+ .with_overrides(opts[:entity_style_mappings])
85
+ .with_default(HtmlDefaults::DEFAULT_ENTITY_STYLE_FN)
86
+ opts[:block_type_mapping] = OverrideableMap.new(HtmlDefaults::BLOCK_TYPE_TO_HTML)
87
+ .with_overrides(opts[:block_type_mapping])
88
+ opts[:newline_squeezer] = opts[:squeeze_newlines] ? ->(text) { text.gsub(/(\n|\r\n)+/, "\n") } : ->(text) { text }
89
+ opts[:inline_style_mapping] = OverrideableMap.new(HtmlDefaults::STYLE_MAP)
90
+ .with_overrides(opts[:inline_style_mapping])
151
91
  opts[:inline_style_renderer] ||= ->(*) { nil }
152
92
  opts
153
93
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DraftjsHtml
4
- VERSION = "0.7.0"
4
+ VERSION = "0.9.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.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - TJ Taylor
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-10-07 00:00:00.000000000 Z
11
+ date: 2022-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -65,7 +65,10 @@ 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/html_defaults.rb
69
+ - lib/draftjs_html/html_depth.rb
68
70
  - lib/draftjs_html/node.rb
71
+ - lib/draftjs_html/overrideable_map.rb
69
72
  - lib/draftjs_html/to_html.rb
70
73
  - lib/draftjs_html/version.rb
71
74
  homepage: https://github.com/dugancathal/draftjs_html
@@ -75,7 +78,7 @@ metadata:
75
78
  homepage_uri: https://github.com/dugancathal/draftjs_html
76
79
  source_code_uri: https://github.com/dugancathal/draftjs_html
77
80
  changelog_uri: https://github.com/dugancathal/draftjs_html/tree/main/CHANGELOG.md
78
- post_install_message:
81
+ post_install_message:
79
82
  rdoc_options: []
80
83
  require_paths:
81
84
  - lib
@@ -90,8 +93,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
93
  - !ruby/object:Gem::Version
91
94
  version: '0'
92
95
  requirements: []
93
- rubygems_version: 3.1.6
94
- signing_key:
96
+ rubygems_version: 3.1.2
97
+ signing_key:
95
98
  specification_version: 4
96
99
  summary: A tool for converting DraftJS JSON to HTML (and back again)
97
100
  test_files: []