UNPKG

10.4 kBJavaScriptView Raw
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 */
8import ClassicEditorUI from './classiceditorui';
9import ClassicEditorUIView from './classiceditoruiview';
10import { Editor, Context, DataApiMixin, ElementApiMixin, attachToForm } from 'ckeditor5/src/core';
11import { getDataFromElement, CKEditorError } from 'ckeditor5/src/utils';
12import { ContextWatchdog, EditorWatchdog } from 'ckeditor5/src/watchdog';
13import { 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 */
37export 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 */
201ClassicEditor.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 */
207ClassicEditor.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 */
213ClassicEditor.ContextWatchdog = ContextWatchdog;
214function getInitialData(sourceElementOrData) {
215 return isElement(sourceElementOrData) ? getDataFromElement(sourceElementOrData) : sourceElementOrData;
216}
217function isElement(value) {
218 return _isElement(value);
219}