Move codes to override behaviors around bookmarks to a JS code module.
Moreover, it reduces use of eval.
This commit is contained in:
parent
978bda448d
commit
fdc472441b
@ -1,221 +1,15 @@
|
|||||||
Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
|
Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||||
XPCOMUtils.defineLazyModuleGetter(this,
|
XPCOMUtils.defineLazyModuleGetter(this,
|
||||||
'TreeStyleTabUtils', 'resource://treestyletab-modules/utils.js');
|
'TreeStyleTabUtils', 'resource://treestyletab-modules/utils.js');
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this,
|
||||||
|
'TreeStyleTabBookmarksService', 'resource://treestyletab-modules/bookmark.js');
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
let { ReferenceCounter } = Components.utils.import('resource://treestyletab-modules/ReferenceCounter.js', {});
|
let { ReferenceCounter } = Components.utils.import('resource://treestyletab-modules/ReferenceCounter.js', {});
|
||||||
let { inherit } = Components.utils.import('resource://treestyletab-modules/lib/inherit.jsm', {});
|
let { inherit } = Components.utils.import('resource://treestyletab-modules/lib/inherit.jsm', {});
|
||||||
var TreeStyleTabBookmarksService = inherit(TreeStyleTabService, {
|
|
||||||
|
|
||||||
get BookmarksService() {
|
|
||||||
if (!this._BookmarksService) {
|
|
||||||
this._BookmarksService = Components
|
|
||||||
.classes['@mozilla.org/browser/nav-bookmarks-service;1']
|
|
||||||
.getService(Components.interfaces.nsINavBookmarksService);
|
|
||||||
}
|
|
||||||
return this._BookmarksService;
|
|
||||||
},
|
|
||||||
_BookmarksService : null,
|
|
||||||
|
|
||||||
|
|
||||||
beginAddBookmarksFromTabs : function TSTBMService_beginAddBookmarksFromTabs(aTabs) /* PUBLIC API */
|
|
||||||
{
|
|
||||||
if (this._observing) return;
|
|
||||||
this._observing = true;
|
|
||||||
|
|
||||||
aTabs = this.cleanUpTabsArray(aTabs);
|
|
||||||
|
|
||||||
this._addingBookmarks = [];
|
|
||||||
this._addingBookmarkTreeStructure = aTabs.map(function(aTab) {
|
|
||||||
var parent = this.getParentTab(aTab);
|
|
||||||
return aTabs.indexOf(parent);
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
this.BookmarksService.addObserver(this, false);
|
|
||||||
},
|
|
||||||
|
|
||||||
endAddBookmarksFromTabs : function TSTBMService_endAddBookmarksFromTabs() /* PUBLIC API */
|
|
||||||
{
|
|
||||||
if (!this._observing) return;
|
|
||||||
this._observing = false;
|
|
||||||
|
|
||||||
this.BookmarksService.removeObserver(this);
|
|
||||||
this.handleNewBookmarksFromTabs(this._addingBookmarks, this._addingBookmarkTreeStructure);
|
|
||||||
this._addingBookmarks = [];
|
|
||||||
this._addingBookmarkTreeStructure = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
handleNewBookmarksFromTabs : function TSTBMService_handleNewBookmarksFromTabs(aBookarmks, aTreeStructure)
|
|
||||||
{
|
|
||||||
// this is adding bookmark folder from tabs, so ignroe the first item!
|
|
||||||
if (
|
|
||||||
aBookarmks.length == aTreeStructure.length+1 &&
|
|
||||||
this.BookmarksService.getItemType(aBookarmks[0].id) == this.BookmarksService.TYPE_FOLDER
|
|
||||||
) {
|
|
||||||
aBookarmks.shift();
|
|
||||||
}
|
|
||||||
else if (aBookarmks.length != aTreeStructure.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0, maxi = aBookarmks.length; i < maxi; i++)
|
|
||||||
{
|
|
||||||
let item = aBookarmks[i];
|
|
||||||
item.position = this.BookmarksService.getItemIndex(item.id);
|
|
||||||
}
|
|
||||||
aBookarmks.sort(function(aA, aB) {
|
|
||||||
return aA.position - aB.position;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let i = 0, maxi = aBookarmks.length; i < maxi; i++)
|
|
||||||
{
|
|
||||||
let item = aBookarmks[i];
|
|
||||||
if (this.BookmarksService.getItemType(item.id) != this.BookmarksService.TYPE_BOOKMARK)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
let uri = this.BookmarksService.getBookmarkURI(item.id);
|
|
||||||
if (/^about:treestyletab-group\b/.test(uri.spec)) {
|
|
||||||
let title = this.BookmarksService.getItemTitle(item.id);
|
|
||||||
let folderId = this.BookmarksService.createFolder(item.parent, title, item.position);
|
|
||||||
this.BookmarksService.removeItem(item.id);
|
|
||||||
item.id = folderId;
|
|
||||||
item.isFolder = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let index = aTreeStructure[i];
|
|
||||||
let parent = index > -1 ? aBookarmks[index] : null ;
|
|
||||||
if (parent && (parent.folder || parent).isFolder) {
|
|
||||||
let folder = parent.isFolder ? parent : parent.folder ;
|
|
||||||
this.BookmarksService.moveItem(item.id, folder.id, -1);
|
|
||||||
item.folder = folder;
|
|
||||||
}
|
|
||||||
if (parent && !parent.isFolder) {
|
|
||||||
PlacesUtils.setAnnotationsForItem(item.id, [{
|
|
||||||
name : this.kPARENT,
|
|
||||||
value : parent ? parent.id : -1,
|
|
||||||
expires : PlacesUtils.annotations.EXPIRE_NEVER
|
|
||||||
}]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
bookmarkTabSubtree : function TSTBMService_bookmarkTabSubtree(aTabOrTabs)
|
|
||||||
{
|
|
||||||
var tabs = aTabOrTabs;
|
|
||||||
if (!Array.isArray(tabs)) {
|
|
||||||
tabs = [aTabOrTabs];
|
|
||||||
}
|
|
||||||
|
|
||||||
var folderName = (this.isGroupTab(tabs[0], true) || tabs.length == 1) ?
|
|
||||||
tabs[0].label :
|
|
||||||
null ;
|
|
||||||
|
|
||||||
var b = this.getTabBrowserFromChild(tabs[0]);
|
|
||||||
var bookmarkedTabs = [];
|
|
||||||
for (let i = 0, maxi = tabs.length; i < maxi; i++)
|
|
||||||
{
|
|
||||||
let tab = tabs[i];
|
|
||||||
if (!this.isGroupTab(tab, i == 0)) bookmarkedTabs.push(tab);
|
|
||||||
bookmarkedTabs = bookmarkedTabs.concat(b.treeStyleTab.getDescendantTabs(tab));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.beginAddBookmarksFromTabs(bookmarkedTabs);
|
|
||||||
try {
|
|
||||||
window['piro.sakura.ne.jp'].bookmarkMultipleTabs.addBookmarkFor(bookmarkedTabs, folderName);
|
|
||||||
}
|
|
||||||
catch(e) {
|
|
||||||
}
|
|
||||||
this.endAddBookmarksFromTabs();
|
|
||||||
},
|
|
||||||
bookmarkTabSubTree : function() { return this.bookmarkTabSubtree.apply(this, arguments); }, // obsolete, for backward compatibility
|
|
||||||
|
|
||||||
getParentItem : function TSTBMService_getParentItem(aId)
|
|
||||||
{
|
|
||||||
if (aId < 0) return -1;
|
|
||||||
var annotations = PlacesUtils.getAnnotationsForItem(aId);
|
|
||||||
for (let i in annotations)
|
|
||||||
{
|
|
||||||
if (annotations[i].name != this.kPARENT) continue;
|
|
||||||
return parseInt(annotations[i].value);
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
},
|
|
||||||
|
|
||||||
getTreeStructureFromItems : function TSTBMService_getTreeStructureFromItems(aIDs, aDefaultParentID)
|
|
||||||
{
|
|
||||||
/* this returns a result same to getTreeStructureFromTabs().
|
|
||||||
[A] => -1 (parent is not in this tree)
|
|
||||||
[B] => 0 (parent is 1st item in this tree)
|
|
||||||
[C] => 0 (parent is 1st item in this tree)
|
|
||||||
[D] => 2 (parent is 2nd in this tree)
|
|
||||||
[E] => -1 (parent is not in this tree, and this creates another tree)
|
|
||||||
[F] => 0 (parent is 1st item in this another tree)
|
|
||||||
*/
|
|
||||||
if (aDefaultParentID === void(0))
|
|
||||||
aDefaultParentID = -1;
|
|
||||||
|
|
||||||
/* Get array of parents. The index becomes to -1,
|
|
||||||
if there is NO PARENT or the parent is THE TAB ITSELF. */
|
|
||||||
var treeStructure = aIDs.map(function(aId, aIndex) {
|
|
||||||
let id = this.getParentItem(aId);
|
|
||||||
let index = aIDs.indexOf(id);
|
|
||||||
return index >= aIndex ? aDefaultParentID : index ;
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
/* Correct patterns like:
|
|
||||||
[TabA]
|
|
||||||
[TabB] - this has no parent
|
|
||||||
[TabC] - TabA's child
|
|
||||||
to:
|
|
||||||
[TabA]
|
|
||||||
[TabB]
|
|
||||||
[TabC]
|
|
||||||
*/
|
|
||||||
treeStructure = treeStructure.reverse();
|
|
||||||
treeStructure = treeStructure.map(function(aPosition, aIndex) {
|
|
||||||
if (aIndex > 0 &&
|
|
||||||
aIndex < treeStructure.length-1 &&
|
|
||||||
aPosition < 0) {
|
|
||||||
aPosition = treeStructure[aIndex-1];
|
|
||||||
}
|
|
||||||
return aPosition;
|
|
||||||
});
|
|
||||||
treeStructure = treeStructure.reverse();
|
|
||||||
|
|
||||||
return this.cleanUpTreeStructureArray(treeStructure, aDefaultParentID);
|
|
||||||
},
|
|
||||||
|
|
||||||
// based on PlacesUtils.getURLsForContainerNode()
|
|
||||||
getItemIdsForContainerNode : function TSTBMService_getItemIdsForContainerNode(aNode)
|
|
||||||
{
|
|
||||||
var ids = [];
|
|
||||||
if (!aNode || !PlacesUtils.nodeIsContainer(aNode)) return ids;
|
|
||||||
|
|
||||||
var root = aNode;
|
|
||||||
if ('getContainerNodeWithOptions' in PlacesUtils) {
|
|
||||||
root = PlacesUtils.getContainerNodeWithOptions(root, false, true);
|
|
||||||
}
|
|
||||||
var oldViewer = root.parentResult.viewer;
|
|
||||||
var wasOpen = root.containerOpen;
|
|
||||||
if (!wasOpen) {
|
|
||||||
if (oldViewer)
|
|
||||||
root.parentResult.viewer = null;
|
|
||||||
root.containerOpen = true;
|
|
||||||
}
|
|
||||||
for (let i = 0, maxi = root.childCount; i < maxi; ++i)
|
|
||||||
{
|
|
||||||
let child = root.getChild(i);
|
|
||||||
if (PlacesUtils.nodeIsURI(child)) ids.push(child.itemId || -1);
|
|
||||||
}
|
|
||||||
if (!wasOpen) {
|
|
||||||
root.containerOpen = false;
|
|
||||||
if (oldViewer)
|
|
||||||
root.parentResult.viewer = oldViewer;
|
|
||||||
}
|
|
||||||
return ids;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
var TreeStyleTabBookmarksUIService = inherit(TreeStyleTabService, {
|
||||||
preInit : function TSTBMService_preInit()
|
preInit : function TSTBMService_preInit()
|
||||||
{
|
{
|
||||||
window.addEventListener('load', this, false);
|
window.addEventListener('load', this, false);
|
||||||
@ -231,250 +25,35 @@ var TreeStyleTabBookmarksService = inherit(TreeStyleTabService, {
|
|||||||
window.addEventListener('unload', this, false);
|
window.addEventListener('unload', this, false);
|
||||||
ReferenceCounter.add('window,unload,TSTBMService,false');
|
ReferenceCounter.add('window,unload,TSTBMService,false');
|
||||||
|
|
||||||
if (!('PlacesUIUtils' in window)) return;
|
|
||||||
|
|
||||||
if (!PlacesUIUtils.__treestyletab__done) {
|
|
||||||
var ns = Components.utils.import('resource:///modules/PlacesUIUtils.jsm', {});
|
|
||||||
var sv = this;
|
|
||||||
with (ns) {
|
|
||||||
|
|
||||||
{
|
|
||||||
let method = (TreeStyleTabUtils.getTreePref('compatibility.TabUtilities') && PlacesUIUtils.TU__openTabset) ?
|
|
||||||
'TU__openTabset' :
|
|
||||||
'_openTabset';
|
|
||||||
if (PlacesUIUtils.__openbookmarkintab__openTabset)
|
|
||||||
method = '__openbookmarkintab__openTabset';
|
|
||||||
TreeStyleTabUtils.doPatching(PlacesUIUtils[method], 'PlacesUIUtils.'+method, function(aName, aSource) {
|
|
||||||
var patched = eval(aName+' = '+aSource.replace(
|
|
||||||
/(function[^\(]*\([^\)]+)(\))/,
|
|
||||||
'$1, aFolderTitle$2'
|
|
||||||
).replace(
|
|
||||||
'{',
|
|
||||||
'{ var TSTTreeStructure = null, TSTPreviousTabs, TSTTreeStructureApplied = true, TSTOpenGroupBookmarkBehavior;'
|
|
||||||
).replace(
|
|
||||||
'var urls = [];',
|
|
||||||
'$& var ids = [];'
|
|
||||||
).replace(
|
|
||||||
'urls.push(item.uri);',
|
|
||||||
'if (item.uri) { $& ids.push(item.id); }'
|
|
||||||
).replace(
|
|
||||||
'this.markPageAsTyped(item.uri);',
|
|
||||||
'if (item.uri) { $& }'
|
|
||||||
).replace(
|
|
||||||
/(browserWindow\.(?:getBrowser\(\)|gBrowser)\.loadTabs\([^;]+\);)/,
|
|
||||||
'var TSTResult = browserWindow.TreeStyleTabBookmarksService.handleTabsOpenProcess(where, aEvent, browserWindow, ids, urls, aFolderTitle);\n' +
|
|
||||||
'TSTTreeStructure = TSTResult.treeStructure;\n' +
|
|
||||||
'TSTPreviousTabs = TSTResult.previousTabs;\n' +
|
|
||||||
'TSTTreeStructureApplied = TSTResult.treeStructureApplied;\n' +
|
|
||||||
'TSTOpenGroupBookmarkBehavior = TSTResult.behavior;\n' +
|
|
||||||
'$1'
|
|
||||||
).replace(
|
|
||||||
/(\}\)?)$/,
|
|
||||||
' if (TSTTreeStructure && TSTPreviousTabs) {\n' +
|
|
||||||
' let tabs = browserWindow.TreeStyleTabService.getNewTabsFromPreviousTabsInfo(browserWindow.gBrowser, TSTPreviousTabs);\n' +
|
|
||||||
' if (!TSTTreeStructureApplied)\n' +
|
|
||||||
' browserWindow.TreeStyleTabService.applyTreeStructureToTabs(tabs, TSTTreeStructure, TSTOpenGroupBookmarkBehavior & browserWindow.TreeStyleTabBookmarksService.kGROUP_BOOKMARK_EXPAND_ALL_TREE);\n' +
|
|
||||||
' if (!loadInBackground) {\n' +
|
|
||||||
' browserWindow.setTimeout(function() {\n' +
|
|
||||||
' browserWindow.gBrowser.treeStyleTab.scrollToTabs(tabs);\n' +
|
|
||||||
' }, browserWindow.gBrowser.treeStyleTab.collapseDuration); // start scroll after expanding animation is finished\n' +
|
|
||||||
' }\n' +
|
|
||||||
' }\n' +
|
|
||||||
'$1'
|
|
||||||
));
|
|
||||||
if (TreeStyleTabUtils.getTreePref('compatibility.TabUtilities') && method.indexOf('TU_') > -1)
|
|
||||||
window[method] = patched;
|
|
||||||
return patched;
|
|
||||||
}, 'TreeStyleTab');
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let method = (TreeStyleTabUtils.getTreePref('compatibility.TabUtilities') && PlacesUIUtils.TU_openContainerNodeInTabs) ?
|
|
||||||
'TU_openContainerNodeInTabs' :
|
|
||||||
'openContainerNodeInTabs';
|
|
||||||
TreeStyleTabUtils.doPatching(PlacesUIUtils[method], 'PlacesUIUtils.'+method, function(aName, aSource) {
|
|
||||||
var patched = eval(aName+' = '+aSource.replace(
|
|
||||||
/(this\._openTabset\([^\)]+)(\))/,
|
|
||||||
'{\n' +
|
|
||||||
' let w = "_getTopBrowserWin" in this ?\n' +
|
|
||||||
' this._getTopBrowserWin() :\n' +
|
|
||||||
' "_getCurrentActiveWin" in this ?\n' +
|
|
||||||
' this._getCurrentActiveWin() :\n' +
|
|
||||||
' window;\n' +
|
|
||||||
' let nodes = w.TreeStyleTabBookmarksService.getItemIdsForContainerNode(aNode);\n' +
|
|
||||||
' for (let i in nodes) {\n' +
|
|
||||||
' urlsToOpen[i].id = nodes[i];\n' +
|
|
||||||
' }\n' +
|
|
||||||
'}\n' +
|
|
||||||
'$1, aNode.title$2'
|
|
||||||
));
|
|
||||||
if (TreeStyleTabUtils.getTreePref('compatibility.TabUtilities') && method.indexOf('TU_') > -1)
|
|
||||||
window[method] = patched;
|
|
||||||
return patched;
|
|
||||||
}, 'TreeStyleTab');
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let method = (TreeStyleTabUtils.getTreePref('compatibility.TabUtilities') && PlacesUIUtils.TU_openURINodesInTabs) ?
|
|
||||||
'TU_openURINodesInTabs' :
|
|
||||||
'openURINodesInTabs';
|
|
||||||
TreeStyleTabUtils.doPatching(PlacesUIUtils[method], 'PlacesUIUtils.'+method, function(aName, aSource) {
|
|
||||||
var patched = eval(aName+' = '+aSource.replace(
|
|
||||||
'{',
|
|
||||||
'{\n' +
|
|
||||||
' var TSTBS, TSTUtils;\n' +
|
|
||||||
' {\n'+
|
|
||||||
' let w = "_getTopBrowserWin" in this ?\n' +
|
|
||||||
' this._getTopBrowserWin() :\n' +
|
|
||||||
' "_getCurrentActiveWin" in this ?\n' +
|
|
||||||
' this._getCurrentActiveWin() :\n' +
|
|
||||||
' window;\n' +
|
|
||||||
' TSTBS = w.TreeStyleTabBookmarksService;\n' +
|
|
||||||
' TSTUtils = w.TreeStyleTabUtils;\n' +
|
|
||||||
' PlacesUtils = w.PlacesUtils;\n' +
|
|
||||||
' }'
|
|
||||||
).replace(
|
|
||||||
'uri: aNodes[i].uri,',
|
|
||||||
'id: aNodes[i].itemId, $&'
|
|
||||||
).replace(
|
|
||||||
/(this\._openTabset\([^\)]+)(\))/,
|
|
||||||
'$1,\n' +
|
|
||||||
' TSTUtils.treeBundle\n' +
|
|
||||||
' .getFormattedString(\n' +
|
|
||||||
' PlacesUtils.nodeIsBookmark(aNodes[0]) ?\n' +
|
|
||||||
' "openSelectedPlaces.bookmarks" :\n' +
|
|
||||||
' "openSelectedPlaces.history",\n' +
|
|
||||||
' [aNodes[0].title, aNodes.length]\n' +
|
|
||||||
' )\n' +
|
|
||||||
'$2'
|
|
||||||
));
|
|
||||||
if (TreeStyleTabUtils.getTreePref('compatibility.TabUtilities') && method.indexOf('TU_') > -1)
|
|
||||||
window[method] = patched;
|
|
||||||
return patched;
|
|
||||||
}, 'TreeStyleTab');
|
|
||||||
}
|
|
||||||
|
|
||||||
PlacesUIUtils.__treestyletab__done = true;
|
|
||||||
|
|
||||||
} // end of with
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('PlacesCommandHook' in window && 'bookmarkCurrentPages' in PlacesCommandHook) {
|
if ('PlacesCommandHook' in window && 'bookmarkCurrentPages' in PlacesCommandHook) {
|
||||||
// Bookmark All Tabs
|
// Bookmark All Tabs
|
||||||
TreeStyleTabUtils.doPatching(PlacesCommandHook.bookmarkCurrentPages, 'PlacesCommandHook.bookmarkCurrentPages', function(aName, aSource) {
|
PlacesCommandHook.__treestyletab__bookmarkCurrentPages = PlacesCommandHook.bookmarkCurrentPages;
|
||||||
return eval(aName+' = '+aSource.replace(
|
PlacesCommandHook.bookmarkCurrentPages = function(...aArgs) {
|
||||||
'{',
|
TreeStyleTabBookmarksService.beginAddBookmarksFromTabs((function() {
|
||||||
'{\n' +
|
var tabs = [];
|
||||||
' TreeStyleTabBookmarksService.beginAddBookmarksFromTabs((function() {\n' +
|
var seen = {};
|
||||||
' var tabs = [];\n' +
|
var allTabs = gBrowser.mTabContainer.childNodes;
|
||||||
' var seen = {};\n' +
|
for (let i = 0, maxi = allTabs.length; i < maxi; i++)
|
||||||
' var allTabs = getBrowser().mTabContainer.childNodes;\n' +
|
{
|
||||||
' for (let i = 0, maxi = allTabs.length; i < maxi; i++)\n' +
|
let tab = allTabs[i];
|
||||||
' {\n' +
|
let uri = tab.linkedBrowser.currentURI.spec;
|
||||||
' let tab = allTabs[i];\n' +
|
if (uri in seen)
|
||||||
' let uri = tab.linkedBrowser.currentURI.spec;\n' +
|
continue;
|
||||||
' if (uri in seen) continue;\n' +
|
seen[uri] = true;
|
||||||
' seen[uri] = true;\n' +
|
tabs.push(tab);
|
||||||
' tabs.push(tab);\n' +
|
}
|
||||||
' }\n' +
|
return tabs;
|
||||||
' return tabs;\n' +
|
})());
|
||||||
' })());\n' +
|
try {
|
||||||
' try {'
|
return this.__treestyletab__bookmarkCurrentPages.apply(this, aArgs);
|
||||||
).replace(
|
}
|
||||||
/(\}\)?)$/,
|
finally {
|
||||||
' }\n' +
|
TreeStyleTabBookmarksService.endAddBookmarksFromTabs();
|
||||||
' catch(e) {\n' +
|
}
|
||||||
' }\n' +
|
|
||||||
' TreeStyleTabBookmarksService.endAddBookmarksFromTabs();\n' +
|
|
||||||
'$1'
|
|
||||||
));
|
|
||||||
}, 'TreeStyleTab');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleTabsOpenProcess : function TSTBMService_handleTabsOpenProcess(aWhere, aEvent, aBrowserWindow, aIDs, aURLs, aFolderTitle)
|
|
||||||
{
|
|
||||||
var result = {
|
|
||||||
behavior : undefined,
|
|
||||||
treeStructure : undefined,
|
|
||||||
previousTabs : undefined,
|
|
||||||
treeStructureApplied : false
|
|
||||||
};
|
};
|
||||||
if (
|
|
||||||
aEvent.type != 'drop' &&
|
|
||||||
aWhere.indexOf('tab') != 0 &&
|
|
||||||
aEvent.target.id != 'placesContext_openContainer:tabs' &&
|
|
||||||
aEvent.target.id != 'placesContext_openLinks:tabs' &&
|
|
||||||
aEvent.target != aEvent.target.parentNode._endOptOpenAllInTabs &&
|
|
||||||
aEvent.target.getAttribute('openInTabs') != 'true'
|
|
||||||
)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
var sv = aBrowserWindow.TreeStyleTabBookmarksService;
|
|
||||||
result.behavior = sv.openGroupBookmarkBehavior();
|
|
||||||
if (result.behavior & sv.kGROUP_BOOKMARK_SUBTREE) {
|
|
||||||
let treeStructure = result.behavior & sv.kGROUP_BOOKMARK_DONT_RESTORE_TREE_STRUCTURE ?
|
|
||||||
null :
|
|
||||||
sv.getTreeStructureFromItems(aIDs) ;
|
|
||||||
if (treeStructure) {
|
|
||||||
let parentTabs = treeStructure.filter(function(aParent) {
|
|
||||||
return aParent < 0;
|
|
||||||
});
|
|
||||||
let haveMultipleTrees = parentTabs.length != treeStructure.length;
|
|
||||||
if (result.behavior & sv.kGROUP_BOOKMARK_USE_DUMMY) {
|
|
||||||
let parentCount = 0;
|
|
||||||
let childCount = 0;
|
|
||||||
for (let i in treeStructure) {
|
|
||||||
if (treeStructure[i] == -1)
|
|
||||||
parentCount++;
|
|
||||||
else
|
|
||||||
childCount++;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
parentCount > 1 &&
|
|
||||||
(
|
|
||||||
result.behavior & sv.kGROUP_BOOKMARK_USE_DUMMY_FORCE ||
|
|
||||||
// when there is any orphan, then all of parents and orphans should be grouped under a dummy tab.
|
|
||||||
childCount < parentCount
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
aIDs.unshift(-1);
|
|
||||||
treeStructure = sv.getTreeStructureFromItems(aIDs, 0);
|
|
||||||
aURLs.unshift(sv.getGroupTabURI({
|
|
||||||
title: aFolderTitle,
|
|
||||||
temporary: TreeStyleTabUtils.getTreePref('openGroupBookmark.temporaryGroup')
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!haveMultipleTrees) {
|
|
||||||
// make the first item parent.
|
|
||||||
treeStructure = treeStructure.map(function(aParent, aIndex) {
|
|
||||||
if (aIndex == 0)
|
|
||||||
return aParent;
|
|
||||||
if (aParent < 0)
|
|
||||||
return 0;
|
|
||||||
return aParent;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.treeStructure = treeStructure;
|
|
||||||
result.previousTabs = aBrowserWindow.TreeStyleTabService.getTabsInfo(aBrowserWindow.gBrowser);
|
|
||||||
|
|
||||||
if (TreeStyleTabUtils.getTreePref('compatibility.TMP') &&
|
|
||||||
'TMP_Places' in aBrowserWindow &&
|
|
||||||
'openGroup' in aBrowserWindow.TMP_Places) {
|
|
||||||
result.treeStructureApplied = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sv.readyToOpenNewTabGroup(null, treeStructure, result.behavior & sv.kGROUP_BOOKMARK_EXPAND_ALL_TREE);
|
|
||||||
result.treeStructureApplied = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
destroy : function TSTBMService_destroy()
|
destroy : function TSTBMService_destroy()
|
||||||
{
|
{
|
||||||
window.removeEventListener('unload', this, false);
|
window.removeEventListener('unload', this, false);
|
||||||
@ -483,22 +62,6 @@ var TreeStyleTabBookmarksService = inherit(TreeStyleTabService, {
|
|||||||
ReferenceCounter.remove('window,EVENT_TYPE_TABS_DROP,TSTBMService,false');
|
ReferenceCounter.remove('window,EVENT_TYPE_TABS_DROP,TSTBMService,false');
|
||||||
},
|
},
|
||||||
|
|
||||||
// observer for nsINavBookmarksService
|
|
||||||
onItemAdded : function TSTBMService_onItemAdded(aID, aFolderID, aPosition)
|
|
||||||
{
|
|
||||||
this._addingBookmarks.push({
|
|
||||||
id : aID,
|
|
||||||
parent : aFolderID
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onItemRemoved : function TSTBMService_onItemRemoved(aID, aFolderID, aPosition) {},
|
|
||||||
onItemMoved : function TSTBMService_onItemMoved(aID, aFolderID, aPosition) {},
|
|
||||||
onItemChanged : function TSTBMService_onItemChanged(aID, aChange, aIsAnnotation, aNewValue) {},
|
|
||||||
onItemVisited : function TSTBMService_onItemVisited(aID, aHistoryID, aDate) {},
|
|
||||||
onBeginUpdateBatch : function TSTBMService_onBeginUpdateBatch() {},
|
|
||||||
onEndUpdateBatch : function TSTBMService_onEndUpdateBatch() {},
|
|
||||||
|
|
||||||
|
|
||||||
_onTabsDrop : function TSTBMService_onTabsDrop(aEvent)
|
_onTabsDrop : function TSTBMService_onTabsDrop(aEvent)
|
||||||
{
|
{
|
||||||
var tabs = aEvent.detail.tabs;
|
var tabs = aEvent.detail.tabs;
|
||||||
@ -533,7 +96,7 @@ var TreeStyleTabBookmarksService = inherit(TreeStyleTabService, {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
TreeStyleTabBookmarksService.preInit();
|
TreeStyleTabBookmarksUIService.preInit();
|
||||||
|
|
||||||
window.TreeStyleTabBookmarksService = TreeStyleTabBookmarksService;
|
window.TreeStyleTabBookmarksUIService = TreeStyleTabBookmarksUIService;
|
||||||
})();
|
})();
|
||||||
|
@ -327,13 +327,22 @@ var TreeStyleTabBookmarksServiceEditable = inherit(TreeStyleTabBookmarksService,
|
|||||||
},
|
},
|
||||||
_getSiblingItemsIterator : function TSTBMEditable_getSiblingItemsIterator(aId)
|
_getSiblingItemsIterator : function TSTBMEditable_getSiblingItemsIterator(aId)
|
||||||
{
|
{
|
||||||
return this._getItemsInFolderIterator(PlacesUtils.bookmarks.getFolderIdForItem(aId));
|
try {
|
||||||
|
var folderId = PlacesUtils.bookmarks.getFolderIdForItem(aId);
|
||||||
|
return this._getItemsInFolderIterator(folderId);
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
dump('TSTBMEditable_getSiblingItemsIterator('+aId+') failed.\n');
|
||||||
|
dump(e+'\n');
|
||||||
|
dump(new Error().stack+'\n');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
saveParentFor : function TSTBMEditable_saveParentFor(aId, aJustNow)
|
saveParentFor : function TSTBMEditable_saveParentFor(aId, aJustNow)
|
||||||
{
|
{
|
||||||
var newParentId = parseInt(this.menulist.value || -1);
|
var newParentId = parseInt(this.menulist.value || -1);
|
||||||
if (this.canceled || newParentId == this.getParentItem(aId)) return;
|
if (this.canceled || newParentId == this.getParentItem(aId))
|
||||||
|
return;
|
||||||
|
|
||||||
var itemsIterator = this._getSiblingItemsIterator(aId);
|
var itemsIterator = this._getSiblingItemsIterator(aId);
|
||||||
var items = [];
|
var items = [];
|
||||||
|
@ -726,6 +726,7 @@ pref("extensions.treestyletab.prefsVersion", 0);
|
|||||||
pref("extensions.treestyletab.debug.all", false);
|
pref("extensions.treestyletab.debug.all", false);
|
||||||
pref("extensions.treestyletab.debug.autoHide", false);
|
pref("extensions.treestyletab.debug.autoHide", false);
|
||||||
pref("extensions.treestyletab.debug.base", false);
|
pref("extensions.treestyletab.debug.base", false);
|
||||||
|
pref("extensions.treestyletab.debug.bookmark", false);
|
||||||
pref("extensions.treestyletab.debug.browser", false);
|
pref("extensions.treestyletab.debug.browser", false);
|
||||||
pref("extensions.treestyletab.debug.browserUIShowHideObserver", false);
|
pref("extensions.treestyletab.debug.browserUIShowHideObserver", false);
|
||||||
pref("extensions.treestyletab.debug.contentBridge", false);
|
pref("extensions.treestyletab.debug.contentBridge", false);
|
||||||
|
@ -370,10 +370,6 @@ var TreeStyleTabBase = inherit(TreeStyleTabConstants, {
|
|||||||
|
|
||||||
return behavior
|
return behavior
|
||||||
},
|
},
|
||||||
kDROPLINK_ASK : 0,
|
|
||||||
kDROPLINK_FIXED : 1 + 2,
|
|
||||||
kDROPLINK_LOAD : 1,
|
|
||||||
kDROPLINK_NEWTAB : 2,
|
|
||||||
|
|
||||||
openGroupBookmarkBehavior : function TSTBase_openGroupBookmarkBehavior()
|
openGroupBookmarkBehavior : function TSTBase_openGroupBookmarkBehavior()
|
||||||
{
|
{
|
||||||
@ -409,14 +405,6 @@ var TreeStyleTabBase = inherit(TreeStyleTabConstants, {
|
|||||||
}
|
}
|
||||||
return behavior;
|
return behavior;
|
||||||
},
|
},
|
||||||
kGROUP_BOOKMARK_ASK : 0,
|
|
||||||
kGROUP_BOOKMARK_FIXED : 1 + 2 + 4,
|
|
||||||
kGROUP_BOOKMARK_SUBTREE : 1,
|
|
||||||
kGROUP_BOOKMARK_SEPARATE : 2,
|
|
||||||
kGROUP_BOOKMARK_USE_DUMMY : 256,
|
|
||||||
kGROUP_BOOKMARK_USE_DUMMY_FORCE : 1024,
|
|
||||||
kGROUP_BOOKMARK_DONT_RESTORE_TREE_STRUCTURE : 512,
|
|
||||||
kGROUP_BOOKMARK_EXPAND_ALL_TREE : 2048,
|
|
||||||
|
|
||||||
bookmarkDroppedTabsBehavior : function TSTBase_bookmarkDroppedTabsBehavior()
|
bookmarkDroppedTabsBehavior : function TSTBase_bookmarkDroppedTabsBehavior()
|
||||||
{
|
{
|
||||||
@ -2282,48 +2270,6 @@ var TreeStyleTabBase = inherit(TreeStyleTabConstants, {
|
|||||||
return collapsedStates;
|
return collapsedStates;
|
||||||
},
|
},
|
||||||
|
|
||||||
getTreeStructureFromTabs : function TSTBase_getTreeStructureFromTabs(aTabs)
|
|
||||||
{
|
|
||||||
/* this returns...
|
|
||||||
[A] => -1 (parent is not in this tree)
|
|
||||||
[B] => 0 (parent is 1st item in this tree)
|
|
||||||
[C] => 0 (parent is 1st item in this tree)
|
|
||||||
[D] => 2 (parent is 2nd in this tree)
|
|
||||||
[E] => -1 (parent is not in this tree, and this creates another tree)
|
|
||||||
[F] => 0 (parent is 1st item in this another tree)
|
|
||||||
*/
|
|
||||||
return this.cleanUpTreeStructureArray(
|
|
||||||
aTabs.map(function(aTab, aIndex) {
|
|
||||||
let tab = this.getParentTab(aTab);
|
|
||||||
let index = tab ? aTabs.indexOf(tab) : -1 ;
|
|
||||||
return index >= aIndex ? -1 : index ;
|
|
||||||
}, this),
|
|
||||||
-1
|
|
||||||
);
|
|
||||||
},
|
|
||||||
cleanUpTreeStructureArray : function TSTBase_cleanUpTreeStructureArray(aTreeStructure, aDefaultParent)
|
|
||||||
{
|
|
||||||
var offset = 0;
|
|
||||||
aTreeStructure = aTreeStructure
|
|
||||||
.map(function(aPosition, aIndex) {
|
|
||||||
return (aPosition == aIndex) ? -1 : aPosition ;
|
|
||||||
})
|
|
||||||
.map(function(aPosition, aIndex) {
|
|
||||||
if (aPosition == -1) {
|
|
||||||
offset = aIndex;
|
|
||||||
return aPosition;
|
|
||||||
}
|
|
||||||
return aPosition - offset;
|
|
||||||
});
|
|
||||||
|
|
||||||
/* The final step, this validates all of values.
|
|
||||||
Smaller than -1 is invalid, so it becomes to -1. */
|
|
||||||
aTreeStructure = aTreeStructure.map(function(aIndex) {
|
|
||||||
return aIndex < -1 ? aDefaultParent : aIndex ;
|
|
||||||
}, this);
|
|
||||||
return aTreeStructure;
|
|
||||||
},
|
|
||||||
|
|
||||||
applyTreeStructureToTabs : function TSTBase_applyTreeStructureToTabs(aTabs, aTreeStructure, aExpandStates)
|
applyTreeStructureToTabs : function TSTBase_applyTreeStructureToTabs(aTabs, aTreeStructure, aExpandStates)
|
||||||
{
|
{
|
||||||
var b = this.getTabBrowserFromChild(aTabs[0]);
|
var b = this.getTabBrowserFromChild(aTabs[0]);
|
||||||
@ -2375,7 +2321,7 @@ var TreeStyleTabBase = inherit(TreeStyleTabConstants, {
|
|||||||
|
|
||||||
getTreeStructureFromTabBrowser : function TSTBase_getTreeStructureFromTabBrowser(aTabBrowser)
|
getTreeStructureFromTabBrowser : function TSTBase_getTreeStructureFromTabBrowser(aTabBrowser)
|
||||||
{
|
{
|
||||||
return this.getTreeStructureFromTabs(this.getAllTabs(aTabBrowser));
|
return utils.getTreeStructureFromTabs(this.getAllTabs(aTabBrowser));
|
||||||
},
|
},
|
||||||
|
|
||||||
applyTreeStructureToTabBrowser : function TSTBase_applyTreeStructureToTabBrowser(aTabBrowser, aTreeStructure, aExpandAllTree)
|
applyTreeStructureToTabBrowser : function TSTBase_applyTreeStructureToTabBrowser(aTabBrowser, aTreeStructure, aExpandAllTree)
|
||||||
|
489
modules/bookmark.js
Normal file
489
modules/bookmark.js
Normal file
@ -0,0 +1,489 @@
|
|||||||
|
/* ***** BEGIN LICENSE BLOCK *****
|
||||||
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||||
|
*
|
||||||
|
* The contents of this file are subject to the Mozilla Public License Version
|
||||||
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
* http://www.mozilla.org/MPL/
|
||||||
|
*
|
||||||
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||||
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||||
|
* for the specific language governing rights and limitations under the
|
||||||
|
* License.
|
||||||
|
*
|
||||||
|
* The Original Code is the Tree Style Tab.
|
||||||
|
*
|
||||||
|
* The Initial Developer of the Original Code is YUKI "Piro" Hiroshi.
|
||||||
|
* Portions created by the Initial Developer are Copyright (C) 2016
|
||||||
|
* the Initial Developer. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Contributor(s): YUKI "Piro" Hiroshi <piro.outsider.reflex@gmail.com>
|
||||||
|
*
|
||||||
|
* Alternatively, the contents of this file may be used under the terms of
|
||||||
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||||
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||||
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||||
|
* of those above. If you wish to allow use of your version of this file only
|
||||||
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||||
|
* use your version of this file under the terms of the MPL, indicate your
|
||||||
|
* decision by deleting the provisions above and replace them with the notice
|
||||||
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||||
|
* the provisions above, a recipient may use your version of this file under
|
||||||
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||||
|
*
|
||||||
|
* ***** END LICENSE BLOCK ******/
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ['TreeStyleTabBookmarksService'];
|
||||||
|
|
||||||
|
const Cc = Components.classes;
|
||||||
|
const Ci = Components.interfaces;
|
||||||
|
const Cu = Components.utils;
|
||||||
|
|
||||||
|
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||||
|
|
||||||
|
Cu.import('resource:///modules/PlacesUIUtils.jsm');
|
||||||
|
Cu.import('resource://gre/modules/PlacesUtils.jsm');
|
||||||
|
|
||||||
|
Cu.import('resource://treestyletab-modules/lib/inherit.jsm');
|
||||||
|
Cu.import('resource://treestyletab-modules/constants.js');
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils');
|
||||||
|
|
||||||
|
function mydump(aString) {
|
||||||
|
if (utils.isDebugging('bookmark'))
|
||||||
|
dump(aString);
|
||||||
|
}
|
||||||
|
|
||||||
|
var TreeStyleTabBookmarksService = inherit(TreeStyleTabConstants, {
|
||||||
|
get BookmarksService() {
|
||||||
|
if (!this._BookmarksService) {
|
||||||
|
this._BookmarksService = Cc['@mozilla.org/browser/nav-bookmarks-service;1']
|
||||||
|
.getService(Ci.nsINavBookmarksService);
|
||||||
|
}
|
||||||
|
return this._BookmarksService;
|
||||||
|
},
|
||||||
|
_BookmarksService : null,
|
||||||
|
|
||||||
|
|
||||||
|
beginAddBookmarksFromTabs : function TSTBMService_beginAddBookmarksFromTabs(aTabs) /* PUBLIC API */
|
||||||
|
{
|
||||||
|
if (this._observing ||
|
||||||
|
!aTabs ||
|
||||||
|
aTabs.length <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._observing = true;
|
||||||
|
|
||||||
|
var TST = aTabs[0].ownerDocument.defaultView.TreeStyleTabService;
|
||||||
|
aTabs = TST.cleanUpTabsArray(aTabs);
|
||||||
|
|
||||||
|
this._addingBookmarks = [];
|
||||||
|
this._addingBookmarkTreeStructure = aTabs.map(function(aTab) {
|
||||||
|
var parent = TST.getParentTab(aTab);
|
||||||
|
return aTabs.indexOf(parent);
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.BookmarksService.addObserver(this, false);
|
||||||
|
},
|
||||||
|
|
||||||
|
endAddBookmarksFromTabs : function TSTBMService_endAddBookmarksFromTabs() /* PUBLIC API */
|
||||||
|
{
|
||||||
|
if (!this._observing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._observing = false;
|
||||||
|
|
||||||
|
this.BookmarksService.removeObserver(this);
|
||||||
|
this.handleNewBookmarksFromTabs(this._addingBookmarks, this._addingBookmarkTreeStructure);
|
||||||
|
this._addingBookmarks = [];
|
||||||
|
this._addingBookmarkTreeStructure = [];
|
||||||
|
},
|
||||||
|
|
||||||
|
handleNewBookmarksFromTabs : function TSTBMService_handleNewBookmarksFromTabs(aBookarmks, aTreeStructure)
|
||||||
|
{
|
||||||
|
// this is adding bookmark folder from tabs, so ignroe the first item!
|
||||||
|
if (
|
||||||
|
aBookarmks.length == aTreeStructure.length+1 &&
|
||||||
|
this.BookmarksService.getItemType(aBookarmks[0].id) == this.BookmarksService.TYPE_FOLDER
|
||||||
|
) {
|
||||||
|
aBookarmks.shift();
|
||||||
|
}
|
||||||
|
else if (aBookarmks.length != aTreeStructure.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0, maxi = aBookarmks.length; i < maxi; i++)
|
||||||
|
{
|
||||||
|
let item = aBookarmks[i];
|
||||||
|
item.position = this.BookmarksService.getItemIndex(item.id);
|
||||||
|
}
|
||||||
|
aBookarmks.sort(function(aA, aB) {
|
||||||
|
return aA.position - aB.position;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0, maxi = aBookarmks.length; i < maxi; i++)
|
||||||
|
{
|
||||||
|
let item = aBookarmks[i];
|
||||||
|
if (this.BookmarksService.getItemType(item.id) != this.BookmarksService.TYPE_BOOKMARK)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let uri = this.BookmarksService.getBookmarkURI(item.id);
|
||||||
|
if (/^about:treestyletab-group\b/.test(uri.spec)) {
|
||||||
|
let title = this.BookmarksService.getItemTitle(item.id);
|
||||||
|
let folderId = this.BookmarksService.createFolder(item.parent, title, item.position);
|
||||||
|
this.BookmarksService.removeItem(item.id);
|
||||||
|
item.id = folderId;
|
||||||
|
item.isFolder = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = aTreeStructure[i];
|
||||||
|
let parent = index > -1 ? aBookarmks[index] : null ;
|
||||||
|
if (parent && (parent.folder || parent).isFolder) {
|
||||||
|
let folder = parent.isFolder ? parent : parent.folder ;
|
||||||
|
this.BookmarksService.moveItem(item.id, folder.id, -1);
|
||||||
|
item.folder = folder;
|
||||||
|
}
|
||||||
|
if (parent && !parent.isFolder) {
|
||||||
|
PlacesUtils.setAnnotationsForItem(item.id, [{
|
||||||
|
name : this.kPARENT,
|
||||||
|
value : parent ? parent.id : -1,
|
||||||
|
expires : PlacesUtils.annotations.EXPIRE_NEVER
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
bookmarkTabSubtree : function TSTBMService_bookmarkTabSubtree(aTabOrTabs)
|
||||||
|
{
|
||||||
|
var tabs = aTabOrTabs;
|
||||||
|
if (!Array.isArray(tabs)) {
|
||||||
|
tabs = [aTabOrTabs];
|
||||||
|
}
|
||||||
|
if (tabs.length <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var window = tabs[0].ownerDocument.defaultView;
|
||||||
|
var TST = window.TreeStyleTabService;
|
||||||
|
|
||||||
|
var folderName = (TST.isGroupTab(tabs[0], true) || tabs.length == 1) ?
|
||||||
|
tabs[0].label :
|
||||||
|
null ;
|
||||||
|
|
||||||
|
var b = TST.getTabBrowserFromChild(tabs[0]);
|
||||||
|
var bookmarkedTabs = [];
|
||||||
|
for (let i = 0, maxi = tabs.length; i < maxi; i++)
|
||||||
|
{
|
||||||
|
let tab = tabs[i];
|
||||||
|
if (!TST.isGroupTab(tab, i == 0))
|
||||||
|
bookmarkedTabs.push(tab);
|
||||||
|
bookmarkedTabs = bookmarkedTabs.concat(b.treeStyleTab.getDescendantTabs(tab));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.beginAddBookmarksFromTabs(bookmarkedTabs);
|
||||||
|
try {
|
||||||
|
window['piro.sakura.ne.jp'].bookmarkMultipleTabs.addBookmarkFor(bookmarkedTabs, folderName);
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
}
|
||||||
|
this.endAddBookmarksFromTabs();
|
||||||
|
},
|
||||||
|
bookmarkTabSubTree : function() { return this.bookmarkTabSubtree.apply(this, arguments); }, // obsolete, for backward compatibility
|
||||||
|
|
||||||
|
getParentItem : function TSTBMService_getParentItem(aId)
|
||||||
|
{
|
||||||
|
if (aId < 0) return -1;
|
||||||
|
var annotations = PlacesUtils.getAnnotationsForItem(aId);
|
||||||
|
for (let i in annotations)
|
||||||
|
{
|
||||||
|
if (annotations[i].name != this.kPARENT) continue;
|
||||||
|
return parseInt(annotations[i].value);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
|
||||||
|
getTreeStructureFromItems : function TSTBMService_getTreeStructureFromItems(aIDs, aDefaultParentID)
|
||||||
|
{
|
||||||
|
/* this returns a result same to utils.getTreeStructureFromTabs().
|
||||||
|
[A] => -1 (parent is not in this tree)
|
||||||
|
[B] => 0 (parent is 1st item in this tree)
|
||||||
|
[C] => 0 (parent is 1st item in this tree)
|
||||||
|
[D] => 2 (parent is 2nd in this tree)
|
||||||
|
[E] => -1 (parent is not in this tree, and this creates another tree)
|
||||||
|
[F] => 0 (parent is 1st item in this another tree)
|
||||||
|
*/
|
||||||
|
if (aDefaultParentID === void(0))
|
||||||
|
aDefaultParentID = -1;
|
||||||
|
|
||||||
|
/* Get array of parents. The index becomes to -1,
|
||||||
|
if there is NO PARENT or the parent is THE TAB ITSELF. */
|
||||||
|
var treeStructure = aIDs.map(function(aId, aIndex) {
|
||||||
|
let id = this.getParentItem(aId);
|
||||||
|
let index = aIDs.indexOf(id);
|
||||||
|
return index >= aIndex ? aDefaultParentID : index ;
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
/* Correct patterns like:
|
||||||
|
[TabA]
|
||||||
|
[TabB] - this has no parent
|
||||||
|
[TabC] - TabA's child
|
||||||
|
to:
|
||||||
|
[TabA]
|
||||||
|
[TabB]
|
||||||
|
[TabC]
|
||||||
|
*/
|
||||||
|
treeStructure = treeStructure.reverse();
|
||||||
|
treeStructure = treeStructure.map(function(aPosition, aIndex) {
|
||||||
|
if (aIndex > 0 &&
|
||||||
|
aIndex < treeStructure.length-1 &&
|
||||||
|
aPosition < 0) {
|
||||||
|
aPosition = treeStructure[aIndex-1];
|
||||||
|
}
|
||||||
|
return aPosition;
|
||||||
|
});
|
||||||
|
treeStructure = treeStructure.reverse();
|
||||||
|
|
||||||
|
return utils.cleanUpTreeStructureArray(treeStructure, aDefaultParentID);
|
||||||
|
},
|
||||||
|
|
||||||
|
// based on PlacesUtils.getURLsForContainerNode()
|
||||||
|
getItemIdsForContainerNode : function TSTBMService_getItemIdsForContainerNode(aNode)
|
||||||
|
{
|
||||||
|
var ids = [];
|
||||||
|
if (!aNode || !PlacesUtils.nodeIsContainer(aNode)) return ids;
|
||||||
|
|
||||||
|
var root = aNode;
|
||||||
|
if ('getContainerNodeWithOptions' in PlacesUtils) {
|
||||||
|
root = PlacesUtils.getContainerNodeWithOptions(root, false, true);
|
||||||
|
}
|
||||||
|
var oldViewer = root.parentResult.viewer;
|
||||||
|
var wasOpen = root.containerOpen;
|
||||||
|
if (!wasOpen) {
|
||||||
|
if (oldViewer)
|
||||||
|
root.parentResult.viewer = null;
|
||||||
|
root.containerOpen = true;
|
||||||
|
}
|
||||||
|
for (let i = 0, maxi = root.childCount; i < maxi; ++i)
|
||||||
|
{
|
||||||
|
let child = root.getChild(i);
|
||||||
|
if (PlacesUtils.nodeIsURI(child)) ids.push(child.itemId || -1);
|
||||||
|
}
|
||||||
|
if (!wasOpen) {
|
||||||
|
root.containerOpen = false;
|
||||||
|
if (oldViewer)
|
||||||
|
root.parentResult.viewer = oldViewer;
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleTabsOpenProcess : function TSTBMService_handleTabsOpenProcess(aWhere, aEvent, aBrowserWindow, aIDs, aURLs, aItemsToOpen, aFolderTitle)
|
||||||
|
{
|
||||||
|
var result = {
|
||||||
|
behavior : undefined,
|
||||||
|
treeStructure : undefined,
|
||||||
|
previousTabs : undefined,
|
||||||
|
treeStructureApplied : false
|
||||||
|
};
|
||||||
|
if (
|
||||||
|
aEvent.type != 'drop' &&
|
||||||
|
aWhere.indexOf('tab') != 0 &&
|
||||||
|
aEvent.target.id != 'placesContext_openContainer:tabs' &&
|
||||||
|
aEvent.target.id != 'placesContext_openLinks:tabs' &&
|
||||||
|
aEvent.target != aEvent.target.parentNode._endOptOpenAllInTabs &&
|
||||||
|
aEvent.target.getAttribute('openInTabs') != 'true'
|
||||||
|
)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
var TST = aBrowserWindow.TreeStyleTabService;
|
||||||
|
|
||||||
|
result.behavior = TST.openGroupBookmarkBehavior();
|
||||||
|
if (result.behavior & this.kGROUP_BOOKMARK_SUBTREE) {
|
||||||
|
mydump('TSTBMService_handleTabsOpenProcess: open as a group\n');
|
||||||
|
let treeStructure = result.behavior & this.kGROUP_BOOKMARK_DONT_RESTORE_TREE_STRUCTURE ?
|
||||||
|
null :
|
||||||
|
this.getTreeStructureFromItems(aIDs) ;
|
||||||
|
mydump(' treeStructure => '+JSON.stringify(treeStructure)+'\n');
|
||||||
|
if (treeStructure) {
|
||||||
|
let parentTabs = treeStructure.filter(function(aParent) {
|
||||||
|
return aParent < 0;
|
||||||
|
});
|
||||||
|
let haveMultipleTrees = parentTabs.length != treeStructure.length;
|
||||||
|
if (result.behavior & this.kGROUP_BOOKMARK_USE_DUMMY) {
|
||||||
|
mydump(' trying to use dummy group tab\n');
|
||||||
|
let parentCount = 0;
|
||||||
|
let childCount = 0;
|
||||||
|
for (let i in treeStructure) {
|
||||||
|
if (treeStructure[i] == -1)
|
||||||
|
parentCount++;
|
||||||
|
else
|
||||||
|
childCount++;
|
||||||
|
}
|
||||||
|
mydump(' parentCount: '+parentCount+'\n');
|
||||||
|
mydump(' childCount: '+childCount+'\n');
|
||||||
|
if (
|
||||||
|
parentCount > 1 &&
|
||||||
|
(
|
||||||
|
result.behavior & this.kGROUP_BOOKMARK_USE_DUMMY_FORCE ||
|
||||||
|
// when there is any orphan, then all of parents and orphans should be grouped under a dummy tab.
|
||||||
|
childCount < parentCount
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
aIDs.unshift(-1);
|
||||||
|
treeStructure = this.getTreeStructureFromItems(aIDs, 0);
|
||||||
|
let uri = TST.getGroupTabURI({
|
||||||
|
title: aFolderTitle,
|
||||||
|
temporary: utils.getTreePref('openGroupBookmark.temporaryGroup')
|
||||||
|
});
|
||||||
|
aURLs.unshift(uri);
|
||||||
|
aItemsToOpen.unshift({
|
||||||
|
itemId: -1,
|
||||||
|
title: aFolderTitle,
|
||||||
|
uri: uri
|
||||||
|
})
|
||||||
|
mydump(' updated treeStructure => '+JSON.stringify(treeStructure)+'\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!haveMultipleTrees) {
|
||||||
|
// make the first item parent.
|
||||||
|
treeStructure = treeStructure.map(function(aParent, aIndex) {
|
||||||
|
if (aIndex == 0)
|
||||||
|
return aParent;
|
||||||
|
if (aParent < 0)
|
||||||
|
return 0;
|
||||||
|
return aParent;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.treeStructure = treeStructure;
|
||||||
|
result.previousTabs = TST.getTabsInfo(aBrowserWindow.gBrowser);
|
||||||
|
|
||||||
|
if (utils.getTreePref('compatibility.TMP') &&
|
||||||
|
'TMP_Places' in aBrowserWindow &&
|
||||||
|
'openGroup' in aBrowserWindow.TMP_Places) {
|
||||||
|
result.treeStructureApplied = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
TST.readyToOpenNewTabGroup(null, treeStructure, result.behavior & this.kGROUP_BOOKMARK_EXPAND_ALL_TREE);
|
||||||
|
result.treeStructureApplied = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// observer for nsINavBookmarksService
|
||||||
|
onItemAdded : function TSTBMService_onItemAdded(aID, aFolderID, aPosition)
|
||||||
|
{
|
||||||
|
this._addingBookmarks.push({
|
||||||
|
id : aID,
|
||||||
|
parent : aFolderID
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onItemRemoved : function TSTBMService_onItemRemoved(aID, aFolderID, aPosition) {},
|
||||||
|
onItemMoved : function TSTBMService_onItemMoved(aID, aFolderID, aPosition) {},
|
||||||
|
onItemChanged : function TSTBMService_onItemChanged(aID, aChange, aIsAnnotation, aNewValue) {},
|
||||||
|
onItemVisited : function TSTBMService_onItemVisited(aID, aHistoryID, aDate) {},
|
||||||
|
onBeginUpdateBatch : function TSTBMService_onBeginUpdateBatch() {},
|
||||||
|
onEndUpdateBatch : function TSTBMService_onEndUpdateBatch() {}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
PlacesUIUtils.__treestyletab__openTabset = PlacesUIUtils._openTabset;
|
||||||
|
PlacesUIUtils._openTabset = function(aItemsToOpen, aEvent, aWindow, ...aArgs) {
|
||||||
|
mydump('TSTBookmarks_openTabset\n');
|
||||||
|
|
||||||
|
var uris = [];
|
||||||
|
var ids = [];
|
||||||
|
var nodes = this.__treestyletab__openTabset_rawNodes || [];
|
||||||
|
aItemsToOpen = aItemsToOpen.filter(function(aItem, aIndex) {
|
||||||
|
if (aItem.uri) {
|
||||||
|
uris.push(aItem.uri);
|
||||||
|
let id = aItem.id;
|
||||||
|
if (!id && aIndex in nodes)
|
||||||
|
id = nodes[aIndex].itemId;
|
||||||
|
ids.push(id);
|
||||||
|
mydump(' '+aIndex+': '+id+' / '+aItem.uri+'\n');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
mydump(' items => '+aItemsToOpen.length+'\n');
|
||||||
|
|
||||||
|
var allArgs = [aItemsToOpen, aEvent, aWindow].concat(aArgs);
|
||||||
|
if (aItemsToOpen.length <= 0)
|
||||||
|
return this.__treestyletab__openTabset.apply(this, allArgs);
|
||||||
|
|
||||||
|
var w = aWindow && aWindow.document.documentElement.getAttribute('windowtype') == 'navigator:browser' ?
|
||||||
|
aWindow :
|
||||||
|
this._getTopBrowserWin() ;
|
||||||
|
var TST = w.TreeStyleTabService;
|
||||||
|
var BS = TreeStyleTabBookmarksService;
|
||||||
|
|
||||||
|
var where = w && w.whereToOpenLink(aEvent, false, true) || 'window';
|
||||||
|
mydump(' where: '+where+'\n');
|
||||||
|
if (where === 'window')
|
||||||
|
return this.__treestyletab__openTabset.apply(this, allArgs);
|
||||||
|
|
||||||
|
var result = BS.handleTabsOpenProcess(where, aEvent, w, ids, uris, aItemsToOpen, this.__treestyletab__folderName);
|
||||||
|
|
||||||
|
mydump(' result: '+JSON.stringify(result)+'\n');
|
||||||
|
this.__treestyletab__openTabset.apply(this, allArgs);
|
||||||
|
|
||||||
|
var tabs = [];
|
||||||
|
if (result.treeStructure && result.previousTabs)
|
||||||
|
tabs = TST.getNewTabsFromPreviousTabsInfo(w.gBrowser, result.previousTabs);
|
||||||
|
|
||||||
|
if (!result.treeStructureApplied)
|
||||||
|
TST.applyTreeStructureToTabs(
|
||||||
|
tabs,
|
||||||
|
result.treeStructure,
|
||||||
|
result.behavior & BS.kGROUP_BOOKMARK_EXPAND_ALL_TREE
|
||||||
|
);
|
||||||
|
|
||||||
|
var loadInBackground = where == 'tabshifted';
|
||||||
|
if (!loadInBackground) {
|
||||||
|
// start scroll after expanding animation is finished
|
||||||
|
w.setTimeout(function() {
|
||||||
|
w.gBrowser.treeStyleTab.scrollToTabs(tabs);
|
||||||
|
}, w.gBrowser.treeStyleTab.collapseDuration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
PlacesUtils.__treestyletab__getURLsForContainerNode = PlacesUtils.getURLsForContainerNode;
|
||||||
|
PlacesUtils.getURLsForContainerNode = function(aNode, ...aArgs) {
|
||||||
|
var uris = this.__treestyletab__getURLsForContainerNode.apply(this, [aNode].concat(aArgs));
|
||||||
|
var nodes = TreeStyleTabBookmarksService.getItemIdsForContainerNode(aNode);
|
||||||
|
for (let i in nodes) {
|
||||||
|
uris[i].id = nodes[i];
|
||||||
|
}
|
||||||
|
return uris;
|
||||||
|
};
|
||||||
|
|
||||||
|
PlacesUIUtils.__treestyletab__openContainerNodeInTabs = PlacesUIUtils.openContainerNodeInTabs;
|
||||||
|
PlacesUIUtils.openContainerNodeInTabs = function(aNode, ...aArgs) {
|
||||||
|
this.__treestyletab__folderName = aNode.title;
|
||||||
|
try {
|
||||||
|
return this.__treestyletab__openContainerNodeInTabs.apply(this, [aNode].concat(aArgs));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
delete this.__treestyletab__folderName;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
PlacesUIUtils.__treestyletab__openURINodesInTabs = PlacesUIUtils.openURINodesInTabs;
|
||||||
|
PlacesUIUtils.openURINodesInTabs = function(aNode, ...aArgs) {
|
||||||
|
try {
|
||||||
|
this.__treestyletab__openTabset_rawNodes = aNodes;
|
||||||
|
this.__treestyletab__folderName = utils.treeBundle.getFormattedString(
|
||||||
|
PlacesUtils.nodeIsBookmark(aNodes[0]) ?
|
||||||
|
'openSelectedPlaces.bookmarks' :
|
||||||
|
'openSelectedPlaces.history',
|
||||||
|
[aNodes[0].title, aNodes.length]
|
||||||
|
);
|
||||||
|
return this.__treestyletab__openURINodesInTabs.apply(this, [aNode].concat(aArgs));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
delete this.__treestyletab__openTabset_rawNodes;
|
||||||
|
delete this.__treestyletab__folderName;
|
||||||
|
}
|
||||||
|
};
|
@ -6274,7 +6274,7 @@ TreeStyleTabBrowser.prototype = inherit(TreeStyleTabWindow.prototype, {
|
|||||||
sourceService.getAllTabs(sourceBrowser).length == aTabs.length
|
sourceService.getAllTabs(sourceBrowser).length == aTabs.length
|
||||||
);
|
);
|
||||||
var newTabs = [];
|
var newTabs = [];
|
||||||
var treeStructure = sourceService.getTreeStructureFromTabs(aTabs);
|
var treeStructure = utils.getTreeStructureFromTabs(aTabs);
|
||||||
|
|
||||||
// Firefox fails to "move" collapsed tabs. So, expand them first
|
// Firefox fails to "move" collapsed tabs. So, expand them first
|
||||||
// and collapse them after they are moved.
|
// and collapse them after they are moved.
|
||||||
@ -6979,7 +6979,8 @@ TreeStyleTabBrowser.prototype = inherit(TreeStyleTabWindow.prototype, {
|
|||||||
scrollToTabs : function TSTBrowser_scrollToTabs(aTabs)
|
scrollToTabs : function TSTBrowser_scrollToTabs(aTabs)
|
||||||
{
|
{
|
||||||
var firstTab = aTabs[0];
|
var firstTab = aTabs[0];
|
||||||
if (!firstTab.parentNode) // do nothing for closed tab!
|
if (!firstTab ||
|
||||||
|
!firstTab.parentNode) // do nothing for closed tab!
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var b = this.mTabBrowser;
|
var b = this.mTabBrowser;
|
||||||
|
@ -220,6 +220,22 @@ var TreeStyleTabConstants = Object.freeze({
|
|||||||
RESTORE_STATE_STRUCTURE_RESTORED : 2,
|
RESTORE_STATE_STRUCTURE_RESTORED : 2,
|
||||||
|
|
||||||
|
|
||||||
|
kDROPLINK_ASK : 0,
|
||||||
|
kDROPLINK_FIXED : 1 + 2,
|
||||||
|
kDROPLINK_LOAD : 1,
|
||||||
|
kDROPLINK_NEWTAB : 2,
|
||||||
|
|
||||||
|
|
||||||
|
kGROUP_BOOKMARK_ASK : 0,
|
||||||
|
kGROUP_BOOKMARK_FIXED : 1 + 2 + 4,
|
||||||
|
kGROUP_BOOKMARK_SUBTREE : 1,
|
||||||
|
kGROUP_BOOKMARK_SEPARATE : 2,
|
||||||
|
kGROUP_BOOKMARK_USE_DUMMY : 256,
|
||||||
|
kGROUP_BOOKMARK_USE_DUMMY_FORCE : 1024,
|
||||||
|
kGROUP_BOOKMARK_DONT_RESTORE_TREE_STRUCTURE : 512,
|
||||||
|
kGROUP_BOOKMARK_EXPAND_ALL_TREE : 2048,
|
||||||
|
|
||||||
|
|
||||||
CONTENT_SCRIPT : 'chrome://treestyletab/content/content-utils.js',
|
CONTENT_SCRIPT : 'chrome://treestyletab/content/content-utils.js',
|
||||||
CONTENT_SCRIPT_AUTOHIDE : 'chrome://treestyletab/content/content-utils-autohide.js',
|
CONTENT_SCRIPT_AUTOHIDE : 'chrome://treestyletab/content/content-utils-autohide.js',
|
||||||
MESSAGE_TYPE : 'treestyletab',
|
MESSAGE_TYPE : 'treestyletab',
|
||||||
|
@ -572,7 +572,7 @@ catch(e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var treeStructure = sourceService.getTreeStructureFromTabs(draggedTabs);
|
var treeStructure = utils.getTreeStructureFromTabs(draggedTabs);
|
||||||
|
|
||||||
var newTabs = sv.moveTabsInternal(draggedTabs, {
|
var newTabs = sv.moveTabsInternal(draggedTabs, {
|
||||||
duplicate : aInfo.action & sv.kACTION_DUPLICATE,
|
duplicate : aInfo.action & sv.kACTION_DUPLICATE,
|
||||||
|
@ -335,6 +335,50 @@ var TreeStyleTabUtils = {
|
|||||||
this.isPrefChanging(aKey);
|
this.isPrefChanging(aKey);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
getTreeStructureFromTabs : function TSTUtils_getTreeStructureFromTabs(aTabs)
|
||||||
|
{
|
||||||
|
/* this returns...
|
||||||
|
[A] => -1 (parent is not in this tree)
|
||||||
|
[B] => 0 (parent is 1st item in this tree)
|
||||||
|
[C] => 0 (parent is 1st item in this tree)
|
||||||
|
[D] => 2 (parent is 2nd in this tree)
|
||||||
|
[E] => -1 (parent is not in this tree, and this creates another tree)
|
||||||
|
[F] => 0 (parent is 1st item in this another tree)
|
||||||
|
*/
|
||||||
|
return this.cleanUpTreeStructureArray(
|
||||||
|
aTabs.map(function(aTab, aIndex) {
|
||||||
|
let tab = this.getParentTab(aTab);
|
||||||
|
let index = tab ? aTabs.indexOf(tab) : -1 ;
|
||||||
|
return index >= aIndex ? -1 : index ;
|
||||||
|
}, this),
|
||||||
|
-1
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cleanUpTreeStructureArray : function TSTUtils_cleanUpTreeStructureArray(aTreeStructure, aDefaultParent)
|
||||||
|
{
|
||||||
|
var offset = 0;
|
||||||
|
aTreeStructure = aTreeStructure
|
||||||
|
.map(function(aPosition, aIndex) {
|
||||||
|
return (aPosition == aIndex) ? -1 : aPosition ;
|
||||||
|
})
|
||||||
|
.map(function(aPosition, aIndex) {
|
||||||
|
if (aPosition == -1) {
|
||||||
|
offset = aIndex;
|
||||||
|
return aPosition;
|
||||||
|
}
|
||||||
|
return aPosition - offset;
|
||||||
|
});
|
||||||
|
|
||||||
|
/* The final step, this validates all of values.
|
||||||
|
Smaller than -1 is invalid, so it becomes to -1. */
|
||||||
|
aTreeStructure = aTreeStructure.map(function(aIndex) {
|
||||||
|
return aIndex < -1 ? aDefaultParent : aIndex ;
|
||||||
|
}, this);
|
||||||
|
return aTreeStructure;
|
||||||
|
},
|
||||||
|
|
||||||
/* Pref Listener */
|
/* Pref Listener */
|
||||||
domains : [
|
domains : [
|
||||||
'extensions.treestyletab.'
|
'extensions.treestyletab.'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user