panda-editor 0.3.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 +4 -4
- data/CHANGELOG.md +25 -0
- data/README.md +24 -0
- data/app/services/panda/editor/html_to_editor_js_converter.rb +68 -68
- data/config/importmap.rb +9 -9
- data/docs/FOOTNOTES.md +51 -2
- data/lib/panda/editor/asset_loader.rb +30 -32
- data/lib/panda/editor/blocks/alert.rb +10 -10
- data/lib/panda/editor/blocks/base.rb +1 -1
- data/lib/panda/editor/blocks/header.rb +2 -2
- data/lib/panda/editor/blocks/image.rb +11 -11
- data/lib/panda/editor/blocks/list.rb +5 -5
- data/lib/panda/editor/blocks/paragraph.rb +10 -10
- data/lib/panda/editor/blocks/quote.rb +6 -6
- data/lib/panda/editor/blocks/table.rb +6 -6
- data/lib/panda/editor/content.rb +8 -8
- data/lib/panda/editor/engine.rb +7 -9
- data/lib/panda/editor/footnote_registry.rb +48 -6
- data/lib/panda/editor/renderer.rb +33 -32
- data/lib/panda/editor/version.rb +1 -1
- data/lib/panda/editor.rb +14 -14
- data/lib/tasks/assets.rake +28 -28
- data/panda-editor.gemspec +23 -22
- metadata +29 -18
|
@@ -5,19 +5,19 @@ module Panda
|
|
|
5
5
|
module Blocks
|
|
6
6
|
class Image < Base
|
|
7
7
|
def render
|
|
8
|
-
url = data[
|
|
9
|
-
caption = sanitize(data[
|
|
10
|
-
with_border = data[
|
|
11
|
-
with_background = data[
|
|
12
|
-
stretched = data[
|
|
8
|
+
url = data['url']
|
|
9
|
+
caption = sanitize(data['caption'])
|
|
10
|
+
with_border = data['withBorder']
|
|
11
|
+
with_background = data['withBackground']
|
|
12
|
+
stretched = data['stretched']
|
|
13
13
|
|
|
14
|
-
css_classes = [
|
|
15
|
-
css_classes <<
|
|
16
|
-
css_classes <<
|
|
17
|
-
css_classes <<
|
|
14
|
+
css_classes = ['prose']
|
|
15
|
+
css_classes << 'border' if with_border
|
|
16
|
+
css_classes << 'bg-gray-100' if with_background
|
|
17
|
+
css_classes << 'w-full' if stretched
|
|
18
18
|
|
|
19
19
|
html_safe(<<~HTML)
|
|
20
|
-
<figure class="#{css_classes.join(
|
|
20
|
+
<figure class="#{css_classes.join(' ')}">
|
|
21
21
|
<img src="#{url}" alt="#{caption}" />
|
|
22
22
|
#{caption_element(caption)}
|
|
23
23
|
</figure>
|
|
@@ -27,7 +27,7 @@ module Panda
|
|
|
27
27
|
private
|
|
28
28
|
|
|
29
29
|
def caption_element(caption)
|
|
30
|
-
return
|
|
30
|
+
return '' if caption.blank?
|
|
31
31
|
|
|
32
32
|
"<figcaption>#{caption}</figcaption>"
|
|
33
33
|
end
|
|
@@ -5,10 +5,10 @@ module Panda
|
|
|
5
5
|
module Blocks
|
|
6
6
|
class List < Base
|
|
7
7
|
def render
|
|
8
|
-
list_type =
|
|
8
|
+
list_type = data['style'] == 'ordered' ? 'ol' : 'ul'
|
|
9
9
|
html_safe(
|
|
10
10
|
"<#{list_type}>" \
|
|
11
|
-
"#{render_items(data[
|
|
11
|
+
"#{render_items(data['items'])}" \
|
|
12
12
|
"</#{list_type}>"
|
|
13
13
|
)
|
|
14
14
|
end
|
|
@@ -17,14 +17,14 @@ module Panda
|
|
|
17
17
|
|
|
18
18
|
def render_items(items)
|
|
19
19
|
items.map do |item|
|
|
20
|
-
content = item.is_a?(Hash) ? item[
|
|
21
|
-
nested =
|
|
20
|
+
content = item.is_a?(Hash) ? item['content'] : item
|
|
21
|
+
nested = item.is_a?(Hash) && item['items'].present? ? render_nested(item['items']) : ''
|
|
22
22
|
"<li>#{sanitize(content)}#{nested}</li>"
|
|
23
23
|
end.join
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def render_nested(items)
|
|
27
|
-
self.class.new({
|
|
27
|
+
self.class.new({ 'items' => items, 'style' => data['style'] }).render
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
end
|
|
@@ -5,10 +5,10 @@ module Panda
|
|
|
5
5
|
module Blocks
|
|
6
6
|
class Paragraph < Base
|
|
7
7
|
def render
|
|
8
|
-
content = sanitize(data[
|
|
9
|
-
return
|
|
8
|
+
content = sanitize(data['text'])
|
|
9
|
+
return '' if content.blank?
|
|
10
10
|
|
|
11
|
-
content = inject_footnotes(content) if data[
|
|
11
|
+
content = inject_footnotes(content) if data['footnotes'].present?
|
|
12
12
|
|
|
13
13
|
html_safe("<p>#{content}</p>")
|
|
14
14
|
end
|
|
@@ -16,15 +16,15 @@ module Panda
|
|
|
16
16
|
private
|
|
17
17
|
|
|
18
18
|
def inject_footnotes(text)
|
|
19
|
-
return text unless data[
|
|
19
|
+
return text unless data['footnotes'].is_a?(Array)
|
|
20
20
|
|
|
21
21
|
# Sort footnotes by position in descending order to avoid position shifts
|
|
22
|
-
footnotes = data[
|
|
22
|
+
footnotes = data['footnotes'].sort_by { |fn| -fn['position'].to_i }
|
|
23
23
|
|
|
24
24
|
footnotes.each do |footnote|
|
|
25
|
-
position = footnote[
|
|
25
|
+
position = footnote['position'].to_i
|
|
26
26
|
# Skip if position is beyond text length
|
|
27
|
-
next if position
|
|
27
|
+
next if position.negative? || position > text.length
|
|
28
28
|
|
|
29
29
|
# Register footnote with renderer's footnote registry
|
|
30
30
|
footnote_number = register_footnote(footnote)
|
|
@@ -34,7 +34,7 @@ module Panda
|
|
|
34
34
|
marker = "<sup id=\"fnref:#{footnote_number}\"><a href=\"#fn:#{footnote_number}\" class=\"footnote\">#{footnote_number}</a></sup>"
|
|
35
35
|
|
|
36
36
|
# Insert marker at position
|
|
37
|
-
text
|
|
37
|
+
text.insert(position, marker)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
text
|
|
@@ -44,8 +44,8 @@ module Panda
|
|
|
44
44
|
return nil unless options[:footnote_registry]
|
|
45
45
|
|
|
46
46
|
options[:footnote_registry].add(
|
|
47
|
-
id: footnote[
|
|
48
|
-
content: footnote[
|
|
47
|
+
id: footnote['id'],
|
|
48
|
+
content: footnote['content']
|
|
49
49
|
)
|
|
50
50
|
end
|
|
51
51
|
end
|
|
@@ -5,15 +5,15 @@ module Panda
|
|
|
5
5
|
module Blocks
|
|
6
6
|
class Quote < Base
|
|
7
7
|
def render
|
|
8
|
-
text = data[
|
|
9
|
-
caption = data[
|
|
10
|
-
alignment = data[
|
|
8
|
+
text = data['text']
|
|
9
|
+
caption = data['caption']
|
|
10
|
+
alignment = data['alignment'] || 'left'
|
|
11
11
|
|
|
12
12
|
# Build the HTML structure
|
|
13
13
|
html = "<figure class=\"text-#{alignment}\">" \
|
|
14
14
|
"<blockquote>#{wrap_text_in_p(text)}</blockquote>" \
|
|
15
15
|
"#{caption_element(caption)}" \
|
|
16
|
-
|
|
16
|
+
'</figure>'
|
|
17
17
|
|
|
18
18
|
# Return raw HTML - validation will be handled by the main renderer if enabled
|
|
19
19
|
html_safe(html)
|
|
@@ -24,7 +24,7 @@ module Panda
|
|
|
24
24
|
def wrap_text_in_p(text)
|
|
25
25
|
# Only wrap in <p> if it's not already wrapped
|
|
26
26
|
text = sanitize(text)
|
|
27
|
-
if text.start_with?(
|
|
27
|
+
if text.start_with?('<p>') && text.end_with?('</p>')
|
|
28
28
|
text
|
|
29
29
|
else
|
|
30
30
|
"<p>#{text}</p>"
|
|
@@ -32,7 +32,7 @@ module Panda
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def caption_element(caption)
|
|
35
|
-
return
|
|
35
|
+
return '' if caption.blank?
|
|
36
36
|
|
|
37
37
|
"<figcaption>#{sanitize(caption)}</figcaption>"
|
|
38
38
|
end
|
|
@@ -5,8 +5,8 @@ module Panda
|
|
|
5
5
|
module Blocks
|
|
6
6
|
class Table < Base
|
|
7
7
|
def render
|
|
8
|
-
content = data[
|
|
9
|
-
with_headings = data[
|
|
8
|
+
content = data['content']
|
|
9
|
+
with_headings = data['withHeadings']
|
|
10
10
|
|
|
11
11
|
html_safe(<<~HTML)
|
|
12
12
|
<div class="overflow-x-auto">
|
|
@@ -25,10 +25,10 @@ module Panda
|
|
|
25
25
|
|
|
26
26
|
while index < content.length
|
|
27
27
|
rows << if index.zero? && with_headings
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
render_header_row(content[index])
|
|
29
|
+
else
|
|
30
|
+
render_data_row(content[index])
|
|
31
|
+
end
|
|
32
32
|
index += 1
|
|
33
33
|
end
|
|
34
34
|
|
data/lib/panda/editor/content.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require 'json'
|
|
4
4
|
|
|
5
5
|
module Panda
|
|
6
6
|
module Editor
|
|
@@ -36,21 +36,21 @@ module Panda
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def generate_cached_content
|
|
39
|
-
renderer_options = {autolink_urls: true}
|
|
39
|
+
renderer_options = { autolink_urls: true }
|
|
40
40
|
|
|
41
41
|
if content.is_a?(String)
|
|
42
42
|
begin
|
|
43
43
|
parsed_content = JSON.parse(content)
|
|
44
|
-
self.cached_content = if parsed_content.is_a?(Hash) && parsed_content[
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
self.cached_content = if parsed_content.is_a?(Hash) && parsed_content['blocks'].present?
|
|
45
|
+
Panda::Editor::Renderer.new(parsed_content, renderer_options).render
|
|
46
|
+
else
|
|
47
|
+
content
|
|
48
|
+
end
|
|
49
49
|
rescue JSON::ParserError
|
|
50
50
|
# If it's not JSON, treat it as plain text
|
|
51
51
|
self.cached_content = content
|
|
52
52
|
end
|
|
53
|
-
elsif content.is_a?(Hash) && content[
|
|
53
|
+
elsif content.is_a?(Hash) && content['blocks'].present?
|
|
54
54
|
# Process EditorJS content
|
|
55
55
|
self.cached_content = Panda::Editor::Renderer.new(content, renderer_options).render
|
|
56
56
|
else
|
data/lib/panda/editor/engine.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require 'rails'
|
|
4
|
+
require 'sanitize'
|
|
5
5
|
|
|
6
6
|
module Panda
|
|
7
7
|
module Editor
|
|
@@ -12,18 +12,16 @@ module Panda
|
|
|
12
12
|
g.test_framework :rspec
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
initializer
|
|
15
|
+
initializer 'panda_editor.assets' do |app|
|
|
16
16
|
next unless app.config.respond_to?(:assets)
|
|
17
17
|
|
|
18
|
-
app.config.assets.paths << root.join(
|
|
19
|
-
app.config.assets.paths << root.join(
|
|
18
|
+
app.config.assets.paths << root.join('app/javascript')
|
|
19
|
+
app.config.assets.paths << root.join('public')
|
|
20
20
|
app.config.assets.precompile += %w[panda/editor/*.js panda/editor/*.css]
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
initializer
|
|
24
|
-
if app.config.respond_to?(:importmap)
|
|
25
|
-
app.config.importmap.paths << root.join("config/importmap.rb")
|
|
26
|
-
end
|
|
23
|
+
initializer 'panda_editor.importmap', before: 'importmap' do |app|
|
|
24
|
+
app.config.importmap.paths << root.join('config/importmap.rb') if app.config.respond_to?(:importmap)
|
|
27
25
|
end
|
|
28
26
|
end
|
|
29
27
|
end
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'redcarpet'
|
|
4
|
+
|
|
3
5
|
module Panda
|
|
4
6
|
module Editor
|
|
5
7
|
class FootnoteRegistry
|
|
6
8
|
attr_reader :footnotes
|
|
7
9
|
|
|
8
|
-
def initialize(autolink_urls: false)
|
|
10
|
+
def initialize(autolink_urls: false, markdown: false)
|
|
9
11
|
@footnotes = []
|
|
10
12
|
@footnote_ids = {}
|
|
11
13
|
@autolink_urls = autolink_urls
|
|
14
|
+
@markdown = markdown
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
def add(id:, content:)
|
|
@@ -16,7 +19,7 @@ module Panda
|
|
|
16
19
|
return @footnote_ids[id] if @footnote_ids[id]
|
|
17
20
|
|
|
18
21
|
# Add new footnote
|
|
19
|
-
@footnotes << {id: id, content: content}
|
|
22
|
+
@footnotes << { id: id, content: content }
|
|
20
23
|
number = @footnotes.length
|
|
21
24
|
|
|
22
25
|
# Cache the number for this ID
|
|
@@ -26,11 +29,11 @@ module Panda
|
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
def render_sources_section
|
|
29
|
-
return
|
|
32
|
+
return '' if @footnotes.empty?
|
|
30
33
|
|
|
31
34
|
footnote_items = @footnotes.map.with_index do |footnote, index|
|
|
32
35
|
number = index + 1
|
|
33
|
-
content =
|
|
36
|
+
content = process_content(footnote[:content])
|
|
34
37
|
<<~HTML.strip
|
|
35
38
|
<li id="fn:#{number}">
|
|
36
39
|
<p>
|
|
@@ -66,6 +69,45 @@ module Panda
|
|
|
66
69
|
|
|
67
70
|
private
|
|
68
71
|
|
|
72
|
+
def process_content(content)
|
|
73
|
+
# Apply markdown processing if enabled
|
|
74
|
+
content = render_markdown(content) if @markdown
|
|
75
|
+
|
|
76
|
+
# Apply URL autolinking if enabled
|
|
77
|
+
# Note: Markdown already includes autolink, but custom autolink_urls can still be used
|
|
78
|
+
# if needed for additional URL patterns. The autolink_urls method skips already-linked URLs.
|
|
79
|
+
content = autolink_urls(content) if @autolink_urls
|
|
80
|
+
|
|
81
|
+
content
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def render_markdown(text)
|
|
85
|
+
# Configure Redcarpet with safe options for footnotes
|
|
86
|
+
renderer = Redcarpet::Render::HTML.new(
|
|
87
|
+
filter_html: false,
|
|
88
|
+
no_images: true,
|
|
89
|
+
no_styles: true,
|
|
90
|
+
safe_links_only: true,
|
|
91
|
+
link_attributes: { target: '_blank', rel: 'noopener noreferrer' }
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
markdown = Redcarpet::Markdown.new(
|
|
95
|
+
renderer,
|
|
96
|
+
autolink: true,
|
|
97
|
+
space_after_headers: true,
|
|
98
|
+
fenced_code_blocks: false,
|
|
99
|
+
no_intra_emphasis: true,
|
|
100
|
+
strikethrough: true,
|
|
101
|
+
superscript: false,
|
|
102
|
+
underline: false
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Render markdown and strip the wrapping <p> tags if present
|
|
106
|
+
# since we're already wrapping in <p> tags in the template
|
|
107
|
+
html = markdown.render(text).strip
|
|
108
|
+
html.gsub(%r{^<p>(.*)</p>$}m, '\1')
|
|
109
|
+
end
|
|
110
|
+
|
|
69
111
|
def autolink_urls(text)
|
|
70
112
|
# Regex to match URLs that aren't already in <a> tags
|
|
71
113
|
# Matches http://, https://, and other common protocols
|
|
@@ -80,12 +122,12 @@ module Panda
|
|
|
80
122
|
# Don't replace URLs that are already in <a> tags
|
|
81
123
|
text.gsub(url_pattern) do |url|
|
|
82
124
|
# Skip if this URL is already part of an href attribute
|
|
83
|
-
before_match =
|
|
125
|
+
before_match = ::Regexp.last_match.pre_match
|
|
84
126
|
if /<a[^>]*href\s*=\s*["']?\z/i.match?(before_match)
|
|
85
127
|
url
|
|
86
128
|
else
|
|
87
129
|
# Add protocol if missing
|
|
88
|
-
full_url = url.start_with?(
|
|
130
|
+
full_url = url.start_with?('www.') ? "https://#{url}" : url
|
|
89
131
|
%(<a href="#{full_url}" target="_blank" rel="noopener noreferrer">#{url}</a>)
|
|
90
132
|
end
|
|
91
133
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require 'sanitize'
|
|
4
4
|
|
|
5
5
|
module Panda
|
|
6
6
|
module Editor
|
|
@@ -14,15 +14,16 @@ module Panda
|
|
|
14
14
|
@cache_store = options.delete(:cache_store) || Rails.cache
|
|
15
15
|
@validate_html = options.delete(:validate_html) || false
|
|
16
16
|
autolink_urls = options.delete(:autolink_urls) || false
|
|
17
|
-
|
|
17
|
+
markdown = options.delete(:markdown) || false
|
|
18
|
+
@footnote_registry = FootnoteRegistry.new(autolink_urls: autolink_urls, markdown: markdown)
|
|
18
19
|
@options[:footnote_registry] = @footnote_registry
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def render
|
|
22
|
-
return
|
|
23
|
-
return content.to_s unless content.is_a?(Hash) && content[
|
|
23
|
+
return '' if content.nil? || content == {}
|
|
24
|
+
return content.to_s unless content.is_a?(Hash) && content['blocks'].is_a?(Array)
|
|
24
25
|
|
|
25
|
-
rendered = content[
|
|
26
|
+
rendered = content['blocks'].map do |block|
|
|
26
27
|
render_block(block)
|
|
27
28
|
end.join("\n")
|
|
28
29
|
|
|
@@ -34,36 +35,36 @@ module Panda
|
|
|
34
35
|
rendered = [rendered, sources_section].join("\n")
|
|
35
36
|
end
|
|
36
37
|
|
|
37
|
-
rendered.presence ||
|
|
38
|
+
rendered.presence || ''
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
def section(blocks)
|
|
41
|
-
return
|
|
42
|
+
return '' if blocks.nil? || blocks.empty?
|
|
42
43
|
|
|
43
|
-
content = {
|
|
44
|
+
content = { 'blocks' => blocks }
|
|
44
45
|
rendered = self.class.new(content, options).render
|
|
45
46
|
|
|
46
47
|
"<section class=\"content-section\">#{rendered}</section>"
|
|
47
48
|
end
|
|
48
49
|
|
|
49
50
|
def article(blocks, title: nil)
|
|
50
|
-
return
|
|
51
|
+
return '' if blocks.nil? || blocks.empty?
|
|
51
52
|
|
|
52
|
-
content = {
|
|
53
|
+
content = { 'blocks' => blocks }
|
|
53
54
|
rendered = self.class.new(content, options).render
|
|
54
55
|
|
|
55
56
|
[
|
|
56
|
-
|
|
57
|
-
(title ? "<h1>#{title}</h1>" :
|
|
57
|
+
'<article>',
|
|
58
|
+
(title ? "<h1>#{title}</h1>" : ''),
|
|
58
59
|
rendered,
|
|
59
|
-
|
|
60
|
+
'</article>'
|
|
60
61
|
].join("\n")
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
private
|
|
64
65
|
|
|
65
66
|
def validate_html(html)
|
|
66
|
-
return
|
|
67
|
+
return '' if html.blank?
|
|
67
68
|
|
|
68
69
|
begin
|
|
69
70
|
# For quote blocks, only allow specific content
|
|
@@ -72,35 +73,35 @@ module Panda
|
|
|
72
73
|
valid_content = '<figure class="text-left"><blockquote><p>Valid HTML</p></blockquote><figcaption>Valid caption</figcaption></figure>'
|
|
73
74
|
return html if html.strip == valid_content.strip
|
|
74
75
|
|
|
75
|
-
return
|
|
76
|
+
return ''
|
|
76
77
|
end
|
|
77
78
|
|
|
78
79
|
# For other HTML, use sanitize
|
|
79
80
|
config = Sanitize::Config::RELAXED.dup
|
|
80
81
|
config[:elements] += %w[figure figcaption blockquote pre code mention math]
|
|
81
82
|
config[:attributes].merge!({
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
'figure' => ['class'],
|
|
84
|
+
'blockquote' => ['class'],
|
|
85
|
+
'p' => ['class'],
|
|
86
|
+
'figcaption' => ['class']
|
|
87
|
+
})
|
|
87
88
|
|
|
88
89
|
sanitized = Sanitize.fragment(html, config)
|
|
89
|
-
|
|
90
|
-
rescue => e
|
|
90
|
+
sanitized == html ? html : ''
|
|
91
|
+
rescue StandardError => e
|
|
91
92
|
Rails.logger.error("HTML validation error: #{e.message}")
|
|
92
|
-
|
|
93
|
+
''
|
|
93
94
|
end
|
|
94
95
|
end
|
|
95
96
|
|
|
96
97
|
def render_block_with_cache(block)
|
|
97
98
|
# Don't cache blocks with footnotes - they need to register with the footnote registry
|
|
98
|
-
if block[
|
|
99
|
+
if block['data']['footnotes'].present?
|
|
99
100
|
renderer = renderer_for(block)
|
|
100
101
|
return renderer.render
|
|
101
102
|
end
|
|
102
103
|
|
|
103
|
-
cache_key = "editor_js_block/#{block[
|
|
104
|
+
cache_key = "editor_js_block/#{block['type']}/#{Digest::MD5.hexdigest(block['data'].to_json)}"
|
|
104
105
|
|
|
105
106
|
cache_store.fetch(cache_key) do
|
|
106
107
|
renderer = renderer_for(block)
|
|
@@ -109,28 +110,28 @@ module Panda
|
|
|
109
110
|
end
|
|
110
111
|
|
|
111
112
|
def renderer_for(block)
|
|
112
|
-
if custom_renderers[block[
|
|
113
|
-
custom_renderers[block[
|
|
113
|
+
if custom_renderers[block['type']]
|
|
114
|
+
custom_renderers[block['type']].new(block['data'], options)
|
|
114
115
|
else
|
|
115
116
|
default_renderer_for(block)
|
|
116
117
|
end
|
|
117
118
|
end
|
|
118
119
|
|
|
119
120
|
def default_renderer_for(block)
|
|
120
|
-
renderer_class = "Panda::Editor::Blocks::#{block[
|
|
121
|
-
renderer_class.new(block[
|
|
121
|
+
renderer_class = "Panda::Editor::Blocks::#{block['type'].classify}".constantize
|
|
122
|
+
renderer_class.new(block['data'], options)
|
|
122
123
|
rescue NameError
|
|
123
|
-
Panda::Editor::Blocks::Base.new(block[
|
|
124
|
+
Panda::Editor::Blocks::Base.new(block['data'], options)
|
|
124
125
|
end
|
|
125
126
|
|
|
126
127
|
def remove_empty_paragraphs(blocks)
|
|
127
128
|
blocks.reject do |block|
|
|
128
|
-
block[
|
|
129
|
+
block['type'] == 'paragraph' && block['data']['text'].blank?
|
|
129
130
|
end
|
|
130
131
|
end
|
|
131
132
|
|
|
132
133
|
def empty_paragraph?(block)
|
|
133
|
-
block[
|
|
134
|
+
block['type'] == 'paragraph' && block['data']['text'].blank?
|
|
134
135
|
end
|
|
135
136
|
|
|
136
137
|
def render_block(block)
|
data/lib/panda/editor/version.rb
CHANGED
data/lib/panda/editor.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
3
|
+
require 'dry-configurable'
|
|
4
|
+
require_relative 'editor/version'
|
|
5
|
+
require_relative 'editor/engine'
|
|
6
6
|
|
|
7
7
|
module Panda
|
|
8
8
|
module Editor
|
|
@@ -18,19 +18,19 @@ module Panda
|
|
|
18
18
|
class Error < StandardError; end
|
|
19
19
|
|
|
20
20
|
# Autoload components
|
|
21
|
-
autoload :Renderer,
|
|
22
|
-
autoload :Content,
|
|
23
|
-
autoload :FootnoteRegistry,
|
|
21
|
+
autoload :Renderer, 'panda/editor/renderer'
|
|
22
|
+
autoload :Content, 'panda/editor/content'
|
|
23
|
+
autoload :FootnoteRegistry, 'panda/editor/footnote_registry'
|
|
24
24
|
|
|
25
25
|
module Blocks
|
|
26
|
-
autoload :Base,
|
|
27
|
-
autoload :Alert,
|
|
28
|
-
autoload :Header,
|
|
29
|
-
autoload :Image,
|
|
30
|
-
autoload :List,
|
|
31
|
-
autoload :Paragraph,
|
|
32
|
-
autoload :Quote,
|
|
33
|
-
autoload :Table,
|
|
26
|
+
autoload :Base, 'panda/editor/blocks/base'
|
|
27
|
+
autoload :Alert, 'panda/editor/blocks/alert'
|
|
28
|
+
autoload :Header, 'panda/editor/blocks/header'
|
|
29
|
+
autoload :Image, 'panda/editor/blocks/image'
|
|
30
|
+
autoload :List, 'panda/editor/blocks/list'
|
|
31
|
+
autoload :Paragraph, 'panda/editor/blocks/paragraph'
|
|
32
|
+
autoload :Quote, 'panda/editor/blocks/quote'
|
|
33
|
+
autoload :Table, 'panda/editor/blocks/table'
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
end
|