# frozen_string_literal: true require_relative '../../version_detector' require_relative '../../presets/preset_builder' require_relative 'config_helpers' module CKEditor5::Rails module Editor::Helpers::Editor include Editor::Helpers::Config include Cdn::Concerns::BundleBuilder # Creates a CKEditor 5 editor instance in the view. # # @param preset [Symbol, PresetBuilder] The name of the preset or a PresetBuilder object # @param config [Hash] Custom editor configuration that overrides preset configuration # @param extra_config [Hash] Additional configuration to merge with preset/custom config # @param type [Symbol] Editor type (:classic, :inline, :balloon, :decoupled, :multiroot) # @param initial_data [String] Initial HTML content for the editor # @param watchdog [Boolean] Enable/disable the editor crash recovery (default: true) # @param editable_height [Integer] Set fixed height for editor in pixels # @param language [Symbol] Set editor UI language (e.g. :pl, :es) # @param html_attributes [Hash] Additional HTML attributes for editor element # # @example Basic usage with default preset # <%= ckeditor5_editor %> # # @example Custom preset with specific height and initial content # <%= ckeditor5_editor preset: :custom, editable_height: 300, initial_data: "

Hello

" %> # # @example Inline editor with custom styling # <%= ckeditor5_editor type: :inline, style: 'width: 600px' %> # # @example Multiroot editor with multiple editable areas # <%= ckeditor5_editor type: :multiroot do %> # <%= ckeditor5_toolbar %> # <%= ckeditor5_editable 'title', style: 'border: 1px solid gray' %> # <%= ckeditor5_editable 'content' %> # <% end %> # # @example Decoupled editor with custom UI layout # <%= ckeditor5_editor type: :decoupled do %> #
# <%= ckeditor5_toolbar %> #
#
# <%= ckeditor5_editable %> #
# <% end %> # # @example Editor with event handlers # <%= ckeditor5_editor oneditorchange: 'handleChange', # oneditorready: 'handleReady', # oneditorerror: 'handleError' %> # # @example Form integration # <%= form_for @post do |f| %> # <%= f.ckeditor5 :content, required: true %> # <% end %> def ckeditor5_editor( # rubocop:disable Metrics/ParameterLists preset: nil, config: nil, extra_config: {}, type: nil, initial_data: nil, watchdog: true, editable_height: nil, language: nil, **html_attributes, &block ) validate_editor_input!(initial_data, block) context = ckeditor5_context_or_fallback(preset) preset = Engine.find_preset!(preset || context[:preset] || :default) config = build_editor_config(preset, config, extra_config, initial_data) type ||= preset.type # Add some fallbacks config[:licenseKey] ||= context[:license_key] config[:language] = { ui: language } if language editor_props = Editor::Props.new( type, config, bundle: context[:bundle], watchdog: watchdog, editable_height: editable_height || preset.editable_height ) tag_attributes = html_attributes.merge(editor_props.to_attributes) tag.public_send(:'ckeditor-component', **tag_attributes, &block) end # Creates an editable area for multiroot or decoupled editors. # # @param name [String] Identifier for the editable area # @param kwargs [Hash] HTML attributes for the editable element # # @example Creating a named editable area in multiroot editor # <%= ckeditor5_editable 'content', style: 'border: 1px solid gray' %> def ckeditor5_editable(name = nil, **kwargs, &block) tag.public_send(:'ckeditor-editable-component', name: name, **kwargs, &block) end # Creates a UI part component for the editor (toolbar, menubar). # # @param name [String] Name of the UI component ('toolbar', 'menuBarView') # @param kwargs [Hash] HTML attributes for the UI component # # @example Creating a toolbar component # <%= ckeditor5_ui_part 'toolbar' %> def ckeditor5_ui_part(name, **kwargs, &block) tag.public_send(:'ckeditor-ui-part-component', name: name, **kwargs, &block) end # Creates a toolbar component for decoupled editor. # # @param kwargs [Hash] HTML attributes for the toolbar element # # @example Creating a toolbar with custom class # <%= ckeditor5_toolbar class: 'custom-toolbar' %> def ckeditor5_toolbar(**kwargs) ckeditor5_ui_part('toolbar', **kwargs) end # Creates a menubar component for decoupled editor. # # @param kwargs [Hash] HTML attributes for the menubar element # # @example Creating a menubar with custom styling # <%= ckeditor5_menubar style: 'margin-bottom: 10px' %> def ckeditor5_menubar(**kwargs) ckeditor5_ui_part('menuBarView', **kwargs) end private # Validates that initial_data and block are not provided simultaneously # # @param initial_data [String] Initial content for the editor # @param block [Proc] Block containing nested components # @raise [ArgumentError] If both initial_data and block are provided def validate_editor_input!(initial_data, block) return unless initial_data && block raise ArgumentError, 'Cannot pass initial data and block at the same time.' end # Builds the complete editor configuration by merging preset, custom and extra configs # # @param preset [PresetBuilder] The preset configuration object # @param config [Hash] Custom configuration that overrides preset config # @param extra_config [Hash] Additional configuration to merge # @param initial_data [String] Initial content for the editor # @return [Hash] The merged configuration hash def build_editor_config(preset, config, extra_config, initial_data) editor_config = config || preset.config editor_config = editor_config.deep_merge(extra_config) editor_config[:initialData] = initial_data if initial_data if preset.automatic_upgrades? && editor_config[:version].present? detected_version = VersionDetector.latest_safe_version(editor_config[:version]) editor_config[:version] = detected_version if detected_version end editor_config end # Retrieves or creates a context for the editor initialization # # @param preset [Symbol, PresetBuilder] The preset name or object # @return [Hash] Context hash containing bundle and preset information def ckeditor5_context_or_fallback(preset) return @__ckeditor_context if @__ckeditor_context.present? if preset.present? found_preset = Engine.find_preset(preset) return { bundle: create_preset_bundle(found_preset), preset: found_preset } end { bundle: nil, preset: Engine.default_preset } end end end