` component locally
-
-If you do not want to enable the CKEditor 5 component globally, you can import the `Ckeditor` component from the `@ckeditor/ckeditor5-vue` package directly into the Vue component where you want to use it, and add it to the `components` object.
+Now, you can import and use the `Editor.vue` component anywhere in your application.
```html
-
-
-
+
+```
-
+```html
+
+
+
+
+
```
## Component directives
@@ -157,24 +100,12 @@ This directive specifies the editor to be used by the component. It must directl
```html
-
-
-
+
-
```
@@ -196,36 +127,27 @@ A [standard directive](https://v3.vuejs.org/guide/component-basics.html#using-v-
```html
-
-
-
+
+
-
Editor data
- {{ editorData }}
-
+ Editor data
+ {{ data }}
-
```
-In the above example, the `editorData` property will be updated automatically as the user types and the content changes. It can also be used to change (as in `emptyEditor()`) or set the initial content of the editor.
+In the above example, the `data` property will be updated automatically as the user types and the content changes. It can also be used to change (as in `emptyEditor()`) or set the initial content of the editor.
If you only want to execute an action when the editor data changes, use the [`input`](#input) event.
@@ -235,23 +157,15 @@ Allows a one–way data binding that sets the content of the editor. Unlike [`v-
```html
-
-
-
+
-
```
@@ -263,25 +177,19 @@ Specifies the {@link module:core/editor/editorconfig~EditorConfig configuration}
```html
-
-
-
+
-
```
@@ -293,24 +201,15 @@ It sets the initial read–only state of the editor and changes it during its li
```html
-
-
-
+
-
```
@@ -380,35 +279,22 @@ Since accessing the editor toolbar is not possible until after the editor instan
```html
-
-
-
+
-
```
@@ -425,40 +311,28 @@ It is not mandatory to build applications on top of the above sample, however, i
CKEditor 5 supports {@link getting-started/setup/ui-language multiple UI languages}, and so does the official Vue component. Follow the instructions below to translate CKEditor 5 in your Vue application.
-Similarly to CSS style sheets, both packages have separate translations. Import them as shown in the example below. Then, pass them to the `translations` array inside the `editorConfig` prop in the component:
+Similarly to CSS style sheets, both packages have separate translations. Import them as shown in the example below. Then, pass them to the `translations` array inside the `config` prop in the component:
```html
-
-
-
+
-
```
diff --git a/docs/getting-started/legacy-getting-started/integrations/overview.md b/docs/getting-started/legacy-getting-started/integrations/overview.md
index 2119538b012..aabb42af2d2 100644
--- a/docs/getting-started/legacy-getting-started/integrations/overview.md
+++ b/docs/getting-started/legacy-getting-started/integrations/overview.md
@@ -19,7 +19,7 @@ CKEditor 5 is framework agnostic and can be integrated with any JavaScript
There are four official integrations:
* {@link getting-started/integrations/angular CKEditor 5 rich-text editor for Angular}
-* {@link getting-started/integrations/react CKEditor 5 rich-text editor for React}
+* {@link getting-started/integrations/react-default-npm CKEditor 5 rich-text editor for React}
* {@link getting-started/integrations/vuejs-v2 CKEditor 5 rich-text editor for Vue.js 2.x}
* {@link getting-started/integrations/vuejs-v3 CKEditor 5 rich-text editor for Vue.js 3.x}
diff --git a/docs/getting-started/legacy-getting-started/integrations/react.md b/docs/getting-started/legacy-getting-started/integrations/react.md
index 14193ab7a0c..aa8d6a36d20 100644
--- a/docs/getting-started/legacy-getting-started/integrations/react.md
+++ b/docs/getting-started/legacy-getting-started/integrations/react.md
@@ -10,7 +10,7 @@ order: 30
# (Legacy) React rich text editor component
- ⚠️ We changed installation methods and this legacy guide is kept for users' convenience. If you are looking for current CKEditor 5 React integration, please refer to the newest version of the {@link getting-started/integrations/react CKEditor 5 integration} guide.
+ ⚠️ We changed installation methods and this legacy guide is kept for users' convenience. If you are looking for current CKEditor 5 React integration, please refer to the newest version of the {@link getting-started/integrations/react-default-npm CKEditor 5 integration} guide.
diff --git a/docs/getting-started/legacy-getting-started/integrations/vuejs-v2.md b/docs/getting-started/legacy-getting-started/integrations/vuejs-v2.md
index c313713c4ff..aa54ee136b1 100644
--- a/docs/getting-started/legacy-getting-started/integrations/vuejs-v2.md
+++ b/docs/getting-started/legacy-getting-started/integrations/vuejs-v2.md
@@ -26,7 +26,7 @@ The easiest way to use CKEditor 5 in your Vue.js application is by choosing
Additionally, you can [integrate CKEditor 5 from source](#using-ckeditor-from-source) which is a much more flexible and powerful solution, but requires some additional configuration.
- The {@link features/watchdog watchdog feature} is available for the {@link getting-started/integrations/react React} and {@link getting-started/integrations/angular Angular} integrations, but is not supported in Vue yet.
+ The {@link features/watchdog watchdog feature} is available for the {@link getting-started/integrations/react-default-npm React} and {@link getting-started/integrations/angular Angular} integrations, but is not supported in Vue yet.
## Quick start
diff --git a/docs/getting-started/legacy-getting-started/integrations/vuejs-v3.md b/docs/getting-started/legacy-getting-started/integrations/vuejs-v3.md
index 002e80b78a8..dadbe1a5f5b 100644
--- a/docs/getting-started/legacy-getting-started/integrations/vuejs-v3.md
+++ b/docs/getting-started/legacy-getting-started/integrations/vuejs-v3.md
@@ -26,7 +26,7 @@ The easiest way to use CKEditor 5 in your Vue.js application is by choosing
Additionally, you can [integrate CKEditor 5 from source](#using-ckeditor-5-from-source) which is a much more flexible and powerful solution, but requires some additional configuration.
- The {@link features/watchdog watchdog feature} is available for the {@link getting-started/integrations/react React} and {@link getting-started/integrations/angular Angular} integrations, but is not supported in Vue yet.
+ The {@link features/watchdog watchdog feature} is available for the {@link getting-started/integrations/react-default-npm React} and {@link getting-started/integrations/angular Angular} integrations, but is not supported in Vue yet.
diff --git a/docs/getting-started/legacy-getting-started/predefined-builds.md b/docs/getting-started/legacy-getting-started/predefined-builds.md
index 2fcbcf09186..4bd5681144d 100644
--- a/docs/getting-started/legacy-getting-started/predefined-builds.md
+++ b/docs/getting-started/legacy-getting-started/predefined-builds.md
@@ -507,11 +507,10 @@ MultiRootEditor
// Editor configration:
{
cloudServices: {
- // All predefined builds include the Easy Image feature.
+ // All predefined builds included the Easy Image feature.
// Provide correct configuration values to use it.
tokenUrl: 'https://example.com/cs-token-endpoint',
uploadUrl: 'https://your-organization-id.cke-cs.com/easyimage/upload/'
- // Read more about Easy Image - https://ckeditor.com/docs/ckeditor5/latest/features/images/image-upload/easy-image.html.
// For other image upload methods see the guide - https://ckeditor.com/docs/ckeditor5/latest/features/images/image-upload/image-upload.html.
}
}
diff --git a/docs/getting-started/legacy-getting-started/quick-start.md b/docs/getting-started/legacy-getting-started/quick-start.md
index 3464a1d66cc..5345ed4fbab 100644
--- a/docs/getting-started/legacy-getting-started/quick-start.md
+++ b/docs/getting-started/legacy-getting-started/quick-start.md
@@ -9,7 +9,7 @@ modified_at: 2022-06-27
# (Legacy) Quick start
- ⚠️ We changed installation methods and this legacy guide is kept for users' convenience. If you are looking for current CKEditor 5 installation instructions, please refer to the newest version of the {@link getting-started/quick-start CKEditor 5 Quick Start} guide.
+ ⚠️ We changed installation methods and this legacy guide is kept for users' convenience. If you are looking for current CKEditor 5 installation instructions, please refer to the newest version of the {@link getting-started/integrations-cdn/quick-start CKEditor 5 Quick Start} guide.
## Introduction
@@ -95,7 +95,7 @@ The fastest way to run an advanced editor using the {@link features/index rich e
In the superbuild, all editor classes are stored under the `CKEDITOR` object. Apart from that exception, the editor initialization is no different than the one described in the {@link getting-started/legacy-getting-started/predefined-builds#available-builds available builds documentation}.
-Because the superbuild contains a lot of plugins, you may need to remove the plugins you do not need with the `removePlugins` configuration option and adjust the toolbar configuration. There are also some plugins, like the {@link features/productivity-pack Productivity Pack}, that require a license to run. You can learn more about obtaining and activating license keys in the {@link getting-started/setup/license-key-and-activation License key and activation} guide. Observe the configuration below to see this implemented.
+Because the superbuild contains a lot of plugins, you may need to remove the plugins you do not need with the `removePlugins` configuration option and adjust the toolbar configuration. There are also some plugins that require a license to run. You can learn more about obtaining and activating license keys in the {@link getting-started/licensing/license-key-and-activation License key and activation} guide. Observe the configuration below to see this implemented.
### Sample implementation
@@ -279,7 +279,7 @@ In this example, you remove the premium collaboration features and several other
// Careful, with the Mathtype plugin CKEditor will not load when loading this sample
// from a local file system (file://) - load this site via HTTP server if you enable MathType.
'MathType',
- // The following features are part of the Productivity Pack and require additional license.
+ // The following features require additional license.
'SlashCommand',
'Template',
'DocumentOutline',
@@ -306,7 +306,7 @@ While the superbuild is designed to provide as many of them as possible, some of
## Running a full-featured editor with Premium features
-If you would like to quickly evaluate CKEditor 5 with premium features such as real-time collaboration, track changes, and revision history, sign up for a [30-day free trial](https://orders.ckeditor.com/trial/premium-features).
+If you would like to quickly evaluate CKEditor 5 with premium features such as real-time collaboration, track changes, and revision history, sign up for a [14-day free trial](https://orders.ckeditor.com/trial/premium-features).
After you sign up, in the customer dashboard you will find the full code snippet to run the editor with premium features with all the necessary configurations.
diff --git a/docs/getting-started/legacy-getting-started/working-with-typescript.md b/docs/getting-started/legacy-getting-started/working-with-typescript.md
index 8072e28f87d..0185f7a6937 100644
--- a/docs/getting-started/legacy-getting-started/working-with-typescript.md
+++ b/docs/getting-started/legacy-getting-started/working-with-typescript.md
@@ -70,7 +70,7 @@ If you want to integrate CKEditor 5 directly in your TypeScript project, fo
The latest versions of our official components for Angular, React, and Vue 3 were migrated to TypeScript and use native CKEditor 5's type definitions. You do not need to provide custom definitions anymore. You can use the following guides:
* {@link getting-started/integrations/angular Angular component}
-* {@link getting-started/integrations/react React component}
+* {@link getting-started/integrations/react-default-npm React component}
* {@link getting-started/integrations/vuejs-v3 Vue.js 3+ component}
## Developing plugins using TypeScript
diff --git a/docs/getting-started/licensing/license-and-legal.md b/docs/getting-started/licensing/license-and-legal.md
new file mode 100644
index 00000000000..5d85fee1067
--- /dev/null
+++ b/docs/getting-started/licensing/license-and-legal.md
@@ -0,0 +1,34 @@
+---
+# Scope:
+# * Clarify copyright and license conditions.
+
+category: licensing
+meta-title: Editor license | CKEditor 5 Documentation
+meta-description: Choosing the right licence type for you needs.
+menu-title: Editor license
+order: 10
+---
+
+# Editor license and legal terms
+
+The following legal notices apply to CKEditor 5 and all software from CKEditor 5 Ecosystem included with it.
+
+Copyright (c) 2003–2024, CKSource Holding sp. z o.o. All rights reserved.
+
+Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html).
+
+## Free for Open Source
+
+If you are running an Open Source project under an OSS license incompatible with GPL, please [contact us](https://ckeditor.com/contact/). We will be happy to [support your project with a free CKEditor 5 license](https://ckeditor.com/wysiwyg-editor-open-source/).
+
+## Available commercial licenses
+
+TO DO
+
+## Sources of intellectual property included in CKEditor
+
+Where not otherwise indicated, all CKEditor 5 content is authored by CKSource engineers and consists of CKSource-owned intellectual property. In some specific instances, CKEditor will incorporate work done by developers outside of CKSource with their express permission.
+
+## Trademarks
+
+CKEditor is a trademark of [CKSource Holding sp. z o.o.](http://cksource.com/) All other brand and product names are trademarks, registered trademarks, or service marks of their respective holders.
diff --git a/docs/getting-started/setup/license-key-and-activation.md b/docs/getting-started/licensing/license-key-and-activation.md
similarity index 57%
rename from docs/getting-started/setup/license-key-and-activation.md
rename to docs/getting-started/licensing/license-key-and-activation.md
index 410dfb532df..1c0e0b24db3 100644
--- a/docs/getting-started/setup/license-key-and-activation.md
+++ b/docs/getting-started/licensing/license-key-and-activation.md
@@ -1,7 +1,8 @@
---
-category: setup
-order: 80
+category: licensing
+order: 20
meta-title: License key and activation | CKEditor 5 Documentation
+meta-description: Managing your license keys and activating the editor.
menu-title: License key and activation
---
@@ -13,23 +14,23 @@ This article explains how to activate a commercial license of CKEditor 5 an
* {@link features/track-changes Track changes}
* {@link features/comments Comments}
* {@link features/revision-history Revision history}
-* {@link features/pagination Pagination}
* {@link features/ai-assistant-overview AI Assistant}
+* {@link features/case-change Case change}
+* {@link features/document-outline Document outline}
+* {@link features/format-painter Format painter}
* {@link features/multi-level-lists Multi-level list}
-* The Productivity Pack that includes:
- * {@link features/case-change Case change}
- * {@link features/document-outline Document outline}
- * {@link features/format-painter Format painter}
- * {@link features/merge-fields Merge fields}
- * {@link features/paste-from-office-enhanced Paste from Office enhanced}
- * {@link features/slash-commands Slash commands}
- * {@link features/table-of-contents Table of contents}
- * {@link features/template Templates}
+* {@link features/pagination Pagination}
+* {@link features/paste-from-office-enhanced Paste from Office enhanced}
+* {@link features/slash-commands Slash commands}
+* {@link features/table-of-contents Table of contents}
+* {@link features/template Templates}
Other premium features such as {@link features/real-time-collaboration real-time collaboration}, {@link features/export-word export to Word}, {@link features/export-pdf export to PDF}, or {@link features/import-word import from Word} are authenticated on the server side. Please refer to respective feature guides for installation details.
- CKEditor 5 (without premium features listed above) can be used without activation as {@link support/license-and-legal open source software under the GPL license}. It will then {@link getting-started/setup/managing-ckeditor-logo display a small "Powered by CKEditor" logo} in the editor area.
+ CKEditor 5 (without premium features listed above) can be used without activation as {@link getting-started/licensing/license-and-legal open source software under the GPL license}. It will then {@link getting-started/licensing/managing-ckeditor-logo display a small "Powered by CKEditor" logo} in the editor area.
+
+ For commercial purposes, there are {@link getting-started/licensing/license-and-legal trial, development and production license keys} are available.
## Obtaining a license
@@ -42,12 +43,51 @@ If you wish to purchase a commercial CKEditor 5 license or a license to one
### Subscribing to the CKEditor Premium Features free trial
-If you wish to test our offer, you can create an account by [signing up for CKEditor Premium Features 30-day free trial](https://orders.ckeditor.com/trial/premium-features). After signing up, you will receive access to the customer dashboard (CKEditor Ecosystem dashboard).
+If you wish to test our offer, you can create an account by [signing up for CKEditor Premium Features 14-day free trial](https://orders.ckeditor.com/trial/premium-features). After signing up, you will receive access to the customer dashboard (CKEditor Ecosystem dashboard).
The trial is commitment-free, and there is no need to provide credit card details to start it. The Premium Features free trial allows you to test all paid CKEditor Ecosystem products at no cost.
If you are using the trial, refer to the [CKEditor 5 Premium Features free trial documentation](https://ckeditor.com/docs/trial/latest/guides/overview.html) to learn how to access the relevant license key and activate the premium features.
+## License key types
+
+### Trial license key
+
+This key grants access to **all features**. Valid for **14 days**. It does not consume editor loads, but editor is limited functionally (for example session time, number of changes). It is **perfect for evaluating the platform** and all its features. It can be used only for evaluation purposes.
+
+* **Features**: Grants access to all features and add-ons.
+* **Duration**: Valid for 14 days (until 12th May 2024).
+* **Functionality**: The editor is limited functionally, such as session time and the number of changes allowed.
+* **Intended Use**: Ideal for evaluating the platform and all its features.
+* **Usage Limitation**: Can only be used for evaluation purposes and not for production.
+* **Editor Loads**: Does not consume editor loads.
+
+You can sign up for the [CKEditor Premium Features 14-day free trial](https://orders.ckeditor.com/trial/premium-features) to test the editor.
+
+### Development license key
+
+This key grants access to your subscription features. It does not consume editor loads, but editor is limited functionally (for example session time, number of changes, development domains). It is **perfect for development environments** (local work, CI, E2E tests). It must not be used for production environments.
+
+* **Features**: Grants access to subscription features.
+* **Functionality**: Similar to the trial license, the editor is limited functionally, including session time and the number of changes allowed. Additionally, there might be limitations on development domains.
+* **Intended Use**: Designed for development environments such as local work, continuous integration (CI), and end-to-end (E2E) tests.
+* **Usage Limitation**: Must not be used for production environments.
+* **Editor Loads**: Does not consume editor loads.
+
+[Contact us](https://ckeditor.com/contact/?sales=true#contact-form) for more details
+
+### Production license key
+
+This key grants access to your subscription features without imposing any limitations. It **consumes editor loads** (after the 14 days trial period ends).
+
+* **Features**: Grants access to subscription features.
+* **Functionality**: The editor functions without any restrictions.
+* **Intended Use**: Meant for production environments where the software is actively used by end-users.
+* **Usage Limitation**: None specified.
+* **Editor Loads**: Consumes editor loads, especially after the 14-day trial period ends.
+
+There are several commercial plans available to choose from. [Contact us](https://ckeditor.com/contact/?sales=true#contact-form) for more details
+
## Obtaining a license key
Follow this guide to get the license key necessary to activate your purchased premium features or to white-label CKEditor 5 (remove the "Powered by CKEditor" logo).
@@ -64,7 +104,7 @@ After logging in, click "CKEditor" under the "Your products" header on the left.
### Copy the license key
-After clicking "Manage," you can access the license key needed to run the editor and the premium features. Note that the same license key will be valid for both the Productivity Pack and other standalone features, as well as CKEditor 5 itself.
+After clicking "Manage," you can access the license key needed to run the editor and the premium features. Note that the same license key will be valid for both the standalone features, as well as CKEditor 5 itself.
{@img assets/img/ckeditor-key.png 822 Premium features license key in the management console.}
diff --git a/docs/getting-started/setup/managing-ckeditor-logo.md b/docs/getting-started/licensing/managing-ckeditor-logo.md
similarity index 91%
rename from docs/getting-started/setup/managing-ckeditor-logo.md
rename to docs/getting-started/licensing/managing-ckeditor-logo.md
index 390b7e41ac3..1fda430399b 100644
--- a/docs/getting-started/setup/managing-ckeditor-logo.md
+++ b/docs/getting-started/licensing/managing-ckeditor-logo.md
@@ -1,11 +1,12 @@
---
-category: setup
-order: 70
-meta-title: Managing the "Powered by CKEditor" logo | CKEditor 5 Documentation
-meta-description: Managing the "Powered by CKEditor" logo
+category: licensing
+order: 50
+menu-title: Whitelabelling the editor
+meta-title: WHitelabelling CKEditor 5 | CKEditor 5 Documentation
+meta-description: Managing the "Powered by CKEditor" logo.
---
-# Managing the "Powered by CKEditor" logo
+# Whitelabelling CKEditor 5
## Why the "Powered by CKEditor" logo?
@@ -23,7 +24,7 @@ However, even as a paid customer, you can [keep the logo](#how-to-keep-the-power
To remove the logo, you need to obtain a commercial license and then configure the {@link module:core/editor/editorconfig~EditorConfig#licenseKey `config.licenseKey`} setting.
-Refer to the {@link getting-started/setup/license-key-and-activation License key and activation} guide for details on where to find the license key and how to use it in your configuration.
+Refer to the {@link getting-started/licensing/license-key-and-activation License key and activation} guide for details on where to find the license key and how to use it in your configuration.
## How to keep the "Powered by CKEditor" logo?
diff --git a/docs/getting-started/licensing/usage-based-billing.md b/docs/getting-started/licensing/usage-based-billing.md
new file mode 100644
index 00000000000..69416f0faf1
--- /dev/null
+++ b/docs/getting-started/licensing/usage-based-billing.md
@@ -0,0 +1,24 @@
+---
+category: licensing
+menu-title: Usage-based billing
+meta-title: Usage-based billing | CKEditor 5 documentation
+meta-description: Learn how usage-based billing works in CKEditor 5.
+order: 40
+modified_at: 2024-06-25
+---
+
+# Usage-based billing
+
+Usage-Based Billing (UBB) means the cost you incur depends on how much you use CKEditor 5.
+
+## How it works
+
+
+ The information below is only valid for cloud-based users.
+
+
+The billing is based on the number of editor loads. An editor load is a term used for each time CKEditor 5 is initialized in your application. For example, if 100 users load CKEditor 10 times each, there are 1,000 editor loads. The number of editor loads needed depends on your application, however, all our plans have been developed to take into consideration actual use on our cloud infrastructure.
+
+We offer several pricing plans for commercial users. Each of these plans includes a specific number of editor loads. If you require additional editor loads, you have the option to upgrade to a more suitable plan or pay for every block of 1,000 editor loads over your allocated plan limit.
+
+Your billing month starts the day after your trial ends. For example, if the last day of your trial is June 2nd, we start counting your monthly editor loads on June 3rd, and then reset that count to 0 every 3rd day of each subsequent month.
diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md
deleted file mode 100644
index 0e15850b1ba..00000000000
--- a/docs/getting-started/quick-start.md
+++ /dev/null
@@ -1,617 +0,0 @@
----
-category: installation
-order: 10
-menu-title: Quick start
-meta-title: Quick start | CKEditor 5 documentation
-meta-description: Learn the fastest way to install and use CKEditor 5 - the powerful, rich text WYSIWYG editor in your web application using npm or CDN.
-modified_at: 2024-06-25
----
-
-# Quick start
-
-CKEditor 5 is a powerful, rich text editor you can embed in your web application. This guide will show you the fastest way to start using it.
-
-You have a few methods to choose from:
-
-* [Using CKEditor 5 Builder](#ckeditor-5-builder) for the smoothest setup with live preview and multiple integration options.
-* [Using npm](#installing-ckeditor-5-using-npm), where you use a JavaScript package and build the editor with a bundler.
-* [Using CDN](#installing-ckeditor-5-from-cdn), where you use our cloud-distributed CDN in a no-build setup.
-* [Using a ZIP file](#installing-ckeditor-5-from-a-zip-file), where you download the ready-to-run files and copy them to your project.
-* Choosing one of the pre-made integrations with popular frameworks (see table of contents for details).
-
-{@snippet getting-started/use-builder}
-
-## Installing CKEditor 5 using npm
-
-
- To set up the editor from npm, you need a bundler to build the JavaScript files correctly. CKEditor 5 is compatible with all modern JavaScript bundlers. For a quick project setup, we recommend using [Vite](https://vitejs.dev/guide/#scaffolding-your-first-vite-project).
-
-
-First, install the necessary package. The command below will install the main CKEditor 5 package containing all open-source plugins.
-
-```bash
-npm install ckeditor5
-```
-
-Now, you can import all the modules from the `ckeditor5` package. Additionally, you have to import CSS styles separately. Please note the {@link module:essentials/essentials~Essentials `Essentials`} plugin, including all essential editing features.
-
-**Importing and registering UI translations is optional for American English.** To use the editor in any other language, use imported translations, as shown in the {@link getting-started/setup/ui-language setup section}.
-
-```js
-import { ClassicEditor, Essentials, Bold, Italic, Font, Paragraph } from 'ckeditor5';
-
-import 'ckeditor5/ckeditor5.css';
-
-ClassicEditor
- .create( document.querySelector( '#editor' ), {
- plugins: [ Essentials, Bold, Italic, Font, Paragraph ],
- toolbar: [
- 'undo', 'redo', '|', 'bold', 'italic', '|',
- 'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor'
- ]
- } )
- .then( /* ... */ )
- .catch( /* ... */ );
-```
-
-Pass the imported plugins inside the configuration to the {@link module:editor-classic/classiceditor~ClassicEditor#create `create()`} method and add toolbar items where applicable.
-
-The first argument in the `create()` function is a DOM element for the editor placement, so you need to add it to your HTML page.
-
-```html
-
-
Hello from CKEditor 5!
-
-```
-
-That is all the code you need to see a bare-bone editor running in your web browser.
-
-## Installing CKEditor 5 from CDN
-
-CDN is an alternative method of running CKEditor 5 that you can start using in just a few steps.
-
-You have two options to choose from. You can use an ES Modules build with imports similar to the npm setup or UMD build with a global variable. The former is more modern, but the latter offers better compatibility with older development environments.
-
-### CDN with imports
-
-Start by adding a link to the editor style sheet. It contains all styles for the editor's UI and content. Refer to the {@link getting-started/setup/css#styling-the-published-content content styles} guide for more information.
-
-```html
-
-```
-
-Then, you need to attach the script with the JavaScript code. To simplify imports, you can use the feature available in browsers – the [import map](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap). It allows us to map an easy-to-remember specifier (like `ckeditor5`) to the full URL of the file from the CDN. We use this browser feature to share an editor engine code between plugins.
-
-```html
-
-```
-
-Once you have added the import map, you can access the editor and its plugins using the `ckeditor5` specifier.
-
-
- You must run your code on a local server to use import maps. Opening the HTML file directly in your browser will trigger security rules. These rules (CORS policy) ensure loading modules from the same source. Therefore, set up a local server, like `nginx`, `caddy`, `http-server`, to serve your files over HTTP or HTTPS.
-
-
-In the following script tag, import the desired plugins, add them to the `plugins` array, and add toolbar items where applicable. Note that both script tags (this and previous) have the appropriate `type` values.
-
-```html
-
-```
-
-Lastly, add a tag for the editor to attach to.
-
-```html
-
-
Hello from CKEditor 5!
-
-```
-
-Your final page should look similar to the one below.
-
-```html
-
-
-
-
-
- CKEditor 5 - Quick start CDN
-
-
-
-
-
Hello from CKEditor 5!
-
-
-
-
-
-
-
-```
-
-### CDN with global variables
-
-If you prefer to use global variables instead of ES modules, you can use the UMD build from CDN.
-
-Start by adding a link to the editor style sheet. It contains all styles for the editor's UI and content. Refer to the {@link getting-started/setup/css#styling-the-published-content content styles} guide for more information.
-
-```html
-
-```
-
-Then, add the script with the JavaScript code. It registers a global variable called `CKEDITOR` that you can use to access the editor and its plugins.
-
-```html
-
-```
-
-In the following script tag, get the editor class and desired plugins from the `CKEDITOR` object and add them to the `plugins` array. Add toolbar items where applicable.
-
-```html
-
-```
-
-Finally, add a tag for the editor to attach to.
-
-```html
-
-
Hello from CKEditor 5!
-
-```
-
-Your final page should look similar to the one below.
-
-```html
-
-
-
-
-
- CKEditor 5 - Quick start CDN
-
-
-
-
-
Hello from CKEditor 5!
-
-
-
-
-
-
-
-```
-
-## Installing CKEditor 5 from a ZIP file
-
-If you do not want to build your project using npm and cannot rely on the CDN delivery, you can download ready-to-run files with CKEditor 5 and all its plugins.
-
-1. Download the ZIP archive with the latest CKEditor 5 distribution.
-2. Extract the ZIP archive into a dedicated directory inside your project (for example, `vendor/`). Include the editor version in the directory name to ensure proper cache invalidation whenever you install a new version of CKEditor 5.
-
-Files included in the ZIP archive:
-
-* `index.html` – A sample with a working editor.
-* `ckeditor5/ckeditor5.js` – The ready-to-use editor ESM bundle contains the editor and all plugins. [Recommended build]
-* `ckeditor5/ckeditor.js.map` – The source map for the editor ESM bundle.
-* `ckeditor5/ckeditor5.umd.js` – The ready-to-use editor UMD bundle contains the editor and all plugins. [Secondary build]
-* `ckeditor5/ckeditor5.umd.js.map` – The source map for the editor UMD bundle.
-* `ckeditor5/*.css` – The style sheets for the editor. You will use `ckeditor5.css` in most cases. Read about other files in the {@link getting-started/setup/css Editor and content styles} guide.
-* `translations/` – The editor UI translations (see the {@link getting-started/setup/ui-language Setting the UI language} guide).
-* The `README.md` and `LICENSE.md` files.
-
-The easiest way to see the editor in action is to serve the `index.html` file via an HTTP server.
-
-
- You must run your code on a local server to use import maps. Opening the HTML file directly in your browser will trigger security rules. These rules (CORS policy) ensure loading modules from the same source. Therefore, set up a local server, like `nginx`, `caddy`, `http-server`, to serve your files over HTTP or HTTPS.
-
-
-All three installation methods – npm, CDN, ZIP – work similarly. Therefore, you can also use the [CKEditor 5 Builder](https://ckeditor.com/ckeditor-5/builder/) with a ZIP archive. Create a custom preset with the Builder and combine it with the editor loaded from ZIP files.
-
-## Installing premium features
-
-### Installing premium features using npm
-
-All premium features are available as a separate package. You can install it the same as the open-source one.
-
-```bash
-npm install ckeditor5-premium-features
-```
-
-Now, you can import all the modules from both the `ckeditor5` and `ckeditor5-premium-features` packages. Additionally, you have to import CSS styles separately.
-
-```js
-import { ClassicEditor, Essentials, Bold, Italic, Paragraph, Font } from 'ckeditor5';
-import { FormatPainter } from 'ckeditor5-premium-features';
-
-import 'ckeditor5/ckeditor5.css';
-import 'ckeditor5-premium-features/ckeditor5-premium-features.css';
-
-ClassicEditor
- .create( document.querySelector( '#editor' ), {
- plugins: [ Essentials, Bold, Italic, Paragraph, Font, FormatPainter ],
- toolbar: [
- 'undo', 'redo', '|', 'bold', 'italic', '|',
- 'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor', '|',
- 'formatPainter'
- ],
- licenseKey: ''
- } )
- .then( /* ... */ )
- .catch( /* ... */ );
-```
-
-Pass the imported plugins inside the configuration to the {@link module:editor-classic/classiceditor~ClassicEditor#create `create()`} method and add toolbar items where applicable. Please note that to use premium features, you need to activate them with a proper license key, as described in the [final section of this guide](#obtaining-a-license-key).
-
-The first argument in the `create()` function is a DOM element for the editor placement, so you need to add it to your HTML page.
-
-```html
-
-
Hello from CKEditor 5!
-
-```
-
-That is all the code you need to see a bare-bone editor running in your web browser.
-
-### Installing premium features from CDN
-
-Just like with open-source features, you can choose between using an ES Modules build with imports or a UMD build with global variables. However, these methods should not be mixed. You should use the same method for both the open-source and premium features.
-
-#### CDN with imports
-
-Start by adding links to style sheets. They contain all styles for the editor's UI and content. They come in two separate style sheets – for open-source and premium plugins. Refer to the {@link getting-started/setup/css#styling-the-published-content content styles} guide for more information.
-
-```html
-
-
-```
-
-Then, you need to attach the script with the JavaScript code. To simplify imports, you can use the feature available in browsers – the [import map](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap). It allows you to map an easy-to-remember specifier (like `ckeditor5`) to the full URL of the file from the CDN. Use this browser feature to share an editor engine code between plugins.
-
-```html
-
-```
-
-Once you have added the import map, you can access the editor and its plugins using the `ckeditor5` specifier. Import them from the `ckeditor5-premium-features` package. Please note that to use premium features, you need to activate them with a proper license key, as described in the [final section of this guide](#obtaining-a-license-key).
-
-In the following script tag, import the desired plugins, add them to the `plugins` array, and add toolbar items where applicable. Note that both script tags (this and previous) have the appropriate `type` values.
-
-```html
-
-```
-
-Lastly, add a tag for the editor to attach to.
-
-```html
-
-
Hello from CKEditor 5!
-
-```
-
-Your final page should look similar to the one below.
-
-```html
-
-
-
-
-
- CKEditor 5 - Quick start CDN
-
-
-
-
-
-
-
Hello from CKEditor 5!
-
-
-
-
-
-
-
-```
-
-#### CDN with global variables
-
-Start by adding links to style sheets. They contain all styles for the editor's UI and content. The styles come in two separate style sheets – for open-source and premium plugins. Refer to the {@link getting-started/setup/css#styling-the-published-content content styles} guide for more information.
-
-```html
-
-
-```
-
-Then, add the scripts with the JavaScript code. They registers global variables called `CKEDITOR` and `CKEDITOR_PREMIUM_FEATURES` that you can use to access the editor and its plugins.
-
-```html
-
-
-```
-
-Please note, that to use premium features, you need to activate them with a proper license key, as described in the [final section of this guide](#obtaining-a-license-key).
-
-In the following script tag, get the editor class and desired plugins from the `CKEDITOR` and `CKEDITOR_PREMIUM_FEATURES` objects and add them to the `plugins` array. Add toolbar items where applicable.
-
-```html
-
-```
-
-Lastly, add a tag for the editor to attach to.
-
-```html
-
-
Hello from CKEditor 5!
-
-```
-
-Your final page should look similar to the one below.
-
-Final build
-
-```html
-
-
-
-
-
- CKEditor 5 - Quick start CDN
-
-
-
-
-
-
Hello from CKEditor 5!
-
-
-
-
-
-
-
-
-```
-
-### Installing premium features from a ZIP file
-
-1. Download the ZIP archive with the latest CKEditor 5 distribution and premium features.
-2. Extract the ZIP archive into a dedicated directory inside your project (for example, `vendor/`). Include the editor version in the directory name to ensure proper cache invalidation whenever you install a new version of CKEditor 5.
-
-Files in the ZIP archive:
-
-* `index.html` – A sample with a working editor.
-* The `ckeditor5/` directory:
- * `ckeditor5.js` – The ready-to-use editor ESM bundle contains the editor and all plugins. [Recommended build]
- * `ckeditor.js.map` – The source map for the editor ESM bundle.
- * `ckeditor5.umd.js` – The ready-to-use editor UMD bundle contains the editor and all plugins. [Secondary build]
- * `ckeditor5.umd.js.map` – The source map for the editor UMD bundle.
- * `*.css` – The style sheets for the editor, use `ckeditor5.css` in most cases. Read about other files in the {@link getting-started/setup/css Editor and content styles} guide.
- * `translations/` – The editor UI translations (see the {@link getting-started/setup/ui-language Setting the UI language} guide).
- * The `ckeditor5-premium-features/` directory:
- * `ckeditor5-premium-features.js` – ESM bundle of premium features. [Recommended build]
- * `ckeditor5-premium-features.umd.js` – UMD bundle of premium features contains the editor and all plugins. [Secondary build]
- * `*.css` – The style sheets for the premium features. You will use `ckeditor5-premium-features.css` in most cases.
- * `translations/` – The premium features UI translations.
-* The `README.md` and `LICENSE.md` files.
-
-The easiest way to see the editor in action is to serve the `index.html` file via an HTTP server.
-
-
- You must run your code on a local server to use import maps. Opening the HTML file directly in your browser will trigger security rules. These rules (CORS policy) ensure loading modules from the same source. Therefore, set up a local server, like `nginx`, `caddy`, `http-server`, to serve your files over HTTP or HTTPS.
-
-
-All three installation methods – npm, CDN, ZIP – work similarly. Therefore, you can also use the [CKEditor 5 Builder](https://ckeditor.com/ckeditor-5/builder/) with a ZIP archive. Create a custom preset with the Builder and combine it with the editor loaded from ZIP files.
-
-### Obtaining a license key
-
-To activate CKEditor 5 premium features, you will need a commercial license. The easiest way to get one is to sign up for the [CKEditor Premium Features 30-day free trial](https://orders.ckeditor.com/trial/premium-features) to test the premium features.
-
-You can also [contact us](https://ckeditor.com/contact/?sales=true#contact-form) to receive an offer tailored to your needs. To obtain an activation key, please follow the {@link getting-started/setup/license-key-and-activation License key and activation} guide.
-
-## Next steps
-
-* See how to manipulate the editor's data in the {@link getting-started/setup/getting-and-setting-data Getting and setting data} guide.
-* Refer to further guides in the {@link getting-started/setup/configuration setup section} to see how to customize your editor further.
-* Check the {@link features/index features category} to learn more about individual features.
diff --git a/docs/getting-started/setup/configuration.md b/docs/getting-started/setup/configuration.md
index e454bcc4e48..897f3d03c06 100644
--- a/docs/getting-started/setup/configuration.md
+++ b/docs/getting-started/setup/configuration.md
@@ -2,7 +2,7 @@
category: setup
menu-title: Configuring features
meta-title: Configuring editor features | CKEditor 5 documentation
-meta-description: Learn how to configure CKEditor 5.
+meta-description: Learn how to configure CKEditor 5 features.
order: 30
modified_at: 2024-06-25
---
@@ -29,6 +29,7 @@ import { ClassicEditor, Indent, IndentBlock, BlockQuote } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Indent, IndentBlock, BlockQuote, /* ... */ ], // Plugins import.
toolbar: [ 'outdent', 'indent', 'blockquote', /* ... */ ] // Toolbar configuration.
} )
@@ -40,7 +41,7 @@ Note that some features may require more than one plugin to run, as shown above.
### Adding premium features
-CKEditor 5 premium features are imported in the same way. However, they have their own package, named `ckeditor5-premium-features`, to import from. These also {@link getting-started/setup/license-key-and-activation require a license}. Please see an example below, adding the PDF export feature and configuring it.
+CKEditor 5 premium features are imported in the same way. However, they have their own package, named `ckeditor5-premium-features`, to import from. These also {@link getting-started/licensing/license-key-and-activation require a license}. Please see an example below, adding the PDF export feature and configuring it.
```js
import { ClassicEditor } from 'ckeditor5';
@@ -48,6 +49,7 @@ import { ExportPdf } from 'ckeditor5-premium-features';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '',
plugins: [ ExportPdf, /* ... */ ],
toolbar: [ 'exportPdf', '|', /* ... */ ],
exportPdf: {
@@ -74,13 +76,14 @@ ClassicEditor
## Configuring editor settings
-When integrating an editor into your application, you can customize its features by passing a JavaScript object with configuration options to the {@link module:core/editor/editor~Editor.create `create()`} method. These settings, defined in the {@link module:core/editor/editor~Editor.create `EditorConfig`}, allow for extensive customization of the editor's functionality. Remember that customization depends on the editor setup and plugins loaded. The sample snippet below shows the configuration of the toolbar, the headers feature, font family, and color picker settings:
+When integrating an editor into your application, you can customize its features by passing a JavaScript object with configuration options to the {@link module:core/editor/editor~Editor.create `create()`} method. These settings, defined in the {@link module:core/editor/editor~Editor.create `EditorConfig`}, allow for extensive customization of the editor's functionality. Remember that customization depends on the editor setup and plugins loaded. The sample snippet below shows the configuration of the toolbar, the headings feature, font family, and color picker settings:
```js
import { ClassicEditor, Heading, BlockQuote, Bold, Italic, Font, Link, List } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
toolbar: [
'heading',
'|',
@@ -123,6 +126,16 @@ ClassicEditor
See {@link module:core/editor/editorconfig~EditorConfig} to learn about all available configuration options. Also, check out individual {@link features/index feature guides}, listing various configuration options available per feature.
+### Why does the editor filter out content such as styles, classes or HTML elements?
+
+CKEditor 5 implements a custom {@link framework/architecture/editing-engine data model}. This means that the editor needs to convert every piece of loaded content to that model and then render it back to the view.
+
+Each kind of content must be handled by a plugin. For example, the [`ckeditor5-basic-styles`](https://www.npmjs.com/package/@ckeditor/ckeditor5-basic-styles) package handles HTML elements such as ``, ``, ``, etc. along with their representation in the model. The feature defines the two–way conversion between the HTML (view) and the editor model.
+
+If you try to load some content unknown to the editor – without a feature loaded to handle it – the editor will drop it.
+
+If you want the editor to handle the HTML5 elements not covered by available feature plugins, you need to write plugins to support them. You can also use the {@link features/general-html-support General HTML Support (GHS)} feature. Once you do that, CKEditor 5 will not filter anything out. However, GHS needs to be carefully and precisely configured to ensure stability and security of the implementation.
+
## Removing features
In some cases, you may want to have different editor setups in your application, all based on the same build. For that purpose, you need to control the plugins available in the editor at runtime.
diff --git a/docs/getting-started/setup/csp.md b/docs/getting-started/setup/csp.md
index 9d44faa38ed..baedfe13faa 100644
--- a/docs/getting-started/setup/csp.md
+++ b/docs/getting-started/setup/csp.md
@@ -1,6 +1,7 @@
---
category: setup
meta-title: Content Security Policy | CKEditor 5 documentation
+meta-description: Learn about the CKEditor 5 Content Security Policy.
order: 110
---
diff --git a/docs/getting-started/setup/css.md b/docs/getting-started/setup/css.md
index ea072c92e99..dacc3a58e94 100644
--- a/docs/getting-started/setup/css.md
+++ b/docs/getting-started/setup/css.md
@@ -2,6 +2,7 @@
category: setup
menu-title: Editor and content styles
meta-title: Editor and content styles | CKEditor 5 documentation
+meta-description: Learn how to style the editor and content with CSS.
order: 90
modified_at: 2024-06-25
---
@@ -13,7 +14,7 @@ CKEditor 5 is distributed with two types of styles:
* Editor styles, used to style the editor's user interface.
* Content styles, used to style the content in the editor.
-If you went through our {@link getting-started/quick-start Quick start}, you probably noticed that attaching the styles in JavaScript is pretty standard, and we provide CSS style sheets that have both the editor and content styles combined:
+If you went through our {@link getting-started/integrations-cdn/quick-start Quick start}, you probably noticed that attaching the styles in JavaScript is pretty standard, and we provide CSS style sheets that have both the editor and content styles combined:
```js
import 'ckeditor5/ckeditor5.css';
diff --git a/docs/getting-started/setup/editor-lifecycle.md b/docs/getting-started/setup/editor-lifecycle.md
index ad90b3ddbae..0219441fe8f 100644
--- a/docs/getting-started/setup/editor-lifecycle.md
+++ b/docs/getting-started/setup/editor-lifecycle.md
@@ -1,6 +1,7 @@
---
category: setup
meta-title: Editor lifecycle | CKEditor 5 documentation
+meta-description: Handling the editor lifecycle. With examples.
order: 20
modified_at: 2024-06-25
---
@@ -34,7 +35,9 @@ import { ClassicEditor, Essentials } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Essentials, /* ... */ ],
+ toolbar: [ /* .. */ ],
} )
.then( editor => {
console.log( editor );
@@ -71,7 +74,9 @@ import { DecoupledEditor, Essentials } from 'ckeditor5';
DecoupledEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Essentials, /* ... */ ],
+ toolbar: [ /* .. */ ],
} )
.then( editor => {
const toolbarContainer = document.querySelector( '#toolbar-container' );
diff --git a/docs/getting-started/setup/editor-types.md b/docs/getting-started/setup/editor-types.md
index a61c925e45c..e477a230166 100644
--- a/docs/getting-started/setup/editor-types.md
+++ b/docs/getting-started/setup/editor-types.md
@@ -2,7 +2,7 @@
category: setup
menu-title: Editor types
meta-title: Editor types | CKEditor 5 documentation
-meta-description: Learn about available editor types.
+meta-description: Learn more about available CKEditor 5 editor types.
order: 25
modified_at: 2024-06-25
---
@@ -14,7 +14,7 @@ The editor's user interface is dependent on the editor types. The editor provide
If you are unsure which editor type to choose, try the [CKEditor 5 Builder](https://ckeditor.com/ckeditor-5/builder/?redirect=docs). It lets you quickly view and experiment with different presets.
-There are six ready-made editor types (see below) available for CKEditor 5. They offer different functional approaches to editing as well as various UI solutions. Editor types are imported from the main `ckeditor5` package, the same way features are imported, as shown in the {@link getting-started/quick-start Quick start} guide.
+There are six ready-made editor types (see below) available for CKEditor 5. They offer different functional approaches to editing as well as various UI solutions. Editor types are imported from the main `ckeditor5` package, the same way features are imported, as shown in the {@link getting-started/integrations-cdn/quick-start Quick start} guide.
Other custom-tailored editor types can be made using the {@link framework/external-ui CKEditor 5 Framework}.
@@ -25,6 +25,7 @@ import { ClassicEditor, Bold, Italic, Link } from 'ckeditor5'; // Imports.
ClassicEditor // Editor type declaration.
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Bold, Italic, Link ], // Plugins import.
toolbar: [ 'bold', 'italic', 'link' ] // Toolbar configuration.
} )
@@ -66,7 +67,7 @@ See an {@link examples/builds/balloon-block-editor example of the balloon block
The Decoupled editor is named for its unique structure, where the toolbar and editing area are separate elements. This design allows for greater flexibility and customization, making it suitable for a wide range of applications beyond just classic WYSIWYG editing.
-The most popular use case for the Decoupled editor is the “document editor”, similar to large editing packages such as Google Docs or Microsoft Word. It works best for creating documents, which are usually later printed or exported to PDF files.
+The most popular use case for the Decoupled editor is the “document editor,” similar to large editing packages such as Google Docs or Microsoft Word. It works best for creating documents, which are usually later printed or exported to PDF files.
By separating the toolbar from the editing area, you can integrate the editor into different parts of your application or customize its appearance and functionality to suit various needs. For example, you may want to create an email creator that reflects the setup in which the toolbar is at the bottom of the editing area. We have {@link examples/custom/bottom-toolbar-editor a working example} for this.
diff --git a/docs/getting-started/setup/getting-and-setting-data.md b/docs/getting-started/setup/getting-and-setting-data.md
index 82239d290ec..6c141c88c00 100644
--- a/docs/getting-started/setup/getting-and-setting-data.md
+++ b/docs/getting-started/setup/getting-and-setting-data.md
@@ -1,6 +1,7 @@
---
category: setup
meta-title: Getting and setting data | CKEditor 5 documentation
+meta-description: Deep-dive into handling data with CKEditor 5.
order: 10
---
@@ -32,6 +33,7 @@ However, if you cannot alter the HTML or you load the data asynchronously using
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ /* ... */ ],
toolbar: [ /* ... */ ],
initialData: 'Hello, world!
'
@@ -42,7 +44,7 @@ ClassicEditor
The {@link module:core/editor/editorconfig~EditorConfig.initialData `initialData`} property will initialize the editor with the provided data, overriding the content provided at the HTML level.
-If you are setting up the editor with integrations like {@link getting-started/integrations/react React}, consult the documentation for additional properties provided to initialize the data.
+If you are setting up the editor with integrations like {@link getting-started/integrations/react-default-npm React}, consult the documentation for additional properties provided to initialize the data.
## Getting the editor data with `getData()`
@@ -59,6 +61,7 @@ let editor;
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ /* ... */ ],
toolbar: [ /* ... */ ]
} )
@@ -130,6 +133,7 @@ This approach is **only available in the Classic editor**, and only if the edito
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Essentials, Paragraph, Bold, Italic ],
toolbar: [ 'bold', 'italic' ]
} )
diff --git a/docs/getting-started/setup/loading-cdn-resources.md b/docs/getting-started/setup/loading-cdn-resources.md
new file mode 100644
index 00000000000..10528b7d08a
--- /dev/null
+++ b/docs/getting-started/setup/loading-cdn-resources.md
@@ -0,0 +1,121 @@
+---
+category: setup
+meta-title: Loading CDN resources | CKEditor 5 documentation
+meta-description: Learn how to load CKEditor 5 resources from CDN.
+order: 130
+modified_at: 2024-09-10
+---
+
+# Loading CDN resources
+
+Loading CKEditor 5 and its plugins from a CDN requires adding the necessary script and style sheet tags to the `` of your page. In some environments, this can be easily done manually by following the {@link getting-started/integrations-cdn/quick-start CDN for Vanilla JavaScript} guide.
+
+However, other environments may require more work. It is especially true if you want to load some resources conditionally or dynamically or need to wait for the resources to be loaded before using them.
+
+For this reason, we provide the `useCKEditorCloud` and `loadCKEditorCloud` helper functions to make this process easier. These functions will handle adding the necessary script and style sheet tags to your page, ensure that the resources are only loaded once, and provide access to the data exported by them. This way you can load CKEditor 5 and its plugins from a CDN without worrying about the technical details.
+
+If you use our {@link getting-started/integrations-cdn/react-default-cdn React} or {@link getting-started/integrations-cdn/vuejs-v3 Vue.js 3+} integrations, see the {@link getting-started/setup/loading-cdn-resources#using-the-useckeditorcloud-function Using the `useCKEditorCloud` function} section. Otherwise, see the {@link getting-started/setup/loading-cdn-resources#using-the-loadckeditorcloud-function Using the `loadCKEditorCloud` function} section.
+
+## Using the `useCKEditorCloud` function
+
+Our {@link getting-started/integrations-cdn/react-default-cdn React} and {@link getting-started/integrations-cdn/vuejs-v3 Vue.js 3+} integrations export a helper function named `useCKEditorCloud` to help you load CDN resources. These helpers are only small wrappers around the `loadCKEditorCloud` function but are designed to better integrate with the specific framework, its lifecycle, and reactivity mechanisms.
+
+Here is an example of how you can use `useCKEditorCloud`:
+
+```js
+const cloud = useCKEditorCloud( {
+ version: '{@var ckeditor5-version}',
+ premium: true
+} );
+```
+
+This will add the necessary script and style sheet tags to the page's `` and update the internal state to reflect the loading status. Depending on the framework, the `useCKEditorCloud` function may return different values. Please refer to the documentation of the specific integration for more details.
+
+Regardless of the framework used, the `useCKEditorCloud` functions always accept the same options, which are described in {@link getting-started/setup/loading-cdn-resources#the-loadckeditorcloud-function-options The `loadCKEditorCloud` function options} section.
+
+## Using the `loadCKEditorCloud` function
+
+To use the `loadCKEditorCloud` helper, you need to install the `@ckeditor/ckeditor5-integrations-common` package first:
+
+```bash
+npm install @ckeditor/ckeditor5-integrations-common
+```
+
+Then you can use the `loadCKEditorCloud` function like this:
+
+```js
+import { loadCKEditorCloud } from '@ckeditor/ckeditor5-integrations-common';
+
+const { CKEditor, CKEditorPremiumFeatures } = await loadCKEditorCloud( {
+ version: '{@var ckeditor5-version}',
+ premium: true
+} );
+```
+
+The `loadCKEditorCloud` function returns a promise that resolves to an object in which each key contains data of the corresponding CDN resources. The exact object shape depends on the options passed to the function.
+
+The options accepted by the `loadCKEditorCloud` function are described in {@link getting-started/setup/loading-cdn-resources#the-loadckeditorcloud-function-options The `loadCKEditorCloud` function options} section.
+
+## The `loadCKEditorCloud` function options
+
+The `loadCKEditorCloud` function (and `useCKEditorCloud` functions which are small wrappers around it) accepts an object with the following properties:
+
+* `version` (required) – The version of CKEditor 5 and premium features (if `premium` option is set to `true`) to load.
+* `translations` (optional) – An array of language codes to load translations for.
+* `premium` (optional) – A boolean value that indicates whether to load premium plugins. [1]
+* `ckbox` (optional) – Configuration for loading CKBox integration. [1]
+* `plugins` (optional) – Configuration for loading additional plugins. The object should have the global plugin name as keys and the plugin configuration as values. [1]
+* `injectedHtmlElementsAttributes` (optional) – An object with attributes that will be added to the `
```
-See the {@link getting-started/quick-start#installing-ckeditor-5-from-cdn CDN installation guide} for more information.
+See the {@link getting-started/integrations-cdn/quick-start#installing-ckeditor-5-from-cdn CDN installation guide} for more information.
## Setting the language of the content
@@ -139,6 +142,7 @@ Configure {@link module:core/editor/editorconfig~EditorConfig#language `config.l
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other con figuration options ...
language: {
// The UI will be English.
ui: 'en',
diff --git a/docs/tutorials/abbreviation-plugin-tutorial/abbreviation-plugin-level-1.md b/docs/tutorials/abbreviation-plugin-tutorial/abbreviation-plugin-level-1.md
index a6b8d0c3554..92e47fca756 100644
--- a/docs/tutorials/abbreviation-plugin-tutorial/abbreviation-plugin-level-1.md
+++ b/docs/tutorials/abbreviation-plugin-tutorial/abbreviation-plugin-level-1.md
@@ -121,6 +121,7 @@ import Abbreviation from './abbreviation/abbreviation'; // ADDED
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: 'GPL', // Or ''.
plugins: [
Essentials, Paragraph, Heading, List, Bold, Italic,
Abbreviation // ADDED
@@ -343,6 +344,7 @@ import Abbreviation from './abbreviation/abbreviation';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: 'GPL', // Or ''.
plugins: [
Essentials, Paragraph, Heading, List, Bold, Italic, Abbreviation
],
diff --git a/docs/tutorials/crash-course/editor.md b/docs/tutorials/crash-course/editor.md
index a69b4444026..0770dd22016 100644
--- a/docs/tutorials/crash-course/editor.md
+++ b/docs/tutorials/crash-course/editor.md
@@ -71,6 +71,7 @@ The `Essentials` plugin adds the `Undo` and `Redo` operations. Let's add them to
```js
const editor = await ClassicEditor.create( element, {
+ licenseKey: 'GPL', // Or ''.
plugins: [
Essentials,
Paragraph
@@ -90,6 +91,8 @@ After refreshing the page, the editor should have two buttons at the top. If you
The configuration object we have just updated controls the features, appearance, and behavior of the editor. If you want to change any aspect of the editor, it is most likely through this object.
+The `licenseKey` option is needed for the editor to run. You can learn more about it in the {@link getting-started/licensing/license-key-and-activation License key and activation} guide.
+
## Editor methods
Now that you can type in the editor, let's test other editor methods besides `create()`. Add the following to the bottom of the `src/main.js` file. It will allow us to access the editor instance globally for testing purposes.
diff --git a/docs/tutorials/crash-course/view.md b/docs/tutorials/crash-course/view.md
index f745290b262..aedb4462167 100644
--- a/docs/tutorials/crash-course/view.md
+++ b/docs/tutorials/crash-course/view.md
@@ -76,6 +76,7 @@ Open `src/main.js` and update the configuration of the editor:
```js
const editor = await ClassicEditor.create( element, {
+ licenseKey: 'GPL', // Or ''.
plugins: [
Essentials,
Paragraph,
diff --git a/docs/tutorials/creating-simple-plugin-timestamp.md b/docs/tutorials/creating-simple-plugin-timestamp.md
index a0ededac2e0..7f9182821c2 100644
--- a/docs/tutorials/creating-simple-plugin-timestamp.md
+++ b/docs/tutorials/creating-simple-plugin-timestamp.md
@@ -13,13 +13,23 @@ We will create a toolbar button that will insert the current date and time at th
## Let's start!
-The easiest way to set up your project is to grab the starter files from our [GitHub repository for this tutorial](https://github.com/ckeditor/ckeditor5-tutorials-examples/tree/main/timestamp-plugin). We gathered all the necessary dependencies there, including some CKEditor 5 packages and other files needed to run the editor.
+The easiest way to set up your project is to grab the starter files from our GitHub repositories for this tutorial. Depending on your installation method, you can use:
+
+* [npm repository](https://github.com/ckeditor/ckeditor5-tutorials-examples/tree/main/timestamp-plugin/npm)
+* [CDN repository](https://github.com/ckeditor/ckeditor5-tutorials-examples/tree/main/timestamp-plugin/cdn)
+
+We gathered all the necessary dependencies there, including some CKEditor 5 packages and other files needed to run the editor.
The editor has already been created in the `main.js` file with some basic plugins. All you need to do is clone the repository, run the `npm install` command, and you can start coding right away.
```bash
git clone https://github.com/ckeditor/ckeditor5-tutorials-examples
-cd ckeditor5-tutorials-examples/timestamp-plugin/starter-files
+
+# npm
+cd ckeditor5-tutorials-examples/timestamp-plugin/npm/starter-files
+
+# CDN
+cd ckeditor5-tutorials-examples/timestamp-plugin/cdn/starter-files
npm install
npm run dev
@@ -27,10 +37,11 @@ npm run dev
## Creating a plugin
-All features in the CKEditor 5 are powered by plugins. To create our custom timestamp plugin, we need to import the base `Plugin` class.
+All features in the CKEditor 5 are powered by plugins. To create our custom timestamp plugin, we need to import the base `Plugin` class. Imports are the main difference between the CDN and npm installation methods. In the npm method, we use standard JavaScript syntax. The CDN script exposes a global variable we can use to access plugins.
We can now create a `Timestamp` class that extends the basic `Plugin` class. After we define it, we can add it to the editor's plugins array.
+
```js
import {
ClassicEditor,
@@ -53,6 +64,7 @@ class Timestamp extends Plugin {
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: 'GPL', // Or ''.
// Add the Timestamp plugin to config.plugins array.
plugins: [
Essentials, Paragraph, Heading, List, Bold, Italic, Timestamp
@@ -66,6 +78,7 @@ ClassicEditor
console.error( error.stack );
} );
```
+
The development server will refresh. The initialization of the timestamp plugin should be visible. You should see this in the browser (on the left) and the browser's development console (on the right):
@@ -79,6 +92,7 @@ Once we create a new instance of `ButtonView`, we will be able to customize it b
We also need to register our button in the editor's UI `componentFactory`, so it can be displayed in the toolbar. To do it, we will pass the name of the button in the `componentFactory.add` method, to be able to add it into the {@link getting-started/setup/toolbar toolbar} array.
+
```js
import {
ClassicEditor,
@@ -115,6 +129,7 @@ class Timestamp extends Plugin {
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: 'GPL', // Or ''.
plugins: [
Essentials, Paragraph, Heading, List, Bold, Italic, Timestamp
],
@@ -131,6 +146,7 @@ ClassicEditor
} );
```
+
Now, you should be able to see the Timestamp button. It does not do anything just yet, so let's change that.
@@ -146,6 +162,7 @@ To insert anything into the document structure, we need to {@link framework/arch
We will use the `insertContent()` method to insert our timestamp into the document. Inside, we just need to create a new text node with the `writer.createText()` method.
+
```js
class Timestamp extends Plugin {
init() {
@@ -178,6 +195,11 @@ class Timestamp extends Plugin {
}
}
```
+
+
+
+The main difference between installation methods is imports. That is why some snippets may be identical for both installation methods.
+
Well done! You implemented a CKEditor 5 plugin. You should be able to click and see that it works.
@@ -189,7 +211,12 @@ See the result in action.
## Full code
-If you got lost at any point, see [the final implementation of the plugin](https://github.com/ckeditor/ckeditor5-tutorials-examples/tree/main/timestamp-plugin/final-project). You can paste the code from `main.js`, or clone and install the whole thing, and it will run out of the box.
+If you got lost at any point, see the final implementation of the plugin for your installation method:
+
+* [npm final project](https://github.com/ckeditor/ckeditor5-tutorials-examples/tree/main/timestamp-plugin/npm/final-project)
+* [CDN final project](https://github.com/ckeditor/ckeditor5-tutorials-examples/tree/main/timestamp-plugin/cdn/final-project)
+
+You can paste the code from `main.js` or clone and install the whole thing, and it will run out of the box.
**What's next**
diff --git a/docs/tutorials/widgets/data-from-external-source.md b/docs/tutorials/widgets/data-from-external-source.md
index 17ca5350952..5075b271317 100644
--- a/docs/tutorials/widgets/data-from-external-source.md
+++ b/docs/tutorials/widgets/data-from-external-source.md
@@ -112,6 +112,7 @@ import 'ckeditor5/ckeditor5.css';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: 'GPL', // Or ''.
plugins: [ Essentials, Paragraph, Heading, List, Bold, Italic, ExternalDataWidget ],
toolbar: [ 'heading', 'bold', 'italic', 'numberedList', 'bulletedList', '|', 'undo', 'redo' ]
} )
@@ -435,6 +436,7 @@ import ExternalDataWidgetCommand from './externaldatawidgetcommand';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: 'GPL', // Or ''.
plugins: [ Essentials, Paragraph, Heading, List, Bold, Italic, ExternalDataWidget ],
// Insert the "external" button into the editor toolbar.
diff --git a/docs/tutorials/widgets/implementing-a-block-widget.md b/docs/tutorials/widgets/implementing-a-block-widget.md
index 900ffed63b0..a1305427adf 100644
--- a/docs/tutorials/widgets/implementing-a-block-widget.md
+++ b/docs/tutorials/widgets/implementing-a-block-widget.md
@@ -123,6 +123,7 @@ import SimpleBox from './simplebox/simplebox'; /
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: 'GPL', // Or ''.
plugins: [
Essentials, Paragraph, Heading, List, Bold, Italic,
SimpleBox // ADDED
@@ -382,6 +383,7 @@ import CKEditorInspector from '@ckeditor/ckeditor5-inspector'; /
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: 'GPL', // Or ''.
plugins: [
Essentials, Paragraph, Heading, List, Bold, Italic,
SimpleBox
@@ -809,6 +811,7 @@ The last thing you need to do is tell the editor to display the button in the to
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: 'GPL', // Or ''.
plugins: [ Essentials, Paragraph, Heading, List, Bold, Italic, SimpleBox ],
// Insert the "simpleBox" button into the editor toolbar.
toolbar: [ 'heading', 'bold', 'italic', 'numberedList', 'bulletedList', 'simpleBox' ]
diff --git a/docs/tutorials/widgets/implementing-an-inline-widget.md b/docs/tutorials/widgets/implementing-an-inline-widget.md
index 4260d84f750..9ec8fe2c91e 100644
--- a/docs/tutorials/widgets/implementing-an-inline-widget.md
+++ b/docs/tutorials/widgets/implementing-an-inline-widget.md
@@ -115,6 +115,7 @@ import 'ckeditor5/ckeditor5.css';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: 'GPL', // Or ''.
plugins: [ Essentials, Paragraph, Heading, List, Bold, Italic, Placeholder ],
toolbar: [ 'heading', 'bold', 'italic', 'numberedList', 'bulletedList', '|', 'undo', 'redo' ]
} )
@@ -539,6 +540,7 @@ import CKEditorInspector from '@ckeditor/ckeditor5-inspector';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: 'GPL', // Or ''.
plugins: [ Essentials, Paragraph, Heading, List, Bold, Italic, Placeholder ],
// Insert the "placeholder" dropdown into the editor toolbar.
@@ -624,6 +626,7 @@ The plugin is now ready to accept the configuration. Check how this works by add
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: 'GPL', // Or ''.
plugins: [ Essentials, Paragraph, Heading, List, Bold, Italic, Widget, Placeholder ],
toolbar: [ 'heading', 'bold', 'italic', 'numberedList', 'bulletedList', '|', 'placeholder' ],
placeholderConfig: {
diff --git a/docs/tutorials/widgets/using-react-in-a-widget.md b/docs/tutorials/widgets/using-react-in-a-widget.md
index 338238b8172..996a8ba22c7 100644
--- a/docs/tutorials/widgets/using-react-in-a-widget.md
+++ b/docs/tutorials/widgets/using-react-in-a-widget.md
@@ -21,7 +21,7 @@ Later on, you will use the "Product preview" feature to build a simple React app
There are a couple of things you should know before you start:
-* Since you are here, you probably have at least some basic understanding of what React is and how it works. But what you might not know is that CKEditor 5 has an official {@link getting-started/integrations/react **rich text editor component for React**} and it will be one of the key features used in this tutorial. Learning how to use it in your project is a good place to start.
+* Since you are here, you probably have at least some basic understanding of what React is and how it works. But what you might not know is that CKEditor 5 has an official {@link getting-started/integrations/react-default-npm **rich text editor component for React**} and it will be one of the key features used in this tutorial. Learning how to use it in your project is a good place to start.
* In this tutorial, you are going to implement a block editor widget and that itself could give you a headache. It is recommended to at least skim through the {@link tutorials/widgets/implementing-a-block-widget Implementing a block widget} tutorial to get a grip on editor widgets, their API, and possible use cases.
* Various parts of the {@link framework/architecture/intro CKEditor 5 architecture} section will be referenced as you go. While reading them is not necessary to finish this tutorial, it is recommended to read those guides at some point to get a better understanding of the mechanisms used in this tutorial.
@@ -345,7 +345,7 @@ export default function ProductPreview( props ) {
At the moment, you have CKEditor classes that bring the product preview into the content, a list of products, and a product component ready. It is time to glue things together in the `App` component.
-You are going to extend the [main application file](#lets-start) skeleton that you created earlier in this tutorial so it renders the {@link getting-started/integrations/react official `` React component} on the left side, and the list of available products on the right.
+You are going to extend the [main application file](#lets-start) skeleton that you created earlier in this tutorial so it renders the {@link getting-started/integrations/react-default-npm official `` React component} on the left side, and the list of available products on the right.
Have a look at the full source code of the `App` function:
@@ -436,6 +436,7 @@ export default function App( props ) {
editor={ ClassicEditor }
// The configuration of the instance.
config={ {
+ licenseKey: 'GPL', // Or ''.
plugins: [
// A set of editor features to be enabled and made available to the user.
Essentials, Heading, Bold, Italic, Underline,
diff --git a/docs/umberto.json b/docs/umberto.json
index b18fe935e56..147d60b00b4 100644
--- a/docs/umberto.json
+++ b/docs/umberto.json
@@ -89,12 +89,15 @@
"builds/guides/integration/content-styles.html": "getting-started/setup/css.html",
"builds/guides/integration/csp.html": "installation/advanced/csp.html",
"builds/guides/integration/features-html-output-overview.html": "framework/architecture/plugins.html#plugins-and-html-output",
- "builds/guides/integration/frameworks/angular.html": "getting-started/installation/angular.html",
+ "builds/guides/integration/frameworks/angular.html": "getting-started/installation/cloud/angular.html",
+ "getting-started/installation/angular.html": "getting-started/installation/cloud/angular.html",
"builds/guides/integration/frameworks/css.html": "getting-started/installation/css.html",
- "builds/guides/integration/frameworks/react.html": "getting-started/installation/react/react.html",
- "builds/guides/integration/frameworks/vuejs-v2.html": "getting-started/installation/vuejs-v2.html",
- "builds/guides/integration/frameworks/vuejs-v3.html": "getting-started/installation/vuejs-v3.html",
- "builds/guides/integration/frameworks/vuejs.html": "getting-started/installation/vuejs-v3.html",
+ "builds/guides/integration/frameworks/react.html": "getting-started/installation/self-hosted/react/react-default-npm.html",
+ "builds/guides/integration/frameworks/vuejs-v2.html": "getting-started/installation/cloud/vuejs-v2.html",
+ "getting-started/installation/vuejs-v2.html": "getting-started/installation/cloud/vuejs-v2.html",
+ "builds/guides/integration/frameworks/vuejs-v3.html": "getting-started/installation/cloud/vuejs-v3.html",
+ "getting-started/installation/vuejs-v3.html": "getting-started/installation/cloud/vuejs-v3.html",
+ "builds/guides/integration/frameworks/vuejs.html": "getting-started/installation/cloud/vuejs-v3.html",
"builds/guides/integration/installation.html": "installation/legacy-getting-started/quick-start.html",
"builds/guides/integration/saving-data.html": "getting-started/setup/getting-and-setting-data.html",
"installation/integrations/drupal.html": "getting-started/installation/drupal-real-time-collaboration.html",
@@ -146,7 +149,7 @@
"features/collaboration/real-time-collaboration/real-time-collaborative-comments.html": "features/collaboration/real-time-collaboration/real-time-collaboration-integration.html",
"features/collaboration/real-time-collaboration/real-time-collaborative-editing.html": "features/collaboration/real-time-collaboration/real-time-collaboration-integration.html",
"features/collaboration/track-changes.html": "features/collaboration/track-changes/track-changes.html",
- "features/easy-image.html": "features/images/image-upload/easy-image.html",
+ "features/easy-image.html": "features/images/image-upload/image-upload.html",
"features/easy-video.html": "features/media-embed.html",
"features/export-pdf.html": "features/converters/export-pdf.html",
"features/export-word.html": "features/converters/export-word.html",
@@ -160,7 +163,7 @@
"features/images/overview.html": "features/images/images-overview.html",
"features/image-upload.html": "features/images/image-upload/image-upload.html",
"features/image-upload/image-upload.html": "features/images/image-upload/image-upload.html",
- "features/image-upload/easy-image.html": "features/images/image-upload/easy-image.html",
+ "features/image-upload/easy-image.html": "features/images/image-upload/image-upload.html",
"features/image-upload/ckfinder.html": "features/file-management/ckfinder.html",
"features/image-upload/base64-upload-adapter.html": "features/images/image-upload/base64-upload-adapter.html",
"features/image-upload/simple-upload-adapter.html": "features/images/image-upload/simple-upload-adapter.html",
@@ -258,20 +261,20 @@
"installation/advanced/features-html-output-overview.html": "framework/architecture/plugins.html#plugins-and-html-output",
"installation/advanced/plugins.html": "framework/architecture/plugins.html",
"installation/advanced/saving-data.html": "getting-started/setup/getting-and-setting-data.html",
- "installation/frameworks/angular.html": "getting-started/installation/angular.html",
+ "installation/frameworks/angular.html": "getting-started/installation/cloud/angular.html",
"installation/frameworks/css.html": "getting-started/installation/css.html",
"installation/frameworks/index.html": "getting-started/installation/overview.html",
"installation/frameworks/overview.html": "getting-started/installation/overview.html",
"installation/frameworks/react.html": "getting-started/installation/react.html",
- "installation/frameworks/vuejs-v2.html": "getting-started/installation/vuejs-v2.html",
- "installation/frameworks/vuejs-v3.html": "getting-started/installation/vuejs-v3.html",
+ "installation/frameworks/vuejs-v2.html": "getting-started/installation/cloud/vuejs-v2.html",
+ "installation/frameworks/vuejs-v3.html": "getting-started/installation/cloud/vuejs-v3.html",
"installation/getting-started/basic-api.html": "getting-started/setup/editor-lifecycle.html",
"installation/getting-started/frameworks/css.html": "getting-started/installation/css.html",
- "installation/getting-started/frameworks/angular.html": "getting-started/installation/angular.html",
+ "installation/getting-started/frameworks/angular.html": "getting-started/installation/cloud/angular.html",
"installation/getting-started/frameworks/overview.html": "getting-started/installation/overview.html",
"installation/getting-started/frameworks/react.html": "getting-started/installation/react.html",
- "installation/getting-started/frameworks/vuejs-v2.html": "getting-started/installation/vuejs-v2.html",
- "installation/getting-started/frameworks/vuejs-v3.html": "getting-started/installation/vuejs-v3.html",
+ "installation/getting-started/frameworks/vuejs-v2.html": "getting-started/installation/cloud/vuejs-v2.html",
+ "installation/getting-started/frameworks/vuejs-v3.html": "getting-started/installation/cloud/vuejs-v3.html",
"installation/getting-started/installing-plugins.html": "getting-started/setup/configuration.html",
"installation/getting-started/maintenance.html": "updating/maintaining.html",
"installation/getting-started/migration-from-ckeditor-4.html": "updating/ckeditor4/migration-from-ckeditor-4.html",
@@ -282,9 +285,9 @@
"installation/getting-started/configuration.html#adding-simple-standalone-features": "installation/legacy-getting-started/extending-features.html",
"support/getting-support.html": "support/index.html",
"support/versioning-policy.html": "updating/versioning-policy.html",
- "builds/guides/license-and-legal.html": "support/license-and-legal.html",
- "builds/guides/support/license-and-legal.html": "support/license-and-legal.html",
- "support/managing-ckeditor-logo.html": "getting-started/setup/managing-ckeditor-logo.html",
+ "builds/guides/license-and-legal.html": "getting-started/licensing/license-and-legal.html",
+ "builds/guides/getting-started/licensing/license-and-legal.html": "getting-started/licensing/license-and-legal.html",
+ "support/managing-ckeditor-logo.html": "getting-started/licensing/managing-ckeditor-logo.html",
"support/faq.html": "examples/how-tos.html",
"updating/changelog.html": "updating/guides/changelog.html",
"updating/migration-from-ckeditor-4.html": "updating/ckeditor4/migration-from-ckeditor-4.html",
@@ -310,23 +313,27 @@
"installation/getting-started/configuration.html": "getting-started/setup/configuration.html",
"features/ui-language.html": "getting-started/setup/ui-language.html",
"installation/index.html": "getting-started/index.html",
- "installation/getting-started/quick-start.html": "getting-started/installation/quick-start.html",
+ "installation/getting-started/quick-start.html": "getting-started/index.html",
+ "getting-started/installation/quick-start.html": "getting-started/index.html",
"installation/getting-started/predefined-builds.html": "getting-started/legacy/installation-methods/predefined-builds.html",
"installation/getting-started/quick-start-other.html": "getting-started/legacy/installation-methods/quick-start-other.html",
"installation/getting-started/getting-and-setting-data.html": "getting-started/setup/getting-and-setting-data.html",
"installation/getting-started/api-and-events.html": "getting-started/legacy/installation-methods/api-and-events.html",
"installation/getting-started/extending-features.html": "getting-started/legacy/installation-methods/extending-features.html",
"installation/integrations/overview.html": "getting-started/index.html#ckeditor-5-framework-integrations",
- "installation/integrations/angular.html": "getting-started/installation/angular.html",
- "installation/integrations/react.html": "getting-started/installation/react/react.html",
- "installation/integrations/react/react.html": "getting-started/installation/react/react.html",
- "installation/integrations/react/react-multiroot.html": "getting-started/installation/react/react-multiroot.html",
- "installation/integrations/vuejs-v2.html": "getting-started/installation/vuejs-v2.html",
- "installation/integrations/vuejs-v3.html": "getting-started/installation/vuejs-v3.html",
- "installation/integrations/laravel.html": "getting-started/installation/laravel.html",
- "installation/integrations/dotnet.html": "getting-started/installation/dotnet.html",
+ "installation/integrations/angular.html": "getting-started/installation/cloud/angular.html",
+ "installation/integrations/react.html": "getting-started/installation/self-hosted/react/react-default-npm.html",
+ "installation/integrations/react/react.html": "getting-started/installation/self-hosted/react/react-default-npm.html",
+ "installation/integrations/react/react-multiroot.html": "getting-started/installation/self-hosted/react/react-multiroot-npm.html",
+ "installation/integrations/vuejs-v2.html": "getting-started/installation/cloud/vuejs-v2.html",
+ "installation/integrations/vuejs-v3.html": "getting-started/installation/cloud/vuejs-v3.html",
+ "installation/integrations/laravel.html": "getting-started/installation/cloud/laravel.html",
+ "getting-started/installation/laravel.html": "getting-started/installation/cloud/laravel.html",
+ "installation/integrations/dotnet.html": "getting-started/installation/cloud/dotnet.html",
+ "getting-started/installation/dotnet.html": "getting-started/installation/cloud/dotnet.html",
"installation/integrations/css.html": "getting-started/installation/css.html",
- "installation/integrations/next-js.html": "getting-started/installation/next-js.html",
+ "installation/integrations/next-js.html": "getting-started/installation/cloud/next-js.html",
+ "getting-started/installation/next-js.html": "getting-started/installation/cloud/next-js.html",
"installation/integrations/other.html": "getting-started/index.html#ckeditor-5-framework-integrations",
"installation/plugins/plugins.html": "framework/architecture/plugins.html",
"installation/plugins/features-html-output-overview.html": "framework/architecture/plugins.html#plugins-and-html-output",
@@ -356,16 +363,24 @@
"tutorials/widgets/implementing-an-inline-widget.html": "framework/tutorials/widgets/implementing-an-inline-widget.html",
"tutorials/widgets/using-react-in-a-widget.html": "framework/tutorials/widgets/using-react-in-a-widget.html",
"tutorials/widgets/data-from-external-source.html": "framework/tutorials/widgets/data-from-external-source.html",
- "support/licensing/license-and-legal.html": "support/license-and-legal.html",
- "support/licensing/license-key-and-activation.html": "getting-started/setup/license-key-and-activation.html",
- "support/licensing/managing-ckeditor-logo.html": "getting-started/setup/managing-ckeditor-logo.html",
+ "support/licensing/license-and-legal.html": "getting-started/licensing/license-and-legal.html",
+ "support/licensing/license-key-and-activation.html": "getting-started/licensing/license-key-and-activation.html",
+ "getting-started/licensing/license-key-and-activation.html": "getting-started/licensing/license-key-and-activation.html",
+ "getting-started/setup/license-key-and-activation.html": "getting-started/licensing/license-key-and-activation.html",
+ "getting-started/setup/managing-ckeditor-logo.html": "getting-started/licensing/managing-ckeditor-logo.html",
"support/reporting-issues.html": "support/index.html#reporting-issues",
+ "support/license-and-legal": "getting-started/licensing/license-and-legal",
"examples/experiments/mermaid.html": "features/mermaid.html",
"features/toolbar/toolbar.html": "getting-started/setup/toolbar.html",
"features/toolbar/blocktoolbar.html": "getting-started/setup/toolbar.html#block-toolbar",
"features/toolbar/menubar.html": "getting-started/setup/menubar.html",
"examples/builds-custom/bottom-toolbar-editor.html": "examples/framework/bottom-toolbar-editor.html",
"examples/how-tos.html": "framework/how-tos.html",
+ "support/license-and-legal.html": "getting-started/licensing/license-and-legal.html",
+ "features/productivity-pack.html": "features/index.html",
+ "features/images/image-upload/easy-image.html": "features/images/image-upload/image-upload.html",
+ "getting-started/installation/react/react.html": "getting-started/installation/self-hosted/react/react-default-npm.html",
+ "getting-started/installation/react/react-multiroot.html": "getting-started/installation/self-hosted/react/react-multiroot-npm.html",
"examples/framework/content-placeholder.html": "features/merge-fields.html"
},
"scripts": {
@@ -416,7 +431,7 @@
}
},
"og": {
- "description": "Learn how to install, integrate, configure, and develop CKEditor 5. Get to know the CKEditor 5 Framework. Browse through the API documentation and online samples."
+ "description": "Learn how to install, integrate and configure CKEditor 5 Builds and how to work with CKEditor 5 Framework, customize it, create your own plugins and custom editors, change the UI or even bring your own UI to the editor. API reference and examples included."
},
"groups": [
{
@@ -432,10 +447,34 @@
"order": 30,
"categories": [
{
- "name": "React",
- "id": "react",
- "slug": "react",
- "order": 30
+ "name": "Cloud (CDN)",
+ "id": "cloud",
+ "slug": "cloud",
+ "order": 10,
+ "categories": [
+ {
+ "name": "React",
+ "id": "react-cdn",
+ "slug": "react",
+ "folded": true,
+ "order": 30
+ }
+ ]
+ },
+ {
+ "name": "Self-hosted (npm/ZIP)",
+ "id": "self-hosted",
+ "slug": "self-hosted",
+ "order": 20,
+ "categories": [
+ {
+ "name": "React",
+ "id": "react-npm",
+ "slug": "react",
+ "folded": true,
+ "order": 30
+ }
+ ]
}
]
},
@@ -445,6 +484,12 @@
"slug": "setup",
"order": 40
},
+ {
+ "name": "Licensing",
+ "id": "licensing",
+ "slug": "licensing",
+ "order": 50
+ },
{
"name": "Legacy installation methods",
"id": "legacy",
@@ -760,17 +805,23 @@
"navigationIncludeIndex": true,
"slug": "updating",
"categories": [
+ {
+ "name": "Migrations from npm to CDN",
+ "id": "migrations",
+ "slug": "migrations",
+ "order": 10
+ },
{
"name": "New installation methods",
"id": "nim-migration",
"slug": "nim-migration",
- "order": 10
+ "order": 20
},
{
"name": "CKEditor 4 migration",
"id": "ckeditor4-migration",
"slug": "ckeditor4",
- "order": 20
+ "order": 30
},
{
"name": "Update guides",
@@ -784,15 +835,7 @@
"name": "Support",
"id": "support",
"navigationIncludeIndex": true,
- "slug": "support",
- "categories": [
- {
- "name": "Licensing and activation",
- "id": "licensing",
- "slug": "licensing",
- "order": 20
- }
- ]
+ "slug": "support"
}
]
}
diff --git a/docs/updating/ckeditor4-configuration-compatibility.md b/docs/updating/ckeditor4-configuration-compatibility.md
index b9c59b3161c..d815a48b96b 100644
--- a/docs/updating/ckeditor4-configuration-compatibility.md
+++ b/docs/updating/ckeditor4-configuration-compatibility.md
@@ -285,7 +285,7 @@ Note: In CKEditor 5, the number of options was reduced on purpose. Configur
easyimage_class easyimage_defaultStyle easyimage_styles easyimage_toolbar |
- Refer to the {@link features/easy-image Easy Image} and {@link features/images-overview Images} feature guides to learn more about image-related features and Easy Image integration in CKEditor 5. |
+ Refer to the {@link features/images-overview Images} feature guides to learn more about image-related features in CKEditor 5. |
editorplaceholder editorplaceholder_delay |
diff --git a/docs/updating/migration-from-ckeditor-4.md b/docs/updating/migration-from-ckeditor-4.md
index 2441a4b22a7..a488e979c96 100644
--- a/docs/updating/migration-from-ckeditor-4.md
+++ b/docs/updating/migration-from-ckeditor-4.md
@@ -104,10 +104,10 @@ Here are the key differences between the two editor versions:
File management and image upload
- CKFinder, Easy Image
+ CKFinder
|
- CKBox, CKFinder, Easy Image
+ CKBox, CKFinder
|
diff --git a/docs/updating/migration-to-cdn/angular.md b/docs/updating/migration-to-cdn/angular.md
new file mode 100644
index 00000000000..a56992fea9d
--- /dev/null
+++ b/docs/updating/migration-to-cdn/angular.md
@@ -0,0 +1,178 @@
+---
+menu-title: Angular
+meta-title: Angular CKEditor 5 - migrate integration from npm to CDN | CKEditor 5 documentation
+meta-description: Migrate Angular CKEditor 5 integration from npm to CDN in a few simple steps. Learn how to install Angular CKEditor 5 integration in your project using the CDN.
+category: migrations
+order: 50
+---
+
+# Migrating Angular CKEditor 5 integration from npm to CDN
+
+This guide will help you migrate Angular CKEditor 5 integration from an NPM-based installation to a CDN-based one.
+
+## Prerequisites
+
+Remove the existing CKEditor 5 packages from your project. If you are using the NPM-based installation, you can remove it by running the following command:
+
+```bash
+npm uninstall ckeditor5 ckeditor5-premium-features
+```
+
+Upgrade the CKEditor 5 Angular integration to the latest version. You can find it in the {@link getting-started/integrations-cdn/angular Angular integration} documentation.
+
+Ensure that your testing suite uses real web browser environments for testing. If you are using `jsdom` or any other environment without a real DOM, you may need to adjust the testing suite configuration to use a real browser because CDN script injection might not be recognized properly in such environments.
+
+## Migration steps
+
+### Step 1: Remove CKEditor 5 imports
+
+If you have any CKEditor 5 imports in your Angular components, remove them. For example, remove lines like:
+
+```javascript
+import { ClassicEditor, /* ... other imports */ } from 'ckeditor5';
+import { AIAdapter, /* ... other imports */ } from 'ckeditor5-premium-features';
+```
+
+### Step 2: Update your Angular components to use CDN
+
+Replace the CKEditor 5 NPM package imports with the CDN script imports and use the `loadCKEditorCloud` function to load the CKEditor 5 scripts. The `loadCKEditorCloud` function is a part of the `@ckeditor/ckeditor5-angular` package and is used to load CKEditor 5 scripts from the CKEditor Cloud service.
+
+**Before:**
+
+```ts
+// app.component.ts
+
+import { Component, ViewEncapsulation } from '@angular/core';
+import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
+import { ClassicEditor, Bold, Essentials, Italic, Mention, Paragraph, Undo } from 'ckeditor5';
+import { SlashCommand } from 'ckeditor5-premium-features';
+
+@Component( {
+ selector: 'app-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.css'],
+ encapsulation: ViewEncapsulation.None,
+ imports: [ CKEditorModule ],
+ standalone: true
+} )
+export class AppComponent {
+ title = 'angular';
+
+ public Editor = ClassicEditor;
+ public config = {
+ licenseKey: '', // Or 'GPL'.
+ plugins: [ Bold, Essentials, Italic, Mention, Paragraph, SlashCommand, Undo ],
+ toolbar: [ 'undo', 'redo', '|', 'bold', 'italic' ],
+ // ... Other configuration ....
+ }
+}
+```
+
+**After:**
+
+```ts
+// app.component.ts
+
+import { Component, ViewEncapsulation } from '@angular/core';
+import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
+
+@Component( {
+ selector: 'app-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.css'],
+ encapsulation: ViewEncapsulation.None,
+ imports: [ CKEditorModule ],
+ standalone: true
+} )
+export class AppComponent {
+ title = 'angular';
+
+ public Editor;
+ public config;
+
+ public ngOnInit() {
+ // ADDED
+ loadCKEditorCloud( {
+ version: '43.0.0'
+ } )
+ .then( this._setupEditor.bind( this ) );
+ }
+
+ private _setupEditor( cloud ) {
+ const {
+ ClassicEditor, Bold, Essentials, Italic,
+ Mention, Paragraph, SlashCommand, Undo
+ } = cloud.CKEditor;
+
+ this.Editor = ClassicEditor;
+ this.config = {
+ licenseKey: '', // Or 'GPL'.
+ plugins: [ Bold, Essentials, Italic, Mention, Paragraph, SlashCommand, Undo ],
+ toolbar: [ 'undo', 'redo', '|', 'bold', 'italic' ],
+ // ... Other configuration ....
+ }
+ }
+}
+```
+
+```html
+// app.component.html
+
+
+>
+
+```
+
+### Step 3 (Optional): Migrate the CKEditor 5 Angular integration testing suite
+
+If you have any tests that use CKEditor 5 objects, you need to update them to use the `loadCKEditorCloud` function. Here is an example of migrating a test that uses the `ClassicEditor` object:
+
+**Before:**
+
+```javascript
+import { ClassicEditor, /* ... other imports */ } from 'ckeditor5';
+
+it( 'ClassicEditor test', () => {
+ // Your test that uses the CKEditor 5 object.
+} );
+```
+
+**After:**
+
+```javascript
+import { loadCKEditorCloud } from '@ckeditor/ckeditor5-angular';
+
+let cloud;
+
+beforeEach( async () => {
+ cloud = await loadCKEditorCloud( {
+ version: '{@var ckeditor5-version}',
+ } );
+} );
+
+it( 'ClassicEditor test', () => {
+ const { ClassicEditor, /* ... other imports */ } = cloud.CKEditor;
+
+ // Your test that uses the CKEditor 5 object.
+} );
+```
+
+### Step 4 (Optional): Clean up the document head entries before each test
+
+If you use a testing suite that does not clean up the document head entries before each test, you may need to do it manually. It is essential because the CKEditor 5 CDN script will inject the editor into the head section of your HTML file and, you need to ensure that the head section is clean before each test.
+
+However, there is one downside to this approach. Cleaning up the head entries before each test may slow down the test execution because the browser needs to download the CKEditor 5 script each time. In most cases, this should not be a problem, but if you notice that your tests are running slower, you may need to consider other solutions.
+
+Here is an example of how you can Clean up the document head entries before each test:
+
+```javascript
+import { removeAllCkCdnResources } from '@ckeditor/ckeditor5-integrations-common/test-utils';
+
+beforeEach( () => {
+ removeAllCkCdnResources();
+} );
+```
diff --git a/docs/updating/migration-to-cdn/react.md b/docs/updating/migration-to-cdn/react.md
new file mode 100644
index 00000000000..d910b061668
--- /dev/null
+++ b/docs/updating/migration-to-cdn/react.md
@@ -0,0 +1,461 @@
+---
+menu-title: React
+meta-title: React CKEditor 5 - migrate integration from npm to CDN | CKEditor 5 documentation
+meta-description: Migrate React CKEditor 5 integration from npm to CDN in a few simple steps. Learn how to install React CKEditor 5 integration in your project using the CDN.
+category: migrations
+order: 30
+---
+
+# Migrating CKEditor 5 React integration from npm to CDN
+
+This guide will help you migrate CKEditor 5 React integration from an NPM-based installation to a CDN-based one.
+
+## Prerequisites
+
+Remove the existing CKEditor 5 packages from your project. If you are using the NPM-based installation, you can remove it by running the following command:
+
+```bash
+npm uninstall ckeditor5 ckeditor5-premium-features
+```
+
+Upgrade the CKEditor 5 React integration to the latest version. You can find the latest version in the {@link getting-started/integrations-cdn/react-default-cdn React integration} documentation.
+
+Ensure that your testing suite uses real web browser environments for testing. If you are using `jsdom` or any other environment without a real DOM, you may need to adjust the testing suite configuration to use a real browser because CDN script injection might not be recognized properly in such environments.
+
+## Migration steps
+
+### Migrate `CKEditor` component
+
+#### Step 1: Remove CKEditor 5 imports
+
+If you have any CKEditor 5 imports in your test files, remove them. For example, remove lines like:
+
+```javascript
+import { ClassicEditor, /* ... other imports */ } from 'ckeditor5';
+import { AIAdapter, /* ... other imports */ } from 'ckeditor5-premium-features';
+```
+
+#### Step 2: Use `useCKEditorCloud` hook to load CKEditor 5 from CDN
+
+The `useCKEditorCloud` function is a hook that allows you to load CKEditor 5 from the CDN. It returns an object with the `CKEditor` and `CKEditorPremiumFeatures` properties. Here is an example of migrating the basic CKEditor 5 React component:
+
+**Before:**
+
+```jsx
+import { CKEditor } from '@ckeditor/ckeditor5-react';
+import { ClassicEditor, Bold, Essentials, Italic, Mention, Paragraph, Undo } from 'ckeditor5';
+import { SlashCommand } from 'ckeditor5-premium-features';
+
+import 'ckeditor5/ckeditor5.css';
+import 'ckeditor5-premium-features/ckeditor5-premium-features.css';
+
+function App() {
+ return (
+ ', // Or 'GPL'.
+ toolbar: {
+ items: [ 'undo', 'redo', '|', 'bold', 'italic' ],
+ },
+ plugins: [
+ Bold, Essentials, Italic, Mention, Paragraph, SlashCommand, Undo
+ ],
+ mention: {
+ // Mention configuration
+ },
+ initialData: 'Hello from CKEditor 5 in React!
',
+ } }
+ />
+ );
+}
+
+export default App;
+```
+
+**After:**
+
+```jsx
+import { CKEditor, useCKEditorCloud } from '@ckeditor/ckeditor5-react';
+
+function App() {
+ // Load CKEditor 5 from the CKEditor Cloud, using the `useCKEditorCloud` hook.
+ // It'll inject the CKEditor 5 scripts and styles into your document head and after
+ // successful loading, it'll return the `CKEditor` and `CKEditorPremiumFeatures` objects.
+ const cloud = useCKEditorCloud( {
+ version: '{@var ckeditor5-version}', // Required. The version of CKEditor 5 to load.
+ premium: true // Optional. Set to `true` if you want to use premium features.
+ } );
+
+ if ( cloud.status === 'loading' ) {
+ return Loading...
;
+ }
+
+ if ( cloud.status === 'error' ) {
+ console.error( cloud.error );
+
+ return Error!
;
+ }
+
+ // Pick the CKEditor 5 plugins you want to use.
+ const {
+ ClassicEditor, Bold, Essentials, Italic,
+ Mention, Paragraph, SlashCommand, Undo
+ } = cloud.CKEditor;
+
+ const { SlashCommand } = cloud.CKEditorPremiumFeatures;
+
+ return (
+ ', // Or 'GPL'.
+ initialData: 'Hello from CKEditor 5 in React!
',
+ } }
+ />
+ );
+}
+```
+
+#### Step 3 (Optional): Migrate the CKEditor 5 React testing suite
+
+If you have any tests that use CKEditor 5 objects, you need to update them to use the `loadCKEditorCloud` function. Here is an example of migrating a test that uses the `ClassicEditor` object:
+
+**Before:**
+
+```javascript
+import { ClassicEditor, /* ... other imports */ } from 'ckeditor5';
+import { AIAdapter, /* ... other imports */ } from 'ckeditor5-premium-features';
+
+it( 'ClassicEditor test', () => {
+ render(
+
+ );
+} );
+```
+
+**After:**
+
+```javascript
+// It may be counterintuitive that in tests you need to use `loadCKEditorCloud` instead of `useCKEditorCloud`.
+// The reason for this is that `useCKEditorCloud` is a React hook and can only be used in React components,
+// while tests are typically written as functions in testing suites. Therefore, in tests, you should use
+// the `loadCKEditorCloud` function to load CKEditor 5 from the CKEditor Cloud and obtain the necessary
+// CKEditor 5 objects. This allows you to properly test your CKEditor 5 integration without any issues.
+
+import { loadCKEditorCloud } from '@ckeditor/ckeditor5-react';
+
+let cloud;
+
+beforeEach( async () => {
+ cloud = await loadCKEditorCloud( {
+ version: '{@var ckeditor5-version}',
+ } );
+} );
+
+it( 'ClassicEditor test', () => {
+ const { ClassicEditor } = cloud.CKEditor;
+
+ render(
+
+ );
+
+ // Rest of your test.
+} );
+```
+
+#### Step 4 (Optional): Clean up the document head entries before each test
+
+The `useCKEditorCloud` hook under the hood injects the CKEditor 5 scripts and styles into your document head. If you use a testing suite that does not Clean up the document head entries before each test, you may need to do it manually. This is important because the `useCKEditorCloud` hook might reuse the same head entries for each test, which can lead to skipping the `loading` state and directly going to the `success` state. It may cause some tests that rely on the `loading` state to fail.
+
+However, there is one downside to this approach. Cleaning up the head entries before each test may slow down the test execution because the browser needs to download the CKEditor 5 script each time. In most cases, this should not be a problem, but if you notice that your tests are running slower, you may need to consider other solutions.
+
+Here is an example of how you can Clean up the document head entries before each test:
+
+```javascript
+import { removeAllCkCdnResources } from '@ckeditor/ckeditor5-integrations-common/test-utils';
+
+beforeEach( () => {
+ removeAllCkCdnResources();
+} );
+```
+
+The code above will remove all CKEditor 5 CDN scripts, style sheets, and Window objects from the head section of your HTML file before each test, making sure that the `useCKEditorCloud` hook will inject the CKEditor 5 scripts and styles again.
+
+### Migrate `CKEditorContext` component
+
+#### Step 1: Remove CKEditor 5 imports
+
+If you have any CKEditor 5 imports in your test files, remove them. For example, remove lines like:
+
+```javascript
+import { ClassicEditor, ... } from 'ckeditor5';
+import { AIAdapter, ... } from 'ckeditor5-premium-features';
+```
+
+#### Step 2: Use `useCKEditorCloud` hook to load CKEditor 5 Context from CDN
+
+If you use the `CKEditorContext` component, you need to update it to use the `useCKEditorCloud` hook. Here is an example of migrating the `CKEditorContext` component:
+
+**Before:**
+
+```jsx
+import { ClassicEditor, Context, Bold, Essentials, Italic, Paragraph, ContextWatchdog } from 'ckeditor5';
+import { CKEditor, CKEditorContext } from '@ckeditor/ckeditor5-react';
+
+import 'ckeditor5/ckeditor5.css';
+
+function App() {
+ return (
+
+ ', // Or 'GPL'.
+ plugins: [ Essentials, Bold, Italic, Paragraph ],
+ toolbar: [ 'undo', 'redo', '|', 'bold', 'italic' ],
+ } }
+ data='Hello from the first editor working with the context!
'
+ onReady={ ( editor ) => {
+ // You can store the "editor" and use when it is needed.
+ console.log( 'Editor 1 is ready to use!', editor );
+ } }
+ />
+
+ ', // Or 'GPL'.
+ plugins: [ Essentials, Bold, Italic, Paragraph ],
+ toolbar: [ 'undo', 'redo', '|', 'bold', 'italic' ],
+ } }
+ data='Hello from the second editor working with the context!
'
+ onReady={ ( editor ) => {
+ // You can store the "editor" and use when it is needed.
+ console.log( 'Editor 2 is ready to use!', editor );
+ } }
+ />
+
+ );
+}
+
+export default App;
+```
+
+**After:**
+
+```javascript
+import { CKEditor, CKEditorContext, useCKEditorCloud } from '@ckeditor/ckeditor5-react';
+
+function App() {
+ // Load CKEditor 5 from the CKEditor Cloud, using the `useCKEditorCloud` hook.
+ // It'll inject the CKEditor 5 scripts and styles into your document head and after
+ // successful loading, it'll return the `CKEditor` and `CKEditorPremiumFeatures` objects.
+ const cloud = useCKEditorCloud( {
+ version: '{@var ckeditor5-version}', // Required. The version of CKEditor 5 to load.
+ premium: true // Optional. Set to `true` if you want to use premium features.
+ } );
+
+ if ( cloud.status === 'loading' ) {
+ return Loading...
;
+ }
+
+ if ( cloud.status === 'error' ) {
+ console.error( cloud.error );
+
+ return Error!
;
+ }
+
+ // Pick the CKEditor 5 plugins you want to use.
+ const {
+ ClassicEditor, Bold, Essentials, Italic, Paragraph,
+ Context, ContextWatchdog
+ } = cloud.CKEditor;
+
+ return (
+
+ ', // Or 'GPL'.
+ plugins: [ Essentials, Bold, Italic, Paragraph ],
+ toolbar: [ 'undo', 'redo', '|', 'bold', 'italic' ],
+ } }
+ data='Hello from the first editor working with the context!
'
+ onReady={ ( editor ) => {
+ // You can store the "editor" and use when it is needed.
+ console.log( 'Editor 1 is ready to use!', editor );
+ } }
+ />
+
+ ', // Or 'GPL'.
+ plugins: [ Essentials, Bold, Italic, Paragraph ],
+ toolbar: [ 'undo', 'redo', '|', 'bold', 'italic' ],
+ } }
+ data='Hello from the second editor working with the context!
'
+ onReady={ ( editor ) => {
+ // You can store the "editor" and use when it is needed.
+ console.log( 'Editor 2 is ready to use!', editor );
+ } }
+ />
+
+ );
+}
+```
+
+#### Next steps
+
+Now that you have migrated your CKEditor 5 React Context integration to use the CDN, you can continue with the next steps, such as the migration testing suite. It is identical to the steps described in the previous section.
+
+### Migrate `useMultiRootEditor` hook
+
+#### Step 1: Remove CKEditor 5 imports
+
+If you have any CKEditor 5 imports in your test files, remove them. For example, remove lines like:
+
+```javascript
+import { ClassicEditor, /* ... other imports */ } from 'ckeditor5';
+import { AIAdapter, /* ... other imports */ } from 'ckeditor5-premium-features';
+```
+
+#### Step 2: Use `withCKEditorCloud` HOC to load CKEditor 5 from CDN
+
+If you use the `useMultiRootEditor` hook, you need to update it to use the `withCKEditorCloud` HOC. Here is an example of migrating the `useMultiRootEditor` hook:
+
+**Before:**
+
+```jsx
+import { MultiRootEditor, Bold, Essentials, Italic, Paragraph } from 'ckeditor5';
+import { useMultiRootEditor } from '@ckeditor/ckeditor5-react';
+
+import 'ckeditor5/ckeditor5.css';
+
+const App = () => {
+ const editorProps = {
+ editor: MultiRootEditor,
+ data: {
+ intro: 'React multi-root editor
',
+ content: 'Hello from CKEditor 5 multi-root!
'
+ },
+ config: {
+ plugins: [ Essentials, Bold, Italic, Paragraph ],
+ toolbar: {
+ items: [ 'undo', 'redo', '|', 'bold', 'italic' ]
+ },
+ }
+ };
+
+ const {
+ editor,
+ toolbarElement,
+ editableElements,
+ data,
+ setData,
+ attributes,
+ setAttributes
+ } = useMultiRootEditor( editorProps );
+
+ return (
+
+ { toolbarElement }
+ { editableElements }
+
+ );
+}
+
+export default App;
+```
+
+**After:**
+
+```jsx
+import { withCKEditorCloud } from '@ckeditor/ckeditor5-react';
+
+/**
+ * The `withCKEditorCloud` HOC allows you to load CKEditor 5 from the CKEditor Cloud and inject loaded data
+ * as `cloud` property into your component. Configuration of the `cloud` passed to `withCKEditorCloud` is
+ * the same as for the `useCKEditorCloud` hook and you can specify the version of CKEditor 5 to load and
+ * optionally enable premium features.
+ */
+const withCKCloud = withCKEditorCloud( {
+ cloud: {
+ version: '43.0.0',
+ languages: [ 'en', 'de' ]
+ },
+
+ // Optional. Render error when loading CKEditor 5 from the CKEditor Cloud fails.
+ renderError: ( error ) => {
+ console.error( error );
+
+ return Error!
;
+ },
+
+ // Optional: Render loading state when CKEditor 5 is being loaded from the CKEditor Cloud.
+ renderLoader: () => Loading...
,
+} );
+
+const App = withCKCloud( ( { cloud } ) => {
+ const {
+ MultiRootEditor,
+ Bold,
+ Essentials,
+ Italic,
+ Paragraph
+ } = cloud.CKEditor;
+
+ const editorProps = {
+ editor: MultiRootEditor,
+ data: {
+ intro: 'React multi-root editor
',
+ content: 'Hello from CKEditor 5 multi-root!
'
+ },
+ config: {
+ plugins: [ Essentials, Bold, Italic, Paragraph ],
+ toolbar: {
+ items: [ 'undo', 'redo', '|', 'bold', 'italic' ]
+ },
+ }
+ };
+
+ const {
+ editor,
+ toolbarElement,
+ editableElements,
+ data,
+ setData,
+ attributes,
+ setAttributes
+ } = useMultiRootEditor( editorProps );
+
+ return (
+
+ { toolbarElement }
+ { editableElements }
+
+ );
+} );
+```
diff --git a/docs/updating/migration-to-cdn/testing-suite.md b/docs/updating/migration-to-cdn/testing-suite.md
new file mode 100644
index 00000000000..69f59b7a480
--- /dev/null
+++ b/docs/updating/migration-to-cdn/testing-suite.md
@@ -0,0 +1,90 @@
+---
+menu-title: Testing suite
+meta-title: Vanilla JS CKEditor 5 - migrate testing suite from npm to CDN | CKEditor 5 documentation
+meta-description: Migrate CKEditor 5 testing suite from npm to CDN in a few simple steps. Learn how to install CKEditor 5 testing suite in your project using the CDN.
+category: migrations
+order: 20
+---
+
+# Migrating CKEditor 5 testing suite from npm to CDN
+
+This guide will help you migrate the CKEditor 5 testing suite from an NPM-based installation to a CDN-based one.
+
+## Prerequisites
+
+Ensure that your testing suite uses real web browser environments for testing. If you are using `jsdom` or any other environment without a real DOM, you may need to adjust the testing suite configuration to use a real browser because CDN script injection might not be recognized properly in such environments.
+
+## Migration steps
+
+### Step 1: Remove CKEditor 5 imports
+
+If you have any CKEditor 5 imports in your test files, remove them. For example, remove lines like:
+
+```javascript
+import { ClassicEditor, ... } from 'ckeditor5';
+import { AIAdapter, ... } from 'ckeditor5-premium-features';
+```
+
+### Step 2: Update your test files to use CDN
+
+**Before:**
+
+```javascript
+import { ClassicEditor, /* ... other imports */ } from 'ckeditor5';
+
+it( 'ClassicEditor test', () => {
+ // Your test that use CKEditor 5 object.
+} );
+```
+
+**After:**
+
+```javascript
+import { loadCKEditorCloud } from '@ckeditor/ckeditor5-integrations-common';
+
+let cloud;
+
+beforeEach( async () => {
+ cloud = await loadCKEditorCloud( {
+ version: '{@var ckeditor5-version}',
+ } );
+} );
+
+it( 'ClassicEditor test', () => {
+ const { ClassicEditor } = cloud;
+
+ // Your test that use CKEditor 5 object.
+} );
+```
+
+### Step 3 (Optional): Clean up the document head entries before each test
+
+If you use a testing suite that does not clean up the document head entries before each test, you may need to do it manually. It is essential because the CKEditor 5 CDN script will inject the editor into the head section of your HTML file, and you need to ensure that the head section is clean before each test.
+
+However, there is one downside to this approach. Cleaning up the head entries before each test may slow down the test execution because the browser needs to download the CKEditor 5 script each time. In most cases, this should not be a problem, but if you notice that your tests are running slower, you may need to consider other solutions.
+
+Here is an example of how you can clean up the document head entries before each test:
+
+```javascript
+import { removeAllCkCdnResources } from '@ckeditor/ckeditor5-integrations-common/test-utils';
+
+beforeEach( () => {
+ removeAllCkCdnResources();
+} );
+```
+
+The code above will remove all CKEditor 5 CDN scripts, style sheets, and Window objects from the head section of your HTML file before each test.
+
+## Known issues
+
+### Slow test execution
+
+If you notice that your tests are running slower after migrating to CDN, it may be caused by the browser downloading the CKEditor 5 script each time the test is executed. While it is not recommended to disable the head cleanup before each test, you may disable it if you notice a significant slowdown in your test execution and your code handles the CKEditor 5 script async injection properly.
+
+### Script injection issues
+
+If you notice that the CKEditor 5 script is not injected properly, ensure that your testing suite uses a real browser environment for testing. If you use `jsdom` or any other environment without a real DOM, you may need to adjust the testing suite configuration to use a real browser. Consider using tools like:
+
+* [Vitest](https://vitest.dev/)
+* [Playwright](https://playwright.dev/)
+* [Cypress](https://www.cypress.io/)
diff --git a/docs/updating/migration-to-cdn/vanilla-js.md b/docs/updating/migration-to-cdn/vanilla-js.md
new file mode 100644
index 00000000000..917f38c52a6
--- /dev/null
+++ b/docs/updating/migration-to-cdn/vanilla-js.md
@@ -0,0 +1,131 @@
+---
+menu-title: Vanilla JS
+meta-title: Vanilla JS CKEditor 5 - migrate from npm to CDN | CKEditor 5 documentation
+meta-description: Migrate CKEditor 5 from npm to CDN in a few simple steps. Learn how to install CKEditor 5 in your project using the CDN.
+category: migrations
+order: 10
+---
+
+# Migrating CKEditor 5 from npm to CDN
+
+This guide will help you migrate CKEditor 5 from an NPM-based installation to a CDN-based one. CDN-based installations can simplify the setup process by providing a bundler-agnostic way to lazy initialization of CKEditor 5 scripts and styles injection. It reduces complexity in the project setup.
+
+## Prerequisites
+
+If you use frameworks like [Laravel](https://laravel.com/), [Symfony](https://symfony.com/), or [Ruby on Rails](https://rubyonrails.org/), modify the head section in the main layout file and follow the traditional HTML integration to install CKEditor 5.
+
+However, if you use SPA frameworks like [React](https://reactjs.org/), [Angular](https://angular.io/), [Vue.js](https://vuejs.org/), or [Svelte](https://svelte.dev/) and do not use official integrations, you may need to follow different steps to migrate CKEditor 5 from npm to CDN. In this case, you can utilize the lazy injection of CKEditor 5 since you cannot directly modify the head section.
+
+## Traditional HTML integration
+
+The traditional HTML integration to installing CKEditor 5 involves modification of the HTML head section to include the CKEditor 5 script from the CDN. The editor is then initialized using the `window.CKEDITOR` global variable.
+
+### Step 1: Update your HTML file
+
+First, update your HTML file to include the CKEditor 5 script from the CDN. Add the following script and style sheet tag inside the `` section of your HTML file:
+
+```html
+
+
+
+
+
+ CKEditor 5 CDN Example
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Step 2: Replace CKEditor 5 imports with `window.CKEDITOR`
+
+Since the CKEditor 5 script is now included via the CDN, you can access the `ClassicEditor` object directly in your JavaScript file using the `window.CKEDITOR` global variable. It means that `import` statements are no longer needed and you can remove them from your JavaScript files. Here is an example of migrating the CKEditor 5 initialization code:
+
+**Before:**
+
+```javascript
+import { ClassicEditor } from 'ckeditor5';
+import { AIAdapter, /* ... other imports */ } from 'ckeditor5-premium-features';
+
+ClassicEditor
+ .create( document.querySelector('#editor'), {
+ licenseKey: '', // Or 'GPL'.
+ // ... other configuration
+ } )
+ .catch( error => {
+ console.error(error);
+ } );
+```
+
+**After:**
+
+```javascript
+const { ClassicEditor } = window.CKEDITOR;
+const { AIAdapter, /* ... other imports */ } = window.CKEDITOR_PREMIUM_FEATURES;
+
+ClassicEditor
+ .create( document.querySelector('#editor'), {
+ licenseKey: '', // Or 'GPL'.
+ // ... other configuration
+ } )
+ .catch( error => {
+ console.error(error);
+ } );
+```
+
+## Using lazy injection of CKEditor 5
+
+If you prefer to automatically inject the CKEditor 5 script into your HTML file, you can migrate your project using the `@ckeditor/ckeditor5-integrations-common` package. This package provides a `loadCKEditorCloud` function that automatically injects the CKEditor 5 scripts and styles into your HTML file. It may be useful when your project uses a bundler like Webpack or Rollup and you cannot modify your head section directly.
+
+### Step 1: Install the `@ckeditor/ckeditor5-integrations-common` Package
+
+First, install the `@ckeditor/ckeditor5-integrations-common` package using the following command:
+
+```bash
+npm install @ckeditor/ckeditor5-integrations-common
+```
+
+### Step 2: Replace CKEditor 5 Imports
+
+If you have any CKEditor 5 imports in your JavaScript files, remove them. For example, remove lines like:
+
+```javascript
+import { ClassicEditor, ... } from 'ckeditor5';
+import { AIAdapter, ... } from 'ckeditor5-premium-features';
+```
+
+Next, update your JavaScript file to use the `loadCKEditorCloud` function from the `@ckeditor/ckeditor5-integrations-common` package. Here is an example of migrating the CKEditor 5 initialization code:
+
+**Before:**
+
+```javascript
+import { ClassicEditor } from 'ckeditor5';
+
+ClassicEditor
+ .create( document.querySelector('#editor') )
+ .catch( error => {
+ console.error(error);
+ } );
+```
+
+**After:**
+
+```javascript
+import { loadCKEditorCloud } from '@ckeditor/ckeditor5-integrations-common';
+
+const { ClassicEditor } = await loadCKEditorCloud( {
+ version: '{@var ckeditor5-version}',
+} );
+```
+
+## Conclusion
+
+Following these steps, you successfully migrated CKEditor 5 from an NPM-based installation to a CDN-based installation using Vanilla JS. This approach simplifies the setup process and can help improve the performance of your application by reducing the bundle size.
diff --git a/docs/updating/migration-to-cdn/vuejs-v3.md b/docs/updating/migration-to-cdn/vuejs-v3.md
new file mode 100644
index 00000000000..889017ab451
--- /dev/null
+++ b/docs/updating/migration-to-cdn/vuejs-v3.md
@@ -0,0 +1,189 @@
+---
+menu-title: Vue 3+
+meta-title: Vue CKEditor 5 - migrate integration from npm to CDN | CKEditor 5 documentation
+meta-description: Migrate Vue 3+ CKEditor 5 integration from npm to CDN in a few simple steps. Learn how to install Vue 3+ CKEditor 5 integration in your project using the CDN.
+category: migrations
+order: 40
+---
+
+# Migrating Vue 3+ CKEditor 5 integration from npm to CDN
+
+This guide will help you migrate Vue 3 CKEditor 5 integration from an NPM-based installation to a CDN-based one.
+
+## Prerequisites
+
+Remove the existing CKEditor 5 packages from your project. If you are using the NPM-based installation, you can remove it by running the following command:
+
+```bash
+npm uninstall ckeditor5 ckeditor5-premium-features
+```
+
+Upgrade the CKEditor 5 Vue 3 integration to the latest version. You can find the latest version in the {@link getting-started/integrations-cdn/vuejs-v3 Vue 3 integration} documentation.
+
+Ensure that your testing suite uses real web browser environments for testing. If you are using `jsdom` or any other environment without a real DOM, you may need to adjust the testing suite configuration to use a real browser because CDN script injection might not be recognized properly in such environments.
+
+## Migration steps
+
+### Step 1: Remove CKEditor 5 imports
+
+If you have any CKEditor 5 imports in your Vue components, remove them. For example, remove lines like:
+
+```javascript
+import { ClassicEditor, /* ... other imports */ } from 'ckeditor5';
+import { AIAdapter, /* ... other imports */ } from 'ckeditor5-premium-features';
+```
+
+### Step 2: Update your Vue components to use CDN
+
+Replace the CKEditor 5 NPM package imports with the CDN script imports and use the `useCKEditorCloud` function to load the CKEditor 5 scripts. The `useCKEditorCloud` function is a part of the `@ckeditor/ckeditor5-vue` package and is used to load CKEditor 5 scripts from the CKEditor Cloud service.
+
+**Before:**
+
+```html
+
+ Using CKEditor 5 from NPM in Vue 3
+
+
+
+
+
+```
+
+**After:**
+
+```html
+
+ Using CKEditor 5 from NPM in Vue 3
+
+
+
+
+```
+
+### Step 3 (Optional): Migrate the CKEditor 5 Vue 3+ integration testing suite
+
+If you have any tests that use CKEditor 5 objects, you need to update them to use the `loadCKEditorCloud` function. Here is an example of migrating a test that uses the `ClassicEditor` object:
+
+**Before:**
+
+```javascript
+import { ClassicEditor, /* ... other imports */ } from 'ckeditor5';
+
+it( 'ClassicEditor test', () => {
+ // Your test that uses the CKEditor 5 object.
+} );
+```
+
+**After:**
+
+```javascript
+// It may be counterintuitive that in tests you need to use `loadCKEditorCloud` instead of `useCKEditorCloud`.
+// The reason for this is that `useCKEditorCloud` is composable and can only be used in Vue components,
+// while tests are typically written as functions in testing suites. Therefore, in tests, you should use
+// the `loadCKEditorCloud` function to load CKEditor 5 from the CKEditor Cloud and obtain the necessary
+// CKEditor 5 objects. This allows you to properly test your CKEditor 5 integration without any issues.
+
+import { loadCKEditorCloud } from '@ckeditor/ckeditor5-vue';
+
+let cloud;
+
+beforeEach( async () => {
+ cloud = await loadCKEditorCloud( {
+ version: '{@var ckeditor5-version}',
+ } );
+} );
+
+it( 'ClassicEditor test', () => {
+ const { ClassicEditor, ... } = cloud.CKEditor;
+
+ // Your test that uses the CKEditor 5 object.
+} );
+```
+
+#### Step 4 (Optional): Clean up the document head entries before each test
+
+The `useCKEditorCloud` composable under the hood injects the CKEditor 5 scripts and styles into your document head. If you use a testing suite that does not Clean up the document head entries before each test, you may need to do it manually. This is important because the `useCKEditorCloud` composable might reuse the same head entries for each test, which can lead to skipping the `loading` state and directly going to the `success` state. It may cause some tests that rely on the `loading` state to fail.
+
+However, there is one downside to this approach. Cleaning up the head entries before each test may slow down the test execution because the browser needs to download the CKEditor 5 script each time. In most cases, this should not be a problem, but if you notice that your tests are running slower, you may need to consider other solutions.
+
+Here is an example of how you can Clean up the document head entries before each test:
+
+```javascript
+import { removeAllCkCdnResources } from '@ckeditor/ckeditor5-integrations-common/test-utils';
+
+beforeEach( () => {
+ removeAllCkCdnResources();
+} );
+```
+
+The code above will remove all CKEditor 5 CDN scripts, style sheets, and Window objects from the head section of your HTML file before each test, making sure that the `useCKEditorCloud` composable will inject the CKEditor 5 scripts and styles again.
diff --git a/docs/updating/nim-migration/predefined-builds.md b/docs/updating/nim-migration/predefined-builds.md
index 626229974fe..50c3d46fc8b 100644
--- a/docs/updating/nim-migration/predefined-builds.md
+++ b/docs/updating/nim-migration/predefined-builds.md
@@ -1722,7 +1722,6 @@ One notable difference between the old build and the new ESM build is that the f
toolbar: [
'aiCommands', 'aiAssistant',
'|',
- // Productivity pack.
'tableOfContents', 'formatPainter', 'insertTemplate', 'caseChange',
'|',
'pagination',
diff --git a/docs/updating/update-to-38.md b/docs/updating/update-to-38.md
index ee129080916..d73c1d1aee9 100644
--- a/docs/updating/update-to-38.md
+++ b/docs/updating/update-to-38.md
@@ -45,7 +45,7 @@ Starting from version 38.0.0, all **open source installations** of CKEditor 
If you have a **commercial license**, you can hide the logo by adding {@link module:core/editor/editorconfig~EditorConfig#licenseKey `config.licenseKey`} to your configuration. If you already use pagination, productivity pack, or asynchronous collaboration features, you do not need to take any action as you should already have `config.licenseKey` in place. The logo will not be visible in your editor.
-We have prepared a detailed {@link getting-started/setup/managing-ckeditor-logo Managing the "Powered by CKEditor" logo} guide to help everyone through the transition and explain any concerns.
+We have prepared a detailed {@link getting-started/licensing/managing-ckeditor-logo Managing the "Powered by CKEditor" logo} guide to help everyone through the transition and explain any concerns.
### Introduction of color pickers to font color and font background color features
diff --git a/package.json b/package.json
index ee84d59d195..d10360fead0 100644
--- a/package.json
+++ b/package.json
@@ -130,7 +130,7 @@
"coveralls": "^3.1.0",
"date-fns": "^2.30.0",
"eslint": "^7.19.0",
- "eslint-config-ckeditor5": "^7.0.0",
+ "eslint-config-ckeditor5": "^7.1.0",
"estree-walker": "^3.0.3",
"fs-extra": "^11.1.1",
"glob": "^10.2.5",
diff --git a/packages/ckeditor5-alignment/docs/_snippets/features/custom-text-alignment-options.js b/packages/ckeditor5-alignment/docs/_snippets/features/custom-text-alignment-options.js
index c74ca218d76..c37942d410f 100644
--- a/packages/ckeditor5-alignment/docs/_snippets/features/custom-text-alignment-options.js
+++ b/packages/ckeditor5-alignment/docs/_snippets/features/custom-text-alignment-options.js
@@ -39,7 +39,8 @@ ClassicEditor
alignment: {
options: [ 'left', 'right' ]
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-alignment/docs/_snippets/features/custom-text-alignment-toolbar.js b/packages/ckeditor5-alignment/docs/_snippets/features/custom-text-alignment-toolbar.js
index 8eb648d8662..5beb4e8e40a 100644
--- a/packages/ckeditor5-alignment/docs/_snippets/features/custom-text-alignment-toolbar.js
+++ b/packages/ckeditor5-alignment/docs/_snippets/features/custom-text-alignment-toolbar.js
@@ -36,7 +36,8 @@ ClassicEditor
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-alignment/docs/_snippets/features/text-alignment.js b/packages/ckeditor5-alignment/docs/_snippets/features/text-alignment.js
index f0d0392c4c2..7790e2e2516 100644
--- a/packages/ckeditor5-alignment/docs/_snippets/features/text-alignment.js
+++ b/packages/ckeditor5-alignment/docs/_snippets/features/text-alignment.js
@@ -36,7 +36,8 @@ ClassicEditor
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-alignment/docs/features/text-alignment.md b/packages/ckeditor5-alignment/docs/features/text-alignment.md
index 70034d4183a..29f6d64e08c 100644
--- a/packages/ckeditor5-alignment/docs/features/text-alignment.md
+++ b/packages/ckeditor5-alignment/docs/features/text-alignment.md
@@ -26,15 +26,19 @@ Click inside a paragraph or a header and use the toolbar dropdown {@icon @ckedit
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Alignment } from 'ckeditor5';
ClassicEditor.
create( document.querySelector( '#editor' ), {
+ licenseKey: '' // Or 'GPL'.
plugins: [ Alignment, /* ... */ ],
toolbar: [ 'alignment', /* ... */ ]
+ alignment: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -55,6 +59,7 @@ For example, the following editor will support two alignment options: to the lef
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
alignment: {
options: [ 'left', 'right' ]
},
@@ -81,6 +86,7 @@ The following configuration will set `.my-align-left` and `.my-align-right` to l
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
alignment: {
options: [
{ name: 'left', className: 'my-align-left' },
@@ -102,6 +108,7 @@ You can choose to use the alignment dropdown (`'alignment'`) or configure the to
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
toolbar: [
'heading', '|', 'alignment:left', 'alignment:right', 'alignment:center', 'alignment:justify'
]
diff --git a/packages/ckeditor5-autoformat/docs/_snippets/features/autoformat.js b/packages/ckeditor5-autoformat/docs/_snippets/features/autoformat.js
index 2f9f546376c..12a04b405b8 100644
--- a/packages/ckeditor5-autoformat/docs/_snippets/features/autoformat.js
+++ b/packages/ckeditor5-autoformat/docs/_snippets/features/autoformat.js
@@ -59,7 +59,8 @@ ClassicEditor
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorBasic = editor;
diff --git a/packages/ckeditor5-autoformat/docs/features/autoformat.md b/packages/ckeditor5-autoformat/docs/features/autoformat.md
index 379f540d545..1fba6d24acd 100644
--- a/packages/ckeditor5-autoformat/docs/features/autoformat.md
+++ b/packages/ckeditor5-autoformat/docs/features/autoformat.md
@@ -52,13 +52,14 @@ The following {@link features/basic-styles basic styles} inline formatting optio
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Autoformat } from 'ckeditor5';
ClassicEditor.
create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Autoformat, /* ... */ ],
toolbar: [ /* ... */ ]
} )
diff --git a/packages/ckeditor5-autosave/docs/_snippets/features/autosave.js b/packages/ckeditor5-autosave/docs/_snippets/features/autosave.js
index 4addcc2259f..354b569a6d1 100644
--- a/packages/ckeditor5-autosave/docs/_snippets/features/autosave.js
+++ b/packages/ckeditor5-autosave/docs/_snippets/features/autosave.js
@@ -42,7 +42,8 @@ ClassicEditor
save( editor ) {
return saveData( editor.getData() );
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-autosave/docs/features/autosave.md b/packages/ckeditor5-autosave/docs/features/autosave.md
index e46740e5c2e..435c79bcc6b 100644
--- a/packages/ckeditor5-autosave/docs/features/autosave.md
+++ b/packages/ckeditor5-autosave/docs/features/autosave.md
@@ -37,7 +37,7 @@ How to understand this demo:
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list.
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list.
Assuming that you have implemented some form of the `saveData()` function that sends the data to your server and returns a promise which is resolved once the data is successfully saved, configuring the {@link module:autosave/autosave~Autosave} feature is simple:
@@ -46,19 +46,12 @@ import { ClassicEditor, Autosave } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [
- Autosave,
-
- // ... other plugins.
- ],
+ licenseKey: '', // Or 'GPL'.
+ plugins: [ Autosave, /* ... */ ],
autosave: {
- save( editor ) {
- return saveData( editor.getData() );
- }
- },
-
- // ... other configuration options.
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -82,12 +75,11 @@ One second is the default waiting time before the next save action if nothing ha
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
autosave: {
waitingTime: 5000, // in ms
save( editor ) {}
},
-
- // ... other configuration options.
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -100,12 +92,7 @@ The demo example at the beginning of this guide shows a simple integration of th
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [
- Autosave,
-
- // ... other plugins.
- ],
-
+ // ... Other configuration options ...
autosave: {
save( editor ) {
return saveData( editor.getData() );
diff --git a/packages/ckeditor5-basic-styles/docs/_snippets/features/basic-styles.js b/packages/ckeditor5-basic-styles/docs/_snippets/features/basic-styles.js
index 84179fa0503..347a661910c 100644
--- a/packages/ckeditor5-basic-styles/docs/_snippets/features/basic-styles.js
+++ b/packages/ckeditor5-basic-styles/docs/_snippets/features/basic-styles.js
@@ -34,7 +34,8 @@ ClassicEditor
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-basic-styles/docs/features/basic-styles.md b/packages/ckeditor5-basic-styles/docs/features/basic-styles.md
index 8c7573ce673..f6f9fdfcd5e 100644
--- a/packages/ckeditor5-basic-styles/docs/features/basic-styles.md
+++ b/packages/ckeditor5-basic-styles/docs/features/basic-styles.md
@@ -72,13 +72,14 @@ CKEditor 5 allows for typing both at the inner and outer boundaries of code
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the plugins which you need to your plugin list. Then, simply configure the toolbar items to make the features available in the user interface.
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the plugins which you need to your plugin list. Then, simply configure the toolbar items to make the features available in the user interface.
```js
import { ClassicEditor, Bold, Code, Italic, Strikethrough, Subscript, Superscript, Underline } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Bold, Code, Italic, Strikethrough, Subscript, Superscript, Underline ],
toolbar: {
items: [ 'bold', 'italic', 'underline', 'strikethrough', 'code', 'subscript', 'superscript' ]
diff --git a/packages/ckeditor5-block-quote/docs/_snippets/features/block-quote-source.js b/packages/ckeditor5-block-quote/docs/_snippets/features/block-quote-source.js
index eb60d3bbeaf..0cf4ad97c44 100644
--- a/packages/ckeditor5-block-quote/docs/_snippets/features/block-quote-source.js
+++ b/packages/ckeditor5-block-quote/docs/_snippets/features/block-quote-source.js
@@ -45,7 +45,8 @@ ClassicEditor.defaultConfig = {
},
image: {
toolbar: [ 'toggleImageCaption', 'imageTextAlternative', 'ckboxImageEdit' ]
- }
+ },
+ licenseKey: 'GPL'
};
window.ClassicEditor = ClassicEditor;
diff --git a/packages/ckeditor5-block-quote/docs/features/block-quote.md b/packages/ckeditor5-block-quote/docs/features/block-quote.md
index 3af0af0ca15..0e0167ebcb6 100644
--- a/packages/ckeditor5-block-quote/docs/features/block-quote.md
+++ b/packages/ckeditor5-block-quote/docs/features/block-quote.md
@@ -39,13 +39,14 @@ Support for nested block quotes is provided as backward compatibility for loadin
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, BlockQuote } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ BlockQuote, /* ... */ ],
toolbar: [ 'blockQuote', /* ... */ ]
} )
diff --git a/packages/ckeditor5-ckbox/docs/_snippets/features/ckbox.js b/packages/ckeditor5-ckbox/docs/_snippets/features/ckbox.js
index 57da1b01389..d27c9be1f64 100644
--- a/packages/ckeditor5-ckbox/docs/_snippets/features/ckbox.js
+++ b/packages/ckeditor5-ckbox/docs/_snippets/features/ckbox.js
@@ -61,7 +61,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
forceDemoLabel: true,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-ckbox/docs/features/ckbox.md b/packages/ckeditor5-ckbox/docs/features/ckbox.md
index 72974b56591..6cd96985d55 100644
--- a/packages/ckeditor5-ckbox/docs/features/ckbox.md
+++ b/packages/ckeditor5-ckbox/docs/features/ckbox.md
@@ -15,12 +15,7 @@ badges: [ premium ]
CKBox is a dedicated asset manager supporting file and image upload. The CKBox feature lets you easily upload various files and insert images and links to other files into your content. It also offers image conversion and optimization capabilities and provides a {@link features/images-responsive responsive images mechanism} for CKEditor 5.
- This is a premium feature and you need a subscription to use it. You can [purchase it here](https://ckeditor.com/pricing/) for your open-source CKEditor implementation. [Contact us](https://ckeditor.com/contact/?sales=true#contact-form) if:
- * CKEditor commercial license is needed for your application.
- * You need the **on-premises (self-hosted)** version of the service.
- * You have other licensing questions.
-
- You can also sign up for the [CKEditor Premium Features 30-day free trial](https://orders.ckeditor.com/trial/premium-features) to test the feature.
+ Unlock this feature with a CKEditor Paid Plan. [Sign up for a free trial](https://portal.ckeditor.com/checkout?plan=free), or [select the Plan](https://ckeditor.com/pricing/) that provides access to all the premium features you need.
## How CKBox enhances CKEditor 5
@@ -90,11 +85,11 @@ import { ClassicEditor, Image, ImageUpload, PictureEditing, CKBox, CKBoxImageEdi
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Image, PictureEditing, ImageUpload, CloudServices, CKBox, CKBoxImageEdit, /* ... */ ],
toolbar: [ 'ckbox', 'ckboxImageEdit', /* ... */ ], // Depending on your preference.
ckbox: {
- // Feature configuration including license key.
- // ...
+ // Configuration.
}
} )
.then( /* ... */ )
@@ -114,6 +109,7 @@ The snippet below shows an example image contextual toolbar configuration.
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
image: {
toolbar: [ 'toggleImageCaption', 'imageTextAlternative', 'ckboxImageEdit' ]
}
@@ -131,7 +127,7 @@ The feature can be configured via the {@link module:ckbox/ckboxconfig~CKBoxConfi
This is a premium feature. [Contact us](https://ckeditor.com/contact/?sales=true#contact-form) to receive an offer tailored to your needs.
- You can also sign up for the [CKEditor Premium Features 30-day free trial](https://orders.ckeditor.com/trial/premium-features) to test the feature.
+ You can also sign up for the [CKEditor Premium Features 14-day free trial](https://orders.ckeditor.com/trial/premium-features) to test the feature.
If you already have a valid license, please log into your [user dashboard](https://dashboard.ckeditor.com/) to access the feature settings.
@@ -143,12 +139,9 @@ After you purchase a license, log into the CKEditor Ecosystem customer dashboard
By default, the CKBox feature maps the uploaded image type to the category configured on the cloud service. You can override this behavior and provide your own mappings via the {@link module:ckbox/ckboxconfig~CKBoxConfig#defaultUploadCategories `config.ckbox.defaultUploadCategories`} configuration option. It is an object, where the keys define categories and their values are the types of images that will be uploaded to these categories. The categories might be referenced either by their name or by their ID. Referencing by ID is future-proof because it will not require configuration changes when a category name changes.
```js
-import { ClassicEditor, CKBox } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ CKBox, /* ... */ ],
- toolbar: [ 'ckbox', /* ... */ ],
+ // ... Other configuration options ...
ckbox: {
defaultUploadCategories: {
Bitmaps: [ 'bmp' ],
@@ -178,12 +171,9 @@ The [CKBox workspaces](https://ckeditor.com/docs/ckbox/latest/features/file-mana
If the user is assigned to more than one workspace, by default all the files uploaded directly from CKEditor are located in the first workspace in the list of workspaces allowed in the user's JWT token. This corresponds to uploads through drag and drop into the editor area, pasting images from the clipboard, or images uploaded using the Image {@icon @ckeditor/ckeditor5-core/theme/icons/image-upload.svg Image} feature. If you would like to define a specific workspace for files uploaded this way, you can define its ID in the `defaultUploadWorkspaceId` option. After that, all the files uploaded directly from CKEditor will be placed in the specified workspace.
```js
-import { ClassicEditor, CKBox } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ CKBox, /* ... */ ],
- toolbar: [ 'ckbox', /* ... */ ],
+ // ... Other configuration options ...
ckbox: {
tokenUrl: 'https://your.token.url',
// Sample workspace referenced by its ID.
@@ -201,12 +191,9 @@ You can obtain the list of available workspaces using the [Workspaces REST API](
After choosing an asset from the CKBox dialog, it is inserted into the editor content with a unique `data-ckbox-resource-id` attribute. If you want to disable it and do not want to add this attribute, set the {@link module:ckbox/ckboxconfig~CKBoxConfig#ignoreDataId `config.ckbox.ignoreDataId`} option to `true`:
```js
-import { ClassicEditor, CKBox } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ CKBox, /* ... */ ],
- toolbar: [ 'ckbox', /* ... */ ],
+ // ... Other configuration options ...
ckbox: {
ignoreDataId: true
}
@@ -220,12 +207,9 @@ ClassicEditor
By default, the CKBox dialog takes the current language from the editor. If you want to use a different language, you can set the language code in the {@link module:ckbox/ckboxconfig~CKBoxConfig#language `config.ckbox.language`} option:
```js
-import { ClassicEditor, CKBox } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ CKBox, /* ... */ ],
- toolbar: [ 'ckbox', /* ... */ ],
+ // ... Other configuration options ...
ckbox: {
language: 'es'
}
@@ -246,12 +230,9 @@ Also, make sure to include the translation file after loading the CKBox library:
The CKBox feature requires the token endpoint URL configured in the {@link module:ckbox/ckboxconfig~CKBoxConfig#tokenUrl `config.ckbox.tokenUrl`} key. If not explicitly provided, the token URL from {@link module:cloud-services/cloudservicesconfig~CloudServicesConfig#tokenUrl `config.cloudServices.tokenUrl`} is used instead. If both are provided, the token URL defined in `config.ckbox.tokenUrl` takes precedence over the `config.cloudServices.tokenUrl`.
```js
-import { ClassicEditor, CKBox } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ CKBox, /* ... */ ],
- toolbar: [ 'ckbox', /* ... */ ],
+ // ... Other configuration options ...
ckbox: {
tokenUrl: 'https://example.com/cs-token-endpoint'
}
@@ -265,12 +246,9 @@ ClassicEditor
If you host the cloud service in your environment, you should configure the base URL of the API service via the {@link module:ckbox/ckboxconfig~CKBoxConfig#serviceOrigin `config.ckbox.serviceOrigin`} option:
```js
-import { ClassicEditor, CKBox } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ CKBox, /* ... */ ],
- toolbar: [ 'ckbox', /* ... */ ],
+ // ... Other configuration options ...
ckbox: {
serviceOrigin: 'https://example.com/'
}
@@ -284,12 +262,9 @@ ClassicEditor
If you want to allow CKBox to edit external images, not hosted by the file manager (for example, pasted via URL) you need to whitelist the URLs of the images. You can do this using the {@link module:ckbox/ckboxconfig~CKBoxConfig#allowExternalImagesEditing `config.ckbox.allowExternalImagesEditing`} option:
```js
-import { CKBox } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ CKBox, /* ... */ ],
- toolbar: [ 'ckbox', /* ... */ ],
+ // ... Other configuration options ...
ckbox: {
allowExternalImagesEditing: [ 'origin', /^cksource.com/ ]
}
diff --git a/packages/ckeditor5-ckfinder/docs/_snippets/features/ckfinder-options.js b/packages/ckeditor5-ckfinder/docs/_snippets/features/ckfinder-options.js
index 64adfb20b84..9dd341d3e8f 100644
--- a/packages/ckeditor5-ckfinder/docs/_snippets/features/ckfinder-options.js
+++ b/packages/ckeditor5-ckfinder/docs/_snippets/features/ckfinder-options.js
@@ -29,7 +29,8 @@ ClassicEditor
width: 800,
resourceType: 'Images'
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-ckfinder/docs/_snippets/features/ckfinder-upload-only.js b/packages/ckeditor5-ckfinder/docs/_snippets/features/ckfinder-upload-only.js
index ba348ae843c..fe6ff0f1240 100644
--- a/packages/ckeditor5-ckfinder/docs/_snippets/features/ckfinder-upload-only.js
+++ b/packages/ckeditor5-ckfinder/docs/_snippets/features/ckfinder-upload-only.js
@@ -56,7 +56,8 @@ ClassicEditor
ckfinder: {
// eslint-disable-next-line max-len
uploadUrl: 'https://ckeditor.com/apps/ckfinder/3.5.0/core/connector/php/connector.php?command=QuickUpload&type=Files&responseType=json'
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-ckfinder/docs/_snippets/features/ckfinder.js b/packages/ckeditor5-ckfinder/docs/_snippets/features/ckfinder.js
index 836271cdeae..3984c74c41d 100644
--- a/packages/ckeditor5-ckfinder/docs/_snippets/features/ckfinder.js
+++ b/packages/ckeditor5-ckfinder/docs/_snippets/features/ckfinder.js
@@ -60,7 +60,8 @@ ClassicEditor
height: 600,
width: 800
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-ckfinder/docs/features/ckfinder.md b/packages/ckeditor5-ckfinder/docs/features/ckfinder.md
index 5d4d044e2e6..12ee31cbf1a 100644
--- a/packages/ckeditor5-ckfinder/docs/features/ckfinder.md
+++ b/packages/ckeditor5-ckfinder/docs/features/ckfinder.md
@@ -13,10 +13,8 @@ badges: [ premium ]
The CKFinder feature lets you insert images and links to files into your content. CKFinder is a powerful file manager with various image editing and image upload options.
-
- This is a premium feature and you need a license for it on top of your CKEditor 5 commercial license. [Contact us](https://ckeditor.com/contact/?sales=true#contact-form) to receive an offer tailored to your needs.
-
- You can also sign up for the [CKEditor Premium Features 30-day free trial](https://orders.ckeditor.com/trial/premium-features) to test the feature.
+
+ Unlock this feature with a CKEditor Paid Plan. [Sign up for a free trial](https://portal.ckeditor.com/checkout?plan=free), or [select the Plan](https://ckeditor.com/pricing/) that provides access to all the premium features you need.
## Demos
@@ -70,18 +68,18 @@ You can use this feature in the rich-text editor in two different ways:
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, CKFinder, CKFinderUploadAdapter } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ CKFinder, CKFinderUploadAdapter, /* ... */ ],
toolbar: [ 'ckfinder', 'uploadImage', /* ... */ ], // Depending on your preference.
ckfinder: {
- // Feature configuration.
- // ...
+ // Configuration.
}
} )
.then( /* ... */ )
@@ -99,15 +97,9 @@ This feature can upload images automatically to the server (for example, when th
Assuming that you [installed the CKFinder PHP server-side connector](https://ckeditor.com/docs/ckfinder/ckfinder3-php/quickstart.html#quickstart_installation_folders) (and it is available under `https://example.com/ckfinder/`), use the following [quick upload](https://ckeditor.com/docs/ckfinder/ckfinder3-php/commands.html#command_quick_upload) command URL to enable the image upload:
```js
-import { ClassicEditor, CKFinder, CKFinderUploadAdapter } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ CKFinder, CKFinderUploadAdapter, /* ... */ ],
-
- // Enable the insert image button in the toolbar.
- toolbar: [ 'uploadImage', /* ... */ ],
-
+ // ... Other configuration options ...
ckfinder: {
// Upload the images to the server using the CKFinder QuickUpload command.
uploadUrl: 'https://example.com/ckfinder/core/connector/php/connector.php?command=QuickUpload&type=Images&responseType=json'
@@ -139,15 +131,9 @@ Then:
* You can define [`options.language`](https://ckeditor.com/docs/ckfinder/ckfinder3/#!/api/CKFinder.Config-cfg-language) to set the UI language of CKFinder. By default, it will be set to the UI language of the editor.
```js
-import { ClassicEditor, CKFinder, CKFinderUploadAdapter } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ CKFinder, CKFinderUploadAdapter, /* ... */ ],
-
- // Enable the CKFinder button in the toolbar.
- toolbar: [ 'ckfinder', /* ... */ ]
-
+ // ... Other configuration options ...
ckfinder: {
// Upload the images to the server using the CKFinder QuickUpload command.
uploadUrl: 'https://example.com/ckfinder/core/connector/php/connector.php?command=QuickUpload&type=Images&responseType=json',
@@ -171,12 +157,9 @@ You can change the way CKFinder opens using the {@link module:ckfinder/ckfinderc
By default, the file manager opens as a modal. To open it in a new pop-up window, set the configuration value to `popup`:
```js
-import { ClassicEditor, CKFinder, CKFinderUploadAdapter } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ CKFinder, CKFinderUploadAdapter, /* ... */ ],
- toolbar: [ 'ckfinder', /* ... */ ]
+ // ... Other configuration options ...
ckfinder: {
// Open the file manager in the pop-up window.
openerMethod: 'popup'
diff --git a/packages/ckeditor5-clipboard/docs/_snippets/features/build-drag-drop-source.js b/packages/ckeditor5-clipboard/docs/_snippets/features/build-drag-drop-source.js
index 3bf8a297f8d..bd569d2c5eb 100644
--- a/packages/ckeditor5-clipboard/docs/_snippets/features/build-drag-drop-source.js
+++ b/packages/ckeditor5-clipboard/docs/_snippets/features/build-drag-drop-source.js
@@ -143,7 +143,8 @@ const defaultConfig = {
options: [ 10, 12, 14, 'default', 18, 20, 22 ],
supportAllValues: true
},
- language: 'en'
+ language: 'en',
+ licenseKey: 'GPL'
};
class ClassicEditor extends ClassicEditorBase {}
diff --git a/packages/ckeditor5-clipboard/docs/_snippets/features/paste-plain-text.js b/packages/ckeditor5-clipboard/docs/_snippets/features/paste-plain-text.js
index 54e67c3eab7..94b59d3ead1 100644
--- a/packages/ckeditor5-clipboard/docs/_snippets/features/paste-plain-text.js
+++ b/packages/ckeditor5-clipboard/docs/_snippets/features/paste-plain-text.js
@@ -43,7 +43,8 @@ ClassicEditor
supportAllValues: true
},
placeholder: 'Paste the content here to test the feature.',
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-clipboard/docs/features/drag-drop.md b/packages/ckeditor5-clipboard/docs/features/drag-drop.md
index 881d5cc4e0f..941c7577a34 100644
--- a/packages/ckeditor5-clipboard/docs/features/drag-drop.md
+++ b/packages/ckeditor5-clipboard/docs/features/drag-drop.md
@@ -59,13 +59,14 @@ In the balloon block editor, you can also drag content blocks using the drag han
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Clipboard } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Clipboard, Bold, /* ... */ ],
})
.then( /* ... */ )
diff --git a/packages/ckeditor5-clipboard/docs/features/paste-plain-text.md b/packages/ckeditor5-clipboard/docs/features/paste-plain-text.md
index f6b9fa324a1..c22050c812a 100644
--- a/packages/ckeditor5-clipboard/docs/features/paste-plain-text.md
+++ b/packages/ckeditor5-clipboard/docs/features/paste-plain-text.md
@@ -37,13 +37,14 @@ Pasting plain text with a double line break will turn the break into a paragraph
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { Bold, ClassicEditor, Clipboard } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Clipboard, Bold, /* ... */ ]
} )
.then( /* ... */ )
diff --git a/packages/ckeditor5-code-block/docs/_snippets/features/code-block-custom-languages.js b/packages/ckeditor5-code-block/docs/_snippets/features/code-block-custom-languages.js
index ded4e84d46e..c3cf93e86d3 100644
--- a/packages/ckeditor5-code-block/docs/_snippets/features/code-block-custom-languages.js
+++ b/packages/ckeditor5-code-block/docs/_snippets/features/code-block-custom-languages.js
@@ -40,7 +40,8 @@ ClassicEditor
{ language: 'css', label: 'CSS' },
{ language: 'html', label: 'HTML' }
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-code-block/docs/_snippets/features/code-block.js b/packages/ckeditor5-code-block/docs/_snippets/features/code-block.js
index c1ee3857c9f..20648dddd04 100644
--- a/packages/ckeditor5-code-block/docs/_snippets/features/code-block.js
+++ b/packages/ckeditor5-code-block/docs/_snippets/features/code-block.js
@@ -34,7 +34,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-code-block/docs/features/code-blocks.md b/packages/ckeditor5-code-block/docs/features/code-blocks.md
index 8a12e123fcb..c338ec07c2c 100644
--- a/packages/ckeditor5-code-block/docs/features/code-blocks.md
+++ b/packages/ckeditor5-code-block/docs/features/code-blocks.md
@@ -29,15 +29,19 @@ Each code block has a [specific programming language assigned](#configuring-code
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, CodeBlock } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ CodeBlock, /* ... */ ],
toolbar: [ 'codeBlock', /* ... */ ]
+ codeBlock: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -56,6 +60,7 @@ It is possible to configure which languages are available to the users. You can
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
codeBlock: {
languages: [
{ language: 'css', label: 'CSS' },
@@ -74,6 +79,7 @@ By default, the CSS class of the `` element in the data and editing is gen
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
codeBlock: {
languages: [
// Do not render the CSS class for the plain text code blocks.
diff --git a/packages/ckeditor5-core/src/editor/editor.ts b/packages/ckeditor5-core/src/editor/editor.ts
index ff82a2cd9c5..c385de96fae 100644
--- a/packages/ckeditor5-core/src/editor/editor.ts
+++ b/packages/ckeditor5-core/src/editor/editor.ts
@@ -11,9 +11,16 @@ import {
Config,
CKEditorError,
ObservableMixin,
+ logError,
+ parseBase64EncodedObject,
+ releaseDate,
+ toArray,
+ uid,
+ crc32,
type Locale,
type LocaleTranslate,
- type ObservableChangeEvent
+ type ObservableChangeEvent,
+ type CRCData
} from '@ckeditor/ckeditor5-utils';
import {
@@ -36,6 +43,11 @@ import Accessibility from '../accessibility.js';
import type { LoadedPlugins, PluginConstructor } from '../plugin.js';
import type { EditorConfig } from './editorconfig.js';
+declare global {
+ // eslint-disable-next-line no-var
+ var CKEDITOR_GLOBAL_LICENSE_KEY: string | undefined;
+}
+
/**
* The class representing a basic, generic editor.
*
@@ -312,6 +324,8 @@ export default abstract class Editor extends /* #__PURE__ */ ObservableMixin() {
this.config.define( 'plugins', availablePlugins );
this.config.define( this._context._getEditorConfig() );
+ checkLicenseKeyIsDefined( this.config );
+
this.plugins = new PluginCollection( this, availablePlugins, this._context.plugins );
this.locale = this._context.locale;
@@ -346,6 +360,193 @@ export default abstract class Editor extends /* #__PURE__ */ ObservableMixin() {
this.keystrokes.listenTo( this.editing.view.document );
this.accessibility = new Accessibility( this );
+
+ verifyLicenseKey( this );
+
+ // Checks if the license key is defined and throws an error if it is not.
+ function checkLicenseKeyIsDefined( config: Config ) {
+ let licenseKey = config.get( 'licenseKey' );
+
+ if ( !licenseKey && window.CKEDITOR_GLOBAL_LICENSE_KEY ) {
+ licenseKey = window.CKEDITOR_GLOBAL_LICENSE_KEY;
+ config.set( 'licenseKey', licenseKey );
+ }
+
+ if ( !licenseKey ) {
+ /**
+ * The licenseKey is missing. Add your license or 'GPL' string to the editor config.
+ *
+ * @error editor-license-key-missing
+ */
+ throw new CKEditorError( 'editor-license-key-missing' );
+ }
+ }
+
+ function verifyLicenseKey( editor: Editor ) {
+ const licenseKey = editor.config.get( 'licenseKey' )!;
+ const distributionChannel = ( window as any )[ Symbol.for( 'cke distribution' ) ] || 'sh';
+
+ if ( licenseKey == 'GPL' ) {
+ if ( distributionChannel == 'cloud' ) {
+ blockEditor( 'distributionChannel' );
+ }
+
+ return;
+ }
+
+ const encodedPayload = getPayload( licenseKey );
+
+ if ( !encodedPayload ) {
+ blockEditor( 'invalid' );
+
+ return;
+ }
+
+ const licensePayload = parseBase64EncodedObject( encodedPayload );
+
+ if ( !licensePayload ) {
+ blockEditor( 'invalid' );
+
+ return;
+ }
+
+ if ( !hasAllRequiredFields( licensePayload ) ) {
+ blockEditor( 'invalid' );
+
+ return;
+ }
+
+ if ( licensePayload.distributionChannel && !toArray( licensePayload.distributionChannel ).includes( distributionChannel ) ) {
+ blockEditor( 'distributionChannel' );
+
+ return;
+ }
+
+ if ( crc32( getCrcInputData( licensePayload ) ) != licensePayload.vc.toLowerCase() ) {
+ blockEditor( 'invalid' );
+
+ return;
+ }
+
+ const expirationDate = new Date( licensePayload.exp * 1000 );
+
+ if ( expirationDate < releaseDate ) {
+ blockEditor( 'expired' );
+
+ return;
+ }
+
+ const licensedHosts: Array | undefined = licensePayload.licensedHosts;
+
+ if ( licensedHosts && licensedHosts.length > 0 && !checkLicensedHosts( licensedHosts ) ) {
+ blockEditor( 'domainLimit' );
+
+ return;
+ }
+
+ if ( [ 'evaluation', 'trial' ].includes( licensePayload.licenseType ) && licensePayload.exp * 1000 < Date.now() ) {
+ blockEditor( 'expired' );
+
+ return;
+ }
+
+ if ( [ 'evaluation', 'trial', 'development' ].includes( licensePayload.licenseType ) ) {
+ const licenseType: 'evaluation' | 'trial' | 'development' = licensePayload.licenseType;
+
+ console.info(
+ `You are using the ${ licenseType } version of CKEditor 5 with limited usage. ` +
+ 'Make sure you will not use it in the production environment.'
+ );
+
+ const timerId = setTimeout( () => {
+ blockEditor( `${ licenseType }Limit` );
+ }, 600000 );
+
+ editor.on( 'destroy', () => {
+ clearTimeout( timerId );
+ } );
+ }
+
+ if ( licensePayload.usageEndpoint ) {
+ editor.once( 'ready', () => {
+ const telemetry = editor._getTelemetryData();
+
+ const request = {
+ requestId: uid(),
+ requestTime: Math.round( Date.now() / 1000 ),
+ license: licenseKey,
+ telemetry
+ };
+
+ editor._sendUsageRequest( licensePayload.usageEndpoint, request ).then( response => {
+ const { status, message } = response;
+
+ if ( message ) {
+ console.warn( message );
+ }
+
+ if ( status != 'ok' ) {
+ blockEditor( 'usageLimit' );
+ }
+ }, () => {
+ /**
+ * Your license key cannot be validated because of a network issue.
+ * Please make sure that your setup does not block the request.
+ *
+ * @error license-key-validation-endpoint-not-reachable
+ * @param {String} url The URL that was attempted to reach.
+ */
+ logError( 'license-key-validation-endpoint-not-reachable', { url: licensePayload.usageEndpoint } );
+ } );
+ }, { priority: 'high' } );
+ }
+
+ function getPayload( licenseKey: string ): string | null {
+ const parts = licenseKey.split( '.' );
+
+ if ( parts.length != 3 ) {
+ return null;
+ }
+
+ return parts[ 1 ];
+ }
+
+ function blockEditor( reason: LicenseErrorReason ) {
+ editor.enableReadOnlyMode( Symbol( 'invalidLicense' ) );
+ editor._showLicenseError( reason );
+ }
+
+ function hasAllRequiredFields( licensePayload: Record ) {
+ const requiredFields = [ 'exp', 'jti', 'vc' ];
+
+ return requiredFields.every( field => field in licensePayload );
+ }
+
+ function getCrcInputData( licensePayload: Record ): CRCData {
+ const keysToCheck = Object.getOwnPropertyNames( licensePayload ).sort();
+
+ const filteredValues = keysToCheck
+ .filter( key => key != 'vc' && licensePayload[ key ] != null )
+ .map( key => licensePayload[ key ] );
+
+ return filteredValues as CRCData;
+ }
+
+ function checkLicensedHosts( licensedHosts: Array ): boolean {
+ const { hostname } = new URL( window.location.href );
+
+ if ( licensedHosts.includes( hostname ) ) {
+ return true;
+ }
+
+ const segments = hostname.split( '.' );
+
+ return licensedHosts
+ .filter( host => host.includes( '*' ) )
+ .map( host => host.split( '.' ) )
+ .some( octets => segments.every( ( segment, index ) => octets[ index ] === segment || octets[ index ] === '*' ) );
+ }
+ }
}
/**
@@ -675,8 +876,145 @@ export default abstract class Editor extends /* #__PURE__ */ ObservableMixin() {
* Exposed as static editor field for easier access in editor builds.
*/
public static ContextWatchdog = ContextWatchdog;
+
+ private _getTelemetryData() {
+ return {
+ editorVersion: globalThis.CKEDITOR_VERSION
+ };
+ }
+
+ private _showLicenseError( reason: LicenseErrorReason, pluginName?: string ) {
+ setTimeout( () => {
+ if ( reason == 'invalid' ) {
+ /**
+ * Invalid license key. Please contact our customer support at https://ckeditor.com/contact/.
+ *
+ * @error invalid-license-key
+ */
+ throw new CKEditorError( 'invalid-license-key', this );
+ }
+
+ if ( reason == 'expired' ) {
+ /**
+ * Your license key has expired. Please renew your license at https://ckeditor.com/TODO/.
+ *
+ * @error license-key-expired
+ */
+ throw new CKEditorError( 'license-key-expired', this );
+ }
+
+ if ( reason == 'domainLimit' ) {
+ /**
+ * The hostname is not allowed by your license. Please update your license configuration at https://ckeditor.com/TODO/.
+ *
+ * @error license-key-domain-limit
+ */
+ throw new CKEditorError( 'license-key-domain-limit', this );
+ }
+
+ if ( reason == 'featureNotAllowed' ) {
+ /**
+ * The plugin is not allowed by your license.
+ *
+ * Please check your license or contact support at https://ckeditor.com/contact/ for more information.
+ *
+ * @error license-key-feature-not-allowed
+ * @param {String} pluginName
+ */
+ throw new CKEditorError( 'license-key-feature-not-allowed', this, { pluginName } );
+ }
+
+ if ( reason == 'evaluationLimit' ) {
+ /**
+ * You have exhausted the evaluation usage limit. Restart the editor.
+ *
+ * Please contact our customer support to get full access at https://ckeditor.com/contact/.
+ *
+ * @error license-key-evaluation-limit
+ */
+ throw new CKEditorError( 'license-key-evaluation-limit', this );
+ }
+
+ if ( reason == 'trialLimit' ) {
+ /**
+ * You have exhausted the trial usage limit. Restart the editor.
+ *
+ * Please contact our customer support to get full access at https://ckeditor.com/contact/.
+ *
+ * @error license-key-trial-limit
+ */
+ throw new CKEditorError( 'license-key-trial-limit', this );
+ }
+
+ if ( reason == 'developmentLimit' ) {
+ /**
+ * You have reached the development usage limit. Restart the editor.
+ *
+ * Please contact our customer support to get full access at https://ckeditor.com/contact/.
+ *
+ * @error license-key-development-limit
+ */
+ throw new CKEditorError( 'license-key-development-limit', this );
+ }
+
+ if ( reason == 'usageLimit' ) {
+ /**
+ * The editor usage limit has been reached.
+ *
+ * Visit Contact support to extend the limit at https://ckeditor.com/contact/.
+ *
+ * @error license-key-usage-limit
+ */
+ throw new CKEditorError( 'license-key-usage-limit', this );
+ }
+
+ if ( reason == 'distributionChannel' ) {
+ /**
+ * The usage is not valid for this distribution channel.
+ *
+ * Please check your installation or contact support at https://ckeditor.com/contact/ for more information.
+ *
+ * @error license-key-distribution-channel
+ */
+ throw new CKEditorError( 'license-key-distribution-channel', this );
+ }
+
+ /* istanbul ignore next -- @preserve */
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const unreachable: never = reason;
+ }, 0 );
+
+ this._showLicenseError = () => {};
+ }
+
+ private async _sendUsageRequest( endpoint: string, request: unknown ) {
+ const headers = new Headers( { 'Content-Type': 'application/json' } );
+ const response = await fetch( new URL( endpoint ), {
+ method: 'POST',
+ headers,
+ body: JSON.stringify( request )
+ } );
+
+ if ( !response.ok ) {
+ // TODO: refine message.
+ throw new Error( `HTTP Response: ${ response.status }` );
+ }
+
+ return response.json();
+ }
}
+type LicenseErrorReason =
+ 'invalid' |
+ 'expired' |
+ 'domainLimit' |
+ 'featureNotAllowed' |
+ 'evaluationLimit' |
+ 'trialLimit' |
+ 'developmentLimit' |
+ 'usageLimit' |
+ 'distributionChannel';
+
/**
* Fired when the {@link module:engine/controller/datacontroller~DataController#event:ready data} and all additional
* editor components are ready.
diff --git a/packages/ckeditor5-core/src/editor/editorconfig.ts b/packages/ckeditor5-core/src/editor/editorconfig.ts
index c923d5ab6d5..e0b8eb60a2d 100644
--- a/packages/ckeditor5-core/src/editor/editorconfig.ts
+++ b/packages/ckeditor5-core/src/editor/editorconfig.ts
@@ -935,7 +935,7 @@ export interface PoweredByConfig {
*
* @default 'border'
*/
- position: 'inside' | 'border';
+ position?: 'inside' | 'border';
/**
* Allows choosing the side of the editing area where the logo will be displayed.
@@ -945,7 +945,7 @@ export interface PoweredByConfig {
*
* @default 'right'
*/
- side: 'left' | 'right';
+ side?: 'left' | 'right';
/**
* Allows changing the label displayed next to the CKEditor logo.
@@ -954,7 +954,7 @@ export interface PoweredByConfig {
*
* @default 'Powered by'
*/
- label: string | null;
+ label?: string | null;
/**
* The vertical distance the logo can be moved away from its default position.
@@ -963,14 +963,14 @@ export interface PoweredByConfig {
*
* @default 5
*/
- verticalOffset: number;
+ verticalOffset?: number;
/**
* The horizontal distance between the side of the editing root and the nearest side of the logo.
*
* @default 5
*/
- horizontalOffset: number;
+ horizontalOffset?: number;
/**
* Allows to show the logo even if the valid commercial license is configured using
diff --git a/packages/ckeditor5-core/src/plugincollection.ts b/packages/ckeditor5-core/src/plugincollection.ts
index e860d9e8773..bc692b00658 100644
--- a/packages/ckeditor5-core/src/plugincollection.ts
+++ b/packages/ckeditor5-core/src/plugincollection.ts
@@ -371,7 +371,7 @@ export default class PluginCollection
* that you tried loading plugins by name. However, unlike CKEditor 4, CKEditor 5 does not implement a "plugin loader".
* This means that CKEditor 5 does not know where to load the plugin modules from. Therefore, you need to
* provide each plugin through a reference (as a constructor function). Check out the examples in the
- * {@glink getting-started/installation/quick-start Quick start} guide.
+ * {@glink getting-started/installation/cloud/quick-start Quick start} guide.
*
* @error plugincollection-plugin-not-found
* @param plugin The name of the plugin which could not be loaded.
diff --git a/packages/ckeditor5-core/tests/_utils-tests/generatelicensekey.js b/packages/ckeditor5-core/tests/_utils-tests/generatelicensekey.js
new file mode 100644
index 00000000000..04130933454
--- /dev/null
+++ b/packages/ckeditor5-core/tests/_utils-tests/generatelicensekey.js
@@ -0,0 +1,70 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals atob */
+
+import generateLicenseKey from '../_utils/generatelicensekey.js';
+
+describe( 'generateLicenseKey util', () => {
+ describe( 'generateLicenseKey()', () => {
+ it( 'should generate a license key with custom properties', () => {
+ const { licenseKey } = generateLicenseKey( {
+ licensedHosts: [ 'example.com' ],
+ licenseType: 'trial',
+ usageEndpoint: 'https://example.com/usage',
+ distributionChannel: 'cdn',
+ whiteLabel: true
+ } );
+
+ const decodedPayload = JSON.parse( atob( licenseKey.split( '.' )[ 1 ] ) );
+
+ expect( decodedPayload.licensedHosts ).to.deep.equal( [ 'example.com' ] );
+ expect( decodedPayload.licenseType ).to.equal( 'trial' );
+ expect( decodedPayload.usageEndpoint ).to.equal( 'https://example.com/usage' );
+ expect( decodedPayload.distributionChannel ).to.equal( 'cdn' );
+ expect( decodedPayload.whiteLabel ).to.be.true;
+ } );
+
+ it( 'should generate a license key without header and tail', () => {
+ const { licenseKey } = generateLicenseKey( {
+ skipHeader: true,
+ skipTail: true
+ } );
+
+ expect( licenseKey.startsWith( 'foo.' ) ).to.be.false;
+ expect( licenseKey.endsWith( '.bar' ) ).to.be.false;
+ } );
+
+ it( 'should generate a license key with custom VC', () => {
+ const { licenseKey } = generateLicenseKey( {
+ customVc: 'abc123'
+ } );
+
+ const decodedPayload = JSON.parse( atob( licenseKey.split( '.' )[ 1 ] ) );
+
+ expect( decodedPayload.vc ).to.equal( 'abc123' );
+ } );
+
+ it( 'should generate a license key with custom expiration date', () => {
+ const { licenseKey } = generateLicenseKey( {
+ isExpired: true
+ } );
+
+ const decodedPayload = JSON.parse( atob( licenseKey.split( '.' )[ 1 ] ) );
+
+ expect( decodedPayload.exp ).to.be.below( Date.now() / 1000 );
+ } );
+
+ it( 'should generate a license key with custom jti', () => {
+ const { licenseKey } = generateLicenseKey( {
+ jtiExist: false
+ } );
+
+ const decodedPayload = JSON.parse( atob( licenseKey.split( '.' )[ 1 ] ) );
+
+ expect( decodedPayload.jti ).to.be.undefined;
+ } );
+ } );
+} );
diff --git a/packages/ckeditor5-core/tests/_utils/generatelicensekey.js b/packages/ckeditor5-core/tests/_utils/generatelicensekey.js
new file mode 100644
index 00000000000..820898cc24f
--- /dev/null
+++ b/packages/ckeditor5-core/tests/_utils/generatelicensekey.js
@@ -0,0 +1,80 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals btoa */
+
+import { releaseDate, crc32 } from '@ckeditor/ckeditor5-utils';
+
+/**
+ * Generates a license key for testing purposes.
+ */
+export default function generateKey( options = {} ) {
+ const {
+ isExpired = false,
+ jtiExist = true,
+ expExist = true,
+ vcExist = true,
+ customVc = undefined,
+ skipHeader = false,
+ skipTail = false,
+ daysAfterExpiration = 0
+ } = options;
+
+ const jti = 'foo';
+ const releaseTimestamp = Date.parse( releaseDate );
+ const day = 86400000; // one day in milliseconds.
+
+ // Depending on isExpired parameter we are creating timestamp ten days
+ // before or after release day.
+ const expirationTimestamp = isExpired ? releaseTimestamp - 10 * day : releaseTimestamp + 10 * day;
+ const todayTimestamp = ( expirationTimestamp + daysAfterExpiration * day );
+
+ const payload = {};
+
+ [ 'licensedHosts', 'licenseType', 'usageEndpoint', 'distributionChannel', 'whiteLabel' ].forEach( prop => {
+ if ( prop in options ) {
+ payload[ prop ] = options[ prop ];
+ }
+ } );
+
+ if ( jtiExist ) {
+ payload.jti = jti;
+ }
+
+ if ( expExist ) {
+ payload.exp = Math.ceil( expirationTimestamp / 1000 );
+ }
+
+ if ( customVc ) {
+ payload.vc = customVc;
+ } else if ( vcExist ) {
+ const vc = crc32( getCrcInputData( payload ) );
+
+ payload.vc = vc;
+ }
+
+ return {
+ licenseKey: `${ skipHeader ? '' : 'foo.' }${ encodePayload( payload ) }${ skipTail ? '' : '.bar' }`,
+ todayTimestamp
+ };
+}
+
+function encodePayload( claims ) {
+ return encodeBase64Safe( JSON.stringify( claims ) );
+}
+
+function encodeBase64Safe( text ) {
+ return btoa( text ).replace( /\+/g, '-' ).replace( /\//g, '_' ).replace( /=+$/, '' );
+}
+
+function getCrcInputData( licensePayload ) {
+ const keysToCheck = Object.getOwnPropertyNames( licensePayload ).sort();
+
+ const filteredValues = keysToCheck
+ .filter( key => key != 'vc' && licensePayload[ key ] != null )
+ .map( key => licensePayload[ key ] );
+
+ return [ ...filteredValues ];
+}
diff --git a/packages/ckeditor5-core/tests/editor/licensecheck.js b/packages/ckeditor5-core/tests/editor/licensecheck.js
new file mode 100644
index 00000000000..2e29c0c9747
--- /dev/null
+++ b/packages/ckeditor5-core/tests/editor/licensecheck.js
@@ -0,0 +1,706 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals window, console, Response, globalThis, URL */
+
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror.js';
+import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils.js';
+import Editor from '../../src/editor/editor.js';
+import testUtils from '../../tests/_utils/utils.js';
+import generateKey from '../_utils/generatelicensekey.js';
+
+class TestEditor extends Editor {
+ static create( config ) {
+ return new Promise( resolve => {
+ const editor = new this( config );
+
+ resolve(
+ editor.initPlugins()
+ .then( () => {
+ editor.fire( 'ready' );
+ } )
+ .then( () => editor )
+ );
+ } );
+ }
+}
+
+describe( 'Editor - license check', () => {
+ afterEach( () => {
+ delete TestEditor.builtinPlugins;
+ delete TestEditor.defaultConfig;
+
+ sinon.restore();
+ } );
+
+ describe( 'license key verification', () => {
+ let showErrorStub, consoleInfoStub;
+
+ beforeEach( () => {
+ showErrorStub = testUtils.sinon.stub( TestEditor.prototype, '_showLicenseError' );
+ consoleInfoStub = sinon.stub( console, 'info' );
+ } );
+
+ describe( 'required fields in the license key', () => {
+ it( 'should not block the editor when required fields are provided and are valid', () => {
+ const { licenseKey } = generateKey();
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.notCalled( showErrorStub );
+ expect( editor.isReadOnly ).to.be.false;
+ } );
+
+ it( 'should block the editor when the `exp` field is missing', () => {
+ const { licenseKey } = generateKey( { expExist: false } );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.calledWithMatch( showErrorStub, 'invalid' );
+ expect( editor.isReadOnly ).to.be.true;
+ } );
+
+ it( 'should block the editor when the `jti` field is missing', () => {
+ const { licenseKey } = generateKey( { jtiExist: false } );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.calledWithMatch( showErrorStub, 'invalid' );
+ expect( editor.isReadOnly ).to.be.true;
+ } );
+
+ it( 'should block the editor when the `vc` field is missing', () => {
+ const { licenseKey } = generateKey( { vcExist: false } );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.calledWithMatch( showErrorStub, 'invalid' );
+ expect( editor.isReadOnly ).to.be.true;
+ } );
+ } );
+
+ describe( '"licensedHosts" check', () => {
+ const sets = {
+ success: [
+ {
+ name: 'direct domain match',
+ hostname: 'example.com',
+ licensedHost: 'example.com'
+ },
+ {
+ name: 'direct IP match',
+ hostname: '127.0.0.1',
+ licensedHost: '127.0.0.1'
+ },
+ {
+ name: 'wildcard IP match',
+ hostname: '127.0.0.1',
+ licensedHost: '127.*.*.*'
+ },
+ {
+ name: 'wildcard subdomain match',
+ hostname: 'subdomain.example.com',
+ licensedHost: '*.example.com'
+ }
+ ],
+ fail: [
+ {
+ name: 'domain mismatch',
+ hostname: 'example.com',
+ licensedHost: 'example.net'
+ },
+ {
+ name: 'IP mismatch',
+ hostname: '127.0.0.1',
+ licensedHost: '127.0.0.2'
+ },
+ {
+ name: 'domain mismatch (wildcard subdomain)',
+ hostname: 'sub.example.com',
+ licensedHost: '*.example.net'
+ },
+ {
+ name: 'IP mismatch (wildcard)',
+ hostname: '127.0.0.1',
+ licensedHost: '192.168.*.*'
+ },
+ {
+ name: 'subdomain mismatch',
+ hostname: 'subdomain.example.com',
+ licensedHost: 'sub.example.com'
+ },
+ {
+ name: 'missing root domain',
+ hostname: 'example.com',
+ licensedHost: 'subdomain.example.com'
+ },
+ {
+ name: 'missing subdomain',
+ hostname: 'subdomain.example.com',
+ licensedHost: 'example.com'
+ }
+ ]
+ };
+
+ sets.success.forEach( set => {
+ it( `works on ${ set.name }`, () => {
+ sinon.stub( URL.prototype, 'hostname' ).value( set.hostname );
+
+ const { licenseKey } = generateKey( { licensedHosts: [ set.licensedHost ] } );
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.notCalled( showErrorStub );
+
+ expect( editor.isReadOnly ).to.be.false;
+ } );
+ } );
+
+ sets.fail.forEach( set => {
+ it( `fails on ${ set.name }`, () => {
+ sinon.stub( URL.prototype, 'hostname' ).value( set.hostname );
+
+ const { licenseKey } = generateKey( { licensedHosts: [ set.licensedHost ] } );
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.calledWithMatch( showErrorStub, 'domainLimit' );
+
+ expect( editor.isReadOnly ).to.be.true;
+ } );
+ } );
+ } );
+
+ describe( 'distribution channel check', () => {
+ afterEach( () => {
+ delete window[ Symbol.for( 'cke distribution' ) ];
+ } );
+
+ it( 'should not block if distribution channel match', () => {
+ setChannel( 'xyz' );
+
+ const { licenseKey } = generateKey( { distributionChannel: 'xyz' } );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.notCalled( showErrorStub );
+ expect( editor.isReadOnly ).to.be.false;
+ } );
+
+ it( 'should not block if one of distribution channel match', () => {
+ setChannel( 'xyz' );
+
+ const { licenseKey } = generateKey( { distributionChannel: [ 'abc', 'xyz' ] } );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.notCalled( showErrorStub );
+ expect( editor.isReadOnly ).to.be.false;
+ } );
+
+ it( 'should not block if implicit distribution channel match', () => {
+ const { licenseKey } = generateKey( { distributionChannel: 'sh' } );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.notCalled( showErrorStub );
+ expect( editor.isReadOnly ).to.be.false;
+ } );
+
+ it( 'should not block if distribution channel is not restricted', () => {
+ setChannel( 'xyz' );
+
+ const { licenseKey } = generateKey();
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.notCalled( showErrorStub );
+ expect( editor.isReadOnly ).to.be.false;
+ } );
+
+ it( 'should block if distribution channel doesn\'t match', () => {
+ setChannel( 'abc' );
+
+ const { licenseKey } = generateKey( { distributionChannel: 'xyz' } );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.calledWithMatch( showErrorStub, 'distributionChannel' );
+ expect( editor.isReadOnly ).to.be.true;
+ } );
+
+ it( 'should block if none of distribution channel doesn\'t match', () => {
+ setChannel( 'abc' );
+
+ const { licenseKey } = generateKey( { distributionChannel: [ 'xyz', 'def' ] } );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.calledWithMatch( showErrorStub, 'distributionChannel' );
+ expect( editor.isReadOnly ).to.be.true;
+ } );
+
+ it( 'should block if implicit distribution channel doesn\'t match', () => {
+ const { licenseKey } = generateKey( { distributionChannel: 'xyz' } );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.calledWithMatch( showErrorStub, 'distributionChannel' );
+ expect( editor.isReadOnly ).to.be.true;
+ } );
+
+ describe( 'GPL license', () => {
+ it( 'should block if distribution channel is cloud', () => {
+ setChannel( 'cloud' );
+
+ const licenseKey = 'GPL';
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.calledWithMatch( showErrorStub, 'distributionChannel' );
+ expect( editor.isReadOnly ).to.be.true;
+ } );
+
+ it( 'should not block if distribution channel is not cloud', () => {
+ setChannel( 'xyz' );
+
+ const licenseKey = 'GPL';
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.notCalled( showErrorStub );
+ expect( editor.isReadOnly ).to.be.false;
+ } );
+ } );
+
+ function setChannel( channel ) {
+ window[ Symbol.for( 'cke distribution' ) ] = channel;
+ }
+ } );
+
+ describe( 'GPL check', () => {
+ it( 'should not throw if license key is GPL', () => {
+ const licenseKey = 'GPL';
+
+ expect( () => {
+ // eslint-disable-next-line no-new
+ new TestEditor( { licenseKey } );
+ } ).to.not.throw();
+ } );
+
+ it( 'should not throw if license key is missing (CKEditor testing environment)', () => {
+ expect( () => {
+ // eslint-disable-next-line no-new
+ new TestEditor( {} );
+ } ).to.not.throw();
+ } );
+
+ it( 'should throw if license key is missing (outside of CKEditor testing environment)', () => {
+ window.CKEDITOR_GLOBAL_LICENSE_KEY = undefined;
+
+ expect( () => {
+ // eslint-disable-next-line no-new
+ new TestEditor( {} );
+ } ).to.throw( CKEditorError, 'editor-license-key-missing' );
+
+ window.CKEDITOR_GLOBAL_LICENSE_KEY = 'GPL';
+ } );
+ } );
+
+ describe( 'evaluation/trial check', () => {
+ const licenseTypes = [ 'evaluation', 'trial' ];
+
+ beforeEach( () => {
+ sinon.useFakeTimers( { now: Date.now() } );
+ } );
+
+ afterEach( () => {
+ sinon.restore();
+ } );
+
+ licenseTypes.forEach( licenseType => {
+ it( `should not block if ${ licenseType } license did not expired`, () => {
+ const { licenseKey, todayTimestamp } = generateKey( {
+ licenseType,
+ isExpired: false,
+ daysAfterExpiration: -1
+ } );
+
+ const today = todayTimestamp;
+ const dateNow = sinon.stub( Date, 'now' ).returns( today );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.notCalled( showErrorStub );
+ expect( editor.isReadOnly ).to.be.false;
+
+ dateNow.restore();
+ } );
+
+ it( `should block if ${ licenseType } license is expired`, () => {
+ const { licenseKey, todayTimestamp } = generateKey( {
+ licenseType,
+ daysAfterExpiration: 1
+ } );
+
+ const dateNow = sinon.stub( Date, 'now' ).returns( todayTimestamp );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.calledWithMatch( showErrorStub, 'expired' );
+ expect( editor.isReadOnly ).to.be.true;
+
+ dateNow.restore();
+ } );
+
+ it( `should block editor after 10 minutes on ${ licenseType } license`, () => {
+ const { licenseKey, todayTimestamp } = generateKey( {
+ licenseType,
+ isExpired: false,
+ daysAfterExpiration: -1
+ } );
+
+ const dateNow = sinon.stub( Date, 'now' ).returns( todayTimestamp );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.notCalled( showErrorStub );
+ expect( editor.isReadOnly ).to.be.false;
+
+ sinon.clock.tick( 600100 );
+
+ sinon.assert.calledWithMatch( showErrorStub, licenseType + 'Limit' );
+ expect( editor.isReadOnly ).to.be.true;
+ sinon.assert.calledOnce( consoleInfoStub );
+ sinon.assert.calledWith(
+ consoleInfoStub,
+ `You are using the ${ licenseType } version of CKEditor 5 with limited usage. ` +
+ 'Make sure you will not use it in the production environment.'
+ );
+
+ dateNow.restore();
+ } );
+
+ it( `should clear timer on editor destroy on ${ licenseType } license`, done => {
+ const { licenseKey, todayTimestamp } = generateKey( {
+ licenseType,
+ isExpired: false,
+ daysAfterExpiration: -1
+ } );
+
+ const dateNow = sinon.stub( Date, 'now' ).returns( todayTimestamp );
+ const editor = new TestEditor( { licenseKey } );
+ const clearTimeoutSpy = sinon.spy( globalThis, 'clearTimeout' );
+
+ editor.fire( 'ready' );
+ editor.on( 'destroy', () => {
+ sinon.assert.calledOnce( clearTimeoutSpy );
+ done();
+ } );
+
+ editor.destroy();
+ dateNow.restore();
+ } );
+ } );
+ } );
+
+ describe( 'development license', () => {
+ beforeEach( () => {
+ sinon.useFakeTimers( { now: Date.now() } );
+ } );
+
+ afterEach( () => {
+ sinon.restore();
+ } );
+
+ it( 'should log information to the console about using the development license', () => {
+ const { licenseKey } = generateKey( {
+ licenseType: 'development'
+ } );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ expect( editor.isReadOnly ).to.be.false;
+ sinon.assert.calledOnce( consoleInfoStub );
+ sinon.assert.calledWith( consoleInfoStub, 'You are using the development version of CKEditor 5 with ' +
+ 'limited usage. Make sure you will not use it in the production environment.' );
+ } );
+
+ it( 'should not block the editor if 10 minutes have not passed (development license)', () => {
+ const { licenseKey } = generateKey( {
+ licenseType: 'development'
+ } );
+
+ const today = 1715166436000; // 08.05.2024
+ const dateNow = sinon.stub( Date, 'now' ).returns( today );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.notCalled( showErrorStub );
+ expect( editor.isReadOnly ).to.be.false;
+
+ sinon.clock.tick( 1 );
+
+ sinon.assert.notCalled( showErrorStub );
+ expect( editor.isReadOnly ).to.be.false;
+
+ dateNow.restore();
+ } );
+
+ it( 'should block editor after 10 minutes (development license)', () => {
+ const { licenseKey, todayTimestamp } = generateKey( {
+ licenseType: 'development'
+ } );
+
+ const dateNow = sinon.stub( Date, 'now' ).returns( todayTimestamp );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.notCalled( showErrorStub );
+ expect( editor.isReadOnly ).to.be.false;
+
+ sinon.clock.tick( 600100 );
+
+ sinon.assert.calledWithMatch( showErrorStub, 'developmentLimit' );
+ expect( editor.isReadOnly ).to.be.true;
+
+ dateNow.restore();
+ } );
+
+ it( 'should clear timer on editor destroy', done => {
+ const { licenseKey, todayTimestamp } = generateKey( {
+ licenseType: 'development'
+ } );
+
+ const dateNow = sinon.stub( Date, 'now' ).returns( todayTimestamp );
+ const editor = new TestEditor( { licenseKey } );
+ const clearTimeoutSpy = sinon.spy( globalThis, 'clearTimeout' );
+
+ editor.fire( 'ready' );
+ editor.on( 'destroy', () => {
+ sinon.assert.calledOnce( clearTimeoutSpy );
+ done();
+ } );
+
+ editor.destroy();
+ dateNow.restore();
+ } );
+ } );
+
+ it( 'should block the editor when the license key is not valid (expiration date in the past)', () => {
+ const { licenseKey } = generateKey( {
+ isExpired: true
+ } );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.calledWithMatch( showErrorStub, 'expired' );
+ expect( editor.isReadOnly ).to.be.true;
+ } );
+
+ it( 'should block the editor when the license key has wrong format (wrong verificationCode)', () => {
+ const { licenseKey } = generateKey( {
+ customVc: 'wrong vc'
+ } );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.calledWithMatch( showErrorStub, 'invalid' );
+ expect( editor.isReadOnly ).to.be.true;
+ } );
+
+ it( 'should block the editor when the license key has wrong format (missing header part)', () => {
+ const { licenseKey } = generateKey( {
+ isExpired: true,
+ skipHeader: true
+ } );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.calledWithMatch( showErrorStub, 'invalid' );
+ expect( editor.isReadOnly ).to.be.true;
+ } );
+
+ it( 'should block the editor when the license key has wrong format (missing tail part)', () => {
+ const { licenseKey } = generateKey( {
+ isExpired: true,
+ skipTail: true
+ } );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.calledWithMatch( showErrorStub, 'invalid' );
+ expect( editor.isReadOnly ).to.be.true;
+ } );
+
+ it( 'should block the editor when the license key has wrong format (payload does not start with `ey`)', () => {
+ const licenseKey = 'foo.JleHAiOjIyMDg5ODg4MDAsImp0aSI6ImZvbyIsInZlcmlmaWNhdGlvbkNvZGUiOiJjNTU2YWQ3NCJ9.bar';
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.calledWithMatch( showErrorStub, 'invalid' );
+ expect( editor.isReadOnly ).to.be.true;
+ } );
+
+ it( 'should block the editor when the license key has wrong format (payload not parsable as a JSON object)', () => {
+ const licenseKey = 'foo.eyZm9v.bar';
+
+ const editor = new TestEditor( { licenseKey } );
+
+ sinon.assert.calledWithMatch( showErrorStub, 'invalid' );
+ expect( editor.isReadOnly ).to.be.true;
+ } );
+ } );
+
+ describe( 'usage endpoint', () => {
+ it( 'should send request with telemetry data if license key contains a usage endpoint', () => {
+ const fetchStub = sinon.stub( window, 'fetch' );
+
+ const { licenseKey } = generateKey( {
+ usageEndpoint: 'https://ckeditor.com'
+ } );
+ const editor = new TestEditor( { licenseKey } );
+
+ editor.fire( 'ready' );
+
+ sinon.assert.calledOnce( fetchStub );
+
+ const sentData = JSON.parse( fetchStub.firstCall.lastArg.body );
+
+ expect( sentData.license ).to.equal( licenseKey );
+ expect( sentData.telemetry ).to.deep.equal( { editorVersion: globalThis.CKEDITOR_VERSION } );
+ } );
+
+ it( 'should not send any request if license key does not contain a usage endpoint', () => {
+ const fetchStub = sinon.stub( window, 'fetch' );
+
+ const { licenseKey } = generateKey();
+ const editor = new TestEditor( { licenseKey } );
+
+ editor.fire( 'ready' );
+
+ sinon.assert.notCalled( fetchStub );
+ } );
+
+ it( 'should display error on the console and not block the editor if response status is not ok (HTTP 500)', async () => {
+ const { licenseKey } = generateKey( {
+ usageEndpoint: 'https://ckeditor.com'
+ } );
+ const fetchStub = sinon.stub( window, 'fetch' ).resolves( new Response( null, { status: 500 } ) );
+ const errorStub = sinon.stub( console, 'error' );
+
+ const editor = new TestEditor( { licenseKey } );
+
+ editor.fire( 'ready' );
+ await wait( 1 );
+
+ sinon.assert.calledOnce( fetchStub );
+ sinon.assert.calledWithMatch(
+ errorStub, 'license-key-validation-endpoint-not-reachable', { 'url': 'https://ckeditor.com' } );
+ expect( editor.isReadOnly ).to.be.false;
+ } );
+
+ it( 'should display warning and block the editor when usage status is not ok', async () => {
+ const fetchStub = sinon.stub( window, 'fetch' ).resolves( {
+ ok: true,
+ json: () => Promise.resolve( {
+ status: 'foo'
+ } )
+ } );
+ const showErrorStub = testUtils.sinon.stub( TestEditor.prototype, '_showLicenseError' );
+
+ const { licenseKey } = generateKey( {
+ usageEndpoint: 'https://ckeditor.com'
+ } );
+ const editor = new TestEditor( { licenseKey } );
+
+ editor.fire( 'ready' );
+ await wait( 1 );
+
+ sinon.assert.calledOnce( fetchStub );
+ sinon.assert.calledOnce( showErrorStub );
+ sinon.assert.calledWithMatch( showErrorStub, 'usageLimit' );
+ expect( editor.isReadOnly ).to.be.true;
+ } );
+
+ it( 'should display additional warning when usage status is not ok and message is provided', async () => {
+ const fetchStub = sinon.stub( window, 'fetch' ).resolves( {
+ ok: true,
+ json: () => Promise.resolve( {
+ status: 'foo',
+ message: 'bar'
+ } )
+ } );
+ const warnStub = testUtils.sinon.stub( console, 'warn' );
+ const showErrorStub = testUtils.sinon.stub( TestEditor.prototype, '_showLicenseError' );
+
+ const { licenseKey } = generateKey( {
+ usageEndpoint: 'https://ckeditor.com'
+ } );
+ const editor = new TestEditor( { licenseKey } );
+
+ editor.fire( 'ready' );
+ await wait( 1 );
+
+ sinon.assert.calledOnce( fetchStub );
+ sinon.assert.calledOnce( warnStub );
+ sinon.assert.calledOnce( showErrorStub );
+ sinon.assert.calledWithMatch( warnStub, 'bar' );
+ sinon.assert.calledWithMatch( showErrorStub, 'usageLimit' );
+ expect( editor.isReadOnly ).to.be.true;
+ } );
+ } );
+
+ describe( 'license errors', () => {
+ let clock;
+
+ beforeEach( () => {
+ clock = sinon.useFakeTimers( { toFake: [ 'setTimeout' ] } );
+ } );
+
+ const testCases = [
+ { reason: 'invalid', error: 'invalid-license-key' },
+ { reason: 'expired', error: 'license-key-expired' },
+ { reason: 'domainLimit', error: 'license-key-domain-limit' },
+ { reason: 'featureNotAllowed', error: 'license-key-feature-not-allowed', pluginName: 'PluginABC' },
+ { reason: 'evaluationLimit', error: 'license-key-evaluation-limit' },
+ { reason: 'trialLimit', error: 'license-key-trial-limit' },
+ { reason: 'developmentLimit', error: 'license-key-development-limit' },
+ { reason: 'usageLimit', error: 'license-key-usage-limit' },
+ { reason: 'distributionChannel', error: 'license-key-distribution-channel' }
+ ];
+
+ for ( const testCase of testCases ) {
+ const { reason, error, pluginName } = testCase;
+ const expectedData = pluginName ? { pluginName } : undefined;
+
+ it( `should throw \`${ error }\` error`, () => {
+ const editor = new TestEditor( { licenseKey: 'GPL' } );
+
+ editor._showLicenseError( reason, pluginName );
+
+ expectToThrowCKEditorError( () => clock.tick( 1 ), error, editor, expectedData );
+ } );
+ }
+
+ it( 'should throw error only once', () => {
+ const editor = new TestEditor( { licenseKey: 'GPL' } );
+
+ editor._showLicenseError( 'invalid' );
+
+ try {
+ clock.tick( 1 );
+ } catch ( e ) {
+ // Do nothing.
+ }
+
+ editor._showLicenseError( 'invalid' );
+
+ expect( () => clock.tick( 1 ) ).to.not.throw();
+ } );
+ } );
+} );
+
+function wait( time ) {
+ return new Promise( res => {
+ window.setTimeout( res, time );
+ } );
+}
diff --git a/packages/ckeditor5-easy-image/ckeditor5-metadata.json b/packages/ckeditor5-easy-image/ckeditor5-metadata.json
index 260bf92da1f..ecf163ecac5 100644
--- a/packages/ckeditor5-easy-image/ckeditor5-metadata.json
+++ b/packages/ckeditor5-easy-image/ckeditor5-metadata.json
@@ -4,7 +4,6 @@
"name": "Easy Image",
"className": "EasyImage",
"description": "An image upload tool with virtually zero server setup. The images are automatically rescaled, optimized, responsive and delivered through a CDN.",
- "docs": "features/images/image-upload/easy-image.html",
"path": "src/easyimage.js",
"requires": [
"CloudServices",
diff --git a/packages/ckeditor5-easy-image/docs/_snippets/features/easy-image.js b/packages/ckeditor5-easy-image/docs/_snippets/features/easy-image.js
index 974f7760718..51108cd2c98 100644
--- a/packages/ckeditor5-easy-image/docs/_snippets/features/easy-image.js
+++ b/packages/ckeditor5-easy-image/docs/_snippets/features/easy-image.js
@@ -14,7 +14,8 @@ ClassicEditor
viewportOffset: {
top: window.getViewportTopOffsetConfig()
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-easy-image/docs/api/easy-image.md b/packages/ckeditor5-easy-image/docs/api/easy-image.md
index 76e2ec9c6ae..c2ea5318fd3 100644
--- a/packages/ckeditor5-easy-image/docs/api/easy-image.md
+++ b/packages/ckeditor5-easy-image/docs/api/easy-image.md
@@ -6,13 +6,13 @@ category: api-reference
[![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-easy-image.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-easy-image)
-This package implements the {@link features/easy-image Easy Image} feature for CKEditor 5.
+This package implements the Easy Image feature for CKEditor 5.
Easy Image lets you easily insert images which are automatically rescaled, optimized, responsive and delivered through a blazing-fast CDN. It integrates automatically with the [CKEditor Cloud Services](https://ckeditor.com/ckeditor-cloud-services/).
## Documentation
-See the {@link features/easy-image "Easy Image integration" guide} and the {@link module:easy-image/easyimage~EasyImage plugin documentation} to learn how to enable the integration.
+See the {@link module:easy-image/easyimage~EasyImage plugin documentation} to learn how to enable the integration.
Check out the {@link features/image-upload comprehensive "Image upload" guide} to learn about other ways to upload images into CKEditor 5.
diff --git a/packages/ckeditor5-easy-image/docs/features/easy-image.md b/packages/ckeditor5-easy-image/docs/features/easy-image.md
deleted file mode 100644
index aa91b5a4927..00000000000
--- a/packages/ckeditor5-easy-image/docs/features/easy-image.md
+++ /dev/null
@@ -1,167 +0,0 @@
----
-category: features-image-upload
-menu-title: Easy Image
-meta-title: Easy Image | CKEditor 5 Documentation
-order: 30
-badges: [ premium ]
----
-
-# Easy Image integration
-
-The [Easy Image](https://ckeditor.com/ckeditor-cloud-services/easy-image/) is an intuitive tool for uploading images. Unlike the {@link features/ckbox CKBox} feature, which is a full-fledged file manager, Easy Image concentrates on upload only.
-
-
- This is a premium feature and you need a license for it on top of your CKEditor 5 commercial license. [Contact us](https://ckeditor.com/contact/?sales=true#contact-form) to receive an offer tailored to your needs.
-
- You can also sign up for the [CKEditor Premium Features 30-day free trial](https://orders.ckeditor.com/trial/premium-features) to test the feature.
-
-
-## Demo
-
-The demo below uses the classic editor type. It is configured to use the Easy Image service provided by CKEditor Cloud Services.
-
-{@snippet build-classic-source}
-
-{@snippet features/easy-image}
-
-
- This demo presents a limited set of features. Visit the {@link examples/builds/full-featured-editor feature-rich editor example} to see more in action.
-
-
-## Additional feature information
-
-Easy Image is part of the CKEditor Cloud Services. It is a SaaS product which:
-
-* securely uploads images,
-* takes care of rescaling and [optimizing them](https://ckeditor.com/docs/cs/latest/guides/easy-image/service-details.html#image-processing) as well as providing [various image sizes](#responsive-images) (responsive images),
-* delivers uploaded images through a blazing-fast CDN.
-
-All that with virtually zero server setup.
-
-## Configuration
-
-To make enabling image upload in CKEditor 5 a breeze, the {@link module:easy-image/easyimage~EasyImage `EasyImage` plugin} integrates with the Easy Image service provided by [CKEditor Cloud Services](https://ckeditor.com/ckeditor-cloud-services/). Enabling it is straightforward and the results are immediate:
-
-1. Follow the [Easy Image – Quick start guide](https://ckeditor.com/docs/cs/latest/guides/easy-image/quick-start.html) to set up an account.
-2. Configure CKEditor 5 (see {@link module:cloud-services/cloudservicesconfig~CloudServicesConfig `CloudServicesConfig`}):
-
-```js
-ClassicEditor
- .create( document.querySelector( '#editor' ), {
- cloudServices: {
- tokenUrl: 'https://example.com/cs-token-endpoint',
- uploadUrl: 'https://your-organization-id.cke-cs.com/easyimage/upload/'
- }
- } )
- .then( /* ... */ )
- .catch( /* ... */ );
-```
-
-This is all. At this point, image upload will be automatically enabled in your application.
-
-If you are having trouble with setting up Easy Image, please [contact us](https://ckeditor.com/contact/).
-
-### Configuring allowed file types
-
-The allowed file types that can be uploaded should actually be configured in two places:
-
-* On the client side, in CKEditor 5, restricting image upload through the CKEditor 5 UI and commands.
-* On the server side, in Easy Image, restricting the file formats that are accepted in Easy Image.
-
-#### Client-side configuration
-
-Use the {@link module:image/imageconfig~ImageUploadConfig#types `image.upload.types`} configuration option to define the allowed image MIME types that can be uploaded to CKEditor 5.
-
-By default, users are allowed to upload `jpeg`, `png`, `gif`, `bmp`, `webp` and `tiff` files. This corresponds with file formats supported by Easy Image, but you can modify the list to limit the number of allowed image types.
-
-#### Server-side configuration
-
-Check the [list of file formats supported by Easy Image](https://ckeditor.com/docs/cs/latest/guides/easy-image/service-details.html#supported-file-formats). At the moment it is not possible to limit or extend this list so any restrictions need to be introduced on the client side.
-
-## Responsive images
-
-Another great feature introduced with CKEditor 5 is the ability to have responsive images in the rich-text editor content. With a single image upload, several optimized versions of that image are created, each for a different size of the display. All this is transparent to the end user who uploaded the image.
-
-{@img assets/img/responsive-images.svg 550 The visualization of the responsive images approach for CKEditor 5 WYSIWYG editor.}
-
-### Why responsive images?
-
-Responsive images have two main advantages over the "traditional" image delivery:
-
-* **They save the data transfer**. There are countless device and screen size combinations that can be used to display images in your application (smartphones, tablets, laptops, etc.). You do not need to serve the same full–scale images to all of them, though.
-
- Using Easy Image guarantees only the particular size variant corresponding to the user's screen size is served, minimizing the amount of data transferred to the client. For large images, this can save up to 90% of the transferred data – [see it yourself!](https://ckeditor.com/ckeditor-cloud-services/easy-image/)
-* **They load faster**. Because only the image matching the size of the screen is transferred, in most cases it can be loaded and displayed much faster than a "regular" full–scale image. The faster it loads, the sooner the users can see it, which greatly improves the user experience of your application. You no longer need to wait ages for high–resolution photos to load on a tiny smartphone screen.
-
-### Responsive images in the markup
-
-Responsive images delivered by the Easy Image service are transparent to your application. Once uploaded, the image appears in the editor content as a "regular" image but with some additional attributes like the `srcset`.
-
-The `srcset` attribute specifies the image variants dedicated for the various screen sizes for the web browser to choose from (360px, 720px, 1080px, 1440px, etc.). For instance, the `image.jpg` file uploaded by the user will have the following markup:
-
-```html
-
-```
-
-The variety of the image sizes in the `srcset` attribute allows the web browser to choose the best one for the particular screen size. As a result, it loads faster and with less data transferred. See the detailed [Easy Image service documentation](https://ckeditor.com/docs/cs/latest/guides/easy-image/service-details.html) to learn more about responsive images and other features offered by the service.
-
-## Installation
-
-
- ⚠️ **New import paths**
-
- Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-
-
-This package is part of our open-source aggregate package.
-
-```bash
-npm install ckeditor5
-```
-
-Then add {@link module:easy-image/easyimage~EasyImage} to your plugin list and [configure](#configuration) the feature. For instance:
-
-```js
-import { EasyImage, Image } from 'ckeditor5';
-
-ClassicEditor
- .create( document.querySelector( '#editor' ), {
- plugins: [ EasyImage, Image, /* ... */ ],
- toolbar: [ 'uploadImage', /* ... */ ],
-
- // Configure the endpoint. See the "Configuration" section above.
- cloudServices: {
- tokenUrl: 'https://example.com/cs-token-endpoint',
- uploadUrl: 'https://your-organization-id.cke-cs.com/easyimage/upload/'
- }
- } )
- .then( /* ... */ )
- .catch( /* ... */ );
-```
-
-
- Please note that most integrations will also require the {@link module:image/image~Image} plugin to be loaded in the editor to make this feature work properly (or one of {@link module:image/imageblock~ImageBlock} or {@link module:image/imageinline~ImageInline}). Check out the comprehensive {@link features/images-installation guide to images} in CKEditor 5 to learn more.
-
-
-## What's next
-
-Check out the comprehensive {@link features/image-upload Image upload overview} to learn more about different ways of uploading images in CKEditor 5.
-
-See the {@link features/images-overview Image feature} guide to find out more about handling images in CKEditor 5.
-
-## Contribute
-
-The source code of the feature is available on GitHub at [https://github.com/ckeditor/ckeditor5/tree/master/packages/ckeditor5-easy-image](https://github.com/ckeditor/ckeditor5/tree/master/packages/ckeditor5-easy-image).
diff --git a/packages/ckeditor5-easy-image/src/easyimage.ts b/packages/ckeditor5-easy-image/src/easyimage.ts
index 64d9512ee55..064c42e337c 100644
--- a/packages/ckeditor5-easy-image/src/easyimage.ts
+++ b/packages/ckeditor5-easy-image/src/easyimage.ts
@@ -26,7 +26,7 @@ import CloudServicesUploadAdapter from './cloudservicesuploadadapter.js';
* * {@link module:image/image~Image},
* * {@link module:image/imageupload~ImageUpload},
*
- * See the {@glink features/images/image-upload/easy-image "Easy Image integration" guide} to learn how to configure
+ * See the [Easy Image Quick Start guide](https://ckeditor.com/docs/cs/latest/guides/easy-image/quick-start.html) to learn how to configure
* and use this feature.
*
* Check out the {@glink features/images/image-upload/image-upload comprehensive "Image upload" guide} to learn about
diff --git a/packages/ckeditor5-editor-decoupled/docs/framework/document-editor.md b/packages/ckeditor5-editor-decoupled/docs/framework/document-editor.md
index 8ce56387a85..e09e339d7f2 100644
--- a/packages/ckeditor5-editor-decoupled/docs/framework/document-editor.md
+++ b/packages/ckeditor5-editor-decoupled/docs/framework/document-editor.md
@@ -42,10 +42,6 @@ DecoupledEditor.create( document.querySelector( '.document-editor__editable' ),
You may have noticed that you have to make sure the editor UI is injected into your application after it fires the {@link module:ui/editorui/editorui~EditorUI#event:ready `EditorUI#ready`} event. The toolbar element can be found under `editor.ui.view.toolbar.element`.
-
- Document editor supports the Easy Image plugin provided by [CKEditor Cloud Services](https://ckeditor.com/ckeditor-cloud-services/) out of the box. Please refer to the {@link features/easy-image Easy Image documentation} to learn more.
-
-
## The user interface
The code you have just created will run the editor but the user interface is still missing. Start off with a basic HTML structure to host the editor components (the toolbar and the editable).
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/element-reconversion-demo.js b/packages/ckeditor5-engine/docs/_snippets/framework/element-reconversion-demo.js
index 5db10c14ec5..e00cd10bea9 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/element-reconversion-demo.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/element-reconversion-demo.js
@@ -349,7 +349,8 @@ ClassicEditor
viewportOffset: {
top: window.getViewportTopOffsetConfig()
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-external-link-target.js b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-external-link-target.js
index 1786b61205a..7408808b804 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-external-link-target.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-external-link-target.js
@@ -39,7 +39,8 @@ ClassicEditor
viewportOffset: {
top: window.getViewportTopOffsetConfig()
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-heading-class.js b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-heading-class.js
index c52d1dc994c..f4d67a13563 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-heading-class.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-heading-class.js
@@ -25,7 +25,8 @@ ClassicEditor
viewportOffset: {
top: window.getViewportTopOffsetConfig()
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-link-class.js b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-link-class.js
index 77bbaeb4e3f..bc38e45008a 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-link-class.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-link-class.js
@@ -35,7 +35,8 @@ ClassicEditor
viewportOffset: {
top: window.getViewportTopOffsetConfig()
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-unsafe-link-class.js b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-unsafe-link-class.js
index 5b2d5274e32..f2bdc994ff7 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-unsafe-link-class.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-unsafe-link-class.js
@@ -35,7 +35,8 @@ ClassicEditor
viewportOffset: {
top: window.getViewportTopOffsetConfig()
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-allow-div-attributes.js b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-allow-div-attributes.js
index 9ad3513fec2..34b19c81d6f 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-allow-div-attributes.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-allow-div-attributes.js
@@ -66,7 +66,8 @@ ClassicEditor
viewportOffset: {
top: window.getViewportTopOffsetConfig()
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-allow-link-target.js b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-allow-link-target.js
index 83b1676bbb8..5c81d2dff50 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-allow-link-target.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-allow-link-target.js
@@ -39,7 +39,8 @@ ClassicEditor
viewportOffset: {
top: window.getViewportTopOffsetConfig()
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-arbitrary-attribute-values.js b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-arbitrary-attribute-values.js
index 631a8bee3e8..e65a0d8eab0 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-arbitrary-attribute-values.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-arbitrary-attribute-values.js
@@ -62,7 +62,8 @@ ClassicEditor
},
fontSize: {
options: [ 10, 12, 14, 'default', 18, 20, 22 ]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-element-converter.js b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-element-converter.js
index d6482589f43..fe0b4ea3dd7 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-element-converter.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-element-converter.js
@@ -198,7 +198,8 @@ ClassicEditor
viewportOffset: {
top: window.getViewportTopOffsetConfig()
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-figure-attributes.js b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-figure-attributes.js
index 9b0acfa8d83..8f59c8f1c57 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-figure-attributes.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-figure-attributes.js
@@ -180,7 +180,8 @@ ClassicEditor
viewportOffset: {
top: window.getViewportTopOffsetConfig()
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading-interactive.js b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading-interactive.js
index 8782018a324..65345df1f89 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading-interactive.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading-interactive.js
@@ -47,7 +47,8 @@ function CustomHeading( editor ) {
DecoupledEditor.create( document.querySelector( '#mini-inspector-heading-interactive' ), {
plugins: [ Essentials, CustomHeading ],
- toolbar: []
+ toolbar: [],
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading.js b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading.js
index 7287ec4e08a..fef1c50e728 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-heading.js
@@ -19,7 +19,8 @@ function CustomHeading( editor ) {
DecoupledEditor.create( document.querySelector( '#mini-inspector-heading' ), {
plugins: [ Essentials, CustomHeading ],
- toolbar: []
+ toolbar: [],
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-structure.js b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-structure.js
index 4c546cae69d..da65fcdfec5 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-structure.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-structure.js
@@ -91,7 +91,8 @@ function Structure( editor ) {
DecoupledEditor.create( document.querySelector( '#mini-inspector-structure' ), {
plugins: [ Essentials, Paragraph, Structure ],
- toolbar: []
+ toolbar: [],
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-upcast-attribute.js b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-upcast-attribute.js
index 2ba929ddc05..e38ad16f91b 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-upcast-attribute.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-upcast-attribute.js
@@ -27,7 +27,8 @@ function Image( editor ) {
DecoupledEditor.create( document.querySelector( '#mini-inspector-upcast-attribute' ), {
plugins: [ Essentials, Image ],
- toolbar: []
+ toolbar: [],
+ licenseKey: 'GPL'
} )
.then( editor => {
MiniCKEditorInspector.attach(
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-upcast-element.js b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-upcast-element.js
index fed4b4e9591..0e1037f0869 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-upcast-element.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector-upcast-element.js
@@ -21,7 +21,8 @@ function Example( editor ) {
DecoupledEditor.create( document.querySelector( '#mini-inspector-upcast-element' ), {
plugins: [ Essentials, Example ],
- toolbar: []
+ toolbar: [],
+ licenseKey: 'GPL'
} )
.then( editor => {
MiniCKEditorInspector.attach(
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector.js b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector.js
index a7c25c00b53..130454538e1 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/mini-inspector.js
@@ -131,7 +131,8 @@ DecoupledEditor.defaultConfig = {
}
},
// This value must be kept in sync with the language defined in webpack.config.js.
- language: 'en'
+ language: 'en',
+ licenseKey: 'GPL'
};
window.DecoupledEditor = DecoupledEditor;
diff --git a/packages/ckeditor5-find-and-replace/docs/_snippets/features/find-and-replace-dropdown.js b/packages/ckeditor5-find-and-replace/docs/_snippets/features/find-and-replace-dropdown.js
index 42d427b4e08..9a84e8e3cf1 100644
--- a/packages/ckeditor5-find-and-replace/docs/_snippets/features/find-and-replace-dropdown.js
+++ b/packages/ckeditor5-find-and-replace/docs/_snippets/features/find-and-replace-dropdown.js
@@ -37,7 +37,8 @@ ClassicEditor
},
findAndReplace: {
uiType: 'dropdown'
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorWithDropdown = editor;
diff --git a/packages/ckeditor5-find-and-replace/docs/_snippets/features/find-and-replace.js b/packages/ckeditor5-find-and-replace/docs/_snippets/features/find-and-replace.js
index b11c7ed89fa..3f57fec1067 100644
--- a/packages/ckeditor5-find-and-replace/docs/_snippets/features/find-and-replace.js
+++ b/packages/ckeditor5-find-and-replace/docs/_snippets/features/find-and-replace.js
@@ -34,7 +34,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-find-and-replace/docs/features/find-and-replace.md b/packages/ckeditor5-find-and-replace/docs/features/find-and-replace.md
index d8991d35151..2d77fd562a2 100644
--- a/packages/ckeditor5-find-and-replace/docs/features/find-and-replace.md
+++ b/packages/ckeditor5-find-and-replace/docs/features/find-and-replace.md
@@ -27,15 +27,19 @@ Use the find and replace toolbar button {@icon @ckeditor/ckeditor5-find-and-repl
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, FindAndReplace } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ FindAndReplace, /* ... */ ],
toolbar: [ 'findAndReplace', /* ... */ ],
+ findAndReplace: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -50,6 +54,7 @@ By default, the find and replace form displays inside a dialog. That allows for
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
findAndReplace: {
uiType: 'dropdown'
}
diff --git a/packages/ckeditor5-font/docs/_snippets/features/custom-font-color-and-background-color-options.js b/packages/ckeditor5-font/docs/_snippets/features/custom-font-color-and-background-color-options.js
index a1be15637ee..122d22a4cac 100644
--- a/packages/ckeditor5-font/docs/_snippets/features/custom-font-color-and-background-color-options.js
+++ b/packages/ckeditor5-font/docs/_snippets/features/custom-font-color-and-background-color-options.js
@@ -227,7 +227,8 @@ ClassicEditor
colorPicker: {
format: 'hex'
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
if ( !window.editors ) {
diff --git a/packages/ckeditor5-font/docs/_snippets/features/custom-font-family-options.js b/packages/ckeditor5-font/docs/_snippets/features/custom-font-family-options.js
index a76c6f2dd5c..0f33ddcf92c 100644
--- a/packages/ckeditor5-font/docs/_snippets/features/custom-font-family-options.js
+++ b/packages/ckeditor5-font/docs/_snippets/features/custom-font-family-options.js
@@ -43,7 +43,8 @@ ClassicEditor
'Ubuntu, Arial, sans-serif',
'Ubuntu Mono, Courier New, Courier, monospace'
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-font/docs/_snippets/features/custom-font-size-named-options.js b/packages/ckeditor5-font/docs/_snippets/features/custom-font-size-named-options.js
index cdc52d9704b..5a939a38c76 100644
--- a/packages/ckeditor5-font/docs/_snippets/features/custom-font-size-named-options.js
+++ b/packages/ckeditor5-font/docs/_snippets/features/custom-font-size-named-options.js
@@ -44,7 +44,8 @@ ClassicEditor
'default',
'big'
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-font/docs/_snippets/features/custom-font-size-numeric-options.js b/packages/ckeditor5-font/docs/_snippets/features/custom-font-size-numeric-options.js
index 0e945543bd3..87c20889257 100644
--- a/packages/ckeditor5-font/docs/_snippets/features/custom-font-size-numeric-options.js
+++ b/packages/ckeditor5-font/docs/_snippets/features/custom-font-size-numeric-options.js
@@ -48,7 +48,8 @@ ClassicEditor
19,
21
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-font/docs/_snippets/features/font.js b/packages/ckeditor5-font/docs/_snippets/features/font.js
index f24dfbd40d5..0e12d9a218f 100644
--- a/packages/ckeditor5-font/docs/_snippets/features/font.js
+++ b/packages/ckeditor5-font/docs/_snippets/features/font.js
@@ -35,7 +35,8 @@ ClassicEditor
ckbox: {
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-font/docs/features/font.md b/packages/ckeditor5-font/docs/features/font.md
index f011b28787f..978d9102c48 100644
--- a/packages/ckeditor5-font/docs/features/font.md
+++ b/packages/ckeditor5-font/docs/features/font.md
@@ -40,15 +40,23 @@ The plugin enables the following features in the rich-text editor:
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Font } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Font, /* ... */ ],
toolbar: [ 'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor', /* ... */ ]
+ fontFamily: {
+ // Configuration.
+ }
+ fontCOlor: {
+ // Configuration.
+ }
+ // ...
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -79,6 +87,7 @@ For example, the following editor supports two font families besides the default
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
fontFamily: {
options: [
'default',
@@ -103,6 +112,7 @@ By default, all `font-family` values that are not specified in the `config.fontF
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
fontFamily: {
options: [
// Font family configuration options are described in the "Configuring the font family feature" section.
@@ -167,6 +177,7 @@ An example of an editor that supports two font sizes:
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
fontSize: {
options: [
'tiny',
@@ -199,6 +210,7 @@ Here is an example of the WYSIWYG editor that supports numerical font sizes. Not
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
fontSize: {
options: [
9,
@@ -227,6 +239,7 @@ By default, all `font-size` values that are not specified in the `config.fontSiz
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
fontSize: {
options: [
// Numerical values.
@@ -264,6 +277,7 @@ It is possible to configure which colors are available in the color dropdown. Us
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
fontColor: {
colors: [
{
@@ -334,6 +348,7 @@ Usually, you will want to use this option when changing the number of [available
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
fontColor: {
colors: [
// 9 colors defined here.
@@ -367,6 +382,7 @@ By default, the number of displayed document colors is limited to one row, but y
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
fontColor: {
// Display 6 columns in the color grid.
columns: 6,
@@ -403,6 +419,7 @@ To turn off the color picker entirely for the given feature, set the {@link modu
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
fontColor: {
colorPicker: {
// Use 'hex' format for output instead of 'hsl'.
diff --git a/packages/ckeditor5-heading/docs/_snippets/features/custom-heading-buttons.js b/packages/ckeditor5-heading/docs/_snippets/features/custom-heading-buttons.js
index d07a5c357a5..ccb11f59821 100644
--- a/packages/ckeditor5-heading/docs/_snippets/features/custom-heading-buttons.js
+++ b/packages/ckeditor5-heading/docs/_snippets/features/custom-heading-buttons.js
@@ -51,7 +51,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-heading/docs/_snippets/features/custom-heading-elements.js b/packages/ckeditor5-heading/docs/_snippets/features/custom-heading-elements.js
index 27881cbe1a5..46f342ebcd0 100644
--- a/packages/ckeditor5-heading/docs/_snippets/features/custom-heading-elements.js
+++ b/packages/ckeditor5-heading/docs/_snippets/features/custom-heading-elements.js
@@ -57,7 +57,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-heading/docs/_snippets/features/custom-heading-levels.js b/packages/ckeditor5-heading/docs/_snippets/features/custom-heading-levels.js
index 4afb90f0381..b8741b221e0 100644
--- a/packages/ckeditor5-heading/docs/_snippets/features/custom-heading-levels.js
+++ b/packages/ckeditor5-heading/docs/_snippets/features/custom-heading-levels.js
@@ -47,7 +47,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-heading/docs/_snippets/features/default-headings.js b/packages/ckeditor5-heading/docs/_snippets/features/default-headings.js
index 0c293a560d8..a2df322276f 100644
--- a/packages/ckeditor5-heading/docs/_snippets/features/default-headings.js
+++ b/packages/ckeditor5-heading/docs/_snippets/features/default-headings.js
@@ -48,7 +48,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-heading/docs/_snippets/features/heading-buttons.js b/packages/ckeditor5-heading/docs/_snippets/features/heading-buttons.js
index a8beea0f138..1b06b57e4ff 100644
--- a/packages/ckeditor5-heading/docs/_snippets/features/heading-buttons.js
+++ b/packages/ckeditor5-heading/docs/_snippets/features/heading-buttons.js
@@ -48,7 +48,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-heading/docs/_snippets/features/title.js b/packages/ckeditor5-heading/docs/_snippets/features/title.js
index 7e7b28f640b..d751dbb255e 100644
--- a/packages/ckeditor5-heading/docs/_snippets/features/title.js
+++ b/packages/ckeditor5-heading/docs/_snippets/features/title.js
@@ -105,7 +105,8 @@ BalloonEditor.defaultConfig = {
]
},
// This value must be kept in sync with the language defined in webpack.config.js.
- language: 'en'
+ language: 'en',
+ licenseKey: 'GPL'
};
BalloonEditor.builtinPlugins.push( Title );
@@ -133,7 +134,8 @@ BalloonEditor
'blockQuote',
'insertTable',
'mediaEmbed'
- ]
+ ],
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-heading/docs/features/headings.md b/packages/ckeditor5-heading/docs/features/headings.md
index aec5d8c8c80..d00cf72e6ab 100644
--- a/packages/ckeditor5-heading/docs/features/headings.md
+++ b/packages/ckeditor5-heading/docs/features/headings.md
@@ -47,15 +47,19 @@ The heading feature lets you also use a set of heading buttons instead of the dr
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Heading } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Heading, /* ... */ ],
toolbar: [ 'heading', /* ... */ ]
+ heading: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -88,6 +92,7 @@ For example, the following editor will support only two levels of headings &ndas
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
heading: {
options: [
{ model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
@@ -128,6 +133,7 @@ For example, the following editor will support the following two heading options
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
heading: {
options: [
{ model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
@@ -161,6 +167,7 @@ To use individual toolbar buttons instead of the heading dropdown, you need to p
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
toolbar: [ 'paragraph', 'heading1', 'heading2', 'heading3', 'heading4', 'heading5', 'heading6', '|', 'undo', 'redo' ],
heading: {
options: [
diff --git a/packages/ckeditor5-heading/docs/features/title.md b/packages/ckeditor5-heading/docs/features/title.md
index cf87732463c..aff9e4b43bb 100644
--- a/packages/ckeditor5-heading/docs/features/title.md
+++ b/packages/ckeditor5-heading/docs/features/title.md
@@ -30,14 +30,18 @@ The title plugin lets you move from the title to the body element using the
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Title } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Title, /* ... */ ]
+ title: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -52,7 +56,7 @@ To change the title placeholder, use the {@link module:heading/title~TitleConfig
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ Title, /* ... */ ],
+ // ... Other configuration options ...
title: {
placeholder: 'My custom placeholder for the title'
},
diff --git a/packages/ckeditor5-highlight/docs/_snippets/features/custom-highlight-colors-inline.js b/packages/ckeditor5-highlight/docs/_snippets/features/custom-highlight-colors-inline.js
index 2bc859f3cd7..bd9e1dac036 100644
--- a/packages/ckeditor5-highlight/docs/_snippets/features/custom-highlight-colors-inline.js
+++ b/packages/ckeditor5-highlight/docs/_snippets/features/custom-highlight-colors-inline.js
@@ -59,7 +59,8 @@ ClassicEditor
type: 'pen'
}
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-highlight/docs/_snippets/features/custom-highlight-colors-variables.js b/packages/ckeditor5-highlight/docs/_snippets/features/custom-highlight-colors-variables.js
index 2f23b33a289..7dcac3e1abf 100644
--- a/packages/ckeditor5-highlight/docs/_snippets/features/custom-highlight-colors-variables.js
+++ b/packages/ckeditor5-highlight/docs/_snippets/features/custom-highlight-colors-variables.js
@@ -59,7 +59,8 @@ ClassicEditor
type: 'pen'
}
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-highlight/docs/_snippets/features/custom-highlight-options.js b/packages/ckeditor5-highlight/docs/_snippets/features/custom-highlight-options.js
index 156e11f5a32..bfe13edb0e2 100644
--- a/packages/ckeditor5-highlight/docs/_snippets/features/custom-highlight-options.js
+++ b/packages/ckeditor5-highlight/docs/_snippets/features/custom-highlight-options.js
@@ -52,7 +52,8 @@ ClassicEditor
type: 'pen'
}
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-highlight/docs/_snippets/features/highlight-buttons.js b/packages/ckeditor5-highlight/docs/_snippets/features/highlight-buttons.js
index 58447d11527..b90ef64953a 100644
--- a/packages/ckeditor5-highlight/docs/_snippets/features/highlight-buttons.js
+++ b/packages/ckeditor5-highlight/docs/_snippets/features/highlight-buttons.js
@@ -35,7 +35,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-highlight/docs/_snippets/features/highlight.js b/packages/ckeditor5-highlight/docs/_snippets/features/highlight.js
index 7be4ddc9c8e..05c5559bad6 100644
--- a/packages/ckeditor5-highlight/docs/_snippets/features/highlight.js
+++ b/packages/ckeditor5-highlight/docs/_snippets/features/highlight.js
@@ -34,7 +34,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-highlight/docs/features/highlight.md b/packages/ckeditor5-highlight/docs/features/highlight.md
index 845e164cd10..e46d5bb8e4b 100644
--- a/packages/ckeditor5-highlight/docs/features/highlight.md
+++ b/packages/ckeditor5-highlight/docs/features/highlight.md
@@ -27,15 +27,19 @@ Select the text you want to highlight. Then use the highlight toolbar button {@i
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Highlight } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Highlight, /* ... */ ],
toolbar: [ 'highlight', /* ... */ ]
+ highlight: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -58,6 +62,7 @@ For example, the following editor supports two styles (a green marker and a red
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
highlight: {
options: [
{
@@ -75,10 +80,7 @@ ClassicEditor
type: 'pen'
}
]
- },
- toolbar: [
- 'heading', '|', 'bulletedList', 'numberedList', 'highlight', 'undo', 'redo'
- ]
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -93,6 +95,7 @@ Instead of using the (default) `'highlight'` button, the feature also supports a
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
toolbar: {
items: [
'heading',
@@ -142,6 +145,7 @@ You can use inline color values in the `rgba(R, G, B, A)`, `#RRGGBB[AA]`, or `hs
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
highlight: {
options: [
{
@@ -166,10 +170,7 @@ ClassicEditor
type: 'pen'
}
]
- },
- toolbar: [
- 'heading', '|', 'bulletedList', 'numberedList', 'highlight', 'undo', 'redo'
- ]
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
diff --git a/packages/ckeditor5-horizontal-line/docs/_snippets/features/horizontal-line.js b/packages/ckeditor5-horizontal-line/docs/_snippets/features/horizontal-line.js
index f960d95ba5a..1e4fa82c67c 100644
--- a/packages/ckeditor5-horizontal-line/docs/_snippets/features/horizontal-line.js
+++ b/packages/ckeditor5-horizontal-line/docs/_snippets/features/horizontal-line.js
@@ -62,7 +62,8 @@ ClassicEditor
table: {
contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-horizontal-line/docs/features/horizontal-line.md b/packages/ckeditor5-horizontal-line/docs/features/horizontal-line.md
index ae56ad2166c..bdde95a2046 100644
--- a/packages/ckeditor5-horizontal-line/docs/features/horizontal-line.md
+++ b/packages/ckeditor5-horizontal-line/docs/features/horizontal-line.md
@@ -26,13 +26,14 @@ To insert a horizontal line in the demo below, use the toolbar button {@icon @ck
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, HorizontalLine } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ HorizontalLine, /* ... */ ],
toolbar: [ 'horizontalLine', /* ... */ ],
} )
diff --git a/packages/ckeditor5-html-embed/docs/_snippets/features/html-embed.js b/packages/ckeditor5-html-embed/docs/_snippets/features/html-embed.js
index 3dfaf33fae1..fc706461b9e 100644
--- a/packages/ckeditor5-html-embed/docs/_snippets/features/html-embed.js
+++ b/packages/ckeditor5-html-embed/docs/_snippets/features/html-embed.js
@@ -44,7 +44,7 @@ const initialData =
Documentation
See:
@@ -109,7 +109,8 @@ ClassicEditor
table: {
contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-html-embed/docs/features/html-embed.md b/packages/ckeditor5-html-embed/docs/features/html-embed.md
index 7be306baceb..f5a643c8d36 100644
--- a/packages/ckeditor5-html-embed/docs/features/html-embed.md
+++ b/packages/ckeditor5-html-embed/docs/features/html-embed.md
@@ -48,15 +48,19 @@ We recommended using the {@link features/media-embed media embed} feature for em
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, HtmlEmbed } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ HtmlEmbed, /* ... */ ],
toolbar: [ 'htmlEmbed', /* ... */ ],
+ htmlEmbed: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -73,8 +77,7 @@ However, by showing previews of the embedded HTML snippets, you expose the users
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ HtmlEmbed, /* ... */ ],
- toolbar: [ 'htmlEmbed', /* ... */ ],
+ // ... Other configuration options ...
htmlEmbed: {
showPreviews: true,
sanitizeHtml: ( inputHtml ) => {
diff --git a/packages/ckeditor5-html-support/docs/_snippets/features/full-page-html.js b/packages/ckeditor5-html-support/docs/_snippets/features/full-page-html.js
index 123239c6cc6..7741fc9d35f 100644
--- a/packages/ckeditor5-html-support/docs/_snippets/features/full-page-html.js
+++ b/packages/ckeditor5-html-support/docs/_snippets/features/full-page-html.js
@@ -70,7 +70,8 @@ ClassicEditor
},
{ name: 'script' }
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-html-support/docs/_snippets/features/general-html-support-source.js b/packages/ckeditor5-html-support/docs/_snippets/features/general-html-support-source.js
index 3334ef24271..5059d93ff1e 100644
--- a/packages/ckeditor5-html-support/docs/_snippets/features/general-html-support-source.js
+++ b/packages/ckeditor5-html-support/docs/_snippets/features/general-html-support-source.js
@@ -60,7 +60,8 @@ ClassicEditor.defaultConfig = {
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
};
window.ClassicEditor = ClassicEditor;
diff --git a/packages/ckeditor5-html-support/docs/_snippets/features/general-html-support.js b/packages/ckeditor5-html-support/docs/_snippets/features/general-html-support.js
index 6249ad7c173..b690953160f 100644
--- a/packages/ckeditor5-html-support/docs/_snippets/features/general-html-support.js
+++ b/packages/ckeditor5-html-support/docs/_snippets/features/general-html-support.js
@@ -69,7 +69,8 @@ ClassicEditor
},
{ name: 'script' }
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-html-support/docs/_snippets/features/html-comment.js b/packages/ckeditor5-html-support/docs/_snippets/features/html-comment.js
index 8b4a8c5bb0e..81ab74f96a7 100644
--- a/packages/ckeditor5-html-support/docs/_snippets/features/html-comment.js
+++ b/packages/ckeditor5-html-support/docs/_snippets/features/html-comment.js
@@ -33,7 +33,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor2 = editor;
diff --git a/packages/ckeditor5-html-support/docs/features/full-page-html.md b/packages/ckeditor5-html-support/docs/features/full-page-html.md
index c898e24713d..11695c68b0c 100644
--- a/packages/ckeditor5-html-support/docs/features/full-page-html.md
+++ b/packages/ckeditor5-html-support/docs/features/full-page-html.md
@@ -25,13 +25,14 @@ Use the {@link features/source-editing source editing feature} toolbar button {@
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, FullPage } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ FullPage, /* ... */ ],
} )
.then( /* ... */ )
diff --git a/packages/ckeditor5-html-support/docs/features/general-html-support.md b/packages/ckeditor5-html-support/docs/features/general-html-support.md
index a0aeb0daa42..419b4fd29bc 100644
--- a/packages/ckeditor5-html-support/docs/features/general-html-support.md
+++ b/packages/ckeditor5-html-support/docs/features/general-html-support.md
@@ -61,14 +61,18 @@ Therefore, the main use cases for GHS would be:
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, GeneralHtmlSupport } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ GeneralHtmlSupport, /* ... */ ],
+ htmlSupport: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -81,6 +85,7 @@ By default, enabling the {@link module:html-support/generalhtmlsupport~GeneralHt
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
htmlSupport: {
allow: [ /* HTML features to allow. */ ],
disallow: [ /* HTML features to disallow. */ ]
diff --git a/packages/ckeditor5-html-support/docs/features/html-comments.md b/packages/ckeditor5-html-support/docs/features/html-comments.md
index 163d8484534..70f17b7c5a1 100644
--- a/packages/ckeditor5-html-support/docs/features/html-comments.md
+++ b/packages/ckeditor5-html-support/docs/features/html-comments.md
@@ -31,21 +31,14 @@ The editor below is configured to keep HTML comments in the document content. Yo
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-To add this feature to your rich-text editor, install the [`@ckeditor/ckeditor5-html-support`](https://www.npmjs.com/package/@ckeditor/ckeditor5-html-support) package:
-
-This package is part of our open-source aggregate package.
-
-```bash
-npm install ckeditor5
-```
-
-Then add it to the editor configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { HtmlComment } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ HtmlComment, ... ],
} )
.then( ... )
diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-caption.js b/packages/ckeditor5-image/docs/_snippets/features/image-caption.js
index 02a3871488b..d4b382388a7 100644
--- a/packages/ckeditor5-image/docs/_snippets/features/image-caption.js
+++ b/packages/ckeditor5-image/docs/_snippets/features/image-caption.js
@@ -43,7 +43,8 @@ ClassicEditor
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorCaption = editor;
diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-full.js b/packages/ckeditor5-image/docs/_snippets/features/image-full.js
index 6c5f0dfb664..facb8a21d7b 100644
--- a/packages/ckeditor5-image/docs/_snippets/features/image-full.js
+++ b/packages/ckeditor5-image/docs/_snippets/features/image-full.js
@@ -76,7 +76,8 @@ ClassicEditor
'mergeTableCells'
]
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-insert-via-pasting-url-into-editor.js b/packages/ckeditor5-image/docs/_snippets/features/image-insert-via-pasting-url-into-editor.js
index 71b22a8b910..d43a5d7f5a6 100644
--- a/packages/ckeditor5-image/docs/_snippets/features/image-insert-via-pasting-url-into-editor.js
+++ b/packages/ckeditor5-image/docs/_snippets/features/image-insert-via-pasting-url-into-editor.js
@@ -39,7 +39,8 @@ ClassicEditor
'|',
'ckboxImageEdit'
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorInsertImageViaPastingUrlIntoEditor = editor;
diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-insert-via-url.js b/packages/ckeditor5-image/docs/_snippets/features/image-insert-via-url.js
index ca1f07031ed..2f461b63d8f 100644
--- a/packages/ckeditor5-image/docs/_snippets/features/image-insert-via-url.js
+++ b/packages/ckeditor5-image/docs/_snippets/features/image-insert-via-url.js
@@ -46,7 +46,8 @@ ClassicEditor
integrations: [ 'url' ]
}
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorInsertImageViaUrl = editor;
diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-link.js b/packages/ckeditor5-image/docs/_snippets/features/image-link.js
index 7b00df4b1b7..c6d10e7a010 100644
--- a/packages/ckeditor5-image/docs/_snippets/features/image-link.js
+++ b/packages/ckeditor5-image/docs/_snippets/features/image-link.js
@@ -41,7 +41,8 @@ ClassicEditor
'ckboxImageEdit'
]
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorLinks = editor;
diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-resize-buttons-dropdown.js b/packages/ckeditor5-image/docs/_snippets/features/image-resize-buttons-dropdown.js
index e51bd158a65..8bfb55a88ae 100644
--- a/packages/ckeditor5-image/docs/_snippets/features/image-resize-buttons-dropdown.js
+++ b/packages/ckeditor5-image/docs/_snippets/features/image-resize-buttons-dropdown.js
@@ -56,7 +56,8 @@ ClassicEditor
],
toolbar: [ 'resizeImage', '|', 'ckboxImageEdit' ]
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorResizeUIDropdown = editor;
diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-resize-buttons.js b/packages/ckeditor5-image/docs/_snippets/features/image-resize-buttons.js
index 98051c38d61..1459c5bad4e 100644
--- a/packages/ckeditor5-image/docs/_snippets/features/image-resize-buttons.js
+++ b/packages/ckeditor5-image/docs/_snippets/features/image-resize-buttons.js
@@ -66,7 +66,8 @@ ClassicEditor
'ckboxImageEdit'
]
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorResizeUI = editor;
diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-resize-px.js b/packages/ckeditor5-image/docs/_snippets/features/image-resize-px.js
index 0e02fd70bbb..316c022f7c1 100644
--- a/packages/ckeditor5-image/docs/_snippets/features/image-resize-px.js
+++ b/packages/ckeditor5-image/docs/_snippets/features/image-resize-px.js
@@ -56,7 +56,8 @@ ClassicEditor
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorResizePX = editor;
diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-resize.js b/packages/ckeditor5-image/docs/_snippets/features/image-resize.js
index 1ed5c9cf900..f6cb472cd87 100644
--- a/packages/ckeditor5-image/docs/_snippets/features/image-resize.js
+++ b/packages/ckeditor5-image/docs/_snippets/features/image-resize.js
@@ -39,7 +39,8 @@ ClassicEditor
'ckboxImageEdit'
]
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorResize = editor;
diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-responsive.js b/packages/ckeditor5-image/docs/_snippets/features/image-responsive.js
index 714bc4d8c0d..931894edee4 100644
--- a/packages/ckeditor5-image/docs/_snippets/features/image-responsive.js
+++ b/packages/ckeditor5-image/docs/_snippets/features/image-responsive.js
@@ -76,7 +76,8 @@ ClassicEditor
forceDemoLabel: true,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ]
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-semantical-style.js b/packages/ckeditor5-image/docs/_snippets/features/image-semantical-style.js
index ddbf35c48a6..cfb38e25dfc 100644
--- a/packages/ckeditor5-image/docs/_snippets/features/image-semantical-style.js
+++ b/packages/ckeditor5-image/docs/_snippets/features/image-semantical-style.js
@@ -33,7 +33,8 @@ ClassicEditor
},
image: {
toolbar: [ 'imageStyle:inline', 'imageStyle:block', 'imageStyle:wrapText', '|', 'ckboxImageEdit' ]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorStyleSemantical = editor;
diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-style-custom.js b/packages/ckeditor5-image/docs/_snippets/features/image-style-custom.js
index b386212acc8..0e7644e6b9e 100644
--- a/packages/ckeditor5-image/docs/_snippets/features/image-style-custom.js
+++ b/packages/ckeditor5-image/docs/_snippets/features/image-style-custom.js
@@ -79,7 +79,8 @@ ClassicEditor
defaultItem: 'imageStyle:block'
}, '|', 'toggleImageCaption', 'linkImage', '|', 'ckboxImageEdit'
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorStyleCustom = editor;
diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-style-presentational.js b/packages/ckeditor5-image/docs/_snippets/features/image-style-presentational.js
index 3c1ca8208b1..01f5c37f4b4 100644
--- a/packages/ckeditor5-image/docs/_snippets/features/image-style-presentational.js
+++ b/packages/ckeditor5-image/docs/_snippets/features/image-style-presentational.js
@@ -63,7 +63,8 @@ ClassicEditor
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorStylePresentational = editor;
diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-text-alternative.js b/packages/ckeditor5-image/docs/_snippets/features/image-text-alternative.js
index 63e746c82bd..2c4e272a6bb 100644
--- a/packages/ckeditor5-image/docs/_snippets/features/image-text-alternative.js
+++ b/packages/ckeditor5-image/docs/_snippets/features/image-text-alternative.js
@@ -43,7 +43,8 @@ ClassicEditor
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorCaption = editor;
diff --git a/packages/ckeditor5-image/docs/features/images-captions.md b/packages/ckeditor5-image/docs/features/images-captions.md
index 8ded26813f2..6f05d9415aa 100644
--- a/packages/ckeditor5-image/docs/features/images-captions.md
+++ b/packages/ckeditor5-image/docs/features/images-captions.md
@@ -40,12 +40,6 @@ By default, if the image caption is empty, the `` element is not vis
## Installation
-
- ⚠️ **New import paths**
-
- Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-
-
To enable this feature, you need to load the {@link module:link/linkimage~LinkImage} plugin. Read more in the {@link features/images-installation installation guide}.
## Common API
diff --git a/packages/ckeditor5-image/docs/features/images-inserting.md b/packages/ckeditor5-image/docs/features/images-inserting.md
index 542e8a57828..8b94a2be8d7 100644
--- a/packages/ckeditor5-image/docs/features/images-inserting.md
+++ b/packages/ckeditor5-image/docs/features/images-inserting.md
@@ -38,6 +38,7 @@ import { ClassicEditor, Image, ImageInsert } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Image, ImageInsert, /* ... */ ],
toolbar: [ 'insertImage', /* ... */ ]
} )
@@ -68,10 +69,9 @@ You can paste an image URL directly into the editor content, and it will be auto
The {@link module:image/autoimage~AutoImage} plugin recognizes image links in the pasted content and embeds them shortly after they are injected into the document to speed up the editing. Accepted image extensions are: `jpg`, `jpeg`, `png`, `gif`, and `ico`. Use the following code to enable the plugin in your editor. There is no toolbar configuration for this feature.
```js
-import { ClassicEditor, Image, AutoImage } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
plugins: [ /* ... */ , Image, AutoImage ]
} )
.then( /* ... */ )
diff --git a/packages/ckeditor5-image/docs/features/images-installation.md b/packages/ckeditor5-image/docs/features/images-installation.md
index d38ba9e616d..5a79eed9cac 100644
--- a/packages/ckeditor5-image/docs/features/images-installation.md
+++ b/packages/ckeditor5-image/docs/features/images-installation.md
@@ -15,7 +15,7 @@ modified_at: 2021-06-17
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the {@link features/images-overview#image-features subfeatures that you need} to your plugin list and to the editor toolbar:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the {@link features/images-overview#image-features subfeatures that you need} to your plugin list and to the editor toolbar:
```js
import {
@@ -30,8 +30,12 @@ import {
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Image, ImageToolbar, ImageCaption, ImageStyle, ImageResize, LinkImage ],
toolbar: [ 'insertImage', /* ... */ ],
+ image: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -42,11 +46,9 @@ ClassicEditor
The Image feature comes with the unified image insert dropdown component {@icon @ckeditor/ckeditor5-core/theme/icons/image-upload.svg Image insert}. It automatically collects installed image insert methods. For example, if you install the `ImageUpload` plugin, the corresponding button will automatically appear in the dropdown. You only need to add a button to the toolbar:
```js
-import { ClassicEditor, Image } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ Image ],
+ // ... Other configuration options ...
toolbar: [ 'insertImage', /* ... */ ]
} )
.then( /* ... */ )
@@ -64,12 +66,9 @@ Note that the insert methods mentioned above will only be added if you install d
If you need to limit the methods included in the dropdown (apart from not installing a specific feature) or change their order you can use the `image.insert.integration` configuration option:
```js
-import { ClassicEditor, Image } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ Image ],
- toolbar: [ 'insertImage', /* ... */ ],
+ // ... Other configuration options ...
image: {
insert: {
// This is the default configuration, you do not need to provide
@@ -99,6 +98,7 @@ import {
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
plugins: [ Image, ImageToolbar, ImageCaption, ImageStyle, ImageResize, LinkImage ],
toolbar: [ 'insertImage', /* ... */ ],
image: {
diff --git a/packages/ckeditor5-image/docs/features/images-resizing.md b/packages/ckeditor5-image/docs/features/images-resizing.md
index fbdf63c6b40..320dabf47d4 100644
--- a/packages/ckeditor5-image/docs/features/images-resizing.md
+++ b/packages/ckeditor5-image/docs/features/images-resizing.md
@@ -45,9 +45,11 @@ import { ClassicEditor, Image, ImageResizeEditing, ImageResizeHandles } from 'ck
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Image, ImageResizeEditing, ImageResizeHandles, /* ... */ ],
- // More of editor's configuration.
- // ...
+ image: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -327,9 +329,8 @@ import { ClassicEditor, Image, ImageResize } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ Image, ImageResize, /* ... */ ],
- // More of editor's configuration.
- // ...
+ // ... Other configuration options ...
+ plugins: [ Image, ImageResize, /* ... */ ]
} )
.then( /* ... */ )
.catch( /* ... */ );
diff --git a/packages/ckeditor5-image/docs/features/images-styles.md b/packages/ckeditor5-image/docs/features/images-styles.md
index aafc999ffd2..9db43bb4847 100644
--- a/packages/ckeditor5-image/docs/features/images-styles.md
+++ b/packages/ckeditor5-image/docs/features/images-styles.md
@@ -184,8 +184,7 @@ This editor uses custom image styles, custom image toolbar configuration with {@
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
- // More of editor's configuration.
- // ...
+ // ... Other configuration options ...
image: {
styles: {
// Defining custom styling options for the images.
diff --git a/packages/ckeditor5-image/docs/features/images-text-alternative.md b/packages/ckeditor5-image/docs/features/images-text-alternative.md
index b2b78d80f0c..7dc0ef06de5 100644
--- a/packages/ckeditor5-image/docs/features/images-text-alternative.md
+++ b/packages/ckeditor5-image/docs/features/images-text-alternative.md
@@ -48,12 +48,6 @@ When using the {@link features/ckbox CKBox file manager}, you can utilize its {@
## Installation
-
- ⚠️ **New import paths**
-
- Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-
-
Check out the {@link features/images-installation image features installation guide} to learn how to enable this feature.
## Common API
diff --git a/packages/ckeditor5-image/docs/framework/deep-dive/upload-adapter.md b/packages/ckeditor5-image/docs/framework/deep-dive/upload-adapter.md
index 4931e455f01..1cf90005cb4 100644
--- a/packages/ckeditor5-image/docs/framework/deep-dive/upload-adapter.md
+++ b/packages/ckeditor5-image/docs/framework/deep-dive/upload-adapter.md
@@ -354,10 +354,6 @@ Other image sizes can also be provided in the response, allowing [responsive ima
The {@link module:image/imageupload~ImageUpload image upload} plugin is capable of handling multiple image sizes returned by the upload adapter. It will automatically add the URLs to other images sizes to the `srcset` attribute of the image in the content.
-
- The {@link features/easy-image Easy Image} feature provides responsive image support {@link features/easy-image#responsive-images out of the box}.
-
-
Knowing that, you can implement the `XMLHttpRequest#load` listener that resolves the upload promise in the [previous section](#using-xmlhttprequest-in-an-adapter) so that it passes the entire `urls` property of the server response to the image upload plugin:
```js
diff --git a/packages/ckeditor5-indent/docs/_snippets/features/custom-indent-block-classes.js b/packages/ckeditor5-indent/docs/_snippets/features/custom-indent-block-classes.js
index 1689e6aed96..8a018021c07 100644
--- a/packages/ckeditor5-indent/docs/_snippets/features/custom-indent-block-classes.js
+++ b/packages/ckeditor5-indent/docs/_snippets/features/custom-indent-block-classes.js
@@ -41,7 +41,8 @@ ClassicEditor
'custom-block-indent-b',
'custom-block-indent-c'
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-indent/docs/_snippets/features/indent.js b/packages/ckeditor5-indent/docs/_snippets/features/indent.js
index c294621c63d..ba9d26fbb02 100644
--- a/packages/ckeditor5-indent/docs/_snippets/features/indent.js
+++ b/packages/ckeditor5-indent/docs/_snippets/features/indent.js
@@ -34,7 +34,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-indent/docs/features/indent.md b/packages/ckeditor5-indent/docs/features/indent.md
index 6798d569c18..62c44d660bd 100644
--- a/packages/ckeditor5-indent/docs/features/indent.md
+++ b/packages/ckeditor5-indent/docs/features/indent.md
@@ -26,15 +26,19 @@ Use the indent {@icon @ckeditor/ckeditor5-core/theme/icons/indent.svg Indent} or
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Indent, IndentBlock } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Indent, IndentBlock, /* ... */ ],
toolbar: [ 'outdent', 'indent', /* ... */ ]
+ indentBlock: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -53,14 +57,9 @@ The rich-text editor from the {@link features/indent#demo demo} section above us
You can change that value to, for example, `1em`:
```js
-import { ClassicEditor, Indent } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ Indent, /* ... */ ],
- toolbar: {
- items: [ 'heading', '|', 'outdent', 'indent', '|', 'bulletedList', 'numberedList', '|', 'undo', 'redo' ]
- },
+ // ... Other configuration options ...
indentBlock: {
offset: 1,
unit: 'em'
@@ -77,14 +76,9 @@ If you want more semantics in your content, use CSS classes instead of fixed ind
Here is how you can configure the block indentation feature to set indentation by applying one of the defined CSS classes:
```js
-import { ClassicEditor, Indent, IndentBlock } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ Indent, IndentBlock, /* ... */ ],
- toolbar: {
- items: [ 'heading', '|', 'outdent', 'indent', '|', 'bulletedList', 'numberedList', '|', 'undo', 'redo' ]
- },
+ // ... Other configuration options ...
indentBlock: {
classes: [
'custom-block-indent-a', // First step - smallest indentation.
diff --git a/packages/ckeditor5-language/docs/_snippets/features/textpartlanguage.js b/packages/ckeditor5-language/docs/_snippets/features/textpartlanguage.js
index 1b75eb210a3..fad93583e7a 100644
--- a/packages/ckeditor5-language/docs/_snippets/features/textpartlanguage.js
+++ b/packages/ckeditor5-language/docs/_snippets/features/textpartlanguage.js
@@ -55,7 +55,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-language/docs/features/language.md b/packages/ckeditor5-language/docs/features/language.md
index 6c0d9dd4930..5464dafeab0 100644
--- a/packages/ckeditor5-language/docs/features/language.md
+++ b/packages/ckeditor5-language/docs/features/language.md
@@ -32,15 +32,19 @@ The text part language feature implements the [WCAG 3.1.2 Language of Parts](htt
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, TextPartLanguage } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ TextPartLanguage, /* ... */ ],
toolbar: [ 'textPartLanguage', /* ... */ ]
+ language: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -55,8 +59,7 @@ The example below shows the configuration used for the [demo](#demo) above:
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
- // More of editor's configuration.
- // ...
+ // ... Other configuration options ...
language: {
textPartLanguage: [
{ title: 'Arabic', languageCode: 'ar' },
diff --git a/packages/ckeditor5-link/docs/_snippets/features/autolink.js b/packages/ckeditor5-link/docs/_snippets/features/autolink.js
index 3ccd768951a..cdf0d6725c0 100644
--- a/packages/ckeditor5-link/docs/_snippets/features/autolink.js
+++ b/packages/ckeditor5-link/docs/_snippets/features/autolink.js
@@ -29,7 +29,8 @@ ClassicEditor
viewportOffset: {
top: window.getViewportTopOffsetConfig()
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-link/docs/_snippets/features/link.js b/packages/ckeditor5-link/docs/_snippets/features/link.js
index 1adfcbcce43..a44deb14b60 100644
--- a/packages/ckeditor5-link/docs/_snippets/features/link.js
+++ b/packages/ckeditor5-link/docs/_snippets/features/link.js
@@ -34,7 +34,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-link/docs/_snippets/features/linkdecorators.js b/packages/ckeditor5-link/docs/_snippets/features/linkdecorators.js
index f78180b34e2..99232be587e 100644
--- a/packages/ckeditor5-link/docs/_snippets/features/linkdecorators.js
+++ b/packages/ckeditor5-link/docs/_snippets/features/linkdecorators.js
@@ -45,7 +45,8 @@ ClassicEditor
}
}
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
if ( !window.editors ) {
diff --git a/packages/ckeditor5-link/docs/features/link.md b/packages/ckeditor5-link/docs/features/link.md
index 283a5eddd78..32448226912 100644
--- a/packages/ckeditor5-link/docs/features/link.md
+++ b/packages/ckeditor5-link/docs/features/link.md
@@ -38,15 +38,19 @@ CKEditor 5 allows for typing both at the inner and outer boundaries of link
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, AutoLink, Link } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Link, AutoLink, /* ... */ ],
toolbar: [ 'link', /* ... */ ],
+ link: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -76,13 +80,7 @@ The following code runs this editor. Learn more about the [configuration](#confi
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
- toolbar: {
- items: [
- 'link',
- // More toolbar items.
- // ...
- ],
- },
+ // ... Other configuration options ...
link: {
// Automatically add target="_blank" and rel="noopener noreferrer" to all external links.
addTargetToExternalLinks: true,
@@ -120,6 +118,7 @@ A common use case for (automatic) link decorators is adding the `target="_blank"
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
link: {
addTargetToExternalLinks: true
}
@@ -135,6 +134,7 @@ Internally, this configuration corresponds to an [automatic decorator](#adding-a
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
link: {
decorators: {
addTargetToExternalLinks: {
@@ -159,6 +159,7 @@ If you want to leave the decision whether a link should open in a new tab to the
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
link: {
decorators: {
openInNewTab: {
@@ -189,6 +190,7 @@ See a basic configuration example:
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
link: {
defaultProtocol: 'http://'
}
@@ -214,6 +216,7 @@ See a configuration example:
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
link: {
// You can use `s?` suffix like below to allow both `http` and `https` protocols at the same time.
allowedProtocols: [ 'https?', 'tel', 'sms', 'sftp', 'smb', 'slack' ]
@@ -238,6 +241,7 @@ For instance, to create an automatic decorator that adds the `download="file.pdf
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
link: {
decorators: {
detectDownloadable: {
@@ -269,6 +273,7 @@ To configure a "Downloadable" switch button in the link editing balloon that add
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
link: {
decorators: {
toggleDownloadable: {
@@ -289,8 +294,6 @@ ClassicEditor
}
}
}
- // More of the editor's configuration.
- // ...
} )
.then( /* ... */ )
.catch( /* ... */ );
diff --git a/packages/ckeditor5-list/docs/_snippets/features/lists-basic.js b/packages/ckeditor5-list/docs/_snippets/features/lists-basic.js
index fcaa4dcc5c1..92f0980aeac 100644
--- a/packages/ckeditor5-list/docs/_snippets/features/lists-basic.js
+++ b/packages/ckeditor5-list/docs/_snippets/features/lists-basic.js
@@ -51,7 +51,8 @@ ClassicEditor
reversed: true
}
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorBasic = editor;
diff --git a/packages/ckeditor5-list/docs/_snippets/features/lists-document.js b/packages/ckeditor5-list/docs/_snippets/features/lists-document.js
index 5e810691415..b9070dd03c0 100644
--- a/packages/ckeditor5-list/docs/_snippets/features/lists-document.js
+++ b/packages/ckeditor5-list/docs/_snippets/features/lists-document.js
@@ -51,7 +51,8 @@ ClassicEditor
reversed: true
}
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorStyles = editor;
diff --git a/packages/ckeditor5-list/docs/_snippets/features/lists-index.js b/packages/ckeditor5-list/docs/_snippets/features/lists-index.js
index bd2dd674cd2..be890361566 100644
--- a/packages/ckeditor5-list/docs/_snippets/features/lists-index.js
+++ b/packages/ckeditor5-list/docs/_snippets/features/lists-index.js
@@ -51,7 +51,8 @@ ClassicEditor
reversed: false
}
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorStyles = editor;
diff --git a/packages/ckeditor5-list/docs/_snippets/features/lists-reversed.js b/packages/ckeditor5-list/docs/_snippets/features/lists-reversed.js
index caf4a0f4f30..d2b3194451a 100644
--- a/packages/ckeditor5-list/docs/_snippets/features/lists-reversed.js
+++ b/packages/ckeditor5-list/docs/_snippets/features/lists-reversed.js
@@ -51,7 +51,8 @@ ClassicEditor
reversed: true
}
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorStyles = editor;
diff --git a/packages/ckeditor5-list/docs/_snippets/features/lists-style.js b/packages/ckeditor5-list/docs/_snippets/features/lists-style.js
index 343b6f4dfc6..475fb839b28 100644
--- a/packages/ckeditor5-list/docs/_snippets/features/lists-style.js
+++ b/packages/ckeditor5-list/docs/_snippets/features/lists-style.js
@@ -51,7 +51,8 @@ ClassicEditor
reversed: false
}
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorStyles = editor;
diff --git a/packages/ckeditor5-list/docs/_snippets/features/todo-list.js b/packages/ckeditor5-list/docs/_snippets/features/todo-list.js
index aec38b06b2f..48df56b60ba 100644
--- a/packages/ckeditor5-list/docs/_snippets/features/todo-list.js
+++ b/packages/ckeditor5-list/docs/_snippets/features/todo-list.js
@@ -44,7 +44,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-list/docs/features/lists-editing.md b/packages/ckeditor5-list/docs/features/lists-editing.md
index af7deb89ba3..f86ae1651f5 100644
--- a/packages/ckeditor5-list/docs/features/lists-editing.md
+++ b/packages/ckeditor5-list/docs/features/lists-editing.md
@@ -33,6 +33,7 @@ import { ClassicEditor, List } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ List, /* ... */ ],
toolbar: [ 'bulletedList', 'numberedList', /* ... */ ],
list: {
@@ -56,6 +57,7 @@ import { ClassicEditor, List, AdjacentListsSupport } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ List, AdjacentListsSupport, /* ... */ ]
} )
.then( /* ... */ )
diff --git a/packages/ckeditor5-list/docs/features/lists.md b/packages/ckeditor5-list/docs/features/lists.md
index 309f51e51bc..0a8d5028e97 100644
--- a/packages/ckeditor5-list/docs/features/lists.md
+++ b/packages/ckeditor5-list/docs/features/lists.md
@@ -84,15 +84,19 @@ The `List` plugin provides the {@link features/lists ordered (numbered) and unor
### List feature
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
-import { List } from 'ckeditor5';
+import { ClassicEditor, List } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ List, /* ... */ ],
toolbar: [ 'bulletedList', 'numberedList', /* ... */ ]
+ list: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -100,16 +104,16 @@ ClassicEditor
### List properties
-After {@link getting-started/quick-start installing the editor}, add `ListProperties` to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add `ListProperties` to your plugin list and toolbar configuration:
To enable selected sub-features of the list properties, add their configuration to your editor. Set `true` for each feature you want to enable:
```js
-import { ListProperties } from 'ckeditor5';
+import { ClassicEditor, List, ListProperties } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ ListProperties, /* ... */ ],
+ plugins: [ List, ListProperties, /* ... */ ],
toolbar: [ 'bulletedList', 'numberedList', /* ... */ ],
list: {
properties: {
diff --git a/packages/ckeditor5-list/docs/features/todo-lists.md b/packages/ckeditor5-list/docs/features/todo-lists.md
index e2066114269..69b1965e792 100644
--- a/packages/ckeditor5-list/docs/features/todo-lists.md
+++ b/packages/ckeditor5-list/docs/features/todo-lists.md
@@ -31,15 +31,19 @@ You can check and clear a list item by using the Ctrl + Enter
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
-import { TodoList } from 'ckeditor5';
+import { ClassicEditor, TodoList } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ TodoList, /* ... */ ],
toolbar: [ 'todoList', /* ... */ ],
+ list: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
diff --git a/packages/ckeditor5-markdown-gfm/docs/_snippets/features/markdown.js b/packages/ckeditor5-markdown-gfm/docs/_snippets/features/markdown.js
index da83b068f16..5cb1b462b92 100644
--- a/packages/ckeditor5-markdown-gfm/docs/_snippets/features/markdown.js
+++ b/packages/ckeditor5-markdown-gfm/docs/_snippets/features/markdown.js
@@ -67,7 +67,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-markdown-gfm/docs/_snippets/features/paste-from-markdown.js b/packages/ckeditor5-markdown-gfm/docs/_snippets/features/paste-from-markdown.js
index 15634d12033..c63e481d8b1 100644
--- a/packages/ckeditor5-markdown-gfm/docs/_snippets/features/paste-from-markdown.js
+++ b/packages/ckeditor5-markdown-gfm/docs/_snippets/features/paste-from-markdown.js
@@ -80,7 +80,8 @@ ClassicEditor
viewportOffset: {
top: window.getViewportTopOffsetConfig()
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-markdown-gfm/docs/features/markdown.md b/packages/ckeditor5-markdown-gfm/docs/features/markdown.md
index e116ddbd69c..9ce192f14e4 100644
--- a/packages/ckeditor5-markdown-gfm/docs/features/markdown.md
+++ b/packages/ckeditor5-markdown-gfm/docs/features/markdown.md
@@ -35,25 +35,21 @@ Please remember that Markdown syntax is really simple and it does not cover all
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the {@link module:markdown-gfm/markdown~Markdown} plugin to the editor configuration. It will change the default {@link module:engine/dataprocessor/dataprocessor~DataProcessor data processor} to the {@link module:markdown-gfm/gfmdataprocessor~GFMDataProcessor}:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the {@link module:markdown-gfm/markdown~Markdown} plugin to the editor configuration. It will change the default {@link module:engine/dataprocessor/dataprocessor~DataProcessor data processor} to the {@link module:markdown-gfm/gfmdataprocessor~GFMDataProcessor}:
```js
import { ClassicEditor, Bold, Italic, Essentials, Markdown } from 'ckeditor5';
-// More imports.
-// ...
ClassicEditor
.create( document.querySelector( '#snippet-markdown' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [
Markdown,
Essentials,
Bold,
Italic,
// More plugins.
- // ...
],
- // More of editor's configuration.
- // ...
} )
.then( /* ... */ )
.catch( /* ... */ );
diff --git a/packages/ckeditor5-markdown-gfm/docs/features/paste-markdown.md b/packages/ckeditor5-markdown-gfm/docs/features/paste-markdown.md
index 1edc6469772..17740653f71 100644
--- a/packages/ckeditor5-markdown-gfm/docs/features/paste-markdown.md
+++ b/packages/ckeditor5-markdown-gfm/docs/features/paste-markdown.md
@@ -33,15 +33,14 @@ Paste some Markdown-formatted content into the demo editor below and see it turn
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Bold, Italic, Essentials, PasteFromMarkdownExperimental } from 'ckeditor5';
-// More imports.
-// ...
ClassicEditor
.create( document.querySelector( '#snippet-markdown' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [
PasteFromMarkdownExperimental,
Essentials,
@@ -50,8 +49,6 @@ ClassicEditor
// More plugins.
// ...
],
- // More of editor's configuration.
- // ...
} )
.then( /* ... */ )
.catch( /* ... */ );
diff --git a/packages/ckeditor5-media-embed/docs/_snippets/features/media-embed-preview.js b/packages/ckeditor5-media-embed/docs/_snippets/features/media-embed-preview.js
index 9fa2d8125db..62aa6ff2b4a 100644
--- a/packages/ckeditor5-media-embed/docs/_snippets/features/media-embed-preview.js
+++ b/packages/ckeditor5-media-embed/docs/_snippets/features/media-embed-preview.js
@@ -60,7 +60,8 @@ ClassicEditor
}
}
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-media-embed/docs/_snippets/features/media-embed.js b/packages/ckeditor5-media-embed/docs/_snippets/features/media-embed.js
index 29aefe156d7..a221fcb2efd 100644
--- a/packages/ckeditor5-media-embed/docs/_snippets/features/media-embed.js
+++ b/packages/ckeditor5-media-embed/docs/_snippets/features/media-embed.js
@@ -34,7 +34,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-media-embed/docs/features/media-embed.md b/packages/ckeditor5-media-embed/docs/features/media-embed.md
index 543c6886ce7..4c72b742cec 100644
--- a/packages/ckeditor5-media-embed/docs/features/media-embed.md
+++ b/packages/ckeditor5-media-embed/docs/features/media-embed.md
@@ -33,18 +33,18 @@ You can use the insert media button in the toolbar {@icon @ckeditor/ckeditor5-me
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, MediaEmbed } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ MediaEmbed, /* ... */ ],
toolbar: [ 'mediaEmbed', /* ... */ ]
mediaEmbed: {
- // Configuration
- // ...
+ // Configuration.
}
} )
.then( /* ... */ )
@@ -177,8 +177,7 @@ For instance, to leave only the previewable providers, configure this feature as
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ MediaEmbed, /* ... */ ],
- toolbar: [ 'mediaEmbed', /* ... */ ]
+ // ... Other configuration options ...
mediaEmbed: {
removeProviders: [ 'instagram', 'twitter', 'googleMaps', 'flickr', 'facebook' ]
}
@@ -194,8 +193,7 @@ To override the default providers, use {@link module:media-embed/mediaembedconfi
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ MediaEmbed, /* ... */ ],,
- toolbar: [ 'mediaEmbed', /* ... */ ]
+ // ... Other configuration options ...
mediaEmbed: {
providers: [
{
diff --git a/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.js b/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.js
index e65950d54a2..e8c89bbb6a9 100644
--- a/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.js
+++ b/packages/ckeditor5-mention/docs/_snippets/examples/chat-with-mentions.js
@@ -73,7 +73,8 @@ ClassicEditor
]
}
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
const editingView = editor.editing.view;
diff --git a/packages/ckeditor5-mention/docs/_snippets/features/custom-mention-colors-variables.js b/packages/ckeditor5-mention/docs/_snippets/features/custom-mention-colors-variables.js
index c2d09d721e1..41268ab0e82 100644
--- a/packages/ckeditor5-mention/docs/_snippets/features/custom-mention-colors-variables.js
+++ b/packages/ckeditor5-mention/docs/_snippets/features/custom-mention-colors-variables.js
@@ -42,7 +42,8 @@ ClassicEditor
feed: [ '@Barney', '@Lily', '@Marry Ann', '@Marshall', '@Robin', '@Ted' ]
}
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-mention/docs/_snippets/features/mention-customization.js b/packages/ckeditor5-mention/docs/_snippets/features/mention-customization.js
index 79909a2bc53..98875df69d2 100644
--- a/packages/ckeditor5-mention/docs/_snippets/features/mention-customization.js
+++ b/packages/ckeditor5-mention/docs/_snippets/features/mention-customization.js
@@ -45,7 +45,8 @@ ClassicEditor
itemRenderer: customItemRenderer
}
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-mention/docs/_snippets/features/mention.js b/packages/ckeditor5-mention/docs/_snippets/features/mention.js
index f0582c81654..f35c5cc1de6 100644
--- a/packages/ckeditor5-mention/docs/_snippets/features/mention.js
+++ b/packages/ckeditor5-mention/docs/_snippets/features/mention.js
@@ -43,7 +43,8 @@ ClassicEditor
minimumCharacters: 0
}
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-mention/docs/examples/chat-with-mentions.md b/packages/ckeditor5-mention/docs/examples/chat-with-mentions.md
index 00ff4f2f9bd..6a9a9d4c09b 100644
--- a/packages/ckeditor5-mention/docs/examples/chat-with-mentions.md
+++ b/packages/ckeditor5-mention/docs/examples/chat-with-mentions.md
@@ -36,6 +36,7 @@ import {
ClassicEditor
.create( document.querySelector( '.chat__editor' ), {
+ licenseKey: 'GPL', // Or ''.
extraPlugins: [ Essentials, Paragraph, Mention, MentionLinks, Bold, Italic, Underline, Strikethrough, Link ],
toolbar: {
items: [
diff --git a/packages/ckeditor5-mention/docs/features/mentions.md b/packages/ckeditor5-mention/docs/features/mentions.md
index 86ff78ba49a..1019b9497df 100644
--- a/packages/ckeditor5-mention/docs/features/mentions.md
+++ b/packages/ckeditor5-mention/docs/features/mentions.md
@@ -33,17 +33,17 @@ You can read more about possible implementations of the mention feature in a [de
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Mention } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Mention, /* ... */ ],
mention: {
// Configuration.
- // ...
}
} )
.then( /* ... */ )
@@ -59,10 +59,7 @@ The code snippet below was used to configure the demo above. It defines the list
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
- // This feature is available in the superbuild only.
- // See the "Installation" section.
- plugins: [ Mention, /* ... */ ],
-
+ // ... Other configuration options ...
mention: {
feeds: [
{
@@ -103,10 +100,7 @@ The callback receives the query text which should be used to filter item suggest
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
- // This feature is available in the superbuild only.
- // See the "Installation" section.
- plugins: [ Mention, /* ... */ ],
-
+ // ... Other configuration options ...
mention: {
feeds: [
{
@@ -171,7 +165,7 @@ This callback takes a feed item (it contains at least the `name` property) and m
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ Mention, /* ... */ ],
+ // ... Other configuration options ...
mention: {
feeds: [
{
@@ -212,7 +206,7 @@ The number of items displayed in the autocomplete list can be customized by defi
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ Mention, /* ... */ ],
+ // ... Other configuration options ...
mention: {
// Define the custom number of visible mentions.
dropdownLimit: 4
@@ -236,7 +230,7 @@ You can control the text inserted into the editor when creating a mention via th
```js
ClassicEditor
.create( editorElement, {
- plugins: [ Mention, ... ],
+ // ... Other configuration options ...
mention: {
feeds: [
// Feed items as objects.
@@ -288,6 +282,7 @@ By default, attribute elements that are next to each other and have the same val
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
plugins: [ Mention, MentionCustomization, /* ... */ ], // Add the custom mention plugin function.
mention: {
// Configuration.
@@ -372,6 +367,7 @@ Below is an example of a customized mention feature that:
```js
ClassicEditor
.create( document.querySelector( '#snippet-mention-customization' ), {
+ // ... Other configuration options ...
plugins: [ Mention, MentionCustomization, /* ... */ ],
mention: {
dropdownLimit: 4,
diff --git a/packages/ckeditor5-minimap/docs/_snippets/features/minimap.js b/packages/ckeditor5-minimap/docs/_snippets/features/minimap.js
index b79eb54ac57..5378a3b445d 100644
--- a/packages/ckeditor5-minimap/docs/_snippets/features/minimap.js
+++ b/packages/ckeditor5-minimap/docs/_snippets/features/minimap.js
@@ -100,7 +100,8 @@ const config = {
tokeUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
};
DecoupledEditor
diff --git a/packages/ckeditor5-minimap/docs/features/minimap.md b/packages/ckeditor5-minimap/docs/features/minimap.md
index 3ece7efd406..3c39f6d550a 100644
--- a/packages/ckeditor5-minimap/docs/features/minimap.md
+++ b/packages/ckeditor5-minimap/docs/features/minimap.md
@@ -33,17 +33,17 @@ Scroll the content, and the minimap in the sidebar will show your current locati
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { DecoupledEditor, Minimap } from 'ckeditor5';
DecoupledEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Minimap, /* ... */ ],
minimap: {
- // Reference to the container element as shown in the configuration section of the guide
- // ...
+ // Configuration.
}
} )
.then( /* ... */ )
@@ -61,11 +61,9 @@ DecoupledEditor
The container element is essential for the minimap to render. You should pass the reference to the container element in {@link module:minimap/minimapconfig~MinimapConfig#container `config.minimap.container`}. Note that it must have a fixed `width` and `overflow: hidden` when the editor is created:
```js
-import { DecoupledEditor, Minimap } from 'ckeditor5';
-
DecoupledEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ Minimap, /* ... */ ],
+ // ... Other configuration options ...
minimap: {
container: document.querySelector( '.minimap-container' )
}
@@ -167,11 +165,9 @@ Employ the following CSS:
Finally, the JavaScript to run the editor (learn how to [install](#installation) the feature):
```js
-import { DecoupledEditor, Minimap } from 'ckeditor5';
-
DecoupledEditor
.create( document.querySelector( '#editor-content' ), {
- plugins: [ Minimap, /* ... */ ],
+ // ... Other configuration options ...
minimap: {
container: document.querySelector( '.minimap-container' ),
}
diff --git a/packages/ckeditor5-page-break/docs/_snippets/features/page-break.js b/packages/ckeditor5-page-break/docs/_snippets/features/page-break.js
index 1ddc5521c22..dfeba65b359 100644
--- a/packages/ckeditor5-page-break/docs/_snippets/features/page-break.js
+++ b/packages/ckeditor5-page-break/docs/_snippets/features/page-break.js
@@ -62,7 +62,8 @@ ClassicEditor
table: {
contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-page-break/docs/features/page-break.md b/packages/ckeditor5-page-break/docs/features/page-break.md
index 9bbee004d08..a310eb4c1a0 100644
--- a/packages/ckeditor5-page-break/docs/features/page-break.md
+++ b/packages/ckeditor5-page-break/docs/features/page-break.md
@@ -26,13 +26,14 @@ Use the insert page break toolbar button {@icon @ckeditor/ckeditor5-page-break/t
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, PageBreak } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ PageBreak, /* ... */ ],
toolbar: [ 'pageBreak', /* ... */ ],
} )
diff --git a/packages/ckeditor5-paste-from-office/docs/_snippets/features/paste-from-office.js b/packages/ckeditor5-paste-from-office/docs/_snippets/features/paste-from-office.js
index 2fb08000f7a..bcc990310fa 100644
--- a/packages/ckeditor5-paste-from-office/docs/_snippets/features/paste-from-office.js
+++ b/packages/ckeditor5-paste-from-office/docs/_snippets/features/paste-from-office.js
@@ -78,7 +78,8 @@ ClassicEditor
}
},
placeholder: 'Paste the content here to test the feature.',
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-paste-from-office/docs/features/paste-from-google-docs.md b/packages/ckeditor5-paste-from-office/docs/features/paste-from-google-docs.md
index c1ced241a8e..38c1e4a3417 100644
--- a/packages/ckeditor5-paste-from-office/docs/features/paste-from-google-docs.md
+++ b/packages/ckeditor5-paste-from-office/docs/features/paste-from-google-docs.md
@@ -47,13 +47,14 @@ This means that if you did not enable, for instance, {@link features/font font f
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, PasteFromOffice } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ PasteFromOffice, /* ... */ ]
} )
.then( /* ... */ )
diff --git a/packages/ckeditor5-paste-from-office/docs/features/paste-from-office.md b/packages/ckeditor5-paste-from-office/docs/features/paste-from-office.md
index 16fac793b07..69318f28386 100644
--- a/packages/ckeditor5-paste-from-office/docs/features/paste-from-office.md
+++ b/packages/ckeditor5-paste-from-office/docs/features/paste-from-office.md
@@ -72,13 +72,14 @@ This means that if you did not enable, for instance, {@link features/font font f
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, PasteFromOffice } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ PasteFromOffice, /* ... */ ]
} )
.then( /* ... */ )
diff --git a/packages/ckeditor5-paste-from-office/tests/_utils/utils.js b/packages/ckeditor5-paste-from-office/tests/_utils/utils.js
index c0383d3f2ca..8b1576adec0 100644
--- a/packages/ckeditor5-paste-from-office/tests/_utils/utils.js
+++ b/packages/ckeditor5-paste-from-office/tests/_utils/utils.js
@@ -81,7 +81,9 @@ export function generateTests( config ) {
describe( config.type, () => {
describe( config.input, () => {
- const editorConfig = config.editorConfig || {};
+ const editorConfig = typeof config.editorConfig == 'function' ?
+ config.editorConfig :
+ () => Promise.resolve( config.editorConfig || {} );
for ( const group of Object.keys( groups ) ) {
const skip = config.skip && config.skip[ group ] || [];
@@ -152,12 +154,8 @@ function generateNormalizationTests( title, fixtures, editorConfig, skip, only )
describe( title, () => {
let editor;
- beforeEach( () => {
- return VirtualTestEditor
- .create( editorConfig )
- .then( newEditor => {
- editor = newEditor;
- } );
+ beforeEach( async () => {
+ editor = await VirtualTestEditor.create( await editorConfig() );
} );
afterEach( () => {
@@ -207,16 +205,12 @@ function generateIntegrationTests( title, fixtures, editorConfig, skip, only ) {
let element, editor;
let data = {};
- before( () => {
+ before( async () => {
element = document.createElement( 'div' );
document.body.appendChild( element );
- return ClassicTestEditor
- .create( element, editorConfig )
- .then( editorInstance => {
- editor = editorInstance;
- } );
+ editor = await ClassicTestEditor.create( element, await editorConfig() );
} );
beforeEach( () => {
diff --git a/packages/ckeditor5-remove-format/docs/_snippets/features/remove-format.js b/packages/ckeditor5-remove-format/docs/_snippets/features/remove-format.js
index 5709bf384bf..202c4d9591d 100644
--- a/packages/ckeditor5-remove-format/docs/_snippets/features/remove-format.js
+++ b/packages/ckeditor5-remove-format/docs/_snippets/features/remove-format.js
@@ -38,7 +38,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-remove-format/docs/features/remove-format.md b/packages/ckeditor5-remove-format/docs/features/remove-format.md
index d282fe61d46..a131f8e7fe9 100644
--- a/packages/ckeditor5-remove-format/docs/features/remove-format.md
+++ b/packages/ckeditor5-remove-format/docs/features/remove-format.md
@@ -27,13 +27,14 @@ Select the content you want to clean up and press the remove format button {@ico
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, RemoveFormat } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ RemoveFormat, /* ... */ ],
toolbar: [ 'removeFormat', /* ... */ ]
} )
@@ -74,6 +75,7 @@ Enable the `RemoveFormatLinks` plugin in the {@link getting-started/setup/config
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
plugins: [
RemoveFormat,
RemoveFormatLinks,
diff --git a/packages/ckeditor5-restricted-editing/docs/_snippets/features/restricted-editing.js b/packages/ckeditor5-restricted-editing/docs/_snippets/features/restricted-editing.js
index be762f35560..90bd82f4fae 100644
--- a/packages/ckeditor5-restricted-editing/docs/_snippets/features/restricted-editing.js
+++ b/packages/ckeditor5-restricted-editing/docs/_snippets/features/restricted-editing.js
@@ -83,7 +83,8 @@ async function startStandardEditingMode() {
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
},
- updateSourceElementOnDestroy: true
+ updateSourceElementOnDestroy: true,
+ licenseKey: 'GPL'
} );
}
@@ -107,7 +108,8 @@ async function startRestrictedEditingMode() {
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ]
},
- updateSourceElementOnDestroy: true
+ updateSourceElementOnDestroy: true,
+ licenseKey: 'GPL'
} );
}
diff --git a/packages/ckeditor5-restricted-editing/docs/features/restricted-editing.md b/packages/ckeditor5-restricted-editing/docs/features/restricted-editing.md
index a340532998d..36dccff3bb2 100644
--- a/packages/ckeditor5-restricted-editing/docs/features/restricted-editing.md
+++ b/packages/ckeditor5-restricted-editing/docs/features/restricted-editing.md
@@ -48,7 +48,7 @@ By using this feature, the users of your application will be able to create temp
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration.
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration.
### Running the standard editing mode
@@ -59,6 +59,7 @@ import { ClassicEditor, StandardEditingMode } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ StandardEditingMode, /* ... */ ],
toolbar: [ 'restrictedEditingException', /* ... */ ]
} )
@@ -75,6 +76,7 @@ import { ClassicEditor, RestrictedEditingMode } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ RestrictedEditingMode, /* ... */ ],
toolbar: [ 'restrictedEditing', /* ... */ ]
} )
@@ -91,6 +93,7 @@ import { ClassicEditor, RestrictedEditingMode, Bold } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Bold, RestrictedEditingMode, /* ... */ ],
toolbar: [ 'bold', '|', 'restrictedEditing', /* ... */ ],
restrictedEditing: {
diff --git a/packages/ckeditor5-select-all/docs/_snippets/features/select-all.js b/packages/ckeditor5-select-all/docs/_snippets/features/select-all.js
index 065b6be1bcc..a6b9e682d39 100644
--- a/packages/ckeditor5-select-all/docs/_snippets/features/select-all.js
+++ b/packages/ckeditor5-select-all/docs/_snippets/features/select-all.js
@@ -44,7 +44,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-select-all/docs/features/select-all.md b/packages/ckeditor5-select-all/docs/features/select-all.md
index 5ebec0cdc42..b20e41745ef 100644
--- a/packages/ckeditor5-select-all/docs/features/select-all.md
+++ b/packages/ckeditor5-select-all/docs/features/select-all.md
@@ -28,17 +28,15 @@ Press Ctrl/Cmd+A or use the toolbar button {@ic
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, SelectAll } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
- // Load the plugin.
+ licenseKey: '', // Or 'GPL'.
plugins: [ SelectAll, /* ... */ ],
-
- // Display the "Select all" button in the toolbar.
toolbar: [ 'selectAll', /* ... */ ],
} )
.then( /* ... */ )
diff --git a/packages/ckeditor5-show-blocks/docs/_snippets/features/show-blocks.js b/packages/ckeditor5-show-blocks/docs/_snippets/features/show-blocks.js
index 7eea810426f..186478ee58e 100644
--- a/packages/ckeditor5-show-blocks/docs/_snippets/features/show-blocks.js
+++ b/packages/ckeditor5-show-blocks/docs/_snippets/features/show-blocks.js
@@ -58,7 +58,8 @@ ClassicEditor
classes: true
}
]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-show-blocks/docs/features/show-blocks.md b/packages/ckeditor5-show-blocks/docs/features/show-blocks.md
index 2093f84316f..fa497545459 100644
--- a/packages/ckeditor5-show-blocks/docs/features/show-blocks.md
+++ b/packages/ckeditor5-show-blocks/docs/features/show-blocks.md
@@ -26,17 +26,15 @@ Toggle the block elements visibility with the show block {@icon @ckeditor/ckedit
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, ShowBlocks } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
- // Load the plugin.
+ licenseKey: '', // Or 'GPL'.
plugins: [ ShowBlocks, /* ... */ ],
-
- // Display the "Show blocks" button in the toolbar.
toolbar: [ 'showBlocks', /* ... */ ],
} )
.then( /* ... */ )
diff --git a/packages/ckeditor5-source-editing/docs/_snippets/features/source-editing-imports.js b/packages/ckeditor5-source-editing/docs/_snippets/features/source-editing-imports.js
index 52a25a51ea8..96179fe5975 100644
--- a/packages/ckeditor5-source-editing/docs/_snippets/features/source-editing-imports.js
+++ b/packages/ckeditor5-source-editing/docs/_snippets/features/source-editing-imports.js
@@ -69,7 +69,8 @@ ClassicEditor.defaultConfig = {
viewportOffset: {
top: window.getViewportTopOffsetConfig()
}
- }
+ },
+ licenseKey: 'GPL'
};
window.ClassicEditor = ClassicEditor;
diff --git a/packages/ckeditor5-source-editing/docs/_snippets/features/source-editing-with-markdown.js b/packages/ckeditor5-source-editing/docs/_snippets/features/source-editing-with-markdown.js
index e4836f7e290..1ba95d92174 100644
--- a/packages/ckeditor5-source-editing/docs/_snippets/features/source-editing-with-markdown.js
+++ b/packages/ckeditor5-source-editing/docs/_snippets/features/source-editing-with-markdown.js
@@ -64,7 +64,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-source-editing/docs/_snippets/features/source-editing.js b/packages/ckeditor5-source-editing/docs/_snippets/features/source-editing.js
index f36de2af597..e4d74d6cb47 100644
--- a/packages/ckeditor5-source-editing/docs/_snippets/features/source-editing.js
+++ b/packages/ckeditor5-source-editing/docs/_snippets/features/source-editing.js
@@ -71,7 +71,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-source-editing/docs/features/source-editing.md b/packages/ckeditor5-source-editing/docs/features/source-editing.md
index efc79583cba..babb1255af0 100644
--- a/packages/ckeditor5-source-editing/docs/features/source-editing.md
+++ b/packages/ckeditor5-source-editing/docs/features/source-editing.md
@@ -30,13 +30,14 @@ You can also use one of the many CKEditor 5 features available in the toolb
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, SourceEditing } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ SourceEditing, /* ... */ ],
toolbar: [ 'sourceEditing', /* ... */ ]
} )
@@ -51,6 +52,7 @@ import { ClassicEditor, SourceEditing, Markdown } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ SourceEditing, Markdown, /* ... */ ],
toolbar: [ 'sourceEditing', /* ... */ ]
} )
diff --git a/packages/ckeditor5-special-characters/docs/_snippets/features/special-characters-extended-category.js b/packages/ckeditor5-special-characters/docs/_snippets/features/special-characters-extended-category.js
index 9560e9bb067..86183c8a5cd 100644
--- a/packages/ckeditor5-special-characters/docs/_snippets/features/special-characters-extended-category.js
+++ b/packages/ckeditor5-special-characters/docs/_snippets/features/special-characters-extended-category.js
@@ -52,7 +52,8 @@ ClassicEditor
table: {
contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-special-characters/docs/_snippets/features/special-characters-limited-categories.js b/packages/ckeditor5-special-characters/docs/_snippets/features/special-characters-limited-categories.js
index 280c8979e65..ca21511a325 100644
--- a/packages/ckeditor5-special-characters/docs/_snippets/features/special-characters-limited-categories.js
+++ b/packages/ckeditor5-special-characters/docs/_snippets/features/special-characters-limited-categories.js
@@ -44,7 +44,8 @@ ClassicEditor
table: {
contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-special-characters/docs/_snippets/features/special-characters-new-category.js b/packages/ckeditor5-special-characters/docs/_snippets/features/special-characters-new-category.js
index bbc1dba8712..95a92a4e702 100644
--- a/packages/ckeditor5-special-characters/docs/_snippets/features/special-characters-new-category.js
+++ b/packages/ckeditor5-special-characters/docs/_snippets/features/special-characters-new-category.js
@@ -54,7 +54,8 @@ ClassicEditor
table: {
contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-special-characters/docs/_snippets/features/special-characters.js b/packages/ckeditor5-special-characters/docs/_snippets/features/special-characters.js
index 1efad45622d..c18a9fd7b05 100644
--- a/packages/ckeditor5-special-characters/docs/_snippets/features/special-characters.js
+++ b/packages/ckeditor5-special-characters/docs/_snippets/features/special-characters.js
@@ -44,7 +44,8 @@ ClassicEditor
table: {
contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-special-characters/docs/features/special-characters.md b/packages/ckeditor5-special-characters/docs/features/special-characters.md
index 79aceab3211..672e2d677bd 100644
--- a/packages/ckeditor5-special-characters/docs/features/special-characters.md
+++ b/packages/ckeditor5-special-characters/docs/features/special-characters.md
@@ -28,7 +28,7 @@ Use the special characters toolbar button {@icon @ckeditor/ckeditor5-special-cha
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
// Core plugin provides the API for the management of special characters and their categories.
@@ -37,8 +37,12 @@ import { ClassicEditor, SpecialCharacters, SpecialCharactersEssentials } from 'c
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ SpecialCharacters, SpecialCharactersEssentials, /* ... */ ],
toolbar: [ 'specialCharacters', /* ... */ ],
+ specialCharacters: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -55,8 +59,6 @@ You can define a new special characters category using the {@link module:special
For example, the following plugin adds the "Emoji" category to the special characters dropdown.
```js
-import { ClassicEditor, SpecialCharacters, SpecialCharactersEssentials } from 'ckeditor5';
-
function SpecialCharactersEmoji( editor ) {
editor.plugins.get( 'SpecialCharacters' ).addItems( 'Emoji', [
{ title: 'smiley face', character: '😊' },
@@ -69,12 +71,10 @@ function SpecialCharactersEmoji( editor ) {
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
plugins: [
SpecialCharacters, SpecialCharactersEssentials, SpecialCharactersEmoji,
- // More plugins.
- // ...
],
- toolbar: [ 'specialCharacters', /* ... */ ],
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -99,8 +99,6 @@ Below you can see a demo based on the example shown above. Use the special chara
By using the {@link module:special-characters/specialcharacters~SpecialCharacters#addItems `SpecialCharacters#addItems()`} function you can also add new special characters to an existing category.
```js
-import { ClassicEditor, SpecialCharacters, SpecialCharactersEssentials } from 'ckeditor5';
-
function SpecialCharactersExtended( editor ) {
editor.plugins.get( 'SpecialCharacters' ).addItems( 'Mathematical', [
{ title: 'alpha', character: 'α' },
@@ -111,13 +109,10 @@ function SpecialCharactersExtended( editor ) {
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
plugins: [
SpecialCharacters, SpecialCharactersEssentials, SpecialCharactersExtended,
-
- // More plugins.
- // ...
],
- toolbar: [ 'specialCharacters', /* ... */ ],
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -151,13 +146,10 @@ import { ClassicEditor, SpecialCharacters, SpecialCharactersCurrency, SpecialCha
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
plugins: [
SpecialCharacters, SpecialCharactersCurrency, SpecialCharactersMathematical,
-
- // More plugins.
- // ...
],
- toolbar: [ 'specialCharacters', /* ... */ ],
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -176,6 +168,7 @@ The categories order can be customized using the {@link module:special-character
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
plugins: [ SpecialCharacters, SpecialCharactersEssentials, ... ],
specialCharacters: {
order: [
diff --git a/packages/ckeditor5-style/docs/_snippets/features/styles.js b/packages/ckeditor5-style/docs/_snippets/features/styles.js
index 4c609ca187a..5b8773aedf6 100644
--- a/packages/ckeditor5-style/docs/_snippets/features/styles.js
+++ b/packages/ckeditor5-style/docs/_snippets/features/styles.js
@@ -122,7 +122,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-style/docs/features/style.md b/packages/ckeditor5-style/docs/features/style.md
index ab8cceac715..7037f90fb2c 100644
--- a/packages/ckeditor5-style/docs/features/style.md
+++ b/packages/ckeditor5-style/docs/features/style.md
@@ -231,26 +231,19 @@ The style sheet:
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Style } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Style, /* ... */ ],
- toolbar: {
- items: [
- 'style',
- // More toolbar items.
- // ...
- ],
+ toolbar: [ 'style', /* ... */ ],
},
style: {
- definitions: [
- // Styles definitions.
- // ...
- ]
+ // Configuration.
}
} )
.then( /* ... */ )
@@ -264,14 +257,7 @@ Configuring the styles feature takes two steps. First, you need to define the st
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ Style, /* ... */ ],
- toolbar: {
- items: [
- 'style',
- // More toolbar items.
- // ...
- ],
- },
+ // ... Other configuration options ...
style: {
definitions: [
{
diff --git a/packages/ckeditor5-table/docs/_snippets/features/build-table-source.js b/packages/ckeditor5-table/docs/_snippets/features/build-table-source.js
index 13a0002f2b0..1619e713ab3 100644
--- a/packages/ckeditor5-table/docs/_snippets/features/build-table-source.js
+++ b/packages/ckeditor5-table/docs/_snippets/features/build-table-source.js
@@ -44,7 +44,8 @@ ClassicEditor.defaultConfig = {
top: window.getViewportTopOffsetConfig()
}
},
- indentBlock: { offset: 30, unit: 'px' }
+ indentBlock: { offset: 30, unit: 'px' },
+ licenseKey: 'GPL'
};
window.ClassicEditor = ClassicEditor;
diff --git a/packages/ckeditor5-table/docs/_snippets/features/table-caption.js b/packages/ckeditor5-table/docs/_snippets/features/table-caption.js
index 7eaa5e90466..25642a1db13 100644
--- a/packages/ckeditor5-table/docs/_snippets/features/table-caption.js
+++ b/packages/ckeditor5-table/docs/_snippets/features/table-caption.js
@@ -34,7 +34,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorCaption = editor;
diff --git a/packages/ckeditor5-table/docs/_snippets/features/table-column-resize.js b/packages/ckeditor5-table/docs/_snippets/features/table-column-resize.js
index eb8cdecaefe..d5bf8328251 100644
--- a/packages/ckeditor5-table/docs/_snippets/features/table-column-resize.js
+++ b/packages/ckeditor5-table/docs/_snippets/features/table-column-resize.js
@@ -34,7 +34,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorCaption = editor;
diff --git a/packages/ckeditor5-table/docs/_snippets/features/table-default-headings.js b/packages/ckeditor5-table/docs/_snippets/features/table-default-headings.js
index 0af85a6e651..56c141852c9 100644
--- a/packages/ckeditor5-table/docs/_snippets/features/table-default-headings.js
+++ b/packages/ckeditor5-table/docs/_snippets/features/table-default-headings.js
@@ -34,7 +34,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorDefaultHeadings = editor;
diff --git a/packages/ckeditor5-table/docs/_snippets/features/table-default-properties.js b/packages/ckeditor5-table/docs/_snippets/features/table-default-properties.js
index bf99f6fbfc5..3d45dc1e338 100644
--- a/packages/ckeditor5-table/docs/_snippets/features/table-default-properties.js
+++ b/packages/ckeditor5-table/docs/_snippets/features/table-default-properties.js
@@ -53,7 +53,8 @@ ClassicEditor
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
},
- placeholder: 'Insert the new table with the default styles applied.'
+ placeholder: 'Insert the new table with the default styles applied.',
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorDefaultStyles = editor;
diff --git a/packages/ckeditor5-table/docs/_snippets/features/table-nesting.js b/packages/ckeditor5-table/docs/_snippets/features/table-nesting.js
index 5c595bd9ccb..ac5a6e83158 100644
--- a/packages/ckeditor5-table/docs/_snippets/features/table-nesting.js
+++ b/packages/ckeditor5-table/docs/_snippets/features/table-nesting.js
@@ -127,7 +127,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorStyling = editor;
diff --git a/packages/ckeditor5-table/docs/_snippets/features/table-styling-colors.js b/packages/ckeditor5-table/docs/_snippets/features/table-styling-colors.js
index e09ab6ff407..99ffa3dba82 100644
--- a/packages/ckeditor5-table/docs/_snippets/features/table-styling-colors.js
+++ b/packages/ckeditor5-table/docs/_snippets/features/table-styling-colors.js
@@ -128,7 +128,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorStyling = editor;
diff --git a/packages/ckeditor5-table/docs/_snippets/features/table-styling.js b/packages/ckeditor5-table/docs/_snippets/features/table-styling.js
index 017dabdba7e..b7602a110f0 100644
--- a/packages/ckeditor5-table/docs/_snippets/features/table-styling.js
+++ b/packages/ckeditor5-table/docs/_snippets/features/table-styling.js
@@ -37,7 +37,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editorStyling = editor;
diff --git a/packages/ckeditor5-table/docs/_snippets/features/tables.js b/packages/ckeditor5-table/docs/_snippets/features/tables.js
index 6ffbf368f39..359f1335914 100644
--- a/packages/ckeditor5-table/docs/_snippets/features/tables.js
+++ b/packages/ckeditor5-table/docs/_snippets/features/tables.js
@@ -40,7 +40,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-table/docs/features/tables-caption.md b/packages/ckeditor5-table/docs/features/tables-caption.md
index dac34ef2936..51e86cff48d 100644
--- a/packages/ckeditor5-table/docs/features/tables-caption.md
+++ b/packages/ckeditor5-table/docs/features/tables-caption.md
@@ -30,13 +30,14 @@ In the demo below, click the table caption to edit it. Once you click the captio
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Table, TableCaption, TableToolbar } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Table, TableToolbar, TableCaption, /* ... */ ],
toolbar: [ 'insertTable', /* ... */ ],
table: {
diff --git a/packages/ckeditor5-table/docs/features/tables-resize.md b/packages/ckeditor5-table/docs/features/tables-resize.md
index 40c943ffb9c..c4468c6efe7 100644
--- a/packages/ckeditor5-table/docs/features/tables-resize.md
+++ b/packages/ckeditor5-table/docs/features/tables-resize.md
@@ -31,13 +31,14 @@ The column resize feature is compatible with the {@link features/export-word Exp
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Table, TableColumnResize } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Table, TableColumnResize, /* ... */ ],
toolbar: [ 'insertTable', /* ... */ ],
} )
diff --git a/packages/ckeditor5-table/docs/features/tables-styling.md b/packages/ckeditor5-table/docs/features/tables-styling.md
index 412a8d0f271..36fe6d71d66 100644
--- a/packages/ckeditor5-table/docs/features/tables-styling.md
+++ b/packages/ckeditor5-table/docs/features/tables-styling.md
@@ -32,13 +32,14 @@ Put the caret anywhere inside the table to open the table toolbar. Click the tab
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Table, TableCellProperties, TableProperties, TableToolbar } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Table, TableToolbar, TableProperties, TableCellProperties, /* ... */ ],
toolbar: [ 'insertTable', /* ... */ ],
table: {
@@ -49,12 +50,10 @@ ClassicEditor
tableProperties: {
// The configuration of the TableProperties plugin.
- // ...
},
tableCellProperties: {
// The configuration of the TableCellProperties plugin.
- // ...
}
}
} )
@@ -118,8 +117,7 @@ const customColorPalette = [
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ Table, TableToolbar, TableProperties, TableCellProperties, Bold, /* ... */ ],
- toolbar: [ 'insertTable', /* ... */ ],
+ // ... Other configuration options ...
table: {
contentToolbar: [
'tableColumn', 'tableRow', 'mergeTableCells',
diff --git a/packages/ckeditor5-table/docs/features/tables.md b/packages/ckeditor5-table/docs/features/tables.md
index 57eef982233..8dce9cbdb3f 100644
--- a/packages/ckeditor5-table/docs/features/tables.md
+++ b/packages/ckeditor5-table/docs/features/tables.md
@@ -187,15 +187,32 @@ The above model structure will be rendered to the data and to the editing view a
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Table, TableToolbar } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Table, TableToolbar, Bold, /* ... */ ],
toolbar: [ 'insertTable', /* ... */ ],
+ table: {
+ // Configuration.
+ }
+ } )
+ .then( /* ... */ )
+ .catch( /* ... */ );
+```
+
+### Table contextual toolbar
+
+Easily control your tables employing a dedicated toolbar.
+
+```js
+ClassicEditor
+ .create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
table: {
contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
}
@@ -204,17 +221,15 @@ ClassicEditor
.catch( /* ... */ );
```
+
### Default table headers
To make every inserted table have `n` number of rows and columns as table headers by default, set an optional table configuration property `defaultHeadings` as follows:
```js
-import { ClassicEditor, Table, TableToolbar } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ Table, TableToolbar, Bold, /* ... */ ],
- toolbar: [ 'insertTable', /* ... */ ],
+ // ... Other configuration options ...
table: {
defaultHeadings: { rows: 1, columns: 1 }
}
@@ -246,9 +261,8 @@ function DisallowNestingTables( editor ) {
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ // ... Other configuration options ...
extraPlugins: [ DisallowNestingTables ],
-
- // The rest of the configuration.
} )
.then( /* ... */ )
.catch( /* ... */ );
diff --git a/packages/ckeditor5-theme-lark/docs/_snippets/examples/theme-lark.js b/packages/ckeditor5-theme-lark/docs/_snippets/examples/theme-lark.js
index b48ec196912..6330c53e51c 100644
--- a/packages/ckeditor5-theme-lark/docs/_snippets/examples/theme-lark.js
+++ b/packages/ckeditor5-theme-lark/docs/_snippets/examples/theme-lark.js
@@ -34,7 +34,8 @@ ClassicEditor
image: {
toolbar: [ 'imageStyle:inline', 'imageStyle:block', 'imageStyle:wrapText', '|', 'toggleImageCaption', 'imageTextAlternative' ]
},
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-theme-lark/docs/framework/theme-customization.md b/packages/ckeditor5-theme-lark/docs/framework/theme-customization.md
index 9c05f851035..aa494be74cf 100644
--- a/packages/ckeditor5-theme-lark/docs/framework/theme-customization.md
+++ b/packages/ckeditor5-theme-lark/docs/framework/theme-customization.md
@@ -12,7 +12,7 @@ Below you can see a demo of an editor with the dark theme as a result of customi
## Customization with CSS variables
-Assuming you finished our {@link getting-started/quick-start quick start} guide, and you have a running CKEditor 5 instance, let's use the full potential of CSS variables (custom properties). The customization explained in this guide will make the theme dark, with slightly bigger text and more rounded corners.
+Assuming you finished our {@link getting-started/integrations-cdn/quick-start quick start} guide, and you have a running CKEditor 5 instance, let's use the full potential of CSS variables (custom properties). The customization explained in this guide will make the theme dark, with slightly bigger text and more rounded corners.
The file containing custom variables can be named `custom.css` and it will look as below:
diff --git a/packages/ckeditor5-typing/docs/_snippets/features/text-transformation-extended.js b/packages/ckeditor5-typing/docs/_snippets/features/text-transformation-extended.js
index 6897318b899..16d23edd3f0 100644
--- a/packages/ckeditor5-typing/docs/_snippets/features/text-transformation-extended.js
+++ b/packages/ckeditor5-typing/docs/_snippets/features/text-transformation-extended.js
@@ -72,7 +72,8 @@ ClassicEditor
}
]
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-typing/docs/_snippets/features/text-transformation.js b/packages/ckeditor5-typing/docs/_snippets/features/text-transformation.js
index bd8535eb774..e11f16f0ba0 100644
--- a/packages/ckeditor5-typing/docs/_snippets/features/text-transformation.js
+++ b/packages/ckeditor5-typing/docs/_snippets/features/text-transformation.js
@@ -35,7 +35,8 @@ ClassicEditor
tokenUrl: TOKEN_URL,
allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ],
forceDemoLabel: true
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-typing/docs/features/text-transformation.md b/packages/ckeditor5-typing/docs/features/text-transformation.md
index a76f57a7f6e..520861a3ee9 100644
--- a/packages/ckeditor5-typing/docs/features/text-transformation.md
+++ b/packages/ckeditor5-typing/docs/features/text-transformation.md
@@ -71,14 +71,18 @@ You may find interesting details and usage examples in the [Automatic text trans
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, TextTransformation } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ TextTransformation, /* ... */ ],
+ typing: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -101,6 +105,7 @@ For instance, to use the transformations from the "quotes" and "typography" grou
```js
ClassicEditor
.create( editorElement, {
+ // ... Other configuration options ...
typing: {
transformations: {
include: [
@@ -125,6 +130,7 @@ Another example, removing some transformations and adding some extra ones:
```js
ClassicEditor
.create( editorElement, {
+ // ... Other configuration options ...
typing: {
transformations: {
remove: [
diff --git a/packages/ckeditor5-ui/docs/_snippets/examples/bootstrap-ui-inner.js b/packages/ckeditor5-ui/docs/_snippets/examples/bootstrap-ui-inner.js
index 7a0ed697503..8868fa37336 100644
--- a/packages/ckeditor5-ui/docs/_snippets/examples/bootstrap-ui-inner.js
+++ b/packages/ckeditor5-ui/docs/_snippets/examples/bootstrap-ui-inner.js
@@ -281,7 +281,8 @@ BootstrapEditor
Clipboard, Enter, Typing, Paragraph, EasyImage, Image, ImageUpload, CloudServices,
BoldEditing, ItalicEditing, UnderlineEditing, HeadingEditing, UndoEditing
],
- cloudServices: CS_CONFIG
+ cloudServices: CS_CONFIG,
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-ui/src/badge/badge.ts b/packages/ckeditor5-ui/src/badge/badge.ts
new file mode 100644
index 00000000000..fe64bba2b86
--- /dev/null
+++ b/packages/ckeditor5-ui/src/badge/badge.ts
@@ -0,0 +1,346 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module ui/badge/badge
+ */
+
+import type { Editor } from '@ckeditor/ckeditor5-core';
+
+import {
+ Rect,
+ DomEmitterMixin,
+ type PositionOptions
+} from '@ckeditor/ckeditor5-utils';
+
+import type View from '../view.js';
+import BalloonPanelView from '../panel/balloon/balloonpanelview.js';
+
+import { throttle } from 'lodash-es';
+
+// ⚠ Note, whenever changing the threshold, make sure to update the docs/support/managing-ckeditor-logo.md docs
+// as this information is also mentioned there ⚠.
+const NARROW_ROOT_HEIGHT_THRESHOLD = 50;
+const NARROW_ROOT_WIDTH_THRESHOLD = 350;
+
+/**
+ * A helper that enables the badge feature in the editor and renders a custom view next to the bottom of the editable element
+ * (editor root, source editing area, etc.) when the editor is focused.
+ *
+ * @private
+ */
+export default abstract class Badge extends /* #__PURE__ */ DomEmitterMixin() {
+ /**
+ * Editor instance the helper was created for.
+ */
+ protected readonly editor: Editor;
+
+ /**
+ * A reference to the balloon panel hosting and positioning the badge content.
+ */
+ private _balloonView: BalloonPanelView | null = null;
+
+ /**
+ * A throttled version of the {@link #_showBalloon} method meant for frequent use to avoid performance loss.
+ */
+ private _showBalloonThrottled = throttle( () => this._showBalloon(), 50, { leading: true } );
+
+ /**
+ * A reference to the last editable element (root, source editing area, etc.) focused by the user.
+ * Since the focus can move to other focusable elements in the UI, this reference allows positioning the balloon over the
+ * right element whether the user is typing or using the UI.
+ */
+ private _lastFocusedEditableElement: HTMLElement | null = null;
+
+ /**
+ * An additional CSS class added to the `BalloonView`.
+ */
+ private readonly _balloonClass: string | undefined;
+
+ /**
+ * Creates a badge for a given editor. The feature is initialized on Editor#ready
+ * event.
+ */
+ protected constructor( editor: Editor, options: { balloonClass?: string } = {} ) {
+ super();
+
+ this.editor = editor;
+ this._balloonClass = options.balloonClass;
+
+ editor.on( 'ready', () => this._handleEditorReady() );
+ }
+
+ /**
+ * Destroys the badge along with its view.
+ */
+ public destroy(): void {
+ const balloon = this._balloonView;
+
+ if ( balloon ) {
+ // Balloon gets destroyed by the body collection.
+ // The badge view gets destroyed by the balloon.
+ balloon.unpin();
+ this._balloonView = null;
+ }
+
+ this._showBalloonThrottled.cancel();
+ this.stopListening();
+ }
+
+ /**
+ * Enables badge label once the editor (ui) is ready.
+ */
+ protected _handleEditorReady(): void {
+ const editor = this.editor;
+
+ if ( !this._isEnabled() ) {
+ return;
+ }
+
+ // No view means no body collection to append the badge balloon to.
+ if ( !editor.ui.view ) {
+ return;
+ }
+
+ editor.ui.focusTracker.on( 'change:isFocused', ( evt, data, isFocused ) => {
+ this._updateLastFocusedEditableElement();
+
+ if ( isFocused ) {
+ this._showBalloon();
+ } else {
+ this._hideBalloon();
+ }
+ } );
+
+ editor.ui.focusTracker.on( 'change:focusedElement', ( evt, data, focusedElement ) => {
+ this._updateLastFocusedEditableElement();
+
+ if ( focusedElement ) {
+ this._showBalloon();
+ }
+ } );
+
+ editor.ui.on( 'update', () => {
+ this._showBalloonThrottled();
+ } );
+ }
+
+ /**
+ * Returns normalized configuration for the badge.
+ */
+ protected _getNormalizedConfig(): BadgeConfig {
+ return {
+ side: this.editor.locale.contentLanguageDirection === 'ltr' ? 'right' : 'left',
+ position: 'border',
+ verticalOffset: 0,
+ horizontalOffset: 5
+ };
+ }
+
+ /**
+ * Creates the badge content.
+ */
+ protected abstract _createBadgeContent(): View;
+
+ /**
+ * Enables the badge feature.
+ */
+ protected abstract _isEnabled(): boolean;
+
+ /**
+ * Attempts to display the balloon with the badge view.
+ */
+ private _showBalloon(): void {
+ const attachOptions = this._getBalloonAttachOptions();
+
+ if ( !attachOptions ) {
+ return;
+ }
+
+ if ( !this._balloonView ) {
+ this._balloonView = this._createBalloonView();
+ }
+
+ this._balloonView.pin( attachOptions );
+ }
+
+ /**
+ * Hides the badge balloon if already visible.
+ */
+ private _hideBalloon(): void {
+ if ( this._balloonView ) {
+ this._balloonView.unpin();
+ }
+ }
+
+ /**
+ * Creates an instance of the {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView balloon panel}
+ * with the badge view inside ready for positioning.
+ */
+ private _createBalloonView(): BalloonPanelView {
+ const editor = this.editor;
+ const balloon = new BalloonPanelView();
+ const view = this._createBadgeContent();
+
+ balloon.content.add( view );
+
+ if ( this._balloonClass ) {
+ balloon.class = this._balloonClass;
+ }
+
+ editor.ui.view.body.add( balloon );
+
+ return balloon;
+ }
+
+ /**
+ * Returns the options for attaching the balloon to the focused editable element.
+ */
+ private _getBalloonAttachOptions(): Partial | null {
+ if ( !this._lastFocusedEditableElement ) {
+ return null;
+ }
+
+ const badgeConfig = this._getNormalizedConfig();
+
+ const positioningFunction = badgeConfig.side === 'right' ?
+ getLowerRightCornerPosition( this._lastFocusedEditableElement, badgeConfig ) :
+ getLowerLeftCornerPosition( this._lastFocusedEditableElement, badgeConfig );
+
+ return {
+ target: this._lastFocusedEditableElement,
+ positions: [ positioningFunction ]
+ };
+ }
+
+ /**
+ * Updates the {@link #_lastFocusedEditableElement} based on the state of the global focus tracker.
+ */
+ private _updateLastFocusedEditableElement(): void {
+ const editor = this.editor;
+ const isFocused = editor.ui.focusTracker.isFocused;
+ const focusedElement = editor.ui.focusTracker.focusedElement! as HTMLElement;
+
+ if ( !isFocused || !focusedElement ) {
+ this._lastFocusedEditableElement = null;
+
+ return;
+ }
+
+ const editableEditorElements = Array.from( editor.ui.getEditableElementsNames() ).map( name => {
+ return editor.ui.getEditableElement( name );
+ } );
+
+ if ( editableEditorElements.includes( focusedElement ) ) {
+ this._lastFocusedEditableElement = focusedElement;
+ } else {
+ // If it's none of the editable element, then the focus is somewhere in the UI. Let's display the badge
+ // over the first element then.
+ this._lastFocusedEditableElement = editableEditorElements[ 0 ]!;
+ }
+ }
+}
+
+function getLowerRightCornerPosition( focusedEditableElement: HTMLElement, config: BadgeConfig ) {
+ return getLowerCornerPosition( focusedEditableElement, config, ( rootRect, balloonRect ) => {
+ return rootRect.left + rootRect.width - balloonRect.width - config.horizontalOffset;
+ } );
+}
+
+function getLowerLeftCornerPosition( focusedEditableElement: HTMLElement, config: BadgeConfig ) {
+ return getLowerCornerPosition( focusedEditableElement, config, rootRect => rootRect.left + config.horizontalOffset );
+}
+
+function getLowerCornerPosition(
+ focusedEditableElement: HTMLElement,
+ config: BadgeConfig,
+ getBalloonLeft: ( visibleEditableElementRect: Rect, balloonRect: Rect ) => number
+) {
+ return ( visibleEditableElementRect: Rect, balloonRect: Rect ) => {
+ const editableElementRect = new Rect( focusedEditableElement );
+
+ if ( editableElementRect.width < NARROW_ROOT_WIDTH_THRESHOLD || editableElementRect.height < NARROW_ROOT_HEIGHT_THRESHOLD ) {
+ return null;
+ }
+
+ let balloonTop;
+
+ if ( config.position === 'inside' ) {
+ balloonTop = editableElementRect.bottom - balloonRect.height;
+ }
+ else {
+ balloonTop = editableElementRect.bottom - balloonRect.height / 2;
+ }
+
+ balloonTop -= config.verticalOffset;
+
+ const balloonLeft = getBalloonLeft( editableElementRect, balloonRect );
+
+ // Clone the editable element rect and place it where the balloon would be placed.
+ // This will allow getVisible() to work from editable element's perspective (rect source).
+ // and yield a result as if the balloon was on the same (scrollable) layer as the editable element.
+ const newBalloonPositionRect = visibleEditableElementRect
+ .clone()
+ .moveTo( balloonLeft, balloonTop )
+ .getIntersection( balloonRect.clone().moveTo( balloonLeft, balloonTop ) )!;
+
+ const newBalloonPositionVisibleRect = newBalloonPositionRect.getVisible();
+
+ if ( !newBalloonPositionVisibleRect || newBalloonPositionVisibleRect.getArea() < balloonRect.getArea() ) {
+ return null;
+ }
+
+ return {
+ top: balloonTop,
+ left: balloonLeft,
+ name: `position_${ config.position }-side_${ config.side }`,
+ config: {
+ withArrow: false
+ }
+ };
+ };
+}
+
+/**
+ * The badge configuration options.
+ **/
+export interface BadgeConfig {
+
+ /**
+ * The position of the badge.
+ *
+ * * When `'inside'`, the badge will be displayed within the boundaries of the editing area.
+ * * When `'border'`, the basge will be displayed over the bottom border of the editing area.
+ *
+ * @default 'border'
+ */
+ position: 'inside' | 'border';
+
+ /**
+ * Allows choosing the side of the editing area where the badge will be displayed.
+ *
+ * **Note:** If {@link module:core/editor/editorconfig~EditorConfig#language `config.language`} is set to an RTL (right-to-left)
+ * language, the side switches to `'left'` by default.
+ *
+ * @default 'right'
+ */
+ side: 'left' | 'right';
+
+ /**
+ * The vertical distance the badge can be moved away from its default position.
+ *
+ * **Note:** If `position` is `'border'`, the offset is measured from the (vertical) center of the badge.
+ *
+ * @default 5
+ */
+ verticalOffset: number;
+
+ /**
+ * The horizontal distance between the side of the editing root and the nearest side of the badge.
+ *
+ * @default 5
+ */
+ horizontalOffset: number;
+}
diff --git a/packages/ckeditor5-ui/src/editorui/editorui.ts b/packages/ckeditor5-ui/src/editorui/editorui.ts
index 21851581a36..f635555560c 100644
--- a/packages/ckeditor5-ui/src/editorui/editorui.ts
+++ b/packages/ckeditor5-ui/src/editorui/editorui.ts
@@ -12,6 +12,7 @@
import ComponentFactory from '../componentfactory.js';
import TooltipManager from '../tooltipmanager.js';
import PoweredBy from './poweredby.js';
+import EvaluationBadge from './evaluationbadge.js';
import AriaLiveAnnouncer from '../arialiveannouncer.js';
import type EditorUIView from './editoruiview.js';
@@ -66,6 +67,11 @@ export default abstract class EditorUI extends /* #__PURE__ */ ObservableMixin()
*/
public readonly poweredBy: PoweredBy;
+ /**
+ * A helper that enables the "evaluation badge" feature in the editor.
+ */
+ public readonly evaluationBadge: EvaluationBadge;
+
/**
* A helper that manages the content of an `aria-live` regions used by editor features to announce status changes
* to screen readers.
@@ -154,6 +160,7 @@ export default abstract class EditorUI extends /* #__PURE__ */ ObservableMixin()
this.focusTracker = new FocusTracker();
this.tooltipManager = new TooltipManager( editor );
this.poweredBy = new PoweredBy( editor );
+ this.evaluationBadge = new EvaluationBadge( editor );
this.ariaLiveAnnouncer = new AriaLiveAnnouncer( editor );
this.set( 'viewportOffset', this._readViewportOffsetFromConfig() );
@@ -206,6 +213,7 @@ export default abstract class EditorUI extends /* #__PURE__ */ ObservableMixin()
this.focusTracker.destroy();
this.tooltipManager.destroy( this.editor );
this.poweredBy.destroy();
+ this.evaluationBadge.destroy();
// Clean–up the references to the CKEditor instance stored in the native editable DOM elements.
for ( const domElement of this._editableElementsMap.values() ) {
diff --git a/packages/ckeditor5-ui/src/editorui/evaluationbadge.ts b/packages/ckeditor5-ui/src/editorui/evaluationbadge.ts
new file mode 100644
index 00000000000..182c2ac6c48
--- /dev/null
+++ b/packages/ckeditor5-ui/src/editorui/evaluationbadge.ts
@@ -0,0 +1,120 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module ui/editorui/evaluationbadge
+ */
+
+import type { Editor } from '@ckeditor/ckeditor5-core';
+import { parseBase64EncodedObject, type Locale } from '@ckeditor/ckeditor5-utils';
+
+import View from '../view.js';
+import Badge, { type BadgeConfig } from '../badge/badge.js';
+
+/**
+ * A helper that enables the "evaluation badge" feature in the editor at the bottom of the editable element
+ * (editor root, source editing area, etc.) when the editor is focused.
+ *
+ * @private
+ */
+export default class EvaluationBadge extends Badge {
+ private licenseTypeMessage: Record = {
+ evaluation: 'For evaluation purposes only',
+ trial: 'For evaluation purposes only',
+ development: 'For development purposes only'
+ };
+
+ constructor( editor: Editor ) {
+ super( editor, { balloonClass: 'ck-evaluation-badge-balloon' } );
+ }
+
+ /**
+ * Enables "evaluation badge" label.
+ */
+ protected override _isEnabled(): boolean {
+ const editor = this.editor;
+ const licenseKey = editor.config.get( 'licenseKey' )!;
+ const licenseType = getLicenseTypeFromLicenseKey( licenseKey );
+
+ return Boolean( licenseType && this.licenseTypeMessage[ licenseType ] );
+ }
+
+ /**
+ * Creates the content of the "evaluation badge".
+ */
+ protected override _createBadgeContent(): View {
+ const licenseKey = this.editor.config.get( 'licenseKey' )!;
+ const licenseType = getLicenseTypeFromLicenseKey( licenseKey )!;
+
+ return new EvaluationBadgeView( this.editor.locale, this.licenseTypeMessage[ licenseType ] );
+ }
+
+ /**
+ * Returns the normalized configuration for the "evaluation badge".
+ * It takes 'ui.poweredBy' configuration into account to determine the badge position and side.
+ */
+ protected override _getNormalizedConfig(): BadgeConfig {
+ const badgeConfig = super._getNormalizedConfig();
+ const userConfig = this.editor.config.get( 'ui.poweredBy' ) || {};
+ const position = userConfig.position || badgeConfig.position;
+ const poweredBySide = userConfig.side || badgeConfig.side;
+
+ return {
+ position,
+ side: poweredBySide === 'left' ? 'right' : 'left',
+ verticalOffset: badgeConfig.verticalOffset,
+ horizontalOffset: badgeConfig.horizontalOffset
+ };
+ }
+}
+
+/**
+ * A view displaying the "evaluation badge".
+ */
+class EvaluationBadgeView extends View {
+ /**
+ * Creates an instance of the "evaluation badge" view.
+ *
+ * @param locale The localization services instance.
+ * @param label The label text.
+ */
+ constructor( locale: Locale, label: string ) {
+ super( locale );
+
+ this.setTemplate( {
+ tag: 'div',
+ attributes: {
+ class: [ 'ck', 'ck-evaluation-badge' ],
+ 'aria-hidden': true
+ },
+ children: [
+ {
+ tag: 'span',
+ attributes: {
+ class: [ 'ck', 'ck-evaluation-badge__label' ]
+ },
+ children: [ label ]
+ }
+ ]
+ } );
+ }
+}
+
+/**
+ * Returns the license type based on the license key.
+ */
+function getLicenseTypeFromLicenseKey( licenseKey: string ): string | null {
+ if ( licenseKey == 'GPL' ) {
+ return 'GPL';
+ }
+
+ const licenseContent = parseBase64EncodedObject( licenseKey.split( '.' )[ 1 ] );
+
+ if ( !licenseContent ) {
+ return null;
+ }
+
+ return licenseContent.licenseType || 'production';
+}
diff --git a/packages/ckeditor5-ui/src/editorui/poweredby.ts b/packages/ckeditor5-ui/src/editorui/poweredby.ts
index 6f75ddd1357..cff7de48f78 100644
--- a/packages/ckeditor5-ui/src/editorui/poweredby.ts
+++ b/packages/ckeditor5-ui/src/editorui/poweredby.ts
@@ -8,26 +8,17 @@
*/
import type { Editor, UiConfig } from '@ckeditor/ckeditor5-core';
-import {
- DomEmitterMixin,
- Rect,
- verifyLicense,
- type PositionOptions,
- type Locale
-} from '@ckeditor/ckeditor5-utils';
-import BalloonPanelView from '../panel/balloon/balloonpanelview.js';
-import IconView from '../icon/iconview.js';
+import { parseBase64EncodedObject, type Locale } from '@ckeditor/ckeditor5-utils';
+
import View from '../view.js';
-import { throttle, type DebouncedFunc } from 'lodash-es';
+import Badge from '../badge/badge.js';
+import IconView from '../icon/iconview.js';
import poweredByIcon from '../../theme/icons/project-logo.svg';
const ICON_WIDTH = 53;
const ICON_HEIGHT = 10;
-// ⚠ Note, whenever changing the threshold, make sure to update the docs/support/managing-ckeditor-logo.md docs
-// as this information is also mentioned there ⚠.
-const NARROW_ROOT_HEIGHT_THRESHOLD = 50;
-const NARROW_ROOT_WIDTH_THRESHOLD = 350;
+
const DEFAULT_LABEL = 'Powered by';
type PoweredByConfig = Required[ 'poweredBy' ];
@@ -38,176 +29,62 @@ type PoweredByConfig = Required[ 'poweredBy' ];
*
* @private
*/
-export default class PoweredBy extends /* #__PURE__ */ DomEmitterMixin() {
- /**
- * Editor instance the helper was created for.
- */
- private readonly editor: Editor;
-
- /**
- * A reference to the balloon panel hosting and positioning the "powered by" link and logo.
- */
- private _balloonView: BalloonPanelView | null;
-
- /**
- * A throttled version of the {@link #_showBalloon} method meant for frequent use to avoid performance loss.
- */
- private _showBalloonThrottled: DebouncedFunc<() => void>;
-
- /**
- * A reference to the last editable element (root, source editing area, etc.) focused by the user.
- * Since the focus can move to other focusable elements in the UI, this reference allows positioning the balloon over the
- * right element whether the user is typing or using the UI.
- */
- private _lastFocusedEditableElement: HTMLElement | null;
-
- /**
- * Creates a "powered by" helper for a given editor. The feature is initialized on Editor#ready
- * event.
- *
- * @param editor
- */
+export default class PoweredBy extends Badge {
constructor( editor: Editor ) {
- super();
-
- this.editor = editor;
- this._balloonView = null;
- this._lastFocusedEditableElement = null;
- this._showBalloonThrottled = throttle( this._showBalloon.bind( this ), 50, { leading: true } );
-
- editor.on( 'ready', this._handleEditorReady.bind( this ) );
+ super( editor, { balloonClass: 'ck-powered-by-balloon' } );
}
/**
- * Destroys the "powered by" helper along with its view.
+ * Enables "powered by" label.
*/
- public destroy(): void {
- const balloon = this._balloonView;
-
- if ( balloon ) {
- // Balloon gets destroyed by the body collection.
- // The powered by view gets destroyed by the balloon.
- balloon.unpin();
- this._balloonView = null;
- }
-
- this._showBalloonThrottled.cancel();
- this.stopListening();
- }
-
- /**
- * Enables "powered by" label once the editor (ui) is ready.
- */
- private _handleEditorReady(): void {
+ protected override _isEnabled(): boolean {
const editor = this.editor;
- const forceVisible = !!editor.config.get( 'ui.poweredBy.forceVisible' );
-
- /* istanbul ignore next -- @preserve */
- if ( !forceVisible && verifyLicense( editor.config.get( 'licenseKey' ) ) === 'VALID' ) {
- return;
- }
+ const forceVisible = editor.config.get( 'ui.poweredBy.forceVisible' );
- // No view means no body collection to append the powered by balloon to.
- if ( !editor.ui.view ) {
- return;
+ if ( forceVisible ) {
+ return true;
}
- editor.ui.focusTracker.on( 'change:isFocused', ( evt, data, isFocused ) => {
- this._updateLastFocusedEditableElement();
-
- if ( isFocused ) {
- this._showBalloon();
- } else {
- this._hideBalloon();
- }
- } );
-
- editor.ui.focusTracker.on( 'change:focusedElement', ( evt, data, focusedElement ) => {
- this._updateLastFocusedEditableElement();
-
- if ( focusedElement ) {
- this._showBalloon();
- }
- } );
-
- editor.ui.on( 'update', () => {
- this._showBalloonThrottled();
- } );
- }
-
- /**
- * Creates an instance of the {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView balloon panel}
- * with the "powered by" view inside ready for positioning.
- */
- private _createBalloonView(): void {
- const editor = this.editor;
- const balloon = this._balloonView = new BalloonPanelView();
- const poweredByConfig = getNormalizedConfig( editor );
- const view = new PoweredByView( editor.locale, poweredByConfig.label );
-
- balloon.content.add( view );
- balloon.set( {
- class: 'ck-powered-by-balloon'
- } );
-
- editor.ui.view.body.add( balloon );
+ const licenseKey = editor.config.get( 'licenseKey' )!;
- this._balloonView = balloon;
- }
-
- /**
- * Attempts to display the balloon with the "powered by" view.
- */
- private _showBalloon(): void {
- if ( !this._lastFocusedEditableElement ) {
- return;
+ if ( licenseKey == 'GPL' ) {
+ return true;
}
- const attachOptions = getBalloonAttachOptions( this.editor, this._lastFocusedEditableElement );
-
- if ( attachOptions ) {
- if ( !this._balloonView ) {
- this._createBalloonView();
- }
+ const licenseContent = parseBase64EncodedObject( licenseKey.split( '.' )[ 1 ] );
- this._balloonView!.pin( attachOptions );
+ if ( !licenseContent ) {
+ return true;
}
+
+ return !licenseContent.whiteLabel;
}
/**
- * Hides the "powered by" balloon if already visible.
+ * Creates a "powered by" badge content.
*/
- private _hideBalloon(): void {
- if ( this._balloonView ) {
- this._balloonView!.unpin();
- }
+ protected override _createBadgeContent(): View {
+ return new PoweredByView( this.editor.locale, this._getNormalizedConfig().label );
}
/**
- * Updates the {@link #_lastFocusedEditableElement} based on the state of the global focus tracker.
+ * Returns the normalized configuration for the "powered by" badge.
+ * It takes the user configuration into account and falls back to the default one.
*/
- private _updateLastFocusedEditableElement(): void {
- const editor = this.editor;
- const isFocused = editor.ui.focusTracker.isFocused;
- const focusedElement = editor.ui.focusTracker.focusedElement! as HTMLElement;
-
- if ( !isFocused || !focusedElement ) {
- this._lastFocusedEditableElement = null;
-
- return;
- }
+ protected override _getNormalizedConfig(): Required {
+ const badgeConfig = super._getNormalizedConfig();
+ const userConfig = this.editor.config.get( 'ui.poweredBy' ) || {};
+ const position = userConfig.position || badgeConfig.position;
+ const verticalOffset = position === 'inside' ? 5 : badgeConfig.verticalOffset;
- const editableEditorElements = Array.from( editor.ui.getEditableElementsNames() ).map( name => {
- return editor.ui.getEditableElement( name );
- } );
-
- if ( editableEditorElements.includes( focusedElement ) ) {
- this._lastFocusedEditableElement = focusedElement;
- } else {
- // If it's none of the editable element, then the focus is somewhere in the UI. Let's display powered by
- // over the first element then.
- this._lastFocusedEditableElement = editableEditorElements[ 0 ]!;
- }
+ return {
+ position,
+ side: userConfig.side || badgeConfig.side,
+ label: userConfig.label === undefined ? DEFAULT_LABEL : userConfig.label,
+ verticalOffset: userConfig.verticalOffset !== undefined ? userConfig.verticalOffset : verticalOffset,
+ horizontalOffset: userConfig.horizontalOffset !== undefined ? userConfig.horizontalOffset : badgeConfig.horizontalOffset,
+ forceVisible: !!userConfig.forceVisible
+ };
}
}
@@ -216,7 +93,7 @@ export default class PoweredBy extends /* #__PURE__ */ DomEmitterMixin() {
*/
class PoweredByView extends View {
/**
- * Created an instance of the "powered by" view.
+ * Creates an instance of the "powered by" view.
*
* @param locale The localization services instance.
* @param label The label text.
@@ -276,90 +153,3 @@ class PoweredByView extends View {
} );
}
}
-
-function getBalloonAttachOptions( editor: Editor, focusedEditableElement: HTMLElement ): Partial | null {
- const poweredByConfig = getNormalizedConfig( editor )!;
- const positioningFunction = poweredByConfig.side === 'right' ?
- getLowerRightCornerPosition( focusedEditableElement, poweredByConfig ) :
- getLowerLeftCornerPosition( focusedEditableElement, poweredByConfig );
-
- return {
- target: focusedEditableElement,
- positions: [ positioningFunction ]
- };
-}
-
-function getLowerRightCornerPosition( focusedEditableElement: HTMLElement, config: PoweredByConfig ) {
- return getLowerCornerPosition( focusedEditableElement, config, ( rootRect, balloonRect ) => {
- return rootRect.left + rootRect.width - balloonRect.width - config.horizontalOffset;
- } );
-}
-
-function getLowerLeftCornerPosition( focusedEditableElement: HTMLElement, config: PoweredByConfig ) {
- return getLowerCornerPosition( focusedEditableElement, config, rootRect => rootRect.left + config.horizontalOffset );
-}
-
-function getLowerCornerPosition(
- focusedEditableElement: HTMLElement,
- config: PoweredByConfig,
- getBalloonLeft: ( visibleEditableElementRect: Rect, balloonRect: Rect ) => number
-) {
- return ( visibleEditableElementRect: Rect, balloonRect: Rect ) => {
- const editableElementRect = new Rect( focusedEditableElement );
-
- if ( editableElementRect.width < NARROW_ROOT_WIDTH_THRESHOLD || editableElementRect.height < NARROW_ROOT_HEIGHT_THRESHOLD ) {
- return null;
- }
-
- let balloonTop;
-
- if ( config.position === 'inside' ) {
- balloonTop = editableElementRect.bottom - balloonRect.height;
- }
- else {
- balloonTop = editableElementRect.bottom - balloonRect.height / 2;
- }
-
- balloonTop -= config.verticalOffset;
-
- const balloonLeft = getBalloonLeft( editableElementRect, balloonRect );
-
- // Clone the editable element rect and place it where the balloon would be placed.
- // This will allow getVisible() to work from editable element's perspective (rect source).
- // and yield a result as if the balloon was on the same (scrollable) layer as the editable element.
- const newBalloonPositionRect = visibleEditableElementRect
- .clone()
- .moveTo( balloonLeft, balloonTop )
- .getIntersection( balloonRect.clone().moveTo( balloonLeft, balloonTop ) )!;
-
- const newBalloonPositionVisibleRect = newBalloonPositionRect.getVisible();
-
- if ( !newBalloonPositionVisibleRect || newBalloonPositionVisibleRect.getArea() < balloonRect.getArea() ) {
- return null;
- }
-
- return {
- top: balloonTop,
- left: balloonLeft,
- name: `position_${ config.position }-side_${ config.side }`,
- config: {
- withArrow: false
- }
- };
- };
-}
-
-function getNormalizedConfig( editor: Editor ): PoweredByConfig {
- const userConfig = editor.config.get( 'ui.poweredBy' );
- const position = userConfig && userConfig.position || 'border';
-
- return {
- position,
- label: DEFAULT_LABEL,
- verticalOffset: position === 'inside' ? 5 : 0,
- horizontalOffset: 5,
-
- side: editor.locale.contentLanguageDirection === 'ltr' ? 'right' : 'left',
- ...userConfig
- };
-}
diff --git a/packages/ckeditor5-ui/tests/badge/badge.js b/packages/ckeditor5-ui/tests/badge/badge.js
new file mode 100644
index 00000000000..a5a82891a3c
--- /dev/null
+++ b/packages/ckeditor5-ui/tests/badge/badge.js
@@ -0,0 +1,666 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* global document, window, HTMLElement */
+
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor.js';
+import SourceEditing from '@ckeditor/ckeditor5-source-editing/src/sourceediting.js';
+import { Rect, global } from '@ckeditor/ckeditor5-utils';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils.js';
+
+import { BalloonPanelView } from '../../src/index.js';
+import View from '../../src/view.js';
+import Badge from '../../src/badge/badge.js';
+
+class BadgeExtended extends Badge {
+ _isEnabled() {
+ return true;
+ }
+
+ _createBadgeContent() {
+ return new EvaluationBadgeView( this.editor.locale, 'Badge extended label' );
+ }
+}
+
+class EvaluationBadgeView extends View {
+ constructor( locale, label ) {
+ super( locale );
+
+ this.setTemplate( {
+ tag: 'div',
+ attributes: {
+ class: [ 'ck-badge-extended' ]
+ },
+ children: [
+ {
+ tag: 'span',
+ attributes: {
+ class: [ 'ck-badge-extended__label' ]
+ },
+ children: [ label ]
+ }
+ ]
+ } );
+ }
+}
+
+describe( 'Badge', () => {
+ let editor, element, badge;
+
+ testUtils.createSinonSandbox();
+
+ beforeEach( async () => {
+ element = document.createElement( 'div' );
+ document.body.appendChild( element );
+ editor = await createEditor( element );
+
+ badge = new BadgeExtended( editor );
+ editor.fire( 'ready' );
+
+ testUtils.sinon.stub( editor.editing.view.getDomRoot(), 'getBoundingClientRect' ).returns( {
+ top: 0,
+ left: 0,
+ right: 400,
+ width: 400,
+ bottom: 100,
+ height: 100
+ } );
+
+ testUtils.sinon.stub( document.body, 'getBoundingClientRect' ).returns( {
+ top: 0,
+ right: 1000,
+ bottom: 1000,
+ left: 0,
+ width: 1000,
+ height: 1000
+ } );
+
+ sinon.stub( global.window, 'innerWidth' ).value( 1000 );
+ sinon.stub( global.window, 'innerHeight' ).value( 1000 );
+ } );
+
+ afterEach( async () => {
+ element.remove();
+ await editor.destroy();
+ } );
+
+ describe( 'constructor()', () => {
+ describe( 'balloon creation', () => {
+ it( 'should create the balloon on demand', () => {
+ expect( badge._balloonView ).to.be.null;
+
+ focusEditor( editor );
+
+ expect( badge._balloonView ).to.be.instanceOf( BalloonPanelView );
+ } );
+ } );
+
+ describe( 'balloon management on editor focus change', () => {
+ const originalGetVisible = Rect.prototype.getVisible;
+
+ it( 'should show the balloon when the editor gets focused', () => {
+ focusEditor( editor );
+
+ expect( badge._balloonView.isVisible ).to.be.true;
+ } );
+
+ it( 'should show the balloon if the focus is not in the editing root but in other editor UI', async () => {
+ const focusableEditorUIElement = document.createElement( 'input' );
+ focusableEditorUIElement.type = 'text';
+ document.body.appendChild( focusableEditorUIElement );
+
+ editor.ui.focusTracker.add( focusableEditorUIElement );
+
+ // Just generate the balloon on demand.
+ focusEditor( editor );
+ blurEditor( editor );
+
+ await wait( 10 );
+ const pinSpy = testUtils.sinon.spy( badge._balloonView, 'pin' );
+
+ focusEditor( editor, focusableEditorUIElement );
+
+ sinon.assert.calledOnce( pinSpy );
+ sinon.assert.calledWith( pinSpy, sinon.match.has( 'target', editor.editing.view.getDomRoot() ) );
+
+ focusableEditorUIElement.remove();
+ } );
+
+ it( 'should hide the balloon on blur', async () => {
+ focusEditor( editor );
+
+ expect( badge._balloonView.isVisible ).to.be.true;
+
+ blurEditor( editor );
+
+ // FocusTracker's blur handler is asynchronous.
+ await wait( 200 );
+
+ expect( badge._balloonView.isVisible ).to.be.false;
+ } );
+
+ // This is a weak test because it does not check the geometry but it will do.
+ it( 'should show the balloon when the source editing is engaged', async () => {
+ const domRoot = editor.editing.view.getDomRoot();
+ const originalGetBoundingClientRect = HTMLElement.prototype.getBoundingClientRect;
+
+ function isEditableElement( element ) {
+ return Array.from( editor.ui.getEditableElementsNames() ).map( name => {
+ return editor.ui.getEditableElement( name );
+ } ).includes( element );
+ }
+
+ // Rect#getVisible() passthrough to ignore ancestors. Makes testing a lot easier.
+ testUtils.sinon.stub( Rect.prototype, 'getVisible' ).callsFake( function() {
+ if ( isEditableElement( this._source ) ) {
+ return new Rect( this._source );
+ } else {
+ return originalGetVisible.call( this );
+ }
+ } );
+
+ // Stub textarea's client rect.
+ testUtils.sinon.stub( HTMLElement.prototype, 'getBoundingClientRect' ).callsFake( function() {
+ if ( this.parentNode.classList.contains( 'ck-source-editing-area' ) ) {
+ return {
+ top: 0,
+ left: 0,
+ right: 400,
+ width: 400,
+ bottom: 200,
+ height: 200
+ };
+ }
+
+ return originalGetBoundingClientRect.call( this );
+ } );
+
+ focusEditor( editor );
+
+ domRoot.getBoundingClientRect.returns( {
+ top: 0,
+ left: 0,
+ right: 350,
+ width: 350,
+ bottom: 100,
+ height: 100
+ } );
+
+ const pinSpy = testUtils.sinon.spy( badge._balloonView, 'pin' );
+
+ editor.ui.fire( 'update' );
+
+ await wait( 75 );
+
+ expect( badge._balloonView.isVisible ).to.be.true;
+ expect( badge._balloonView.position ).to.equal( 'position_border-side_right' );
+ sinon.assert.calledWith( pinSpy.lastCall, sinon.match.has( 'target', domRoot ) );
+
+ editor.plugins.get( 'SourceEditing' ).isSourceEditingMode = true;
+
+ const sourceAreaElement = editor.ui.getEditableElement( 'sourceEditing:main' );
+
+ focusEditor( editor, sourceAreaElement );
+ sinon.assert.calledWith(
+ pinSpy.lastCall,
+ sinon.match.has( 'target', sourceAreaElement )
+ );
+
+ expect( badge._balloonView.isVisible ).to.be.true;
+ expect( badge._balloonView.position ).to.equal( 'position_border-side_right' );
+
+ editor.plugins.get( 'SourceEditing' ).isSourceEditingMode = false;
+ focusEditor( editor );
+
+ expect( badge._balloonView.isVisible ).to.be.true;
+ expect( badge._balloonView.position ).to.equal( 'position_border-side_right' );
+ sinon.assert.calledWith( pinSpy.lastCall, sinon.match.has( 'target', domRoot ) );
+ } );
+ } );
+
+ describe( 'balloon management on EditorUI#update', () => {
+ it( 'should not trigger if the editor is not focused', () => {
+ expect( badge._balloonView ).to.be.null;
+
+ editor.ui.fire( 'update' );
+
+ expect( badge._balloonView ).to.be.null;
+ } );
+
+ it( 'should (re-)show the balloon but throttled', async () => {
+ focusEditor( editor );
+
+ const pinSpy = testUtils.sinon.spy( badge._balloonView, 'pin' );
+
+ editor.ui.fire( 'update' );
+ editor.ui.fire( 'update' );
+
+ sinon.assert.notCalled( pinSpy );
+
+ await wait( 75 );
+
+ sinon.assert.calledOnce( pinSpy );
+ sinon.assert.calledWith( pinSpy.firstCall, sinon.match.has( 'target', editor.editing.view.getDomRoot() ) );
+ } );
+
+ it( 'should (re-)show the balloon if the focus is not in the editing root but in other editor UI', async () => {
+ const focusableEditorUIElement = document.createElement( 'input' );
+ focusableEditorUIElement.type = 'text';
+ editor.ui.focusTracker.add( focusableEditorUIElement );
+ document.body.appendChild( focusableEditorUIElement );
+
+ focusEditor( editor, focusableEditorUIElement );
+
+ const pinSpy = testUtils.sinon.spy( badge._balloonView, 'pin' );
+
+ sinon.assert.notCalled( pinSpy );
+
+ editor.ui.fire( 'update' );
+ editor.ui.fire( 'update' );
+
+ sinon.assert.calledOnce( pinSpy );
+
+ await wait( 75 );
+
+ sinon.assert.calledTwice( pinSpy );
+ sinon.assert.calledWith( pinSpy, sinon.match.has( 'target', editor.editing.view.getDomRoot() ) );
+ focusableEditorUIElement.remove();
+ } );
+ } );
+
+ describe( 'balloon view', () => {
+ let balloon, focusTrackerAddSpy;
+
+ beforeEach( () => {
+ focusTrackerAddSpy = testUtils.sinon.spy( editor.ui.focusTracker, 'add' );
+
+ focusEditor( editor );
+
+ balloon = badge._balloonView;
+ } );
+
+ it( 'should be an instance of BalloonPanelView', () => {
+ expect( balloon ).to.be.instanceOf( BalloonPanelView );
+ } );
+
+ it( 'should host a badge view', () => {
+ expect( balloon.content.first ).to.be.instanceOf( View );
+ } );
+
+ it( 'should have no arrow', () => {
+ expect( balloon.withArrow ).to.be.false;
+ } );
+
+ it( 'should not have a specific CSS class if not provided', () => {
+ expect( balloon.class ).to.be.undefined;
+ } );
+
+ it( 'should be added to editor\'s body view collection', () => {
+ expect( editor.ui.view.body.has( balloon ) ).to.be.true;
+ } );
+
+ it( 'should be registered in the focus tracker to avoid focus loss on click', () => {
+ sinon.assert.calledWith( focusTrackerAddSpy, balloon.element );
+ } );
+ } );
+
+ describe( 'badge view', () => {
+ let view;
+
+ beforeEach( () => {
+ focusEditor( editor );
+
+ view = badge._balloonView.content.first;
+ } );
+
+ it( 'should have specific CSS classes', () => {
+ expect( view.element.classList.contains( 'ck-badge-extended' ) ).to.be.true;
+ } );
+
+ it( 'should have a label', () => {
+ expect( view.element.firstChild.tagName ).to.equal( 'SPAN' );
+ expect( view.element.firstChild.classList.contains( 'ck-badge-extended__label' ) ).to.be.true;
+ expect( view.element.firstChild.textContent ).to.equal( 'Badge extended label' );
+ } );
+
+ it( 'should not be accessible via tab key navigation', () => {
+ expect( view.element.firstChild.tabIndex ).to.equal( -1 );
+ } );
+ } );
+ } );
+
+ describe( 'balloon positioning depending on environment and configuration', () => {
+ const originalGetVisible = Rect.prototype.getVisible;
+ let rootRect, balloonRect;
+
+ beforeEach( () => {
+ rootRect = new Rect( { top: 0, left: 0, width: 400, right: 400, bottom: 100, height: 100 } );
+ balloonRect = new Rect( { top: 0, left: 0, width: 20, right: 20, bottom: 10, height: 10 } );
+ } );
+
+ it( 'should not show the balloon if the root is not visible vertically', async () => {
+ const domRoot = editor.editing.view.getDomRoot();
+ const parentWithOverflow = document.createElement( 'div' );
+
+ parentWithOverflow.style.overflow = 'scroll';
+ // Is not enough height to be visible vertically.
+ parentWithOverflow.style.height = '99px';
+
+ document.body.appendChild( parentWithOverflow );
+ parentWithOverflow.appendChild( domRoot );
+
+ focusEditor( editor );
+
+ expect( badge._balloonView.isVisible ).to.be.true;
+ expect( badge._balloonView.position ).to.equal( 'arrowless' );
+
+ parentWithOverflow.remove();
+ } );
+
+ it( 'should not show the balloon if the root is not visible horizontally', async () => {
+ const domRoot = editor.editing.view.getDomRoot();
+ const parentWithOverflow = document.createElement( 'div' );
+
+ parentWithOverflow.style.overflow = 'scroll';
+ // Is not enough width to be visible horizontally.
+ parentWithOverflow.style.width = '399px';
+
+ document.body.appendChild( parentWithOverflow );
+ parentWithOverflow.appendChild( domRoot );
+
+ focusEditor( editor );
+
+ expect( badge._balloonView.isVisible ).to.be.true;
+ expect( badge._balloonView.position ).to.equal( 'arrowless' );
+
+ parentWithOverflow.remove();
+ } );
+
+ it( 'should position to the left side if the UI language is RTL and no side was configured', async () => {
+ const editor = await createEditor( element, {
+ language: 'ar'
+ } );
+
+ badge = new BadgeExtended( editor );
+ editor.fire( 'ready' );
+
+ testUtils.sinon.stub( editor.ui.getEditableElement( 'main' ), 'getBoundingClientRect' ).returns( {
+ top: 0,
+ left: 0,
+ right: 400,
+ width: 400,
+ bottom: 100,
+ height: 100
+ } );
+
+ focusEditor( editor );
+
+ const pinSpy = testUtils.sinon.spy( badge._balloonView, 'pin' );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ sinon.assert.calledOnce( pinSpy );
+
+ const pinArgs = pinSpy.firstCall.args[ 0 ];
+ const positioningFunction = pinArgs.positions[ 0 ];
+
+ expect( pinArgs.target ).to.equal( editor.editing.view.getDomRoot() );
+ expect( positioningFunction( rootRect, balloonRect ) ).to.deep.equal( {
+ top: 95,
+ left: 5,
+ name: 'position_border-side_left',
+ config: {
+ withArrow: false
+ }
+ } );
+
+ await editor.destroy();
+ } );
+
+ it( 'should position the balloon in the lower right corner by default', async () => {
+ focusEditor( editor );
+
+ const pinSpy = testUtils.sinon.spy( badge._balloonView, 'pin' );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ sinon.assert.calledOnce( pinSpy );
+
+ const pinArgs = pinSpy.firstCall.args[ 0 ];
+ const positioningFunction = pinArgs.positions[ 0 ];
+
+ expect( pinArgs.target ).to.equal( editor.editing.view.getDomRoot() );
+ expect( positioningFunction( rootRect, balloonRect ) ).to.deep.equal( {
+ top: 95,
+ left: 375,
+ name: 'position_border-side_right',
+ config: {
+ withArrow: false
+ }
+ } );
+ } );
+
+ it( 'should hide the balloon if the root is invisible (cropped by ancestors)', async () => {
+ const editor = await createEditor( element );
+
+ badge = new BadgeExtended( editor );
+ editor.fire( 'ready' );
+
+ const domRoot = editor.editing.view.getDomRoot();
+
+ rootRect = new Rect( { top: 0, left: 0, width: 100, right: 100, bottom: 10, height: 10 } );
+
+ testUtils.sinon.stub( rootRect, 'getVisible' ).returns( null );
+
+ focusEditor( editor );
+
+ const pinSpy = testUtils.sinon.spy( badge._balloonView, 'pin' );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ sinon.assert.calledOnce( pinSpy );
+
+ const pinArgs = pinSpy.firstCall.args[ 0 ];
+ const positioningFunction = pinArgs.positions[ 0 ];
+
+ expect( pinArgs.target ).to.equal( domRoot );
+ expect( positioningFunction( rootRect, balloonRect ) ).to.equal( null );
+
+ await editor.destroy();
+ } );
+
+ it( 'should hide the balloon if displayed over the bottom root border but partially cropped by an ancestor', async () => {
+ const editor = await createEditor( element );
+
+ badge = new BadgeExtended( editor );
+ editor.fire( 'ready' );
+
+ const domRoot = editor.editing.view.getDomRoot();
+
+ rootRect = new Rect( { top: 0, left: 0, width: 100, right: 100, bottom: 10, height: 10 } );
+
+ focusEditor( editor );
+
+ const pinSpy = testUtils.sinon.spy( badge._balloonView, 'pin' );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ sinon.assert.calledOnce( pinSpy );
+
+ const pinArgs = pinSpy.firstCall.args[ 0 ];
+ const positioningFunction = pinArgs.positions[ 0 ];
+
+ expect( pinArgs.target ).to.equal( domRoot );
+ expect( positioningFunction( rootRect, balloonRect ) ).to.equal( null );
+
+ await editor.destroy();
+ } );
+
+ it( 'should not display the balloon if the root is narrower than 350px', async () => {
+ const domRoot = editor.editing.view.getDomRoot();
+
+ testUtils.sinon.stub( Rect.prototype, 'getVisible' ).callsFake( function() {
+ if ( this._source === domRoot ) {
+ return new Rect( domRoot );
+ } else {
+ return originalGetVisible.call( this );
+ }
+ } );
+
+ domRoot.getBoundingClientRect.returns( {
+ top: 0,
+ left: 0,
+ right: 349,
+ width: 349,
+ bottom: 100,
+ height: 100
+ } );
+
+ focusEditor( editor );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ const pinSpy = testUtils.sinon.spy( badge._balloonView, 'pin' );
+
+ expect( badge._balloonView.isVisible ).to.be.true;
+ expect( badge._balloonView.position ).to.equal( 'arrowless' );
+
+ domRoot.getBoundingClientRect.returns( {
+ top: 0,
+ left: 0,
+ right: 350,
+ width: 350,
+ bottom: 100,
+ height: 100
+ } );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ expect( badge._balloonView.isVisible ).to.be.true;
+ expect( badge._balloonView.position ).to.equal( 'position_border-side_right' );
+
+ const pinArgs = pinSpy.firstCall.args[ 0 ];
+ const positioningFunction = pinArgs.positions[ 0 ];
+
+ expect( pinArgs.target ).to.equal( editor.editing.view.getDomRoot() );
+ expect( positioningFunction( rootRect, balloonRect ) ).to.deep.equal( {
+ top: 95,
+ left: 325,
+ name: 'position_border-side_right',
+ config: {
+ withArrow: false
+ }
+ } );
+ } );
+
+ it( 'should not display the balloon if the root is shorter than 50px', async () => {
+ const domRoot = editor.editing.view.getDomRoot();
+
+ testUtils.sinon.stub( Rect.prototype, 'getVisible' ).callsFake( function() {
+ if ( this._source === domRoot ) {
+ return new Rect( domRoot );
+ } else {
+ return originalGetVisible.call( this );
+ }
+ } );
+
+ domRoot.getBoundingClientRect.returns( {
+ top: 0,
+ left: 0,
+ right: 1000,
+ width: 1000,
+ bottom: 49,
+ height: 49
+ } );
+
+ focusEditor( editor );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ const pinSpy = testUtils.sinon.spy( badge._balloonView, 'pin' );
+
+ expect( badge._balloonView.isVisible ).to.be.true;
+ expect( badge._balloonView.position ).to.equal( 'arrowless' );
+
+ domRoot.getBoundingClientRect.returns( {
+ top: 0,
+ left: 0,
+ right: 1000,
+ width: 1000,
+ bottom: 50,
+ height: 50
+ } );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ expect( badge._balloonView.isVisible ).to.be.true;
+ expect( badge._balloonView.position ).to.equal( 'position_border-side_right' );
+
+ const pinArgs = pinSpy.firstCall.args[ 0 ];
+ const positioningFunction = pinArgs.positions[ 0 ];
+
+ expect( pinArgs.target ).to.equal( editor.editing.view.getDomRoot() );
+ expect( positioningFunction( rootRect, balloonRect ) ).to.deep.equal( {
+ top: 45,
+ left: 975,
+ name: 'position_border-side_right',
+ config: {
+ withArrow: false
+ }
+ } );
+ } );
+ } );
+
+ async function createEditor( element, config = { plugins: [ SourceEditing ] } ) {
+ return ClassicTestEditor.create( element, config );
+ }
+
+ function wait( time ) {
+ return new Promise( res => {
+ window.setTimeout( res, time );
+ } );
+ }
+
+ function focusEditor( editor, focusableUIElement ) {
+ if ( !focusableUIElement ) {
+ focusableUIElement = editor.editing.view.getDomRoot();
+ editor.editing.view.focus();
+ } else {
+ focusableUIElement.focus();
+ }
+
+ editor.ui.focusTracker.focusedElement = focusableUIElement;
+ editor.ui.focusTracker.isFocused = true;
+ }
+
+ function blurEditor( editor ) {
+ editor.ui.focusTracker.focusedElement = null;
+ editor.ui.focusTracker.isFocused = null;
+ }
+} );
diff --git a/packages/ckeditor5-ui/tests/editorui/editorui.js b/packages/ckeditor5-ui/tests/editorui/editorui.js
index 66beefbfe8a..6bc7c731fef 100644
--- a/packages/ckeditor5-ui/tests/editorui/editorui.js
+++ b/packages/ckeditor5-ui/tests/editorui/editorui.js
@@ -9,6 +9,7 @@ import ComponentFactory from '../../src/componentfactory.js';
import ToolbarView from '../../src/toolbar/toolbarview.js';
import TooltipManager from '../../src/tooltipmanager.js';
import PoweredBy from '../../src/editorui/poweredby.js';
+import EvaluationBadge from '../../src/editorui/evaluationbadge.js';
import AriaLiveAnnouncer from '../../src/arialiveannouncer.js';
import { EditorUIView, InlineEditableUIView, MenuBarView, View } from '../../src/index.js';
@@ -68,6 +69,10 @@ describe( 'EditorUI', () => {
expect( ui.poweredBy ).to.be.instanceOf( PoweredBy );
} );
+ it( 'should create #evaluationBadge', () => {
+ expect( ui.evaluationBadge ).to.be.instanceOf( EvaluationBadge );
+ } );
+
it( 'should create the aria live announcer instance', () => {
expect( ui.ariaLiveAnnouncer ).to.be.instanceOf( AriaLiveAnnouncer );
} );
@@ -192,6 +197,14 @@ describe( 'EditorUI', () => {
sinon.assert.calledOnce( destroySpy );
} );
+
+ it( 'should destroy #evaluationBadge', () => {
+ const destroySpy = sinon.spy( ui.evaluationBadge, 'destroy' );
+
+ ui.destroy();
+
+ sinon.assert.calledOnce( destroySpy );
+ } );
} );
describe( 'setEditableElement()', () => {
diff --git a/packages/ckeditor5-ui/tests/editorui/evaluationbadge.js b/packages/ckeditor5-ui/tests/editorui/evaluationbadge.js
new file mode 100644
index 00000000000..bd00cd0ec78
--- /dev/null
+++ b/packages/ckeditor5-ui/tests/editorui/evaluationbadge.js
@@ -0,0 +1,1021 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* global document, window, HTMLElement, getComputedStyle, console */
+
+import { Editor } from '@ckeditor/ckeditor5-core';
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js';
+import EditorUI from '../../src/editorui/editorui.js';
+import { BalloonPanelView } from '../../src/index.js';
+import View from '../../src/view.js';
+
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor.js';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils.js';
+import { Rect, global } from '@ckeditor/ckeditor5-utils';
+import SourceEditing from '@ckeditor/ckeditor5-source-editing/src/sourceediting.js';
+import Heading from '@ckeditor/ckeditor5-heading/src/heading.js';
+import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model.js';
+import generateKey from '@ckeditor/ckeditor5-core/tests/_utils/generatelicensekey.js';
+
+describe( 'EvaluationBadge', () => {
+ let editor, element, developmentLicenseKey;
+
+ testUtils.createSinonSandbox();
+
+ beforeEach( async () => {
+ sinon.stub( console, 'info' );
+ developmentLicenseKey = generateKey( { licenseType: 'development' } ).licenseKey;
+ element = document.createElement( 'div' );
+ document.body.appendChild( element );
+ editor = await createEditor( element, {
+ plugins: [ SourceEditing ],
+ licenseKey: developmentLicenseKey
+ } );
+
+ testUtils.sinon.stub( editor.editing.view.getDomRoot(), 'getBoundingClientRect' ).returns( {
+ top: 0,
+ left: 0,
+ right: 400,
+ width: 400,
+ bottom: 100,
+ height: 100
+ } );
+
+ testUtils.sinon.stub( document.body, 'getBoundingClientRect' ).returns( {
+ top: 0,
+ right: 1000,
+ bottom: 1000,
+ left: 0,
+ width: 1000,
+ height: 1000
+ } );
+
+ sinon.stub( global.window, 'innerWidth' ).value( 1000 );
+ sinon.stub( global.window, 'innerHeight' ).value( 1000 );
+ } );
+
+ afterEach( async () => {
+ element.remove();
+ await editor.destroy();
+ } );
+
+ describe( 'constructor()', () => {
+ describe( 'balloon creation', () => {
+ it( 'should not throw if there is no view in EditorUI', () => {
+ expect( () => {
+ const editor = new Editor( { licenseKey: developmentLicenseKey } );
+
+ editor.model.document.createRoot();
+ editor.ui = new EditorUI( editor );
+ editor.editing.view.attachDomRoot( element );
+ editor.fire( 'ready' );
+ element.style.display = 'block';
+ element.setAttribute( 'contenteditable', 'true' );
+ editor.ui.focusTracker.add( element );
+ element.focus();
+
+ editor.destroy();
+ editor.ui.destroy();
+ } ).to.not.throw();
+ } );
+
+ it( 'should create the balloon on demand', () => {
+ expect( editor.ui.evaluationBadge._balloonView ).to.be.null;
+
+ focusEditor( editor );
+
+ expect( editor.ui.evaluationBadge._balloonView ).to.be.instanceOf( BalloonPanelView );
+ } );
+
+ it( 'should create the balloon when license type is `evaluation`', async () => {
+ const { licenseKey, todayTimestamp } = generateKey( {
+ licenseType: 'evaluation',
+ isExpired: false,
+ daysAfterExpiration: -1
+ } );
+
+ const today = todayTimestamp;
+ const dateNow = sinon.stub( Date, 'now' ).returns( today );
+
+ const editor = await createEditor( element, {
+ licenseKey
+ } );
+
+ expect( editor.ui.evaluationBadge._balloonView ).to.be.null;
+
+ focusEditor( editor );
+
+ expect( editor.ui.evaluationBadge._balloonView ).to.be.instanceOf( BalloonPanelView );
+
+ const balloonElement = editor.ui.evaluationBadge._balloonView.element;
+
+ expect( balloonElement.querySelector( '.ck-evaluation-badge__label' ).textContent ).to.equal(
+ 'For evaluation purposes only'
+ );
+
+ await editor.destroy();
+
+ dateNow.restore();
+ } );
+
+ it( 'should create the balloon when license type is `trial`', async () => {
+ const { licenseKey, todayTimestamp } = generateKey( {
+ licenseType: 'trial',
+ isExpired: false,
+ daysAfterExpiration: -1
+ } );
+
+ const today = todayTimestamp;
+ const dateNow = sinon.stub( Date, 'now' ).returns( today );
+
+ const editor = await createEditor( element, {
+ licenseKey
+ } );
+
+ expect( editor.ui.evaluationBadge._balloonView ).to.be.null;
+
+ focusEditor( editor );
+
+ expect( editor.ui.evaluationBadge._balloonView ).to.be.instanceOf( BalloonPanelView );
+
+ const balloonElement = editor.ui.evaluationBadge._balloonView.element;
+
+ expect( balloonElement.querySelector( '.ck-evaluation-badge__label' ).textContent ).to.equal(
+ 'For evaluation purposes only'
+ );
+
+ await editor.destroy();
+
+ dateNow.restore();
+ } );
+
+ it( 'should create the balloon when license type is `development`', async () => {
+ const editor = await createEditor( element, {
+ licenseKey: developmentLicenseKey
+ } );
+
+ expect( editor.ui.evaluationBadge._balloonView ).to.be.null;
+
+ focusEditor( editor );
+
+ expect( editor.ui.evaluationBadge._balloonView ).to.be.instanceOf( BalloonPanelView );
+
+ const balloonElement = editor.ui.evaluationBadge._balloonView.element;
+
+ expect( balloonElement.querySelector( '.ck-evaluation-badge__label' ).textContent ).to.equal(
+ 'For development purposes only'
+ );
+
+ await editor.destroy();
+ } );
+
+ it( 'should not depend on white-label', async () => {
+ const { licenseKey } = generateKey( { whiteLabel: true, licenseType: 'development' } );
+ const editor = await createEditor( element, {
+ licenseKey
+ } );
+
+ expect( editor.ui.evaluationBadge._balloonView ).to.be.null;
+
+ focusEditor( editor );
+
+ expect( editor.ui.evaluationBadge._balloonView ).to.be.instanceOf( BalloonPanelView );
+
+ await editor.destroy();
+ } );
+ } );
+
+ describe( 'balloon management on editor focus change', () => {
+ const originalGetVisible = Rect.prototype.getVisible;
+
+ it( 'should show the balloon when the editor gets focused', () => {
+ focusEditor( editor );
+
+ expect( editor.ui.evaluationBadge._balloonView.isVisible ).to.be.true;
+ } );
+
+ it( 'should show the balloon if the focus is not in the editing root but in other editor UI', async () => {
+ const focusableEditorUIElement = document.createElement( 'input' );
+ focusableEditorUIElement.type = 'text';
+ document.body.appendChild( focusableEditorUIElement );
+
+ editor.ui.focusTracker.add( focusableEditorUIElement );
+
+ // Just generate the balloon on demand.
+ focusEditor( editor );
+ blurEditor( editor );
+
+ await wait( 10 );
+ const pinSpy = testUtils.sinon.spy( editor.ui.evaluationBadge._balloonView, 'pin' );
+
+ focusEditor( editor, focusableEditorUIElement );
+
+ sinon.assert.calledOnce( pinSpy );
+ sinon.assert.calledWith( pinSpy, sinon.match.has( 'target', editor.editing.view.getDomRoot() ) );
+
+ focusableEditorUIElement.remove();
+ } );
+
+ it( 'should hide the balloon on blur', async () => {
+ focusEditor( editor );
+
+ expect( editor.ui.evaluationBadge._balloonView.isVisible ).to.be.true;
+
+ blurEditor( editor );
+
+ // FocusTracker's blur handler is asynchronous.
+ await wait( 200 );
+
+ expect( editor.ui.evaluationBadge._balloonView.isVisible ).to.be.false;
+ } );
+
+ // This is a weak test because it does not check the geometry but it will do.
+ it( 'should show the balloon when the source editing is engaged', async () => {
+ const domRoot = editor.editing.view.getDomRoot();
+ const originalGetBoundingClientRect = HTMLElement.prototype.getBoundingClientRect;
+
+ function isEditableElement( element ) {
+ return Array.from( editor.ui.getEditableElementsNames() ).map( name => {
+ return editor.ui.getEditableElement( name );
+ } ).includes( element );
+ }
+
+ // Rect#getVisible() passthrough to ignore ancestors. Makes testing a lot easier.
+ testUtils.sinon.stub( Rect.prototype, 'getVisible' ).callsFake( function() {
+ if ( isEditableElement( this._source ) ) {
+ return new Rect( this._source );
+ } else {
+ return originalGetVisible.call( this );
+ }
+ } );
+
+ // Stub textarea's client rect.
+ testUtils.sinon.stub( HTMLElement.prototype, 'getBoundingClientRect' ).callsFake( function() {
+ if ( this.parentNode.classList.contains( 'ck-source-editing-area' ) ) {
+ return {
+ top: 0,
+ left: 0,
+ right: 400,
+ width: 400,
+ bottom: 200,
+ height: 200
+ };
+ }
+
+ return originalGetBoundingClientRect.call( this );
+ } );
+
+ focusEditor( editor );
+
+ domRoot.getBoundingClientRect.returns( {
+ top: 0,
+ left: 0,
+ right: 350,
+ width: 350,
+ bottom: 100,
+ height: 100
+ } );
+
+ const pinSpy = testUtils.sinon.spy( editor.ui.evaluationBadge._balloonView, 'pin' );
+
+ editor.ui.fire( 'update' );
+
+ await wait( 75 );
+
+ expect( editor.ui.evaluationBadge._balloonView.isVisible ).to.be.true;
+ expect( editor.ui.evaluationBadge._balloonView.position ).to.equal( 'position_border-side_left' );
+ sinon.assert.calledWith( pinSpy.lastCall, sinon.match.has( 'target', domRoot ) );
+
+ editor.plugins.get( 'SourceEditing' ).isSourceEditingMode = true;
+
+ const sourceAreaElement = editor.ui.getEditableElement( 'sourceEditing:main' );
+
+ focusEditor( editor, sourceAreaElement );
+ sinon.assert.calledWith(
+ pinSpy.lastCall,
+ sinon.match.has( 'target', sourceAreaElement )
+ );
+
+ expect( editor.ui.evaluationBadge._balloonView.isVisible ).to.be.true;
+ expect( editor.ui.evaluationBadge._balloonView.position ).to.equal( 'position_border-side_left' );
+
+ editor.plugins.get( 'SourceEditing' ).isSourceEditingMode = false;
+ focusEditor( editor );
+
+ expect( editor.ui.evaluationBadge._balloonView.isVisible ).to.be.true;
+ expect( editor.ui.evaluationBadge._balloonView.position ).to.equal( 'position_border-side_left' );
+ sinon.assert.calledWith( pinSpy.lastCall, sinon.match.has( 'target', domRoot ) );
+ } );
+ } );
+
+ describe( 'balloon management on EditorUI#update', () => {
+ it( 'should not trigger if the editor is not focused', () => {
+ expect( editor.ui.evaluationBadge._balloonView ).to.be.null;
+
+ editor.ui.fire( 'update' );
+
+ expect( editor.ui.evaluationBadge._balloonView ).to.be.null;
+ } );
+
+ it( 'should (re-)show the balloon but throttled', async () => {
+ focusEditor( editor );
+
+ const pinSpy = testUtils.sinon.spy( editor.ui.evaluationBadge._balloonView, 'pin' );
+
+ editor.ui.fire( 'update' );
+ editor.ui.fire( 'update' );
+
+ sinon.assert.notCalled( pinSpy );
+
+ await wait( 75 );
+
+ sinon.assert.calledOnce( pinSpy );
+ sinon.assert.calledWith( pinSpy.firstCall, sinon.match.has( 'target', editor.editing.view.getDomRoot() ) );
+ } );
+
+ it( 'should (re-)show the balloon if the focus is not in the editing root but in other editor UI', async () => {
+ const focusableEditorUIElement = document.createElement( 'input' );
+ focusableEditorUIElement.type = 'text';
+ editor.ui.focusTracker.add( focusableEditorUIElement );
+ document.body.appendChild( focusableEditorUIElement );
+
+ focusEditor( editor, focusableEditorUIElement );
+
+ const pinSpy = testUtils.sinon.spy( editor.ui.evaluationBadge._balloonView, 'pin' );
+
+ sinon.assert.notCalled( pinSpy );
+
+ editor.ui.fire( 'update' );
+ editor.ui.fire( 'update' );
+
+ sinon.assert.calledOnce( pinSpy );
+
+ await wait( 75 );
+
+ sinon.assert.calledTwice( pinSpy );
+ sinon.assert.calledWith( pinSpy, sinon.match.has( 'target', editor.editing.view.getDomRoot() ) );
+ focusableEditorUIElement.remove();
+ } );
+ } );
+
+ describe( 'balloon view', () => {
+ let balloon, focusTrackerAddSpy;
+
+ beforeEach( () => {
+ focusTrackerAddSpy = testUtils.sinon.spy( editor.ui.focusTracker, 'add' );
+
+ focusEditor( editor );
+
+ balloon = editor.ui.evaluationBadge._balloonView;
+ } );
+
+ it( 'should be an instance of BalloonPanelView', () => {
+ expect( balloon ).to.be.instanceOf( BalloonPanelView );
+ } );
+
+ it( 'should host an evaluation badge view', () => {
+ expect( balloon.content.first ).to.be.instanceOf( View );
+ } );
+
+ it( 'should have no arrow', () => {
+ expect( balloon.withArrow ).to.be.false;
+ } );
+
+ it( 'should have a specific CSS class', () => {
+ expect( balloon.class ).to.equal( 'ck-evaluation-badge-balloon' );
+ } );
+
+ it( 'should be added to editor\'s body view collection', () => {
+ expect( editor.ui.view.body.has( balloon ) ).to.be.true;
+ } );
+
+ it( 'should be registered in the focus tracker to avoid focus loss on click', () => {
+ sinon.assert.calledWith( focusTrackerAddSpy, balloon.element );
+ } );
+ } );
+
+ describe( 'evaluation badge view', () => {
+ let view;
+
+ beforeEach( () => {
+ focusEditor( editor );
+
+ view = editor.ui.evaluationBadge._balloonView.content.first;
+ } );
+
+ it( 'should have specific CSS classes', () => {
+ expect( view.element.classList.contains( 'ck' ) ).to.be.true;
+ expect( view.element.classList.contains( 'ck-evaluation-badge' ) ).to.be.true;
+ } );
+
+ it( 'should be excluded from the accessibility tree', () => {
+ expect( view.element.getAttribute( 'aria-hidden' ) ).to.equal( 'true' );
+ } );
+
+ it( 'should not be accessible via tab key navigation', () => {
+ expect( view.element.firstChild.tabIndex ).to.equal( -1 );
+ } );
+ } );
+ } );
+
+ describe( 'destroy()', () => {
+ describe( 'if there was a balloon', () => {
+ beforeEach( () => {
+ focusEditor( editor );
+ } );
+
+ it( 'should unpin the balloon', () => {
+ const unpinSpy = testUtils.sinon.spy( editor.ui.evaluationBadge._balloonView, 'unpin' );
+
+ editor.destroy();
+
+ sinon.assert.calledOnce( unpinSpy );
+ } );
+
+ it( 'should destroy the balloon', () => {
+ const destroySpy = testUtils.sinon.spy( editor.ui.evaluationBadge._balloonView, 'destroy' );
+
+ editor.destroy();
+
+ sinon.assert.called( destroySpy );
+
+ expect( editor.ui.evaluationBadge._balloonView ).to.be.null;
+ } );
+
+ it( 'should cancel any throttled show to avoid post-destroy timed errors', () => {
+ const spy = testUtils.sinon.spy( editor.ui.evaluationBadge._showBalloonThrottled, 'cancel' );
+
+ editor.destroy();
+
+ sinon.assert.calledOnce( spy );
+ } );
+ } );
+
+ describe( 'if there was no balloon', () => {
+ it( 'should not throw', () => {
+ expect( () => {
+ editor.destroy();
+ } ).to.not.throw();
+ } );
+ } );
+
+ it( 'should destroy the emitter listeners', () => {
+ const spy = testUtils.sinon.spy( editor.ui.evaluationBadge, 'stopListening' );
+
+ editor.destroy();
+
+ sinon.assert.calledOnce( spy );
+ } );
+ } );
+
+ describe( 'balloon positioning depending on environment and configuration', () => {
+ const originalGetVisible = Rect.prototype.getVisible;
+ let rootRect, balloonRect;
+
+ beforeEach( () => {
+ rootRect = new Rect( { top: 0, left: 0, width: 400, right: 400, bottom: 100, height: 100 } );
+ balloonRect = new Rect( { top: 0, left: 0, width: 20, right: 20, bottom: 10, height: 10 } );
+ } );
+
+ it( 'should not show the balloon if the root is not visible vertically', async () => {
+ const domRoot = editor.editing.view.getDomRoot();
+ const parentWithOverflow = document.createElement( 'div' );
+
+ parentWithOverflow.style.overflow = 'scroll';
+ // Is not enough height to be visible vertically.
+ parentWithOverflow.style.height = '99px';
+
+ document.body.appendChild( parentWithOverflow );
+ parentWithOverflow.appendChild( domRoot );
+
+ focusEditor( editor );
+
+ expect( editor.ui.evaluationBadge._balloonView.isVisible ).to.be.true;
+ expect( editor.ui.evaluationBadge._balloonView.position ).to.equal( 'arrowless' );
+
+ parentWithOverflow.remove();
+ } );
+
+ it( 'should not show the balloon if the root is not visible horizontally', async () => {
+ const domRoot = editor.editing.view.getDomRoot();
+ const parentWithOverflow = document.createElement( 'div' );
+
+ parentWithOverflow.style.overflow = 'scroll';
+ // Is not enough width to be visible horizontally.
+ parentWithOverflow.style.width = '399px';
+
+ document.body.appendChild( parentWithOverflow );
+ parentWithOverflow.appendChild( domRoot );
+
+ focusEditor( editor );
+
+ expect( editor.ui.evaluationBadge._balloonView.isVisible ).to.be.true;
+ expect( editor.ui.evaluationBadge._balloonView.position ).to.equal( 'arrowless' );
+
+ parentWithOverflow.remove();
+ } );
+
+ it( 'should position the badge to the left right if the UI language is RTL (and powered-by is on the left)', async () => {
+ const editor = await createEditor( element, {
+ language: 'ar',
+ licenseKey: developmentLicenseKey
+ } );
+
+ testUtils.sinon.stub( editor.ui.getEditableElement( 'main' ), 'getBoundingClientRect' ).returns( {
+ top: 0,
+ left: 0,
+ right: 400,
+ width: 400,
+ bottom: 100,
+ height: 100
+ } );
+
+ focusEditor( editor );
+
+ const pinSpy = testUtils.sinon.spy( editor.ui.evaluationBadge._balloonView, 'pin' );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ sinon.assert.calledOnce( pinSpy );
+
+ const pinArgs = pinSpy.firstCall.args[ 0 ];
+ const positioningFunction = pinArgs.positions[ 0 ];
+
+ expect( pinArgs.target ).to.equal( editor.editing.view.getDomRoot() );
+ expect( positioningFunction( rootRect, balloonRect ) ).to.deep.equal( {
+ top: 95,
+ left: 375,
+ name: 'position_border-side_right',
+ config: {
+ withArrow: false
+ }
+ } );
+
+ await editor.destroy();
+ } );
+
+ it( 'should position the balloon in the lower left corner by default', async () => {
+ focusEditor( editor );
+
+ const pinSpy = testUtils.sinon.spy( editor.ui.evaluationBadge._balloonView, 'pin' );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ sinon.assert.calledOnce( pinSpy );
+
+ const pinArgs = pinSpy.firstCall.args[ 0 ];
+ const positioningFunction = pinArgs.positions[ 0 ];
+
+ expect( pinArgs.target ).to.equal( editor.editing.view.getDomRoot() );
+ expect( positioningFunction( rootRect, balloonRect ) ).to.deep.equal( {
+ top: 95,
+ left: 5,
+ name: 'position_border-side_left',
+ config: {
+ withArrow: false
+ }
+ } );
+ } );
+
+ it( 'should position the balloon in the lower right corner if poweredby is configured on the left', async () => {
+ const editor = await createEditor( element, {
+ ui: {
+ poweredBy: {
+ side: 'left'
+ }
+ },
+ licenseKey: developmentLicenseKey
+ } );
+
+ testUtils.sinon.stub( editor.ui.getEditableElement( 'main' ), 'getBoundingClientRect' ).returns( {
+ top: 0,
+ left: 0,
+ right: 400,
+ width: 400,
+ bottom: 100,
+ height: 100
+ } );
+
+ focusEditor( editor );
+
+ const pinSpy = testUtils.sinon.spy( editor.ui.evaluationBadge._balloonView, 'pin' );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ sinon.assert.calledOnce( pinSpy );
+
+ const pinArgs = pinSpy.firstCall.args[ 0 ];
+ const positioningFunction = pinArgs.positions[ 0 ];
+
+ expect( pinArgs.target ).to.equal( editor.editing.view.getDomRoot() );
+ expect( positioningFunction( rootRect, balloonRect ) ).to.deep.equal( {
+ top: 95,
+ left: 375,
+ name: 'position_border-side_right',
+ config: {
+ withArrow: false
+ }
+ } );
+
+ await editor.destroy();
+ } );
+
+ it( 'should position the balloon over the bottom root border if configured', async () => {
+ const editor = await createEditor( element, {
+ ui: {
+ poweredBy: {
+ position: 'border'
+ }
+ },
+ licenseKey: developmentLicenseKey
+ } );
+
+ testUtils.sinon.stub( editor.ui.getEditableElement( 'main' ), 'getBoundingClientRect' ).returns( {
+ top: 0,
+ left: 0,
+ right: 400,
+ width: 400,
+ bottom: 100,
+ height: 100
+ } );
+
+ focusEditor( editor );
+
+ const pinSpy = testUtils.sinon.spy( editor.ui.evaluationBadge._balloonView, 'pin' );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ sinon.assert.calledOnce( pinSpy );
+
+ const pinArgs = pinSpy.firstCall.args[ 0 ];
+ const positioningFunction = pinArgs.positions[ 0 ];
+
+ expect( pinArgs.target ).to.equal( editor.editing.view.getDomRoot() );
+ expect( positioningFunction( rootRect, balloonRect ) ).to.deep.equal( {
+ top: 95,
+ left: 5,
+ name: 'position_border-side_left',
+ config: {
+ withArrow: false
+ }
+ } );
+
+ await editor.destroy();
+ } );
+
+ it( 'should position the balloon in the corner of the root if configured', async () => {
+ const editor = await createEditor( element, {
+ ui: {
+ poweredBy: {
+ position: 'inside'
+ }
+ },
+ licenseKey: developmentLicenseKey
+ } );
+
+ testUtils.sinon.stub( editor.ui.getEditableElement( 'main' ), 'getBoundingClientRect' ).returns( {
+ top: 0,
+ left: 0,
+ right: 400,
+ width: 400,
+ bottom: 100,
+ height: 100
+ } );
+
+ focusEditor( editor );
+
+ const pinSpy = testUtils.sinon.spy( editor.ui.evaluationBadge._balloonView, 'pin' );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ sinon.assert.calledOnce( pinSpy );
+
+ const pinArgs = pinSpy.firstCall.args[ 0 ];
+ const positioningFunction = pinArgs.positions[ 0 ];
+
+ expect( pinArgs.target ).to.equal( editor.editing.view.getDomRoot() );
+ expect( positioningFunction( rootRect, balloonRect ) ).to.deep.equal( {
+ top: 90,
+ left: 5,
+ name: 'position_inside-side_left',
+ config: {
+ withArrow: false
+ }
+ } );
+
+ await editor.destroy();
+ } );
+
+ it( 'should hide the balloon if displayed over the bottom root border but partially cropped by an ancestor', async () => {
+ const editor = await createEditor( element, {
+ ui: {
+ poweredBy: {
+ position: 'border'
+ }
+ },
+ licenseKey: developmentLicenseKey
+ } );
+
+ const domRoot = editor.editing.view.getDomRoot();
+
+ rootRect = new Rect( { top: 0, left: 0, width: 100, right: 100, bottom: 10, height: 10 } );
+
+ focusEditor( editor );
+
+ const pinSpy = testUtils.sinon.spy( editor.ui.evaluationBadge._balloonView, 'pin' );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ sinon.assert.calledOnce( pinSpy );
+
+ const pinArgs = pinSpy.firstCall.args[ 0 ];
+ const positioningFunction = pinArgs.positions[ 0 ];
+
+ expect( pinArgs.target ).to.equal( domRoot );
+ expect( positioningFunction( rootRect, balloonRect ) ).to.equal( null );
+
+ await editor.destroy();
+ } );
+
+ it( 'should hide the balloon if displayed in the corner of the root but partially cropped by an ancestor', async () => {
+ const editor = await createEditor( element, {
+ ui: {
+ poweredBy: {
+ position: 'inside'
+ }
+ },
+ licenseKey: developmentLicenseKey
+ } );
+
+ rootRect = new Rect( { top: 0, left: 0, width: 400, right: 400, bottom: 200, height: 200 } );
+
+ testUtils.sinon.stub( rootRect, 'getVisible' ).returns( { top: 0, left: 0, width: 400, right: 400, bottom: 10, height: 10 } );
+
+ balloonRect = new Rect( { top: 200, left: 0, width: 20, right: 20, bottom: 210, height: 10 } );
+
+ const domRoot = editor.editing.view.getDomRoot();
+
+ focusEditor( editor );
+
+ const pinSpy = testUtils.sinon.spy( editor.ui.evaluationBadge._balloonView, 'pin' );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ sinon.assert.calledOnce( pinSpy );
+
+ const pinArgs = pinSpy.firstCall.args[ 0 ];
+ const positioningFunction = pinArgs.positions[ 0 ];
+
+ expect( pinArgs.target ).to.equal( domRoot );
+ expect( positioningFunction( rootRect, balloonRect ) ).to.equal( null );
+
+ await editor.destroy();
+ } );
+
+ it( 'should not display the balloon if the root is narrower than 350px', async () => {
+ const domRoot = editor.editing.view.getDomRoot();
+
+ testUtils.sinon.stub( Rect.prototype, 'getVisible' ).callsFake( function() {
+ if ( this._source === domRoot ) {
+ return new Rect( domRoot );
+ } else {
+ return originalGetVisible.call( this );
+ }
+ } );
+
+ domRoot.getBoundingClientRect.returns( {
+ top: 0,
+ left: 0,
+ right: 349,
+ width: 349,
+ bottom: 100,
+ height: 100
+ } );
+
+ focusEditor( editor );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ const pinSpy = testUtils.sinon.spy( editor.ui.evaluationBadge._balloonView, 'pin' );
+
+ expect( editor.ui.evaluationBadge._balloonView.isVisible ).to.be.true;
+ expect( editor.ui.evaluationBadge._balloonView.position ).to.equal( 'arrowless' );
+
+ domRoot.getBoundingClientRect.returns( {
+ top: 0,
+ left: 0,
+ right: 350,
+ width: 350,
+ bottom: 100,
+ height: 100
+ } );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ expect( editor.ui.evaluationBadge._balloonView.isVisible ).to.be.true;
+ expect( editor.ui.evaluationBadge._balloonView.position ).to.equal( 'position_border-side_left' );
+
+ const pinArgs = pinSpy.firstCall.args[ 0 ];
+ const positioningFunction = pinArgs.positions[ 0 ];
+
+ expect( pinArgs.target ).to.equal( editor.editing.view.getDomRoot() );
+ expect( positioningFunction( rootRect, balloonRect ) ).to.deep.equal( {
+ top: 95,
+ left: 5,
+ name: 'position_border-side_left',
+ config: {
+ withArrow: false
+ }
+ } );
+ } );
+
+ it( 'should not display the balloon if the root is shorter than 50px', async () => {
+ const domRoot = editor.editing.view.getDomRoot();
+
+ testUtils.sinon.stub( Rect.prototype, 'getVisible' ).callsFake( function() {
+ if ( this._source === domRoot ) {
+ return new Rect( domRoot );
+ } else {
+ return originalGetVisible.call( this );
+ }
+ } );
+
+ domRoot.getBoundingClientRect.returns( {
+ top: 0,
+ left: 0,
+ right: 1000,
+ width: 1000,
+ bottom: 49,
+ height: 49
+ } );
+
+ focusEditor( editor );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ const pinSpy = testUtils.sinon.spy( editor.ui.evaluationBadge._balloonView, 'pin' );
+
+ expect( editor.ui.evaluationBadge._balloonView.isVisible ).to.be.true;
+ expect( editor.ui.evaluationBadge._balloonView.position ).to.equal( 'arrowless' );
+
+ domRoot.getBoundingClientRect.returns( {
+ top: 0,
+ left: 0,
+ right: 1000,
+ width: 1000,
+ bottom: 50,
+ height: 50
+ } );
+
+ editor.ui.fire( 'update' );
+
+ // Throttled #update listener.
+ await wait( 75 );
+
+ expect( editor.ui.evaluationBadge._balloonView.isVisible ).to.be.true;
+ expect( editor.ui.evaluationBadge._balloonView.position ).to.equal( 'position_border-side_left' );
+
+ const pinArgs = pinSpy.firstCall.args[ 0 ];
+ const positioningFunction = pinArgs.positions[ 0 ];
+
+ expect( pinArgs.target ).to.equal( editor.editing.view.getDomRoot() );
+ expect( positioningFunction( rootRect, balloonRect ) ).to.deep.equal( {
+ top: 45,
+ left: 5,
+ name: 'position_border-side_left',
+ config: {
+ withArrow: false
+ }
+ } );
+ } );
+ } );
+
+ it( 'should have the z-index lower than a regular BalloonPanelView instance', () => {
+ focusEditor( editor );
+
+ const balloonView = new BalloonPanelView();
+ balloonView.render();
+
+ const zIndexOfEvaluationBadgeBalloon = Number( getComputedStyle( editor.ui.evaluationBadge._balloonView.element ).zIndex );
+
+ document.body.appendChild( balloonView.element );
+
+ const zIndexOfRegularBalloon = Number( getComputedStyle( balloonView.element ).zIndex );
+
+ expect( zIndexOfEvaluationBadgeBalloon ).to.be.lessThan( zIndexOfRegularBalloon );
+
+ balloonView.element.remove();
+ balloonView.destroy();
+ } );
+
+ it( 'should not overlap a dropdown panel in a toolbar', async () => {
+ const editor = await createClassicEditor( element, {
+ toolbar: [ 'heading' ],
+ plugins: [ Heading ],
+ ui: {
+ poweredBy: {
+ position: 'inside'
+ }
+ },
+ licenseKey: developmentLicenseKey
+ } );
+
+ setData( editor.model, 'foo[]bar' );
+
+ focusEditor( editor );
+
+ const headingToolbarButton = editor.ui.view.toolbar.items
+ .find( item => item.buttonView && item.buttonView.label.startsWith( 'Heading' ) );
+
+ const evaluationBadgeElement = editor.ui.evaluationBadge._balloonView.element;
+
+ const evaluationBadgeElementGeometry = new Rect( evaluationBadgeElement );
+
+ const middleOfTheEvaluationBadgeCoords = {
+ x: ( evaluationBadgeElementGeometry.width / 2 ) + evaluationBadgeElementGeometry.left,
+ y: ( evaluationBadgeElementGeometry.height / 2 ) + evaluationBadgeElementGeometry.top
+ };
+
+ let elementFromPoint = document.elementFromPoint(
+ middleOfTheEvaluationBadgeCoords.x,
+ middleOfTheEvaluationBadgeCoords.y
+ );
+
+ expect( elementFromPoint.classList.contains( 'ck-evaluation-badge__label' ) ).to.be.true;
+
+ // show heading dropdown
+ headingToolbarButton.buttonView.fire( 'execute' );
+
+ elementFromPoint = document.elementFromPoint(
+ middleOfTheEvaluationBadgeCoords.x,
+ middleOfTheEvaluationBadgeCoords.y
+ );
+
+ expect( elementFromPoint.classList.contains( 'ck-button__label' ) ).to.be.true;
+
+ await editor.destroy();
+ } );
+
+ async function createEditor( element, config = {} ) {
+ return ClassicTestEditor.create( element, config );
+ }
+
+ async function createClassicEditor( element, config = {} ) {
+ return ClassicEditor.create( element, config );
+ }
+
+ function wait( time ) {
+ return new Promise( res => {
+ window.setTimeout( res, time );
+ } );
+ }
+
+ function focusEditor( editor, focusableUIElement ) {
+ if ( !focusableUIElement ) {
+ focusableUIElement = editor.editing.view.getDomRoot();
+ editor.editing.view.focus();
+ } else {
+ focusableUIElement.focus();
+ }
+
+ editor.ui.focusTracker.focusedElement = focusableUIElement;
+ editor.ui.focusTracker.isFocused = true;
+ }
+
+ function blurEditor( editor ) {
+ editor.ui.focusTracker.focusedElement = null;
+ editor.ui.focusTracker.isFocused = null;
+ }
+} );
diff --git a/packages/ckeditor5-ui/tests/editorui/poweredby.js b/packages/ckeditor5-ui/tests/editorui/poweredby.js
index fdcee1ccae4..253407483d2 100644
--- a/packages/ckeditor5-ui/tests/editorui/poweredby.js
+++ b/packages/ckeditor5-ui/tests/editorui/poweredby.js
@@ -7,16 +7,17 @@
import { Editor } from '@ckeditor/ckeditor5-core';
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js';
-import EditorUI from '../../src/editorui/editorui.js';
-import { BalloonPanelView } from '../../src/index.js';
-import View from '../../src/view.js';
-
import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor.js';
-import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils.js';
-import { Rect, global } from '@ckeditor/ckeditor5-utils';
import SourceEditing from '@ckeditor/ckeditor5-source-editing/src/sourceediting.js';
import Heading from '@ckeditor/ckeditor5-heading/src/heading.js';
+import { Rect, global } from '@ckeditor/ckeditor5-utils';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils.js';
import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model.js';
+import generateKey from '@ckeditor/ckeditor5-core/tests/_utils/generatelicensekey.js';
+
+import EditorUI from '../../src/editorui/editorui.js';
+import { BalloonPanelView } from '../../src/index.js';
+import View from '../../src/view.js';
describe( 'PoweredBy', () => {
let editor, element;
@@ -83,12 +84,42 @@ describe( 'PoweredBy', () => {
expect( editor.ui.poweredBy._balloonView ).to.be.instanceOf( BalloonPanelView );
} );
- it( 'should not create the balloon when a valid license key is configured', async () => {
+ it( 'should create the balloon when license is `GPL`', async () => {
+ const editor = await createEditor( element, {
+ licenseKey: 'GPL'
+ } );
+
+ expect( editor.ui.poweredBy._balloonView ).to.be.null;
+
+ focusEditor( editor );
+
+ expect( editor.ui.poweredBy._balloonView ).to.be.instanceOf( BalloonPanelView );
+
+ await editor.destroy();
+ } );
+
+ it( 'should create the balloon when license is invalid', async () => {
+ const showErrorStub = sinon.stub( ClassicTestEditor.prototype, '_showLicenseError' );
+
+ const editor = await createEditor( element, {
+ licenseKey: ''
+ } );
+
+ expect( editor.ui.poweredBy._balloonView ).to.be.null;
+
+ focusEditor( editor );
+
+ expect( editor.ui.poweredBy._balloonView ).to.be.instanceOf( BalloonPanelView );
+
+ await editor.destroy();
+
+ showErrorStub.restore();
+ } );
+
+ it( 'should not create the balloon when a white-label license key is configured', async () => {
+ const { licenseKey } = generateKey( { whiteLabel: true } );
const editor = await createEditor( element, {
- // eslint-disable-next-line max-len
- // https://github.com/ckeditor/ckeditor5/blob/226bf243d1eb8bae2d447f631d6f5d9961bc6541/packages/ckeditor5-utils/tests/verifylicense.js#L14
- // eslint-disable-next-line max-len
- licenseKey: 'dG9vZWFzZXRtcHNsaXVyb3JsbWlkbXRvb2Vhc2V0bXBzbGl1cm9ybG1pZG10b29lYXNldG1wc2xpdXJvcmxtaWRtLU1qQTBOREEyTVRJPQ=='
+ licenseKey
} );
expect( editor.ui.poweredBy._balloonView ).to.be.null;
@@ -100,12 +131,10 @@ describe( 'PoweredBy', () => {
await editor.destroy();
} );
- it( 'should create the balloon when a valid license key is configured and `forceVisible` is set to true', async () => {
+ it( 'should create the balloon when a white-label license key is configured and `forceVisible` is set to true', async () => {
+ const { licenseKey } = generateKey( { whiteLabel: true } );
const editor = await createEditor( element, {
- // eslint-disable-next-line max-len
- // https://github.com/ckeditor/ckeditor5/blob/226bf243d1eb8bae2d447f631d6f5d9961bc6541/packages/ckeditor5-utils/tests/verifylicense.js#L14
- // eslint-disable-next-line max-len
- licenseKey: 'dG9vZWFzZXRtcHNsaXVyb3JsbWlkbXRvb2Vhc2V0bXBzbGl1cm9ybG1pZG10b29lYXNldG1wc2xpdXJvcmxtaWRtLU1qQTBOREEyTVRJPQ==',
+ licenseKey,
ui: {
poweredBy: {
forceVisible: true
@@ -121,6 +150,21 @@ describe( 'PoweredBy', () => {
await editor.destroy();
} );
+
+ it( 'should create the balloon when a non-white-label license key is configured', async () => {
+ const { licenseKey } = generateKey();
+ const editor = await createEditor( element, {
+ licenseKey
+ } );
+
+ expect( editor.ui.poweredBy._balloonView ).to.be.null;
+
+ focusEditor( editor );
+
+ expect( editor.ui.poweredBy._balloonView ).to.be.instanceOf( BalloonPanelView );
+
+ await editor.destroy();
+ } );
} );
describe( 'balloon management on editor focus change', () => {
diff --git a/packages/ckeditor5-ui/theme/globals/_evaluationbadge.css b/packages/ckeditor5-ui/theme/globals/_evaluationbadge.css
new file mode 100644
index 00000000000..f0f18bd592a
--- /dev/null
+++ b/packages/ckeditor5-ui/theme/globals/_evaluationbadge.css
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+:root {
+ --ck-evaluation-badge-font-size: 7.5px;
+ --ck-evaluation-badge-line-height: 7.5px;
+ --ck-evaluation-badge-padding-vertical: 2px;
+ --ck-evaluation-badge-padding-horizontal: 4px;
+ --ck-evaluation-badge-text-color: hsl(0, 0%, 31%);
+ --ck-evaluation-badge-border-radius: var(--ck-border-radius);
+ --ck-evaluation-badge-background: hsl(0, 0%, 100%);
+ --ck-evaluation-badge-border-color: var(--ck-color-focus-border);
+}
+
+.ck.ck-balloon-panel.ck-evaluation-badge-balloon {
+ --ck-border-radius: var(--ck-evaluation-badge-border-radius);
+
+ box-shadow: none;
+ background: var(--ck-evaluation-badge-background);
+ min-height: unset;
+ z-index: calc( var(--ck-z-panel) - 1 );
+
+ & .ck.ck-evaluation-badge {
+ line-height: var(--ck-evaluation-badge-line-height);
+ padding: var(--ck-evaluation-badge-padding-vertical) var(--ck-evaluation-badge-padding-horizontal);
+
+ & .ck-evaluation-badge__label {
+ font-size: var(--ck-evaluation-badge-font-size);
+ letter-spacing: -.2px;
+ padding-left: 2px;
+ text-transform: uppercase;
+ font-weight: bold;
+ margin-right: 4px;
+ line-height: normal;
+ color: var(--ck-evaluation-badge-text-color);
+ }
+ }
+
+ &[class*="position_inside"] {
+ border-color: transparent;
+ }
+
+ &[class*="position_border"] {
+ border: var(--ck-focus-ring);
+ border-color: var(--ck-evaluation-badge-border-color);
+ }
+}
+
diff --git a/packages/ckeditor5-ui/theme/globals/globals.css b/packages/ckeditor5-ui/theme/globals/globals.css
index 68eaa1bef2a..01e9921a73f 100644
--- a/packages/ckeditor5-ui/theme/globals/globals.css
+++ b/packages/ckeditor5-ui/theme/globals/globals.css
@@ -7,3 +7,4 @@
@import "./_zindex.css";
@import "./_transition.css";
@import "./_poweredby.css";
+@import "./_evaluationbadge.css";
diff --git a/packages/ckeditor5-undo/docs/_snippets/features/undo-redo.js b/packages/ckeditor5-undo/docs/_snippets/features/undo-redo.js
index f244ead6033..1ffbd8895ca 100644
--- a/packages/ckeditor5-undo/docs/_snippets/features/undo-redo.js
+++ b/packages/ckeditor5-undo/docs/_snippets/features/undo-redo.js
@@ -50,7 +50,8 @@ ClassicEditor
},
table: {
contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-undo/docs/features/undo-redo.md b/packages/ckeditor5-undo/docs/features/undo-redo.md
index 8ca43f04958..85202071e05 100644
--- a/packages/ckeditor5-undo/docs/features/undo-redo.md
+++ b/packages/ckeditor5-undo/docs/features/undo-redo.md
@@ -36,17 +36,15 @@ The feature supports both toolbar buttons and {@link features/accessibility#keyb
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Undo } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
- // Load the plugin.
+ licenseKey: '', // Or 'GPL'.
plugins: [ Undo, /* ... */ ],
-
- // Display the "Undo" and "Redo" buttons in the toolbar.
toolbar: [ 'undo', 'redo', /* ... */ ],
} )
.then( /* ... */ )
diff --git a/packages/ckeditor5-upload/docs/_snippets/features/base64-upload.js b/packages/ckeditor5-upload/docs/_snippets/features/base64-upload.js
index f3596b82d21..07384c21594 100644
--- a/packages/ckeditor5-upload/docs/_snippets/features/base64-upload.js
+++ b/packages/ckeditor5-upload/docs/_snippets/features/base64-upload.js
@@ -18,7 +18,8 @@ ClassicEditor
viewportOffset: {
top: window.getViewportTopOffsetConfig()
}
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-upload/docs/features/base64-upload-adapter.md b/packages/ckeditor5-upload/docs/features/base64-upload-adapter.md
index 4d1d80774af..fb8644bffdc 100644
--- a/packages/ckeditor5-upload/docs/features/base64-upload-adapter.md
+++ b/packages/ckeditor5-upload/docs/features/base64-upload-adapter.md
@@ -35,13 +35,14 @@ Use the editor below to see the adapter in action. Open the web browser console
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, Base64UploadAdapter } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Base64UploadAdapter, /* ... */ ],
toolbar: [ /* ... */ ]
} )
diff --git a/packages/ckeditor5-upload/docs/features/simple-upload-adapter.md b/packages/ckeditor5-upload/docs/features/simple-upload-adapter.md
index 9a346020376..d4212c9e914 100644
--- a/packages/ckeditor5-upload/docs/features/simple-upload-adapter.md
+++ b/packages/ckeditor5-upload/docs/features/simple-upload-adapter.md
@@ -17,17 +17,18 @@ The simple upload adapter lets you upload images to your server using the [`XMLH
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, SimpleUploadAdapter } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ SimpleUploadAdapter, /* ... */ ],
toolbar: [ /* ... */ ],
simpleUpload: {
- // Feature configuration.
+ // Configuration.
}
} )
.then( /* ... */ )
@@ -39,12 +40,9 @@ ClassicEditor
The client side of this feature is configurable using the {@link module:upload/uploadconfig~SimpleUploadConfig `config.simpleUpload`} object.
```js
-import { ClassicEditor, SimpleUploadAdapter } from 'ckeditor5';
-
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ SimpleUploadAdapter, /* ... */ ],
- toolbar: [ /* ... */ ],
+ // ... Other configuration options ...
simpleUpload: {
// The URL that the images are uploaded to.
uploadUrl: 'http://example.com',
diff --git a/packages/ckeditor5-utils/src/crc32.ts b/packages/ckeditor5-utils/src/crc32.ts
new file mode 100644
index 00000000000..4734b638a5d
--- /dev/null
+++ b/packages/ckeditor5-utils/src/crc32.ts
@@ -0,0 +1,80 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module utils/crc32
+ */
+
+/**
+ * Generates a CRC lookup table.
+ * This function creates and returns a 256-element array of pre-computed CRC values for quick CRC calculation.
+ * It uses the polynomial 0xEDB88320 to compute each value in the loop, optimizing future CRC calculations.
+ */
+function makeCrcTable(): Array {
+ const crcTable: Array = [];
+
+ for ( let n = 0; n < 256; n++ ) {
+ let c: number = n;
+
+ for ( let k = 0; k < 8; k++ ) {
+ if ( c & 1 ) {
+ c = 0xEDB88320 ^ ( c >>> 1 );
+ } else {
+ c = c >>> 1;
+ }
+ }
+
+ crcTable[ n ] = c;
+ }
+
+ return crcTable;
+}
+
+/**
+ * Calculates CRC-32 checksum for a given inputData to verify the integrity of data.
+ *
+ * @param inputData Accepts a single value (string, number, boolean), an array of strings, or an array of all of the above types.
+ * Non-string values are converted to strings before calculating the checksum.
+ * The checksum calculation is based on the concatenated string representation of the input values:
+ * * `crc32('foo')` is equivalent to `crc32(['foo'])`
+ * * `crc32(123)` is equivalent to `crc32(['123'])`
+ * * `crc32(true)` is equivalent to `crc32(['true'])`
+ * * `crc32(['foo', 123, true])` produces the same result as `crc32('foo123true')`
+ * * Nested arrays of strings are flattened, so `crc32([['foo', 'bar'], 'baz'])` is equivalent to `crc32(['foobar', 'baz'])`
+ *
+ * @returns The CRC-32 checksum, returned as a hexadecimal string.
+ */
+export default function crc32( inputData: CRCData ): string {
+ const dataArray = Array.isArray( inputData ) ? inputData : [ inputData ];
+ const crcTable: Array = makeCrcTable();
+ let crc: number = 0 ^ ( -1 );
+
+ // Convert data to a single string.
+ const dataString: string = dataArray.map( item => {
+ if ( Array.isArray( item ) ) {
+ return item.join( '' );
+ }
+
+ return String( item );
+ } ).join( '' );
+
+ // Calculate the CRC for the resulting string.
+ for ( let i = 0; i < dataString.length; i++ ) {
+ const byte: number = dataString.charCodeAt( i );
+ crc = ( crc >>> 8 ) ^ crcTable[ ( crc ^ byte ) & 0xFF ];
+ }
+
+ crc = ( crc ^ ( -1 ) ) >>> 0; // Force unsigned integer.
+
+ return crc.toString( 16 ).padStart( 8, '0' );
+}
+
+/**
+ * The input data for the CRC-32 checksum calculation.
+ * Can be a single value (string, number, boolean), an array of strings, or an array of all of the above types.
+ */
+export type CRCData = CRCValue | Array;
+
+type CRCValue = string | number | boolean | Array;
diff --git a/packages/ckeditor5-utils/src/index.ts b/packages/ckeditor5-utils/src/index.ts
index 0cc02b52f64..f6a32c94b1f 100644
--- a/packages/ckeditor5-utils/src/index.ts
+++ b/packages/ckeditor5-utils/src/index.ts
@@ -91,8 +91,9 @@ export { default as spliceArray } from './splicearray.js';
export { default as uid } from './uid.js';
export { default as delay, type DelayedFunc } from './delay.js';
-export { default as verifyLicense } from './verifylicense.js';
export { default as wait } from './wait.js';
+export { default as parseBase64EncodedObject } from './parsebase64encodedobject.js';
+export { default as crc32, type CRCData } from './crc32.js';
export * from './unicode.js';
diff --git a/packages/ckeditor5-utils/src/parsebase64encodedobject.ts b/packages/ckeditor5-utils/src/parsebase64encodedobject.ts
new file mode 100644
index 00000000000..a3ed488462a
--- /dev/null
+++ b/packages/ckeditor5-utils/src/parsebase64encodedobject.ts
@@ -0,0 +1,25 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module utils/parsebase64encodedobject
+ */
+
+/**
+ * Parses a base64-encoded object and returns the decoded object, or null if the decoding was unsuccessful.
+ */
+export default function parseBase64EncodedObject( encoded: string ): Record | null {
+ try {
+ if ( !encoded.startsWith( 'ey' ) ) {
+ return null;
+ }
+
+ const decoded = atob( encoded.replace( /-/g, '+' ).replace( /_/g, '/' ) );
+
+ return JSON.parse( decoded );
+ } catch ( e ) {
+ return null;
+ }
+}
diff --git a/packages/ckeditor5-utils/src/verifylicense.ts b/packages/ckeditor5-utils/src/verifylicense.ts
deleted file mode 100644
index 3b6a3dfd0fd..00000000000
--- a/packages/ckeditor5-utils/src/verifylicense.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-/**
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
-
-/**
- * @module utils/verifylicense
- */
-
-import { releaseDate } from './version.js';
-
-/**
- * Possible states of the key after verification.
- */
-export type VerifiedKeyStatus = 'VALID' | 'INVALID';
-
-/**
- * Checks whether the given string contains information that allows you to verify the license status.
- *
- * @param token The string to check.
- * @returns String that represents the state of given `token` parameter.
- */
-export default function verifyLicense( token: string | undefined ): VerifiedKeyStatus {
- // This function implements naive and partial license key check mechanism,
- // used only to decide whether to show or hide the "Powered by CKEditor" logo.
- //
- // You can read the reasoning behind showing the logo to unlicensed (GPL) users
- // in this thread: https://github.com/ckeditor/ckeditor5/issues/14082.
- //
- // We firmly believe in the values behind creating open-source software, even when that
- // means keeping the license verification logic open for everyone to see.
- //
- // Please keep this code intact. Thank you for your understanding.
-
- function oldTokenCheck( token: string ): VerifiedKeyStatus {
- if ( token.length >= 40 && token.length <= 255 ) {
- return 'VALID';
- } else {
- return 'INVALID';
- }
- }
-
- // TODO: issue ci#3175
-
- if ( !token ) {
- return 'INVALID';
- }
-
- let decryptedData = '';
-
- try {
- decryptedData = atob( token );
- } catch ( e ) {
- return 'INVALID';
- }
-
- const splittedDecryptedData = decryptedData.split( '-' );
-
- const firstElement = splittedDecryptedData[ 0 ];
- const secondElement = splittedDecryptedData[ 1 ];
-
- if ( !secondElement ) {
- return oldTokenCheck( token );
- }
-
- try {
- atob( secondElement );
- } catch ( e ) {
- try {
- atob( firstElement );
-
- if ( !atob( firstElement ).length ) {
- return oldTokenCheck( token );
- }
- } catch ( e ) {
- return oldTokenCheck( token );
- }
- }
-
- if ( firstElement.length < 40 || firstElement.length > 255 ) {
- return 'INVALID';
- }
-
- let decryptedSecondElement = '';
-
- try {
- atob( firstElement );
- decryptedSecondElement = atob( secondElement );
- } catch ( e ) {
- return 'INVALID';
- }
-
- if ( decryptedSecondElement.length !== 8 ) {
- return 'INVALID';
- }
-
- const year = Number( decryptedSecondElement.substring( 0, 4 ) );
- const monthIndex = Number( decryptedSecondElement.substring( 4, 6 ) ) - 1;
- const day = Number( decryptedSecondElement.substring( 6, 8 ) );
-
- const date = new Date( year, monthIndex, day );
-
- if ( date < releaseDate || isNaN( Number( date ) ) ) {
- return 'INVALID';
- }
-
- return 'VALID';
-}
diff --git a/packages/ckeditor5-utils/tests/crc32.js b/packages/ckeditor5-utils/tests/crc32.js
new file mode 100644
index 00000000000..b263fa85cca
--- /dev/null
+++ b/packages/ckeditor5-utils/tests/crc32.js
@@ -0,0 +1,99 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import crc32 from '../src/crc32.js';
+
+describe( 'crc32', () => {
+ describe( 'input is a single value (not an array)', () => {
+ it( 'should correctly calculate the CRC32 checksum for a string', () => {
+ const input = 'foo';
+ const expectedHex = '8c736521';
+ expect( crc32( input ) ).to.equal( expectedHex );
+ } );
+
+ it( 'should correctly calculate the CRC32 checksum for a number', () => {
+ const input = 123;
+ const expectedHex = '884863d2';
+ expect( crc32( input ) ).to.equal( expectedHex );
+ } );
+
+ it( 'should correctly calculate the CRC32 checksum for a boolean', () => {
+ const input = true;
+ const expectedHex = 'fdfc4c8d';
+ expect( crc32( input ) ).to.equal( expectedHex );
+ } );
+
+ it( 'should correctly calculate the CRC32 checksum for an empty string', () => {
+ const input = '';
+ const expectedHex = '00000000';
+ expect( crc32( input ) ).to.equal( expectedHex );
+ } );
+ } );
+
+ describe( 'input is an array', () => {
+ it( 'should correctly calculate the CRC32 checksum for a string', () => {
+ const input = [ 'foo' ];
+ const expectedHex = '8c736521';
+ expect( crc32( input ) ).to.equal( expectedHex );
+ } );
+
+ it( 'should correctly calculate the CRC32 checksum for a number', () => {
+ const input = [ 123 ];
+ const expectedHex = '884863d2';
+ expect( crc32( input ) ).to.equal( expectedHex );
+ } );
+
+ it( 'should correctly calculate the CRC32 checksum for a boolean', () => {
+ const input = [ true ];
+ const expectedHex = 'fdfc4c8d';
+ expect( crc32( input ) ).to.equal( expectedHex );
+ } );
+
+ it( 'should correctly calculate the CRC32 checksum for a table of strings', () => {
+ const input = [ 'foo', 'bar', 'baz' ];
+ const expectedHex = '1a7827aa';
+ expect( crc32( input ) ).to.equal( expectedHex );
+ } );
+
+ it( 'should handle mixed data types and compute a valid CRC32 checksum', () => {
+ const input = [ 'foo', 123, false, [ 'bar', 'baz' ] ];
+ const expectedHex = 'ee1795af';
+ expect( crc32( input ) ).to.equal( expectedHex );
+ } );
+
+ it( 'should correctly handle an empty array', () => {
+ const input = [];
+ const expectedHex = '00000000';
+ expect( crc32( input ) ).to.equal( expectedHex );
+ } );
+
+ it( 'should correctly handle arrays containing empty strings', () => {
+ const input = [ '', '', '' ];
+ const expectedHex = '00000000';
+ expect( crc32( input ) ).to.equal( expectedHex );
+ } );
+ } );
+
+ describe( 'return values', () => {
+ it( 'should return a hexadecimal string when returnHex is true', () => {
+ const input = [ 'foo' ];
+ const result = '8c736521';
+ expect( crc32( input ) ).to.equal( result );
+ } );
+
+ it( 'should return a hexadecimal string when returnHex is not set', () => {
+ const input = [ 'foo' ];
+ const result = '8c736521';
+ expect( crc32( input ) ).to.equal( result );
+ } );
+
+ it( 'should return consistent results for the same input', () => {
+ const input = [ 'foo', 'bar' ];
+ const firstRun = crc32( input );
+ const secondRun = crc32( input );
+ expect( firstRun ).to.equal( secondRun );
+ } );
+ } );
+} );
diff --git a/packages/ckeditor5-utils/tests/parsebase64encodedobject.js b/packages/ckeditor5-utils/tests/parsebase64encodedobject.js
new file mode 100644
index 00000000000..e673b4742b6
--- /dev/null
+++ b/packages/ckeditor5-utils/tests/parsebase64encodedobject.js
@@ -0,0 +1,36 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import parseBase64EncodedObject from '../src/parsebase64encodedobject.js';
+
+/* globals btoa */
+
+describe( 'parseBase64EncodedObject', () => {
+ it( 'should return a decoded object', () => {
+ const obj = { foo: 1 };
+ const encoded = btoa( JSON.stringify( obj ) );
+
+ expect( parseBase64EncodedObject( encoded ) ).to.deep.equal( obj );
+ } );
+
+ it( 'should return null if it is not an object', () => {
+ const str = 'foo';
+ const encoded = btoa( JSON.stringify( str ) );
+
+ expect( parseBase64EncodedObject( encoded ) ).to.be.null;
+ } );
+
+ it( 'should return null of it is not parsable', () => {
+ const encoded = btoa( '{"foo":1' );
+
+ expect( parseBase64EncodedObject( encoded ) ).to.be.null;
+ } );
+
+ it( 'should use base64Safe variant of encoding', () => {
+ const encoded = 'eyJmb28iOiJhYmNkZW/n+Glqa2xtbm8ifQ==';
+
+ expect( parseBase64EncodedObject( encoded ) ).to.deep.equal( { foo: 'abcdeoçøijklmno' } );
+ } );
+} );
diff --git a/packages/ckeditor5-utils/tests/verifylicense.js b/packages/ckeditor5-utils/tests/verifylicense.js
deleted file mode 100644
index f59fa30e394..00000000000
--- a/packages/ckeditor5-utils/tests/verifylicense.js
+++ /dev/null
@@ -1,114 +0,0 @@
-/**
- * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
-
-import verifyLicense from '../src/verifylicense.js';
-
-describe( 'utils', () => {
- describe( 'verify', () => {
- describe( 'should return `VALID`', () => {
- it( 'when date is later than the release date', () => {
- // new, future
- // eslint-disable-next-line max-len
- const string = 'dG9vZWFzZXRtcHNsaXVyb3JsbWlkbXRvb2Vhc2V0bXBzbGl1cm9ybG1pZG10b29lYXNldG1wc2xpdXJvcmxtaWRtLU1qQTBOREEyTVRJPQ==';
-
- expect( verifyLicense( string ) ).to.be.equal( 'VALID' );
- } );
-
- it( 'when old token format is given', () => {
- // old
- // eslint-disable-next-line max-len
- const string = 'YWZvb2JkcnphYXJhZWJvb290em9wbWJvbHJ1c21sZnJlYmFzdG1paXN1cm1tZmllenJhb2F0YmFvcmxvb3B6aWJvYWRiZWZzYXJ0bW9ibG8=';
-
- expect( verifyLicense( string ) ).to.be.equal( 'VALID' );
- } );
-
- it( 'when old token format is given with a special sign', () => {
- const string = 'LWRsZ2h2bWxvdWhnbXZsa3ZkaGdzZGhtdmxrc2htZ3Nma2xnaGxtcDk4N212Z3V3OTU4NHc5bWdtdw==';
-
- expect( verifyLicense( string ) ).to.be.equal( 'VALID' );
- } );
-
- it( 'when old token is splitted', () => {
- // eslint-disable-next-line max-len
- const string = 'ZXNybGl1aG1jbGlldWdtbHdpZWgvIUAjNW1nbGNlXVtcd2l1Z2NsZWpnbWNsc2lkZmdjbHNpZGZoZ2xjc2Rnc25jZGZnaGNubHMtd3A5bWN5dDlwaGdtcGM5d2g4dGc3Y3doODdvaGddW10hQCMhdG5jN293NTg0aGdjbzhud2U4Z2Nodw==';
-
- expect( verifyLicense( string ) ).to.be.equal( 'VALID' );
- } );
- } );
-
- describe( 'should return `INVALID`', () => {
- it( 'when token is empty', () => {
- expect( verifyLicense( '' ) ).to.be.equal( 'INVALID' );
- } );
-
- it( 'when token is not passed', () => {
- expect( verifyLicense( ) ).to.be.equal( 'INVALID' );
- } );
-
- describe( 'new', () => {
- it( 'first too short', () => {
- expect( verifyLicense( 'Wm05dlltRnktTWpBeU5UQXhNREU9' ) ).to.be.equal( 'INVALID' );
- } );
-
- it( 'first too long', () => {
- // eslint-disable-next-line max-len
- const string = 'YzNSbGJTQmxjbkp2Y2pvZ2JtVjBPanBGVWxKZlFreFBRMHRGUkY5Q1dWOURURWxGVGxSemRHVnRJR1Z5Y205eU9pQnVaWFE2T2tWU1VsOUNURTlEUzBWRVgwSlpYME5NU1VWT1ZITjBaVzBnWlhKeWIzSTZJRzVsZERvNlJWSlNYMEpNVDBOTFJVUmZRbGxmUTB4SlJVNVVjM1JsYlNCbGNuSnZjam9nYm1WME9qcEZVbEpmUWt4UFEwdEZSRjlDV1Y5RFRFbEZUbFJ6ZEdWdElHVnljbTl5T2lCdVpYUTZPa1ZTVWw5Q1RFOURTMFZFWDBKWlgwTk1TVVZPVkhOMFpXMGdaWEp5YjNJNklHNWxkRG82UlZKU1gwSk1UME5MUlVSZlFsbGZRMHhKUlU1VS1NakF5TlRBeE1ERT0=';
-
- expect( verifyLicense( string ) ).to.be.equal( 'INVALID' );
- } );
-
- it( 'first wrong format', () => {
- const string = 'ZGx1Z2hjbXNsaXVkZ2NobXN8IjolRVdFVnwifCJEVnxERyJXJSUkXkVSVHxWIll8UkRUIkJTfFIlQiItTWpBeU16RXlNekU9';
-
- expect( verifyLicense( string ) ).to.be.equal( 'INVALID' );
- } );
-
- it( 'when date is invalid', () => {
- // invalid = shorten than expected
-
- const string = 'enN6YXJ0YWxhYWZsaWViYnRvcnVpb3Jvb3BzYmVkYW9tcm1iZm9vbS1NVGs1TnpFeA==';
-
- expect( verifyLicense( string ) ).to.be.equal( 'INVALID' );
- } );
-
- it( 'wrong second part format', () => {
- const string = 'Ylc5MGIyeDBiMkZ5YzJsbGMybHNjR1Z0Y20xa2RXMXZkRzlzZEc5aGNuTnBaWE09LVptOXZZbUZ5WW1FPQ==';
-
- expect( verifyLicense( string ) ).to.be.equal( 'INVALID' );
- } );
-
- it( 'when wrong string passed', () => {
- // # instead of second part
- const string = 'Ylc5MGIyeDBiMkZ5YzJsbGMybHNjR1Z0Y20xa2RXMXZkRzlzZEc5aGNuTnBaWE09LSM=';
-
- expect( verifyLicense( string ) ).to.be.equal( 'INVALID' );
- } );
-
- it( 'when date is earlier than the release date', () => {
- // new, past
- // eslint-disable-next-line max-len
- const string = 'Y0dWc1lYVmxhWE4wYlc5a2MyMXBiRzEwY205eWIzQmxiR0YxWldsemRHMXZaSE50YVd4dGRISnZjbTlrYzJGa2MyRmtjMkU9LU1UazRNREF4TURFPQ==';
-
- expect( verifyLicense( string ) ).to.be.equal( 'INVALID' );
- } );
- } );
-
- describe( 'old', () => {
- it( 'when date is missing', () => {
- const string = 'dG1wb3Rhc2llc3VlbHJtZHJvbWlsbw==';
-
- expect( verifyLicense( string ) ).to.be.equal( 'INVALID' );
- } );
- } );
-
- it( 'when passed variable is invalid', () => {
- // invalid
- const string = 'foobarbaz';
-
- expect( verifyLicense( string ) ).to.be.equal( 'INVALID' );
- } );
- } );
- } );
-} );
diff --git a/packages/ckeditor5-watchdog/docs/features/watchdog.md b/packages/ckeditor5-watchdog/docs/features/watchdog.md
index 8b02bf5ca74..404b40376e3 100644
--- a/packages/ckeditor5-watchdog/docs/features/watchdog.md
+++ b/packages/ckeditor5-watchdog/docs/features/watchdog.md
@@ -31,7 +31,7 @@ There are two available types of watchdogs:
### Editor watchdog
-After {@link getting-started/quick-start installing the editor}, change your `ClassicEditor.create()` call to `watchdog.create()` as follows:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, change your `ClassicEditor.create()` call to `watchdog.create()` as follows:
```js
@@ -42,6 +42,7 @@ const watchdog = new EditorWatchdog( ClassicEditor );
// Create a new editor instance.
watchdog.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ Essentials, Paragraph, Bold, Italic ],
toolbar: [ 'bold', 'italic', 'alignment' ]
} );
@@ -139,7 +140,7 @@ watchdog.crashes.forEach( crashInfo => console.log( crashInfo ) );
### Context watchdog
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, ContextWatchdog, Bold, Italic, Context, Essentials, Paragraph } from 'ckeditor5';
diff --git a/packages/ckeditor5-word-count/docs/_snippets/features/build-word-count-source.js b/packages/ckeditor5-word-count/docs/_snippets/features/build-word-count-source.js
index bbfe163200c..a7ab54130e0 100644
--- a/packages/ckeditor5-word-count/docs/_snippets/features/build-word-count-source.js
+++ b/packages/ckeditor5-word-count/docs/_snippets/features/build-word-count-source.js
@@ -102,7 +102,8 @@ BalloonEditor.defaultConfig = {
]
},
// This value must be kept in sync with the language defined in webpack.config.js.
- language: 'en'
+ language: 'en',
+ licenseKey: 'GPL'
};
class ClassicEditor extends ClassicEditorBase {
@@ -150,7 +151,8 @@ ClassicEditor.defaultConfig = {
]
},
// This value must be kept in sync with the language defined in webpack.config.js.
- language: 'en'
+ language: 'en',
+ licenseKey: 'GPL'
};
ClassicEditor.builtinPlugins.push( WordCount );
diff --git a/packages/ckeditor5-word-count/docs/_snippets/features/word-count-update.js b/packages/ckeditor5-word-count/docs/_snippets/features/word-count-update.js
index 7a06b24aa59..3b99f011aa5 100644
--- a/packages/ckeditor5-word-count/docs/_snippets/features/word-count-update.js
+++ b/packages/ckeditor5-word-count/docs/_snippets/features/word-count-update.js
@@ -87,7 +87,8 @@ BalloonEditor
// If the character limit is exceeded, disable the send button.
sendButton.toggleAttribute( 'disabled', isLimitExceeded );
}
- }
+ },
+ licenseKey: 'GPL'
} )
.catch( err => {
console.error( err.stack );
diff --git a/packages/ckeditor5-word-count/docs/_snippets/features/word-count.js b/packages/ckeditor5-word-count/docs/_snippets/features/word-count.js
index fb582285200..0dbb29e99da 100644
--- a/packages/ckeditor5-word-count/docs/_snippets/features/word-count.js
+++ b/packages/ckeditor5-word-count/docs/_snippets/features/word-count.js
@@ -44,7 +44,8 @@ ClassicEditor
},
table: {
contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
- }
+ },
+ licenseKey: 'GPL'
} )
.then( editor => {
window.editor = editor;
diff --git a/packages/ckeditor5-word-count/docs/features/word-count.md b/packages/ckeditor5-word-count/docs/features/word-count.md
index 9c7a874e772..16443e81957 100644
--- a/packages/ckeditor5-word-count/docs/features/word-count.md
+++ b/packages/ckeditor5-word-count/docs/features/word-count.md
@@ -52,14 +52,18 @@ ClassicEditor
Starting with {@link updating/update-to-42 version 42.0.0}, we changed the format of import paths. This guide uses the new, shorter format. Refer to the {@link getting-started/legacy-getting-started/legacy-imports Packages in the legacy setup} guide if you use an older version of CKEditor 5.
-After {@link getting-started/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
+After {@link getting-started/integrations-cdn/quick-start installing the editor}, add the feature to your plugin list and toolbar configuration:
```js
import { ClassicEditor, WordCount } from 'ckeditor5';
ClassicEditor
.create( document.querySelector( '#editor' ), {
+ licenseKey: '', // Or 'GPL'.
plugins: [ WordCount, /* ... */ ],
+ wordCount: {
+ // Configuration.
+ }
} )
.then( /* ... */ )
.catch( /* ... */ );
@@ -101,7 +105,7 @@ You can execute your custom callback every time content statistics change by def
```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
- plugins: [ WordCount, /* ... */ ],
+ // ... Other configuration options ...
wordCount: {
onUpdate: stats => {
// Prints the current content statistics.