ckeditor5 1.0.6 → 1.1.1
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/README.md +438 -78
- data/lib/ckeditor5/rails/assets/assets_bundle.rb +1 -1
- data/lib/ckeditor5/rails/assets/assets_bundle_html_serializer.rb +3 -2
- data/lib/ckeditor5/rails/assets/webcomponent.mjs +134 -19
- data/lib/ckeditor5/rails/editor/helpers.rb +6 -1
- data/lib/ckeditor5/rails/editor/props.rb +1 -1
- data/lib/ckeditor5/rails/editor/props_inline_plugin.rb +32 -0
- data/lib/ckeditor5/rails/editor/props_plugin.rb +18 -13
- data/lib/ckeditor5/rails/engine.rb +17 -0
- data/lib/ckeditor5/rails/hooks/form.rb +23 -0
- data/lib/ckeditor5/rails/hooks/simple_form.rb +25 -0
- data/lib/ckeditor5/rails/presets.rb +59 -8
- data/lib/ckeditor5/rails/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ba0f56261f0c699d67fce659fe6cd4cbc3fc2f288ebedf0fd908f4f6d9555001
|
|
4
|
+
data.tar.gz: 9c5b41693f3858a94841b7e6392bbd573a3bc83c3a936c7dd2fbdcfff42fda63
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e12b59365223eeeee6cccef5e771ae2df9e17e9500275a871c8f7b41b6a39abe0cec725848f887ead1a3235d2d50447fdb8b99ead940d1dc50c5f795369d9354
|
|
7
|
+
data.tar.gz: b163824d28f9867b0bee31e273e5b0be6ad380891d43a1e48ed1fb4df0c38460a4cfddc5fcb7fdf1fa1430950b6cd3d47a6c4cbcf8c4067f5178660f6c991d15
|
data/README.md
CHANGED
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
Unofficial CKEditor 5 Ruby on Rails integration gem. Provides seamless integration of CKEditor 5 with Rails applications through web components and helper methods.
|
|
10
10
|
|
|
11
|
+
<p align="center">
|
|
12
|
+
<img src="docs/intro-classic-editor.png" alt="CKEditor 5 Classic Editor in Ruby on Rails application">
|
|
13
|
+
</p>
|
|
14
|
+
|
|
11
15
|
## Installation 🛠️
|
|
12
16
|
|
|
13
17
|
Add this line to your application's Gemfile:
|
|
@@ -16,21 +20,33 @@ Add this line to your application's Gemfile:
|
|
|
16
20
|
gem 'ckeditor5'
|
|
17
21
|
```
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
In your config:
|
|
24
|
+
|
|
25
|
+
```rb
|
|
26
|
+
# config/initializers/ckeditor5.rb
|
|
27
|
+
|
|
28
|
+
CKEditor5::Rails::Engine.configure do |config|
|
|
29
|
+
config.presets.override :default do
|
|
30
|
+
version '43.3.0'
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
In your view:
|
|
20
36
|
|
|
21
37
|
```erb
|
|
22
38
|
<!-- app/views/demos/index.html.erb -->
|
|
23
39
|
|
|
24
40
|
<% content_for :head do %>
|
|
25
|
-
<%= ckeditor5_assets
|
|
41
|
+
<%= ckeditor5_assets %>
|
|
42
|
+
<!-- or using inline config: ckeditor5_assets version: '43.3.0', premium: false -->
|
|
26
43
|
<% end %>
|
|
27
44
|
|
|
28
|
-
<%= ckeditor5_editor style: 'width: 600px' %>
|
|
45
|
+
<%= ckeditor5_editor style: 'width: 600px', initial_data: '<YOUR DATA>' %>
|
|
46
|
+
<!-- or using inline config: ckeditor5_editor type: :classic, config: { toolbar: [:Bold, :Italic] }, ... -->
|
|
29
47
|
```
|
|
30
48
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-

|
|
49
|
+
Voilà! You have CKEditor 5 integrated with your Rails application. 🎉
|
|
34
50
|
|
|
35
51
|
## Table of Contents 📚
|
|
36
52
|
|
|
@@ -41,17 +57,18 @@ Result:
|
|
|
41
57
|
- [Available Configuration Methods ⚙️](#available-configuration-methods-️)
|
|
42
58
|
- [`version(version)` method](#versionversion-method)
|
|
43
59
|
- [`gpl` method](#gpl-method)
|
|
60
|
+
- [`license_key(key)` method](#license_keykey-method)
|
|
44
61
|
- [`premium` method](#premium-method)
|
|
45
62
|
- [`translations(*languages)` method](#translationslanguages-method)
|
|
46
|
-
- [`license_key(key)` method](#license_keykey-method)
|
|
47
63
|
- [`ckbox` method](#ckbox-method)
|
|
48
64
|
- [`type(type)` method](#typetype-method)
|
|
49
|
-
- [`
|
|
50
|
-
- [`toolbar(*items, should_group_when_full: true)` method](#toolbaritems-should_group_when_full-true-method)
|
|
65
|
+
- [`toolbar(*items, should_group_when_full: true, &block)` method](#toolbaritems-should_group_when_full-true-block-method)
|
|
51
66
|
- [`menubar(visible: true)` method](#menubarvisible-true-method)
|
|
52
67
|
- [`language(ui, content:)` method](#languageui-content-method)
|
|
53
68
|
- [`configure(name, value)` method](#configurename-value-method)
|
|
54
69
|
- [`plugin(name, premium:, import_name:)` method](#pluginname-premium-import_name-method)
|
|
70
|
+
- [`plugins(*names, **kwargs)` method](#pluginsnames-kwargs-method)
|
|
71
|
+
- [`inline_plugin(name, code)` method](#inline_pluginname-code-method)
|
|
55
72
|
- [Including CKEditor 5 assets 📦](#including-ckeditor-5-assets-)
|
|
56
73
|
- [Lazy loading 🚀](#lazy-loading-)
|
|
57
74
|
- [GPL usage 🆓](#gpl-usage-)
|
|
@@ -62,6 +79,18 @@ Result:
|
|
|
62
79
|
- [Inline editor 📝](#inline-editor-)
|
|
63
80
|
- [Balloon editor 🎈](#balloon-editor-)
|
|
64
81
|
- [Decoupled editor 🌐](#decoupled-editor-)
|
|
82
|
+
- [How to access editor instance? 🤔](#how-to-access-editor-instance-)
|
|
83
|
+
- [Events fired by the editor 🔊](#events-fired-by-the-editor-)
|
|
84
|
+
- [`editor-ready` event](#editor-ready-event)
|
|
85
|
+
- [`editor-error` event](#editor-error-event)
|
|
86
|
+
- [Common Tasks and Solutions 💡](#common-tasks-and-solutions-)
|
|
87
|
+
- [Setting Initial Content 📝](#setting-initial-content-)
|
|
88
|
+
- [Setting Editor Language 🌐](#setting-editor-language-)
|
|
89
|
+
- [Integrating with Forms 📋](#integrating-with-forms-)
|
|
90
|
+
- [Rails form builder integration](#rails-form-builder-integration)
|
|
91
|
+
- [Simple form integration](#simple-form-integration)
|
|
92
|
+
- [Custom Styling 🎨](#custom-styling-)
|
|
93
|
+
- [Custom plugins 🧩](#custom-plugins-)
|
|
65
94
|
- [License 📜](#license-)
|
|
66
95
|
|
|
67
96
|
## Presets 🎨
|
|
@@ -75,18 +104,16 @@ You can create your own by defining it in the `config/initializers/ckeditor5.rb`
|
|
|
75
104
|
|
|
76
105
|
CKEditor5::Rails::Engine.configure do |config|
|
|
77
106
|
config.presets.define :custom
|
|
78
|
-
gpl
|
|
79
|
-
|
|
107
|
+
gpl
|
|
80
108
|
type :classic
|
|
81
109
|
|
|
82
110
|
menubar
|
|
83
|
-
|
|
84
111
|
toolbar :undo, :redo, :|, :heading, :|, :bold, :italic, :underline, :|,
|
|
85
|
-
:link, :insertImage, :
|
|
112
|
+
:link, :insertImage, :mediaEmbed, :insertTable, :blockQuote, :|,
|
|
86
113
|
:bulletedList, :numberedList, :todoList, :outdent, :indent
|
|
87
114
|
|
|
88
115
|
plugins :AccessibilityHelp, :Autoformat, :AutoImage, :Autosave,
|
|
89
|
-
:BlockQuote, :Bold, :
|
|
116
|
+
:BlockQuote, :Bold, :CloudServices,
|
|
90
117
|
:Essentials, :Heading, :ImageBlock, :ImageCaption, :ImageInline,
|
|
91
118
|
:ImageInsert, :ImageInsertViaUrl, :ImageResize, :ImageStyle,
|
|
92
119
|
:ImageTextAlternative, :ImageToolbar, :ImageUpload, :Indent,
|
|
@@ -95,6 +122,10 @@ CKEditor5::Rails::Engine.configure do |config|
|
|
|
95
122
|
:SelectAll, :Table, :TableCaption, :TableCellProperties,
|
|
96
123
|
:TableColumnResize, :TableProperties, :TableToolbar,
|
|
97
124
|
:TextTransformation, :TodoList, :Underline, :Undo, :Base64UploadAdapter
|
|
125
|
+
|
|
126
|
+
configure :image, {
|
|
127
|
+
toolbar: ['imageTextAlternative', 'imageStyle:inline', 'imageStyle:block', 'imageStyle:side']
|
|
128
|
+
}
|
|
98
129
|
end
|
|
99
130
|
end
|
|
100
131
|
```
|
|
@@ -107,6 +138,13 @@ In order to override existing presets, you can use the `config.presets.override`
|
|
|
107
138
|
CKEditor5::Rails::Engine.configure do |config|
|
|
108
139
|
config.presets.override :default do
|
|
109
140
|
menubar visible: false
|
|
141
|
+
|
|
142
|
+
toolbar do
|
|
143
|
+
remove :underline, :heading
|
|
144
|
+
|
|
145
|
+
# prepend :underline
|
|
146
|
+
# append :heading
|
|
147
|
+
end
|
|
110
148
|
end
|
|
111
149
|
end
|
|
112
150
|
```
|
|
@@ -146,9 +184,23 @@ config.presets.define :custom do
|
|
|
146
184
|
end
|
|
147
185
|
```
|
|
148
186
|
|
|
187
|
+
#### `license_key(key)` method
|
|
188
|
+
|
|
189
|
+
Defines the license key of CKEditor 5. It calls `premium` method internally. The example below shows how to set the license key:
|
|
190
|
+
|
|
191
|
+
```rb
|
|
192
|
+
# config/initializers/ckeditor5.rb
|
|
193
|
+
|
|
194
|
+
config.presets.define :custom do
|
|
195
|
+
# ... other configuration
|
|
196
|
+
|
|
197
|
+
license_key 'your-license-key'
|
|
198
|
+
end
|
|
199
|
+
```
|
|
200
|
+
|
|
149
201
|
#### `premium` method
|
|
150
202
|
|
|
151
|
-
Defines if premium package
|
|
203
|
+
Defines if premium package should be included in JS assets. The example below shows how to add `ckeditor5-premium-features` to import maps:
|
|
152
204
|
|
|
153
205
|
```rb
|
|
154
206
|
# config/initializers/ckeditor5.rb
|
|
@@ -162,7 +214,7 @@ end
|
|
|
162
214
|
|
|
163
215
|
#### `translations(*languages)` method
|
|
164
216
|
|
|
165
|
-
Defines the translations of CKEditor 5. You can pass the language codes as arguments. The example below shows how to
|
|
217
|
+
Defines the translations of CKEditor 5. You can pass the language codes as arguments. The example below shows how tell integration to fetch Polish and Spanish translations:
|
|
166
218
|
|
|
167
219
|
```rb
|
|
168
220
|
# config/initializers/ckeditor5.rb
|
|
@@ -174,17 +226,15 @@ config.presets.define :custom do
|
|
|
174
226
|
end
|
|
175
227
|
```
|
|
176
228
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
Defines the license key of CKEditor 5. It calls `premium` method internally. The example below shows how to set the license key:
|
|
229
|
+
⚠️ You need to use `language` method to set the default language of the editor, as the `translations` only fetch the translations files and makes them available to later use.
|
|
180
230
|
|
|
181
231
|
```rb
|
|
182
232
|
# config/initializers/ckeditor5.rb
|
|
183
233
|
|
|
184
234
|
config.presets.define :custom do
|
|
185
|
-
|
|
235
|
+
translations :pl
|
|
186
236
|
|
|
187
|
-
|
|
237
|
+
language :pl
|
|
188
238
|
end
|
|
189
239
|
```
|
|
190
240
|
|
|
@@ -224,9 +274,14 @@ config.presets.define :custom do
|
|
|
224
274
|
end
|
|
225
275
|
```
|
|
226
276
|
|
|
227
|
-
#### `
|
|
277
|
+
#### `toolbar(*items, should_group_when_full: true, &block)` method
|
|
228
278
|
|
|
229
|
-
Defines the
|
|
279
|
+
Defines the toolbar items. You can use predefined items like `:undo`, `:redo`, `:|` or specify custom items. There are a few special items:
|
|
280
|
+
|
|
281
|
+
- `:_` - breakpoint
|
|
282
|
+
- `:|` - separator
|
|
283
|
+
|
|
284
|
+
The `should_group_when_full` keyword argument determines whether the toolbar should group items when there is not enough space. It's set to `true` by default.
|
|
230
285
|
|
|
231
286
|
```rb
|
|
232
287
|
# config/initializers/ckeditor5.rb
|
|
@@ -234,33 +289,43 @@ Defines the plugins to be included in the editor. You can specify multiple plugi
|
|
|
234
289
|
config.presets.define :custom do
|
|
235
290
|
# ... other configuration
|
|
236
291
|
|
|
237
|
-
|
|
292
|
+
toolbar :undo, :redo, :|, :heading, :|, :bold, :italic, :underline, :|,
|
|
293
|
+
:link, :insertImage, :ckbox, :mediaEmbed, :insertTable, :blockQuote, :|,
|
|
294
|
+
:bulletedList, :numberedList, :todoList, :outdent, :indent
|
|
238
295
|
end
|
|
239
296
|
```
|
|
240
297
|
|
|
241
|
-
|
|
298
|
+
Keep in mind that the order of items is important, and you should install the corresponding plugins. You can find the list of available plugins in the [CKEditor 5 documentation](https://ckeditor.com/docs/ckeditor5/latest/framework/architecture/plugins.html).
|
|
242
299
|
|
|
243
|
-
|
|
300
|
+
If you want to add or prepend items to the existing toolbar, you can use the block syntax:
|
|
244
301
|
|
|
245
|
-
|
|
246
|
-
|
|
302
|
+
```rb
|
|
303
|
+
# config/initializers/ckeditor5.rb
|
|
247
304
|
|
|
248
|
-
|
|
305
|
+
config.presets.override :default do
|
|
306
|
+
# ... other configuration
|
|
307
|
+
|
|
308
|
+
toolbar do
|
|
309
|
+
append :selectAll, :|, :selectAll, :selectAll
|
|
310
|
+
# Or prepend: prepend :selectAll, :|, :selectAll, :selectAll
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
If you want to remove items from the toolbar, you can use the `remove` method:
|
|
249
316
|
|
|
250
317
|
```rb
|
|
251
318
|
# config/initializers/ckeditor5.rb
|
|
252
319
|
|
|
253
|
-
config.presets.
|
|
320
|
+
config.presets.override :default do
|
|
254
321
|
# ... other configuration
|
|
255
322
|
|
|
256
|
-
toolbar
|
|
257
|
-
|
|
258
|
-
|
|
323
|
+
toolbar do
|
|
324
|
+
remove :selectAll, :heading #, ...
|
|
325
|
+
end
|
|
259
326
|
end
|
|
260
327
|
```
|
|
261
328
|
|
|
262
|
-
Keep in mind that the order of items is important, and you should install the corresponding plugins. You can find the list of available plugins in the [CKEditor 5 documentation](https://ckeditor.com/docs/ckeditor5/latest/framework/architecture/plugins.html).
|
|
263
|
-
|
|
264
329
|
#### `menubar(visible: true)` method
|
|
265
330
|
|
|
266
331
|
Defines the visibility of the menubar. By default, it's set to `true`.
|
|
@@ -335,7 +400,7 @@ config.presets.define :custom do
|
|
|
335
400
|
end
|
|
336
401
|
```
|
|
337
402
|
|
|
338
|
-
In order to import a plugin from a custom package, you can pass the `import_name` keyword argument:
|
|
403
|
+
In order to import a plugin from a custom ESM package, you can pass the `import_name` keyword argument:
|
|
339
404
|
|
|
340
405
|
```rb
|
|
341
406
|
# config/initializers/ckeditor5.rb
|
|
@@ -347,6 +412,57 @@ config.presets.define :custom do
|
|
|
347
412
|
end
|
|
348
413
|
```
|
|
349
414
|
|
|
415
|
+
In order to import a plugin from a custom Window entry, you can pass the `window_name` keyword argument:
|
|
416
|
+
|
|
417
|
+
```rb
|
|
418
|
+
# config/initializers/ckeditor5.rb
|
|
419
|
+
|
|
420
|
+
config.presets.define :custom do
|
|
421
|
+
# ... other configuration
|
|
422
|
+
|
|
423
|
+
plugin :YourPlugin, window_name: 'YourPlugin'
|
|
424
|
+
end
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
#### `plugins(*names, **kwargs)` method
|
|
428
|
+
|
|
429
|
+
Defines the plugins to be included in the editor. You can specify multiple plugins by passing their names as arguments. The keyword arguments are identical to the configuration of the `plugin` method defined below.
|
|
430
|
+
|
|
431
|
+
```rb
|
|
432
|
+
# config/initializers/ckeditor5.rb
|
|
433
|
+
|
|
434
|
+
config.presets.define :custom do
|
|
435
|
+
# ... other configuration
|
|
436
|
+
|
|
437
|
+
plugins :Bold, :Italic, :Underline, :Link
|
|
438
|
+
end
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
#### `inline_plugin(name, code)` method
|
|
442
|
+
|
|
443
|
+
Use with caution as this is an inline definition of the plugin code, and you can define a custom class or function for the plugin here. The example below shows how to define a custom plugin that highlights the text:
|
|
444
|
+
|
|
445
|
+
```rb
|
|
446
|
+
# config/initializers/ckeditor5.rb
|
|
447
|
+
|
|
448
|
+
config.presets.define :custom do
|
|
449
|
+
# ... other configuration
|
|
450
|
+
|
|
451
|
+
inline_plugin :MyCustomPlugin, <<~JS
|
|
452
|
+
import { Plugin } from 'ckeditor5';
|
|
453
|
+
|
|
454
|
+
export default class MyCustomPlugin extends Plugin {
|
|
455
|
+
static get pluginName() {
|
|
456
|
+
return 'MyCustomPlugin';
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
init() {
|
|
460
|
+
// ... Your plugin code
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
JS
|
|
464
|
+
end
|
|
465
|
+
```
|
|
350
466
|
</details>
|
|
351
467
|
|
|
352
468
|
## Including CKEditor 5 assets 📦
|
|
@@ -368,7 +484,7 @@ It has been achieved by using web components, together with import maps, which a
|
|
|
368
484
|
|
|
369
485
|
### GPL usage 🆓
|
|
370
486
|
|
|
371
|
-
If you want to use CKEditor 5 under the GPL license, you can include the assets using the `ckeditor5_assets`
|
|
487
|
+
If you want to use CKEditor 5 under the GPL license, you can include the assets using the `ckeditor5_assets` without passing any arguments. However you can pass the `version` keyword argument with the version of CKEditor 5 you want to use:
|
|
372
488
|
|
|
373
489
|
```erb
|
|
374
490
|
<!-- app/views/demos/index.html.erb -->
|
|
@@ -607,57 +723,301 @@ If you want to use a decoupled editor, you can pass the `type` keyword argument
|
|
|
607
723
|
<%= ckeditor5_assets %>
|
|
608
724
|
<% end %>
|
|
609
725
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
position: relative;
|
|
615
|
-
border: 1px solid red;
|
|
616
|
-
}
|
|
726
|
+
<%= ckeditor5_editor type: :decoupled, style: 'width: 600px' do %>
|
|
727
|
+
<div class="menubar-container">
|
|
728
|
+
<%= ckeditor5_menubar %>
|
|
729
|
+
</div>
|
|
617
730
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
731
|
+
<div class="toolbar-container">
|
|
732
|
+
<%= ckeditor5_toolbar %>
|
|
733
|
+
</div>
|
|
734
|
+
|
|
735
|
+
<div class="editable-container">
|
|
736
|
+
<%= ckeditor5_editable %>
|
|
737
|
+
</div>
|
|
738
|
+
<% end %>
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
## How to access editor instance? 🤔
|
|
742
|
+
|
|
743
|
+
You can access the editor instance using plain HTML and JavaScript, as CKEditor 5 is a web component with defined `instance`, `instancePromise` and `editables` properties.
|
|
744
|
+
|
|
745
|
+
For example:
|
|
746
|
+
|
|
747
|
+
```erb
|
|
748
|
+
<!-- app/views/demos/index.html.erb -->
|
|
749
|
+
|
|
750
|
+
<% content_for :head do %>
|
|
751
|
+
<%= ckeditor5_assets %>
|
|
752
|
+
<% end %>
|
|
753
|
+
|
|
754
|
+
<%= ckeditor5_editor style: 'width: 600px', id: 'editor' %>
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
⚠️ Direct access of `instance` property of the web component. Keep in mind it's unsafe and may cause issues if the editor is not loaded yet.
|
|
758
|
+
|
|
759
|
+
```js
|
|
760
|
+
document.getElementById('editor').instance
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
👌 Accessing the editor instance using `instancePromise` property. It's a promise that resolves to the editor instance when the editor is ready.
|
|
764
|
+
|
|
765
|
+
```js
|
|
766
|
+
document.getElementById('editor').instancePromise.then(editor => {
|
|
767
|
+
console.log(editor);
|
|
768
|
+
});
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
✅ Accessing the editor through the `runAfterEditorReady` helper method. It's a safe way to access the editor instance when the editor is ready.
|
|
772
|
+
|
|
773
|
+
```js
|
|
774
|
+
document.getElementById('editor').runAfterEditorReady(editor => {
|
|
775
|
+
console.log(editor);
|
|
776
|
+
});
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
## Events fired by the editor 🔊
|
|
780
|
+
|
|
781
|
+
### `editor-ready` event
|
|
782
|
+
|
|
783
|
+
The event is fired when the initialization of the editor is completed. You can listen to it using the `editor-ready` event.
|
|
784
|
+
|
|
785
|
+
```js
|
|
786
|
+
document.getElementById('editor').addEventListener('editor-ready', () => {
|
|
787
|
+
console.log('Editor is ready');
|
|
788
|
+
});
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### `editor-error` event
|
|
792
|
+
|
|
793
|
+
The event is fired when the initialization of the editor fails. You can listen to it using the `editor-error` event.
|
|
794
|
+
|
|
795
|
+
```js
|
|
796
|
+
document.getElementById('editor').addEventListener('editor-error', () => {
|
|
797
|
+
console.log('Editor has an error');
|
|
798
|
+
});
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
## Common Tasks and Solutions 💡
|
|
802
|
+
|
|
803
|
+
This section covers frequent questions and scenarios when working with CKEditor 5 in Rails applications.
|
|
804
|
+
|
|
805
|
+
### Setting Initial Content 📝
|
|
806
|
+
|
|
807
|
+
```erb
|
|
808
|
+
<%= ckeditor5_editor initial_data: "<p>Initial content</p>" %>
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
### Setting Editor Language 🌐
|
|
812
|
+
|
|
813
|
+
```rb
|
|
814
|
+
config.presets.override :default do
|
|
815
|
+
translations :pl, :es
|
|
816
|
+
language :pl
|
|
817
|
+
end
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
### Integrating with Forms 📋
|
|
821
|
+
|
|
822
|
+
#### Rails form builder integration
|
|
823
|
+
|
|
824
|
+
```erb
|
|
825
|
+
<%= form_for @post do |f| %>
|
|
826
|
+
<%= f.label :content %>
|
|
827
|
+
<%= f.ckeditor5 :content, required: true, style: 'width: 700px', initial_data: 'Hello World!' %>
|
|
828
|
+
<% end %>
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
#### Simple form integration
|
|
832
|
+
|
|
833
|
+
```erb
|
|
834
|
+
<%= simple_form_for :demo, url: '/demos', html: { novalidate: false } do |f| %>
|
|
835
|
+
<div class="form-group">
|
|
836
|
+
<%= f.input :content, as: :ckeditor5, initial_data: 'Hello, World 12!', input_html: { style: 'width: 600px' }, required: true %>
|
|
837
|
+
</div>
|
|
838
|
+
|
|
839
|
+
<div class="form-group mt-3">
|
|
840
|
+
<%= f.button :submit, 'Save', class: 'btn btn-primary' %>
|
|
841
|
+
</div>
|
|
842
|
+
<% end %>
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
### Custom Styling 🎨
|
|
846
|
+
|
|
847
|
+
```erb
|
|
848
|
+
<%= ckeditor5_editor style: 'height: 400px; margin: 20px;' %>
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
### Custom plugins 🧩
|
|
852
|
+
|
|
853
|
+
You can create custom plugins for CKEditor 5 using the `inline_plugin` method. It allows you to define a custom class or function inside your preset configuration.
|
|
854
|
+
|
|
855
|
+
The example below shows how to define a custom plugin that allows toggling the highlight of the selected text:
|
|
856
|
+
|
|
857
|
+

|
|
858
|
+
|
|
859
|
+
```rb
|
|
860
|
+
# config/initializers/ckeditor5.rb
|
|
861
|
+
|
|
862
|
+
config.presets.define :custom do
|
|
863
|
+
# ... other configuration
|
|
630
864
|
|
|
631
|
-
.
|
|
632
|
-
|
|
633
|
-
|
|
865
|
+
# 1. You can define it inline like below or in a separate file.
|
|
866
|
+
|
|
867
|
+
# In case if plugin is located in external file (recommended), you can simply import it:
|
|
868
|
+
|
|
869
|
+
# inline_plugin :MyCustomPlugin, <<~JS
|
|
870
|
+
# import MyPlugin from 'app/javascript/custom_plugins/highlight.js';
|
|
871
|
+
# export default MyPlugin;
|
|
872
|
+
# JS
|
|
873
|
+
|
|
874
|
+
# 2. You can also use "window_name" option to import plugin from window object:
|
|
875
|
+
|
|
876
|
+
# plugin :MyPlugin, window_name: 'MyPlugin'
|
|
877
|
+
|
|
878
|
+
# 3. Create JavaScript file in app/javascript/custom_plugins/highlight.js:
|
|
879
|
+
# You can also use "plugin" to import plugin from file using 'import_name' option.
|
|
880
|
+
# Your `my-custom-plugin` must be present in import map.
|
|
881
|
+
|
|
882
|
+
# plugin :MyCustomPlugin, import_name: 'my-custom-plugin'
|
|
883
|
+
|
|
884
|
+
# 4 Create JavaScript file in app/javascript/custom_plugins/highlight.js:
|
|
885
|
+
|
|
886
|
+
# In Ruby initializer you can also load plugin code directly from file:
|
|
887
|
+
plugin :MyCustomPlugin, File.read(
|
|
888
|
+
Rails.root.join('app/javascript/custom_plugins/highlight.js')
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
# 5. Or even define it inline:
|
|
892
|
+
# plugin :MyCustomPlugin, <<~JS
|
|
893
|
+
# import { Plugin } from 'ckeditor5';
|
|
894
|
+
#
|
|
895
|
+
# export default class MyCustomPlugin extends Plugin {
|
|
896
|
+
# // ...
|
|
897
|
+
# }
|
|
898
|
+
# JS
|
|
899
|
+
|
|
900
|
+
# Add item to beginning of the toolbar.
|
|
901
|
+
toolbar do
|
|
902
|
+
prepend :highlight
|
|
903
|
+
end
|
|
904
|
+
end
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
<details>
|
|
908
|
+
<summary>Example of Custom Highlight Plugin 🎨</summary>
|
|
909
|
+
|
|
910
|
+
```js
|
|
911
|
+
// app/javascript/custom_plugins/highlight.js
|
|
912
|
+
import { Plugin, Command, ButtonView } from 'ckeditor5';
|
|
913
|
+
|
|
914
|
+
export default class MyCustomPlugin extends Plugin {
|
|
915
|
+
static get pluginName() {
|
|
916
|
+
return 'MyCustomPlugin';
|
|
634
917
|
}
|
|
635
918
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
919
|
+
init() {
|
|
920
|
+
const editor = this.editor;
|
|
921
|
+
|
|
922
|
+
// Define schema for highlight attribute
|
|
923
|
+
editor.model.schema.extend('$text', { allowAttributes: 'highlight' });
|
|
924
|
+
|
|
925
|
+
// Define conversion between model and view
|
|
926
|
+
editor.conversion.attributeToElement({
|
|
927
|
+
model: 'highlight',
|
|
928
|
+
view: {
|
|
929
|
+
name: 'span',
|
|
930
|
+
styles: {
|
|
931
|
+
'background-color': 'yellow'
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
// Create command that handles highlighting logic
|
|
937
|
+
// Command pattern is used to encapsulate all the logic related to executing an action
|
|
938
|
+
const command = new HighlightCommand(editor);
|
|
939
|
+
|
|
940
|
+
// Register command in editor
|
|
941
|
+
editor.commands.add('highlight', command);
|
|
942
|
+
|
|
943
|
+
// Add UI button
|
|
944
|
+
editor.ui.componentFactory.add('highlight', locale => {
|
|
945
|
+
const view = new ButtonView(locale);
|
|
946
|
+
|
|
947
|
+
// Bind button state to command state using bind method
|
|
948
|
+
// bind() allows to sync button state with command state automatically
|
|
949
|
+
view.bind('isOn').to(command, 'value');
|
|
950
|
+
|
|
951
|
+
view.set({
|
|
952
|
+
label: 'Highlight',
|
|
953
|
+
withText: true,
|
|
954
|
+
tooltip: true
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
view.on('execute', () => {
|
|
958
|
+
editor.execute('highlight');
|
|
959
|
+
editor.editing.view.focus();
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
return view;
|
|
963
|
+
});
|
|
640
964
|
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// Command class that handles the highlight feature
|
|
968
|
+
// isEnabled property determines if command can be executed
|
|
969
|
+
class HighlightCommand extends Command {
|
|
970
|
+
execute() {
|
|
971
|
+
const model = this.editor.model;
|
|
972
|
+
const selection = model.document.selection;
|
|
973
|
+
|
|
974
|
+
model.change(writer => {
|
|
975
|
+
const ranges = model.schema.getValidRanges(selection.getRanges(), 'highlight');
|
|
976
|
+
|
|
977
|
+
for (const range of ranges) {
|
|
978
|
+
if (this.value) {
|
|
979
|
+
writer.removeAttribute('highlight', range);
|
|
980
|
+
} else {
|
|
981
|
+
writer.setAttribute('highlight', true, range);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
refresh() {
|
|
988
|
+
const model = this.editor.model;
|
|
989
|
+
const selection = model.document.selection;
|
|
990
|
+
const isAllowed = model.schema.checkAttributeInSelection(selection, 'highlight');
|
|
641
991
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
border: 1px #D3D3D3 solid;
|
|
646
|
-
border-radius: var(--ck-border-radius);
|
|
647
|
-
background: white;
|
|
648
|
-
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
|
992
|
+
// Set if command is enabled based on schema
|
|
993
|
+
this.isEnabled = isAllowed;
|
|
994
|
+
this.value = this.#isHighlightedNodeSelected();
|
|
649
995
|
}
|
|
650
|
-
</style>
|
|
651
996
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
997
|
+
// Check if the highlighted node is selected.
|
|
998
|
+
#isHighlightedNodeSelected() {
|
|
999
|
+
const { model } = this.editor
|
|
1000
|
+
const { schema } = model
|
|
1001
|
+
const selection = model.document.selection
|
|
1002
|
+
|
|
1003
|
+
if (selection.isCollapsed) {
|
|
1004
|
+
return selection.hasAttribute('highlight')
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
return selection.getRanges().some(range =>
|
|
1008
|
+
Array
|
|
1009
|
+
.from(range.getItems())
|
|
1010
|
+
.some(item =>
|
|
1011
|
+
schema.checkAttribute(item, 'highlight') &&
|
|
1012
|
+
item.hasAttribute('highlight')
|
|
1013
|
+
)
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
659
1017
|
```
|
|
660
1018
|
|
|
1019
|
+
</details>
|
|
1020
|
+
|
|
661
1021
|
## License 📜
|
|
662
1022
|
|
|
663
1023
|
The MIT License (MIT)
|
|
@@ -41,7 +41,7 @@ module CKEditor5::Rails::Assets
|
|
|
41
41
|
class JSExportsMeta
|
|
42
42
|
attr_reader :url, :import_meta
|
|
43
43
|
|
|
44
|
-
delegate :esm?, :window?, :import_name, :window_name, :import_as, to: :import_meta
|
|
44
|
+
delegate :esm?, :window?, :import_name, :window_name, :import_as, :to_h, to: :import_meta
|
|
45
45
|
|
|
46
46
|
def initialize(url, translation: false, **import_options)
|
|
47
47
|
@url = url
|
|
@@ -63,13 +63,14 @@ module CKEditor5::Rails::Assets
|
|
|
63
63
|
|
|
64
64
|
def styles_tags
|
|
65
65
|
@styles_tags ||= safe_join(bundle.stylesheets.map do |url|
|
|
66
|
-
tag.link(href: url, rel: 'stylesheet')
|
|
66
|
+
tag.link(href: url, rel: 'stylesheet', crossorigin: 'anonymous')
|
|
67
67
|
end)
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def preload_tags
|
|
71
71
|
@preload_tags ||= safe_join(bundle.preloads.map do |url|
|
|
72
|
-
tag.link(href: url, rel: 'preload', as: self.class.url_resource_preload_type(url)
|
|
72
|
+
tag.link(href: url, rel: 'preload', as: self.class.url_resource_preload_type(url),
|
|
73
|
+
crossorigin: 'anonymous')
|
|
73
74
|
end)
|
|
74
75
|
end
|
|
75
76
|
end
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
*/
|
|
24
24
|
class CKEditorComponent extends HTMLElement {
|
|
25
25
|
/**
|
|
26
|
-
* List of attributes that trigger updates when changed
|
|
26
|
+
* List of attributes that trigger updates when changed.
|
|
27
|
+
*
|
|
27
28
|
* @static
|
|
28
29
|
* @returns {string[]} Array of attribute names to observe
|
|
29
30
|
*/
|
|
@@ -31,6 +32,16 @@ class CKEditorComponent extends HTMLElement {
|
|
|
31
32
|
return ['config', 'plugins', 'translations', 'type'];
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* List of input attributes that trigger updates when changed.
|
|
37
|
+
*
|
|
38
|
+
* @static
|
|
39
|
+
* @returns {string[]} Array of input attribute names to observe
|
|
40
|
+
*/
|
|
41
|
+
static get inputAttributes() {
|
|
42
|
+
return ['name', 'required', 'value'];
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
/** @type {Promise<import('ckeditor5').Editor>|null} Promise to initialize editor instance */
|
|
35
46
|
instancePromise = Promise.withResolvers();
|
|
36
47
|
|
|
@@ -134,16 +145,17 @@ class CKEditorComponent extends HTMLElement {
|
|
|
134
145
|
content = editablesOrContent.main;
|
|
135
146
|
}
|
|
136
147
|
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
{
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
const config = {
|
|
149
|
+
...this.#getConfig(),
|
|
150
|
+
...translations.length && {
|
|
151
|
+
translations
|
|
152
|
+
},
|
|
153
|
+
plugins,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
console.warn('Initializing CKEditor with config:', config);
|
|
157
|
+
|
|
158
|
+
const instance = await Editor.create(content, config);
|
|
147
159
|
|
|
148
160
|
this.dispatchEvent(new CustomEvent('editor-ready', { detail: instance }));
|
|
149
161
|
|
|
@@ -168,6 +180,7 @@ class CKEditorComponent extends HTMLElement {
|
|
|
168
180
|
|
|
169
181
|
if (!this.isMultiroot() && !this.isDecoupled()) {
|
|
170
182
|
this.innerHTML = `<${this.#editorElementTag}></${this.#editorElementTag}>`;
|
|
183
|
+
this.#assignInputAttributes();
|
|
171
184
|
}
|
|
172
185
|
|
|
173
186
|
// Let's track changes in editables if it's a multiroot editor.
|
|
@@ -181,6 +194,8 @@ class CKEditorComponent extends HTMLElement {
|
|
|
181
194
|
|
|
182
195
|
try {
|
|
183
196
|
this.instance = await this.#initializeEditor(this.editables || this.#getConfig().initialData || '');
|
|
197
|
+
this.#setupContentSync();
|
|
198
|
+
|
|
184
199
|
this.instancePromise.resolve(this.instance);
|
|
185
200
|
} catch (err) {
|
|
186
201
|
this.instancePromise.reject(err);
|
|
@@ -251,6 +266,74 @@ class CKEditorComponent extends HTMLElement {
|
|
|
251
266
|
return { main: mainEditable };
|
|
252
267
|
}
|
|
253
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Copies input-related attributes from component to the main editable element
|
|
271
|
+
*
|
|
272
|
+
* @private
|
|
273
|
+
*/
|
|
274
|
+
#assignInputAttributes() {
|
|
275
|
+
const textarea = this.querySelector('textarea');
|
|
276
|
+
|
|
277
|
+
if (!textarea) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
for (const attr of CKEditorComponent.inputAttributes) {
|
|
282
|
+
if (this.hasAttribute(attr)) {
|
|
283
|
+
textarea.setAttribute(attr, this.getAttribute(attr));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Sets up content sync between editor and textarea element.
|
|
290
|
+
*
|
|
291
|
+
* @private
|
|
292
|
+
*/
|
|
293
|
+
#setupContentSync() {
|
|
294
|
+
if (!this.instance) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const textarea = this.querySelector('textarea');
|
|
299
|
+
|
|
300
|
+
if (!textarea) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Initial sync
|
|
305
|
+
const syncInput = () => {
|
|
306
|
+
this.style.position = 'relative';
|
|
307
|
+
|
|
308
|
+
textarea.value = this.instance.getData();
|
|
309
|
+
textarea.tabIndex = -1;
|
|
310
|
+
|
|
311
|
+
Object.assign(textarea.style, {
|
|
312
|
+
display: 'flex',
|
|
313
|
+
position: 'absolute',
|
|
314
|
+
bottom: '0',
|
|
315
|
+
left: '50%',
|
|
316
|
+
width: '1px',
|
|
317
|
+
height: '1px',
|
|
318
|
+
opacity: '0',
|
|
319
|
+
pointerEvents: 'none',
|
|
320
|
+
margin: '0',
|
|
321
|
+
padding: '0',
|
|
322
|
+
border: 'none'
|
|
323
|
+
});
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
syncInput();
|
|
327
|
+
|
|
328
|
+
// Listen for changes
|
|
329
|
+
this.instance.model.document.on('change:data', () => {
|
|
330
|
+
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
331
|
+
textarea.dispatchEvent(new Event('change', { bubbles: true }));
|
|
332
|
+
|
|
333
|
+
syncInput();
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
254
337
|
/**
|
|
255
338
|
* Loads translation modules
|
|
256
339
|
*
|
|
@@ -624,16 +707,48 @@ function execIfDOMReady(callback) {
|
|
|
624
707
|
* @returns {Promise<Array<any>>} Loaded modules
|
|
625
708
|
*/
|
|
626
709
|
function loadAsyncImports(imports = []) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
710
|
+
const loadInlinePlugin = async ({ name, code }) => {
|
|
711
|
+
const module = await import(`data:text/javascript,${encodeURIComponent(code)}`);
|
|
712
|
+
|
|
713
|
+
if (!module.default) {
|
|
714
|
+
throw new Error(`Inline plugin "${name}" must export a default class/function!`);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return module.default;
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
const loadExternalPlugin = async ({ import_name, import_as, window_name }) => {
|
|
721
|
+
if (window_name) {
|
|
722
|
+
if (!Object.prototype.hasOwnProperty.call(window, window_name)) {
|
|
723
|
+
throw new Error(
|
|
724
|
+
`Plugin window['${window_name}'] not found in global scope. ` +
|
|
725
|
+
'Please ensure the plugin is loaded before CKEditor initialization.'
|
|
726
|
+
);
|
|
631
727
|
}
|
|
632
728
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
729
|
+
return window[window_name];
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const module = await import(import_name);
|
|
733
|
+
const imported = module[import_as || 'default'];
|
|
734
|
+
|
|
735
|
+
if (!imported) {
|
|
736
|
+
throw new Error(`Plugin "${import_as}" not found in the ESM module "${import_name}"!`);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
return imported;
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
return Promise.all(imports.map(item => {
|
|
743
|
+
switch(item.type) {
|
|
744
|
+
case 'inline':
|
|
745
|
+
return loadInlinePlugin(item);
|
|
746
|
+
|
|
747
|
+
case 'external':
|
|
748
|
+
default:
|
|
749
|
+
return loadExternalPlugin(item);
|
|
750
|
+
}
|
|
751
|
+
}));
|
|
637
752
|
}
|
|
638
753
|
|
|
639
754
|
customElements.define('ckeditor-component', CKEditorComponent);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'props_plugin'
|
|
4
|
+
require_relative 'props_inline_plugin'
|
|
4
5
|
require_relative 'props'
|
|
5
6
|
|
|
6
7
|
module CKEditor5::Rails
|
|
@@ -11,6 +12,7 @@ module CKEditor5::Rails
|
|
|
11
12
|
def ckeditor5_editor(
|
|
12
13
|
config: nil, extra_config: {},
|
|
13
14
|
type: nil, preset: :default,
|
|
15
|
+
initial_data: nil,
|
|
14
16
|
**html_attributes, &block
|
|
15
17
|
)
|
|
16
18
|
context = validate_and_get_editor_context!
|
|
@@ -19,8 +21,11 @@ module CKEditor5::Rails
|
|
|
19
21
|
config ||= preset.config
|
|
20
22
|
type ||= preset.type
|
|
21
23
|
|
|
24
|
+
config = config.deep_merge(extra_config)
|
|
25
|
+
config[:initialData] = initial_data if initial_data
|
|
26
|
+
|
|
22
27
|
editor_props = build_editor_props(
|
|
23
|
-
config: config
|
|
28
|
+
config: config,
|
|
24
29
|
type: type,
|
|
25
30
|
context: context
|
|
26
31
|
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CKEditor5::Rails::Editor
|
|
4
|
+
class PropsInlinePlugin
|
|
5
|
+
def initialize(name, code)
|
|
6
|
+
@name = name
|
|
7
|
+
@code = code
|
|
8
|
+
validate_code!
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def to_h
|
|
12
|
+
{
|
|
13
|
+
type: :inline,
|
|
14
|
+
name: name,
|
|
15
|
+
code: code
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
attr_reader :name, :code
|
|
22
|
+
|
|
23
|
+
def validate_code!
|
|
24
|
+
raise ArgumentError, 'Code must be a String' unless code.is_a?(String)
|
|
25
|
+
|
|
26
|
+
return if code.include?('export default')
|
|
27
|
+
|
|
28
|
+
raise ArgumentError,
|
|
29
|
+
'Code must include `export default` that exports plugin definition!'
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -4,30 +4,35 @@ module CKEditor5::Rails::Editor
|
|
|
4
4
|
class PropsPlugin
|
|
5
5
|
delegate :to_h, to: :import_meta
|
|
6
6
|
|
|
7
|
-
def initialize(name, premium: false,
|
|
7
|
+
def initialize(name, premium: false, **js_import_meta)
|
|
8
8
|
@name = name
|
|
9
|
-
@
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
@js_import_meta = if js_import_meta.empty?
|
|
10
|
+
{ import_name: premium ? 'ckeditor5-premium-features' : 'ckeditor5' }
|
|
11
|
+
else
|
|
12
|
+
js_import_meta
|
|
13
|
+
end
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def self.normalize(plugin)
|
|
15
17
|
case plugin
|
|
16
18
|
when String, Symbol then new(plugin)
|
|
17
|
-
when PropsPlugin then plugin
|
|
19
|
+
when PropsPlugin, PropsInlinePlugin then plugin
|
|
18
20
|
else raise ArgumentError, "Invalid plugin: #{plugin}"
|
|
19
21
|
end
|
|
20
22
|
end
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
def to_h
|
|
25
|
+
meta = ::CKEditor5::Rails::Assets::JSImportMeta.new(
|
|
26
|
+
import_as: js_import_meta[:window_name] ? nil : name,
|
|
27
|
+
**js_import_meta
|
|
28
|
+
).to_h
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
import_as: name,
|
|
29
|
-
import_name: import_name
|
|
30
|
-
)
|
|
30
|
+
meta.merge!({ type: :external })
|
|
31
|
+
meta
|
|
31
32
|
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
attr_reader :name, :js_import_meta
|
|
32
37
|
end
|
|
33
38
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'rails/engine'
|
|
4
4
|
require_relative 'presets'
|
|
5
|
+
require_relative 'hooks/form'
|
|
5
6
|
|
|
6
7
|
module CKEditor5::Rails
|
|
7
8
|
class Engine < ::Rails::Engine
|
|
@@ -16,6 +17,22 @@ module CKEditor5::Rails
|
|
|
16
17
|
end
|
|
17
18
|
end
|
|
18
19
|
|
|
20
|
+
initializer 'ckeditor5.simple_form' do
|
|
21
|
+
next unless defined?(::SimpleForm)
|
|
22
|
+
|
|
23
|
+
require_relative 'hooks/simple_form'
|
|
24
|
+
|
|
25
|
+
::SimpleForm::FormBuilder.map_type :ckeditor5, to: Hooks::SimpleForm::CKEditor5Input
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
initializer 'ckeditor5.form_builder' do
|
|
29
|
+
require_relative 'hooks/form'
|
|
30
|
+
|
|
31
|
+
ActionView::Helpers::FormBuilder.include(
|
|
32
|
+
Hooks::Form::FormBuilderExtension
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
19
36
|
def self.base
|
|
20
37
|
config.ckeditor5
|
|
21
38
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CKEditor5::Rails::Hooks
|
|
4
|
+
module Form
|
|
5
|
+
module FormBuilderExtension
|
|
6
|
+
def ckeditor5(method, options = {})
|
|
7
|
+
value = if object.respond_to?(method)
|
|
8
|
+
object.send(method)
|
|
9
|
+
else
|
|
10
|
+
options[:initial_data]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
html_options = options.merge(
|
|
14
|
+
name: object_name,
|
|
15
|
+
required: options.delete(:required),
|
|
16
|
+
initial_data: value
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
@template.ckeditor5_editor(**html_options)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CKEditor5::Rails::Hooks
|
|
4
|
+
module SimpleForm
|
|
5
|
+
class CKEditor5Input < ::SimpleForm::Inputs::Base
|
|
6
|
+
def input(wrapper_options = nil)
|
|
7
|
+
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
|
|
8
|
+
@builder.template.ckeditor5_editor(**editor_options(merged_input_options))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def editor_options(merged_input_options)
|
|
14
|
+
{
|
|
15
|
+
preset: input_options.fetch(:preset, :default),
|
|
16
|
+
type: input_options.fetch(:type, :classic),
|
|
17
|
+
config: input_options[:config],
|
|
18
|
+
initial_data: object.try(attribute_name) || input_options[:initial_data],
|
|
19
|
+
name: "#{object_name}[#{attribute_name}]",
|
|
20
|
+
**merged_input_options
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -29,7 +29,7 @@ module CKEditor5::Rails
|
|
|
29
29
|
|
|
30
30
|
private
|
|
31
31
|
|
|
32
|
-
def define_default_preset
|
|
32
|
+
def define_default_preset # rubocop:disable Metrics/MethodLength
|
|
33
33
|
define :default do
|
|
34
34
|
gpl
|
|
35
35
|
|
|
@@ -38,11 +38,11 @@ module CKEditor5::Rails
|
|
|
38
38
|
menubar
|
|
39
39
|
|
|
40
40
|
toolbar :undo, :redo, :|, :heading, :|, :bold, :italic, :underline, :|,
|
|
41
|
-
:link, :insertImage, :
|
|
41
|
+
:link, :insertImage, :mediaEmbed, :insertTable, :blockQuote, :|,
|
|
42
42
|
:bulletedList, :numberedList, :todoList, :outdent, :indent
|
|
43
43
|
|
|
44
44
|
plugins :AccessibilityHelp, :Autoformat, :AutoImage, :Autosave,
|
|
45
|
-
:BlockQuote, :Bold, :
|
|
45
|
+
:BlockQuote, :Bold, :CloudServices,
|
|
46
46
|
:Essentials, :Heading, :ImageBlock, :ImageCaption, :ImageInline,
|
|
47
47
|
:ImageInsert, :ImageInsertViaUrl, :ImageResize, :ImageStyle,
|
|
48
48
|
:ImageTextAlternative, :ImageToolbar, :ImageUpload, :Indent,
|
|
@@ -51,6 +51,10 @@ module CKEditor5::Rails
|
|
|
51
51
|
:SelectAll, :Table, :TableCaption, :TableCellProperties,
|
|
52
52
|
:TableColumnResize, :TableProperties, :TableToolbar,
|
|
53
53
|
:TextTransformation, :TodoList, :Underline, :Undo, :Base64UploadAdapter
|
|
54
|
+
|
|
55
|
+
configure :image, {
|
|
56
|
+
toolbar: ['imageTextAlternative', 'imageStyle:inline', 'imageStyle:block', 'imageStyle:side']
|
|
57
|
+
}
|
|
54
58
|
end
|
|
55
59
|
end
|
|
56
60
|
end
|
|
@@ -144,11 +148,22 @@ module CKEditor5::Rails
|
|
|
144
148
|
}
|
|
145
149
|
end
|
|
146
150
|
|
|
147
|
-
def toolbar(*items, should_group_when_full: true)
|
|
148
|
-
@config[:toolbar]
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
def toolbar(*items, should_group_when_full: true, &block)
|
|
152
|
+
if @config[:toolbar].blank? || !items.empty?
|
|
153
|
+
@config[:toolbar] = {
|
|
154
|
+
items: items,
|
|
155
|
+
shouldNotGroupWhenFull: !should_group_when_full
|
|
156
|
+
}
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
return unless block
|
|
160
|
+
|
|
161
|
+
builder = ToolbarBuilder.new(@config[:toolbar])
|
|
162
|
+
builder.instance_eval(&block)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def inline_plugin(name, code)
|
|
166
|
+
@config[:plugins] << Editor::PropsInlinePlugin.new(name, code)
|
|
152
167
|
end
|
|
153
168
|
|
|
154
169
|
def plugin(name, **kwargs)
|
|
@@ -166,4 +181,40 @@ module CKEditor5::Rails
|
|
|
166
181
|
}
|
|
167
182
|
end
|
|
168
183
|
end
|
|
184
|
+
|
|
185
|
+
class ToolbarBuilder
|
|
186
|
+
def initialize(toolbar_config)
|
|
187
|
+
@toolbar_config = toolbar_config
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def items
|
|
191
|
+
@toolbar_config[:items]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def remove(*removed_items)
|
|
195
|
+
removed_items.each { |item| items.delete(item) }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def prepend(*prepended_items, before: nil)
|
|
199
|
+
if before
|
|
200
|
+
index = items.index(before)
|
|
201
|
+
raise ArgumentError, "Item '#{before}' not found in toolbar" unless index
|
|
202
|
+
|
|
203
|
+
items.insert(index, *prepended_items)
|
|
204
|
+
else
|
|
205
|
+
items.insert(0, *prepended_items)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def append(*appended_items, after: nil)
|
|
210
|
+
if after
|
|
211
|
+
index = items.index(after)
|
|
212
|
+
raise ArgumentError, "Item '#{after}' not found in toolbar" unless index
|
|
213
|
+
|
|
214
|
+
items.insert(index + 1, *appended_items)
|
|
215
|
+
else
|
|
216
|
+
items.push(*appended_items)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
169
220
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ckeditor5
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mateusz Bagiński
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2024-
|
|
12
|
+
date: 2024-11-01 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: rails
|
|
@@ -53,9 +53,12 @@ files:
|
|
|
53
53
|
- lib/ckeditor5/rails/cloud/helpers.rb
|
|
54
54
|
- lib/ckeditor5/rails/editor/helpers.rb
|
|
55
55
|
- lib/ckeditor5/rails/editor/props.rb
|
|
56
|
+
- lib/ckeditor5/rails/editor/props_inline_plugin.rb
|
|
56
57
|
- lib/ckeditor5/rails/editor/props_plugin.rb
|
|
57
58
|
- lib/ckeditor5/rails/engine.rb
|
|
58
59
|
- lib/ckeditor5/rails/helpers.rb
|
|
60
|
+
- lib/ckeditor5/rails/hooks/form.rb
|
|
61
|
+
- lib/ckeditor5/rails/hooks/simple_form.rb
|
|
59
62
|
- lib/ckeditor5/rails/presets.rb
|
|
60
63
|
- lib/ckeditor5/rails/semver.rb
|
|
61
64
|
- lib/ckeditor5/rails/version.rb
|