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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fbf5326beb171bbb5448b7545bfe87fdef49faac3c82f85c1a64a30cc7aa276d
|
|
4
|
+
data.tar.gz: 05c314fcf2d2b2c5cdfe2d4e2684b392bc2d4cd7231c91870a6f6100a1ca7170
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cab18e487c3e4021cebf6ffbd7c98a4c15103ca96a53cbbbd97b3b377a99d83d7c81de246f620359317d166b5716b56d3105be01d7ab1930c96a9f103164ac1b
|
|
7
|
+
data.tar.gz: 1c3ae8993db2fe42ebd8e4c22c664655aa86961757605d897ca5c4c4553b3c0e4427c203d15b23bfae7ce4d83cfc92d6f02d8aa3dc025f3babb3673bc1850de1
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ 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.5.0] - 2025-11-04
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Code quality improvements with Rubocop auto-corrections
|
|
12
|
+
- Fixed 1263 style violations across the codebase
|
|
13
|
+
- Converted double-quoted strings to single quotes where appropriate
|
|
14
|
+
- Improved code consistency and readability
|
|
15
|
+
- Corrected string literal usage throughout
|
|
16
|
+
- Fixed extra spacing and formatting issues
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- All tests passing (53 examples, 0 failures)
|
|
20
|
+
- Improved code maintainability
|
|
21
|
+
|
|
22
|
+
## [0.4.0] - 2025-10-30
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- Markdown support for rich text formatting in footnotes
|
|
26
|
+
- **Bold**, *italic*, `code`, ~~strikethrough~~, and [link](url) support
|
|
27
|
+
- Automatic URL linking in markdown content
|
|
28
|
+
- Security-hardened Redcarpet configuration (no images, safe links only)
|
|
29
|
+
- Works alongside existing autolink_urls option
|
|
30
|
+
- Comprehensive test coverage for markdown features
|
|
31
|
+
- Updated documentation with markdown examples
|
|
32
|
+
|
|
8
33
|
## [0.3.0] - 2025-10-30
|
|
9
34
|
|
|
10
35
|
### Added
|
data/README.md
CHANGED
|
@@ -168,6 +168,27 @@ The sources section includes data attributes for integration with JavaScript fra
|
|
|
168
168
|
|
|
169
169
|
See [docs/FOOTNOTES.md](docs/FOOTNOTES.md) for detailed implementation examples.
|
|
170
170
|
|
|
171
|
+
### Markdown Support
|
|
172
|
+
|
|
173
|
+
Enable markdown formatting in footnote content for rich text citations:
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
renderer = Panda::Editor::Renderer.new(@content, markdown: true)
|
|
177
|
+
html = renderer.render
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Supports **bold**, *italic*, `code`, ~~strikethrough~~, and [links](url):
|
|
181
|
+
|
|
182
|
+
**Input:**
|
|
183
|
+
```
|
|
184
|
+
Smith, J. (2023). **Important study** on *ADHD treatment*. See https://example.com for details.
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Output:**
|
|
188
|
+
```html
|
|
189
|
+
Smith, J. (2023). <strong>Important study</strong> on <em>ADHD treatment</em>. See <a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a> for details.
|
|
190
|
+
```
|
|
191
|
+
|
|
171
192
|
### Auto-linking URLs
|
|
172
193
|
|
|
173
194
|
Enable automatic URL linking in footnote content:
|
|
@@ -195,6 +216,9 @@ Features:
|
|
|
195
216
|
- Won't double-link URLs already in `<a>` tags
|
|
196
217
|
- Supports `http://`, `https://`, `ftp://`, and `www.` URLs
|
|
197
218
|
- Handles multiple URLs in the same footnote
|
|
219
|
+
- Can be combined with `markdown: true` (markdown's autolink runs first, then custom autolink for any remaining URLs)
|
|
220
|
+
|
|
221
|
+
**Note:** When using `markdown: true`, you typically don't need `autolink_urls: true` as markdown includes built-in autolinking. However, both options can work together safely.
|
|
198
222
|
|
|
199
223
|
To enable globally for all content using the `Panda::Editor::Content` concern, pass the option in `generate_cached_content`.
|
|
200
224
|
|
|
@@ -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[
|
|
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,
|
|
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
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
'type' => 'header',
|
|
33
|
+
'data' => {
|
|
34
|
+
'text' => node.text.strip,
|
|
35
|
+
'level' => node.name[1].to_i
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
when
|
|
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 ==
|
|
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
|
|
49
|
+
when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
|
|
50
50
|
blocks << {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
'type' => 'header',
|
|
52
|
+
'data' => {
|
|
53
|
+
'text' => child.text.strip,
|
|
54
|
+
'level' => child.name[1].to_i
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
when
|
|
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
|
|
64
|
-
items = child.css(
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
'type' => 'list',
|
|
69
|
+
'data' => {
|
|
70
|
+
'style' => child.name == 'ul' ? 'unordered' : 'ordered',
|
|
71
|
+
'items' => items
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
when
|
|
74
|
+
when 'blockquote'
|
|
75
75
|
blocks << {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
'type' => 'quote',
|
|
77
|
+
'data' => {
|
|
78
|
+
'text' => process_inline_elements(child),
|
|
79
|
+
'caption' => '',
|
|
80
|
+
'alignment' => 'left'
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
when
|
|
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
|
|
96
|
+
when 'br'
|
|
97
97
|
current_text += "\n\n"
|
|
98
|
-
when
|
|
98
|
+
when 'text'
|
|
99
99
|
text = node.text.strip
|
|
100
100
|
current_text += text if text.present?
|
|
101
|
-
when
|
|
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(
|
|
108
|
+
items = node.css('li').map { |li| process_inline_elements(li) }
|
|
109
109
|
next if items.empty?
|
|
110
110
|
|
|
111
111
|
blocks << {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
'type' => 'list',
|
|
113
|
+
'data' => {
|
|
114
|
+
'style' => node.name == 'ul' ? 'unordered' : 'ordered',
|
|
115
|
+
'items' => items
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
when
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
166
|
-
result +=
|
|
167
|
-
when
|
|
165
|
+
when 'br'
|
|
166
|
+
result += '<br>'
|
|
167
|
+
when 'text'
|
|
168
168
|
result += child.text
|
|
169
|
-
when
|
|
169
|
+
when 'strong', 'b'
|
|
170
170
|
result += "<b>#{child.text}</b>"
|
|
171
|
-
when
|
|
171
|
+
when 'em', 'i'
|
|
172
172
|
result += "<i>#{child.text}</i>"
|
|
173
|
-
when
|
|
174
|
-
href = child[
|
|
173
|
+
when 'a'
|
|
174
|
+
href = child['href']
|
|
175
175
|
text = child.text.strip
|
|
176
176
|
# Handle email links specially
|
|
177
|
-
if href&.start_with?(
|
|
178
|
-
email = href.sub(
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
|
5
|
+
pin_all_from 'app/javascript/panda/editor', under: 'panda/editor'
|
|
6
6
|
|
|
7
7
|
# EditorJS Core and plugins (from CDN)
|
|
8
|
-
pin
|
|
9
|
-
pin
|
|
10
|
-
pin
|
|
11
|
-
pin
|
|
12
|
-
pin
|
|
13
|
-
pin
|
|
14
|
-
pin
|
|
15
|
-
pin
|
|
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'
|
data/docs/FOOTNOTES.md
CHANGED
|
@@ -308,9 +308,58 @@ insert at position 11: "Hello world<sup>2</sup>"
|
|
|
308
308
|
insert at position 5: "Hello<sup>1</sup> world<sup>2</sup>"
|
|
309
309
|
```
|
|
310
310
|
|
|
311
|
+
### Markdown Support
|
|
312
|
+
|
|
313
|
+
The footnote system supports markdown formatting, allowing you to use rich text formatting in your citations.
|
|
314
|
+
|
|
315
|
+
**Enable markdown:**
|
|
316
|
+
|
|
317
|
+
```ruby
|
|
318
|
+
renderer = Panda::Editor::Renderer.new(content, markdown: true)
|
|
319
|
+
output = renderer.render
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Supported markdown features:**
|
|
323
|
+
|
|
324
|
+
- **Bold text** (`**bold**` or `__bold__`)
|
|
325
|
+
- *Italic text* (`*italic*` or `_italic_`)
|
|
326
|
+
- `Inline code` (`` `code` ``)
|
|
327
|
+
- ~~Strikethrough~~ (`~~text~~`)
|
|
328
|
+
- [Links](url) (`[text](url)`)
|
|
329
|
+
- Automatic URL linking
|
|
330
|
+
|
|
331
|
+
**Example:**
|
|
332
|
+
|
|
333
|
+
```ruby
|
|
334
|
+
content = {
|
|
335
|
+
"blocks" => [{
|
|
336
|
+
"type" => "paragraph",
|
|
337
|
+
"data" => {
|
|
338
|
+
"text" => "Research findings",
|
|
339
|
+
"footnotes" => [{
|
|
340
|
+
"id" => "fn-1",
|
|
341
|
+
"content" => "Smith, J. (2023). **Important study** on *ADHD treatment*. See https://example.com for details.",
|
|
342
|
+
"position" => 17
|
|
343
|
+
}]
|
|
344
|
+
}
|
|
345
|
+
}]
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
renderer = Panda::Editor::Renderer.new(content, markdown: true)
|
|
349
|
+
# Output will include: Smith, J. (2023). <strong>Important study</strong> on <em>ADHD treatment</em>. See <a href="https://example.com">https://example.com</a> for details.
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**Important notes:**
|
|
353
|
+
|
|
354
|
+
- Markdown includes built-in URL autolinking, so you typically don't need `autolink_urls: true` when using markdown
|
|
355
|
+
- However, both options can be used together if needed - the custom autolink_urls will skip URLs that markdown already linked
|
|
356
|
+
- Markdown links are rendered with `target="_blank"` and `rel="noopener noreferrer"` for security
|
|
357
|
+
- Images are disabled in markdown footnotes for security
|
|
358
|
+
- HTML styles are stripped from markdown output
|
|
359
|
+
|
|
311
360
|
### Auto-linking URLs
|
|
312
361
|
|
|
313
|
-
The footnote system can automatically convert plain URLs in footnote content into clickable links.
|
|
362
|
+
The footnote system can automatically convert plain URLs in footnote content into clickable links when markdown is not enabled.
|
|
314
363
|
|
|
315
364
|
**Enable auto-linking:**
|
|
316
365
|
|
|
@@ -575,7 +624,7 @@ bundle exec rspec spec/lib/panda/editor/renderer_spec.rb
|
|
|
575
624
|
Potential improvements for future versions:
|
|
576
625
|
|
|
577
626
|
- [ ] Support for footnotes in other block types (headers, quotes, etc.)
|
|
578
|
-
- [
|
|
627
|
+
- [x] Rich text formatting within footnote content (implemented via markdown support)
|
|
579
628
|
- [ ] Footnote tooltips on hover
|
|
580
629
|
- [ ] Customizable footnote markers (*, †, ‡, etc.)
|
|
581
630
|
- [ ] Export footnotes to bibliography formats (BibTeX, etc.)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
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 =
|
|
10
|
-
ASSET_CACHE_DIR = Rails.root.join(
|
|
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
|
-
|
|
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
|
-
|
|
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[
|
|
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:
|
|
56
|
-
stylesheet:
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
83
|
-
css_exists = Dir.glob(Rails.root.join(
|
|
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
|
|
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[
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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[
|
|
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[
|
|
117
|
+
uri = URI(asset['browser_download_url'])
|
|
120
118
|
response = Net::HTTP.get_response(uri)
|
|
121
119
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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(
|
|
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
|
|
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(
|
|
143
|
-
dest_dir = Rails.root.join(
|
|
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(
|
|
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[
|
|
9
|
-
type = data[
|
|
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
|
-
|
|
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
|
|
23
|
-
when
|
|
24
|
-
when
|
|
25
|
-
when
|
|
26
|
-
when
|
|
27
|
-
when
|
|
28
|
-
else
|
|
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
|