/**
* Note TinyMCE Theme - /assets/js/note-tinymce-theme.js
* License: GPLv2 or later
* Copyright: Janneke Van Dorpe (iseulde), http://iseulde.com/
*
* @see https://github.com/iseulde/wp-front-end-editor/
* @see https://github.com/iseulde/wp-front-end-editor/blob/master/js/tinymce.theme.js
* @see https://wordpress.org/plugins/wp-front-end-editor/
*
* We've used Janneke Van Dorpe's TinyMCE theme as a base and modified it to suit our needs.
*/
/* global tinymce */
tinymce.ThemeManager.add( 'note', function( editor ) {
var self = this,
$ = window.jQuery,
DOM = tinymce.DOM,
Factory = tinymce.ui.Factory,
each = tinymce.each,
settings = editor.settings,
admin_bar_height = 32,
focus = false,
open_window = false,
note_toolbar_name = 'main',
toolbars = {},
wp_toolbar_names = {
img: 'img',
link_edit: 'link_edit',
link: 'link'
},
visible_toolbars = {
link_edit: [note_toolbar_name]
},
WP_Link = false,
wplink_toolbar_visible = false,
IE_unlink = false,
linkNode = false;
/**
* Render the UI of the theme
*/
self.renderUI = function() {
var panel,
has_placeholder,
upper_margin = 0;
// Bail if we don't have a toolbar in settings
if ( ! settings.toolbar || ! settings.toolbar.length ) {
return {};
}
// Calculate the upper margin for the entire document
if ( DOM.getStyle( document.body, 'position', true ) === 'relative' ) {
upper_margin =
parseInt( DOM.getStyle( document.body, 'margin-top', true ), 10 ) +
parseInt( DOM.getStyle( document.documentElement, 'padding-top', true ), 10 ) +
parseInt( DOM.getStyle( document.documentElement, 'margin-top', true ), 10 );
}
// Allow the content within the editor to be adjusted (instead of creating an editor element)
settings.content_editable = true;
/*
* TinyMCE Editor Events
*/
// Activate, focus events
editor.on( 'activate focus', function() {
// Set the focus flag
focus = true;
// Add the focus CSS class
DOM.addClass( editor.getBody(), 'mce-edit-focus' );
} );
// Deactivate, blur, hide events
editor.on( 'deactivate blur hide', function() {
// Reset the focus flag
focus = false;
// Remove the focus CSS class
DOM.removeClass( editor.getBody(), 'mce-edit-focus' );
// If we have a panel
if ( panel ) {
// Hide the panel
panel.hide();
}
} );
// Remove event
editor.on( 'remove', function() {
// If we have a panel
if ( panel ) {
// Remove the panel
panel.remove();
// Reset the reference to the panel
panel = null;
}
} );
// Preinit event (once)
editor.once( 'preinit', function() {
// If the WordPress plugin has been initialized and we can create a toolbar
if ( editor.wp && editor.wp._createToolbar ) {
/*
* Panel
*/
// Create the panel (no items)
panel = self.panel = Factory.create( {
type: 'floatpanel',
role: 'application',
classes: 'tinymce tinymce-inline',
layout: 'stack',
items: []
} );
/**
* This function repositions a toolbar (name) within the editor.
*/
panel.reposition = function( name ) {
var panelEl = this.getEl(),
selection = editor.selection.getRng(),
boundary = selection.getBoundingClientRect(),
editorEl = editor.getElement(),
editor_boundary = editorEl.getBoundingClientRect(),
has_boundary = ( boundary.top && boundary.right && boundary.bottom && boundary.left ),
boundary_top = ( has_boundary ) ? boundary.top : editor_boundary.top,
boundary_right = ( has_boundary ) ? boundary.right : editor_boundary.right,
boundary_bottom = ( has_boundary ) ? boundary.bottom : editor_boundary.bottom,
boundary_left = ( has_boundary ) ? boundary.left : editor_boundary.left,
boundary_middle = ( has_boundary ) ? ( ( boundary.left + boundary.right ) / 2 ) : ( ( editor_boundary.left + editor_boundary.right ) / 2 ),
window_width = window.innerWidth,
panel_width, panel_half,
margin = parseInt( DOM.getStyle( panelEl, 'margin-bottom', true ), 10 ) + upper_margin,
top, left, className;
panelEl.className = ( ' ' + panelEl.className + ' ' ).replace( /\smce-arrow-\S+\s/g, ' ' ).slice( 1, -1 );
// Setup the toolbar name
name = name || note_toolbar_name;
// Fallback to the main toolbar if we don't have a reference
if ( ! toolbars[name] ) {
name = note_toolbar_name;
}
// Loop through toolbars
each( toolbars, function( toolbar, toolbar_name ) {
// Hide this toolbar if the flag is set and it doesn't match the name
if ( toolbar.note_hide && toolbar_name !== name && ( ! visible_toolbars[name] || visible_toolbars[name].indexOf( toolbar_name ) === -1 ) ) {
toolbar.hide();
}
} );
// If the editor selection is not hidden
if ( ! editor.selection.isCollapsed() ) {
if ( ! toolbars[name].state.get( 'visible' ) ) {
// Show this toolbar
toolbars[name].show();
}
}
// Otherwise just hide the panel
else {
panel.hide();
}
/*
* Determine the position for this toolbar/panel
*/
panel_width = panelEl.offsetWidth;
panel_half = panel_width / 2;
if ( boundary_top < panelEl.offsetHeight + admin_bar_height ) {
className = ' mce-arrow-up';
top = boundary_bottom + margin;
}
else {
className = ' mce-arrow-down';
top = boundary_top - panelEl.offsetHeight - margin;
}
left = boundary_middle - panel_half;
if ( panel_width >= window_width ) {
className += ' mce-arrow-full';
left = 0;
}
else if ( ( left < 0 && boundary_left + panel_width > window_width ) || ( left + panel_width > window_width && boundary_right - panel_width < 0 ) ) {
left = ( window_width - panel_width ) / 2;
}
else if ( left < 0 ) {
className += ' mce-arrow-left';
left = boundary_left;
}
else if ( left + panel_width > window_width ) {
className += ' mce-arrow-right';
left = boundary_right - panel_width;
}
panelEl.className += className;
DOM.setStyles( panelEl, { 'left': left, 'top': top + window.pageYOffset } );
return this;
};
// Show event
panel.on( 'show', function() {
setTimeout( function() {
// If the panel is visible (checking state)
if ( panel.state.get( 'visible' ) ) {
// Add the active CSS class
DOM.addClass( panel.getEl(), 'mce-inline-toolbar-active' );
}
}, 100 );
} );
// Hide event
panel.on( 'hide', function() {
// If we don't have an editor selection
if ( ! editor.selection || ( editor.selection && editor.selection.isCollapsed() ) ) {
DOM.removeClass( panel.getEl(), 'mce-inline-toolbar-active' );
}
// Loop through toolbars
each( toolbars, function( toolbar ) {
// Loop through the button groups for this toolbar
each( toolbar.items(), function ( buttonGroups ) {
// Loop through individual button group for this toolbar
each( buttonGroups.items(), function ( buttonGroup ) {
// Loop through items for this button group
each( buttonGroup.items(), function ( item ) {
// If this item has a menu
if ( item.state.get( 'menu' ) ) {
// Hide the menu
item.hideMenu();
}
} );
} );
} );
// If this toolbar should be hidden
if ( toolbar.note_hide && toolbar.state.get( 'visible' ) ) {
// Hide the toolbar
toolbar.hide();
}
} );
} );
// Cancel event
panel.on( 'cancel', function() {
// Focus the editor
editor.focus();
} );
// Render the panel to the body element
panel.renderTo( document.body ).reflow().hide();
/*
* Create the main toolbar.
*
* Because the WordPress TinyMCE plugin renders the toolbar to the DOM for us,
* we need to add it after the panel is rendered so that we can append it to our
* panel 'body' element.
*/
// Setup the main toolbar
setupToolbar( editor.wp._createToolbar( settings.toolbar ), note_toolbar_name, panel, {
note_hide: true
} );
}
// wptoolbar event (triggered after WordPress' logic)
editor.on( 'wptoolbar', function( args ) {
// If we have an element and a toolbar
if ( args.element && args.toolbar ) {
// Switch based on type of element
switch ( args.element.nodeName ) {
// Images
case 'IMG':
// If we don't already have a reference to this toolbar
if ( ! toolbars[wp_toolbar_names.img] ) {
// Setup the toolbar
setupToolbar( args.toolbar, wp_toolbar_names.img, panel, {
note_hide: true
} );
// Add an event listener to the editor:image-edit event
if ( wp.media ) {
// Editor - Image Edit event
wp.media.events.on( 'editor:image-edit', function() {
// Add an event listener once to the editor:frame-create event (triggered right after editor:image-edit)
wp.media.events.once( 'editor:frame-create', function( data ) {
// If the frame is not attached
if ( ! data.frame.modal.views.attached ) {
// Attach the frame (fixes a bug in FireFox and IE where the $el is initially visible so the rendering process is never completed @see https://github.com/WordPress/WordPress/blob/4.5-branch/wp-includes/js/media-views.js#L6764)
data.frame.attach();
}
} );
} );
}
}
break;
// Links
case 'A':
var href = args.element.getAttribute( 'href' );
//$link = editor.$( editor.dom.getParent( args.element, 'a' ) );
// Setup a reference to the link node
linkNode = editor.dom.getParent( args.element, 'a' );
// If we don't already have a reference to this toolbar (editing link)
if ( ! toolbars[wp_toolbar_names.link_edit] || ! toolbars[wp_toolbar_names.link] ) {
// If this link is being edited (created)
if ( ! toolbars[wp_toolbar_names.link_edit] && ( href === '_wp_link_placeholder' || args.element.getAttribute( 'data-wplink-edit' ) ) ) {
// Setup the toolbar
setupToolbar( args.toolbar, wp_toolbar_names.link_edit, panel, {
tempHide: true,
note_hide: false
} );
// In IE, set the WP_Link flag here
if ( tinymce.Env.ie ) {
// Set the flag
WP_Link = true;
}
// Show event
args.toolbar.on( 'show', function() {
// Set the wplink_toolbar_visible flag
wplink_toolbar_visible = true;
// In IE, set the WP_Link flag here
if ( tinymce.Env.ie ) {
// Set the flag
WP_Link = true;
// If the panel is not visible (checking state)
if ( ! panel.state.get( 'visible' ) ) {
// Show the panel (fixes a bug in IE)
panel.show();
}
}
} );
// Hide event
args.toolbar.on( 'hide', function() {
// Reset the wplink_toolbar_visible flag
wplink_toolbar_visible = false;
// In IE, if the panel is not visible (checking state)
if ( tinymce.Env.ie && ! panel.state.get( 'visible' ) ) {
// Show the panel (fixes a bug in IE)
panel.show();
}
} );
}
// TODO: We may not need this toolbar for the time being
// Otherwise if this link was already existing
/*else if ( ! toolbars[wp_toolbar_names.link] && href && href !== '_wp_link_placeholder' && ! $link.find( 'img' ).length ) {
// Setup the toolbar
setupToolbar( args.toolbar, wp_toolbar_names.link, panel, {
note_hide: true
} );
}*/
}
break;
}
}
} );
} );
// Selectionchange, nodechange events
editor.on( 'selectionchange nodechange', function( event ) {
var element = event.element || editor.selection.getNode();
// Bail if we don't have a selection
if ( editor.selection.isCollapsed() ) {
// Hide the panel
panel.hide();
return;
}
setTimeout( function() {
var content, name;
// Bail if this editor does not have focus or there is an open window
if ( ! focus || open_window ) {
return;
}
// If the selection isn't collapsed, we have content, and this is not a <hr> element
if ( ! editor.selection.isCollapsed() && ( content = editor.selection.getContent() ) && ( content.replace( /<[^>]+>/g, '' ).trim() || content.indexOf( '<' ) === 0 ) && element.nodeName !== 'HR' || WP_Link ) {
// Switch based on type of element
switch ( element.nodeName ) {
// Images
case 'IMG':
name = wp_toolbar_names.img;
break;
// Links
case 'A':
var href = element.getAttribute( 'href' ),
$link = editor.$( editor.dom.getParent( element, 'a' ) ),
$img = $link.find( 'img' ).eq( 0 ); // Select the first image, if it exists
// Setup a reference to the link node
linkNode = editor.dom.getParent( element, 'a' );
// Default to link_edit
name = wp_toolbar_names.link_edit;
// If this link was already existing
if ( ! WP_Link && href && href !== '_wp_link_placeholder' && ! $link.find( 'img' ).length ) {
name = note_toolbar_name;
}
// In IE, if we have an href value and it's not the placeholder, use the main toolbar
if ( tinymce.Env.ie && href && href !== '_wp_link_placeholder' ) {
name = note_toolbar_name;
}
// If there is an image inside of this link, show the image toolbar instead
if ( $img.length ) {
// Select the image (prevents a bug in Firefox and IE where the link is selected and then editing the image results in a broken reference in the media modal)
editor.selection.select( $img[0] );
editor.fire( 'nodechange', {
element: $img[0],
note: true
} );
// In IE, trigger a click on the image
if ( tinymce.Env.ie ) {
$img.trigger( 'click' );
}
name = wp_toolbar_names.img
}
break;
// Default
default:
name = note_toolbar_name;
break;
}
// If the panel is not visible, show the panel first
if ( ! panel.state.get( 'visible' ) ) {
// Show the panel and reposition the toolbar
panel.show();
}
// Reposition the toolbar
panel.reposition( name );
}
// Otherwise hide the panel
else {
panel.hide();
}
}, 100 );
} );
// Beforeexeccommand event
editor.on( 'beforeexeccommand', function( event ) {
// If this is a WP_Link command
if ( event.command === 'WP_Link' ) {
// Set the flag
WP_Link = true;
// If the wplink toolbar is visible (user clicked on "link" button again)
if ( wplink_toolbar_visible ) {
// Reset the temporary hide flag
toolbars[wp_toolbar_names.link_edit].tempHide = false;
// Execute the wp_link_cancel command
editor.execCommand( 'wp_link_cancel' );
// Prevent default (prevent WP_Link)
event.preventDefault();
}
// In IE, we want to make sure we open the advanced link editor if it exists to fix bugs with rendering the WordPress 4.5 link editor toolbar
if ( tinymce.Env.ie && typeof window.wpLink !== 'undefined' ) {
var node = editor.selection.getNode(),
content = editor.selection.getContent(),
url, href;
// If this is an existing link only
if ( node.nodeName === 'A' || content.indexOf( '<a' ) === 0 ) {
url = content.match( /href=["|'](.+)["|']/ );
url = ( url ) ? url[1] : null;
href = ( linkNode ) ? editor.dom.getAttrib( linkNode, 'href' ) : url;
/*
* Unfortunately IE looses the selection when the editor iframe
* looses focus, so without returning focus to the editor, the code
* in the modal will not be able to get the selection, place the caret
* at the same location, etc.
*/
editor.focus();
// wpLink modal
window.wpLink.open( editor.id, url || null );
/*
* TODO: Fix the non-selection case in the editor
* For some reason when the wpLink modal is open, our editor
* doesn't have a selection. Set the URL value manually.
*/
if ( linkNode && href !== '_wp_link_placeholder' ) {
$( '#wp-link-url' ).val( href );
$( '#wp-link-target' ).prop( 'checked', '_blank' === editor.dom.getAttrib( linkNode, 'target' ) );
$( '#wp-link-submit' ).val( window.wpLinkL10n.update );
}
// Prevent default (prevent WP_Link)
event.preventDefault();
}
}
}
// In IE, if this is an unlink command
if ( tinymce.Env.ie && event.command === 'unlink' ) {
// If the wplink toolbar is visible
if ( wplink_toolbar_visible ) {
// Set the flag
IE_unlink = true;
// Reset the temporary hide flag
toolbars[wp_toolbar_names.link_edit].tempHide = false;
// Execute the wp_link_cancel command
editor.execCommand( 'wp_link_cancel' );
// Prevent default (prevent WP_Link)
event.preventDefault();
}
}
// If this is a wp_link_cancel or wp_link_apply command
if ( event.command === 'wp_link_cancel' || event.command === 'wp_link_apply' ) {
// In IE, if this is a wp_link_cancel command and the wplink toolbar is visible or this is an IE_unlink action
if ( tinymce.Env.ie && event.command === 'wp_link_cancel' && ( wplink_toolbar_visible || IE_unlink ) ) {
// If the flag is not set
if ( ! IE_unlink ) {
// Prevent default
event.preventDefault();
return;
}
// If the flag is set
if ( IE_unlink ) {
// Reset the flag
IE_unlink = false;
return;
}
}
// Reset the flag
WP_Link = false;
// Reset the link node reference
linkNode = false;
}
} );
// Execcommand event
editor.on( 'execcommand', function( event ) {
// If this is a wp_link_cancel or wp_link_apply command
if ( event.command === 'wp_link_cancel' || event.command === 'wp_link_apply' ) {
// If the panel is visible and the editor selection is collapsed hide it (fixes bug in Firefox where selectionchange isn't triggered in some cases)
if ( ! WP_Link && panel.state.get( 'visible' ) && ( editor.selection.isCollapsed() || ( toolbars[wp_toolbar_names.link_edit] && toolbars[wp_toolbar_names.link_edit].state.get( 'visible' ) ) ) ) {
panel.hide();
}
}
} );
// Openwindow event
editor.on( 'openwindow', function() {
// Set the flag
open_window = true;
// Hide the panel
panel.hide();
} );
// Closewindow event
editor.on( 'closewindow', function() {
// Reset the flag
open_window = false;
// Show the panel
panel.show();
} );
/*
* Placeholder
*/
if ( settings.placeholder ) {
// Activate, focus events
editor.on( 'activate focus', function() {
if ( has_placeholder ) {
editor.setContent( '' );
// Make sure the cursor appears in editor
editor.selection.select( editor.getBody(), true );
editor.selection.collapse( false );
}
} );
// Deactivate, blur, LoadContent events
editor.on( 'deactivate blur LoadContent', function( event ) {
// If editor content is empty
if ( isEmpty() ) {
// Set the placeholder (no events)
editor.setContent( settings.placeholder, {
no_events: true
} );
has_placeholder = true;
DOM.addClass( editor.getBody(), 'mce-placeholder' );
// If this is the loadcontent event
if ( event.type === 'loadcontent' ) {
// Focus the editor
editor.focus();
// New thread
setTimeout( function() {
// Make sure the cursor appears in editor
editor.selection.select( editor.getBody() );
editor.selection.collapse( false );
}, 1 );
}
}
// If WP_Link or the wplink toolbar is visible
// TODO: The wp_link_cancel command focuses the editor
/*if ( WP_Link ||wplink_toolbar_visible ) {
// Reset the temporary hide flag
toolbars[wp_toolbar_names.link_edit].tempHide = false;
// Execute the wp_link_cancel command
editor.execCommand( 'wp_link_cancel' );
}*/
} );
// Setcontent event
editor.on( 'setcontent', function( event ) {
if ( has_placeholder && ! event.load ) {
has_placeholder = false;
DOM.removeClass( editor.getBody(), 'mce-placeholder' );
}
} );
// Postprocess event
editor.on( 'postprocess', function( event ) {
if ( has_placeholder && event.content ) {
event.content = '';
}
} );
// Beforeaddundo event
editor.on( 'beforeaddundo', function( event ) {
if ( has_placeholder ) {
event.preventDefault();
}
} );
}
// Window resize event
DOM.bind( window, 'resize', function() {
// Hide the panel
panel.hide();
} );
return {};
};
/**********************
* Internal Functions *
**********************/
/**
* This function determines if the editor content is empty.
*/
function isEmpty() {
return editor.getContent( { format: 'raw' } ).replace( /(?:<p[^>]*>)?(?:<br[^>]*>)?(?:<\/p>)?/, '' ) === '';
}
/**
* This function sets up a toolbar for use in our panel.
*/
function setupToolbar( toolbar, toolbar_name, panel, args, delay ) {
args = args || false;
delay = delay || 0;
// Store a reference to the toolbar
toolbars[toolbar_name] = toolbar;
// If we have arguments to add
if ( args ) {
// Loop through them
for ( var arg in args ) {
// hasOwnProperty
if ( args.hasOwnProperty( arg ) ) {
// Add it to the toolbar
toolbars[toolbar_name][arg] = args[arg];
}
}
}
// setTimeout to ensure this happens on another thread
setTimeout( function() {
// Add the toolbar to our panel
panel.add( toolbars[toolbar_name] );
// Append the toolbar to our panel in the DOM (grab the DOMQuery reference)
toolbars[toolbar_name].$el.appendTo( panel.getEl( 'body' ) );
// If this toolbar should be hidden
if ( toolbars[toolbar_name].note_hide ) {
// Hide the toolbar
toolbars[toolbar_name].hide();
}
}, delay );
}
} );