TinyMCE add item to context menu - javascript

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

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

Add class to selected image in ckeditor

I'm having trouble adding classes to selected image in ckeditor. What I came up with is this http://pokit.org/get/img/8d89802e1d6f6371f5bc326898d8b414.jpg.
I added 2 buttons for selecting whether whether a picture is in portrait or landscape mode. You can select either of them or none, and add costum height/width.
Here is my code:
CKEDITOR.replace('maindesc', {
"extraPlugins": "imgbrowse",
"filebrowserImageBrowseUrl": "/ckeditor/plugins/imgbrowse",
on: {
instanceReady: function() {
this.dataProcessor.htmlFilter.addRules( {
elements: {
img: function( el ) {
// Add an attribute.
if ( !el.attributes.alt ) {
el.attributes.alt = 'Img';
el.addClass('ckeditorImg');
if (Landscape == 1) {
el.addClass('ckLandscape');
el.attributes['style'] = '';
}
else if (Portrait == 1) {
el.addClass('ckPortrait');
el.attributes['style'] = '';
}
}
}
}
} );
}
}
});
So as far as I understand this goes through all, so I wrote that if the image has no alt attribute to add one and add the classes I want. Unfortunately this approach doesn't allow me to change the class on selected image when a user wants to change it, but instead he has to delete the image, select it again and then choose class.
My question is whether there is a way to get to currently selected image instead of going through all <img> tags in ckeditor and change its class.
Here is an example for how to add a new button to ckeditor that is enabled/disables based on the element that you currently select and add a class to that specific element (in this example it's for images, however you can use it in any way you want).
// Set the callback function
var setLandscapeClass = {
exec: function(editor) {
editor.getSelection().getStartElement().addClass('ckLandscape')
}
}
//Create the plugin
CKEDITOR.plugins.add('setLandscapeClass', {
init: function(editor) {
editor.addCommand('setLandscapeClass', setLandscapeClass);
editor.ui.addButton("setLandscapeClass", {
label: 'Set Landscape Class',
icon: '',
command: 'setLandscapeClass'
});
}
});
// Create the instance and add the plugin
CKEDITOR.replace( 'editor1', {
extraPlugins: 'setLandscapeClass',
allowedContent: true
});
// enable/disable the button based on the selection of the text in the editor
CKEDITOR.instances.editor1.on( 'selectionChange', function( evt ) {
var landscapeButton = this.getCommand( 'setLandscapeClass' );
if ( evt.data.path.lastElement.is( 'img' ) ) {
landscapeButton.enable();
} else {
landscapeButton.disable();
}
});
You can see a working demo here:
https://jsfiddle.net/7nm9q1qv/
I only created 1 button, and there is no icon there. I think you can use that code to create also the second button (for portrait class).
Update - add item to the context menu
In order to add a new item to the context-menu you should add this code:
// Add the context-menu
if (editor.addMenuItem) {
editor.addMenuGroup('testgroup');
editor.addMenuItem('setLandscapeItem', {
label: 'Set landscape class',
command: 'setLandscapeClass',
group: 'testgroup'
});
}
// On contextmenu - set the item as "visible" by the menu
if (editor.contextMenu) {
editor.contextMenu.addListener(function(element, selection) {
if (element.hasClass('ckLandscape') === false) {
return { setLandscapeItem: CKEDITOR.TRISTATE_ON };
}
});
}
Inside the init function of the plugin you add.
You can see that I added this line:
if (element.hasClass('ckLandscape') === false) {
(Which you can remove) only to give you an example of how to show the item in the menu only if the ckLandscape class doesn't exists for this image.
The updated version of the jsfiddle is here:
https://jsfiddle.net/7nm9q1qv/1/

Showing Customized Context Menu on clicking shapes(objects of fabric.js) in Canvas

I'm using fabric.js to create shapes on canvas . on right click on the shapes i want to show a context menu based on the shape selected. I'm able to capture the right click event and find which object the right click is done. but i don know how to show a context menu from a javascript (something like contextmenu.show). below is the code which im using to find the object. Any one please help.
$('.upper-canvas').bind('contextmenu', function (e) {
var objectFound = false;
var clickPoint = new fabric.Point(e.offsetX, e.offsetY);
e.preventDefault();
canvas.forEachObject(function (obj) {
if (!objectFound && obj.containsPoint(clickPoint)) {
objectFound = true;
// here need to set a customized context menu and show it
// but dont now how to do so.
}
});
});
Using jquery-ui-contextmenu you could instantiate a context menu on the canvas and modify the menu entries depending on the target.
(Note that the code is untested, but it should show the idea. Have a look at the API docs for details.)
$(document).contextmenu({
delegate: ".upper-canvas",
menu: [...], // default menu
beforeOpen: function (event, ui) {
var clickPoint = new fabric.Point(event.offsetX, event.offsetY);
// find the clicked object and re-define the menu or
// optionally return false, to prevent opening the menu:
// return false;
// En/disable single entries:
$(document).contextmenu("enableEntry", ...);
// Show/hide single entries:
$(document).contextmenu("showEntry", ...);
// Redefine the whole menu:
$(document).contextmenu("replaceMenu", ...);
},
select: function(event, ui) {
// evaluate selected entry...
}
});

jstree: differentiate between left/middle mouse click

The following code sets up my tree (the s:property tags are struts2 stuff):
$(function () {
$("#networkTree").jstree({
"json_data" : {
"ajax" : {
"url" : "<s:property value='networkTreeDataUrl'/>"
}
},
"plugins" : [ "themes", "json_data", "ui" ],
"themes" : {
"theme" : "default",
"dots" : true,
"icons" : false
},
"core" : {
"html_titles" : true
}
}).bind("select_node.jstree", function (event, data) {
window.location.href = "<s:property value='companyDetailsUrl'/>" + "?companyId=" + data.rslt.obj.attr("id");
})
});
When the user left clicks a tree item the window URL changes depending on the companyDetailsUrl. So far correct but I'd like the browser (chrome) to open the link in a new tab when I click the middle mouse button as usual. It seems any mouse click selects the tree node and this triggers the bound event which replaces the window.location. What's the best way to prevent this?
I'd go for the which-property of the eventhandler.
This provides an easy way to distinguish buttons, according the jQuery-documentation:
event.which also normalizes button presses (mousedown and mouseupevents), reporting 1 for left button, 2 for middle, and 3 for right.
//rest of the code omitted
.bind("select_node.jstree", function (event, data) {
if(event.which == 1) {
//open link in current window
window.location.href = YOURURL;
} else if (event.which == 3) {
//open link in new window
window.open(YOURURL, '_blank');
}
})
Note that you have to replace YOURURL (obviously). I omitted it for readability.
Further note that this will most likely open a new window, instead of a new tab. For further reading on why it opens a new window and how you can open a new tab I recommend reading this question.

TinyMCE adding toggle style

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

Categories