I created a custom link button and want it appear highlighted/selected when you select/click on the link in the text editor, just like clicking on bold text shows the Bold icon as selected. In TinyMCE 4 you could simply use "stateSelector" to have it highlight when that kind of DOM element was selected, like this:
editor.ui.registry.addButton('SpecialLink', {
icon: 'link',
onAction: makeSpecialLink(),
**stateSelector: 'a[href]'**
});
I can't find anything about what stateSelector was replaced with in TinyMCE 5 and so far all I've been able to do is recreate some of that functionality inside tinymce.init:
init_instance_callback: function(editor) {
editor.on("SelectionChange", function(e){
let elem = editor.selection.getNode();
if( $(elem).is("a") )
console.log("Highlight the Special Link button");
else
console.log("Deselect the Special Link button");
})
}
I can reference myMCE.plugins.SpecialLink, but I can't call setActive(true) on it.
Any help would be greatly appreciated!
You can use addToggleButton rather than addButton, then call setActive.
Here's a snippet of my code.
editor.ui.registry.addToggleButton('my-action', {
icon: null,
text: 'My action',
onAction: function onAction() {
// ...do stuff
},
onSetup: function(api) {
function nodeChangeHandler(){
const selectedNode = editor.selection.getNode();
return api.setActive(selectedNode.id === constants.id);
}
editor.on('NodeChange', nodeChangeHandler);
}
});
}
https://www.tiny.cloud/docs/ui-components/typesoftoolbarbuttons/#togglebutton
#Muki's answer is similar to what I did, but I referenced the core code for anchor button from tinymce's git repo here https://github.com/tinymce/tinymce/blob/develop/modules/tinymce/src/plugins/anchor/main/ts/ui/Buttons.ts
I changed editor.ui.registry.addButton to editor.ui.registry.addToggleButton and added onSetup: (buttonApi) => editor.selection.selectorChangedWithUnbind('a:not([href])', buttonApi.setActive).unbind instead of stateSelector after onAction.
Something like below:
editor.ui.registry.addToggleButton('SpecialLink', {
icon: 'link',
onAction: makeSpecialLink(),
onSetup: (buttonApi) => editor.selection.selectorChangedWithUnbind('a:not([href])', buttonApi.setActive).unbind
});
Related
I am trying to build a customized version of the commenting plugin available for tinyMCE.
I am using the Annotations API to achieve this. Below is the code snippet currently being used to create a comment on some text selected by the user.
editor.ui.registry.addButton('annotate-alpha', {
text: 'Annotate',
onAction: function () {
var comment = prompt('Comment with?');
editor.annotator.annotate('alpha', {
uid: `comment${commentId}`,
comment: comment
});
if (comment == null || comment == '') {
editor.annotator.remove('alpha');
}
editor.focus();
commentId++;
},
onSetup: function (btnApi) {
editor.annotator.annotationChanged('alpha', function (state, name, obj) {
if (state == true) {
commentsHandler('true');
} else {
commentsHandler('false');
}
btnApi.setDisabled(state);
});
}
});
editor.on('init', function () {
editor.annotator.register('alpha', {
persistent: true,
decorate: function (uid, data) {
return {
attributes: {
'data-mce-comment': data.comment ? data.comment : '',
'data-mce-author': data.author ? data.author : 'anonymous',
}
};
}
});
});
this annotates the selected text correctly.
For example, in the below text if the user highlighted the text "features" and added a comment, the annotation is added and this is what the HTML looks like:
<p>Please try out the <span class="mce-annotation" data-mce-annotation-uid="comment0" data-mce-annotation="alpha" data-mce-comment="Comment 1" data-mce-author="anonymous" data-mce-selected="inline-boundary">features</span> provided in this full featured example.</p>
Screenshot
Now suppose the user tries to highlight the entire sentence and adds a comment, the text "features" moves to the front of the sentence.
Screenshot
The updated HTML looks like:
<p><span class="mce-annotation" data-mce-annotation-uid="comment0" data-mce-annotation="alpha" data-mce-comment="Comment 1" data-mce-author="anonymous" data-mce-selected="inline-boundary">features</span><span class="mce-annotation" data-mce-annotation-uid="comment1" data-mce-annotation="alpha" data-mce-comment="Comment 2" data-mce-author="anonymous">Please try out the provided in this full featured example</span></p>
I believe the existing <span> tag from the 1st comment is causing problems. A workaround can be that I check if the selected text has a previous annotation(span tag) and remove it before adding the new annotation but tinyMCE is loaded within an iframe and I can't access the HTML for manipulation.
How can I overcome this?
I am making a simple MediumEditor component in React.js. I am basing the component on the invoke demo from the Medium.js page. My problem is that my call to invokeElement is effecting the entire content of my editable element not the selection content. The example on the docs page has a call to medium.select() before the call to invokeElement. Here is my version:
componentDidMount: function() {
var editor = this.refs.editor.getDOMNode();
var medium = new Medium({
element: editor,
mode: Medium.richMode,
attributes: null,
tags: null,
pastAsText: false
});
this.editor = editor;
this.medium = medium;
this.refs.editor.getDOMNode().focus();
},
highlight: function() {
if(document.activeElement !== this.editor) {
this.medium.select();
}
},
setMode: function(mode) {
this.highlight();
if(mode == 'bold') {
this.medium.invokeElement('b', {
title: "I'm bold!",
style: "color: #66d9ef"
});
} else if(mode == 'underline') {
this.medium.invokeElement('u', {
title: "I'm underlined!",
style: "color: #a6e22e"
});
} else if(mode == 'italic') {
this.medium.invokeElement('i', {
title: "I'm italics!",
style: "color: #f92672"
});
}
},
I then attach setMode to each of my style buttons (bold, italic, underline). When I use the above code all of the editable element's content is changed not just the selection content. When I look at the source for medium.js I see that select() does select all so this seems like it shouldn't be called before invokeElement is called for selected content. When I remove the highlight call nothing happens...
This seems like a strange setup and the docs don't explain any of this from what i can find. What is the correct way to invokeElement on selected content? Any information related to using medium.js with React.js appreciated as well.
Call
this.medium.focus();
before calling invokeElement.
Jake from Medium.js probably decided to select all text when nothing is selected so that people on the site could see working behaviour without making a selection.
if(document.activeElement !== this.editor) {
this.medium.select();
}
Inside a Wordpress theme I am developing, i've a TinyMCEPopup to add shortcode to the editor, some shortcode requires images. Can i add an "Add media" button which opens the Wordpress media uploader and allow the user to select or upload an image even if i'm inside a TinyMCEPopup?
Don't know if it will help, but I had the same issue and solved it like this.
In functions.php add
add_action( 'after_setup_theme', 'mytheme_theme_setup' );
if ( ! function_exists( 'mytheme_theme_setup' ) ) {
function mytheme_theme_setup() {
add_action( 'init', 'mytheme_buttons' );
}
}
/********* TinyMCE Buttons ***********/
if ( ! function_exists( 'mytheme_buttons' ) ) {
function mytheme_buttons() {
if ( ! current_user_can( 'edit_posts' ) && ! current_user_can( 'edit_pages' ) ) {
return;
}
if ( get_user_option( 'rich_editing' ) !== 'true' ) {
return;
}
add_filter( 'mce_external_plugins', 'mytheme_add_buttons' );
add_filter( 'mce_buttons', 'mytheme_register_buttons' );
}
}
if ( ! function_exists( 'mytheme_add_buttons' ) ) {
function mytheme_add_buttons( $plugin_array ) {
$plugin_array['mybutton'] = get_template_directory_uri().'/js/tinymce_buttons.js';
return $plugin_array;
}
}
if ( ! function_exists( 'mytheme_register_buttons' ) ) {
function mytheme_register_buttons( $buttons ) {
array_push( $buttons, 'mybutton' );
return $buttons;
}
}
This will initialize the button you need. Now in the tinymce_buttons.js you'll add something like
(function() {
tinymce.PluginManager.add('mybutton', function( editor, url ) {
editor.addButton( 'mybutton', {
text: 'My button for media upload',
icon: false,
onclick: function() {
editor.windowManager.open( {
title: 'Insert Media',
body: [
{
type: 'textbox',
name: 'img',
label: 'Image',
value: '',
classes: 'my_input_image',
},
{
type: 'button',
name: 'my_upload_button',
label: '',
text: 'Upload image',
classes: 'my_upload_button',
},
],
onsubmit: function( e ) {
editor.insertContent( '[shortcode-name img="' + e.data.img + '"]');
}
});
},
});
});
})();
jQuery(document).ready(function($){
$(document).on('click', '.mce-my_upload_button', upload_image_tinymce);
function upload_image_tinymce(e) {
e.preventDefault();
var $input_field = $('.mce-my_input_image');
var custom_uploader = wp.media.frames.file_frame = wp.media({
title: 'Add Image',
button: {
text: 'Add Image'
},
multiple: false
});
custom_uploader.on('select', function() {
var attachment = custom_uploader.state().get('selection').first().toJSON();
$input_field.val(attachment.url);
});
custom_uploader.open();
}
});
First you add the button functionality. The list of options to put in popup is given here. It's not 100% complete, but better than the official documentation which is crap.
The first part is the button initialization. This gives you a 'My button for media upload' button, and on click you should get a modal with input field and a button.
On button click the media upload will open and you can select your image. On select the url will be in the input field, and you'll get it in your shortcode.
Hope it helps :)
There was another question like this (Open/Access WP Media library from tinymce plugin popup window) , so I'm pasting my answer here since this is similar:
Hi - I had the same problem just now and found the solution so I'm sharing it here. I hope it's not too late.
First to be able to use WP Add Media button you would have to enqueue the needed script. This is easy, just call the wp_enqueue_media() function like so:
add_action('admin_enqueue_scripts', 'enqueue_scripts_styles_admin');
function enqueue_scripts_styles_admin(){
wp_enqueue_media();
}
This call ensures you have the needed libraries to use the WP Media button.
Of course you should also have the HTML elements to hold the uploaded/selected media file URL, something like this:
<input type="text" class="selected_image" />
<input type="button" class="upload_image_button" value="Upload Image">
The first text field will hold the URL of the media file while the second is a button to open the media popup window itself.
Then in your jscript, you'd have something like this:
var custom_uploader;
$('.upload_image_button').click(function(e) {
e.preventDefault();
var $upload_button = $(this);
//Extend the wp.media object
custom_uploader = wp.media.frames.file_frame = wp.media({
title: 'Choose Image',
button: {
text: 'Choose Image'
},
multiple: false
});
//When a file is selected, grab the URL and set it as the text field's value
custom_uploader.on('select', function() {
var attachment = custom_uploader.state().get('selection').first().toJSON();
$upload_button.siblings('input[type="text"]').val(attachment.url);
});
//Open the uploader dialog
custom_uploader.open();
});
Now I'm not going to explain every line because it's not that hard to understand. The most important part is the one that uses the wp object to make all these to work.
The tricky part is making all these work on a TinyMCE popup(which is the problem I faced). I've searched hi and lo for the solution and here's what worked for me. But before that, I'll talk about what problem I encountered first. When I first tried to implement this, I encountered the "WP is undefined" problem on the popup itself. To solve this, you just have to pass the WP object to the script like so:
(function() {
tinymce.create('tinymce.plugins.someplugin', {
init : function(ed, url) {
// Register commands
ed.addCommand('mcebutton', function() {
ed.windowManager.open(
{
file : url + '/editor_button.php', // file that contains HTML for our modal window
width : 800 + parseInt(ed.getLang('button.delta_width', 0)), // size of our window
height : 600 + parseInt(ed.getLang('button.delta_height', 0)), // size of our window
inline : 1
},
{
plugin_url : url,
wp: wp
}
);
});
// Register buttons
ed.addButton('someplugin_button', {title : 'Insert Seomthing', cmd : 'mcebutton', image: url + '/images/some_button.gif' });
}
});
// Register plugin
// first parameter is the button ID and must match ID elsewhere
// second parameter must match the first parameter of the tinymce.create() function above
tinymce.PluginManager.add('someplugin_button', tinymce.plugins.someplugin);
})();
What we're interested in is this line => "wp: wp" . This line ensures that we are passing the wp object to the popup window (an iframe really...) that is to be opened when we click the tinymce button. You can actually pass anything to the popup window via this object (the 2nd parameter of the ed.windowManager.open method)!
Last but not the least you'd have to reference that passed wp object on your javascript like so:
var args = top.tinymce.activeEditor.windowManager.getParams();
var wp = args.wp;
Make sure you do that before calling/using the WP object.
That's all you have to do to make this work. It worked for me, I hope it works for you :)
How can I add custom HTML instead of regular button in TinyMCE 4 inside the split button drop menu?
ed.addButton('demo_button', {
title: 'Demo Button',
type: 'splitbutton',
onclick: function() {
},
menu: [
{
text : 'Some Regular Button', onclick : function() {
}
},
{
//How to add some custom html for combo box here for example?
}
]
});
In previous version (TinyMCE 3) I was able to use this:
var c = cm.createSplitButton('demo_button', {
title : 'Demo Button',
onclick : function() {
}
});
c.onRenderMenu.add(function(c, m) {
m.onShowMenu.add(function(c,m){
var $menu = jQuery('#menu_'+c.id+'_co').find('tbody:first');
if($menu.data('added')) return;
$menu.append('SOME HTML HERE');
$menu.data('added',true);
});
});
So basically my question is how to migrate this piece of code to TinyMCE 4?
Cheers
If you were using TinyMCE 3.0 are now migrating to TinyMCE 4+, may be you can first try using their compat3x plugin, which will allow you to transition most of your old plugins without modifications as per their documentation available here:
TinyMCE compat3x plugin
Like you did with the v3, you have to edit the dom after TinyMCE rendering.
To catch rendering process, use init_instance_callback
See : http://www.tinymce.com/wiki.php/Configuration%3ainit_instance_callback
And this SO answer : https://stackoverflow.com/a/24557748/911718
I'm working on a TinyMCE plugin and one thing I want it to do is register commands/buttons that toggle custom formatting.
For example if you click the bold button in TinyMCE it will show the bold button highlighted while in bold text. Digging into the source code I see this happens via: tinymce.EditorCommands.addCommands thought I can't seem to figure out how to duplicate it. The documentation of TinyMCE is just horrible as well =(
So given customFormat I want to be able to have a button setup by my plugin that when the customFormat is applied it shows as such like the Bold, Italics, and other such buttons do on the toolbar. And clicking on my customFormat toggles that format on/off. I can easily accomplish the toogle via "addCommand" and "addButton" but then it does not have state tracking like Bold and others do.
Showing my current non-working attempt (this code is inside init of my plugin create method):
tinymce.EditorCommands.call('addCommands', {
'MyFormat' : function(name) {
ed.formatter.toggle("customFormat");
}
},'exec');
tinymce.EditorCommands.call('addCommands', {
'MyFormat' : function(name) {
return ed.formatter.match('customFormat');
}
},'state');
ed.addButton('customformat', {cmd : 'MyFormat'});
And here is the link to the "documentation" of addCommands:
http://www.tinymce.com/wiki.php/API3:method.tinymce.EditorCommands.addCommands
After a lot more looking around I found this which seems to be perfect:
http://www.tinymce.com/wiki.php/API3:method.tinymce.Editor.addQueryStateHandler
But when I implement the code it doesn't change the state of the button:
ed.addCommand('MyFormat', function(ui, v) {
ed.formatter.toggle("thoughtFormat");
});
ed.addQueryStateHandler('MyFormat', function() {
return ed.formatter.match('thoughtFormat');
});
ed.addButton('myformat', {cmd : 'MyFormat'});
In case someone doesn't want to do it the 'plug-in' way, here's the guide for TinyMCE 4.x.
First of all, you need to define a custom format:
formats: {
custom_format: {inline: 'span', styles: {color: "red"}, attributes: {class: 'some_css_class'}}
}
Then you'll have to add a button to your toolbar:
toolbar: "mybutton",
Next, you need to setup your button, so that it toggles the format:
setup: function(editor) {
editor.addButton('mybutton', {
text: 'My button',
icon: false,
onclick: function() {
tinymce.activeEditor.formatter.toggle('custom_format')
}
});
}
Furthermore, if you want the editor to set the state of the button to indicate the format of current node, automatically, add this to setup function:
onPostRender: function() {
var ctrl = this;
editor.on('NodeChange', function(e) {
ctrl.active(e.element.className == "some_css_class")
});
}
Your tinymce.init function should look like this:
tinymce.init({
selector: "textarea",
formats: {
// Other formats...
custom_format: {inline: 'span', styles: {color: "red"}, attributes: {class: 'some_css_class'}}
}
// Other default toolbars
toolbar_n: "mybutton",
// Finally, setup your button
setup: function(editor) {
editor.addButton('mybutton', {
text: 'My Button',
icon: false,
onclick: function() {
tinymce.activeEditor.formatter.toggle('custom_format')
},
onPostRender: function() {
var ctrl = this;
editor.on('NodeChange', function(e) {
ctrl.active(e.element.className == "some_css_class")
});
}
});
}
Note that class attribute I added to my custom format. This approach made it possible for me define my custom styles in a separate stylesheet file and keep my markup as dry as possible (No inline styling!). Point content_css option to your stylesheet and you'll be good to go.
However, due to fact that I'm using Rails as back-end and BatmanJS as front-end (and I'm fairly new to the latter), I couldn't figure out how assets routing works, and ended up adding my custom styles to default content stylesheet file of tinyMCE skin itself (located at skins/SKIN_NAME/content.min.css).
Thanks to Thariama for insights that allowed me to dig deeper finally figuring out how to do this. I'm not sure its the "right way" but as I said TinyMCE has the worst documentation imaginable.
The key for me was to make an hook the onNodeChange event, using the setActive trick. Full example plugin with a custom button that activates when that format is present wherever the cursor is:
(function() {
tinymce.create('tinymce.plugins.CoolPlugin', {
init : function(ed, url) {
ed.addCommand('MyFormat', function(ui, v) {
ed.formatter.toggle("myFormat");
});
ed.addButton("coolformat", {
title : 'MyFormat Tooltip',
cmd : 'MyFormat',
image: url + '/coolformat.png',
});
ed.onNodeChange.add(function(ed, cm, n) {
active = ed.formatter.match('myFormat');
control = ed.controlManager.get('coolformat').setActive(active);
});
ed.onInit.add(function(ed, e) {
ed.formatter.register('myFormat',
{inline: 'span', classes : ['cool'] } );
});
}
});
// Register plugin
tinymce.PluginManager.add('cool', tinymce.plugins.CoolPlugin);
})();
Here is an example:
ed.controlManager.get('my_control_element').setActive(true); // could be bold or whatever