panda-editor 0.5.0 → 0.8.2
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/.ruby-version +1 -0
- data/CHANGELOG.md +19 -0
- data/README.md +68 -0
- data/app/javascript/panda/editor/application.js +8 -6
- data/app/javascript/panda/editor/controllers/index.js +5 -0
- data/app/javascript/panda/editor/editor_js_config.js +7 -5
- data/app/javascript/panda/editor/editor_js_initializer.js +10 -3
- data/app/javascript/panda/editor/plugins/embed.min.js +2 -0
- data/app/javascript/panda/editor/plugins/header.min.js +9 -0
- data/app/javascript/panda/editor/plugins/nested-list.min.js +2 -0
- data/app/javascript/panda/editor/plugins/paragraph.min.js +9 -0
- data/app/javascript/panda/editor/plugins/quote.min.js +2 -0
- data/app/javascript/panda/editor/plugins/simple-image.min.js +2 -0
- data/app/javascript/panda/editor/plugins/table.min.js +2 -0
- data/app/javascript/panda/editor/rich_text_editor.js +2 -3
- data/app/services/panda/editor/html_to_editor_js_converter.rb +68 -68
- data/app/stylesheets/editor.css +120 -0
- data/config/importmap.rb +22 -11
- data/docs/FOOTNOTES.md +96 -3
- data/lefthook.yml +16 -0
- data/lib/panda/editor/asset_loader.rb +27 -27
- 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 +25 -6
- data/lib/panda/editor/blocks/paragraph.rb +41 -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 +11 -8
- data/lib/panda/editor/engine.rb +33 -7
- data/lib/panda/editor/footnote_registry.rb +10 -5
- data/lib/panda/editor/html_to_editor_js_converter.rb +159 -0
- data/lib/panda/editor/markdown_to_editor_js_converter.rb +50 -0
- data/lib/panda/editor/renderer.rb +31 -31
- data/lib/panda/editor/version.rb +1 -1
- data/lib/panda/editor.rb +19 -15
- data/lib/panda-editor.rb +5 -0
- data/lib/tasks/assets.rake +27 -27
- data/mise.toml +2 -0
- data/panda-editor.gemspec +25 -23
- metadata +49 -3
|
@@ -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
|
|
@@ -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
|
|
@@ -20,10 +20,10 @@ module Panda
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def render
|
|
23
|
-
return
|
|
24
|
-
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)
|
|
25
25
|
|
|
26
|
-
rendered = content[
|
|
26
|
+
rendered = content["blocks"].map do |block|
|
|
27
27
|
render_block(block)
|
|
28
28
|
end.join("\n")
|
|
29
29
|
|
|
@@ -35,36 +35,36 @@ module Panda
|
|
|
35
35
|
rendered = [rendered, sources_section].join("\n")
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
rendered.presence ||
|
|
38
|
+
rendered.presence || ""
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def section(blocks)
|
|
42
|
-
return
|
|
42
|
+
return "" if blocks.nil? || blocks.empty?
|
|
43
43
|
|
|
44
|
-
content = {
|
|
44
|
+
content = {"blocks" => blocks}
|
|
45
45
|
rendered = self.class.new(content, options).render
|
|
46
46
|
|
|
47
47
|
"<section class=\"content-section\">#{rendered}</section>"
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def article(blocks, title: nil)
|
|
51
|
-
return
|
|
51
|
+
return "" if blocks.nil? || blocks.empty?
|
|
52
52
|
|
|
53
|
-
content = {
|
|
53
|
+
content = {"blocks" => blocks}
|
|
54
54
|
rendered = self.class.new(content, options).render
|
|
55
55
|
|
|
56
56
|
[
|
|
57
|
-
|
|
58
|
-
(title ? "<h1>#{title}</h1>" :
|
|
57
|
+
"<article>",
|
|
58
|
+
(title ? "<h1>#{title}</h1>" : ""),
|
|
59
59
|
rendered,
|
|
60
|
-
|
|
60
|
+
"</article>"
|
|
61
61
|
].join("\n")
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
private
|
|
65
65
|
|
|
66
66
|
def validate_html(html)
|
|
67
|
-
return
|
|
67
|
+
return "" if html.blank?
|
|
68
68
|
|
|
69
69
|
begin
|
|
70
70
|
# For quote blocks, only allow specific content
|
|
@@ -73,35 +73,35 @@ module Panda
|
|
|
73
73
|
valid_content = '<figure class="text-left"><blockquote><p>Valid HTML</p></blockquote><figcaption>Valid caption</figcaption></figure>'
|
|
74
74
|
return html if html.strip == valid_content.strip
|
|
75
75
|
|
|
76
|
-
return
|
|
76
|
+
return ""
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
# For other HTML, use sanitize
|
|
80
80
|
config = Sanitize::Config::RELAXED.dup
|
|
81
81
|
config[:elements] += %w[figure figcaption blockquote pre code mention math]
|
|
82
82
|
config[:attributes].merge!({
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
"figure" => ["class"],
|
|
84
|
+
"blockquote" => ["class"],
|
|
85
|
+
"p" => ["class"],
|
|
86
|
+
"figcaption" => ["class"]
|
|
87
|
+
})
|
|
88
88
|
|
|
89
89
|
sanitized = Sanitize.fragment(html, config)
|
|
90
|
-
sanitized == html ? html :
|
|
91
|
-
rescue
|
|
90
|
+
(sanitized == html) ? html : ""
|
|
91
|
+
rescue => e
|
|
92
92
|
Rails.logger.error("HTML validation error: #{e.message}")
|
|
93
|
-
|
|
93
|
+
""
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
def render_block_with_cache(block)
|
|
98
98
|
# Don't cache blocks with footnotes - they need to register with the footnote registry
|
|
99
|
-
if block[
|
|
99
|
+
if block["data"]["footnotes"].present?
|
|
100
100
|
renderer = renderer_for(block)
|
|
101
101
|
return renderer.render
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
-
cache_key = "editor_js_block/#{block[
|
|
104
|
+
cache_key = "editor_js_block/#{block["type"]}/#{Digest::MD5.hexdigest(block["data"].to_json)}"
|
|
105
105
|
|
|
106
106
|
cache_store.fetch(cache_key) do
|
|
107
107
|
renderer = renderer_for(block)
|
|
@@ -110,28 +110,28 @@ module Panda
|
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
def renderer_for(block)
|
|
113
|
-
if custom_renderers[block[
|
|
114
|
-
custom_renderers[block[
|
|
113
|
+
if custom_renderers[block["type"]]
|
|
114
|
+
custom_renderers[block["type"]].new(block["data"], options)
|
|
115
115
|
else
|
|
116
116
|
default_renderer_for(block)
|
|
117
117
|
end
|
|
118
118
|
end
|
|
119
119
|
|
|
120
120
|
def default_renderer_for(block)
|
|
121
|
-
renderer_class = "Panda::Editor::Blocks::#{block[
|
|
122
|
-
renderer_class.new(block[
|
|
121
|
+
renderer_class = "Panda::Editor::Blocks::#{block["type"].classify}".constantize
|
|
122
|
+
renderer_class.new(block["data"], options)
|
|
123
123
|
rescue NameError
|
|
124
|
-
Panda::Editor::Blocks::Base.new(block[
|
|
124
|
+
Panda::Editor::Blocks::Base.new(block["data"], options)
|
|
125
125
|
end
|
|
126
126
|
|
|
127
127
|
def remove_empty_paragraphs(blocks)
|
|
128
128
|
blocks.reject do |block|
|
|
129
|
-
block[
|
|
129
|
+
block["type"] == "paragraph" && block["data"]["text"].blank?
|
|
130
130
|
end
|
|
131
131
|
end
|
|
132
132
|
|
|
133
133
|
def empty_paragraph?(block)
|
|
134
|
-
block[
|
|
134
|
+
block["type"] == "paragraph" && block["data"]["text"].blank?
|
|
135
135
|
end
|
|
136
136
|
|
|
137
137
|
def render_block(block)
|
data/lib/panda/editor/version.rb
CHANGED
data/lib/panda/editor.rb
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
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
|
|
9
9
|
extend Dry::Configurable
|
|
10
10
|
|
|
11
|
+
mattr_accessor :importmap
|
|
12
|
+
|
|
11
13
|
# EditorJS configuration
|
|
12
14
|
setting :editor_js_tools, default: []
|
|
13
15
|
setting :editor_js_tool_config, default: {}
|
|
@@ -17,20 +19,22 @@ module Panda
|
|
|
17
19
|
|
|
18
20
|
class Error < StandardError; end
|
|
19
21
|
|
|
20
|
-
#
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
# Require components
|
|
23
|
+
require_relative "editor/renderer"
|
|
24
|
+
require_relative "editor/content"
|
|
25
|
+
require_relative "editor/footnote_registry"
|
|
26
|
+
require_relative "editor/markdown_to_editor_js_converter"
|
|
27
|
+
require_relative "editor/html_to_editor_js_converter"
|
|
24
28
|
|
|
25
29
|
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,
|
|
30
|
+
autoload :Base, "panda/editor/blocks/base"
|
|
31
|
+
autoload :Alert, "panda/editor/blocks/alert"
|
|
32
|
+
autoload :Header, "panda/editor/blocks/header"
|
|
33
|
+
autoload :Image, "panda/editor/blocks/image"
|
|
34
|
+
autoload :List, "panda/editor/blocks/list"
|
|
35
|
+
autoload :Paragraph, "panda/editor/blocks/paragraph"
|
|
36
|
+
autoload :Quote, "panda/editor/blocks/quote"
|
|
37
|
+
autoload :Table, "panda/editor/blocks/table"
|
|
34
38
|
end
|
|
35
39
|
end
|
|
36
40
|
end
|
data/lib/panda-editor.rb
ADDED
data/lib/tasks/assets.rake
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
namespace :panda_editor do
|
|
4
4
|
namespace :assets do
|
|
5
|
-
desc
|
|
5
|
+
desc "Compile Panda Editor assets for production"
|
|
6
6
|
task compile: :environment do
|
|
7
|
-
require
|
|
7
|
+
require "fileutils"
|
|
8
8
|
|
|
9
|
-
puts
|
|
9
|
+
puts "Compiling Panda Editor assets..."
|
|
10
10
|
|
|
11
11
|
# Create temporary directory for assets
|
|
12
|
-
tmp_dir = Rails.root.join(
|
|
12
|
+
tmp_dir = Rails.root.join("tmp", "panda_editor_assets")
|
|
13
13
|
FileUtils.mkdir_p(tmp_dir)
|
|
14
14
|
|
|
15
15
|
# Get version from gem
|
|
@@ -22,51 +22,51 @@ namespace :panda_editor do
|
|
|
22
22
|
compile_css(tmp_dir, version)
|
|
23
23
|
|
|
24
24
|
# Copy to public directory
|
|
25
|
-
public_dir = Rails.root.join(
|
|
25
|
+
public_dir = Rails.root.join("public", "panda-editor-assets")
|
|
26
26
|
FileUtils.mkdir_p(public_dir)
|
|
27
|
-
FileUtils.cp_r(Dir.glob(tmp_dir.join(
|
|
27
|
+
FileUtils.cp_r(Dir.glob(tmp_dir.join("*")), public_dir)
|
|
28
28
|
|
|
29
|
-
puts
|
|
29
|
+
puts "✅ Assets compiled successfully"
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
desc
|
|
32
|
+
desc "Download Panda Editor assets from GitHub"
|
|
33
33
|
task download: :environment do
|
|
34
|
-
require
|
|
34
|
+
require "panda/editor/asset_loader"
|
|
35
35
|
|
|
36
|
-
puts
|
|
36
|
+
puts "Downloading Panda Editor assets from GitHub..."
|
|
37
37
|
Panda::Editor::AssetLoader.send(:download_assets_from_github)
|
|
38
|
-
puts
|
|
38
|
+
puts "✅ Assets downloaded successfully"
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
desc
|
|
41
|
+
desc "Upload compiled assets to GitHub release"
|
|
42
42
|
task upload: :environment do
|
|
43
|
-
require
|
|
44
|
-
require
|
|
43
|
+
require "net/http"
|
|
44
|
+
require "json"
|
|
45
45
|
|
|
46
|
-
puts
|
|
46
|
+
puts "Uploading Panda Editor assets to GitHub release..."
|
|
47
47
|
|
|
48
48
|
# This task would be run in CI to upload compiled assets
|
|
49
49
|
# to the GitHub release when a new version is tagged
|
|
50
50
|
|
|
51
|
-
version = ENV[
|
|
52
|
-
token = ENV[
|
|
51
|
+
version = ENV["GITHUB_REF_NAME"] || "v#{Panda::Editor::VERSION}"
|
|
52
|
+
token = ENV["GITHUB_TOKEN"]
|
|
53
53
|
|
|
54
54
|
unless token
|
|
55
|
-
puts
|
|
55
|
+
puts "❌ GITHUB_TOKEN environment variable required"
|
|
56
56
|
exit 1
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
# Find compiled assets
|
|
60
|
-
assets_dir = Rails.root.join(
|
|
61
|
-
js_file = Dir.glob(assets_dir.join(
|
|
62
|
-
css_file = Dir.glob(assets_dir.join(
|
|
60
|
+
assets_dir = Rails.root.join("public", "panda-editor-assets")
|
|
61
|
+
js_file = Dir.glob(assets_dir.join("panda-editor-*.js")).first
|
|
62
|
+
css_file = Dir.glob(assets_dir.join("panda-editor-*.css")).first
|
|
63
63
|
|
|
64
64
|
if js_file && css_file
|
|
65
65
|
upload_to_release(js_file, version, token)
|
|
66
66
|
upload_to_release(css_file, version, token)
|
|
67
|
-
puts
|
|
67
|
+
puts "✅ Assets uploaded successfully"
|
|
68
68
|
else
|
|
69
|
-
puts
|
|
69
|
+
puts "❌ Compiled assets not found"
|
|
70
70
|
exit 1
|
|
71
71
|
end
|
|
72
72
|
end
|
|
@@ -74,9 +74,9 @@ namespace :panda_editor do
|
|
|
74
74
|
private
|
|
75
75
|
|
|
76
76
|
def compile_javascript(tmp_dir, version)
|
|
77
|
-
puts
|
|
77
|
+
puts " Compiling JavaScript..."
|
|
78
78
|
|
|
79
|
-
js_files = Dir.glob(Panda::Editor::Engine.root.join(
|
|
79
|
+
js_files = Dir.glob(Panda::Editor::Engine.root.join("app/javascript/panda/editor/**/*.js"))
|
|
80
80
|
|
|
81
81
|
output = js_files.map { |file| File.read(file) }.join("\n\n")
|
|
82
82
|
|
|
@@ -88,9 +88,9 @@ namespace :panda_editor do
|
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
def compile_css(tmp_dir, version)
|
|
91
|
-
puts
|
|
91
|
+
puts " Compiling CSS..."
|
|
92
92
|
|
|
93
|
-
css_files = Dir.glob(Panda::Editor::Engine.root.join(
|
|
93
|
+
css_files = Dir.glob(Panda::Editor::Engine.root.join("app/assets/stylesheets/panda/editor/**/*.css"))
|
|
94
94
|
|
|
95
95
|
output = css_files.map { |file| File.read(file) }.join("\n\n")
|
|
96
96
|
|
data/mise.toml
ADDED
data/panda-editor.gemspec
CHANGED
|
@@ -1,43 +1,45 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
3
|
+
require_relative "lib/panda/editor/version"
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name =
|
|
6
|
+
spec.name = "panda-editor"
|
|
7
7
|
spec.version = Panda::Editor::VERSION
|
|
8
|
-
spec.authors = [
|
|
9
|
-
spec.email = [
|
|
8
|
+
spec.authors = ["Otaina Limited", "James Inman"]
|
|
9
|
+
spec.email = ["james@otaina.co.uk"]
|
|
10
10
|
|
|
11
|
-
spec.summary =
|
|
12
|
-
spec.description =
|
|
13
|
-
spec.homepage =
|
|
14
|
-
spec.license =
|
|
15
|
-
spec.required_ruby_version =
|
|
11
|
+
spec.summary = "EditorJS integration for Rails applications"
|
|
12
|
+
spec.description = "A modular, extensible rich text editor using EditorJS for Rails applications. Extracted from Panda CMS."
|
|
13
|
+
spec.homepage = "https://github.com/tastybamboo/panda-editor"
|
|
14
|
+
spec.license = "BSD-3-Clause"
|
|
15
|
+
spec.required_ruby_version = ">= 3.2.0"
|
|
16
16
|
|
|
17
|
-
spec.metadata[
|
|
18
|
-
spec.metadata[
|
|
19
|
-
spec.metadata[
|
|
20
|
-
spec.metadata[
|
|
17
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/tastybamboo/panda-editor"
|
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/tastybamboo/panda-editor/blob/main/CHANGELOG.md"
|
|
21
21
|
|
|
22
22
|
spec.files = Dir.chdir(__dir__) do
|
|
23
23
|
`git ls-files -z`.split("\x0").reject do |f|
|
|
24
24
|
(File.expand_path(f) == __FILE__) ||
|
|
25
25
|
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile]) ||
|
|
26
|
-
f.end_with?(
|
|
26
|
+
f.end_with?(".gem")
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
|
-
spec.bindir =
|
|
29
|
+
spec.bindir = "exe"
|
|
30
30
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
31
|
-
spec.require_paths = [
|
|
31
|
+
spec.require_paths = ["lib"]
|
|
32
32
|
|
|
33
33
|
# Rails dependencies
|
|
34
|
-
spec.add_dependency
|
|
35
|
-
spec.add_dependency
|
|
36
|
-
spec.add_dependency
|
|
37
|
-
spec.add_dependency
|
|
34
|
+
spec.add_dependency "dry-configurable", "~> 1.0"
|
|
35
|
+
spec.add_dependency "nokogiri", "~> 1.15"
|
|
36
|
+
spec.add_dependency "panda-core" # Must load before panda-editor for ModuleRegistry
|
|
37
|
+
spec.add_dependency "rails", ">= 7.1"
|
|
38
|
+
spec.add_dependency "redcarpet", "~> 3.6"
|
|
39
|
+
spec.add_dependency "sanitize", "~> 6.0"
|
|
38
40
|
|
|
39
41
|
# Development dependencies
|
|
40
|
-
spec.add_development_dependency
|
|
41
|
-
spec.add_development_dependency
|
|
42
|
-
spec.add_development_dependency
|
|
42
|
+
spec.add_development_dependency "factory_bot_rails", "~> 6.2"
|
|
43
|
+
spec.add_development_dependency "rspec-rails", "~> 6.0"
|
|
44
|
+
spec.add_development_dependency "standard", ">= 1.35.1"
|
|
43
45
|
end
|