Custom context menu in Firefox with Add-on SDK? - javascript

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.

Related

How to detect when a menu is rendered in tinymce 4?

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

Get Id of TitlePane programatically defined to show/hide

I am very new to Dojo and this is what I am trying to do. I have a titlepane which is programatically declared using the code below:
var pane = this._createTitlePane(config.widgets.title, config.widgets.position,
config.widgets.open);
_createTitlePane: function (title, position, open, optclass) {
var tp = new TitlePane({
title: title,
open: open
}).placeAt(this.sidebar, position);
domClass.add(tp.domNode, 'titlePaneBottomFix titlePaneRightFix');
if (optclass) {
domClass.add(tp.domNode, optclass);
}
tp.startup();
return tp;
},
Later I am trying to hide this title pane when a button is clicked using esri.hide. My question is how do I get a reference to this title pane? There's no Id when it is defined.
When I look in the chrome debugger, I see the below line highlights the widget
<div class="titlePaneBottomFix titlePaneRightFix dijitTitlePane" title="" role="group" id="dijit_TitlePane_1" widgetid="dijit_TitlePane_1">
If I try to do something like esri.hide(dojo.byId("dijit_TitlePane_1")), then it hides the widget. But can I refer to the title pane using this widget Id?
You may want to just give the title pane its own id in the function:
_createTitlePane: function (title, position, open, optclass, paneId) {
var tp = new TitlePane({
title: title,
id: paneId, // TitlePane id here
open: open
}).placeAt(this.sidebar, position);
domClass.add(tp.domNode, 'titlePaneBottomFix titlePaneRightFix');
if (optclass) {
domClass.add(tp.domNode, optclass);
}
tp.startup();
return tp;
}
Then you can refer to it with and hide it with:
esri.hide(dijit.byId("theIdYouGaveIt").domNode);
To understand the difference between dojo.byId and dijit.byId, this link may help.
Also, if you're creating this in your own custom widget, you can also make the title pane a local reference, ie: this.tp = new TitlePane({...}). Anywhere you need to access it from inside the widget, you can simply call "this.tp". Outside of the widget, you can access it using dot notataion: myWidget.tp.doSomething(). Better yet, if you create it declaratively in a template like this: <div data-dojo-type=dijit/TitlePane" data-dojo-attach-point="tp" ...></div>, when the widget is instantiated it will automatically have a handle to "this.tp" via the attach point.

Chrome extension context menu - modify text box selection

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

How to check whether a twitter bootstrap popover is visible or not?

I have an element $('#anElement') with a potential popover attached, like
<div id="anElement" data-original-title="my title" data-trigger="manual" data-content="my content" rel="popover"></div>
I just would like to know how to check whether the popover is visible or not: how this can be accomplished with jQuery?
If this functionality is not built into the framework you are using (it's no longer twitter bootstrap, just bootstrap), then you'll have to inspect the HTML that is generated/modified to create this feature of bootstrap.
Take a look at the popupver documentation. There is a button there that you can use to see it in action. This is a great place to inspect the HTML elements that are at work behind the scene.
Crack open your chrome developers tools or firebug (of firefox) and take a look at what it happening. It looks like there is simply a <div> being inserted after the button -
<div class="popover fade right in" style="... />
All you would have to do is check for the existence of that element. Depending on how your markup is written, you could use something like this -
if ($("#popoverTrigger").next('div.popover:visible').length){
// popover is visible
}
#popoverTrigger is the element that triggered that popover to appear in the first place and as we noticed above, bootstrap simply appends the popover div after the element.
There is no method implemented explicitly in the boostrap popover plugin so you need to find a way around that. Here's a hack that will return true or false wheter the plugin is visible or not.
var isVisible = $('#anElement').data('bs.popover').tip().hasClass('in');
console.log(isVisible); // true or false
It accesses the data stored by the popover plugin which is in fact a Popover object, calls the object's tip() method which is responsible for fetching the tip element, and then checks if the element returned has the class in, which is indicative that the popover attached to that element is visible.
You should also check if there is a popover attached to make sure you can call the tip() method:
if ($('#anElement').data('bs.popover') instanceof Popover) {
// do your popover visibility check here
}
In the current version of Bootstrap, you can check whether your element has aria-describedby set. The value of the attribute is the id of the actual popover.
So for instance, if you want to change the content of the visible popover, you can do:
var popoverId = $('#myElement').attr('aria-describedby');
$('#myElement').next(popoverid, '.popover-content').html('my new content');
This checks if the given div is visible.
if ($('#div:visible').length > 0)
or
if ($('#div').is(':visible'))
Perhaps the most reliable option would be listening to shown/hidden events, as demonstrated below. This would eliminate the necessity of digging deep into the DOM that could be error prone.
var isMyPopoverVisible = false;//assuming popovers are hidden by default
$("#myPopoverElement").on('shown.bs.popover',function(){
isMyPopoverVisible = true;
});
$("#myPopoverElement").on('hidden.bs.popover',function(){
isMyPopoverVisible = false;
});
These events seem to be triggered even if you hide/show/toggle the popover programmatically, without user interaction.
P. S. tested with BS3.
Here is simple jQuery plugin to manage this. I've added few commented options to present different approaches of accessing objects and left uncommented that of my favor.
For current Bootstrap 4.0.0 you can take bundle with Popover.js: https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.bundle.min.js
// jQuery plugins
(function($)
{
// Fired immiedately
$.fn.isPopover = function (options)
{
// Is popover?
// jQuery
//var result = $(this).hasAttr("data-toggle");
// Popover API
var result = !!$(this).data('bs.popover');
if (!options) return result;
var $tip = this.popoverTip();
if (result) switch (options)
{
case 'shown' :
result = $tip.is(':visible');
break;
default:
result = false;
}
return result;
};
$.fn.popoverTip = function ()
{
// jQuery
var tipId = '#' + this.attr('aria-describedby');
return $(tipId);
// Popover API by id
//var tipId = this.data('bs.popover').tip.id;
//return $(tipId);
// Popover API by object
//var tip = this.data('bs.popover').tip; // DOM element
//return $(tip);
};
// Load indicator
$.fn.loadIndicator = function (action)
{
var indicatorClass = 'loading';
// Take parent if no container has been defined
var $container = this.closest('.loading-container') || this.parent();
switch (action)
{
case 'show' :
$container.append($('<div>').addClass(indicatorClass));
break;
case 'hide' :
$container.find('.' + indicatorClass).remove();
break;
}
};
})(jQuery);
// Usage
// Assuming 'this' points to popover object (e.g. an anchor or a button)
// Check if popover tip is visible
var isVisible = $(this).isPopover('shown');
// Hide all popovers except this
if (!isVisible) $('[data-toggle="popover"]').not(this).popover('hide');
// Show load indicator inside tip on 'shown' event while loading an iframe content
$(this).on('shown.bs.popover', function ()
{
$(this).popoverTip().find('iframe').loadIndicator('show');
});
Here a way to check the state with Vanilla JS.
document.getElementById("popover-dashboard").nextElementSibling.classList.contains('popover');
This works with BS4:
$(document).on('show.bs.tooltip','#anElement', function() {
$('#anElement').data('isvisible', true);
});
$(document).on('hidden.bs.tooltip','#anElement', function() {
$('#anElement').data('isvisible', false);
});
if ($('#anElement').data('isvisible'))
{
// popover is visible
$('#tipUTAbiertas').tooltip('hide');
$('#tipUTAbiertas').tooltip('show');
}
Bootstrap 5:
const toggler = document.getElementById(togglerId);
const popover = bootstrap.Popover.getInstance(toggler);
const isShowing = popover && popover.tip && popover.tip.classList.contains('show');
Using a popover with boostrap 4, tip() doesn't seem to be a function anymore. This is one way to check if a popover is enabled, basically if it has been clicked and is active:
if ($('#element').data('bs.popover')._activeTrigger.click == true){
...do something
}

Modify CKEditor link dialog to add custom attribute to links

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.

Categories

Resources