ckeditor5 1.0.5 → 1.1.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.
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
- Usage in your Rails application:
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 version: '43.2.0', translations: [:pl, :es] %>
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
- Effect:
32
-
33
- ![CKEditor 5 Classic Editor in Ruby on Rails application](docs/intro-classic-editor.png)
49
+ Voilà! You have CKEditor 5 integrated with your Rails application. 🎉
34
50
 
35
51
  ## Table of Contents 📚
36
52
 
@@ -39,13 +55,20 @@ Effect:
39
55
  - [Table of Contents 📚](#table-of-contents-)
40
56
  - [Presets 🎨](#presets-)
41
57
  - [Available Configuration Methods ⚙️](#available-configuration-methods-️)
42
- - [`shape(type)` method](#shapetype-method)
43
- - [`plugins(*names)` method](#pluginsnames-method)
44
- - [`toolbar(*items, should_group_when_full: true)` method](#toolbaritems-should_group_when_full-true-method)
58
+ - [`version(version)` method](#versionversion-method)
59
+ - [`gpl` method](#gpl-method)
60
+ - [`license_key(key)` method](#license_keykey-method)
61
+ - [`premium` method](#premium-method)
62
+ - [`translations(*languages)` method](#translationslanguages-method)
63
+ - [`ckbox` method](#ckbox-method)
64
+ - [`type(type)` method](#typetype-method)
65
+ - [`toolbar(*items, should_group_when_full: true, &block)` method](#toolbaritems-should_group_when_full-true-block-method)
45
66
  - [`menubar(visible: true)` method](#menubarvisible-true-method)
46
67
  - [`language(ui, content:)` method](#languageui-content-method)
47
68
  - [`configure(name, value)` method](#configurename-value-method)
48
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)
49
72
  - [Including CKEditor 5 assets 📦](#including-ckeditor-5-assets-)
50
73
  - [Lazy loading 🚀](#lazy-loading-)
51
74
  - [GPL usage 🆓](#gpl-usage-)
@@ -56,31 +79,41 @@ Effect:
56
79
  - [Inline editor 📝](#inline-editor-)
57
80
  - [Balloon editor 🎈](#balloon-editor-)
58
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-)
59
94
  - [License 📜](#license-)
60
95
 
61
96
  ## Presets 🎨
62
97
 
63
98
  Presets are predefined configurations of CKEditor 5, allowing quick setup with specific features. The gem includes a `:default` preset with common features like bold, italic, underline, and link for the classic editor.
64
99
 
65
- You can override the default preset or create your own by defining a new preset in the `config/initializers/ckeditor5.rb` file using the `config.presets.define` method.
66
-
67
- The example below shows how to define a custom preset with a classic editor and a custom toolbar:
100
+ You can create your own by defining it in the `config/initializers/ckeditor5.rb` file using the `config.presets.define` method. The example below illustrates the setup of a custom preset with a classic editor and a custom toolbar:
68
101
 
69
102
  ```rb
70
103
  # config/initializers/ckeditor5.rb
71
104
 
72
105
  CKEditor5::Rails::Engine.configure do |config|
73
106
  config.presets.define :custom
74
- shape :classic
107
+ gpl
108
+ type :classic
75
109
 
76
110
  menubar
77
-
78
111
  toolbar :undo, :redo, :|, :heading, :|, :bold, :italic, :underline, :|,
79
- :link, :insertImage, :ckbox, :mediaEmbed, :insertTable, :blockQuote, :|,
112
+ :link, :insertImage, :mediaEmbed, :insertTable, :blockQuote, :|,
80
113
  :bulletedList, :numberedList, :todoList, :outdent, :indent
81
114
 
82
115
  plugins :AccessibilityHelp, :Autoformat, :AutoImage, :Autosave,
83
- :BlockQuote, :Bold, :CKBox, :CKBoxImageEdit, :CloudServices,
116
+ :BlockQuote, :Bold, :CloudServices,
84
117
  :Essentials, :Heading, :ImageBlock, :ImageCaption, :ImageInline,
85
118
  :ImageInsert, :ImageInsertViaUrl, :ImageResize, :ImageStyle,
86
119
  :ImageTextAlternative, :ImageToolbar, :ImageUpload, :Indent,
@@ -89,11 +122,15 @@ CKEditor5::Rails::Engine.configure do |config|
89
122
  :SelectAll, :Table, :TableCaption, :TableCellProperties,
90
123
  :TableColumnResize, :TableProperties, :TableToolbar,
91
124
  :TextTransformation, :TodoList, :Underline, :Undo, :Base64UploadAdapter
125
+
126
+ configure :image, {
127
+ toolbar: ['imageTextAlternative', 'imageStyle:inline', 'imageStyle:block', 'imageStyle:side']
128
+ }
92
129
  end
93
130
  end
94
131
  ```
95
132
 
96
- In order to override existing presets, you can use the `config.presets.override` method. The method takes the name of the preset you want to override and a block with the new configuration. In example below, we override the `:default` preset to hide the menubar.
133
+ In order to override existing presets, you can use the `config.presets.override` method. The method takes the name of the preset you want to override and a block with the old configuration. The example below shows how to hide the menubar in the default preset:
97
134
 
98
135
  ```rb
99
136
  # config/initializers/ckeditor5.rb
@@ -105,46 +142,132 @@ CKEditor5::Rails::Engine.configure do |config|
105
142
  end
106
143
  ```
107
144
 
108
- You can generate your preset using the CKEditor 5 [online builder](https://ckeditor.com/ckeditor-5/online-builder/). After generating the configuration, you can copy it to the `config/initializers/ckeditor5.rb` file.
145
+ Configuration of the editor can be complex, and it's recommended to use the CKEditor 5 [online builder](https://ckeditor.com/ckeditor-5/online-builder/) to generate the configuration. It allows you to select the features you want to include and generate the configuration code in JavaScript format. Keep in mind that you need to convert the JavaScript configuration to Ruby format before using it in this gem.
109
146
 
110
147
  ### Available Configuration Methods ⚙️
111
148
 
112
149
  <details>
113
150
  <summary>Expand to show available methods 📖</summary>
114
151
 
115
- #### `shape(type)` method
152
+ #### `version(version)` method
116
153
 
117
- Defines the type of editor. Available options:
154
+ Defines the version of CKEditor 5 to be used. The example below shows how to set the version to `43.2.0`:
118
155
 
119
- - `:classic` - classic edytor
120
- - `:inline` - inline editor
121
- - `:decoupled` - decoupled editor
122
- - `:balloon` - balloon editor
123
- - `:multiroot` - editor with multiple editing areas
156
+ ```rb
157
+ # config/initializers/ckeditor5.rb
158
+
159
+ config.presets.define :custom do
160
+ # ... other configuration
161
+
162
+ version '43.2.0'
163
+ end
164
+ ```
165
+
166
+ #### `gpl` method
167
+
168
+ Defines the license of CKEditor 5. The example below shows how to set the license to GPL:
169
+
170
+ ```rb
171
+ # config/initializers/ckeditor5.rb
172
+
173
+ config.presets.define :custom do
174
+ # ... other configuration
175
+
176
+ gpl
177
+ end
178
+ ```
179
+
180
+ #### `license_key(key)` method
124
181
 
125
- The example below shows how to define a multiroot editor:
182
+ Defines the license key of CKEditor 5. It calls `premium` method internally. The example below shows how to set the license key:
126
183
 
127
184
  ```rb
128
185
  # config/initializers/ckeditor5.rb
129
186
 
130
187
  config.presets.define :custom do
131
- shape :multiroot
188
+ # ... other configuration
189
+
190
+ license_key 'your-license-key'
132
191
  end
133
192
  ```
134
193
 
135
- #### `plugins(*names)` method
194
+ #### `premium` method
136
195
 
137
- Defines the plugins to be included in the editor. You can specify multiple plugins by passing their names as arguments.
196
+ Defines if premium package should be included in JS assets. The example below shows how to add `ckeditor5-premium-features` to import maps:
138
197
 
139
198
  ```rb
140
199
  # config/initializers/ckeditor5.rb
141
200
 
142
201
  config.presets.define :custom do
143
- plugins :Bold, :Italic, :Underline, :Link
202
+ # ... other configuration
203
+
204
+ premium
144
205
  end
145
206
  ```
146
207
 
147
- #### `toolbar(*items, should_group_when_full: true)` method
208
+ #### `translations(*languages)` method
209
+
210
+ 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:
211
+
212
+ ```rb
213
+ # config/initializers/ckeditor5.rb
214
+
215
+ config.presets.define :custom do
216
+ # ... other configuration
217
+
218
+ translations :pl, :es
219
+ end
220
+ ```
221
+
222
+ ⚠️ 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.
223
+
224
+ ```rb
225
+ # config/initializers/ckeditor5.rb
226
+
227
+ config.presets.define :custom do
228
+ translations :pl
229
+
230
+ language :pl
231
+ end
232
+ ```
233
+
234
+ #### `ckbox` method
235
+
236
+ Defines the CKBox plugin to be included in the editor. The example below shows how to include the CKBox plugin:
237
+
238
+ ```rb
239
+ # config/initializers/ckeditor5.rb
240
+
241
+ config.presets.define :custom do
242
+ # ... other configuration
243
+
244
+ ckbox '2.5.4', theme: :lark
245
+ end
246
+ ```
247
+
248
+ #### `type(type)` method
249
+
250
+ Defines the type of editor. Available options:
251
+
252
+ - `:classic` - classic edytor
253
+ - `:inline` - inline editor
254
+ - `:decoupled` - decoupled editor
255
+ - `:balloon` - balloon editor
256
+ - `:multiroot` - editor with multiple editing areas
257
+
258
+ The example below sets the editor type to `multiroot` in the custom preset:
259
+
260
+ ```rb
261
+ # config/initializers/ckeditor5.rb
262
+
263
+ config.presets.define :custom do
264
+ # ... other configuration
265
+
266
+ type :multiroot
267
+ end
268
+ ```
269
+
270
+ #### `toolbar(*items, should_group_when_full: true, &block)` method
148
271
 
149
272
  Defines the toolbar items. You can use predefined items like `:undo`, `:redo`, `:|` or specify custom items. There are a few special items:
150
273
 
@@ -167,6 +290,21 @@ end
167
290
 
168
291
  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).
169
292
 
293
+ If you want to add or prepend items to the existing toolbar, you can use the block syntax:
294
+
295
+ ```rb
296
+ # config/initializers/ckeditor5.rb
297
+
298
+ config.presets.override :default do
299
+ # ... other configuration
300
+
301
+ toolbar do
302
+ append :selectAll, :|, :selectAll, :selectAll
303
+ # Or prepend: prepend :selectAll, :|, :selectAll, :selectAll
304
+ end
305
+ end
306
+ ```
307
+
170
308
  #### `menubar(visible: true)` method
171
309
 
172
310
  Defines the visibility of the menubar. By default, it's set to `true`.
@@ -241,7 +379,7 @@ config.presets.define :custom do
241
379
  end
242
380
  ```
243
381
 
244
- In order to import a plugin from a custom package, you can pass the `import_name` keyword argument:
382
+ In order to import a plugin from a custom ESM package, you can pass the `import_name` keyword argument:
245
383
 
246
384
  ```rb
247
385
  # config/initializers/ckeditor5.rb
@@ -253,20 +391,71 @@ config.presets.define :custom do
253
391
  end
254
392
  ```
255
393
 
394
+ In order to import a plugin from a custom Window entry, you can pass the `window_name` keyword argument:
395
+
396
+ ```rb
397
+ # config/initializers/ckeditor5.rb
398
+
399
+ config.presets.define :custom do
400
+ # ... other configuration
401
+
402
+ plugin :YourPlugin, window_name: 'YourPlugin'
403
+ end
404
+ ```
405
+
406
+ #### `plugins(*names, **kwargs)` method
407
+
408
+ 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.
409
+
410
+ ```rb
411
+ # config/initializers/ckeditor5.rb
412
+
413
+ config.presets.define :custom do
414
+ # ... other configuration
415
+
416
+ plugins :Bold, :Italic, :Underline, :Link
417
+ end
418
+ ```
419
+
420
+ #### `inline_plugin(name, code)` method
421
+
422
+ 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:
423
+
424
+ ```rb
425
+ # config/initializers/ckeditor5.rb
426
+
427
+ config.presets.define :custom do
428
+ # ... other configuration
429
+
430
+ inline_plugin :MyCustomPlugin, <<~JS
431
+ import { Plugin } from 'ckeditor5';
432
+
433
+ export default class MyCustomPlugin extends Plugin {
434
+ static get pluginName() {
435
+ return 'MyCustomPlugin';
436
+ }
437
+
438
+ init() {
439
+ // ... Your plugin code
440
+ }
441
+ }
442
+ JS
443
+ end
444
+ ```
256
445
  </details>
257
446
 
258
447
  ## Including CKEditor 5 assets 📦
259
448
 
260
- To include CKEditor 5 assets in your application, you can use the `ckeditor5_assets` helper method. This method takes the version of CKEditor 5 as an argument and includes the necessary assets. It allows you to specify custom translations to be included.
449
+ To include CKEditor 5 assets in your application, you can use the `ckeditor5_assets` helper method. This method takes the version of CKEditor 5 as an argument and includes the necessary resources of the editor. Depending on the specified configuration, it includes the JS and CSS assets from the official CKEditor 5 CDN or one of the popular CDNs.
261
450
 
262
- Keep in mind that you need to include the assets in the `head` section of your layout. In examples below, we use `content_for` to include the assets in the `head` section.
451
+ Keep in mind that you need to include the helper result in the `head` section of your layout. In examples below, we use `content_for` helper to include the assets in the `head` section of the view.
263
452
 
264
453
  ### Lazy loading 🚀
265
454
 
266
455
  <details>
267
456
  <summary>Loading JS and CSS Assets</summary>
268
457
 
269
- All JS assets defined by the `ckeditor5_assets` helper method are loaded asynchronously. It means that the assets are loaded in the background without blocking the rendering of the page. However, the CSS assets are loaded synchronously to prevent the flash of unstyled content and ensure that the editor is styled correctly.
458
+ All JS assets defined by the `ckeditor5_assets` helper method are loaded **asynchronously**. It means that the assets are loaded in the background without blocking the rendering of the page. However, the CSS assets are loaded **synchronously** to prevent the flash of unstyled content and ensure that the editor is styled correctly.
270
459
 
271
460
  It has been achieved by using web components, together with import maps, which are supported by modern browsers. The web components are used to define the editor and its plugins, while the import maps are used to define the dependencies between the assets.
272
461
 
@@ -274,7 +463,7 @@ It has been achieved by using web components, together with import maps, which a
274
463
 
275
464
  ### GPL usage 🆓
276
465
 
277
- If you want to use CKEditor 5 under the GPL license, you can include the assets using the `ckeditor5_assets` helper method with the `version` keyword argument. The example below shows how to include the assets for version `43.3.0`:
466
+ 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:
278
467
 
279
468
  ```erb
280
469
  <!-- app/views/demos/index.html.erb -->
@@ -286,6 +475,28 @@ If you want to use CKEditor 5 under the GPL license, you can include the assets
286
475
 
287
476
  It'll include the necessary assets for the GPL license from one of the most popular CDNs. In our scenario, we use the `jsdelivr` CDN which is the default one.
288
477
 
478
+ Version is optional as long as you defined it in the `config/initializers/ckeditor5.rb` file. If you want to use the default version, you can omit the `version` keyword argument:
479
+
480
+ ```erb
481
+ <!-- app/views/demos/index.html.erb -->
482
+
483
+ <% content_for :head do %>
484
+ <%= ckeditor5_assets %>
485
+ <% end %>
486
+ ```
487
+
488
+ Set the version in the `config/initializers/ckeditor5.rb` file:
489
+
490
+ ```rb
491
+ # config/initializers/ckeditor5.rb
492
+
493
+ CKEditor5::Rails::Engine.configure do
494
+ presets.override :default do
495
+ version '43.3.0'
496
+ end
497
+ end
498
+ ```
499
+
289
500
  In order to use `unpkg` CDN, you can pass the `cdn` keyword argument with the value `:unpkg`:
290
501
 
291
502
  ```erb
@@ -349,7 +560,7 @@ In this scenario, the assets are included from the official CKEditor 5 CDN which
349
560
 
350
561
  ## Editor placement 🏗️
351
562
 
352
- The `ckeditor5_editor` helper renders CKEditor 5 instances in your views. Before using it, ensure you've included the necessary assets in your page's head section.
563
+ The `ckeditor5_editor` helper renders CKEditor 5 instances in your views. Before using it, ensure you've included the necessary assets in your page's head section otherwise the editor won't work as there are no CKEditor 5 JavaScript and CSS files loaded.
353
564
 
354
565
  ### Classic editor 📝
355
566
 
@@ -365,7 +576,7 @@ The example below shows how to include the classic editor in your view:
365
576
  <!-- app/views/demos/index.html.erb -->
366
577
 
367
578
  <% content_for :head do %>
368
- <%= ckeditor5_assets version: '43.3.0' %>
579
+ <%= ckeditor5_assets %>
369
580
  <% end %>
370
581
 
371
582
  <%= ckeditor5_editor style: 'width: 600px' %>
@@ -379,7 +590,7 @@ While example above uses predefined `:default` preset, you can use your custom p
379
590
  <!-- app/views/demos/index.html.erb -->
380
591
 
381
592
  <% content_for :head do %>
382
- <%= ckeditor5_assets version: '43.3.0' %>
593
+ <%= ckeditor5_assets %>
383
594
  <% end %>
384
595
 
385
596
  <%= ckeditor5_editor preset: :custom, style: 'width: 600px' %>
@@ -391,7 +602,7 @@ If your configuration is even more complex, you can pass the `config` and `type`
391
602
  <!-- app/views/demos/index.html.erb -->
392
603
 
393
604
  <% content_for :head do %>
394
- <%= ckeditor5_assets version: '43.3.0' %>
605
+ <%= ckeditor5_assets %>
395
606
  <% end %>
396
607
 
397
608
  <%= ckeditor5_editor type: :classic, config: { plugins: [:Bold, :Italic], toolbar: [:Bold, :Italic] }, style: 'width: 600px' %>
@@ -403,7 +614,7 @@ If you want to override the configuration of the editor specified in default or
403
614
  <!-- app/views/demos/index.html.erb -->
404
615
 
405
616
  <% content_for :head do %>
406
- <%= ckeditor5_assets version: '43.3.0' %>
617
+ <%= ckeditor5_assets %>
407
618
  <% end %>
408
619
 
409
620
  <%= ckeditor5_editor extra_config: { toolbar: [:Bold, :Italic] }, style: 'width: 600px' %>
@@ -425,7 +636,7 @@ If you want to use a multiroot editor, you can pass the `type` keyword argument
425
636
  <!-- app/views/demos/index.html.erb -->
426
637
 
427
638
  <% content_for :head do %>
428
- <%= ckeditor5_assets version: '43.2.0' %>
639
+ <%= ckeditor5_assets %>
429
640
  <% end %>
430
641
 
431
642
  <%= ckeditor5_editor type: :multiroot, style: 'width: 600px' do %>
@@ -454,7 +665,7 @@ If you want to use an inline editor, you can pass the `type` keyword argument wi
454
665
  <!-- app/views/demos/index.html.erb -->
455
666
 
456
667
  <% content_for :head do %>
457
- <%= ckeditor5_assets version: '43.2.0' %>
668
+ <%= ckeditor5_assets %>
458
669
  <% end %>
459
670
 
460
671
  <%= ckeditor5_editor type: :inline, style: 'width: 600px' %>
@@ -472,7 +683,7 @@ If you want to use a balloon editor, you can pass the `type` keyword argument wi
472
683
  <!-- app/views/demos/index.html.erb -->
473
684
 
474
685
  <% content_for :head do %>
475
- <%= ckeditor5_assets version: '43.2.0' %>
686
+ <%= ckeditor5_assets %>
476
687
  <% end %>
477
688
 
478
689
  <%= ckeditor5_editor type: :balloon, style: 'width: 600px' %>
@@ -488,60 +699,304 @@ If you want to use a decoupled editor, you can pass the `type` keyword argument
488
699
 
489
700
  ```erb
490
701
  <% content_for :head do %>
491
- <%= ckeditor5_assets version: '43.2.0', translations: [:pl, :es] %>
702
+ <%= ckeditor5_assets %>
492
703
  <% end %>
493
704
 
494
- <style>
495
- .menubar-container,
496
- .editable-container,
497
- .toolbar-container {
498
- position: relative;
499
- border: 1px solid red;
500
- }
705
+ <%= ckeditor5_editor type: :decoupled, style: 'width: 600px' do %>
706
+ <div class="menubar-container">
707
+ <%= ckeditor5_menubar %>
708
+ </div>
501
709
 
502
- .menubar-container::after,
503
- .editable-container::after,
504
- .toolbar-container::after {
505
- content: attr(class);
506
- position: absolute;
507
- background: red;
508
- color: #fff;
509
- top: 0;
510
- right: 0;
511
- font: 10px/2 monospace;
512
- padding: .1em .3em;
513
- }
710
+ <div class="toolbar-container">
711
+ <%= ckeditor5_toolbar %>
712
+ </div>
713
+
714
+ <div class="editable-container">
715
+ <%= ckeditor5_editable %>
716
+ </div>
717
+ <% end %>
718
+ ```
719
+
720
+ ## How to access editor instance? 🤔
721
+
722
+ 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.
723
+
724
+ For example:
725
+
726
+ ```erb
727
+ <!-- app/views/demos/index.html.erb -->
728
+
729
+ <% content_for :head do %>
730
+ <%= ckeditor5_assets %>
731
+ <% end %>
732
+
733
+ <%= ckeditor5_editor style: 'width: 600px', id: 'editor' %>
734
+ ```
735
+
736
+ ⚠️ 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.
737
+
738
+ ```js
739
+ document.getElementById('editor').instance
740
+ ```
741
+
742
+ 👌 Accessing the editor instance using `instancePromise` property. It's a promise that resolves to the editor instance when the editor is ready.
743
+
744
+ ```js
745
+ document.getElementById('editor').instancePromise.then(editor => {
746
+ console.log(editor);
747
+ });
748
+ ```
749
+
750
+ ✅ Accessing the editor through the `runAfterEditorReady` helper method. It's a safe way to access the editor instance when the editor is ready.
751
+
752
+ ```js
753
+ document.getElementById('editor').runAfterEditorReady(editor => {
754
+ console.log(editor);
755
+ });
756
+ ```
757
+
758
+ ## Events fired by the editor 🔊
759
+
760
+ ### `editor-ready` event
761
+
762
+ The event is fired when the initialization of the editor is completed. You can listen to it using the `editor-ready` event.
763
+
764
+ ```js
765
+ document.getElementById('editor').addEventListener('editor-ready', () => {
766
+ console.log('Editor is ready');
767
+ });
768
+ ```
769
+
770
+ ### `editor-error` event
771
+
772
+ The event is fired when the initialization of the editor fails. You can listen to it using the `editor-error` event.
514
773
 
515
- .menubar-container,
516
- .toolbar-container {
517
- padding: 1em;
774
+ ```js
775
+ document.getElementById('editor').addEventListener('editor-error', () => {
776
+ console.log('Editor has an error');
777
+ });
778
+ ```
779
+
780
+ ## Common Tasks and Solutions 💡
781
+
782
+ This section covers frequent questions and scenarios when working with CKEditor 5 in Rails applications.
783
+
784
+ ### Setting Initial Content 📝
785
+
786
+ ```erb
787
+ <%= ckeditor5_editor initial_data: "<p>Initial content</p>" %>
788
+ ```
789
+
790
+ ### Setting Editor Language 🌐
791
+
792
+ ```rb
793
+ config.presets.override :default do
794
+ translations :pl, :es
795
+ language :pl
796
+ end
797
+ ```
798
+
799
+ ### Integrating with Forms 📋
800
+
801
+ #### Rails form builder integration
802
+
803
+ ```erb
804
+ <%= form_for @post do |f| %>
805
+ <%= f.label :content %>
806
+ <%= f.ckeditor5 :content, required: true, style: 'width: 700px', initial_data: 'Hello World!' %>
807
+ <% end %>
808
+ ```
809
+
810
+ #### Simple form integration
811
+
812
+ ```erb
813
+ <%= simple_form_for :demo, url: '/demos', html: { novalidate: false } do |f| %>
814
+ <div class="form-group">
815
+ <%= f.input :content, as: :ckeditor5, initial_data: 'Hello, World 12!', input_html: { style: 'width: 600px' }, required: true %>
816
+ </div>
817
+
818
+ <div class="form-group mt-3">
819
+ <%= f.button :submit, 'Save', class: 'btn btn-primary' %>
820
+ </div>
821
+ <% end %>
822
+ ```
823
+
824
+ ### Custom Styling 🎨
825
+
826
+ ```erb
827
+ <%= ckeditor5_editor style: 'height: 400px; margin: 20px;' %>
828
+ ```
829
+
830
+ ### Custom plugins 🧩
831
+
832
+ 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.
833
+
834
+ The example below shows how to define a custom plugin that allows toggling the highlight of the selected text:
835
+
836
+ ![CKEditor 5 Custom Highlight Plugin in Ruby on Rails application](docs/custom-highlight-plugin.png)
837
+
838
+ ```rb
839
+ # config/initializers/ckeditor5.rb
840
+
841
+ config.presets.define :custom do
842
+ # ... other configuration
843
+
844
+ # 1. You can define it inline like below or in a separate file.
845
+
846
+ # In case if plugin is located in external file (recommended), you can simply import it:
847
+
848
+ # inline_plugin :MyCustomPlugin, <<~JS
849
+ # import MyPlugin from 'app/javascript/custom_plugins/highlight.js';
850
+ # export default MyPlugin;
851
+ # JS
852
+
853
+ # 2. You can also use "window_name" option to import plugin from window object:
854
+
855
+ # plugin :MyPlugin, window_name: 'MyPlugin'
856
+
857
+ # 3. Create JavaScript file in app/javascript/custom_plugins/highlight.js:
858
+ # You can also use "plugin" to import plugin from file using 'import_name' option.
859
+ # Your `my-custom-plugin` must be present in import map.
860
+
861
+ # plugin :MyCustomPlugin, import_name: 'my-custom-plugin'
862
+
863
+ # 4 Create JavaScript file in app/javascript/custom_plugins/highlight.js:
864
+
865
+ # In Ruby initializer you can also load plugin code directly from file:
866
+ plugin :MyCustomPlugin, File.read(
867
+ Rails.root.join('app/javascript/custom_plugins/highlight.js')
868
+ )
869
+
870
+ # 5. Or even define it inline:
871
+ # plugin :MyCustomPlugin, <<~JS
872
+ # import { Plugin } from 'ckeditor5';
873
+ #
874
+ # export default class MyCustomPlugin extends Plugin {
875
+ # // ...
876
+ # }
877
+ # JS
878
+
879
+ # Add item to beginning of the toolbar.
880
+ toolbar do
881
+ prepend :highlight
882
+ end
883
+ end
884
+ ```
885
+
886
+ <details>
887
+ <summary>Example of Custom Highlight Plugin 🎨</summary>
888
+
889
+ ```js
890
+ // app/javascript/custom_plugins/highlight.js
891
+ import { Plugin, Command, ButtonView } from 'ckeditor5';
892
+
893
+ export default class MyCustomPlugin extends Plugin {
894
+ static get pluginName() {
895
+ return 'MyCustomPlugin';
518
896
  }
519
897
 
520
- .editable-container {
521
- padding: 3em;
522
- overflow-y: scroll;
523
- max-height: 300px;
898
+ init() {
899
+ const editor = this.editor;
900
+
901
+ // Define schema for highlight attribute
902
+ editor.model.schema.extend('$text', { allowAttributes: 'highlight' });
903
+
904
+ // Define conversion between model and view
905
+ editor.conversion.attributeToElement({
906
+ model: 'highlight',
907
+ view: {
908
+ name: 'span',
909
+ styles: {
910
+ 'background-color': 'yellow'
911
+ }
912
+ }
913
+ });
914
+
915
+ // Create command that handles highlighting logic
916
+ // Command pattern is used to encapsulate all the logic related to executing an action
917
+ const command = new HighlightCommand(editor);
918
+
919
+ // Register command in editor
920
+ editor.commands.add('highlight', command);
921
+
922
+ // Add UI button
923
+ editor.ui.componentFactory.add('highlight', locale => {
924
+ const view = new ButtonView(locale);
925
+
926
+ // Bind button state to command state using bind method
927
+ // bind() allows to sync button state with command state automatically
928
+ view.bind('isOn').to(command, 'value');
929
+
930
+ view.set({
931
+ label: 'Highlight',
932
+ withText: true,
933
+ tooltip: true
934
+ });
935
+
936
+ view.on('execute', () => {
937
+ editor.execute('highlight');
938
+ editor.editing.view.focus();
939
+ });
940
+
941
+ return view;
942
+ });
943
+ }
944
+ }
945
+
946
+ // Command class that handles the highlight feature
947
+ // isEnabled property determines if command can be executed
948
+ class HighlightCommand extends Command {
949
+ execute() {
950
+ const model = this.editor.model;
951
+ const selection = model.document.selection;
952
+
953
+ model.change(writer => {
954
+ const ranges = model.schema.getValidRanges(selection.getRanges(), 'highlight');
955
+
956
+ for (const range of ranges) {
957
+ if (this.value) {
958
+ writer.removeAttribute('highlight', range);
959
+ } else {
960
+ writer.setAttribute('highlight', true, range);
961
+ }
962
+ }
963
+ });
524
964
  }
525
965
 
526
- .editable-container .ck-editor__editable {
527
- min-height: 21cm;
528
- padding: 2em;
529
- border: 1px #D3D3D3 solid;
530
- border-radius: var(--ck-border-radius);
531
- background: white;
532
- box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
966
+ refresh() {
967
+ const model = this.editor.model;
968
+ const selection = model.document.selection;
969
+ const isAllowed = model.schema.checkAttributeInSelection(selection, 'highlight');
970
+
971
+ // Set if command is enabled based on schema
972
+ this.isEnabled = isAllowed;
973
+ this.value = this.#isHighlightedNodeSelected();
533
974
  }
534
- </style>
535
975
 
536
- <%= ckeditor5_editor type: :decoupled, style: 'width: 600px' do %>
537
- <div class="menubar-container"><%= ckeditor5_menubar %></div>
538
- <br>
539
- <div class="toolbar-container"><%= ckeditor5_toolbar %></div>
540
- <br>
541
- <div class="editable-container"><%= ckeditor5_editable %></div>
542
- <% end %>
976
+ // Check if the highlighted node is selected.
977
+ #isHighlightedNodeSelected() {
978
+ const { model } = this.editor
979
+ const { schema } = model
980
+ const selection = model.document.selection
981
+
982
+ if (selection.isCollapsed) {
983
+ return selection.hasAttribute('highlight')
984
+ }
985
+
986
+ return selection.getRanges().some(range =>
987
+ Array
988
+ .from(range.getItems())
989
+ .some(item =>
990
+ schema.checkAttribute(item, 'highlight') &&
991
+ item.hasAttribute('highlight')
992
+ )
993
+ );
994
+ }
995
+ }
543
996
  ```
544
997
 
998
+ </details>
999
+
545
1000
  ## License 📜
546
1001
 
547
1002
  The MIT License (MIT)