I’m new to stack exchange. I hope my question is not to long.
I want to integrate an inline tinymce editor in a component and use it in a gutenberg block. But my editors ui conflicts with gutenberg. (first screenshot)
The editor-element is inside a backbone view. I can’t use the RichText editor component in my case.
Background:
I‘m developing a plugin to insert leaflet maps into a post https://github.com/jhotadhari/geo-masala
The Plugin has a Gutenberg block to insert and control a map and its appearance/popups/controls/markers… .
Map-Features (Markers/Lines/Polygons) are saved as a custom-post-type: geom_feature. Each geom_feature has meta for geoJSON, appearance…
The custom-post-type is registered without ui. All crud actions for geom_feature(s) are done within a gutenberg block (eg. drawing a marker creates a new geom_feature)
Inside the block, one component renders a leaflet map into a ref element. The component uses backbone to fetch the geom_feature(s) as a collection.
When a Map-Feature (Leaflet.layer) is clicked, a toolbar opens with different editing options. The popup content can be edited within a popup (using a backform form with a custom wysiwyg field). The popup content is the post-content of the geom_feature.
The custom wysiwyg field uses a tinymce inline editor. All (eg updating new content to database) works fine, but the ui of that editor breaks. The editor toolbar and widgets are not in place.
I can init the editor with a fixed_toolbar_container. Like this:
initMceEditor: function(){
let self = this;
if ( this.mceEditor )
return this;
// setup editor element
this.getMceElement().attr( 'id', this.cid );
// setup toolbar element
this.setupToolbar();
let settings = _.extend( {}, wp.editor.getDefaultSettings().tinymce, {
selector: this.cid,
inline: true,
toolbar1: [
'formatselect fontsizeselect bold italic underline',
'bullist numlist',
'alignleft aligncenter alignright',
'link pastetext',
].join(' | '),
toolbar: true,
fixed_toolbar_container: '#geom-inline-editor-toolbar-' + this.cid,
content_css: geomData.pluginDirUrl + '/css/tinymce_content.min.css',
setup: function (editor) {
// editor events
// ... some lines skipped
},
});
// init mceEditor
this.mceEditor = tinymce.createEditor( this.cid, settings );
// render mceEditor
this.mceEditor.render();
return this;
},
This works but the appearance of the toolbar is not as expected. And mce panels and tooltips are displayed at the end of the document. See the first screenshot.
I can apply some style to fix this:
.geom-toolbar {
display: table;
width: 100%;
position: relative;
.geom-inline-editor-toolbar {
width: 100%;
.mce-flow-layout-item {
float: left;
}
.mce-widget.mce-btn {
float: left;
button {
padding: 0;
}
}
}
}
// stupid fix, dont do that!
.mce-widget.mce-tooltip[role="presentation"] {
position: absolute;
background-color: #111;
padding: 0.5em;
}
.mce-container.mce-panel.mce-floatpanel {
position: absolute;
max-width: 200px;
width: 200px !important;
}
The Editor looks like this then:
That’s not a good solution at all. And it will conflict somewhere.
Back to the Question
Does someone knows a way, to include an inline tinymce editor that doesn’t conflict with gutenberg?
Do I have to load some default styles for my tinymce editor that are not loaded by gutenberg? And how to limit their scope to my components
Or can advise me way.
I don’t search for a solution that uses gutenbergs RichText component.
Thank you
1 Answer
Gutenbergs FreeformEdit Component (used for the classic editor block) demonstrates the way how to integrate a tinyMce editor.
Recoded my custom Backform WysiwygControl and adopted the tinyMce integration.
/**
* External dependencies
*/
import Backform from 'Backform';
/**
* WordPress dependencies
*/
const { __ } = wp.i18n;
const { F10, ESCAPE, ALT } = wp.utils.keycodes;
/**
* Wysiwyg Control
*
* The tinyMce integration is mostly copied from the gutenberg/core-blocks/freeform component (classic editor)
*
*/
const WysiwygControl = Backform.Control.extend({
defaults: {
label: '',
helpMessage: null
},
// ./form.js inits the controls with options for layer and model
initialize( options ) {
Backform.Control.prototype.initialize.apply(this, arguments);
this.layer = options.layer;
},
template: _.template([
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
'<div class="<%=Backform.controlsClassName%>">',
' <div class="geom-inline-editor" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> >',
' <%= value %>',
' </div>',
' <% if (helpMessage && helpMessage.length) { %>',
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
' <% } %>',
'</div>'
].join('\n')),
events: _.extend({}, Backform.Control.prototype.events, {
'click': 'onClick',
'dblclick': 'onClick',
}),
onClick(e){
if ( e ) e.preventDefault();
const { baseURL, suffix } = window.wpEditorL10n.tinymce;
if ( this.editor ) return this;
window.tinymce.EditorManager.overrideDefaults( {
base_url: baseURL,
suffix,
} );
if ( document.readyState === 'complete' ) {
this.initEditor();
} else {
window.addEventListener( 'DOMContentLoaded', this.initEditor );
}
},
setupToolbar(){
if ( ! this.$toolbar ){
// create toolbar element
this.$toolbar = $('<div>', {
id: 'geom-inline-editor-toolbar-' + this.cid,
class: 'geom-inline-editor-toolbar freeform-toolbar',
['data-placeholder']: __( 'Classic' ),
} );
// append toolbar to container
this.$el.closest('div[data-type="geom/map"]').find('.geom-toolbar').append(this.$toolbar);
// animate toolbar
let autoHeight = this.$toolbar.css('height', 'auto').height();
this.$toolbar.height(0).animate({height: autoHeight}, 500, () => this.$toolbar.css('height', 'auto') );
// toolbar events
this.$toolbar.on('keydown', this.onToolbarKeyDown.bind(this) );
}
},
getEditorElement(){
return this.$el.find('.geom-inline-editor');
},
initEditor() {
const { settings } = window.wpEditorL10n.tinymce;
if ( this.editor ) return;
// setup editor element
this.getEditorElement().attr( 'id', 'editor-' + this.cid );
// setup toolbar element
this.setupToolbar();
// initialize
wp.oldEditor.initialize( 'editor-' + this.cid, {
tinymce: {
...settings,
inline: true,
content_css: geomData.pluginDirUrl + '/css/geom_block_map_editor_tinymce_content.min.css',
fixed_toolbar_container: '#geom-inline-editor-toolbar-' + this.cid,
setup: this.onSetup.bind(this),
},
} );
},
onSetup( editor ) {
const self = this;
const content = this.getEditorElement().html();
this.editor = editor;
editor.addButton( 'kitchensink', {
tooltip: __( 'More' ),
icon: 'dashicon dashicons-editor-kitchensink',
onClick: function() {
const button = this;
const active = ! button.active();
button.active( active );
editor.dom.toggleClass( self.$toolbar, 'has-advanced-toolbar', active );
},
} );
if ( content ) {
editor.on( 'loadContent', () => editor.setContent( content ) );
}
editor.on( 'init', () => {
// Create the toolbar by refocussing the editor.
editor.getBody().blur();
editor.focus();
} );
// // ??? well that doesn't work... will fix that in future
// editor.on('keydown', ( event ) => {
// const { altKey } = event;
// // Prevent Mousetrap from kicking in: TinyMCE already uses its own 'alt+f10' shortcut to focus its toolbar.
// // if ( altKey && event.keyCode === F10 ) {
// if ( event.keyCode === F10 ) {
// event.stopPropagation();
// }
// });
editor.on( 'blur', (event) => {
this.setModelVal(event);
return false;
} );
editor.on('KeyUp Change Paste input touchend', ( event ) => {
// close editor if esc pressed
if ( event.keyCode === ESCAPE ) {
this.close(event);
}
});
editor.on('focusout', ( event ) => {
if ( undefined !== $( event.explicitOriginalTarget ) ){
if ( $( event.explicitOriginalTarget ).attr('id') ){
if ( $( event.explicitOriginalTarget ).attr('id').startsWith('mce') ){
return;
}
}
if ( event.explicitOriginalTarget.tagName === 'BUTTON' ){
this.setModelVal(event);
this.close(event);
$( event.explicitOriginalTarget ).trigger('click');
return;
}
}
this.setModelVal(event);
this.close(event);
});
},
focus() {
if ( this.editor ) {
this.editor.focus();
}
},
onToolbarKeyDown( event ) {
// Prevent WritingFlow from kicking in and allow arrows navigation on the toolbar.
event.stopPropagation();
// Prevent Mousetrap from moving focus to the top toolbar when pressing 'alt+f10' on this block toolbar.
event.nativeEvent.stopImmediatePropagation();
},
close(e){
if ( e ) e.preventDefault();
this.removeEditor();
},
setModelVal(e){
if ( e ) e.preventDefault();
const model = this.model;
const val = this.editor.getContent();
const oldVal = model.get( this.field.get( 'name' ) ) || model.get( this.field.get( 'name' ) ).rendered;
const newVal = this.formatter.toRaw( val ) || this.formatter.toRaw( val ).rendered;
if ( ! _.isUndefined( newVal ) ) this.model.set( 'content', newVal );
},
getValueFromDOM() {
return this.formatter.toRaw( this.getEditorElement().html(), this.model );
},
removeEditor() {
if ( this.editor ){
window.addEventListener( 'DOMContentLoaded', this.initEditor );
wp.oldEditor.remove( 'editor-' + this.cid );
this.removeToolbar();
delete this.editor;
this.getEditorElement().attr( 'id', null);
}
},
removeToolbar(){
if ( this.$toolbar ){
this.$toolbar.animate({height: 0}, 500, () => {
this.$toolbar.remove();
delete this.$toolbar;
});
}
},
});
export default WysiwygControl;
New Plugin Version 0.0.7
… well the best way would be to build everything in React. But thats another story
Update 10 July 2018: Updated plugin links to version 0.0.6 (compatible with gb 3.2.0 and wp 4.9.7)
Update 12 July 2018: Updated plugin links to version 0.0.7 (compatible with gb 3.2.0 and wp 4.9.7)