TinyMCE adding toggle style - javascript

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

Related

TinyMCE 5.x - highlight custom button

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
});

CKEditor put focus on html type dialog

I'm trying to create a select dialog that supports optgroup. The built in 'select' dialog doesn't handle that so I'm using the 'html' type dialog. It's working fine, but I'd like to put the focus on that select box when the dialog opens.
I've tried various things, but I can't get it to work. I'm wondering if I need to override getInputElement() and have it return the select element so I can call focus() on it, but I have no idea how to do that.
I also tried selecting the element with jQuery and using its focus() method, but that doesn't work.
I found that I can define a focus() function on the dialog element and in there make the appropriate call to the jQuery .focus().
CKEDITOR.dialog.add( 'myDialog', function( editor ) {
"use strict";
return {
title: 'Custom Dialog',
contents: [
{
id: 'tab-basic',
elements: [
{
type: 'html',
id: 'mealplan_select',
html: '<select id="my-select"></select>',
focus: function() {
$('#my-select').focus();
},
onShow: function() {
// focus this element
this.getInputElement().focus();
}
}
]
}
]
};
});

Medium.js in React.js component using invokeElement

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();
}

TinyMCE 4 - Custom HTML inside the SplitButton

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

TinyMCE add item to context menu

I want to add a new menu item to TinyMCE's context menu, and perform a command when the user clicks it, so far i have this, which is not working:
tinyMCE.init({
...
setup : function(ed) {
ed.onContextMenu.add(function(ed, menu) {
menu.add({title : 'Menu 1', onclick : function() {
alert('Item 1 was clicked.');
}});
});
}
The code above throws an error saying "menu.add is not a function", if i remove the menu.add stuff and place a console.log(menu), it returns "contextmenu" upon opening the context menu.
What would be the right way to add an item to the context menu ? Preferably without having to modify the plugin itself. Thanks in advance.
You will need something like
ed.onContextMenu.add(function(ed, e) {
if (!e.ctrlKey) {
// Restore the last selection since it was removed
if (lastRng)
ed.selection.setRng(lastRng);
var menu = this._getMenu(ed);
if ((typeof menu).toLowerCase() == 'object')
{
menu.showMenu(e.clientX, e.clientY);
Event.add(ed.getDoc(), 'click', function(e) {
hide(ed, e);
});
Event.cancel(e);
}
}
});
and the function _getMenu where you may insert contextmenu options:
//example this will only display if an image was clicked
if (node !== "undefined" && node.nodeName.toLowerCase() == 'img') {
m.add({
title: 'my menu',
});
m.addSeparator();
// Inline-Element editieren
m.add({
title: 'to be choosen1',
icon: 'http://...',
cmd: 'undo'
});
t.onContextMenu.dispatch(t, m, el, col);
return m;
}
EDIT:
you can get the default menu using (the plugin contextmenu needs to be active)
var editor = tinymce.get(editor_id);
var menu = editor.plugins.contextmenu._getMenu(editor);
adding an entry to the menu should work as follows
menu.add({title : 'undo', icon : 'undo', cmd : 'Undo'});
it might be necessary to render the menu explicitly using showMenu.
Another way to insert a menu to the contextemenu is to modify the editor_plugin.js in the tiny_mce/plugins/contextemneu directory and add the entry directly. You may also copy the plugin, modify and rename it - let it work as a custom plugin.
You can add context menu like this:
setup : function(ed) {
ed.onContextMenu.add(function(ed, menu) {
displayContextMenu(ed,e);
}});
});
}
function displayContextMenu(ed,e){
var m = ed.plugins.contextmenu._getMenu(ed);
m.add({title : 'advanced.bold_desc', icon : 'bold', cmd : 'bold'});
}

Categories