# frozen_string_literal: true
require_relative 'concerns/configuration_methods'
require_relative 'concerns/plugin_methods'
module CKEditor5::Rails
module Presets
class PresetBuilder
include Editor::Helpers::Config
include Concerns::ConfigurationMethods
include Concerns::PluginMethods
# @example Basic initialization
# PresetBuilder.new do
# version '43.3.1'
# gpl
# type :classic
# end
def initialize(&block)
@version = nil
@premium = false
@cdn = :jsdelivr
@translations = [:en]
@license_key = nil
@type = :classic
@ckbox = nil
@editable_height = nil
@automatic_upgrades = false
@config = {
plugins: [],
toolbar: []
}
instance_eval(&block) if block_given?
end
# @example Copy preset and modify it
# original = PresetBuilder.new
# copied = original.initialize_copy(original)
def initialize_copy(source)
super
@translations = source.translations.dup
@ckbox = source.ckbox.dup if source.ckbox
@config = {
plugins: source.config[:plugins].map(&:dup),
toolbar: deep_copy_toolbar(source.config[:toolbar])
}.merge(
source.config.except(:plugins, :toolbar).deep_dup
)
end
# Check if preset is using premium features
# @return [Boolean]
def premium?
@premium
end
# Check if preset is using GPL license
# @return [Boolean]
def gpl?
license_key == 'GPL'
end
def deconstruct_keys(keys)
keys.index_with do |key|
public_send(key)
end
end
# Create a new preset by overriding current configuration
# @example Override existing preset
# preset.override do
# menubar visible: false
# toolbar do
# remove :underline, :heading
# end
# end
# @return [PresetBuilder] New preset instance
def override(&block)
clone.tap do |preset|
preset.instance_eval(&block)
end
end
# Merge preset with configuration hash
# @param overrides [Hash] Configuration options to merge
# @return [self]
def merge_with_hash!(**overrides)
@version = Semver.new(overrides[:version]) if overrides.key?(:version)
@premium = overrides.fetch(:premium, premium)
@cdn = overrides.fetch(:cdn, cdn)
@translations = overrides.fetch(:translations, translations)
@license_key = overrides.fetch(:license_key, license_key)
@type = overrides.fetch(:type, type)
@editable_height = overrides.fetch(:editable_height, editable_height)
@automatic_upgrades = overrides.fetch(:automatic_upgrades, automatic_upgrades)
@ckbox = overrides.fetch(:ckbox, ckbox) if overrides.key?(:ckbox) || ckbox
@config = config.merge(overrides.fetch(:config, {}))
self
end
# Set or get editable height in pixels
# @param height [Integer, nil] Height in pixels
# @example Set editor height to 300px
# editable_height 300
# @return [Integer, nil] Current height value
def editable_height(height = nil)
return @editable_height if height.nil?
@editable_height = height
end
# Configure CKBox integration
# @param version [String, nil] CKBox version
# @param theme [Symbol] Theme name (:lark)
# @example Enable CKBox with custom version
# ckbox '2.6.0', theme: :lark
def ckbox(version = nil, theme: :lark)
return @ckbox if version.nil?
@ckbox = {
version: version,
theme: theme
}
end
# Set or get license key
# @param license_key [String, nil] License key
# @example Set commercial license
# license_key 'your-license-key'
# @return [String, nil] Current license key
def license_key(license_key = nil)
return @license_key if license_key.nil?
@license_key = license_key
cdn(:cloud) unless gpl?
end
# Set GPL license and disable premium features
# @example Enable GPL license
# gpl
def gpl
license_key('GPL')
premium(false)
end
# Enable or check premium features
# @param premium [Boolean, nil] Enable/disable premium features
# @example Enable premium features
# premium true
# @return [Boolean] Premium status
def premium(premium = nil)
return @premium if premium.nil?
@premium = premium
end
# Set or get translations
# @param translations [Array<Symbol>] Language codes
# @example Add Polish and Spanish translations
# translations :pl, :es
# @return [Array<Symbol>] Current translations
def translations(*translations)
return @translations if translations.empty?
@translations = translations
end
# Set or get editor version
# @param version [String, nil] Editor version
# @example Set specific version
# version '43.3.1'
# @return [String, nil] Current version
def version(version = nil)
return @version&.to_s if version.nil?
if @automatic_upgrades && version
detected = VersionDetector.latest_safe_version(version)
@version = Semver.new(detected || version)
else
@version = Semver.new(version)
end
end
# Enable or disable automatic version upgrades
# @param enabled [Boolean] Enable/disable upgrades
# @example Enable automatic upgrades
# automatic_upgrades enabled: true
def automatic_upgrades(enabled: true)
@automatic_upgrades = enabled
end
# Check if automatic upgrades are enabled
# @return [Boolean]
def automatic_upgrades?
@automatic_upgrades
end
# Configure CDN source
# @param cdn [Symbol, nil] CDN name or custom block
# @example Use jsDelivr CDN
# cdn :jsdelivr
# @example Custom CDN configuration
# cdn do |bundle, version, path|
# "https://custom-cdn.com/#{bundle}@#{version}/#{path}"
# end
# @return [Symbol, Proc] Current CDN configuration
def cdn(cdn = nil, &block)
return @cdn if cdn.nil? && block.nil?
if block_given?
unless block.arity == 3
raise ArgumentError,
'Block must accept exactly 3 arguments: bundle, version, path'
end
@cdn = block
else
@cdn = cdn
end
end
# Set or get editor type
# @param type [Symbol, nil] Editor type (:classic, :inline, :balloon, :decoupled)
# @example Set editor type to inline
# type :inline
# @raise [ArgumentError] If invalid type provided
# @return [Symbol] Current editor type
def type(type = nil)
return @type if type.nil?
raise ArgumentError, "Invalid editor type: #{type}" unless Editor::Props.valid_editor_type?(type)
@type = type
end
# Configure menubar visibility
# @param visible [Boolean] Show/hide menubar
# @example Hide menubar
# menubar visible: false
def menubar(visible: true)
config[:menuBar] = {
isVisible: visible
}
end
# Check if menubar is visible
# @return [Boolean]
def menubar?
config.dig(:menuBar, :isVisible) || false
end
# Configure toolbar items and grouping
# @param items [Array<Symbol>] Toolbar items
# @param should_group_when_full [Boolean] Enable grouping
# @example Configure toolbar items
# toolbar :bold, :italic, :|, :link
# @example Configure with block
# toolbar do
# append :selectAll
# remove :heading
# end
# @return [ToolbarBuilder] Toolbar configuration
def toolbar(*items, should_group_when_full: true, &block)
if @config[:toolbar].blank? || !items.empty?
@config[:toolbar] = {
items: items,
shouldNotGroupWhenFull: !should_group_when_full
}
end
builder = ToolbarBuilder.new(@config[:toolbar][:items])
builder.instance_eval(&block) if block_given?
builder
end
# Check if language is configured
# @return [Boolean]
def language?
config[:language].present?
end
# Configure editor language
# @param ui [Symbol, nil] UI language code
# @param content [Symbol] Content language code
# @example Set Polish UI and content language
# language :pl
# @example Different UI and content languages
# language :pl, content: :en
# @return [Hash, nil] Language configuration
def language(ui = nil, content: ui) # rubocop:disable Naming/MethodParameterName
return config[:language] if ui.nil?
@translations << ui.to_sym unless @translations.map(&:to_sym).include?(ui.to_sym)
config[:language] = {
ui: ui,
content: content
}
end
# Configure simple upload adapter
# @param upload_url [String] Upload endpoint URL
# @example Enable upload adapter
# simple_upload_adapter '/uploads'
def simple_upload_adapter(upload_url = '/uploads')
plugins do
remove(:Base64UploadAdapter)
end
plugin(Plugins::SimpleUploadAdapter.new)
configure(:simpleUpload, { uploadUrl: upload_url })
end
# Configure WProofreader plugin
# @param version [String, nil] Plugin version
# @param cdn [String, nil] CDN URL
# @param config [Hash] Plugin configuration
# @example Basic configuration
# wproofreader serviceId: 'your-service-ID',
# srcUrl: 'https://svc.webspellchecker.net/spellcheck31/wscbundle/wscbundle.js'
def wproofreader(version: nil, cdn: nil, **config)
configure :wproofreader, config
plugins do
prepend(Plugins::WProofreaderSync.new)
append(Plugins::WProofreader.new(version: version, cdn: cdn))
end
end
private
def deep_copy_toolbar(toolbar)
return toolbar.dup if toolbar.is_a?(Array)
return {} if toolbar.nil?
{
items: toolbar[:items].dup,
shouldNotGroupWhenFull: toolbar[:shouldNotGroupWhenFull]
}
end
end
end
end