ckeditor5 1.19.5 → 1.20.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d6ff9f2f6d02d969211f0334a2595d6a9a9ee6e616f9188cd95fea5f1b229bf
4
- data.tar.gz: 1959c3d2dd380fd570662b975dba42bf8bb0d40c3bf7086952e6b97ba47d76b2
3
+ metadata.gz: e314a874501f623577e3e18a2d69aad4c1d1c901cf2aea3b7feb51d1b854f8cc
4
+ data.tar.gz: 42cb9fca3c8206ea2cba20b0d3229f06bc8420075f49da795849032bdad06da4
5
5
  SHA512:
6
- metadata.gz: 70f1c137763bb37f318654b450257e5a2db658bf256c10247c47fe5699a9ad87df2d2848f05f6ea1e96ca8708ba12e8f2cbbf384784583cd59380ad814b4cabe
7
- data.tar.gz: 7b52dd2dd1ed4f3fdae3024ee6e2b0215eea6ba558534faaa29ac5e824f1f9665ba1702621aa9345202a67efd17a1eccbb4bf01b1113f7a3ff837554989206a5
6
+ metadata.gz: f99b51b674897abae5b231e5e34f71a4e64963a2a88b16ef9f5f00dba1a9f17998f5bbfd7a4e1a19c6abd6716601f4c0bcbb978003a04938d2fa1a5b4ebbdf28
7
+ data.tar.gz: 050a3bf881dca0d54e186fe01066c8b7a4112d0f818e84f80de4ba8301cdd53500dc88cef800259ea9b2c1b0a23a42f93d3cce4fe0618206540a9bc9281222a8
data/Gemfile CHANGED
@@ -8,17 +8,20 @@ group :development do
8
8
  gem 'brakeman', '~> 6.1', '>= 6.1.1', require: false
9
9
  gem 'guard', '~> 2.19', '>= 2.19.0'
10
10
  gem 'guard-process', '~> 1.2'
11
+ gem 'importmap-rails'
12
+ gem 'propshaft'
11
13
  gem 'pry', '~> 0.15', '>= 0.15.0'
12
14
  gem 'pry-rails', '~> 0.3', '>= 0.3.11'
13
- gem 'rails', '~> 7.0', '>= 7.0.0'
15
+ gem 'rails', '~> 8.0', '>= 8.0.0'
14
16
  gem 'rake', '~> 13.2', '>= 13.2.1'
15
17
  gem 'rubocop', '~> 1.66', require: false
16
18
  gem 'rubocop-rails', '~> 2.26', '>= 2.26.2', require: false
17
19
  gem 'rubocop-rails-omakase', '~> 1.0.0', require: false
18
20
  gem 'simple_form', '~> 5.3', '>= 5.3.0'
19
21
  gem 'slim', '~> 5.2', '>= 5.2.0'
20
- gem 'sprockets-rails', '~> 3.2', '>= 3.2.2'
21
22
  gem 'sqlite3', '>= 1.4'
23
+ gem 'stimulus-rails'
24
+ gem 'turbo-rails'
22
25
  end
23
26
 
24
27
  group :test, :development do
data/README.md CHANGED
@@ -30,6 +30,8 @@ In your layout:
30
30
  <!DOCTYPE html>
31
31
  <html>
32
32
  <head>
33
+ <!-- ⚠️ If you use `rails-importmap` you have to include the importmap somewhere in the head. -->
34
+ <%= javascript_importmap_tags %>
33
35
  <%= yield :head %>
34
36
  </head>
35
37
  <body>
@@ -9,22 +9,26 @@ module CKEditor5::Rails::Assets
9
9
  class AssetsBundleHtmlSerializer
10
10
  include ActionView::Helpers::TagHelper
11
11
 
12
- attr_reader :bundle
12
+ attr_reader :bundle, :importmap
13
13
 
14
- def initialize(bundle)
14
+ def initialize(bundle, importmap: true)
15
15
  raise TypeError, 'bundle must be an instance of AssetsBundle' unless bundle.is_a?(AssetsBundle)
16
16
 
17
+ @importmap = importmap
17
18
  @bundle = bundle
18
19
  end
19
20
 
20
21
  def to_html
21
- safe_join([
22
- scripts_import_map_tag,
23
- preload_tags,
24
- styles_tags,
25
- window_scripts_tags,
26
- web_component_tag
27
- ])
22
+ tags = [
23
+ preload_tags,
24
+ styles_tags,
25
+ window_scripts_tags,
26
+ web_component_tag
27
+ ]
28
+
29
+ tags.prepend(AssetsImportMap.new(bundle).to_html) if importmap
30
+
31
+ safe_join(tags)
28
32
  end
29
33
 
30
34
  def self.url_resource_preload_type(url)
@@ -47,22 +51,6 @@ module CKEditor5::Rails::Assets
47
51
  end)
48
52
  end
49
53
 
50
- def scripts_import_map_tag
51
- return @scripts_import_map_tag if defined?(@scripts_import_map_tag)
52
-
53
- import_map = bundle.scripts.each_with_object({}) do |script, map|
54
- next if !script.esm? || looks_like_url?(script.import_name)
55
-
56
- map[script.import_name] = script.url
57
- end
58
-
59
- @scripts_import_map_tag = tag.script(
60
- { imports: import_map }.to_json.html_safe,
61
- type: 'importmap',
62
- nonce: true
63
- )
64
- end
65
-
66
54
  def styles_tags
67
55
  @styles_tags ||= safe_join(bundle.stylesheets.map do |url|
68
56
  tag.link(href: url, rel: 'stylesheet', crossorigin: 'anonymous')
@@ -86,6 +74,36 @@ module CKEditor5::Rails::Assets
86
74
  end
87
75
  end)
88
76
  end
77
+ end
78
+
79
+ class AssetsImportMap
80
+ include ActionView::Helpers::TagHelper
81
+
82
+ attr_reader :bundle
83
+
84
+ def initialize(bundle)
85
+ @bundle = bundle
86
+ end
87
+
88
+ def to_json(*_args)
89
+ import_map = bundle.scripts.each_with_object({}) do |script, map|
90
+ next if !script.esm? || looks_like_url?(script.import_name)
91
+
92
+ map[script.import_name] = script.url
93
+ end
94
+
95
+ { imports: import_map }.to_json
96
+ end
97
+
98
+ def to_html
99
+ tag.script(
100
+ to_json.html_safe,
101
+ type: 'importmap',
102
+ nonce: true
103
+ )
104
+ end
105
+
106
+ private
89
107
 
90
108
  def looks_like_url?(str)
91
109
  uri = URI.parse(str)
@@ -12,7 +12,58 @@ require_relative 'ckbox_bundle'
12
12
 
13
13
  module CKEditor5::Rails
14
14
  module Cdn::Helpers
15
- def ckeditor5_assets(preset: :default, **kwargs)
15
+ class ImportmapAlreadyRenderedError < ArgumentError; end
16
+
17
+ # The `ckeditor5_assets` helper includes CKEditor 5 assets in your application.
18
+ # It's responsible for generating the necessary JavaScript and CSS imports based on
19
+ # the specified preset and configuration.
20
+ #
21
+ # @param [Symbol, PresetBuilder] preset The name of the preset to use (default: :default)
22
+ # or a PresetBuilder object created using ckeditor5_preset helper
23
+ # @param [Hash] kwargs Additional configuration options:
24
+ # - version: Specify a custom CKEditor version
25
+ # - cdn: Select CDN provider (:jsdelivr, :unpkg, etc.)
26
+ # - translations: Array of language codes to include
27
+ # - ckbox: Configuration hash for CKBox integration
28
+ # - license_key: Commercial license key
29
+ # - premium: Enable premium features
30
+ # - language: Set editor UI language (e.g. :pl, :es)
31
+ #
32
+ # @example Basic usage with default preset
33
+ # <%= ckeditor5_assets %>
34
+ # <%= ckeditor5_editor %>
35
+ #
36
+ # @example Simple editor with custom configuration
37
+ # <%= ckeditor5_assets preset: :basic %>
38
+ # <%= ckeditor5_editor toolbar: [:bold, :italic], plugins: [:Bold, :Italic] %>
39
+ #
40
+ # @example Using custom preset with translations and language
41
+ # <%= ckeditor5_assets preset: :custom, translations: [:pl, :es], language: :pl %>
42
+ #
43
+ # @example Commercial usage with license key
44
+ # <%= ckeditor5_assets license_key: 'your-license-key' %>
45
+ #
46
+ # @example Using preset builder object
47
+ # <% @preset = ckeditor5_preset do
48
+ # version '43.3.1'
49
+ # toolbar :bold, :italic
50
+ # plugins :Bold, :Italic
51
+ # end %>
52
+ # <%= ckeditor5_assets preset: @preset %>
53
+ #
54
+ # @example Editor only configuration with different types
55
+ # <%= ckeditor5_assets preset: :basic %>
56
+ # <%= ckeditor5_editor type: :classic %>
57
+ # <%= ckeditor5_editor type: :inline %>
58
+ # <%= ckeditor5_editor type: :balloon %>
59
+ #
60
+ def ckeditor5_assets(
61
+ preset: :default,
62
+ importmap: true,
63
+ **kwargs
64
+ )
65
+ ensure_importmap_not_rendered!
66
+
16
67
  mapped_preset = merge_with_editor_preset(preset, **kwargs)
17
68
  mapped_preset => {
18
69
  cdn:,
@@ -34,7 +85,7 @@ module CKEditor5::Rails
34
85
  preset: mapped_preset
35
86
  }
36
87
 
37
- Assets::AssetsBundleHtmlSerializer.new(bundle).to_html
88
+ build_html_tags(bundle, importmap)
38
89
  end
39
90
 
40
91
  Cdn::UrlGenerator::CDN_THIRD_PARTY_GENERATORS.each_key do |key|
@@ -105,5 +156,33 @@ module CKEditor5::Rails
105
156
  bundle << plugin.preload_assets_bundle if plugin.preload_assets_bundle.present?
106
157
  end
107
158
  end
159
+
160
+ def importmap_available?
161
+ respond_to?(:importmap_rendered?)
162
+ end
163
+
164
+ def ensure_importmap_not_rendered!
165
+ return unless importmap_available? && importmap_rendered?
166
+
167
+ raise ImportmapAlreadyRenderedError,
168
+ 'CKEditor5 assets must be included before javascript_importmap_tags. ' \
169
+ 'Please move ckeditor5_assets helper before javascript_importmap_tags in your layout.'
170
+ end
171
+
172
+ def build_html_tags(bundle, importmap)
173
+ serializer = Assets::AssetsBundleHtmlSerializer.new(
174
+ bundle,
175
+ importmap: importmap && !importmap_available?
176
+ )
177
+
178
+ html = serializer.to_html
179
+
180
+ if importmap_available?
181
+ @__ckeditor_context[:html_tags] = html
182
+ nil
183
+ else
184
+ html
185
+ end
186
+ end
108
187
  end
109
188
  end
@@ -5,6 +5,22 @@ require_relative 'preset_serializer'
5
5
 
6
6
  module CKEditor5::Rails::Context
7
7
  module Helpers
8
+ # Creates a CKEditor context component that can be shared between multiple editors.
9
+ # This allows you to define common plugins that will be available to all editors
10
+ # within the context.
11
+ #
12
+ # @param [PresetBuilder] preset The preset object containing shared plugins configuration
13
+ # @yield The block where editor instances should be defined
14
+ #
15
+ # @example Basic usage with shared plugins
16
+ # <% preset = ckeditor5_context_preset do
17
+ # plugins :Mention, :Emoji # These plugins will be shared across all editors
18
+ # end %>
19
+ #
20
+ # <%= ckeditor5_context(preset) do %>
21
+ # <%= ckeditor5_editor preset: :content %>
22
+ # <%= ckeditor5_editor preset: :description %>
23
+ # <% end %>
8
24
  def ckeditor5_context(preset = nil, &block)
9
25
  preset ||= PresetBuilder.new
10
26
  context_props = PresetSerializer.new(preset)
@@ -12,6 +28,18 @@ module CKEditor5::Rails::Context
12
28
  tag.public_send(:'ckeditor-context-component', **context_props.to_attributes, &block)
13
29
  end
14
30
 
31
+ # Creates a new preset builder object for use with ckeditor5_context.
32
+ # Used to define shared plugins that will be available to all editors within the context.
33
+ # Note: Only plugins configuration is relevant for context, other settings like toolbar
34
+ # should be configured at the editor level.
35
+ #
36
+ # @yield Block for configuring the shared plugins
37
+ # @return [PresetBuilder] A new preset builder instance
38
+ #
39
+ # @example Creating a context with shared plugins
40
+ # <% preset = ckeditor5_context_preset do
41
+ # plugins :Comments, :TrackChanges, :Collaboration # Shared functionality plugins
42
+ # end %>
15
43
  def ckeditor5_context_preset(&block)
16
44
  PresetBuilder.new(&block)
17
45
  end
@@ -5,10 +5,39 @@ require_relative '../presets/concerns/plugin_methods'
5
5
 
6
6
  module CKEditor5::Rails
7
7
  module Context
8
+ # PresetBuilder provides functionality for building CKEditor 5 presets.
9
+ #
10
+ # This class includes configuration and plugin handling methods from concerns
11
+ # and allows defining presets either through initialization blocks or method chaining.
12
+ #
13
+ # @example Basic preset definition
14
+ # preset = PresetBuilder.new do
15
+ # version '43.3.1'
16
+ # gpl
17
+ # type :classic
18
+ # toolbar :bold, :italic
19
+ # end
20
+ #
21
+ # @example Cloning and modifying a preset
22
+ # new_preset = preset.clone
23
+ # new_preset.toolbar do
24
+ # append :underline
25
+ # end
26
+ #
27
+ # @see Presets::Concerns::ConfigurationMethods
28
+ # @see Presets::Concerns::PluginMethods
8
29
  class PresetBuilder
9
30
  include Presets::Concerns::ConfigurationMethods
10
31
  include Presets::Concerns::PluginMethods
11
32
 
33
+ # Initializes a new preset builder with optional configuration block
34
+ #
35
+ # @param block [Proc] Optional configuration block
36
+ # @example Initialize with block
37
+ # PresetBuilder.new do
38
+ # version '43.3.1'
39
+ # toolbar :bold, :italic
40
+ # end
12
41
  def initialize(&block)
13
42
  @config = {
14
43
  plugins: []
@@ -17,6 +46,10 @@ module CKEditor5::Rails
17
46
  instance_eval(&block) if block_given?
18
47
  end
19
48
 
49
+ # Creates a deep copy of the preset builder
50
+ #
51
+ # @param source [PresetBuilder] Source preset to copy from
52
+ # @return [PresetBuilder] New preset instance with copied configuration
20
53
  def initialize_copy(source)
21
54
  super
22
55
 
@@ -2,10 +2,49 @@
2
2
 
3
3
  module CKEditor5::Rails::Editor::Helpers
4
4
  module Config
5
+ # Creates a reference to a DOM element that will be used by CKEditor's features.
6
+ # This is particularly useful for features that need to check element presence
7
+ # or operate on specific DOM elements.
8
+ #
9
+ # @param selector [String] CSS selector for the target element
10
+ # @return [Hash] A hash with the element reference in CKEditor's format
11
+ #
12
+ # @example Referencing an element in plugin configuration
13
+ # configure :yourPlugin, {
14
+ # element: ckeditor5_element_ref("body")
15
+ # }
5
16
  def ckeditor5_element_ref(selector)
6
17
  { '$element': selector }
7
18
  end
8
19
 
20
+ # Creates or retrieves a preset configuration for CKEditor.
21
+ # When called with a name, finds and returns an existing preset.
22
+ # When called with a block, creates a new preset with the given configuration.
23
+ #
24
+ # @param name [Symbol, nil] The name of an existing preset to retrieve
25
+ # @yield Block for configuring a new preset
26
+ # @return [PresetBuilder] The preset configuration object
27
+ #
28
+ # @example Finding an existing preset
29
+ # @preset = ckeditor5_preset(:default)
30
+ #
31
+ # @example Creating a custom preset in controller
32
+ # @preset = ckeditor5_preset do
33
+ # version '43.3.1'
34
+ # toolbar :sourceEditing, :|, :bold, :italic
35
+ # plugins :Essentials, :Paragraph, :Bold, :Italic
36
+ # end
37
+ #
38
+ # @example Using preset in view
39
+ # <%= ckeditor5_assets preset: @preset %>
40
+ # <%= ckeditor5_editor %>
41
+ #
42
+ # @example Overriding existing preset
43
+ # @preset = ckeditor5_preset(:default).override do
44
+ # toolbar do
45
+ # remove :underline, :heading
46
+ # end
47
+ # end
9
48
  def ckeditor5_preset(name = nil, &block)
10
49
  return CKEditor5::Rails::Engine.find_preset(name) if name
11
50
 
@@ -11,19 +11,53 @@ module CKEditor5::Rails
11
11
  class EditorContextError < StandardError; end
12
12
  class PresetNotFoundError < ArgumentError; end
13
13
 
14
- # Creates a CKEditor 5 editor instance
15
- #
16
- # @param preset [Symbol] High-level configuration preset that defines base editor setup,
17
- # including editor type, plugins, and default configuration
18
- # @param config [Hash] Editor-specific configuration that overrides preset defaults
19
- # @param extra_config [Hash] Additional configuration to be merged with the base config
20
- # @param type [Symbol] Editor type (:classic, :inline, :balloon, :decoupled, :multiroot),
21
- # defaults to preset's type if not specified
22
- # @param initial_data [String] Initial content for the editor
23
- # @param watchdog [Boolean] Whether to enable the CKEditor watchdog feature
24
- # @param editable_height [String, Integer] Height of the editable area (Classic editor only)
25
- # @param language [String] Language code for the editor UI
26
- # @param html_attributes [Hash] Additional HTML attributes for the editor element
14
+ # Creates a CKEditor 5 editor instance in the view.
15
+ #
16
+ # @param preset [Symbol, PresetBuilder] The name of the preset or a PresetBuilder object
17
+ # @param config [Hash] Custom editor configuration that overrides preset configuration
18
+ # @param extra_config [Hash] Additional configuration to merge with preset/custom config
19
+ # @param type [Symbol] Editor type (:classic, :inline, :balloon, :decoupled, :multiroot)
20
+ # @param initial_data [String] Initial HTML content for the editor
21
+ # @param watchdog [Boolean] Enable/disable the editor crash recovery (default: true)
22
+ # @param editable_height [Integer] Set fixed height for editor in pixels
23
+ # @param language [Symbol] Set editor UI language (e.g. :pl, :es)
24
+ # @param html_attributes [Hash] Additional HTML attributes for editor element
25
+ #
26
+ # @example Basic usage with default preset
27
+ # <%= ckeditor5_editor %>
28
+ #
29
+ # @example Custom preset with specific height and initial content
30
+ # <%= ckeditor5_editor preset: :custom, editable_height: 300, initial_data: "<p>Hello</p>" %>
31
+ #
32
+ # @example Inline editor with custom styling
33
+ # <%= ckeditor5_editor type: :inline, style: 'width: 600px' %>
34
+ #
35
+ # @example Multiroot editor with multiple editable areas
36
+ # <%= ckeditor5_editor type: :multiroot do %>
37
+ # <%= ckeditor5_toolbar %>
38
+ # <%= ckeditor5_editable 'title', style: 'border: 1px solid gray' %>
39
+ # <%= ckeditor5_editable 'content' %>
40
+ # <% end %>
41
+ #
42
+ # @example Decoupled editor with custom UI layout
43
+ # <%= ckeditor5_editor type: :decoupled do %>
44
+ # <div class="toolbar-container">
45
+ # <%= ckeditor5_toolbar %>
46
+ # </div>
47
+ # <div class="editor-container">
48
+ # <%= ckeditor5_editable %>
49
+ # </div>
50
+ # <% end %>
51
+ #
52
+ # @example Editor with event handlers
53
+ # <%= ckeditor5_editor oneditorchange: 'handleChange',
54
+ # oneditorready: 'handleReady',
55
+ # oneditorerror: 'handleError' %>
56
+ #
57
+ # @example Form integration
58
+ # <%= form_for @post do |f| %>
59
+ # <%= f.ckeditor5 :content, required: true %>
60
+ # <% end %>
27
61
  def ckeditor5_editor( # rubocop:disable Metrics/ParameterLists
28
62
  preset: nil,
29
63
  config: nil, extra_config: {}, type: nil,
@@ -51,18 +85,44 @@ module CKEditor5::Rails
51
85
  tag.public_send(:'ckeditor-component', **tag_attributes, &block)
52
86
  end
53
87
 
88
+ # Creates an editable area for multiroot or decoupled editors.
89
+ #
90
+ # @param name [String] Identifier for the editable area
91
+ # @param kwargs [Hash] HTML attributes for the editable element
92
+ #
93
+ # @example Creating a named editable area in multiroot editor
94
+ # <%= ckeditor5_editable 'content', style: 'border: 1px solid gray' %>
54
95
  def ckeditor5_editable(name = nil, **kwargs, &block)
55
96
  tag.public_send(:'ckeditor-editable-component', name: name, **kwargs, &block)
56
97
  end
57
98
 
99
+ # Creates a UI part component for the editor (toolbar, menubar).
100
+ #
101
+ # @param name [String] Name of the UI component ('toolbar', 'menuBarView')
102
+ # @param kwargs [Hash] HTML attributes for the UI component
103
+ #
104
+ # @example Creating a toolbar component
105
+ # <%= ckeditor5_ui_part 'toolbar' %>
58
106
  def ckeditor5_ui_part(name, **kwargs, &block)
59
107
  tag.public_send(:'ckeditor-ui-part-component', name: name, **kwargs, &block)
60
108
  end
61
109
 
110
+ # Creates a toolbar component for decoupled editor.
111
+ #
112
+ # @param kwargs [Hash] HTML attributes for the toolbar element
113
+ #
114
+ # @example Creating a toolbar with custom class
115
+ # <%= ckeditor5_toolbar class: 'custom-toolbar' %>
62
116
  def ckeditor5_toolbar(**kwargs)
63
117
  ckeditor5_ui_part('toolbar', **kwargs)
64
118
  end
65
119
 
120
+ # Creates a menubar component for decoupled editor.
121
+ #
122
+ # @param kwargs [Hash] HTML attributes for the menubar element
123
+ #
124
+ # @example Creating a menubar with custom styling
125
+ # <%= ckeditor5_menubar style: 'margin-bottom: 10px' %>
66
126
  def ckeditor5_menubar(**kwargs)
67
127
  ckeditor5_ui_part('menuBarView', **kwargs)
68
128
  end
@@ -3,8 +3,6 @@
3
3
  require 'rails/engine'
4
4
 
5
5
  require_relative 'presets/manager'
6
- require_relative 'hooks/form'
7
-
8
6
  require_relative 'plugins/simple_upload_adapter'
9
7
  require_relative 'plugins/wproofreader'
10
8
 
@@ -30,6 +28,16 @@ module CKEditor5::Rails
30
28
  ActionView::Helpers::FormBuilder.include(Hooks::Form::FormBuilderExtension)
31
29
  end
32
30
 
31
+ initializer 'ckeditor5.importmap', after: :load_config_initializers, before: 'importmap' do |_app|
32
+ require_relative 'hooks/importmap'
33
+
34
+ ActiveSupport.on_load(:action_view) do
35
+ if defined?(::Importmap::ImportmapTagsHelper)
36
+ ::Importmap::ImportmapTagsHelper.prepend(Hooks::Importmap::ImportmapTagsHelper)
37
+ end
38
+ end
39
+ end
40
+
33
41
  class << self
34
42
  def base
35
43
  config.ckeditor5
@@ -45,7 +53,7 @@ module CKEditor5::Rails
45
53
  end
46
54
 
47
55
  def find_preset(preset)
48
- return preset if preset.is_a?(CKEditor5::Rails::Presets::PresetBuilder)
56
+ return preset if preset.is_a?(Presets::PresetBuilder)
49
57
 
50
58
  base.presets[preset]
51
59
  end
@@ -48,6 +48,50 @@ module CKEditor5::Rails::Hooks
48
48
  end
49
49
 
50
50
  module FormBuilderExtension
51
+ # Creates a CKEditor 5 field for the specified form attribute.
52
+ #
53
+ # @param method [Symbol] The model attribute to edit
54
+ # @param options [Hash] Options for customizing the editor
55
+ #
56
+ # @option options [Symbol] :preset (:default) The preset configuration to use
57
+ # @option options [Symbol] :type (:classic) Editor type (classic, inline, balloon, decoupled)
58
+ # @option options [Hash] :config Custom editor configuration
59
+ # @option options [String] :initial_data Initial content for the editor
60
+ # @option options [Boolean] :required (false) Whether the field is required
61
+ # @option options [String] :class CSS classes for the editor
62
+ # @option options [String] :style Inline CSS styles
63
+ #
64
+ # @example Basic usage
65
+ # <%= form_for @post do |f| %>
66
+ # <%= f.ckeditor5 :content %>
67
+ # <% end %>
68
+ #
69
+ # @example With custom styling and required field
70
+ # <%= form_for @post do |f| %>
71
+ # <%= f.ckeditor5 :content,
72
+ # style: 'width: 700px',
73
+ # required: true,
74
+ # initial_data: 'Hello World!'
75
+ # %>
76
+ # <% end %>
77
+ #
78
+ # @example Using custom preset and type
79
+ # <%= form_for @post do |f| %>
80
+ # <%= f.ckeditor5 :content,
81
+ # preset: :custom,
82
+ # type: :inline,
83
+ # class: 'custom-editor'
84
+ # %>
85
+ # <% end %>
86
+ #
87
+ # @example Simple Form integration
88
+ # <%= simple_form_for @post do |f| %>
89
+ # <%= f.input :content,
90
+ # as: :ckeditor5,
91
+ # input_html: { style: 'width: 600px' },
92
+ # required: true
93
+ # %>
94
+ # <% end %>
51
95
  def ckeditor5(method, options = {})
52
96
  EditorInputBuilder.new(object_name, object, @template)
53
97
  .build_editor(method, options)
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CKEditor5::Rails::Hooks
4
+ module Importmap
5
+ module ImportmapTagsHelper
6
+ def javascript_importmap_tags(entry_point = 'application', importmap: Rails.application.importmap)
7
+ @importmap_rendered = true
8
+ serialized_tags = [
9
+ javascript_importmap_module_preload_tags(importmap, entry_point: entry_point),
10
+ javascript_import_module_tag(entry_point)
11
+ ]
12
+
13
+ importmap_json = prepare_importmap_json(importmap)
14
+
15
+ if @__ckeditor_context.present?
16
+ process_ckeditor_context(serialized_tags, importmap_json)
17
+ else
18
+ serialized_tags.prepend(javascript_inline_importmap_tag(importmap_json))
19
+ end
20
+
21
+ safe_join(serialized_tags, "\n")
22
+ end
23
+
24
+ def importmap_rendered?
25
+ @importmap_rendered
26
+ end
27
+
28
+ private
29
+
30
+ def prepare_importmap_json(importmap)
31
+ importmap.to_json(resolver: self)
32
+ end
33
+
34
+ def process_ckeditor_context(serialized_tags, importmap_json)
35
+ bundle = @__ckeditor_context.fetch(:bundle)
36
+ merged_json = merge_ckeditor_importmap(bundle, importmap_json)
37
+
38
+ serialized_tags.prepend(javascript_inline_importmap_tag(merged_json))
39
+ serialized_tags.append(@__ckeditor_context[:html_tags])
40
+ end
41
+
42
+ def merge_ckeditor_importmap(bundle, base_importmap_json)
43
+ ckeditor_json = CKEditor5::Rails::Assets::AssetsImportMap.new(bundle).to_json
44
+ merge_import_maps_json(ckeditor_json, base_importmap_json)
45
+ end
46
+
47
+ def merge_import_maps_json(a_json, b_json)
48
+ a = JSON.parse(a_json)
49
+ b = JSON.parse(b_json)
50
+ a['imports'].merge!(b['imports'])
51
+ a.to_json
52
+ rescue JSON::ParserError => e
53
+ Rails.logger.error "Failed to merge import maps: #{e.message}"
54
+ b_json
55
+ end
56
+ end
57
+ end
58
+ end