panda-editor 0.4.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: 148f84007d3e1e31886f6be8297edfd19f5ddc13e29d1ff565d66a7c27261f85
4
- data.tar.gz: 0e2794c6183a16940bdd62c62ff0f63e6b55c0e529adb025e596f93041de565f
3
+ metadata.gz: adac7a7394c996ae17e8c3312f0b20bc8517724aa82397694ecce20e82821280
4
+ data.tar.gz: 595a17b99fde3d04816f1b957ce9c875de521475d69bc9358f3c370e9c380301
5
5
  SHA512:
6
- metadata.gz: 06cef5d5517ef1211bcfc1b0b5c0c63dba5c8bfd9d189b1074e1174d261d14de5d15c522734cdc69fca71c3ef3cfbbed88fe741de6e739a4c9b49a4a7960219d
7
- data.tar.gz: c169a830348145a9b9f24a62d15333a02fb9b27cc0bdc2356a383ade82010af16369e2279034bae78607ff751844c52a417d872a0146023445bcf7c4d7dc29b8
6
+ metadata.gz: 9395e397b2c94eea2d8d8c0a0183a6262a1bfe79dbc9aae23f833a255b214be22ffc6ca4f92f2e2218adadf948410e388c853bea3b3eaf589ed555ba9c1c2df4
7
+ data.tar.gz: 6fc84783464e4761bcf1fec9f162ff7fec33b571bf816d026167514c1d63cc4f640ffc8c0863e765bf58730f0713a7a47ff5f62b99c810a5f1806804b62e7f8c
data/CHANGELOG.md CHANGED
@@ -5,6 +5,39 @@ 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
+
27
+ ## [0.5.0] - 2025-11-04
28
+
29
+ ### Fixed
30
+ - Code quality improvements with Rubocop auto-corrections
31
+ - Fixed 1263 style violations across the codebase
32
+ - Converted double-quoted strings to single quotes where appropriate
33
+ - Improved code consistency and readability
34
+ - Corrected string literal usage throughout
35
+ - Fixed extra spacing and formatting issues
36
+
37
+ ### Changed
38
+ - All tests passing (53 examples, 0 failures)
39
+ - Improved code maintainability
40
+
8
41
  ## [0.4.0] - 2025-10-30
9
42
 
10
43
  ### Added
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:
@@ -9,78 +9,78 @@ module Panda
9
9
  return {} if html.blank?
10
10
 
11
11
  # If it's already in EditorJS format, return as is
12
- return html if html.is_a?(Hash) && (html["blocks"].present? || html[:blocks].present?)
12
+ return html if html.is_a?(Hash) && (html['blocks'].present? || html[:blocks].present?)
13
13
 
14
14
  begin
15
15
  # Parse the HTML content
16
16
  doc = Nokogiri::HTML.fragment(html.to_s)
17
- raise ConversionError, "Failed to parse HTML content" unless doc
17
+ raise ConversionError, 'Failed to parse HTML content' unless doc
18
18
 
19
19
  blocks = []
20
- current_text = ""
20
+ current_text = ''
21
21
 
22
22
  doc.children.each do |node|
23
23
  case node.name
24
- when "h1", "h2", "h3", "h4", "h5", "h6"
24
+ when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
25
25
  # Add any accumulated text as a paragraph before the header
26
26
  if current_text.present?
27
27
  blocks << create_paragraph_block(current_text)
28
- current_text = ""
28
+ current_text = ''
29
29
  end
30
30
 
31
31
  blocks << {
32
- "type" => "header",
33
- "data" => {
34
- "text" => node.text.strip,
35
- "level" => node.name[1].to_i
32
+ 'type' => 'header',
33
+ 'data' => {
34
+ 'text' => node.text.strip,
35
+ 'level' => node.name[1].to_i
36
36
  }
37
37
  }
38
- when "p", "div"
38
+ when 'p', 'div'
39
39
  # Add any accumulated text first
40
40
  if current_text.present?
41
41
  blocks << create_paragraph_block(current_text)
42
- current_text = ""
42
+ current_text = ''
43
43
  end
44
44
 
45
- if node.name == "div"
45
+ if node.name == 'div'
46
46
  # Process div children separately
47
47
  node.children.each do |child|
48
48
  case child.name
49
- when "h1", "h2", "h3", "h4", "h5", "h6"
49
+ when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
50
50
  blocks << {
51
- "type" => "header",
52
- "data" => {
53
- "text" => child.text.strip,
54
- "level" => child.name[1].to_i
51
+ 'type' => 'header',
52
+ 'data' => {
53
+ 'text' => child.text.strip,
54
+ 'level' => child.name[1].to_i
55
55
  }
56
56
  }
57
- when "p"
57
+ when 'p'
58
58
  text = process_inline_elements(child)
59
59
  paragraphs = text.split(%r{<br\s*/?>\s*<br\s*/?>}).map(&:strip)
60
60
  paragraphs.each do |paragraph|
61
61
  blocks << create_paragraph_block(paragraph) if paragraph.present?
62
62
  end
63
- when "ul", "ol"
64
- items = child.css("li").map { |li| process_inline_elements(li) }
63
+ when 'ul', 'ol'
64
+ items = child.css('li').map { |li| process_inline_elements(li) }
65
65
  next if items.empty?
66
66
 
67
67
  blocks << {
68
- "type" => "list",
69
- "data" => {
70
- "style" => (child.name == "ul") ? "unordered" : "ordered",
71
- "items" => items
68
+ 'type' => 'list',
69
+ 'data' => {
70
+ 'style' => child.name == 'ul' ? 'unordered' : 'ordered',
71
+ 'items' => items
72
72
  }
73
73
  }
74
- when "blockquote"
74
+ when 'blockquote'
75
75
  blocks << {
76
- "type" => "quote",
77
- "data" => {
78
- "text" => process_inline_elements(child),
79
- "caption" => "",
80
- "alignment" => "left"
76
+ 'type' => 'quote',
77
+ 'data' => {
78
+ 'text' => process_inline_elements(child),
79
+ 'caption' => '',
80
+ 'alignment' => 'left'
81
81
  }
82
82
  }
83
- when "text"
83
+ when 'text'
84
84
  text = child.text.strip
85
85
  current_text += text if text.present?
86
86
  end
@@ -93,41 +93,41 @@ module Panda
93
93
  blocks << create_paragraph_block(paragraph) if paragraph.present?
94
94
  end
95
95
  end
96
- when "br"
96
+ when 'br'
97
97
  current_text += "\n\n"
98
- when "text"
98
+ when 'text'
99
99
  text = node.text.strip
100
100
  current_text += text if text.present?
101
- when "ul", "ol"
101
+ when 'ul', 'ol'
102
102
  # Add any accumulated text first
103
103
  if current_text.present?
104
104
  blocks << create_paragraph_block(current_text)
105
- current_text = ""
105
+ current_text = ''
106
106
  end
107
107
 
108
- items = node.css("li").map { |li| process_inline_elements(li) }
108
+ items = node.css('li').map { |li| process_inline_elements(li) }
109
109
  next if items.empty?
110
110
 
111
111
  blocks << {
112
- "type" => "list",
113
- "data" => {
114
- "style" => (node.name == "ul") ? "unordered" : "ordered",
115
- "items" => items
112
+ 'type' => 'list',
113
+ 'data' => {
114
+ 'style' => node.name == 'ul' ? 'unordered' : 'ordered',
115
+ 'items' => items
116
116
  }
117
117
  }
118
- when "blockquote"
118
+ when 'blockquote'
119
119
  # Add any accumulated text first
120
120
  if current_text.present?
121
121
  blocks << create_paragraph_block(current_text)
122
- current_text = ""
122
+ current_text = ''
123
123
  end
124
124
 
125
125
  blocks << {
126
- "type" => "quote",
127
- "data" => {
128
- "text" => process_inline_elements(node),
129
- "caption" => "",
130
- "alignment" => "left"
126
+ 'type' => 'quote',
127
+ 'data' => {
128
+ 'text' => process_inline_elements(node),
129
+ 'caption' => '',
130
+ 'alignment' => 'left'
131
131
  }
132
132
  }
133
133
  end
@@ -138,11 +138,11 @@ module Panda
138
138
 
139
139
  # Return the complete EditorJS structure
140
140
  {
141
- "time" => Time.current.to_i * 1000,
142
- "blocks" => blocks,
143
- "version" => "2.28.2"
141
+ 'time' => Time.current.to_i * 1000,
142
+ 'blocks' => blocks,
143
+ 'version' => '2.28.2'
144
144
  }
145
- rescue => e
145
+ rescue StandardError => e
146
146
  Rails.logger.error "HTML to EditorJS conversion failed: #{e.message}"
147
147
  Rails.logger.error e.backtrace.join("\n")
148
148
  raise ConversionError, "Failed to convert HTML to EditorJS format: #{e.message}"
@@ -151,41 +151,41 @@ module Panda
151
151
 
152
152
  def self.create_paragraph_block(text)
153
153
  {
154
- "type" => "paragraph",
155
- "data" => {
156
- "text" => text.strip
154
+ 'type' => 'paragraph',
155
+ 'data' => {
156
+ 'text' => text.strip
157
157
  }
158
158
  }
159
159
  end
160
160
 
161
161
  def self.process_inline_elements(node)
162
- result = ""
162
+ result = ''
163
163
  node.children.each do |child|
164
164
  case child.name
165
- when "br"
166
- result += "<br>"
167
- when "text"
165
+ when 'br'
166
+ result += '<br>'
167
+ when 'text'
168
168
  result += child.text
169
- when "strong", "b"
169
+ when 'strong', 'b'
170
170
  result += "<b>#{child.text}</b>"
171
- when "em", "i"
171
+ when 'em', 'i'
172
172
  result += "<i>#{child.text}</i>"
173
- when "a"
174
- href = child["href"]
173
+ when 'a'
174
+ href = child['href']
175
175
  text = child.text.strip
176
176
  # Handle email links specially
177
- if href&.start_with?("mailto:")
178
- email = href.sub("mailto:", "")
177
+ if href&.start_with?('mailto:')
178
+ email = href.sub('mailto:', '')
179
179
  result += "<a href=\"mailto:#{email}\">#{text}</a>"
180
180
  else
181
181
  result += "<a href=\"#{href}\">#{text}</a>"
182
182
  end
183
183
  else
184
184
  result += if child.text?
185
- child.text
186
- else
187
- child.to_html
188
- end
185
+ child.text
186
+ else
187
+ child.to_html
188
+ end
189
189
  end
190
190
  end
191
191
  result.strip
data/config/importmap.rb CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  # Pin npm packages by running ./bin/importmap
4
4
 
5
- pin_all_from "app/javascript/panda/editor", under: "panda/editor"
5
+ pin_all_from 'app/javascript/panda/editor', under: 'panda/editor'
6
6
 
7
7
  # EditorJS Core and plugins (from CDN)
8
- pin "@editorjs/editorjs", to: "https://cdn.jsdelivr.net/npm/@editorjs/editorjs@2.28.2/+esm"
9
- pin "@editorjs/paragraph", to: "https://cdn.jsdelivr.net/npm/@editorjs/paragraph@2.11.3/+esm"
10
- pin "@editorjs/header", to: "https://cdn.jsdelivr.net/npm/@editorjs/header@2.8.1/+esm"
11
- pin "@editorjs/nested-list", to: "https://cdn.jsdelivr.net/npm/@editorjs/nested-list@1.4.2/+esm"
12
- pin "@editorjs/quote", to: "https://cdn.jsdelivr.net/npm/@editorjs/quote@2.6.0/+esm"
13
- pin "@editorjs/simple-image", to: "https://cdn.jsdelivr.net/npm/@editorjs/simple-image@1.6.0/+esm"
14
- pin "@editorjs/table", to: "https://cdn.jsdelivr.net/npm/@editorjs/table@2.3.0/+esm"
15
- pin "@editorjs/embed", to: "https://cdn.jsdelivr.net/npm/@editorjs/embed@2.7.0/+esm"
8
+ pin '@editorjs/editorjs', to: 'https://cdn.jsdelivr.net/npm/@editorjs/editorjs@2.28.2/+esm'
9
+ pin '@editorjs/paragraph', to: 'https://cdn.jsdelivr.net/npm/@editorjs/paragraph@2.11.3/+esm'
10
+ pin '@editorjs/header', to: 'https://cdn.jsdelivr.net/npm/@editorjs/header@2.8.1/+esm'
11
+ pin '@editorjs/nested-list', to: 'https://cdn.jsdelivr.net/npm/@editorjs/nested-list@1.4.2/+esm'
12
+ pin '@editorjs/quote', to: 'https://cdn.jsdelivr.net/npm/@editorjs/quote@2.6.0/+esm'
13
+ pin '@editorjs/simple-image', to: 'https://cdn.jsdelivr.net/npm/@editorjs/simple-image@1.6.0/+esm'
14
+ pin '@editorjs/table', to: 'https://cdn.jsdelivr.net/npm/@editorjs/table@2.3.0/+esm'
15
+ pin '@editorjs/embed', to: 'https://cdn.jsdelivr.net/npm/@editorjs/embed@2.7.0/+esm'
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "net/http"
4
- require "json"
3
+ require 'net/http'
4
+ require 'json'
5
5
 
6
6
  module Panda
7
7
  module Editor
8
8
  class AssetLoader
9
- GITHUB_RELEASES_URL = "https://api.github.com/repos/tastybamboo/panda-editor/releases/latest"
10
- ASSET_CACHE_DIR = Rails.root.join("tmp", "panda_editor_assets")
9
+ GITHUB_RELEASES_URL = 'https://api.github.com/repos/tastybamboo/panda-editor/releases/latest'
10
+ ASSET_CACHE_DIR = Rails.root.join('tmp', 'panda_editor_assets')
11
11
 
12
12
  class << self
13
13
  def load_assets
@@ -22,7 +22,7 @@ module Panda
22
22
  if use_compiled_assets?
23
23
  compiled_javascript_url
24
24
  else
25
- "/assets/panda/editor/application.js"
25
+ '/assets/panda/editor/application.js'
26
26
  end
27
27
  end
28
28
 
@@ -30,7 +30,7 @@ module Panda
30
30
  if use_compiled_assets?
31
31
  compiled_stylesheet_url
32
32
  else
33
- "/assets/panda/editor/application.css"
33
+ '/assets/panda/editor/application.css'
34
34
  end
35
35
  end
36
36
 
@@ -39,7 +39,7 @@ module Panda
39
39
  def use_compiled_assets?
40
40
  Rails.env.production? ||
41
41
  Rails.env.test? ||
42
- ENV["PANDA_EDITOR_USE_COMPILED_ASSETS"] == "true"
42
+ ENV['PANDA_EDITOR_USE_COMPILED_ASSETS'] == 'true'
43
43
  end
44
44
 
45
45
  def load_compiled_assets
@@ -52,23 +52,23 @@ module Panda
52
52
 
53
53
  def load_development_assets
54
54
  {
55
- javascript: "/assets/panda/editor/application.js",
56
- stylesheet: "/assets/panda/editor/application.css"
55
+ javascript: '/assets/panda/editor/application.js',
56
+ stylesheet: '/assets/panda/editor/application.css'
57
57
  }
58
58
  end
59
59
 
60
60
  def compiled_javascript_url
61
- asset_path = find_latest_asset("js")
61
+ asset_path = find_latest_asset('js')
62
62
  asset_path ? "/panda-editor-assets/#{File.basename(asset_path)}" : nil
63
63
  end
64
64
 
65
65
  def compiled_stylesheet_url
66
- asset_path = find_latest_asset("css")
66
+ asset_path = find_latest_asset('css')
67
67
  asset_path ? "/panda-editor-assets/#{File.basename(asset_path)}" : nil
68
68
  end
69
69
 
70
70
  def find_latest_asset(extension)
71
- pattern = Rails.root.join("public", "panda-editor-assets", "panda-editor-*.#{extension}")
71
+ pattern = Rails.root.join('public', 'panda-editor-assets', "panda-editor-*.#{extension}")
72
72
  Dir.glob(pattern).max_by { |f| File.mtime(f) }
73
73
  end
74
74
 
@@ -79,18 +79,18 @@ module Panda
79
79
  end
80
80
 
81
81
  def assets_exist?
82
- js_exists = Dir.glob(Rails.root.join("public", "panda-editor-assets", "panda-editor-*.js")).any?
83
- css_exists = Dir.glob(Rails.root.join("public", "panda-editor-assets", "panda-editor-*.css")).any?
82
+ js_exists = Dir.glob(Rails.root.join('public', 'panda-editor-assets', 'panda-editor-*.js')).any?
83
+ css_exists = Dir.glob(Rails.root.join('public', 'panda-editor-assets', 'panda-editor-*.css')).any?
84
84
  js_exists && css_exists
85
85
  end
86
86
 
87
87
  def download_assets_from_github
88
- Rails.logger.info "[Panda Editor] Downloading assets from GitHub releases..."
88
+ Rails.logger.info '[Panda Editor] Downloading assets from GitHub releases...'
89
89
 
90
90
  begin
91
91
  release_data = fetch_latest_release
92
- download_release_assets(release_data["assets"])
93
- rescue => e
92
+ download_release_assets(release_data['assets'])
93
+ rescue StandardError => e
94
94
  Rails.logger.error "[Panda Editor] Failed to download assets: #{e.message}"
95
95
  use_fallback_assets
96
96
  end
@@ -100,32 +100,30 @@ module Panda
100
100
  uri = URI(GITHUB_RELEASES_URL)
101
101
  response = Net::HTTP.get_response(uri)
102
102
 
103
- if response.code == "200"
104
- JSON.parse(response.body)
105
- else
106
- raise "GitHub API returned #{response.code}"
107
- end
103
+ raise "GitHub API returned #{response.code}" unless response.code == '200'
104
+
105
+ JSON.parse(response.body)
108
106
  end
109
107
 
110
108
  def download_release_assets(assets)
111
109
  assets.each do |asset|
112
- next unless asset["name"].match?(/panda-editor.*\.(js|css)$/)
110
+ next unless asset['name'].match?(/panda-editor.*\.(js|css)$/)
113
111
 
114
112
  download_asset(asset)
115
113
  end
116
114
  end
117
115
 
118
116
  def download_asset(asset)
119
- uri = URI(asset["browser_download_url"])
117
+ uri = URI(asset['browser_download_url'])
120
118
  response = Net::HTTP.get_response(uri)
121
119
 
122
- if response.code == "200"
123
- save_asset(asset["name"], response.body)
124
- end
120
+ return unless response.code == '200'
121
+
122
+ save_asset(asset['name'], response.body)
125
123
  end
126
124
 
127
125
  def save_asset(filename, content)
128
- dir = Rails.root.join("public", "panda-editor-assets")
126
+ dir = Rails.root.join('public', 'panda-editor-assets')
129
127
  FileUtils.mkdir_p(dir)
130
128
 
131
129
  File.write(dir.join(filename), content)
@@ -133,17 +131,17 @@ module Panda
133
131
  end
134
132
 
135
133
  def use_fallback_assets
136
- Rails.logger.warn "[Panda Editor] Using fallback embedded assets"
134
+ Rails.logger.warn '[Panda Editor] Using fallback embedded assets'
137
135
  # Copy embedded assets from gem to public directory
138
136
  copy_embedded_assets
139
137
  end
140
138
 
141
139
  def copy_embedded_assets
142
- source_dir = Panda::Editor::Engine.root.join("public", "panda-editor-assets")
143
- dest_dir = Rails.root.join("public", "panda-editor-assets")
140
+ source_dir = Panda::Editor::Engine.root.join('public', 'panda-editor-assets')
141
+ dest_dir = Rails.root.join('public', 'panda-editor-assets')
144
142
 
145
143
  FileUtils.mkdir_p(dest_dir)
146
- FileUtils.cp_r(Dir.glob(source_dir.join("*")), dest_dir)
144
+ FileUtils.cp_r(Dir.glob(source_dir.join('*')), dest_dir)
147
145
  end
148
146
  end
149
147
  end
@@ -5,13 +5,13 @@ module Panda
5
5
  module Blocks
6
6
  class Alert < Base
7
7
  def render
8
- message = sanitize(data["message"])
9
- type = data["type"] || "primary"
8
+ message = sanitize(data['message'])
9
+ type = data['type'] || 'primary'
10
10
 
11
11
  html_safe(
12
12
  "<div class=\"#{alert_classes(type)} p-4 mb-4 rounded-lg\">" \
13
13
  "#{message}" \
14
- "</div>"
14
+ '</div>'
15
15
  )
16
16
  end
17
17
 
@@ -19,13 +19,13 @@ module Panda
19
19
 
20
20
  def alert_classes(type)
21
21
  case type
22
- when "primary" then "bg-blue-100 text-blue-800"
23
- when "secondary" then "bg-gray-100 text-gray-800"
24
- when "success" then "bg-green-100 text-green-800"
25
- when "danger" then "bg-red-100 text-red-800"
26
- when "warning" then "bg-yellow-100 text-yellow-800"
27
- when "info" then "bg-indigo-100 text-indigo-800"
28
- else "bg-blue-100 text-blue-800"
22
+ when 'primary' then 'bg-blue-100 text-blue-800'
23
+ when 'secondary' then 'bg-gray-100 text-gray-800'
24
+ when 'success' then 'bg-green-100 text-green-800'
25
+ when 'danger' then 'bg-red-100 text-red-800'
26
+ when 'warning' then 'bg-yellow-100 text-yellow-800'
27
+ when 'info' then 'bg-indigo-100 text-indigo-800'
28
+ else 'bg-blue-100 text-blue-800'
29
29
  end
30
30
  end
31
31
  end
@@ -15,7 +15,7 @@ module Panda
15
15
  end
16
16
 
17
17
  def render
18
- ""
18
+ ''
19
19
  end
20
20
 
21
21
  protected
@@ -5,8 +5,8 @@ module Panda
5
5
  module Blocks
6
6
  class Header < Base
7
7
  def render
8
- content = sanitize(data["text"])
9
- level = data["level"] || 2
8
+ content = sanitize(data['text'])
9
+ level = data['level'] || 2
10
10
  html_safe("<h#{level}>#{content}</h#{level}>")
11
11
  end
12
12
  end