In tinymce 4, the menu bar is rendered but each menu is rendered only on click.
To illustrate this, notice that each menu from the menu bar has the mce-menu class.
At any time, if no menu is open, if you try to get the set of menus, you'll fail because they aren't rendered yet:
var menuSet = $('.mce-menu');
// menuSet.length : 0
But if you click a menu bar header, let say the insert menu, it will be rendered and opened. Now, keeping it open, going to the console and retrying:
var menuSet = $('.mce-menu');
// menuSet.length : 1
and you'll get the opened menu.
Now if you close it clicking anywhere out of the opened menu, and retrying:
var menuSet = $('.mce-menu');
// menuSet.length : 1
... the menu is not removed from the DOM. It's a good news: since the menu was rendered once, we can get and manipulate it.
I have some DOM manipulation to do with each .mce-menu elements, but I'll have to now when each menu is opened the first time.
But how to handle a such event ?
I can't get any clue from the official documentation nor the forums, or anywhere.
It's definitely possible, but we both were not aware enough of how JS events are managed.
I tried to code my events handlers the old way :
$('body').on('click', function() {
do_stuff();
});
While I had to do it the new, correct way :
$('body').on('click', '.mce-btn', function() {
do_stuff();
});
With this, the events are correctly managed.
Try to use onPostrender funtion :
editor.addMenuItem("mybtn", {
type: "menuitem",
name: 'mybtn',
onPostRender:function (){
// write your code here//
},
I resolved the issue by writting a convenient `Tinymce 4 plugin focused on that purpose.
Of course this plugin is open-sourced by the GNU GPL v2 license, following the original Tinymce licensing policy.
Tinymce Plugin MenusController:
https://github.com/sirap-group/tinymce-plugin-menuscontroller
But I didn't wrote the documentation yet, my apologies.
However, here is how you can use it:
Install the plugin
Download the latest release tarball from github or, even better, install it from bower:
bower install tinymce-plugin-menuscontroller
If you don't know bower, discover it here: https://bower.io (npm i -g bower; bower --help).
The npm package isn't available yet, I'd provide it soon (but any Pull Request on github is welcome...).
By default, the plugin folder would be downoaded and placed in ./bower_components. If you've installed tinymce the same way, you've got also ./bower_components/tinymce or ./bower_components/tinymce-dist.
You don't need to add the script to your index.html file because tinymce load it itself if you setup it correctly.
So you need to :
symlink it to the tinymce plugin folder:
$ cd ./bower_components/tinymce/plugins
$ ln -s ../../tinymce-plugin-menuscontroller menuscontroller
load it in tinymce init. For example:
tinymce.init({
selector: 'textarea',
// [...]
plugins: 'menuscontroller'
})
Get the plugin instance:
var editor = window.tinymce.activeEditor
var menusCtl = editor.plugins.menuscontroller
// at this point, if menusCtl is undefined, something gone wrong in the setup step: please check the previous steps.
Plugin API (v0.2.1)
Instance Methods
Get the menu bar:
menusCtl.getMenubar()
Get each menu by the name it was registered with:
menusCtl.getMenuByName(String: name)
Get the toolbars
menusCtl.getToolbars
Events
Event: menusController:mceMenuRendered event
When any tinymce menu is rendered
$('body').on('menusController:mceMenuRendered', function (evt, menuDomNode) {
console.log(menuDomNode)
})
The menusController:mceMenuRendered event is called one for each menu of the active editor menubar, when it is rendered, so when the user click the dropdown menu (File link for the "file" menu, Insert for the "insert" menu, etc...).
Event: menusController:mceMenuItemRendered:<menuDomID>
When any menu item is rendered. Let's say we've created a menu item with the my-custom-menu-item identifier. So tinymce set its DOM ID to my-custom-menu-item. Thus, the MenusController plugin will create and bind to body the following event:
menusController:mceMenuItemRendered:my-custom-menu-item
So you can handle the rendered event of your custom menu item listening on it:
$('body').on('menusController:mceMenuItemRendered:my-custom-menu-item',
function (evt, menuItemDomNode) {
console.log(menuItemDomNode)
}
)
MenusController API (v0.3.0+)
A the time of wrinting (Monday, mars the 13th, 2017), the last released version is the v0.2.1. But the v0.3.0 is planned to be released soon, and will provide a new event, more useful than the last.
Event: menusController:mceMenuItemRendered
When you need to know the menu item ID to handle the event menusController:mceMenuItemRendered:<menuDomID> and get the menu item DOM Node as callback argument, the event menusController:mceMenuItemRendered don't needs it but provides it as callback argument for each new rendered menu item:
$('body').on('menusController:mceMenuItemRendered',
function (evt, menuItemID) {
console.log(menuItemID) // 'my-custom-menu-item'
// So you can hanlde all menu item even if you don't know its ID
// And you can also handle the DOM Node with the selector by ID
var selector = '#' + menuItemID
var menuItem = $(selector)
console.log(menuItem) // jQuery object (the menu item)
}
)
With tinymce you can fully customize the menu buttons via the editor object:
tinymce.init({
/*....*/
setup: function(editor) {
editor.addButton('mybutton', {
type: 'menubutton',
text: 'My button',
icon: false,
onclick: function(){
alert('Some Message');
},
menu: [{
text: 'Menu item 1',
onclick: function() {
alert('Some Message');
}
}]
});
}
/*....*/
});
Unfortunately you cannot insert html inside the text property, but i think you can do that with more research. You can also create a callback function for the click event on the menu button.
Personally, I will use tinymce official api to modify the dom instead of doing some other event driven dom manipulation.
You can find more a good example here
Related
I'm trying to run a ckeditor plugin "showblocks" with several different approaches from question and answer but nothing work. Does anyone know how to run a plugin without click?
CKEDITOR.tools.callFunction(199, this);
CKEDITOR.instances['editor1'].execCommand('show blocks');
The name of the command is "showblocks", not "show blocks" (there is no space between the words).
CKEDITOR.instances['editor1'].execCommand('showblocks');
EDIT: After reading your comments, you are trying to execute showblocks automatically when ckeditor loads, but you can't do that until ckeditor fully loads and is ready for interaction. Also, the configuration option is called startupOutlineBlocks. You have 3 options.
First option (enable showblocks globally using startupOutlineBlocks ):
CKEDITOR.config.startupOutlineBlocks = true;
Second option (enable showblocks for specific instance):
CKEDITOR.replace('editor1', {
startupOutlineBlocks: true
});
Third option (execute showblocks command after ckeditor fully loads using instanceReady event ):
CKEDITOR.replace('editor1', {
on: {
instanceReady: function(evt) {
this.execCommand('showblocks');
}
}
});
You don't need third option if you have enabled first or second option which are better anyway.
Often I realize halfway through a notebook that I forgot an import and I want to move it to the top of the notebook (where I try to keep most of my imports). Is there a way to add a keyboard shortcut to ~/.jupyter/custom/custom.js that moves a cell to the top of a notebook?
Currently I do this by cutting the cell, scrolling to the top of the notebook, pasting and scrolling back down to where I was (often losing my place on the way back).
Below is some code from fastai forums to accomplish a different task: going to the running cell:
Jupyter.keyboard_manager.command_shortcuts.add_shortcut('CMD-I', {
help : 'Go to Running cell',
help_index : 'zz',
handler : function (event) {
setTimeout(function() {
// Find running cell and click the first one
if ($('.running').length > 0) {
//alert("found running cell");
$('.running')[0].scrollIntoView();
}}, 250);
return false;
}
});
It's partially documented; however you have to know a little JavaScript to create a keyboard shortcut.
As you've figured out, edit custom.js to bind a keyboard shortcut, as mentioned in the documentation (the second method must be used if the action is not built-in)
For the documentation, read the source code (either online or in site-packages/notebook/static/notebook/js/*.js if you installed jupyter locally). In particular, actions.js contains example bindings and notebook.js contains the functions that you can call on the notebook.
This is an example. It has the disadvantage of modifying the jupyter clipboard. To avoid that, you might be able to look into the source code of notebook.move_cell_down function to see how it's implemented, and modify it correspondingly.
Jupyter.keyboard_manager.command_shortcuts.add_shortcut('cmdtrl-I', {
help : 'Move cell to first in notebook',
handler : function (env) {
var index = env.notebook.get_selected_index();
env.notebook.cut_cell();
env.notebook.select(0);
env.notebook.paste_cell_above();
env.notebook.select(index);
}
});
I wish to add a single menu item to the firefox context menu that shows up
only if the user right-clicks a specific url. I have a function to test the url.
I used to do this by subscribing to "popupshowing" event and:
var item = document.getElementById("custom-menu-id");
if (item) // show only for specific links
item.hidden = gContextMenu.onLink && acceptableURL(gContextMenu.linkURL);
I'm trying now to use the Add-on SDK, but there I no longer have access to gContextMenu.
This snippet from the documentation doesn't work for me:
var cm = require("sdk/context-menu");
cm.Item({
label: "Copy name to clipboard",
context: cm.URLContext("http://scholar.google*"),
contentScript: 'self.on("context", function(node) {return true; });'
});
Here I'd think that it should be possible to get something like node.URL and test that,
but it doesn't work. Maybe someone could suggest either how to get access to gContextMenu from the sdk or how to get URL from node or something else.
This code should only show the menu item when right-clicking on links directed at stackoverflow.com:
In your main module main.js:
exports.main = function() {
require("sdk/context-menu").Item({
label: "stack overflow link",
context: require("sdk/context-menu").SelectorContext("a[href]"),
contentScriptFile: require("sdk/self").data.url("check-node.js"),
onMessage: function(msg){},
});
};
In your content script (or content script file; in this case, check-node.js):
self.on("click",function(node,data){
self.postMessage("click");
});
self.on("context", function(node){
if(node.href && node.href.match(/^https?:\/\/(\w+\.)*stackoverflow\.com(\/.*)?$/))return true; //show in context menu if return value is true.
});
Re: Your sample code. You have URLContext which determines what pages your menu items show up on and this snippet self.on("context", function(node) {return true; }); causes the menu item to always show when URLContext conditions are met. Use SelectorContext instead. And test the node.href as shown above, returning true only if you want the menu item to show.
I am using CKEditor on a website and I need to be able to put a special data attributes on some of the links created through the editor. The user would indicate that they need the special attribute put on the link by checking a checkbox in the link dialog. I have managed to add a checkbox to the link dialog with the following code:
CKEDITOR.on('dialogDefinition', function(ev) {
if (ev.data.name == "link") {
var info = dialog.getContents("info");
info.elements.push({
type: "vbox",
id: "urlOptions",
children: [{
type: "hbox",
children: [{
id: "button",
type: "checkbox",
label: "Button",
commit: function(data) {
data.button = this.getValue()
console.log("commit", data.button, data);
},
setup: function(data) {
this.setValue(data.button);
console.log("setup", data.button, data);
}
}]
}]
});
}
});
Now I have two problems. The first one is that despite me adding the code in the commit and setup functions that should save the state of the checkbox, it's not working. It's as if the data can't hold any other parameters but the ones there by default.
The second problem is that I don't know how to add / remove the data attribute on my links. It seems to me that I should be doing that in my onOk callback on the dialog, however, the link dialog already has an onOk callback, so I'm not sure how I should be proceeding. I, of course, do not want to modify any of CKEditor's files directly.
How can I accomplish these things?
You best option is to modify the plugin. So you need to open the source code and find the file links.js in c:\ckeditor_3.6.5\ckeditor\_source\plugins\link\dialogs\
The source code is quite big (40k) but here you can modify the dialog however you want. When you finish just copy it to your plugins folder, and compress it: http://jscompress.com/
I have done what you need myself. The whole uncompressed file is here: https://gist.github.com/3940239
What you need to do:
First add this line just before the dialog "browse" button is appended. Approx. in line: 547:
{
id: "button",
type: "checkbox",
label: "Button",
setup: function (data) {
this.allowOnChange = false;
if (data.button)
this.setValue(data.button);
this.allowOnChange = true;
},
commit: function (data) {
data.button = this.getValue()
this.allowOnChange = false;
}
},
This part is actually your code. I just copied and pasted it.
Then, go to the onOk function, approx. in line 1211: and after commitContent add this code:
this.commitContent( data );
//My custom attribute
if (data.button)
attributes["custom-attribute"] = "button";
else
attributes["custom-attribute"] = "";
This will modify your link adding the attribute to the element such as text
That's it. Although, you may also like to load the current status of the checkbox. Then, go to the function parseLink . Approx. line 179 to load the attributes:
...
if ( element )
{
retval.button = element.getAttribute('custom-attribute');
var target = element.getAttribute( 'target' );
...
I am exploring the same thing now. What I have decided to do at this point is to:
Get a base ckeditor install without the link plugin
create my own fork of the link plugin, and add my changes to it, then activate and use this plugin within the group that link normally shows up in.
...working with it as a custom plugin (albeit a copy of the original) should alleviate the problem of upgrading. You just simply do not use the original link plugin at all. Copy and rename it, and use your custom copy instead.
I'm using ExtJS 3.2.1 and I need a component almost identical to the bundled HtmlEditor, with one exception: it must start editing the HTML source code directly. The reason I don't use a normal TextArea is that the user should be able to preview the result of his actions before submitting.
I've tried calling toggleSourceEdit(), as per ExtJS documentation, with no success. Debugging, I see that the editor object has the sourceEditMode property set to true, and the Source Edit button seems as if it was "pressed", but clicking on it does not render the typed HTML, and clicking it again goes to the Source Mode.
I've tried calling toggleSourceEdit() after the container show() method, on the container afterLayout listener and on the editor afterRender listener. I've tried also calling it on another button that I added to the container. The result is the same on every try.
The only other option I see is updating ExtJS to 3.3.0, but I haven't seem anything related on the changelogs. Either way, it's going to be my next step. EDIT: The app had another problems when updating, we'll make a bigger effort to update later. As of right now, we are using the HtmlEditor in its original setting.
Thanks!
ran into the same problem (using 3.3.0 by the way)
stumbled upon a fix by dumb luck. i have no idea why this works, but second time is the charm. call it twice in a row to achieve the desired effect..
HTMLEditor.toggleSourceEdit(true);
HTMLEditor.toggleSourceEdit(true);
hope that helps!
Rather calling toggleSourceEdit(), try to setup the configuration while you create HtmlEditor Object
Using toggleSourceEdit() caused some problems for me. One was that this seemed to put the editor somewhere in limbo between source edit and WYSIWYG mode unless I used a timeout of 250ms or so. It also puts the focus in that editor, and I don't want to start the form's focus in the editor, especially since it's below the fold and the browser scrolls to the focused html editor when it opens.
The only thing that worked for me was to extend Ext.form.HtmlEditor and then overwrite toggleSourceEdit, removing the focus command. Then adding a listener for toggling to the source editor when the component is initialized. This is for Ext 4.1 and up. For older versions, replace me.updateLayout() with me.doComponentLayout().
var Namespace = {
SourceEditor: Ext.define('Namespace.SourceEditor', {
extend: 'Ext.form.HtmlEditor',
alias: 'widget.sourceeditor',
initComponent: function() {
this.callParent(arguments);
},
toggleSourceEdit: function (sourceEditMode) {
var me = this,
iframe = me.iframeEl,
textarea = me.textareaEl,
hiddenCls = Ext.baseCSSPrefix + 'hidden',
btn = me.getToolbar().getComponent('sourceedit');
if (!Ext.isBoolean(sourceEditMode)) {
sourceEditMode = !me.sourceEditMode;
}
me.sourceEditMode = sourceEditMode;
if (btn.pressed !== sourceEditMode) {
btn.toggle(sourceEditMode);
}
if (sourceEditMode) {
me.disableItems(true);
me.syncValue();
iframe.addCls(hiddenCls);
textarea.removeCls(hiddenCls);
textarea.dom.removeAttribute('tabindex');
//textarea.focus();
me.inputEl = textarea;
} else {
if (me.initialized) {
me.disableItems(me.readOnly);
}
me.pushValue();
iframe.removeCls(hiddenCls);
textarea.addCls(hiddenCls);
textarea.dom.setAttribute('tabindex', -1);
me.deferFocus();
me.inputEl = iframe;
}
me.fireEvent('editmodechange', me, sourceEditMode);
me.updateLayout();
}
})
}
Then to use it:
Ext.create('Namespace.SourceEditor', {
/*regular options*/
listeners: {
initialize: function(thisEditor) {
thisEditor.toggleSourceEdit();
}
}
});
htmlEditor.toggleSourceEdit(true);
one time should be enough if you do this listening to the afterrender event of the editor.