UNPKG

5.57 kBJavaScriptView Raw
1/**
2 * WordPress dependencies
3 */
4import { Component } from '@wordpress/element';
5import { __, _x } from '@wordpress/i18n';
6import { BACKSPACE, DELETE, F10 } from '@wordpress/keycodes';
7
8const { wp } = window;
9
10function isTmceEmpty( editor ) {
11 // When tinyMce is empty the content seems to be:
12 // <p><br data-mce-bogus="1"></p>
13 // avoid expensive checks for large documents
14 const body = editor.getBody();
15 if ( body.childNodes.length > 1 ) {
16 return false;
17 } else if ( body.childNodes.length === 0 ) {
18 return true;
19 }
20 if ( body.childNodes[ 0 ].childNodes.length > 1 ) {
21 return false;
22 }
23 return /^\n?$/.test( body.innerText || body.textContent );
24}
25
26export default class ClassicEdit extends Component {
27 constructor( props ) {
28 super( props );
29 this.initialize = this.initialize.bind( this );
30 this.onSetup = this.onSetup.bind( this );
31 this.focus = this.focus.bind( this );
32 }
33
34 componentDidMount() {
35 const { baseURL, suffix } = window.wpEditorL10n.tinymce;
36
37 window.tinymce.EditorManager.overrideDefaults( {
38 base_url: baseURL,
39 suffix,
40 } );
41
42 if ( document.readyState === 'complete' ) {
43 this.initialize();
44 } else {
45 window.addEventListener( 'DOMContentLoaded', this.initialize );
46 }
47 }
48
49 componentWillUnmount() {
50 window.addEventListener( 'DOMContentLoaded', this.initialize );
51 wp.oldEditor.remove( `editor-${ this.props.clientId }` );
52 }
53
54 componentDidUpdate( prevProps ) {
55 const { clientId, attributes: { content } } = this.props;
56
57 const editor = window.tinymce.get( `editor-${ clientId }` );
58
59 if ( prevProps.attributes.content !== content ) {
60 editor.setContent( content || '' );
61 }
62 }
63
64 initialize() {
65 const { clientId } = this.props;
66 const { settings } = window.wpEditorL10n.tinymce;
67 wp.oldEditor.initialize( `editor-${ clientId }`, {
68 tinymce: {
69 ...settings,
70 inline: true,
71 content_css: false,
72 fixed_toolbar_container: `#toolbar-${ clientId }`,
73 setup: this.onSetup,
74 },
75 } );
76 }
77
78 onSetup( editor ) {
79 const { attributes: { content }, setAttributes } = this.props;
80 const { ref } = this;
81 let bookmark;
82
83 this.editor = editor;
84
85 if ( content ) {
86 editor.on( 'loadContent', () => editor.setContent( content ) );
87 }
88
89 editor.on( 'blur', () => {
90 bookmark = editor.selection.getBookmark( 2, true );
91
92 setAttributes( {
93 content: editor.getContent(),
94 } );
95
96 editor.once( 'focus', () => {
97 if ( bookmark ) {
98 editor.selection.moveToBookmark( bookmark );
99 }
100 } );
101
102 return false;
103 } );
104
105 editor.on( 'mousedown touchstart', () => {
106 bookmark = null;
107 } );
108
109 editor.on( 'keydown', ( event ) => {
110 if ( ( event.keyCode === BACKSPACE || event.keyCode === DELETE ) && isTmceEmpty( editor ) ) {
111 // delete the block
112 this.props.onReplace( [] );
113 event.preventDefault();
114 event.stopImmediatePropagation();
115 }
116
117 const { altKey } = event;
118 /*
119 * Prevent Mousetrap from kicking in: TinyMCE already uses its own
120 * `alt+f10` shortcut to focus its toolbar.
121 */
122 if ( altKey && event.keyCode === F10 ) {
123 event.stopPropagation();
124 }
125 } );
126
127 // TODO: the following is for back-compat with WP 4.9, not needed in WP 5.0. Remove it after the release.
128 editor.addButton( 'kitchensink', {
129 tooltip: _x( 'More', 'button to expand options' ),
130 icon: 'dashicon dashicons-editor-kitchensink',
131 onClick() {
132 const button = this;
133 const active = ! button.active();
134
135 button.active( active );
136 editor.dom.toggleClass( ref, 'has-advanced-toolbar', active );
137 },
138 } );
139
140 // Show the second, third, etc. toolbars when the `kitchensink` button is removed by a plugin.
141 editor.on( 'init', function() {
142 if ( editor.settings.toolbar1 && editor.settings.toolbar1.indexOf( 'kitchensink' ) === -1 ) {
143 editor.dom.addClass( ref, 'has-advanced-toolbar' );
144 }
145 } );
146
147 editor.addButton( 'wp_add_media', {
148 tooltip: __( 'Insert Media' ),
149 icon: 'dashicon dashicons-admin-media',
150 cmd: 'WP_Medialib',
151 } );
152 // End TODO.
153
154 editor.on( 'init', () => {
155 const rootNode = this.editor.getBody();
156
157 // Create the toolbar by refocussing the editor.
158 if ( document.activeElement === rootNode ) {
159 rootNode.blur();
160 this.editor.focus();
161 }
162 } );
163 }
164
165 focus() {
166 if ( this.editor ) {
167 this.editor.focus();
168 }
169 }
170
171 onToolbarKeyDown( event ) {
172 // Prevent WritingFlow from kicking in and allow arrows navigation on the toolbar.
173 event.stopPropagation();
174 // Prevent Mousetrap from moving focus to the top toolbar when pressing `alt+f10` on this block toolbar.
175 event.nativeEvent.stopImmediatePropagation();
176 }
177
178 render() {
179 const { clientId } = this.props;
180
181 // Disable reason: the toolbar itself is non-interactive, but must capture
182 // events from the KeyboardShortcuts component to stop their propagation.
183 /* eslint-disable jsx-a11y/no-static-element-interactions */
184 return [
185 // Disable reason: Clicking on this visual placeholder should create
186 // the toolbar, it can also be created by focussing the field below.
187 /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
188 <div
189 key="toolbar"
190 id={ `toolbar-${ clientId }` }
191 ref={ ( ref ) => this.ref = ref }
192 className="block-library-classic__toolbar"
193 onClick={ this.focus }
194 data-placeholder={ __( 'Classic' ) }
195 onKeyDown={ this.onToolbarKeyDown }
196 />,
197 <div
198 key="editor"
199 id={ `editor-${ clientId }` }
200 className="wp-block-freeform block-library-rich-text__tinymce"
201 />,
202 ];
203 /* eslint-enable jsx-a11y/no-static-element-interactions */
204 }
205}