ckeditor5 1.36.5 → 1.37.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab80ebc46afaf91a9fc0da8b14449387618c817a9618e365d89bee7e27b27de2
4
- data.tar.gz: 7db8addd18a58570ca37abdcc35dbb0cf89eb9cb289015b031fef872586113bb
3
+ metadata.gz: 130f19380b0a4789ded272e7bf2d462e1126b92bbf942ec8503c365b615418a2
4
+ data.tar.gz: c8e79a7ed7e4a113fd54f36a56c2722b8d8bd728aa7806a01defcfa5c157fd16
5
5
  SHA512:
6
- metadata.gz: fc3e9c371a27f64937ac4f1ed87f4979bfedfb96db2b1edbcb2e10bf779ef51f205a149ed996985c8c3be60a08962c2e0aaeac0bc0bbd971d7171e4f857d4f8a
7
- data.tar.gz: 14eb3340d0ca94cb2ec62397cb1041075947f95b2a33bebcedbb501b800a18bbb3d19206eeefcd4728ccd6f4d16d31f2493e9dcb46dfeaccb4eafb2dd8c22838
6
+ metadata.gz: 718530350f986982755579ed950dba6aa8fb9af44ed6ae2bb045e63034e9f14798ffddb9ee20f4c30ecf4cf370259b7815312addf056b7b2963dccf7691ead8e
7
+ data.tar.gz: 6032f74aeb05dc4c12e56f06ccc1e6073729aea54893443c129c4176170b8a49ee8e2a7e632e8aab22e1e9f963dc1bd6b405fbfb6f3b3c43634dfd2f1f932aa2
data/README.md CHANGED
@@ -11,10 +11,6 @@
11
11
 
12
12
  CKEditor 5 Ruby on Rails integration gem. Provides seamless integration of CKEditor 5 with Rails applications through web components and helper methods. This gem supports various editor types, including classic, inline, balloon, and decoupled editors. It also includes support for custom plugins, translations, and configuration options.
13
13
 
14
- **Requirements:**
15
- * Ruby >= 2.5
16
- * Rails >= 5.0
17
-
18
14
  > [!IMPORTANT]
19
15
  > This gem is unofficial and not maintained by CKSource. For official CKEditor 5 documentation, visit [ckeditor.com](https://ckeditor.com/docs/ckeditor5/latest/). If you encounter any issues in editor, please report them on the [GitHub repository](https://github.com/ckeditor/ckeditor5/issues).
20
16
 
@@ -22,6 +18,13 @@ CKEditor 5 Ruby on Rails integration gem. Provides seamless integration of CKEdi
22
18
  <img src="docs/intro-classic-editor.png" alt="CKEditor 5 Classic Editor in Ruby on Rails application">
23
19
  </p>
24
20
 
21
+ ## Compatibility 🔗
22
+
23
+ | CKEditor 5 Version | Integration Version | Ruby Version | Rails Version |
24
+ |--------------------|---------------------|--------------|---------------|
25
+ | 43.x – 47.x | `<= 1.36.x` | `>= 2.5` | `>= 5.0` |
26
+ | >= 48.0 | `>= 1.37.x` | `>= 2.5` | `>= 5.0` |
27
+
25
28
  ## Installation 🛠️
26
29
 
27
30
  Add this line to your application's Gemfile:
@@ -96,7 +99,7 @@ CKEditor5::Rails.configure do
96
99
 
97
100
  # Optionally, you can specify version of CKEditor 5 to use.
98
101
  # If it's not specified the default version specified in the gem will be used.
99
- # version '48.2.0'
102
+ # version '48.3.0'
100
103
 
101
104
  # Upload images to the server using the simple upload adapter, instead of Base64 encoding.
102
105
  # simple_upload_adapter
@@ -147,7 +150,7 @@ bundle install
147
150
 
148
151
  # Start the server
149
152
  npm run npm_package:watch
150
- bundle exec guard -g rails
153
+ bundle exec guard -g rails
151
154
  ```
152
155
 
153
156
  Open [http://localhost:3000/](http://localhost:3000/) in a browser to start experimenting. Modify the code as needed.
@@ -157,6 +160,7 @@ For extending CKEditor's functionality, refer to the [plugins directory](https:/
157
160
  ## Table of Contents 📚
158
161
 
159
162
  - [CKEditor 5 Rails Integration ✨](#ckeditor-5-rails-integration-)
163
+ - [Compatibility 🔗](#compatibility-)
160
164
  - [Installation 🛠️](#installation-️)
161
165
  - [If you are not using importmap (custom bundler) 📦](#if-you-are-not-using-importmap-custom-bundler-)
162
166
  - [Try Demos! 🎮 ✨](#try-demos--)
@@ -206,9 +210,12 @@ For extending CKEditor's functionality, refer to the [plugins directory](https:/
206
210
  - [Commercial usage 💰](#commercial-usage-)
207
211
  - [Editor placement 🏗️](#editor-placement-️)
208
212
  - [Setting Initial Content 📝](#setting-initial-content-)
213
+ - [Single-line editable areas (`$inlineRoot`) 📏](#single-line-editable-areas-inlineroot-)
209
214
  - [Watchdog 🐕](#watchdog-)
210
215
  - [Classic editor 📝](#classic-editor-)
211
216
  - [Multiroot editor 🌳](#multiroot-editor-)
217
+ - [Inline roots (single-line editable areas)](#inline-roots-single-line-editable-areas)
218
+ - [Setting initial content of a root](#setting-initial-content-of-a-root)
212
219
  - [Inline editor 📝](#inline-editor-)
213
220
  - [Balloon editor 🎈](#balloon-editor-)
214
221
  - [Decoupled editor 🌐](#decoupled-editor-)
@@ -253,7 +260,7 @@ You can create your own by defining it in the [`config/initializers/ckeditor5.rb
253
260
 
254
261
  CKEditor5::Rails.configure do
255
262
  # It's possible to override the default preset right in the initializer.
256
- version '48.2.0'
263
+ version '48.3.0'
257
264
 
258
265
  # New presets inherit properties from the default preset defined in the initializer.
259
266
  # In this example, the custom preset inherits everything from default but disables the menubar:
@@ -263,7 +270,7 @@ CKEditor5::Rails.configure do
263
270
 
264
271
  # In order to define preset from scratch, you can use the `inherit: false` option.
265
272
  presets.define :blank_preset, inherit: false do
266
- version '48.2.0'
273
+ version '48.3.0'
267
274
 
268
275
  # It tells the integration to fetch the newest security patches and bug fixes.
269
276
  # It may be disabled, but it's highly recommended to keep it enabled to avoid
@@ -387,7 +394,7 @@ Defines the version of CKEditor 5 to be used. The example below shows how to set
387
394
  CKEditor5::Rails.configure do
388
395
  # ... other configuration
389
396
 
390
- version '48.2.0'
397
+ version '48.3.0'
391
398
  end
392
399
  ```
393
400
 
@@ -399,7 +406,7 @@ In order to disable default patches, you can pass the `apply_patches: false` key
399
406
  CKEditor5::Rails.configure do
400
407
  # ... other configuration
401
408
 
402
- version '48.2.0', apply_patches: false
409
+ version '48.3.0', apply_patches: false
403
410
  end
404
411
  ```
405
412
 
@@ -1379,7 +1386,7 @@ It may be useful when you want to define a preset based on the current user or r
1379
1386
  class ApplicationController < ActionController::Base
1380
1387
  def show
1381
1388
  @preset = ckeditor5_preset do
1382
- version '48.2.0'
1389
+ version '48.3.0'
1383
1390
 
1384
1391
  toolbar :sourceEditing, :|, :bold, :italic, :underline, :strikethrough,
1385
1392
  :subscript, :superscript, :removeFormat, :|, :bulletedList, :numberedList,
@@ -1414,7 +1421,7 @@ If you want to override the preset defined in the initializer, you can search fo
1414
1421
  class ApplicationController < ActionController::Base
1415
1422
  def show
1416
1423
  @preset = ckeditor5_preset(:default).override do
1417
- version '48.2.0'
1424
+ version '48.3.0'
1418
1425
 
1419
1426
  toolbar :sourceEditing, :|, :bold, :italic, :underline, :strikethrough,
1420
1427
  :subscript, :superscript, :removeFormat, :|, :bulletedList, :numberedList,
@@ -1587,7 +1594,7 @@ In that scenario it's recommended to add `gpl` method to the initializer along w
1587
1594
 
1588
1595
  CKEditor5::Rails.configure do
1589
1596
  gpl
1590
- version '48.2.0'
1597
+ version '48.3.0'
1591
1598
  end
1592
1599
  ```
1593
1600
 
@@ -1659,6 +1666,25 @@ In this scenario, the assets are included from the official CKEditor 5 CDN which
1659
1666
 
1660
1667
  The `ckeditor5_editor` helper renders CKEditor 5 instances in your views. Before using it, ensure you've included the necessary assets in your page's head section otherwise the editor won't work as there are no CKEditor 5 JavaScript and CSS files loaded.
1661
1668
 
1669
+ ```ruby
1670
+ ckeditor5_editor(
1671
+ preset: nil, # Symbol, PresetBuilder - preset to use (default: context preset or :default)
1672
+ config: nil, # Hash - custom config; replaces the preset's config (use extra_config to merge instead)
1673
+ extra_config: {}, # Hash - additional config, deep-merged on top of the preset/custom config
1674
+ type: nil, # Symbol - :classic, :inline, :balloon, :decoupled, :multiroot (default: preset's type)
1675
+ initial_data: nil, # String - initial HTML content for the editor (mutually exclusive with a block)
1676
+ watchdog: true, # Boolean - enables/disables the CKEditor 5 crash-recovery watchdog
1677
+ editable_height: nil, # Integer, String - fixed height of the editing area, e.g. 300 or '300px' (:classic/:balloon only)
1678
+ language: nil, # Symbol - UI language override for this editor instance
1679
+ inline: false, # Boolean - use the $inlineRoot model element instead of $root for the (single) root
1680
+ # (not applicable to :multiroot - use `inline` on ckeditor5_editable per root instead)
1681
+ **html_attributes # any other keyword becomes an HTML attribute: style:, class:, id:, data-*,
1682
+ # form attributes (name:, required:, value:), event handlers (oneditorready:, oneditorchange:, oneditorerror:), ...
1683
+ )
1684
+ # &block - nested content: initial data for single-root editors, or ckeditor5_toolbar / ckeditor5_menubar / ckeditor5_editable
1685
+ # for multiroot/decoupled editors. Mutually exclusive with `initial_data`.
1686
+ ```
1687
+
1662
1688
  ### Setting Initial Content 📝
1663
1689
 
1664
1690
  You can set the initial content of the editor using the `initial_data` keyword argument or by passing the content directly to the `ckeditor5_editor` helper block.
@@ -1681,6 +1707,18 @@ The example below shows how to set the initial content of the editor using the `
1681
1707
  <% end %>
1682
1708
  ```
1683
1709
 
1710
+ ### Single-line editable areas (`$inlineRoot`) 📏
1711
+
1712
+ If you want the (single) root to behave as a single-line editable area — e.g. a title field — instead of a regular multi-block `$root`, pass the `inline` keyword argument:
1713
+
1714
+ ```erb
1715
+ <!-- app/views/demos/index.html.erb -->
1716
+
1717
+ <%= ckeditor5_editor inline: true, initial_data: 'Document title', style: 'width: 600px' %>
1718
+ ```
1719
+
1720
+ This works for `:classic`, `:inline`, `:balloon` and `:decoupled` editors. For `:multiroot` editors, set `inline: true` per root using `ckeditor5_editable` instead — see [Inline roots](#inline-roots-single-line-editable-areas).
1721
+
1684
1722
  ### Watchdog 🐕
1685
1723
 
1686
1724
  CKEditor 5 uses a watchdog utility to protect you from data loss in case the editor crashes. It saves your content just before the crash and creates a new instance of the editor with your content intact. It's enabled by default in the gem.
@@ -1797,6 +1835,52 @@ If you want to use a multiroot editor, you can pass the `type` keyword argument
1797
1835
 
1798
1836
  Roots can be defined later to the editor by simply adding new elements rendered by `ckeditor5_editable` helper.
1799
1837
 
1838
+ ```ruby
1839
+ ckeditor5_editable(
1840
+ name = nil, # String - root identifier, matched against the editor's roots (required for multiroot editors)
1841
+ inline: false, # Boolean - use the $inlineRoot model element instead of $root (single-line areas, e.g. titles)
1842
+ initial_data: nil, # String - initial HTML content via the `initial-data` attribute (mutually exclusive with a block)
1843
+ **kwargs # any other keyword becomes an HTML attribute: style:, class:, id:, ...
1844
+ )
1845
+ # &block - initial HTML content for this root, provided as nested markup. Mutually exclusive with `initial_data`.
1846
+ ```
1847
+
1848
+ #### Inline roots (single-line editable areas)
1849
+
1850
+ Multiroot editors can expose *inline* roots — single-line editable areas (e.g. a title) that use the `$inlineRoot` model element instead of the default `$root`. Enable it by passing `inline: true` to `ckeditor5_editable`:
1851
+
1852
+ ```erb
1853
+ <!-- app/views/demos/index.html.erb -->
1854
+
1855
+ <%= ckeditor5_editor type: :multiroot, style: 'width: 600px' do %>
1856
+ <%= ckeditor5_toolbar %>
1857
+ <br>
1858
+ <%= ckeditor5_editable 'title', inline: true, style: 'border: 1px solid var(--ck-color-base-border);' %>
1859
+ <br>
1860
+ <%= ckeditor5_editable 'content', style: 'border: 1px solid var(--ck-color-base-border)' %>
1861
+ <% end %>
1862
+ ```
1863
+
1864
+ #### Setting initial content of a root
1865
+
1866
+ You can set the initial content of a specific root using the `initial_data` keyword argument, instead of passing it as the tag's content:
1867
+
1868
+ ```erb
1869
+ <!-- app/views/demos/index.html.erb -->
1870
+
1871
+ <%= ckeditor5_editable 'content', initial_data: '<p>Hello</p>' %>
1872
+ ```
1873
+
1874
+ This is equivalent to (and mutually exclusive with) passing the content as a block:
1875
+
1876
+ ```erb
1877
+ <!-- app/views/demos/index.html.erb -->
1878
+
1879
+ <%= ckeditor5_editable 'content' do %>
1880
+ <p>Hello</p>
1881
+ <% end %>
1882
+ ```
1883
+
1800
1884
  ### Inline editor 📝
1801
1885
 
1802
1886
  Inline editor allows you to create an editor that can be placed inside any element. Keep in mind that inline editor does not work with `textarea` elements so it might be not suitable for all use cases.
@@ -19,6 +19,9 @@ module CKEditor5::Rails
19
19
  # @param watchdog [Boolean] Enable/disable the editor crash recovery (default: true)
20
20
  # @param editable_height [Integer] Set fixed height for editor in pixels
21
21
  # @param language [Symbol] Set editor UI language (e.g. :pl, :es)
22
+ # @param inline [Boolean] Whether the (single) root should use the `$inlineRoot` model
23
+ # element instead of the default `$root`. Not applicable to `:multiroot` editors —
24
+ # use the `inline` argument of `ckeditor5_editable` per root instead.
22
25
  # @param html_attributes [Hash] Additional HTML attributes for editor element
23
26
  #
24
27
  # @example Basic usage with default preset
@@ -30,6 +33,9 @@ module CKEditor5::Rails
30
33
  # @example Inline editor with custom styling
31
34
  # <%= ckeditor5_editor type: :inline, style: 'width: 600px' %>
32
35
  #
36
+ # @example Classic editor with a single-line ($inlineRoot) editing area
37
+ # <%= ckeditor5_editor inline: true, style: 'width: 600px' %>
38
+ #
33
39
  # @example Multiroot editor with multiple editable areas
34
40
  # <%= ckeditor5_editor type: :multiroot do %>
35
41
  # <%= ckeditor5_toolbar %>
@@ -60,7 +66,7 @@ module CKEditor5::Rails
60
66
  preset: nil,
61
67
  config: nil, extra_config: {}, type: nil,
62
68
  initial_data: nil, watchdog: true,
63
- editable_height: nil, language: nil,
69
+ editable_height: nil, language: nil, inline: false,
64
70
  **html_attributes, &block
65
71
  )
66
72
  validate_editor_input!(initial_data, block)
@@ -68,7 +74,7 @@ module CKEditor5::Rails
68
74
  context = ckeditor5_context_or_fallback(preset)
69
75
 
70
76
  preset = Engine.find_preset!(preset || context[:preset] || :default)
71
- config = build_editor_config(preset, config, extra_config, initial_data)
77
+ config = build_editor_config(preset, config, extra_config, initial_data, inline: inline)
72
78
 
73
79
  type ||= preset.type
74
80
 
@@ -91,15 +97,30 @@ module CKEditor5::Rails
91
97
  # Creates an editable area for multiroot or decoupled editors.
92
98
  #
93
99
  # @param name [String] Identifier for the editable area
100
+ # @param inline [Boolean] Whether the root should use the `$inlineRoot` model element
101
+ # instead of the default `$root`. Useful for single-line editable areas (e.g. titles).
102
+ # @param initial_data [String] Initial HTML content for this editable area, passed via
103
+ # the `initial-data` attribute instead of the element's content. Cannot be used
104
+ # together with a block.
94
105
  # @param kwargs [Hash] HTML attributes for the editable element
95
106
  #
96
107
  # @example Creating a named editable area in multiroot editor
97
108
  # <%= ckeditor5_editable 'content', style: 'border: 1px solid gray' %>
98
- def ckeditor5_editable(name = nil, **kwargs, &block)
109
+ #
110
+ # @example Creating an inline editable area (e.g. a title) in multiroot editor
111
+ # <%= ckeditor5_editable 'title', inline: true %>
112
+ #
113
+ # @example Setting initial content using the initial_data argument
114
+ # <%= ckeditor5_editable 'content', initial_data: '<p>Hello</p>' %>
115
+ def ckeditor5_editable(name = nil, inline: false, initial_data: nil, **kwargs, &block)
116
+ raise ArgumentError, 'Cannot pass initial data and block at the same time.' if initial_data && block
117
+
99
118
  tag.public_send(
100
119
  :'ckeditor-editable-component',
101
120
  'data-turbo-temporary': true,
102
121
  name: name,
122
+ inline: inline,
123
+ 'initial-data': initial_data,
103
124
  **kwargs, &block
104
125
  )
105
126
  end
@@ -159,11 +180,18 @@ module CKEditor5::Rails
159
180
  # @param config [Hash] Custom configuration that overrides preset config
160
181
  # @param extra_config [Hash] Additional configuration to merge
161
182
  # @param initial_data [String] Initial content for the editor
183
+ # @param inline [Boolean] Whether the root should use the `$inlineRoot` model element
162
184
  # @return [Hash] The merged configuration hash
163
- def build_editor_config(preset, config, extra_config, initial_data)
185
+ def build_editor_config(preset, config, extra_config, initial_data, inline: false)
164
186
  editor_config = config || preset.config
165
187
  editor_config = editor_config.deep_merge(extra_config)
166
- editor_config[:initialData] = initial_data if initial_data
188
+
189
+ if initial_data || inline
190
+ editor_config[:root] = (editor_config[:root] || {}).merge(
191
+ **(initial_data ? { initialData: initial_data } : {}),
192
+ **(inline ? { modelElement: '$inlineRoot' } : {})
193
+ )
194
+ end
167
195
 
168
196
  if preset.automatic_upgrades? && editor_config[:version].present?
169
197
  detected_version = VersionDetector.latest_safe_version(editor_config[:version])
@@ -2,8 +2,8 @@
2
2
 
3
3
  module CKEditor5
4
4
  module Rails
5
- VERSION = '1.36.5'
5
+ VERSION = '1.37.0'
6
6
 
7
- DEFAULT_CKEDITOR_VERSION = '48.2.0'
7
+ DEFAULT_CKEDITOR_VERSION = '48.3.0'
8
8
  end
9
9
  end
@@ -1,2 +1,2 @@
1
- "use strict";var C=Object.create;var w=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var x=Object.getPrototypeOf,S=Object.prototype.hasOwnProperty;var k=(s,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of P(t))!S.call(s,n)&&n!==e&&w(s,n,{get:()=>t[n],enumerable:!(i=A(t,n))||i.enumerable});return s};var g=(s,t,e)=>(e=s!=null?C(x(s)):{},k(t||!s||!s.__esModule?w(e,"default",{value:s,enumerable:!0}):e,s));function p(s){switch(document.readyState){case"loading":document.addEventListener("DOMContentLoaded",s,{once:!0});break;case"interactive":case"complete":setTimeout(s,0);break;default:console.warn("Unexpected document.readyState:",document.readyState),setTimeout(s,0)}}const b=new Map;function R(s){if(b.has(s))return b.get(s);const t=new Promise((e,i)=>{const n=document.createElement("script");n.src=s,n.onload=e,n.onerror=i,document.head.appendChild(n)});return b.set(s,t),t}function M(s){return typeof s=="string"&&s!=="__proto__"&&s!=="constructor"&&s!=="prototype"}function $(s){return Array.from(document.styleSheets).some(t=>t.href===s||t.href===new URL(s,window.location.href).href)}function E(s=[]){const t=s.map(e=>new Promise((i,n)=>{if($(e)){i();return}const r=document.createElement("link");r.rel="stylesheet",r.href=e,r.onerror=n,r.onload=()=>i(),document.head.appendChild(r)}));return Promise.all(t)}function h(s=[]){const t=async({url:i,import_name:n,import_as:r,window_name:o,stylesheets:a})=>{if(a?.length&&await E(a),o){let d=function(){return Object.prototype.hasOwnProperty.call(window,o)};if(i&&!d()&&await R(i),d()||window.dispatchEvent(new CustomEvent(`ckeditor:request-cjs-plugin:${o}`)),!d())throw new Error(`Plugin window['${o}'] not found in global scope. Please ensure the plugin is loaded before CKEditor initialization.`);return window[o]}const c=await import(n),l=c[r||"default"];if(!l)throw new Error(`Plugin "${r||"default"}" not found in the ESM module "${n}"! Available imports: ${Object.keys(c).join(", ")}! Consider changing "import_as" value.`);return l};function e(i){return t(typeof i=="string"?{import_name:"ckeditor5",import_as:i}:i)}return Promise.all(s.map(e))}function u(s){if(!s||typeof s!="object")return s;if(Array.isArray(s))return s.map(i=>u(i));const t=s;if(t.$element&&typeof t.$element=="string"){const i=document.querySelector(t.$element);return i||console.warn(`Element not found for selector: ${t.$element}`),i||null}const e=Object.create(null);for(const[i,n]of Object.entries(s))e[i]=u(n);return e}function I(){return Math.random().toString(36).substring(2)}class T extends HTMLElement{instance=null;instancePromise=Promise.withResolvers();#e=new Set;static get observedAttributes(){return["plugins","config"]}async connectedCallback(){try{p(()=>this.#t())}catch(t){console.error("Failed to initialize context:",t),this.dispatchEvent(new CustomEvent("context-error",{detail:t}))}}async attributeChangedCallback(t,e,i){e!==null&&e!==i&&await this.#t()}async disconnectedCallback(){this.instance&&(await this.instance.destroy(),this.instance=null)}registerEditor(t){this.#e.add(t)}unregisterEditor(t){this.#e.delete(t)}async#t(){this.instance&&(this.instancePromise=Promise.withResolvers(),await this.instance.destroy(),this.instance=null),window.dispatchEvent(new CustomEvent("ckeditor:context:attach:before",{detail:{element:this}}));const{Context:t,ContextWatchdog:e}=await import("ckeditor5"),i=await this.#i(),n=this.#s();window.dispatchEvent(new CustomEvent("ckeditor:context:attach",{detail:{config:n,element:this}})),this.instance=new e(t,{crashNumberLimit:10}),await this.instance.create({...n,plugins:i}),this.instance.on("itemError",(...r)=>{console.error("Context item error:",...r)}),this.instancePromise.resolve(this.instance),this.dispatchEvent(new CustomEvent("context-ready",{detail:this.instance})),await Promise.all([...this.#e].map(r=>r.reinitializeEditor()))}async#i(){const t=this.getAttribute("plugins");return h(t?JSON.parse(t):[])}#s(){const t=JSON.parse(this.getAttribute("config")||"{}");return u(t)}}customElements.define("ckeditor-context-component",T);class L extends HTMLElement{static get observedAttributes(){return["name"]}get name(){return this.getAttribute("name")||"editable"}get editableElement(){return this.querySelector("div")}connectedCallback(){p(()=>{const t=this.#e();if(!t)throw new Error("ckeditor-editable-component must be a child of ckeditor-component");if(this.innerHTML=`<div>${this.innerHTML}</div>`,this.style.display="block",t.isDecoupled())t.runAfterEditorReady(e=>{this.appendChild(e.ui.view[this.name].element)});else if(t.editables){if(!this.name)throw new Error('Editable component missing required "name" attribute');t.editables[this.name]=this}})}attributeChangedCallback(t,e,i){if(e!==i)if(t==="name"){if(!e)return;const n=this.#e();n?.editables?.[e]&&(n.editables[i]=n.editables[e],delete n.editables[e])}else this.editableElement.setAttribute(t,i)}disconnectedCallback(){const t=this.#e();t?.editables&&delete t.editables[this.name]}#e(){return this.closest("ckeditor-component")||document.body.querySelector("ckeditor-component")}}customElements.define("ckeditor-editable-component",L);class y{#e;#t;constructor(t,e={}){return this.#e=t,this.#t=e,new Proxy(this,{get(i,n){return typeof i[n]=="function"?i[n].bind(i):i.#t[n]},set(i,n,r){return i.#t[n]!==r&&(i.attachRoot(n,r),i.#t[n]=r),!0},deleteProperty(i,n){return i.detachRoot(n),delete i.#t[n],!0}})}async attachRoot(t,e){return await this.detachRoot(t),this.#e.runAfterEditorReady(i=>{const{ui:n,editing:r,model:o}=i;i.addRoot(t,{isUndoable:!1,data:e.innerHTML});const a=o.document.getRoot(t);n.getEditableElement(t)&&i.detachEditable(a);const c=n.view.createEditable(t,e);n.addEditable(c),r.view.forceRender()})}async detachRoot(t){return this.#e.runAfterEditorReady(e=>{const i=e.model.document.getRoot(t);i&&(e.detachEditable(i),e.detachRoot(t,!0))})}getAll(){return this.#t}}class m extends HTMLElement{instancePromise=Promise.withResolvers();watchdog=null;instance=null;editables=Object.create({});#e="";#t=null;#i=null;#s=null;static get observedAttributes(){return["config","plugins","translations","type"]}static get inputAttributes(){return["name","required","value"]}get oneditorchange(){return this.#r("editorchange")}set oneditorchange(t){this.#o("editorchange",t)}get oneditorready(){return this.#r("editorready")}set oneditorready(t){this.#o("editorready",t)}get oneditorerror(){return this.#r("editorerror")}set oneditorerror(t){this.#o("editorerror",t)}#r(t){if(this.hasAttribute(`on${t}`)){const e=this.getAttribute(`on${t}`);if(!M(e))throw new Error(`Unsafe event handler attribute value: ${e}`);return window[e]||new Function("event",e)}return this[`#${t}Handler`]}#o(t,e){typeof e=="string"?this.setAttribute(`on${t}`,e):(this.removeAttribute(`on${t}`),this[`#${t}Handler`]=e)}connectedCallback(){this.#t=this.closest("ckeditor-context-component"),this.#e=this.innerHTML;try{p(async()=>{this.#t&&(await this.#t.instancePromise.promise,this.#t.registerEditor(this)),await this.reinitializeEditor()})}catch(t){console.error("Failed to initialize editor:",t);const e=new CustomEvent("editor-error",{detail:t});this.dispatchEvent(e),this.oneditorerror?.(e)}}async attributeChangedCallback(t,e,i){e!==null&&e!==i&&m.observedAttributes.includes(t)&&this.isConnected&&await this.reinitializeEditor()}async disconnectedCallback(){this.#t&&this.#t.unregisterEditor(this);try{await this.#d()}catch(t){console.error("Failed to destroy editor:",t)}}runAfterEditorReady(t){return this.instance?Promise.resolve(t(this.instance)):this.instancePromise.promise.then(t)}get#n(){return this.getAttribute("type")==="ClassicEditor"?"textarea":"div"}get#a(){return this.#t?.instance}async#d(){this.#i&&await this.#a.remove(this.#i),await this.instance?.destroy(),this.watchdog?.destroy()}#l(){const t=JSON.parse(this.getAttribute("config")||"{}");return u(t)}async#u(t){await Promise.all([this.#b(),this.#w()]);let e=t;t instanceof y?e=t.getAll():typeof t!="string"&&(e=t.main);const i={...e instanceof HTMLElement&&{element:e},...typeof e=="string"&&{data:e},...e instanceof Object&&{editables:e}};window.dispatchEvent(new CustomEvent("ckeditor:attach:before",{detail:i}));const n=await this.#v(),[r,o]=await Promise.all([this.#E(),this.#y()]),a={...this.#l(),...o.length&&{translations:o},plugins:r};window.dispatchEvent(new CustomEvent("ckeditor:attach",{detail:{config:a,...i}}));let c=null,l=null,d=null;if(this.#t)d=I(),await this.#a.add({creator:(f,v)=>n.create(f,v),id:d,sourceElementOrData:e,type:"editor",config:a}),l=this.#a.getItem(d);else if(this.hasWatchdog()){const{EditorWatchdog:f}=await import("ckeditor5");c=new f(n),await c.create(e,a),l=c.editor}else l=await n.create(e,a);return{contextId:d,instance:l,watchdog:c}}async reinitializeEditor(){this.instance&&(this.instancePromise=Promise.withResolvers(),await this.#d(),this.instance=null),this.style.display="block",!this.isMultiroot()&&!this.isDecoupled()&&(this.innerHTML=`<${this.#n}>${this.#e}</${this.#n}>`,this.#p()),this.isMultiroot()?this.editables=new y(this,this.#h()):this.isDecoupled()?this.editables=null:this.editables=this.#h();try{const{watchdog:t,instance:e,contextId:i}=await this.#u(this.editables||this.#l().initialData||"");this.watchdog=t,this.instance=e,this.#i=i,this.#f(),this.#g(),this.#m(),this.instancePromise.resolve(this.instance);const n=new CustomEvent("editor-ready",{detail:this.instance});this.dispatchEvent(n),this.oneditorready?.(n)}catch(t){throw this.instancePromise.reject(t),t}}#m(){const t=i=>this.instance.getData({rootName:i}),e=()=>this.instance?.model.document.getRootNames().reduce((i,n)=>({...i,[n]:t(n)}),{});this.instance?.model.document.on("change:data",()=>{const i=new CustomEvent("editor-change",{detail:{editor:this.instance,data:e()},bubbles:!0});this.dispatchEvent(i),this.oneditorchange?.(i)})}isClassic(){return this.getAttribute("type")==="ClassicEditor"}isBallon(){return this.getAttribute("type")==="BalloonEditor"}isMultiroot(){return this.getAttribute("type")==="MultiRootEditor"}isDecoupled(){return this.getAttribute("type")==="DecoupledEditor"}hasWatchdog(){return this.getAttribute("watchdog")==="true"}#h(){if(this.isDecoupled())return{};if(this.isMultiroot())return[...this.querySelectorAll("ckeditor-editable-component")].reduce((i,n)=>{if(!n.name)throw new Error('Editable component missing required "name" attribute');return i[n.name]=n,i},Object.create(null));const t=this.querySelector(this.#n);if(!t)throw new Error(`No ${this.#n} element found`);return{main:t}}#p(){const t=this.querySelector("textarea");if(t)for(const e of m.inputAttributes)this.hasAttribute(e)&&t.setAttribute(e,this.getAttribute(e))}#f(){if(!this.instance)return;const t=this.querySelector("textarea");if(!t)return;const e=()=>{this.style.position="relative",t.innerHTML="",t.value=this.instance.getData(),t.tabIndex=-1,Object.assign(t.style,{display:"flex",position:"absolute",bottom:"0",left:"50%",width:"1px",height:"1px",opacity:"0",pointerEvents:"none",margin:"0",padding:"0",border:"none"})};e(),this.instance.model.document.on("change:data",()=>{t.dispatchEvent(new Event("input",{bubbles:!0})),t.dispatchEvent(new Event("change",{bubbles:!0})),e()})}#g(){if(!this.isClassic()&&!this.isBallon())return;const{instance:t}=this,e=Number.parseInt(this.getAttribute("editable-height"),10);Number.isNaN(e)||t.editing.view.change(i=>{i.setStyle("height",`${e}px`,t.editing.view.document.getRoot())})}#c(){return this.#s||=JSON.parse(this.getAttribute("bundle"))}async#b(){await E(this.#c()?.stylesheets||[])}async#w(){const t=(this.#c()?.scripts||[]).filter(e=>!!e.window_name);await h(t)}async#y(){const t=this.#c()?.scripts.filter(e=>e.translation);return h(t)}async#E(){const t=this.getAttribute("plugins"),i=(t?JSON.parse(t):[]).map(n=>typeof n=="string"?{import_name:"ckeditor5",import_as:n}:n);return h(i)}async#v(){const t=await import("ckeditor5"),e=this.getAttribute("type");if(!e||!Object.prototype.hasOwnProperty.call(t,e))throw new Error(`Invalid editor type: ${e}`);return t[e]}}customElements.define("ckeditor-component",m);class O extends HTMLElement{connectedCallback(){p(async()=>{const t=this.getAttribute("name"),e=await this.#e().instancePromise.promise;this.appendChild(e.ui.view[t].element)})}#e(){return this.closest("ckeditor-component")||document.body.querySelector("ckeditor-component")}}customElements.define("ckeditor-ui-part-component",O);
1
+ "use strict";var C=Object.create;var w=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var x=Object.getPrototypeOf,S=Object.prototype.hasOwnProperty;var R=(e,t,i,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of P(t))!S.call(e,s)&&s!==i&&w(e,s,{get:()=>t[s],enumerable:!(n=A(t,s))||n.enumerable});return e};var E=(e,t,i)=>(i=e!=null?C(x(e)):{},R(t||!e||!e.__esModule?w(i,"default",{value:e,enumerable:!0}):i,e));function f(e){switch(document.readyState){case"loading":document.addEventListener("DOMContentLoaded",e,{once:!0});break;case"interactive":case"complete":setTimeout(e,0);break;default:console.warn("Unexpected document.readyState:",document.readyState),setTimeout(e,0)}}const b=new Map;function M(e){if(b.has(e))return b.get(e);const t=new Promise((i,n)=>{const s=document.createElement("script");s.src=e,s.onload=i,s.onerror=n,document.head.appendChild(s)});return b.set(e,t),t}function k(e){return typeof e=="string"&&e!=="__proto__"&&e!=="constructor"&&e!=="prototype"}function D(e){return Array.from(document.styleSheets).some(t=>t.href===e||t.href===new URL(e,window.location.href).href)}function y(e=[]){const t=e.map(i=>new Promise((n,s)=>{if(D(i)){n();return}const r=document.createElement("link");r.rel="stylesheet",r.href=i,r.onerror=s,r.onload=()=>n(),document.head.appendChild(r)}));return Promise.all(t)}function h(e=[]){const t=async({url:n,import_name:s,import_as:r,window_name:c,stylesheets:d})=>{if(d?.length&&await y(d),c){let a=function(){return Object.prototype.hasOwnProperty.call(window,c)};if(n&&!a()&&await M(n),a()||window.dispatchEvent(new CustomEvent(`ckeditor:request-cjs-plugin:${c}`)),!a())throw new Error(`Plugin window['${c}'] not found in global scope. Please ensure the plugin is loaded before CKEditor initialization.`);return window[c]}const o=await import(s),l=o[r||"default"];if(!l)throw new Error(`Plugin "${r||"default"}" not found in the ESM module "${s}"! Available imports: ${Object.keys(o).join(", ")}! Consider changing "import_as" value.`);return l};function i(n){return t(typeof n=="string"?{import_name:"ckeditor5",import_as:n}:n)}return Promise.all(e.map(i))}function m(e){if(!e||typeof e!="object")return e;if(Array.isArray(e))return e.map(n=>m(n));const t=e;if(t.$element&&typeof t.$element=="string"){const n=document.querySelector(t.$element);return n||console.warn(`Element not found for selector: ${t.$element}`),n||null}const i=Object.create(null);for(const[n,s]of Object.entries(e))i[n]=m(s);return i}function $(){return Math.random().toString(36).substring(2)}class H extends HTMLElement{instance=null;instancePromise=Promise.withResolvers();#e=new Set;static get observedAttributes(){return["plugins","config"]}async connectedCallback(){try{f(()=>this.#t())}catch(t){console.error("Failed to initialize context:",t),this.dispatchEvent(new CustomEvent("context-error",{detail:t}))}}async attributeChangedCallback(t,i,n){i!==null&&i!==n&&await this.#t()}async disconnectedCallback(){this.instance&&(await this.instance.destroy(),this.instance=null)}registerEditor(t){this.#e.add(t)}unregisterEditor(t){this.#e.delete(t)}async#t(){this.instance&&(this.instancePromise=Promise.withResolvers(),await this.instance.destroy(),this.instance=null),window.dispatchEvent(new CustomEvent("ckeditor:context:attach:before",{detail:{element:this}}));const{Context:t,ContextWatchdog:i}=await import("ckeditor5"),n=await this.#i(),s=this.#s();window.dispatchEvent(new CustomEvent("ckeditor:context:attach",{detail:{config:s,element:this}})),this.instance=new i(t,{crashNumberLimit:10}),await this.instance.create({...s,plugins:n}),this.instance.on("itemError",(...r)=>{console.error("Context item error:",...r)}),this.instancePromise.resolve(this.instance),this.dispatchEvent(new CustomEvent("context-ready",{detail:this.instance})),await Promise.all([...this.#e].map(r=>r.reinitializeEditor()))}async#i(){const t=this.getAttribute("plugins");return h(t?JSON.parse(t):[])}#s(){const t=JSON.parse(this.getAttribute("config")||"{}");return m(t)}}customElements.define("ckeditor-context-component",H);class T extends HTMLElement{get name(){return this.getAttribute("name")||"editable"}get editableElement(){return this.querySelector("div")}get modelElement(){return this.getAttribute("inline")==="true"?"$inlineRoot":void 0}get initialData(){return this.hasAttribute("initial-data")?this.getAttribute("initial-data"):void 0}connectedCallback(){f(()=>{const t=this.#e();if(!t)throw new Error("ckeditor-editable-component must be a child of ckeditor-component");if(this.innerHTML=`<div>${this.innerHTML}</div>`,this.style.display="block",t.isDecoupled())t.runAfterEditorReady(i=>{this.appendChild(i.ui.view[this.name].element)});else if(t.editables){if(!this.name)throw new Error('Editable component missing required "name" attribute');t.editables[this.name]=this}})}disconnectedCallback(){const t=this.#e();t?.editables&&delete t.editables[this.name]}#e(){return this.closest("ckeditor-component")||document.body.querySelector("ckeditor-component")}}customElements.define("ckeditor-editable-component",T);class v{#e;#t;constructor(t,i={}){return this.#e=t,this.#t=i,new Proxy(this,{get(n,s){return typeof n[s]=="function"?n[s].bind(n):n.#t[s]},set(n,s,r){return n.#t[s]!==r&&(n.attachRoot(s,r),n.#t[s]=r),!0},deleteProperty(n,s){return n.detachRoot(s),delete n.#t[s],!0}})}async attachRoot(t,i){return await this.detachRoot(t),this.#e.runAfterEditorReady(n=>{const{ui:s,editing:r,model:c}=n,d=i.modelElement,o=i.initialData;n.addRoot(t,{isUndoable:!1,initialData:o??i.innerHTML,...d&&{modelElement:d}});const l=c.document.getRoot(t);s.getEditableElement(t)&&n.detachEditable(l);const a=s.view.createEditable(t,i);s.addEditable(a),r.view.forceRender()})}async detachRoot(t){return this.#e.runAfterEditorReady(i=>{const n=i.model.document.getRoot(t);n&&(i.detachEditable(n),i.detachRoot(t,!0))})}getAll(){return this.#t}}class p extends HTMLElement{instancePromise=Promise.withResolvers();watchdog=null;instance=null;editables=Object.create(null);#e="";#t=null;#i=null;#s=null;static get observedAttributes(){return["config","plugins","translations","type"]}static get inputAttributes(){return["name","required","value"]}get oneditorchange(){return this.#r("editorchange")}set oneditorchange(t){this.#o("editorchange",t)}get oneditorready(){return this.#r("editorready")}set oneditorready(t){this.#o("editorready",t)}get oneditorerror(){return this.#r("editorerror")}set oneditorerror(t){this.#o("editorerror",t)}#r(t){return this.hasAttribute(`on${t}`)?G(this.getAttribute(`on${t}`)):this[`#${t}Handler`]}#o(t,i){typeof i=="string"?this.setAttribute(`on${t}`,i):(this.removeAttribute(`on${t}`),this[`#${t}Handler`]=i)}connectedCallback(){this.#t=this.closest("ckeditor-context-component"),this.#e=this.innerHTML;try{f(async()=>{this.#t&&(await this.#t.instancePromise.promise,this.#t.registerEditor(this)),await this.reinitializeEditor()})}catch(t){console.error("Failed to initialize editor:",t);const i=new CustomEvent("editor-error",{detail:t});this.dispatchEvent(i),this.oneditorerror?.(i)}}async attributeChangedCallback(t,i,n){i!==null&&i!==n&&p.observedAttributes.includes(t)&&this.isConnected&&await this.reinitializeEditor()}async disconnectedCallback(){this.#t&&this.#t.unregisterEditor(this);try{await this.#l()}catch(t){console.error("Failed to destroy editor:",t)}}runAfterEditorReady(t){return this.instance?Promise.resolve(t(this.instance)):this.instancePromise.promise.then(t)}get#n(){return L(this.getAttribute("type"))}get#a(){return this.#t?.instance}async#l(){this.#i&&await this.#a.remove(this.#i),await this.instance?.destroy(),this.watchdog?.destroy()}#d(){return I(this.getAttribute("config"))}async#h(t){await Promise.all([this.#E(),this.#b()]);const{isMultiroot:i,content:n}=O(t),s=q(n,i);window.dispatchEvent(new CustomEvent("ckeditor:attach:before",{detail:s}));const r=await this.#v(),[c,d]=await Promise.all([this.#y(),this.#w()]),o={...this.#d(),...d.length&&{translations:d},plugins:c};j(o,n,i,this.isClassic()),window.dispatchEvent(new CustomEvent("ckeditor:attach",{detail:{config:o,...s}}));let l=null,a=null,u=null;if(this.#t)u=$(),await this.#a.add({creator:g=>r.create(g),id:u,type:"editor",config:o}),a=this.#a.getItem(u);else if(this.hasWatchdog()){const{EditorWatchdog:g}=await import("ckeditor5");l=new g(r),await l.create(o),a=l.editor}else a=await r.create(o);return{contextId:u,instance:a,watchdog:l}}async reinitializeEditor(){this.instance&&(this.instancePromise=Promise.withResolvers(),await this.#l(),this.instance=null),this.style.display="block",!this.isMultiroot()&&!this.isDecoupled()&&(this.innerHTML=`<${this.#n}>${this.#e}</${this.#n}>`,this.#p()),this.isMultiroot()?this.editables=new v(this,this.#u()):this.isDecoupled()?this.editables=null:this.editables=this.#u();try{const{watchdog:t,instance:i,contextId:n}=await this.#h(this.editables||this.#d().root?.initialData||"");this.watchdog=t,this.instance=i,this.#i=n,this.#f(),this.#g(),this.#m(),this.instancePromise.resolve(this.instance);const s=new CustomEvent("editor-ready",{detail:this.instance});this.dispatchEvent(s),this.oneditorready?.(s)}catch(t){throw this.instancePromise.reject(t),t}}#m(){const t=n=>this.instance.getData({rootName:n}),i=()=>this.instance?.model.document.getRootNames().reduce((n,s)=>({...n,[s]:t(s)}),{});this.instance?.model.document.on("change:data",()=>{const n=new CustomEvent("editor-change",{detail:{editor:this.instance,data:i()},bubbles:!0});this.dispatchEvent(n),this.oneditorchange?.(n)})}isClassic(){return this.getAttribute("type")==="ClassicEditor"}isBallon(){return this.getAttribute("type")==="BalloonEditor"}isMultiroot(){return this.getAttribute("type")==="MultiRootEditor"}isDecoupled(){return this.getAttribute("type")==="DecoupledEditor"}hasWatchdog(){return this.getAttribute("watchdog")==="true"}#u(){if(this.isDecoupled())return{};if(this.isMultiroot()){const i=[...this.querySelectorAll("ckeditor-editable-component")];return z(i)}const t=this.querySelector(this.#n);if(!t)throw new Error(`No ${this.#n} element found`);return{main:t}}#p(){const t=this.querySelector("textarea");t&&B(this,t,p.inputAttributes)}#f(){if(!this.instance)return;const t=this.querySelector("textarea");if(!t)return;const i=()=>{this.style.position="relative",t.innerHTML="",t.value=this.instance.getData(),K(t)};i(),this.instance.model.document.on("change:data",()=>{t.dispatchEvent(new Event("input",{bubbles:!0})),t.dispatchEvent(new Event("change",{bubbles:!0})),i()})}#g(){if(!this.isClassic()&&!this.isBallon())return;const t=W(this.getAttribute("editable-height"));t!==null&&J(this.instance,t)}#c(){return this.#s||=U(this.getAttribute("bundle"))}async#E(){await y(this.#c()?.stylesheets||[])}async#b(){const t=(this.#c()?.scripts||[]).filter(i=>!!i.window_name);await h(t)}async#w(){const t=this.#c()?.scripts.filter(i=>i.translation);return h(t)}async#y(){return h(_(this.getAttribute("plugins")))}async#v(){const t=await import("ckeditor5");return F(t,this.getAttribute("type"))}}function L(e){return e==="ClassicEditor"?"textarea":"div"}function I(e){const t=JSON.parse(e||"{}");return m(t)}function O(e){const t=e instanceof v;let i=e;return t?i=e.getAll():typeof e!="string"&&(i=e.main),{isMultiroot:t,content:i}}function q(e,t){return{...e instanceof HTMLElement&&{element:e},...typeof e=="string"&&{data:e},...t&&{editables:e}}}function j(e,t,i,n){i?e.roots=N(t):n&&t instanceof HTMLElement?e.attachTo=t:t instanceof HTMLElement?e.root={...e.root,element:t}:typeof t=="string"&&(e.root={...e.root,initialData:t})}function N(e){return Object.fromEntries(Object.entries(e).map(([t,i])=>{const n=i.modelElement,s=i.initialData;return[t,{element:i,initialData:s??i.innerHTML,...n&&{modelElement:n}}]}))}function z(e){return e.reduce((t,i)=>{if(!i.name)throw new Error('Editable component missing required "name" attribute');return t[i.name]=i,t},Object.create(null))}function B(e,t,i){for(const n of i)e.hasAttribute(n)&&t.setAttribute(n,e.getAttribute(n))}function K(e){e.tabIndex=-1,Object.assign(e.style,{display:"flex",position:"absolute",bottom:"0",left:"50%",width:"1px",height:"1px",opacity:"0",pointerEvents:"none",margin:"0",padding:"0",border:"none"})}function W(e){if(e===null)return null;const t=Number.parseInt(e,10);return Number.isNaN(t)?null:t}function J(e,t){e.editing.view.change(i=>{i.setStyle("height",`${t}px`,e.editing.view.document.getRoot())})}function U(e){return JSON.parse(e)}function _(e){return(e?JSON.parse(e):[]).map(i=>typeof i=="string"?{import_name:"ckeditor5",import_as:i}:i)}function F(e,t){if(!t||!Object.prototype.hasOwnProperty.call(e,t))throw new Error(`Invalid editor type: ${t}`);return e[t]}function G(e){if(!k(e))throw new Error(`Unsafe event handler attribute value: ${e}`);return window[e]||new Function("event",e)}customElements.define("ckeditor-component",p);class Q extends HTMLElement{connectedCallback(){f(async()=>{const t=this.getAttribute("name"),i=await this.#e().instancePromise.promise;this.appendChild(i.ui.view[t].element)})}#e(){return this.closest("ckeditor-component")||document.body.querySelector("ckeditor-component")}}customElements.define("ckeditor-ui-part-component",Q);
2
2
  //# sourceMappingURL=index.cjs.map