I am making a Google Chrome extension that has tools to quickly make text reversed, upside-down, rainbow, etc and have browser action with those tools working perfectly but I also want a context menu that allows the user to use one of those tools on a text box/area selection. I have a lot of spare time because of school holidays but I just can't think of how to do it and I can't find the answer on Google or Stack Overflow. I figured out how to create a context menu and I just need to know how I can modify the selected text from the background script.
TL;DR: How can I modify the selected text in a text box/area with a context menu?
(I am also using jQuery.)
I guess you need to use jQuery caret plugin.
Code may look like below (untested)
replaceselector.js:
function replaceSelection(selector, replaceText) {
var input = $(selector);
var text = input.text();
input.text(text.substr(0, input.caret().start) + replaceText + text.substr(input.caret().end);
}
Inject content scripts to the target tab then call execScript from your background page
chrome.tabs.executeScript(tabid, {file: "jquery.min.js"} function() {
chrome.tabs.executeScript(tabid, {file: "jquery.caret.1.02.min.js"} function() {
chrome.tabs.executeScript(tabid, {file: "replaceSelector.js"} function() {
chrome.tabs.executeScript(tabid, {code: "replaceSelection('#myInputBox', 'Replace String');"});
});
});
});
Related
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);
}
});
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
In CKEditor, how does one disable drag and dropping?
I do not want people to accidentally drag and drop other elements of the page into their respective editors.
I assume this requires intercepting browser specific events and blocking them but I am unsure how to do this in CKEditor.
Thanks for your help!
TAKEN FROM THIS STACKOVERFLOW ANSWER
At first I try disable with config.removePlugins = 'dragdrop,basket'; but it doesn't work at all.
Then I found this link, which help me to solved this problem and write a plugin to do the job.
Anyway I wrote here how to do the trick too.
With a Litle modification I wrote this plugin:
To use it you have to create a folder inside of ./plugins named "dropoff". Then create a file named plugin.js and put this content:
CKEDITOR.plugins.add('dropoff', {
init: function (editor) {
function regectDrop(event) {
event.data.preventDefault(true);
};
editor.on('contentDom', function() {
editor.document.on('drop',regectDrop);
});
}
});
After it, you have to registrate it on ckeditor config.js.
config.extraPlugins = 'dropoff';
If you already using an extra pluging just put a "," before like this:
config.extraPlugins = 'mypreviousplugin,dropoff';
And be Happy! \o/
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'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.