ckeditor5 1.0.6 → 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.
- checksums.yaml +4 -4
- data/README.md +418 -79
- 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 +55 -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: b14779f0c4c2e4358c77ed004b6c5ef191b2ff766fc95ba6c8f0245f7314f206
|
4
|
+
data.tar.gz: 0a47a7b166356002ef4b25879e420b42693bc1851596da4b4db1382d704fc875
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c7d8727f46f14c759f181dc77e19d4778efdb34e19327667922df06a7dcaa65d2512a4106d9dc0cf2f8810c7cb03b51e8cf440bda521f2cd237f7535447ac644
|
7
|
+
data.tar.gz: f6b08e3fa6292af226cdc2e29867ec5ffaa52cdba2a3ca6843735e4672e6d9411a7a30520b007a4cd267fa7dc69e380de8135831767d396674b4f768efc19737
|
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
|
```
|
@@ -146,9 +177,23 @@ config.presets.define :custom do
|
|
146
177
|
end
|
147
178
|
```
|
148
179
|
|
180
|
+
#### `license_key(key)` method
|
181
|
+
|
182
|
+
Defines the license key of CKEditor 5. It calls `premium` method internally. The example below shows how to set the license key:
|
183
|
+
|
184
|
+
```rb
|
185
|
+
# config/initializers/ckeditor5.rb
|
186
|
+
|
187
|
+
config.presets.define :custom do
|
188
|
+
# ... other configuration
|
189
|
+
|
190
|
+
license_key 'your-license-key'
|
191
|
+
end
|
192
|
+
```
|
193
|
+
|
149
194
|
#### `premium` method
|
150
195
|
|
151
|
-
Defines if premium package
|
196
|
+
Defines if premium package should be included in JS assets. The example below shows how to add `ckeditor5-premium-features` to import maps:
|
152
197
|
|
153
198
|
```rb
|
154
199
|
# config/initializers/ckeditor5.rb
|
@@ -162,7 +207,7 @@ end
|
|
162
207
|
|
163
208
|
#### `translations(*languages)` method
|
164
209
|
|
165
|
-
Defines the translations of CKEditor 5. You can pass the language codes as arguments. The example below shows how to
|
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:
|
166
211
|
|
167
212
|
```rb
|
168
213
|
# config/initializers/ckeditor5.rb
|
@@ -174,17 +219,15 @@ config.presets.define :custom do
|
|
174
219
|
end
|
175
220
|
```
|
176
221
|
|
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:
|
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.
|
180
223
|
|
181
224
|
```rb
|
182
225
|
# config/initializers/ckeditor5.rb
|
183
226
|
|
184
227
|
config.presets.define :custom do
|
185
|
-
|
228
|
+
translations :pl
|
186
229
|
|
187
|
-
|
230
|
+
language :pl
|
188
231
|
end
|
189
232
|
```
|
190
233
|
|
@@ -224,21 +267,7 @@ config.presets.define :custom do
|
|
224
267
|
end
|
225
268
|
```
|
226
269
|
|
227
|
-
#### `
|
228
|
-
|
229
|
-
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.
|
230
|
-
|
231
|
-
```rb
|
232
|
-
# config/initializers/ckeditor5.rb
|
233
|
-
|
234
|
-
config.presets.define :custom do
|
235
|
-
# ... other configuration
|
236
|
-
|
237
|
-
plugins :Bold, :Italic, :Underline, :Link
|
238
|
-
end
|
239
|
-
```
|
240
|
-
|
241
|
-
#### `toolbar(*items, should_group_when_full: true)` method
|
270
|
+
#### `toolbar(*items, should_group_when_full: true, &block)` method
|
242
271
|
|
243
272
|
Defines the toolbar items. You can use predefined items like `:undo`, `:redo`, `:|` or specify custom items. There are a few special items:
|
244
273
|
|
@@ -261,6 +290,21 @@ end
|
|
261
290
|
|
262
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).
|
263
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
|
+
|
264
308
|
#### `menubar(visible: true)` method
|
265
309
|
|
266
310
|
Defines the visibility of the menubar. By default, it's set to `true`.
|
@@ -335,7 +379,7 @@ config.presets.define :custom do
|
|
335
379
|
end
|
336
380
|
```
|
337
381
|
|
338
|
-
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:
|
339
383
|
|
340
384
|
```rb
|
341
385
|
# config/initializers/ckeditor5.rb
|
@@ -347,6 +391,57 @@ config.presets.define :custom do
|
|
347
391
|
end
|
348
392
|
```
|
349
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
|
+
```
|
350
445
|
</details>
|
351
446
|
|
352
447
|
## Including CKEditor 5 assets 📦
|
@@ -368,7 +463,7 @@ It has been achieved by using web components, together with import maps, which a
|
|
368
463
|
|
369
464
|
### GPL usage 🆓
|
370
465
|
|
371
|
-
If you want to use CKEditor 5 under the GPL license, you can include the assets using the `ckeditor5_assets`
|
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:
|
372
467
|
|
373
468
|
```erb
|
374
469
|
<!-- app/views/demos/index.html.erb -->
|
@@ -607,57 +702,301 @@ If you want to use a decoupled editor, you can pass the `type` keyword argument
|
|
607
702
|
<%= ckeditor5_assets %>
|
608
703
|
<% end %>
|
609
704
|
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
position: relative;
|
615
|
-
border: 1px solid red;
|
616
|
-
}
|
705
|
+
<%= ckeditor5_editor type: :decoupled, style: 'width: 600px' do %>
|
706
|
+
<div class="menubar-container">
|
707
|
+
<%= ckeditor5_menubar %>
|
708
|
+
</div>
|
617
709
|
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
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.
|
773
|
+
|
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 📋
|
630
800
|
|
631
|
-
|
632
|
-
|
633
|
-
|
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
|
+

|
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';
|
634
896
|
}
|
635
897
|
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
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
|
+
});
|
640
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
|
+
});
|
964
|
+
}
|
965
|
+
|
966
|
+
refresh() {
|
967
|
+
const model = this.editor.model;
|
968
|
+
const selection = model.document.selection;
|
969
|
+
const isAllowed = model.schema.checkAttributeInSelection(selection, 'highlight');
|
641
970
|
|
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);
|
971
|
+
// Set if command is enabled based on schema
|
972
|
+
this.isEnabled = isAllowed;
|
973
|
+
this.value = this.#isHighlightedNodeSelected();
|
649
974
|
}
|
650
|
-
</style>
|
651
975
|
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
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
|
+
}
|
659
996
|
```
|
660
997
|
|
998
|
+
</details>
|
999
|
+
|
661
1000
|
## License 📜
|
662
1001
|
|
663
1002
|
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,36 @@ 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 prepend(*items, before: nil)
|
191
|
+
toolbar_items = @toolbar_config[:items]
|
192
|
+
|
193
|
+
if before
|
194
|
+
index = toolbar_items.index(before)
|
195
|
+
raise ArgumentError, "Item '#{before}' not found in toolbar" unless index
|
196
|
+
|
197
|
+
toolbar_items.insert(index, *items)
|
198
|
+
else
|
199
|
+
toolbar_items.insert(0, *items)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def append(*items, after: nil)
|
204
|
+
toolbar_items = @toolbar_config[:items]
|
205
|
+
|
206
|
+
if after
|
207
|
+
index = toolbar_items.index(after)
|
208
|
+
raise ArgumentError, "Item '#{after}' not found in toolbar" unless index
|
209
|
+
|
210
|
+
toolbar_items.insert(index + 1, *items)
|
211
|
+
else
|
212
|
+
toolbar_items.push(*items)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
169
216
|
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.0
|
4
|
+
version: 1.1.0
|
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
|