panda-editor 0.6.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/app/javascript/panda/editor/application.js +8 -6
  4. data/app/javascript/panda/editor/controllers/index.js +5 -0
  5. data/app/javascript/panda/editor/editor_js_config.js +7 -5
  6. data/app/javascript/panda/editor/editor_js_initializer.js +10 -3
  7. data/app/javascript/panda/editor/plugins/embed.min.js +2 -0
  8. data/app/javascript/panda/editor/plugins/header.min.js +9 -0
  9. data/app/javascript/panda/editor/plugins/nested-list.min.js +2 -0
  10. data/app/javascript/panda/editor/plugins/paragraph.min.js +9 -0
  11. data/app/javascript/panda/editor/plugins/quote.min.js +2 -0
  12. data/app/javascript/panda/editor/plugins/simple-image.min.js +2 -0
  13. data/app/javascript/panda/editor/plugins/table.min.js +2 -0
  14. data/app/javascript/panda/editor/rich_text_editor.js +2 -3
  15. data/app/services/panda/editor/html_to_editor_js_converter.rb +68 -68
  16. data/app/stylesheets/editor.css +120 -0
  17. data/config/importmap.rb +22 -11
  18. data/docs/FOOTNOTES.md +96 -3
  19. data/lefthook.yml +16 -0
  20. data/lib/panda/editor/asset_loader.rb +27 -27
  21. data/lib/panda/editor/blocks/alert.rb +10 -10
  22. data/lib/panda/editor/blocks/base.rb +1 -1
  23. data/lib/panda/editor/blocks/header.rb +2 -2
  24. data/lib/panda/editor/blocks/image.rb +11 -11
  25. data/lib/panda/editor/blocks/list.rb +25 -6
  26. data/lib/panda/editor/blocks/paragraph.rb +41 -10
  27. data/lib/panda/editor/blocks/quote.rb +6 -6
  28. data/lib/panda/editor/blocks/table.rb +6 -6
  29. data/lib/panda/editor/content.rb +11 -8
  30. data/lib/panda/editor/engine.rb +29 -9
  31. data/lib/panda/editor/footnote_registry.rb +10 -5
  32. data/lib/panda/editor/html_to_editor_js_converter.rb +1 -1
  33. data/lib/panda/editor/renderer.rb +31 -31
  34. data/lib/panda/editor/version.rb +1 -1
  35. data/lib/panda/editor.rb +18 -16
  36. data/lib/tasks/assets.rake +27 -27
  37. data/mise.toml +2 -0
  38. data/panda-editor.gemspec +25 -24
  39. metadata +32 -3
@@ -1,14 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "cgi"
4
+
3
5
  module Panda
4
6
  module Editor
5
7
  module Blocks
6
8
  class Paragraph < Base
7
9
  def render
8
- content = sanitize(data['text'])
9
- return '' if content.blank?
10
+ content = sanitize(data["text"])
11
+ return "" if content.blank?
10
12
 
11
- content = inject_footnotes(content) if data['footnotes'].present?
13
+ content = inject_footnotes(content) if data["footnotes"].present?
12
14
 
13
15
  html_safe("<p>#{content}</p>")
14
16
  end
@@ -16,13 +18,13 @@ module Panda
16
18
  private
17
19
 
18
20
  def inject_footnotes(text)
19
- return text unless data['footnotes'].is_a?(Array)
21
+ return text unless data["footnotes"].is_a?(Array)
20
22
 
21
23
  # Sort footnotes by position in descending order to avoid position shifts
22
- footnotes = data['footnotes'].sort_by { |fn| -fn['position'].to_i }
24
+ footnotes = data["footnotes"].sort_by { |fn| -fn["position"].to_i }
23
25
 
24
26
  footnotes.each do |footnote|
25
- position = footnote['position'].to_i
27
+ position = footnote["position"].to_i
26
28
  # Skip if position is beyond text length
27
29
  next if position.negative? || position > text.length
28
30
 
@@ -30,8 +32,11 @@ module Panda
30
32
  footnote_number = register_footnote(footnote)
31
33
  next unless footnote_number
32
34
 
33
- # Create footnote marker
34
- marker = "<sup id=\"fnref:#{footnote_number}\"><a href=\"#fn:#{footnote_number}\" class=\"footnote\">#{footnote_number}</a></sup>"
35
+ # Get processed content for tooltip
36
+ tooltip_content = get_tooltip_content(footnote["id"])
37
+
38
+ # Create footnote marker with tooltip support
39
+ marker = create_footnote_marker(footnote_number, tooltip_content)
35
40
 
36
41
  # Insert marker at position
37
42
  text.insert(position, marker)
@@ -44,10 +49,36 @@ module Panda
44
49
  return nil unless options[:footnote_registry]
45
50
 
46
51
  options[:footnote_registry].add(
47
- id: footnote['id'],
48
- content: footnote['content']
52
+ id: footnote["id"],
53
+ content: footnote["content"]
49
54
  )
50
55
  end
56
+
57
+ def get_tooltip_content(footnote_id)
58
+ return nil unless options[:footnote_registry]
59
+
60
+ options[:footnote_registry].get_content(footnote_id)
61
+ end
62
+
63
+ def create_footnote_marker(number, tooltip_content)
64
+ # Strip HTML tags for title attribute (simple tooltip fallback)
65
+ plain_content = tooltip_content ? strip_html(tooltip_content) : nil
66
+
67
+ # Build marker with tooltip support
68
+ if tooltip_content
69
+ # Include both title attribute (native browser tooltip) and data attribute (for custom tooltips)
70
+ escaped_content = CGI.escapeHTML(tooltip_content)
71
+ escaped_title = CGI.escapeHTML(plain_content || "")
72
+ %(<sup id="fnref:#{number}" class="footnote-ref" data-footnote-content="#{escaped_content}" title="#{escaped_title}"><a href="#fn:#{number}" class="footnote">#{number}</a></sup>)
73
+ else
74
+ # Fallback without tooltip
75
+ %(<sup id="fnref:#{number}"><a href="#fn:#{number}" class="footnote">#{number}</a></sup>)
76
+ end
77
+ end
78
+
79
+ def strip_html(html)
80
+ html.gsub(/<\/?[^>]*>/, "")
81
+ end
51
82
  end
52
83
  end
53
84
  end
@@ -5,15 +5,15 @@ module Panda
5
5
  module Blocks
6
6
  class Quote < Base
7
7
  def render
8
- text = data['text']
9
- caption = data['caption']
10
- alignment = data['alignment'] || 'left'
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
- '</figure>'
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?('<p>') && text.end_with?('</p>')
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 '' if caption.blank?
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['content']
9
- with_headings = data['withHeadings']
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
- render_header_row(content[index])
29
- else
30
- render_data_row(content[index])
31
- end
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
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
3
+ require "json"
4
4
 
5
5
  module Panda
6
6
  module Editor
@@ -36,21 +36,24 @@ module Panda
36
36
  end
37
37
 
38
38
  def generate_cached_content
39
- renderer_options = { autolink_urls: true }
39
+ renderer_options = {
40
+ autolink_urls: true,
41
+ custom_renderers: Panda::Editor.config.custom_renderers
42
+ }
40
43
 
41
44
  if content.is_a?(String)
42
45
  begin
43
46
  parsed_content = JSON.parse(content)
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
47
+ self.cached_content = if parsed_content.is_a?(Hash) && parsed_content["blocks"].present?
48
+ Panda::Editor::Renderer.new(parsed_content, renderer_options).render
49
+ else
50
+ content
51
+ end
49
52
  rescue JSON::ParserError
50
53
  # If it's not JSON, treat it as plain text
51
54
  self.cached_content = content
52
55
  end
53
- elsif content.is_a?(Hash) && content['blocks'].present?
56
+ elsif content.is_a?(Hash) && content["blocks"].present?
54
57
  # Process EditorJS content
55
58
  self.cached_content = Panda::Editor::Renderer.new(content, renderer_options).render
56
59
  else
@@ -1,7 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails'
4
- require 'sanitize'
3
+ require "rails"
4
+ require "sanitize"
5
+
6
+ # Ensure panda-core is loaded first (provides ModuleRegistry)
7
+ require "panda/core"
8
+ require "panda/core/engine" if defined?(Rails)
5
9
 
6
10
  module Panda
7
11
  module Editor
@@ -14,21 +18,37 @@ module Panda
14
18
 
15
19
  # Eager load converter classes
16
20
  config.to_prepare do
17
- require 'panda/editor/markdown_to_editor_js_converter'
18
- require 'panda/editor/html_to_editor_js_converter'
21
+ require "panda/editor/markdown_to_editor_js_converter"
22
+ require "panda/editor/html_to_editor_js_converter"
19
23
  end
20
24
 
21
- initializer 'panda_editor.assets' do |app|
25
+ initializer "panda_editor.assets" do |app|
22
26
  next unless app.config.respond_to?(:assets)
23
27
 
24
- app.config.assets.paths << root.join('app/javascript')
25
- app.config.assets.paths << root.join('public')
28
+ app.config.assets.paths << root.join("app/javascript")
29
+ app.config.assets.paths << root.join("public")
26
30
  app.config.assets.precompile += %w[panda/editor/*.js panda/editor/*.css]
27
31
  end
28
32
 
29
- initializer 'panda_editor.importmap', before: 'importmap' do |app|
30
- app.config.importmap.paths << root.join('config/importmap.rb') if app.config.respond_to?(:importmap)
33
+ # Create a separate importmap for panda-editor
34
+ # This keeps the engine's JavaScript separate from the app's importmap
35
+ # Admin uses panda_core_javascript helper which reads from ModuleRegistry
36
+ initializer "panda_editor.importmap", before: "importmap" do |app|
37
+ Panda::Editor.importmap = Importmap::Map.new.tap do |map|
38
+ map.draw(Panda::Editor::Engine.root.join("config/importmap.rb"))
39
+ end
31
40
  end
32
41
  end
33
42
  end
34
43
  end
44
+
45
+ # Register with ModuleRegistry so admin can access the importmap
46
+ Panda::Core::ModuleRegistry.register(
47
+ gem_name: "panda-editor",
48
+ engine: "Panda::Editor::Engine",
49
+ paths: {
50
+ views: "app/views/panda/editor/**/*.erb",
51
+ components: "app/components/panda/editor/**/*.rb",
52
+ javascripts: "app/javascript/panda/editor/**/*.js"
53
+ }
54
+ )
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'redcarpet'
3
+ require "redcarpet"
4
4
 
5
5
  module Panda
6
6
  module Editor
@@ -19,7 +19,7 @@ module Panda
19
19
  return @footnote_ids[id] if @footnote_ids[id]
20
20
 
21
21
  # Add new footnote
22
- @footnotes << { id: id, content: content }
22
+ @footnotes << {id: id, content: content}
23
23
  number = @footnotes.length
24
24
 
25
25
  # Cache the number for this ID
@@ -29,7 +29,7 @@ module Panda
29
29
  end
30
30
 
31
31
  def render_sources_section
32
- return '' if @footnotes.empty?
32
+ return "" if @footnotes.empty?
33
33
 
34
34
  footnote_items = @footnotes.map.with_index do |footnote, index|
35
35
  number = index + 1
@@ -67,6 +67,11 @@ module Panda
67
67
  @footnotes.any?
68
68
  end
69
69
 
70
+ def get_content(id)
71
+ footnote = @footnotes.find { |fn| fn[:id] == id }
72
+ footnote ? process_content(footnote[:content]) : nil
73
+ end
74
+
70
75
  private
71
76
 
72
77
  def process_content(content)
@@ -88,7 +93,7 @@ module Panda
88
93
  no_images: true,
89
94
  no_styles: true,
90
95
  safe_links_only: true,
91
- link_attributes: { target: '_blank', rel: 'noopener noreferrer' }
96
+ link_attributes: {target: "_blank", rel: "noopener noreferrer"}
92
97
  )
93
98
 
94
99
  markdown = Redcarpet::Markdown.new(
@@ -127,7 +132,7 @@ module Panda
127
132
  url
128
133
  else
129
134
  # Add protocol if missing
130
- full_url = url.start_with?('www.') ? "https://#{url}" : url
135
+ full_url = url.start_with?("www.") ? "https://#{url}" : url
131
136
  %(<a href="#{full_url}" target="_blank" rel="noopener noreferrer">#{url}</a>)
132
137
  end
133
138
  end
@@ -95,7 +95,7 @@ module Panda
95
95
  end
96
96
 
97
97
  def list_block(node)
98
- style = node.name == "ol" ? "ordered" : "unordered"
98
+ style = (node.name == "ol") ? "ordered" : "unordered"
99
99
  items = node.css("li").map { |li| li.inner_html.strip }
100
100
 
101
101
  {
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'sanitize'
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 '' if content.nil? || content == {}
24
- return content.to_s unless content.is_a?(Hash) && content['blocks'].is_a?(Array)
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['blocks'].map do |block|
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 '' if blocks.nil? || blocks.empty?
42
+ return "" if blocks.nil? || blocks.empty?
43
43
 
44
- content = { 'blocks' => blocks }
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 '' if blocks.nil? || blocks.empty?
51
+ return "" if blocks.nil? || blocks.empty?
52
52
 
53
- content = { 'blocks' => blocks }
53
+ content = {"blocks" => blocks}
54
54
  rendered = self.class.new(content, options).render
55
55
 
56
56
  [
57
- '<article>',
58
- (title ? "<h1>#{title}</h1>" : ''),
57
+ "<article>",
58
+ (title ? "<h1>#{title}</h1>" : ""),
59
59
  rendered,
60
- '</article>'
60
+ "</article>"
61
61
  ].join("\n")
62
62
  end
63
63
 
64
64
  private
65
65
 
66
66
  def validate_html(html)
67
- return '' if html.blank?
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
- 'figure' => ['class'],
84
- 'blockquote' => ['class'],
85
- 'p' => ['class'],
86
- 'figcaption' => ['class']
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 StandardError => e
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['data']['footnotes'].present?
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['type']}/#{Digest::MD5.hexdigest(block['data'].to_json)}"
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['type']]
114
- custom_renderers[block['type']].new(block['data'], options)
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['type'].classify}".constantize
122
- renderer_class.new(block['data'], options)
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['data'], options)
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['type'] == 'paragraph' && block['data']['text'].blank?
129
+ block["type"] == "paragraph" && block["data"]["text"].blank?
130
130
  end
131
131
  end
132
132
 
133
133
  def empty_paragraph?(block)
134
- block['type'] == 'paragraph' && block['data']['text'].blank?
134
+ block["type"] == "paragraph" && block["data"]["text"].blank?
135
135
  end
136
136
 
137
137
  def render_block(block)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Panda
4
4
  module Editor
5
- VERSION = '0.6.0'
5
+ VERSION = "0.8.2"
6
6
  end
7
7
  end
data/lib/panda/editor.rb CHANGED
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry-configurable'
4
- require_relative 'editor/version'
5
- require_relative 'editor/engine'
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: {}
@@ -18,21 +20,21 @@ module Panda
18
20
  class Error < StandardError; end
19
21
 
20
22
  # 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'
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"
26
28
 
27
29
  module Blocks
28
- autoload :Base, 'panda/editor/blocks/base'
29
- autoload :Alert, 'panda/editor/blocks/alert'
30
- autoload :Header, 'panda/editor/blocks/header'
31
- autoload :Image, 'panda/editor/blocks/image'
32
- autoload :List, 'panda/editor/blocks/list'
33
- autoload :Paragraph, 'panda/editor/blocks/paragraph'
34
- autoload :Quote, 'panda/editor/blocks/quote'
35
- autoload :Table, 'panda/editor/blocks/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"
36
38
  end
37
39
  end
38
40
  end
@@ -2,14 +2,14 @@
2
2
 
3
3
  namespace :panda_editor do
4
4
  namespace :assets do
5
- desc 'Compile Panda Editor assets for production'
5
+ desc "Compile Panda Editor assets for production"
6
6
  task compile: :environment do
7
- require 'fileutils'
7
+ require "fileutils"
8
8
 
9
- puts 'Compiling Panda Editor assets...'
9
+ puts "Compiling Panda Editor assets..."
10
10
 
11
11
  # Create temporary directory for assets
12
- tmp_dir = Rails.root.join('tmp', 'panda_editor_assets')
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('public', 'panda-editor-assets')
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('*')), public_dir)
27
+ FileUtils.cp_r(Dir.glob(tmp_dir.join("*")), public_dir)
28
28
 
29
- puts '✅ Assets compiled successfully'
29
+ puts "✅ Assets compiled successfully"
30
30
  end
31
31
 
32
- desc 'Download Panda Editor assets from GitHub'
32
+ desc "Download Panda Editor assets from GitHub"
33
33
  task download: :environment do
34
- require 'panda/editor/asset_loader'
34
+ require "panda/editor/asset_loader"
35
35
 
36
- puts 'Downloading Panda Editor assets from GitHub...'
36
+ puts "Downloading Panda Editor assets from GitHub..."
37
37
  Panda::Editor::AssetLoader.send(:download_assets_from_github)
38
- puts '✅ Assets downloaded successfully'
38
+ puts "✅ Assets downloaded successfully"
39
39
  end
40
40
 
41
- desc 'Upload compiled assets to GitHub release'
41
+ desc "Upload compiled assets to GitHub release"
42
42
  task upload: :environment do
43
- require 'net/http'
44
- require 'json'
43
+ require "net/http"
44
+ require "json"
45
45
 
46
- puts 'Uploading Panda Editor assets to GitHub release...'
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['GITHUB_REF_NAME'] || "v#{Panda::Editor::VERSION}"
52
- token = ENV['GITHUB_TOKEN']
51
+ version = ENV["GITHUB_REF_NAME"] || "v#{Panda::Editor::VERSION}"
52
+ token = ENV["GITHUB_TOKEN"]
53
53
 
54
54
  unless token
55
- puts '❌ GITHUB_TOKEN environment variable required'
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('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
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 '✅ Assets uploaded successfully'
67
+ puts "✅ Assets uploaded successfully"
68
68
  else
69
- puts '❌ Compiled assets not found'
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 ' Compiling JavaScript...'
77
+ puts " Compiling JavaScript..."
78
78
 
79
- js_files = Dir.glob(Panda::Editor::Engine.root.join('app/javascript/panda/editor/**/*.js'))
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 ' Compiling CSS...'
91
+ puts " Compiling CSS..."
92
92
 
93
- css_files = Dir.glob(Panda::Editor::Engine.root.join('app/assets/stylesheets/panda/editor/**/*.css'))
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
@@ -0,0 +1,2 @@
1
+ [tools]
2
+ ruby = "4.0.0"