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 |
|
10 | import { EditorUI } from 'ckeditor5/src/core';
|
11 | import { enableToolbarKeyboardFocus, normalizeToolbarConfig } from 'ckeditor5/src/ui';
|
12 | import { enablePlaceholder } from 'ckeditor5/src/engine';
|
13 | import { ElementReplacer } from 'ckeditor5/src/utils';
|
14 |
|
15 | /**
|
16 | * The classic editor UI class.
|
17 | *
|
18 | * @extends module:core/editor/editorui~EditorUI
|
19 | */
|
20 | export 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 |
|