UNPKG

5.76 kBJavaScriptView Raw
1/**
2 * @license Copyright (c) 2003-2022, 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/**
7 * @module editor-classic/classiceditorui
8 */
9
10import { EditorUI } from 'ckeditor5/src/core';
11import { enableToolbarKeyboardFocus, normalizeToolbarConfig } from 'ckeditor5/src/ui';
12import { enablePlaceholder } from 'ckeditor5/src/engine';
13import { ElementReplacer } from 'ckeditor5/src/utils';
14
15/**
16 * The classic editor UI class.
17 *
18 * @extends module:core/editor/editorui~EditorUI
19 */
20export default class ClassicEditorUI extends EditorUI {
21 /**
22 * Creates an instance of the classic editor UI class.
23 *
24 * @param {module:core/editor/editor~Editor} editor The editor instance.
25 * @param {module:ui/editorui/editoruiview~EditorUIView} view The view of the UI.
26 */
27 constructor( editor, view ) {
28 super( editor );
29
30 /**
31 * The main (top–most) view of the editor UI.
32 *
33 * @readonly
34 * @member {module:ui/editorui/editoruiview~EditorUIView} #view
35 */
36 this.view = view;
37
38 /**
39 * A normalized `config.toolbar` object.
40 *
41 * @private
42 * @member {Object}
43 */
44 this._toolbarConfig = normalizeToolbarConfig( editor.config.get( 'toolbar' ) );
45
46 /**
47 * The element replacer instance used to hide the editor's source element.
48 *
49 * @protected
50 * @member {module:utils/elementreplacer~ElementReplacer}
51 */
52 this._elementReplacer = new ElementReplacer();
53 }
54
55 /**
56 * @inheritDoc
57 */
58 get element() {
59 return this.view.element;
60 }
61
62 /**
63 * Initializes the UI.
64 *
65 * @param {HTMLElement|null} replacementElement The DOM element that will be the source for the created editor.
66 */
67 init( replacementElement ) {
68 const editor = this.editor;
69 const view = this.view;
70 const editingView = editor.editing.view;
71 const editable = view.editable;
72 const editingRoot = editingView.document.getRoot();
73
74 // The editable UI and editing root should share the same name. Then name is used
75 // to recognize the particular editable, for instance in ARIA attributes.
76 editable.name = editingRoot.rootName;
77
78 view.render();
79
80 // The editable UI element in DOM is available for sure only after the editor UI view has been rendered.
81 // But it can be available earlier if a DOM element has been passed to BalloonEditor.create().
82 const editableElement = editable.element;
83
84 // Register the editable UI view in the editor. A single editor instance can aggregate multiple
85 // editable areas (roots) but the classic editor has only one.
86 this.setEditableElement( editable.name, editableElement );
87
88 // Let the global focus tracker know that the editable UI element is focusable and
89 // belongs to the editor. From now on, the focus tracker will sustain the editor focus
90 // as long as the editable is focused (e.g. the user is typing).
91 this.focusTracker.add( editableElement );
92
93 // Let the editable UI element respond to the changes in the global editor focus
94 // tracker. It has been added to the same tracker a few lines above but, in reality, there are
95 // many focusable areas in the editor, like balloons, toolbars or dropdowns and as long
96 // as they have focus, the editable should act like it is focused too (although technically
97 // it isn't), e.g. by setting the proper CSS class, visually announcing focus to the user.
98 // Doing otherwise will result in editable focus styles disappearing, once e.g. the
99 // toolbar gets focused.
100 view.editable.bind( 'isFocused' ).to( this.focusTracker );
101
102 // Bind the editable UI element to the editing view, making it an end– and entry–point
103 // of the editor's engine. This is where the engine meets the UI.
104 editingView.attachDomRoot( editableElement );
105
106 // If an element containing the initial data of the editor was provided, replace it with
107 // an editor instance's UI in DOM until the editor is destroyed. For instance, a <textarea>
108 // can be such element.
109 if ( replacementElement ) {
110 this._elementReplacer.replace( replacementElement, this.element );
111 }
112
113 this._initPlaceholder();
114 this._initToolbar();
115 this.fire( 'ready' );
116 }
117
118 /**
119 * @inheritDoc
120 */
121 destroy() {
122 const view = this.view;
123 const editingView = this.editor.editing.view;
124
125 this._elementReplacer.restore();
126 editingView.detachDomRoot( view.editable.name );
127 view.destroy();
128
129 super.destroy();
130 }
131
132 /**
133 * Initializes the editor toolbar.
134 *
135 * @private
136 */
137 _initToolbar() {
138 const editor = this.editor;
139 const view = this.view;
140 const editingView = editor.editing.view;
141
142 // Set–up the sticky panel with toolbar.
143 view.stickyPanel.bind( 'isActive' ).to( this.focusTracker, 'isFocused' );
144 view.stickyPanel.limiterElement = view.element;
145 view.stickyPanel.bind( 'viewportTopOffset' ).to( this, 'viewportOffset', ( { top } ) => top );
146
147 view.toolbar.fillFromConfig( this._toolbarConfig, this.componentFactory );
148
149 enableToolbarKeyboardFocus( {
150 origin: editingView,
151 originFocusTracker: this.focusTracker,
152 originKeystrokeHandler: editor.keystrokes,
153 toolbar: view.toolbar
154 } );
155 }
156
157 /**
158 * Enable the placeholder text on the editing root, if any was configured.
159 *
160 * @private
161 */
162 _initPlaceholder() {
163 const editor = this.editor;
164 const editingView = editor.editing.view;
165 const editingRoot = editingView.document.getRoot();
166 const sourceElement = editor.sourceElement;
167
168 const placeholderText = editor.config.get( 'placeholder' ) ||
169 sourceElement && sourceElement.tagName.toLowerCase() === 'textarea' && sourceElement.getAttribute( 'placeholder' );
170
171 if ( placeholderText ) {
172 enablePlaceholder( {
173 view: editingView,
174 element: editingRoot,
175 text: placeholderText,
176 isDirectHost: false,
177 keepOnFocus: true
178 } );
179 }
180 }
181}
182