1 | /**
|
2 | * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
3 | * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
4 | */
|
5 | /**
|
6 | * @module editor-classic/classiceditor
|
7 | */
|
8 | import ClassicEditorUI from './classiceditorui';
|
9 | import ClassicEditorUIView from './classiceditoruiview';
|
10 | import { Editor, Context, DataApiMixin, ElementApiMixin, attachToForm } from 'ckeditor5/src/core';
|
11 | import { getDataFromElement, CKEditorError } from 'ckeditor5/src/utils';
|
12 | import { ContextWatchdog, EditorWatchdog } from 'ckeditor5/src/watchdog';
|
13 | import { isElement as _isElement } from 'lodash-es';
|
14 | /**
|
15 | * The {@glink installation/getting-started/predefined-builds#classic-editor classic editor} implementation.
|
16 | * It uses an inline editable and a sticky toolbar, all enclosed in a boxed UI.
|
17 | * See the {@glink examples/builds/classic-editor demo}.
|
18 | *
|
19 | * In order to create a classic editor instance, use the static
|
20 | * {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`} method.
|
21 | *
|
22 | * # Classic editor and classic build
|
23 | *
|
24 | * The classic editor can be used directly from source (if you installed the
|
25 | * [`@ckeditor/ckeditor5-editor-classic`](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-classic) package)
|
26 | * but it is also available in the {@glink installation/getting-started/predefined-builds#classic-editor classic build}.
|
27 | *
|
28 | * {@glink installation/getting-started/predefined-builds Builds}
|
29 | * are ready-to-use editors with plugins bundled in. When using the editor from
|
30 | * source you need to take care of loading all plugins by yourself
|
31 | * (through the {@link module:core/editor/editorconfig~EditorConfig#plugins `config.plugins`} option).
|
32 | * Using the editor from source gives much better flexibility and allows easier customization.
|
33 | *
|
34 | * Read more about initializing the editor from source or as a build in
|
35 | * {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`}.
|
36 | */
|
37 | export default class ClassicEditor extends DataApiMixin(ElementApiMixin(Editor)) {
|
38 | /**
|
39 | * Creates an instance of the classic editor.
|
40 | *
|
41 | * **Note:** do not use the constructor to create editor instances. Use the static
|
42 | * {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`} method instead.
|
43 | *
|
44 | * @param sourceElementOrData The DOM element that will be the source for the created editor
|
45 | * or the editor's initial data. For more information see
|
46 | * {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`}.
|
47 | * @param config The editor configuration.
|
48 | */
|
49 | constructor(sourceElementOrData, config = {}) {
|
50 | // If both `config.initialData` is set and initial data is passed as the constructor parameter, then throw.
|
51 | if (!isElement(sourceElementOrData) && config.initialData !== undefined) {
|
52 | // Documented in core/editor/editorconfig.jsdoc.
|
53 | // eslint-disable-next-line ckeditor5-rules/ckeditor-error-message
|
54 | throw new CKEditorError('editor-create-initial-data', null);
|
55 | }
|
56 | super(config);
|
57 | if (this.config.get('initialData') === undefined) {
|
58 | this.config.set('initialData', getInitialData(sourceElementOrData));
|
59 | }
|
60 | if (isElement(sourceElementOrData)) {
|
61 | this.sourceElement = sourceElementOrData;
|
62 | }
|
63 | this.model.document.createRoot();
|
64 | const shouldToolbarGroupWhenFull = !this.config.get('toolbar.shouldNotGroupWhenFull');
|
65 | const view = new ClassicEditorUIView(this.locale, this.editing.view, {
|
66 | shouldToolbarGroupWhenFull
|
67 | });
|
68 | this.ui = new ClassicEditorUI(this, view);
|
69 | attachToForm(this);
|
70 | }
|
71 | /**
|
72 | * Destroys the editor instance, releasing all resources used by it.
|
73 | *
|
74 | * Updates the original editor element with the data if the
|
75 | * {@link module:core/editor/editorconfig~EditorConfig#updateSourceElementOnDestroy `updateSourceElementOnDestroy`}
|
76 | * configuration option is set to `true`.
|
77 | */
|
78 | destroy() {
|
79 | if (this.sourceElement) {
|
80 | this.updateSourceElement();
|
81 | }
|
82 | this.ui.destroy();
|
83 | return super.destroy();
|
84 | }
|
85 | /**
|
86 | * Creates a new classic editor instance.
|
87 | *
|
88 | * There are three ways how the editor can be initialized.
|
89 | *
|
90 | * # Replacing a DOM element (and loading data from it)
|
91 | *
|
92 | * You can initialize the editor using an existing DOM element:
|
93 | *
|
94 | * ```ts
|
95 | * ClassicEditor
|
96 | * .create( document.querySelector( '#editor' ) )
|
97 | * .then( editor => {
|
98 | * console.log( 'Editor was initialized', editor );
|
99 | * } )
|
100 | * .catch( err => {
|
101 | * console.error( err.stack );
|
102 | * } );
|
103 | * ```
|
104 | *
|
105 | * The element's content will be used as the editor data and the element will be replaced by the editor UI.
|
106 | *
|
107 | * # Creating a detached editor
|
108 | *
|
109 | * Alternatively, you can initialize the editor by passing the initial data directly as a string.
|
110 | * In this case, the editor will render an element that must be inserted into the DOM:
|
111 | *
|
112 | * ```ts
|
113 | * ClassicEditor
|
114 | * .create( '<p>Hello world!</p>' )
|
115 | * .then( editor => {
|
116 | * console.log( 'Editor was initialized', editor );
|
117 | *
|
118 | * // Initial data was provided so the editor UI element needs to be added manually to the DOM.
|
119 | * document.body.appendChild( editor.ui.element );
|
120 | * } )
|
121 | * .catch( err => {
|
122 | * console.error( err.stack );
|
123 | * } );
|
124 | * ```
|
125 | *
|
126 | * This lets you dynamically append the editor to your web page whenever it is convenient for you. You may use this method if your
|
127 | * web page content is generated on the client side and the DOM structure is not ready at the moment when you initialize the editor.
|
128 | *
|
129 | * # Replacing a DOM element (and data provided in `config.initialData`)
|
130 | *
|
131 | * You can also mix these two ways by providing a DOM element to be used and passing the initial data through the configuration:
|
132 | *
|
133 | * ```ts
|
134 | * ClassicEditor
|
135 | * .create( document.querySelector( '#editor' ), {
|
136 | * initialData: '<h2>Initial data</h2><p>Foo bar.</p>'
|
137 | * } )
|
138 | * .then( editor => {
|
139 | * console.log( 'Editor was initialized', editor );
|
140 | * } )
|
141 | * .catch( err => {
|
142 | * console.error( err.stack );
|
143 | * } );
|
144 | * ```
|
145 | *
|
146 | * This method can be used to initialize the editor on an existing element with the specified content in case if your integration
|
147 | * makes it difficult to set the content of the source element.
|
148 | *
|
149 | * Note that an error will be thrown if you pass the initial data both as the first parameter and also in the configuration.
|
150 | *
|
151 | * # Configuring the editor
|
152 | *
|
153 | * See the {@link module:core/editor/editorconfig~EditorConfig editor configuration documentation} to learn more about
|
154 | * customizing plugins, toolbar and more.
|
155 | *
|
156 | * # Using the editor from source
|
157 | *
|
158 | * The code samples listed in the previous sections of this documentation assume that you are using an
|
159 | * {@glink installation/getting-started/predefined-builds editor build} (for example – `@ckeditor/ckeditor5-build-classic`).
|
160 | *
|
161 | * If you want to use the classic editor from source (`@ckeditor/ckeditor5-editor-classic/src/classiceditor`),
|
162 | * you need to define the list of
|
163 | * {@link module:core/editor/editorconfig~EditorConfig#plugins plugins to be initialized} and
|
164 | * {@link module:core/editor/editorconfig~EditorConfig#toolbar toolbar items}. Read more about using the editor from
|
165 | * source in the {@glink installation/advanced/alternative-setups/integrating-from-source-webpack dedicated guide}.
|
166 | *
|
167 | * @param sourceElementOrData The DOM element that will be the source for the created editor
|
168 | * or the editor's initial data.
|
169 | *
|
170 | * If a DOM element is passed, its content will be automatically loaded to the editor upon initialization
|
171 | * and the {@link module:editor-classic/classiceditorui~ClassicEditorUI#element editor element} will replace the passed element
|
172 | * in the DOM (the original one will be hidden and the editor will be injected next to it).
|
173 | *
|
174 | * If the {@link module:core/editor/editorconfig~EditorConfig#updateSourceElementOnDestroy updateSourceElementOnDestroy}
|
175 | * option is set to `true`, the editor data will be set back to the original element once the editor is destroyed and when a form,
|
176 | * in which this element is contained, is submitted (if the original element is a `<textarea>`). This ensures seamless integration
|
177 | * with native web forms.
|
178 | *
|
179 | * If the initial data is passed, a detached editor will be created. In this case you need to insert it into the DOM manually.
|
180 | * It is available under the {@link module:editor-classic/classiceditorui~ClassicEditorUI#element `editor.ui.element`} property.
|
181 | *
|
182 | * @param config The editor configuration.
|
183 | * @returns A promise resolved once the editor is ready. The promise resolves with the created editor instance.
|
184 | */
|
185 | static create(sourceElementOrData, config = {}) {
|
186 | return new Promise(resolve => {
|
187 | const editor = new this(sourceElementOrData, config);
|
188 | resolve(editor.initPlugins()
|
189 | .then(() => editor.ui.init(isElement(sourceElementOrData) ? sourceElementOrData : null))
|
190 | .then(() => editor.data.init(editor.config.get('initialData')))
|
191 | .then(() => editor.fire('ready'))
|
192 | .then(() => editor));
|
193 | });
|
194 | }
|
195 | }
|
196 | /**
|
197 | * The {@link module:core/context~Context} class.
|
198 | *
|
199 | * Exposed as static editor field for easier access in editor builds.
|
200 | */
|
201 | ClassicEditor.Context = Context;
|
202 | /**
|
203 | * The {@link module:watchdog/editorwatchdog~EditorWatchdog} class.
|
204 | *
|
205 | * Exposed as static editor field for easier access in editor builds.
|
206 | */
|
207 | ClassicEditor.EditorWatchdog = EditorWatchdog;
|
208 | /**
|
209 | * The {@link module:watchdog/contextwatchdog~ContextWatchdog} class.
|
210 | *
|
211 | * Exposed as static editor field for easier access in editor builds.
|
212 | */
|
213 | ClassicEditor.ContextWatchdog = ContextWatchdog;
|
214 | function getInitialData(sourceElementOrData) {
|
215 | return isElement(sourceElementOrData) ? getDataFromElement(sourceElementOrData) : sourceElementOrData;
|
216 | }
|
217 | function isElement(value) {
|
218 | return _isElement(value);
|
219 | }
|