>, element ) => {
+ const row = attributes[ element.props.id ].row as string;
+ acc[ row ] = acc[ row ] || [];
+ acc[ row ].push( element );
+
+ return acc;
+ }, {} )
+ );
+
+ return (
+ <>
+ Multi-root Editor Demo (rich integration)
+ This sample demonstrates a more advanced integration of the multi-root editor in React.
+
+ Multiple extra features are implemented to illustrate how you can customize your application and use the provided API.
+ They are optional, and you do not need to include them in your application.
+ However, they can be a good starting point for your own custom features.
+
+
+ The 'Simulate an error' button makes the editor throw an error to show you how it is restarted by
+ the Watchdog
mechanism.
+ Note, that Watchdog
is enabled by default.
+ It can be disabled by passing the `disableWatchdog` flag to the `useMultiRootEditor` hook.
+
+ Component's events are logged to the console.
+
+
+
+
+ Toggle read-only mode
+
+
+
+ Simulate an error
+
+
+
+
+ removeRoot( selectedRoot! ) }
+ disabled={ !selectedRoot }
+ >
+ Remove root
+
+
+ ) => {
+ setSelectedRoot( evt.target.value );
+ }}>
+ Select root to remove
+
+ { Object.keys( data ).map( rootName => (
+ { rootName }
+ ) ) }
+
+
+
+
+ addRoot( { row: 'section-1' } ) }
+ >
+ Add row with roots
+
+
+ Number( e.target.value ) <= 4 && setNumberOfRoots( Number( e.target.value ) )}
+ />
+
+
+
+
+ { toolbarElement }
+
+ { /* Maps through `groupedElements` array to render rows that contains the editor roots. */ }
+ { groupedElements.map( ( [ row, elements ] ) => (
+
+ { elements }
+
+ ) ) }
+ >
+ );
+} );
+
+export default MultiRootEditorRichDemo;
diff --git a/demos/cdn-multiroot-react/index.html b/demos/cdn-multiroot-react/index.html
new file mode 100644
index 00000000..f0066fe6
--- /dev/null
+++ b/demos/cdn-multiroot-react/index.html
@@ -0,0 +1,54 @@
+
+
+
+
+
+ CKEditor 5 via CDN – React Multi Root Component – demo
+
+
+
+
+
+
+
+
+
diff --git a/demos/multiroot-react/main.tsx b/demos/cdn-multiroot-react/main.tsx
similarity index 100%
rename from demos/multiroot-react/main.tsx
rename to demos/cdn-multiroot-react/main.tsx
diff --git a/demos/cdn-multiroot-react/useCKCdnMultiRootEditor.tsx b/demos/cdn-multiroot-react/useCKCdnMultiRootEditor.tsx
new file mode 100644
index 00000000..b156404e
--- /dev/null
+++ b/demos/cdn-multiroot-react/useCKCdnMultiRootEditor.tsx
@@ -0,0 +1,100 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import type { MultiRootEditor } from 'https://cdn.ckeditor.com/typings/ckeditor5.d.ts';
+import type { CKEditorCloudResult } from '../../src/index.js';
+
+export const useCKCdnMultiRootEditor = ( cloud: CKEditorCloudResult ): typeof MultiRootEditor => {
+ const {
+ MultiRootEditor: MultiRootEditorBase,
+ CloudServices,
+ Essentials,
+ CKFinderUploadAdapter,
+ Autoformat,
+ Bold,
+ Italic,
+ BlockQuote,
+ CKBox,
+ CKFinder,
+ EasyImage,
+ Heading,
+ Image,
+ ImageCaption,
+ ImageStyle,
+ ImageToolbar,
+ ImageUpload,
+ Indent,
+ Link,
+ List,
+ MediaEmbed,
+ Paragraph,
+ PasteFromOffice,
+ PictureEditing,
+ Table,
+ TableToolbar,
+ TextTransformation
+ } = cloud.CKEditor;
+
+ return class MultiRootEditor extends MultiRootEditorBase {
+ public static override builtinPlugins = [
+ Essentials,
+ CKFinderUploadAdapter,
+ Autoformat,
+ Bold,
+ Italic,
+ BlockQuote,
+ CKBox,
+ CKFinder,
+ CloudServices,
+ EasyImage,
+ Heading,
+ Image,
+ ImageCaption,
+ ImageStyle,
+ ImageToolbar,
+ ImageUpload,
+ Indent,
+ Link,
+ List,
+ MediaEmbed,
+ Paragraph,
+ PasteFromOffice,
+ PictureEditing,
+ Table,
+ TableToolbar,
+ TextTransformation
+ ];
+
+ public static override defaultConfig = {
+ toolbar: {
+ items: [
+ 'undo', 'redo',
+ '|', 'heading',
+ '|', 'bold', 'italic',
+ '|', 'link', 'uploadImage', 'insertTable', 'blockQuote', 'mediaEmbed',
+ '|', 'bulletedList', 'numberedList', 'outdent', 'indent'
+ ]
+ },
+ image: {
+ toolbar: [
+ 'imageStyle:inline',
+ 'imageStyle:block',
+ 'imageStyle:side',
+ '|',
+ 'toggleImageCaption',
+ 'imageTextAlternative'
+ ]
+ },
+ table: {
+ contentToolbar: [
+ 'tableColumn',
+ 'tableRow',
+ 'mergeTableCells'
+ ]
+ },
+ language: 'en'
+ };
+ };
+};
diff --git a/demos/cdn-react/App.tsx b/demos/cdn-react/App.tsx
new file mode 100644
index 00000000..3f4dfaf0
--- /dev/null
+++ b/demos/cdn-react/App.tsx
@@ -0,0 +1,58 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import React, { useState, type ReactNode } from 'react';
+
+import { CKEditorCloudDemo } from './CKEditorCloudDemo';
+import { CKEditorCloudPluginsDemo } from './CKEditorCloudPluginsDemo';
+import { CKEditorCKBoxCloudDemo } from './CKEditorCKBoxCloudDemo';
+import { CKEditorCloudContextDemo } from './CKEditorCloudContextDemo';
+
+const EDITOR_CONTENT = `
+ Sample
+ This is an instance of the
+ classic editor build .
+
+
+
+
+ You can use this sample to validate whether your
+ custom build works fine.
+`;
+
+const DEMOS = [ 'Editor', 'Context', 'CKBox', 'Cloud Plugins' ] as const;
+
+type Demo = ( typeof DEMOS )[ number ];
+
+export const App = (): ReactNode => {
+ const [ currentDemo, setCurrentDemo ] = useState( 'Editor' );
+
+ const content = ( {
+ Editor: ,
+ Context: ,
+ CKBox: ,
+ 'Cloud Plugins':
+ } )[ currentDemo ];
+
+ return (
+
+ CKEditor 5 – React Component – CDN demo
+
+
+ { DEMOS.map( demo => (
+ setCurrentDemo( demo ) }
+ disabled={ demo === currentDemo}
+ >
+ { demo } demo
+
+ ) ) }
+
+
+ { content }
+
+ );
+};
diff --git a/demos/cdn-react/CKEditorCKBoxCloudDemo.tsx b/demos/cdn-react/CKEditorCKBoxCloudDemo.tsx
new file mode 100644
index 00000000..2c25b8b0
--- /dev/null
+++ b/demos/cdn-react/CKEditorCKBoxCloudDemo.tsx
@@ -0,0 +1,75 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import React, { type ReactNode } from 'react';
+import { getCKCdnClassicEditor } from './getCKCdnClassicEditor.js';
+import { CKEditor, useCKEditorCloud } from '../../src/index.js';
+
+type CKEditorCKBoxCloudDemoProps = {
+ content: string;
+};
+
+export const CKEditorCKBoxCloudDemo = ( { content }: CKEditorCKBoxCloudDemoProps ): ReactNode => {
+ const cloud = useCKEditorCloud( {
+ version: '43.0.0',
+ premium: true,
+ ckbox: {
+ version: '2.5.1'
+ }
+ } );
+
+ if ( cloud.status === 'error' ) {
+ console.error( cloud );
+ }
+
+ if ( cloud.status !== 'success' ) {
+ return Loading...
;
+ }
+
+ const { CKBox, CKBoxImageEdit } = cloud.CKEditor;
+ const CKEditorClassic = getCKCdnClassicEditor( {
+ cloud,
+ additionalPlugins: [
+ CKBox,
+ CKBoxImageEdit
+ ],
+ overrideConfig: {
+ toolbar: {
+ items: [
+ 'undo', 'redo',
+ '|', 'heading',
+ '|', 'bold', 'italic',
+ '|', 'link', 'uploadImage', 'insertTable', 'blockQuote', 'mediaEmbed',
+ '|', 'bulletedList', 'numberedList', 'outdent', 'indent',
+ '|', 'ckbox', 'ckboxImageEdit'
+ ]
+ },
+ image: {
+ toolbar: [
+ 'imageStyle:inline',
+ 'imageStyle:block',
+ 'imageStyle:side',
+ '|',
+ 'toggleImageCaption',
+ 'imageTextAlternative',
+ '|',
+ 'ckboxImageEdit'
+ ]
+ },
+ ckbox: {
+ tokenUrl: 'https://api.ckbox.io/token/demo',
+ forceDemoLabel: true,
+ allowExternalImagesEditing: [ /^data:/, /^i.imgur.com\//, 'origin' ]
+ }
+ }
+ } );
+
+ return (
+
+ );
+};
diff --git a/demos/cdn-react/CKEditorCloudContextDemo.tsx b/demos/cdn-react/CKEditorCloudContextDemo.tsx
new file mode 100644
index 00000000..513bb60d
--- /dev/null
+++ b/demos/cdn-react/CKEditorCloudContextDemo.tsx
@@ -0,0 +1,128 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import React from 'react';
+import { CKEditor, CKEditorContext, useCKEditorCloud } from '../../src/index.js';
+
+export const CKEditorCloudContextDemo = (): JSX.Element => {
+ const cloud = useCKEditorCloud( {
+ version: '43.0.0',
+ premium: true
+ } );
+
+ if ( cloud.status === 'error' ) {
+ console.error( cloud );
+ return Error!
;
+ }
+
+ if ( cloud.status === 'loading' ) {
+ return Loading...
;
+ }
+
+ const { ClassicEditor } = cloud.CKEditor;
+
+ return (
+ {
+ console.log( 'Initialized editors:', editors );
+ } }
+ >
+
+
+
+
+
+
+ );
+};
+
+function CKEditorNestedInstanceDemo( { name, content }: { name: string; content?: string } ): JSX.Element {
+ const cloud = useCKEditorCloud( {
+ version: '43.0.0'
+ } );
+
+ if ( cloud.status === 'error' ) {
+ console.error( cloud );
+ return Error!
;
+ }
+
+ if ( cloud.status === 'loading' ) {
+ return Loading...
;
+ }
+
+ const { CKEditor: CK } = cloud;
+
+ return (
+
+ );
+}
diff --git a/demos/cdn-react/CKEditorCloudDemo.tsx b/demos/cdn-react/CKEditorCloudDemo.tsx
new file mode 100644
index 00000000..41c1e860
--- /dev/null
+++ b/demos/cdn-react/CKEditorCloudDemo.tsx
@@ -0,0 +1,38 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import React, { type ReactNode } from 'react';
+import { getCKCdnClassicEditor } from './getCKCdnClassicEditor.js';
+import { CKEditor, useCKEditorCloud } from '../../src/index.js';
+
+type CKEditorCloudDemoProps = {
+ content: string;
+};
+
+export const CKEditorCloudDemo = ( { content }: CKEditorCloudDemoProps ): ReactNode => {
+ const cloud = useCKEditorCloud( {
+ version: '43.0.0',
+ premium: true
+ } );
+
+ if ( cloud.status === 'error' ) {
+ console.error( cloud );
+ }
+
+ if ( cloud.status !== 'success' ) {
+ return Loading...
;
+ }
+
+ const CKEditorClassic = getCKCdnClassicEditor( {
+ cloud
+ } );
+
+ return (
+
+ );
+};
diff --git a/demos/cdn-react/CKEditorCloudPluginsDemo.tsx b/demos/cdn-react/CKEditorCloudPluginsDemo.tsx
new file mode 100644
index 00000000..cd3711df
--- /dev/null
+++ b/demos/cdn-react/CKEditorCloudPluginsDemo.tsx
@@ -0,0 +1,75 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import React, { type ReactNode } from 'react';
+
+import type { Plugin } from 'https://cdn.ckeditor.com/typings/ckeditor5.d.ts';
+
+import { getCKCdnClassicEditor } from './getCKCdnClassicEditor.js';
+import { CKEditor, useCKEditorCloud } from '../../src/index.js';
+
+type CKEditorCloudPluginsDemoProps = {
+ content: string;
+};
+
+declare global {
+ interface Window {
+ '@wiris/mathtype-ckeditor5': typeof Plugin;
+ }
+}
+
+export const CKEditorCloudPluginsDemo = ( { content }: CKEditorCloudPluginsDemoProps ): ReactNode => {
+ const cloud = useCKEditorCloud( {
+ version: '43.0.0',
+ languages: [ 'pl', 'de' ],
+ premium: true,
+ plugins: {
+ Wiris: {
+ scripts: [
+ 'https://www.wiris.net/demo/plugins/app/WIRISplugins.js',
+ 'https://cdn.jsdelivr.net/npm/@wiris/mathtype-ckeditor5@8.11.0/dist/browser/index.umd.js'
+ ],
+ stylesheets: [
+ 'https://cdn.jsdelivr.net/npm/@wiris/mathtype-ckeditor5@8.11.0/dist/browser/index.css'
+ ],
+ checkPluginLoaded: () => window[ '@wiris/mathtype-ckeditor5' ]
+ }
+ }
+ } );
+
+ if ( cloud.status === 'error' ) {
+ console.error( cloud );
+ }
+
+ if ( cloud.status !== 'success' ) {
+ return Loading...
;
+ }
+
+ const CKEditorClassic = getCKCdnClassicEditor( {
+ cloud,
+ additionalPlugins: [
+ cloud.loadedPlugins!.Wiris
+ ],
+ overrideConfig: {
+ toolbar: {
+ items: [
+ 'undo', 'redo',
+ '|', 'heading',
+ '|', 'bold', 'italic',
+ '|', 'link', 'uploadImage', 'insertTable', 'blockQuote', 'mediaEmbed',
+ '|', 'bulletedList', 'numberedList', 'outdent', 'indent',
+ '|', 'MathType', 'ChemType'
+ ]
+ }
+ }
+ } );
+
+ return (
+
+ );
+};
diff --git a/demos/cdn-react/getCKCdnClassicEditor.ts b/demos/cdn-react/getCKCdnClassicEditor.ts
new file mode 100644
index 00000000..9423ea13
--- /dev/null
+++ b/demos/cdn-react/getCKCdnClassicEditor.ts
@@ -0,0 +1,104 @@
+/**
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import type { ClassicEditor, Plugin, ContextPlugin, EditorConfig } from 'https://cdn.ckeditor.com/typings/ckeditor5.d.ts';
+import type { CKEditorCloudResult } from '../../src';
+
+type ClassicEditorCreatorConfig = {
+ cloud: CKEditorCloudResult;
+ additionalPlugins?: Array;
+ overrideConfig?: EditorConfig;
+};
+
+export const getCKCdnClassicEditor = ( {
+ cloud, additionalPlugins, overrideConfig
+}: ClassicEditorCreatorConfig ): typeof ClassicEditor => {
+ const {
+ ClassicEditor: ClassicEditorBase,
+ Essentials,
+ Autoformat,
+ Bold,
+ Italic,
+ BlockQuote,
+ CloudServices,
+ Heading,
+ Image,
+ ImageCaption,
+ ImageStyle,
+ ImageToolbar,
+ ImageUpload,
+ Indent,
+ Link,
+ List,
+ MediaEmbed,
+ Paragraph,
+ PasteFromOffice,
+ PictureEditing,
+ Table,
+ TableToolbar,
+ TextTransformation
+ } = cloud.CKEditor;
+
+ class CustomEditor extends ClassicEditorBase {
+ public static builtinPlugins = [
+ Essentials,
+ Autoformat,
+ Bold,
+ Italic,
+ BlockQuote,
+ Heading,
+ Image,
+ ImageCaption,
+ ImageStyle,
+ ImageToolbar,
+ ImageUpload,
+ Indent,
+ Link,
+ List,
+ MediaEmbed,
+ Paragraph,
+ PasteFromOffice,
+ PictureEditing,
+ Table,
+ TableToolbar,
+ TextTransformation,
+ CloudServices,
+ ...additionalPlugins || []
+ ];
+
+ public static defaultConfig = {
+ toolbar: {
+ items: [
+ 'undo', 'redo',
+ '|', 'heading',
+ '|', 'bold', 'italic',
+ '|', 'link', 'uploadImage', 'insertTable', 'blockQuote', 'mediaEmbed',
+ '|', 'bulletedList', 'numberedList', 'outdent', 'indent'
+ ]
+ },
+ image: {
+ toolbar: [
+ 'imageStyle:inline',
+ 'imageStyle:block',
+ 'imageStyle:side',
+ '|',
+ 'toggleImageCaption',
+ 'imageTextAlternative'
+ ]
+ },
+ table: {
+ contentToolbar: [
+ 'tableColumn',
+ 'tableRow',
+ 'mergeTableCells'
+ ]
+ },
+ language: 'en',
+ ...overrideConfig
+ };
+ }
+
+ return CustomEditor;
+};
diff --git a/demos/react/index.html b/demos/cdn-react/index.html
similarity index 75%
rename from demos/react/index.html
rename to demos/cdn-react/index.html
index ea4d67b2..c515a040 100644
--- a/demos/react/index.html
+++ b/demos/cdn-react/index.html
@@ -3,7 +3,7 @@
- CKEditor 5 – React Component – demo
+ CKEditor 5 via CDN – React Component – demo
+
+
+
+
+
+
+