227 lines
6.9 KiB
JavaScript
227 lines
6.9 KiB
JavaScript
/**
|
|
* @fileOverview Tab Related Confirmation Library for Firefox 3.5 or later
|
|
* @author SHIMODA "Piro" Hiroshi
|
|
* @version 5
|
|
* Basic usage:
|
|
*
|
|
* @example
|
|
* Components.utils.import('resource://my-modules/confirmWithTab.js');
|
|
*
|
|
* var checkbox = {
|
|
* label : 'Never ask me',
|
|
* checked : false
|
|
* };
|
|
*
|
|
* confirmWithTab({
|
|
* tab : gBrowser.selectedTab, // the related tab
|
|
* label : 'Ara you ready?', // the message
|
|
* value : 'treestyletab-undo-close-tree', // the internal key (optional)
|
|
* buttons : ['Yes', 'No'], // button labels
|
|
* checkbox : checkbox, // checkbox definition (optional)
|
|
* cancelEvents : ['SSTabRestoring'] // events to cancel this deferred (optional)
|
|
* })
|
|
* .next(function(aButtonIndex) {
|
|
* // the next callback receives the index of the clicked button.
|
|
* switch (aButtonIndex) {
|
|
* case 0: // Yes
|
|
* ...
|
|
* break;
|
|
* case 1: // No
|
|
* ...
|
|
* break;
|
|
* }
|
|
* // after the notification bar is closed, "checked" indicates
|
|
* // the state of the checkbox when the user clicks a button.
|
|
* var checked = checkbox.checked;
|
|
* ...
|
|
* })
|
|
* .error(function(aError) {
|
|
* // if the tab is closed, or any event in the cancelEvents array
|
|
* // is fired, then an exception is raised.
|
|
* ...
|
|
* });
|
|
*
|
|
* @license
|
|
* The MIT License, Copyright (c) 2010-2011 SHIMODA "Piro" Hiroshi
|
|
* http://github.com/piroor/fxaddonlibs/blob/master/license.txt
|
|
* @url http://github.com/piroor/fxaddonlibs/blob/master/confirmWithTab.js
|
|
* @url http://github.com/piroor/fxaddonlibs
|
|
*/
|
|
|
|
if (typeof window == 'undefined')
|
|
this.EXPORTED_SYMBOLS = ['confirmWithTab'];
|
|
|
|
// var namespace;
|
|
if (typeof namespace == 'undefined') {
|
|
// If namespace.jsm is available, export symbols to the shared namespace.
|
|
// See: http://github.com/piroor/fxaddonlibs/blob/master/namespace.jsm
|
|
try {
|
|
let ns = {};
|
|
Components.utils.import('resource://treestyletab-modules/lib/namespace.jsm', ns);
|
|
namespace = ns.getNamespaceFor('piro.sakura.ne.jp');
|
|
}
|
|
catch(e) {
|
|
namespace = (typeof window != 'undefined' ? window : null ) || {};
|
|
}
|
|
}
|
|
|
|
// This depends on JSDeferred.
|
|
// See: https://github.com/cho45/jsdeferred
|
|
if (typeof namespace.Deferred == 'undefined')
|
|
Components.utils.import('resource://treestyletab-modules/lib/jsdeferred.js', namespace);
|
|
|
|
var confirmWithTab;
|
|
(function() {
|
|
const currentRevision = 6;
|
|
|
|
var loadedRevision = 'confirmWithTab' in namespace ?
|
|
namespace.confirmWithTab.revision :
|
|
0 ;
|
|
if (loadedRevision && loadedRevision > currentRevision) {
|
|
confirmWithTab = namespace.confirmWithTab;
|
|
return;
|
|
}
|
|
|
|
const Ci = Components.interfaces;
|
|
|
|
confirmWithTab = function confirmWithTab(aOptions)
|
|
{
|
|
var deferred = new namespace.Deferred();
|
|
if (!aOptions.buttons) {
|
|
return deferred
|
|
.next(function() {
|
|
throw new Error('confirmWithTab requires one or more buttons!');
|
|
});
|
|
}
|
|
|
|
var buttonClicked = false;
|
|
var checkbox;
|
|
namespace.Deferred.next(function() {
|
|
aOptions.cancelEvents = (aOptions.cancelEvents || [])
|
|
.concat(['TabClose'])
|
|
.sort()
|
|
.join('\n')
|
|
.replace(/^(.+)$\n(\1\n)+/gm, '$1\n')
|
|
.split('\n');
|
|
|
|
var tab = aOptions.tab;
|
|
var b = getTabBrowserFromChild(tab);
|
|
var box = b.getNotificationBox(tab.linkedBrowser);
|
|
var accessKeys = [];
|
|
var numericAccessKey = 0;
|
|
var notification = box.appendNotification(
|
|
aOptions.label,
|
|
aOptions.value || 'confirmWithTab-'+encodeURIComponent(aOptions.label),
|
|
aOptions.image || null,
|
|
(aOptions.priority ?
|
|
(typeof aOptions.priority == 'number' ? aOptions.priority : box[aOptions.priority] ) :
|
|
box.PRIORITY_INFO_MEDIUM
|
|
),
|
|
aOptions.buttons.map(function(aLabel, aIndex) {
|
|
var accessKey;
|
|
var match = aLabel.match(/\s*\(&([0-9a-z])\)/i);
|
|
if (match) {
|
|
accessKey = match[1];
|
|
aLabel = aLabel.replace(match[0], '');
|
|
}
|
|
else {
|
|
let lastUniqueKey;
|
|
let sanitizedLabel = [];
|
|
for (let i = 0, maxi = aLabel.length; i < maxi; i++)
|
|
{
|
|
let possibleAccessKey = aLabel.charAt(i);
|
|
if (possibleAccessKey == '&' && i < maxi-1) {
|
|
possibleAccessKey = aLabel.charAt(i+1);
|
|
if (possibleAccessKey != '&') {
|
|
accessKey = possibleAccessKey;
|
|
}
|
|
i++;
|
|
}
|
|
else if (!lastUniqueKey &&
|
|
accessKeys.indexOf(possibleAccessKey) < 0) {
|
|
lastUniqueKey = possibleAccessKey;
|
|
}
|
|
sanitizedLabel.push(possibleAccessKey);
|
|
}
|
|
if (!accessKey)
|
|
accessKey = lastUniqueKey;
|
|
if (!accessKey || !/[0-9a-z]/i.test(accessKey))
|
|
accessKey = ++numericAccessKey;
|
|
|
|
aLabel = sanitizedLabel.join('');
|
|
}
|
|
|
|
accessKeys.push(accessKey);
|
|
|
|
return {
|
|
label : aLabel,
|
|
accessKey : accessKey,
|
|
callback : function() {
|
|
buttonClicked = true;
|
|
deferred.call(aIndex);
|
|
}
|
|
};
|
|
})
|
|
);
|
|
|
|
if (aOptions.checkbox) {
|
|
checkbox = notification.ownerDocument.createElement('checkbox');
|
|
checkbox.setAttribute('label', aOptions.checkbox.label);
|
|
if (aOptions.checkbox.checked)
|
|
checkbox.setAttribute('checked', 'true');
|
|
|
|
let container = notification.ownerDocument.createElement('hbox');
|
|
container.setAttribute('align', 'center');
|
|
container.appendChild(checkbox);
|
|
|
|
notification.appendChild(container);
|
|
}
|
|
|
|
var strip = b.tabContainer || b.mTabContainer;
|
|
var handleEvent = function handleEvent(aEvent) {
|
|
if (aEvent.type == 'DOMNodeRemoved' && aEvent.target != notification)
|
|
return;
|
|
if (aOptions.cancelEvents)
|
|
aOptions.cancelEvents.forEach(function(aEventType) {
|
|
strip.removeEventListener(aEventType, handleEvent, false);
|
|
});
|
|
if (notification.parentNode)
|
|
notification.parentNode.removeEventListener('DOMNodeRemoved', handleEvent, false);
|
|
if (aEvent.type != 'DOMNodeRemoved')
|
|
notification.close();
|
|
if (!buttonClicked)
|
|
deferred.fail(aEvent);
|
|
};
|
|
if (aOptions.cancelEvents)
|
|
aOptions.cancelEvents.forEach(function(aEventType) {
|
|
strip.addEventListener(aEventType, handleEvent, false);
|
|
});
|
|
notification.parentNode.addEventListener('DOMNodeRemoved', handleEvent, false);
|
|
});
|
|
|
|
return deferred
|
|
.next(function(aButtonIndex) {
|
|
buttonClicked = true;
|
|
if (aOptions.checkbox)
|
|
aOptions.checkbox.checked = checkbox.checked;
|
|
return aButtonIndex;
|
|
});
|
|
};
|
|
|
|
function getTabBrowserFromChild(aTabBrowserChild)
|
|
{
|
|
var b = aTabBrowserChild.ownerDocument.evaluate(
|
|
'ancestor::*[local-name()="tabbrowser"] | '+
|
|
'ancestor::*[local-name()="tabs"][@tabbrowser] |'+
|
|
'ancestor::*[local-name()="toolbar"]/descendant::*[local-name()="tabs"]',
|
|
aTabBrowserChild,
|
|
null,
|
|
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
null
|
|
).singleNodeValue;
|
|
return (b && b.tabbrowser) || b;
|
|
}
|
|
|
|
namespace.confirmWithTab = confirmWithTab;
|
|
})();
|