panda-editor 0.5.0 → 0.6.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: fbf5326beb171bbb5448b7545bfe87fdef49faac3c82f85c1a64a30cc7aa276d
4
- data.tar.gz: 05c314fcf2d2b2c5cdfe2d4e2684b392bc2d4cd7231c91870a6f6100a1ca7170
3
+ metadata.gz: adac7a7394c996ae17e8c3312f0b20bc8517724aa82397694ecce20e82821280
4
+ data.tar.gz: 595a17b99fde3d04816f1b957ce9c875de521475d69bc9358f3c370e9c380301
5
5
  SHA512:
6
- metadata.gz: cab18e487c3e4021cebf6ffbd7c98a4c15103ca96a53cbbbd97b3b377a99d83d7c81de246f620359317d166b5716b56d3105be01d7ab1930c96a9f103164ac1b
7
- data.tar.gz: 1c3ae8993db2fe42ebd8e4c22c664655aa86961757605d897ca5c4c4553b3c0e4427c203d15b23bfae7ce4d83cfc92d6f02d8aa3dc025f3babb3673bc1850de1
6
+ metadata.gz: 9395e397b2c94eea2d8d8c0a0183a6262a1bfe79dbc9aae23f833a255b214be22ffc6ca4f92f2e2218adadf948410e388c853bea3b3eaf589ed555ba9c1c2df4
7
+ data.tar.gz: 6fc84783464e4761bcf1fec9f162ff7fec33b571bf816d026167514c1d63cc4f640ffc8c0863e765bf58730f0713a7a47ff5f62b99c810a5f1806804b62e7f8c
data/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.6.0] - 2025-11-09
9
+
10
+ ### Added
11
+
12
+ - HTML to EditorJS converter for importing existing HTML content
13
+ - Supports headers (h1-h6), paragraphs, lists (ordered/unordered), quotes, code blocks, tables, and delimiters
14
+ - Preserves inline formatting (bold, italic, links, etc.) as HTML
15
+ - Handles edge cases: empty content, malformed HTML, special characters, whitespace
16
+ - Comprehensive test coverage (22 examples)
17
+ - Available as `Panda::Editor::HtmlToEditorJsConverter.convert(html)`
18
+ - Markdown to EditorJS converter for importing Markdown content
19
+ - Uses Redcarpet to parse markdown to HTML, then converts to EditorJS
20
+ - Supports all standard markdown features: headers, paragraphs, lists, code blocks, tables, blockquotes
21
+ - Includes advanced features: superscript, footnotes, strikethrough, autolinks
22
+ - Security-hardened with noopener/noreferrer on links
23
+ - Comprehensive test coverage (26 examples)
24
+ - Available as `Panda::Editor::MarkdownToEditorJsConverter.convert(markdown)`
25
+ - Added nokogiri as explicit dependency (required for HTML parsing)
26
+
8
27
  ## [0.5.0] - 2025-11-04
9
28
 
10
29
  ### Fixed
data/README.md CHANGED
@@ -55,6 +55,74 @@ renderer = Panda::Editor::Renderer.new(@post.content)
55
55
  <%= raw renderer.render %>
56
56
  ```
57
57
 
58
+ ### Converting Content to EditorJS
59
+
60
+ Panda Editor includes converters for importing existing HTML or Markdown content into EditorJS format:
61
+
62
+ #### From HTML
63
+
64
+ ```ruby
65
+ html = '<h1>Article Title</h1><p>Introduction with <strong>bold</strong> text.</p><ul><li>Point 1</li><li>Point 2</li></ul>'
66
+ editor_data = Panda::Editor::HtmlToEditorJsConverter.convert(html)
67
+
68
+ # Save to your model
69
+ @post.content = editor_data
70
+ @post.save
71
+ ```
72
+
73
+ **Supported HTML elements:**
74
+
75
+ - Headers (h1-h6)
76
+ - Paragraphs with inline formatting (bold, italic, links, etc.)
77
+ - Ordered and unordered lists
78
+ - Blockquotes
79
+ - Code blocks (pre/code)
80
+ - Tables (with or without headers)
81
+ - Horizontal rules (converted to delimiters)
82
+
83
+ #### From Markdown
84
+
85
+ ```ruby
86
+ markdown = <<~MD
87
+ # Article Title
88
+
89
+ Introduction with **bold** and *italic* text.
90
+
91
+ - Point 1
92
+ - Point 2
93
+
94
+ > A famous quote
95
+
96
+ ```ruby
97
+ def hello
98
+ puts "world"
99
+ end
100
+ ```
101
+ MD
102
+
103
+ editor_data = Panda::Editor::MarkdownToEditorJsConverter.convert(markdown)
104
+
105
+ # Save to your model
106
+ @post.content = editor_data
107
+ @post.save
108
+ ```
109
+
110
+ **Supported Markdown features:**
111
+
112
+ - Headers (# through ######)
113
+ - Paragraphs with inline formatting (\*\*bold\*\*, \*italic\*, \`code\`, \~\~strikethrough\~\~)
114
+ - Links (with automatic noopener/noreferrer for security)
115
+ - Ordered and unordered lists
116
+ - Blockquotes
117
+ - Fenced and indented code blocks
118
+ - Tables (GitHub-flavored markdown)
119
+ - Horizontal rules
120
+ - Superscript (^2)
121
+ - Footnotes
122
+ - Automatic URL linking
123
+
124
+ Both converters return a hash in EditorJS format that can be saved directly to your content field.
125
+
58
126
  ### JavaScript Integration
59
127
 
60
128
  In your application.js:
@@ -12,6 +12,12 @@ module Panda
12
12
  g.test_framework :rspec
13
13
  end
14
14
 
15
+ # Eager load converter classes
16
+ config.to_prepare do
17
+ require 'panda/editor/markdown_to_editor_js_converter'
18
+ require 'panda/editor/html_to_editor_js_converter'
19
+ end
20
+
15
21
  initializer 'panda_editor.assets' do |app|
16
22
  next unless app.config.respond_to?(:assets)
17
23
 
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module Panda
6
+ module Editor
7
+ # Converts HTML to EditorJS format
8
+ # Parses HTML and converts it to EditorJS blocks
9
+ class HtmlToEditorJsConverter
10
+ def self.convert(html)
11
+ new(html).convert
12
+ end
13
+
14
+ def initialize(html)
15
+ @html = html
16
+ @blocks = []
17
+ end
18
+
19
+ def convert
20
+ doc = Nokogiri::HTML.fragment(@html)
21
+
22
+ doc.children.each do |node|
23
+ block = node_to_block(node)
24
+ @blocks << block if block
25
+ end
26
+
27
+ {
28
+ time: Time.now.to_i * 1000,
29
+ blocks: @blocks,
30
+ version: "2.28.0"
31
+ }
32
+ end
33
+
34
+ private
35
+
36
+ def node_to_block(node)
37
+ return nil if node.text? && node.text.strip.empty?
38
+
39
+ case node.name
40
+ when "h1", "h2", "h3", "h4", "h5", "h6"
41
+ header_block(node)
42
+ when "p"
43
+ paragraph_block(node)
44
+ when "ul", "ol"
45
+ list_block(node)
46
+ when "blockquote"
47
+ quote_block(node)
48
+ when "pre"
49
+ code_block(node)
50
+ when "table"
51
+ table_block(node)
52
+ when "hr"
53
+ delimiter_block
54
+ when "text"
55
+ # Handle text nodes that aren't wrapped in tags
56
+ text = node.text.strip
57
+ text.empty? ? nil : paragraph_block_from_text(text)
58
+ else
59
+ # For any other node, try to extract text content
60
+ text = node.text.strip
61
+ text.empty? ? nil : paragraph_block_from_text(text)
62
+ end
63
+ end
64
+
65
+ def header_block(node)
66
+ level = node.name[1].to_i
67
+ {
68
+ type: "header",
69
+ data: {
70
+ text: node.inner_html.strip,
71
+ level: level
72
+ }
73
+ }
74
+ end
75
+
76
+ def paragraph_block(node)
77
+ text = node.inner_html.strip
78
+ return nil if text.empty?
79
+
80
+ {
81
+ type: "paragraph",
82
+ data: {
83
+ text: text
84
+ }
85
+ }
86
+ end
87
+
88
+ def paragraph_block_from_text(text)
89
+ {
90
+ type: "paragraph",
91
+ data: {
92
+ text: text
93
+ }
94
+ }
95
+ end
96
+
97
+ def list_block(node)
98
+ style = node.name == "ol" ? "ordered" : "unordered"
99
+ items = node.css("li").map { |li| li.inner_html.strip }
100
+
101
+ {
102
+ type: "list",
103
+ data: {
104
+ style: style,
105
+ items: items
106
+ }
107
+ }
108
+ end
109
+
110
+ def quote_block(node)
111
+ {
112
+ type: "quote",
113
+ data: {
114
+ text: node.inner_html.strip,
115
+ caption: "",
116
+ alignment: "left"
117
+ }
118
+ }
119
+ end
120
+
121
+ def code_block(node)
122
+ code = node.css("code").first
123
+ text = code ? code.text : node.text
124
+
125
+ {
126
+ type: "code",
127
+ data: {
128
+ code: text
129
+ }
130
+ }
131
+ end
132
+
133
+ def table_block(node)
134
+ content = []
135
+
136
+ # Process table rows
137
+ node.css("tr").each do |row|
138
+ cells = row.css("th, td").map { |cell| cell.inner_html.strip }
139
+ content << cells
140
+ end
141
+
142
+ {
143
+ type: "table",
144
+ data: {
145
+ withHeadings: node.css("thead").any? || node.css("th").any?,
146
+ content: content
147
+ }
148
+ }
149
+ end
150
+
151
+ def delimiter_block
152
+ {
153
+ type: "delimiter",
154
+ data: {}
155
+ }
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redcarpet"
4
+
5
+ module Panda
6
+ module Editor
7
+ # Converts Markdown to EditorJS format
8
+ # Uses Redcarpet to parse markdown to HTML, then converts HTML to EditorJS blocks
9
+ class MarkdownToEditorJsConverter
10
+ def self.convert(markdown)
11
+ new(markdown).convert
12
+ end
13
+
14
+ def initialize(markdown)
15
+ @markdown = markdown
16
+ end
17
+
18
+ def convert
19
+ # Step 1: Convert Markdown to HTML using Redcarpet
20
+ html = markdown_to_html
21
+
22
+ # Step 2: Convert HTML to EditorJS using existing converter
23
+ Panda::Editor::HtmlToEditorJsConverter.convert(html)
24
+ end
25
+
26
+ private
27
+
28
+ def markdown_to_html
29
+ renderer = Redcarpet::Render::HTML.new(
30
+ hard_wrap: true,
31
+ link_attributes: {rel: "noopener noreferrer"}
32
+ )
33
+
34
+ markdown_processor = Redcarpet::Markdown.new(
35
+ renderer,
36
+ autolink: true,
37
+ tables: true,
38
+ fenced_code_blocks: true,
39
+ strikethrough: true,
40
+ superscript: true,
41
+ footnotes: true,
42
+ no_intra_emphasis: true,
43
+ space_after_headers: true
44
+ )
45
+
46
+ markdown_processor.render(@markdown)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Panda
4
4
  module Editor
5
- VERSION = '0.5.0'
5
+ VERSION = '0.6.0'
6
6
  end
7
7
  end
data/lib/panda/editor.rb CHANGED
@@ -17,10 +17,12 @@ module Panda
17
17
 
18
18
  class Error < StandardError; end
19
19
 
20
- # Autoload components
21
- autoload :Renderer, 'panda/editor/renderer'
22
- autoload :Content, 'panda/editor/content'
23
- autoload :FootnoteRegistry, 'panda/editor/footnote_registry'
20
+ # Require components
21
+ require_relative 'editor/renderer'
22
+ require_relative 'editor/content'
23
+ require_relative 'editor/footnote_registry'
24
+ require_relative 'editor/markdown_to_editor_js_converter'
25
+ require_relative 'editor/html_to_editor_js_converter'
24
26
 
25
27
  module Blocks
26
28
  autoload :Base, 'panda/editor/blocks/base'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "panda/editor/version"
4
+ require "panda/editor/engine"
5
+ require "panda/editor"
data/panda-editor.gemspec CHANGED
@@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
32
32
 
33
33
  # Rails dependencies
34
34
  spec.add_dependency 'dry-configurable', '~> 1.0'
35
+ spec.add_dependency 'nokogiri', '~> 1.15'
35
36
  spec.add_dependency 'rails', '>= 7.1'
36
37
  spec.add_dependency 'redcarpet', '~> 3.6'
37
38
  spec.add_dependency 'sanitize', '~> 6.0'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: panda-editor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Otaina Limited
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: nokogiri
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.15'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.15'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rails
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -133,6 +147,7 @@ files:
133
147
  - app/services/panda/editor/html_to_editor_js_converter.rb
134
148
  - config/importmap.rb
135
149
  - docs/FOOTNOTES.md
150
+ - lib/panda-editor.rb
136
151
  - lib/panda/editor.rb
137
152
  - lib/panda/editor/asset_loader.rb
138
153
  - lib/panda/editor/blocks/alert.rb
@@ -146,6 +161,8 @@ files:
146
161
  - lib/panda/editor/content.rb
147
162
  - lib/panda/editor/engine.rb
148
163
  - lib/panda/editor/footnote_registry.rb
164
+ - lib/panda/editor/html_to_editor_js_converter.rb
165
+ - lib/panda/editor/markdown_to_editor_js_converter.rb
149
166
  - lib/panda/editor/renderer.rb
150
167
  - lib/panda/editor/version.rb
151
168
  - lib/tasks/assets.rake