ckeditor5 1.0.6 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
![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
|
|
@@ -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
|
+
![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';
|
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
|