Bug 953891 - Participants Need Context Menu, r=florian.
This commit is contained in:
Родитель
ff0044d321
Коммит
6bc43a6ffc
|
@ -141,6 +141,11 @@ TagsService.prototype = {
|
|||
},
|
||||
// Get an array of all existing tags.
|
||||
getTags: function(aTagCount) {
|
||||
if (Tags.length)
|
||||
Tags.sort(function(a, b) a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
|
||||
else
|
||||
this.defaultTag;
|
||||
|
||||
if (aTagCount)
|
||||
aTagCount.value = Tags.length;
|
||||
return Tags;
|
||||
|
|
|
@ -408,7 +408,8 @@ const messageReplacements = {
|
|||
return (aMsg.incoming ? "Incoming" : "Outgoing") + "/buddy_icon.png";
|
||||
},
|
||||
senderScreenName: function(aMsg) TXTToHTML(aMsg.who),
|
||||
sender: function(aMsg) TXTToHTML(aMsg.alias || aMsg.who),
|
||||
sender: function(aMsg)
|
||||
"<span class=\"ib-sender\">" + TXTToHTML(aMsg.alias || aMsg.who) + "</span>",
|
||||
senderColor: function(aMsg) aMsg.color,
|
||||
senderStatusIcon: function(aMsg)
|
||||
getStatusIconFromBuddy(getBuddyFromMessage(aMsg)),
|
||||
|
|
|
@ -28,13 +28,7 @@ var addBuddy = {
|
|||
|
||||
buildTagList: function ab_buildTagList() {
|
||||
var tagList = document.getElementById("taglist");
|
||||
let tags = Services.tags.getTags();
|
||||
if (!tags.length) {
|
||||
let bundle = document.getElementById("instantbirdBundle");
|
||||
tags.push(Services.tags.defaultTag);
|
||||
}
|
||||
|
||||
tags.forEach(function (tag) {
|
||||
Services.tags.getTags().forEach(function(tag) {
|
||||
tagList.appendItem(tag.name, tag.id);
|
||||
});
|
||||
tagList.selectedIndex = 0;
|
||||
|
|
|
@ -86,6 +86,10 @@ function buddyListContextMenu(aXulMenu) {
|
|||
|
||||
document.getElementById("context-openconversation").disabled =
|
||||
!hide && !this.target.canOpenConversation();
|
||||
|
||||
Components.utils.import("resource:///modules/ibTagMenu.jsm");
|
||||
this.tagMenu = new TagMenu(this, window,
|
||||
this.onBuddy ? this.target.contact : this.target);
|
||||
}
|
||||
|
||||
// Prototype for buddyListContextMenu "class."
|
||||
|
@ -141,64 +145,16 @@ buddyListContextMenu.prototype = {
|
|||
|
||||
this.target.remove();
|
||||
},
|
||||
tagsPopupShowing: function blcm_tagsPopupShowing() {
|
||||
if (!this.onContact && !this.onBuddy)
|
||||
return;
|
||||
|
||||
let popup = document.getElementById("context-tags-popup");
|
||||
let item;
|
||||
while ((item = popup.firstChild) && item.localName != "menuseparator")
|
||||
popup.removeChild(item);
|
||||
|
||||
let contact = (this.onBuddy ? this.target.contact : this.target).contact;
|
||||
let tags = contact.getTags();
|
||||
let groupId =
|
||||
(this.onBuddy ? this.target.contact : this.target).group.groupId;
|
||||
let sortFunction = function (a, b) {
|
||||
[a, b] = [a.name.toLowerCase(), b.name.toLowerCase()];
|
||||
return a < b ? 1 : a > b ? -1 : 0;
|
||||
};
|
||||
Services.tags.getTags()
|
||||
.sort(sortFunction)
|
||||
.forEach(function (aTag) {
|
||||
item = document.createElement("menuitem");
|
||||
item.setAttribute("label", aTag.name);
|
||||
item.setAttribute("type", "checkbox");
|
||||
let id = aTag.id;
|
||||
item.groupId = id;
|
||||
if (tags.some(function (t) t.id == id)) {
|
||||
item.setAttribute("checked", "true");
|
||||
if (tags.length == 1)
|
||||
item.setAttribute("disabled", "true"); // can't remove the last tag.
|
||||
}
|
||||
popup.insertBefore(item, popup.firstChild);
|
||||
});
|
||||
addTag: function blcm_addTag(aTag) {
|
||||
// If the contact already has the tag, addTag will return early.
|
||||
this.tagMenu.target.contact.addTag(aTag);
|
||||
},
|
||||
tag: function blcm_tag(aEvent) {
|
||||
let id = aEvent.originalTarget.groupId;
|
||||
if (!id)
|
||||
return;
|
||||
|
||||
let tag = Services.tags.getTagById(id);
|
||||
let contact = (this.onBuddy ? this.target.contact : this.target).contact;
|
||||
if (contact.getTags().some(function (t) t.id == id))
|
||||
contact.removeTag(tag);
|
||||
toggleTag: function blcm_toggleTag(aTag) {
|
||||
let contact = this.tagMenu.target.contact;
|
||||
if (contact.getTags().some(function(t) t.id == aTag.id))
|
||||
contact.removeTag(aTag);
|
||||
else
|
||||
contact.addTag(tag);
|
||||
},
|
||||
addNewTag: function blcm_addNewTag() {
|
||||
let bundle = document.getElementById("instantbirdBundle").stringBundle;
|
||||
let title = bundle.GetStringFromName("newTagPromptTitle");
|
||||
let message = bundle.GetStringFromName("newTagPromptMessage");
|
||||
let name = {};
|
||||
if (!Services.prompt.prompt(window, title, message, name, null,
|
||||
{value: false}) || !name.value)
|
||||
return; // the user canceled
|
||||
|
||||
let contact = (this.onBuddy ? this.target.contact : this.target).contact;
|
||||
// If the tag already exists, createTag will return it, and if the
|
||||
// contact already has it, addTag will return early.
|
||||
contact.addTag(Services.tags.createTag(name.value));
|
||||
contact.addTag(aTag);
|
||||
},
|
||||
_getLogs: function blcm_getLogs() {
|
||||
if (this.onContact)
|
||||
|
|
|
@ -85,13 +85,13 @@
|
|||
label="&tagsCmd.label;"
|
||||
accesskey="&tagsCmd.accesskey;">
|
||||
<menupopup id="context-tags-popup"
|
||||
oncommand="gBuddyListContextMenu.tag(event);"
|
||||
onpopupshowing="gBuddyListContextMenu.tagsPopupShowing();">
|
||||
oncommand="gBuddyListContextMenu.tagMenu.tag(event, gBuddyListContextMenu.toggleTag.bind(gBuddyListContextMenu));"
|
||||
onpopupshowing="gBuddyListContextMenu.tagMenu.tagsPopupShowing();">
|
||||
<menuseparator id="context-create-tag-separator"/>
|
||||
<menuitem id="context-create-tag"
|
||||
label="&addNewTagCmd.label;"
|
||||
accesskey="&addNewTagCmd.accesskey;"
|
||||
oncommand="gBuddyListContextMenu.addNewTag();"/>
|
||||
oncommand="gBuddyListContextMenu.tagMenu.addNewTag(gBuddyListContextMenu.addTag.bind(gBuddyListContextMenu));"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menuitem id="context-hide-tag"
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
</xul:hbox>
|
||||
<xul:listbox anonid="nicklist" class="conv-nicklist"
|
||||
flex="1" seltype="multiple"
|
||||
xbl:inherits="contextmenu=contentcontextmenu"
|
||||
tooltip="buddyTooltip"
|
||||
onclick="onNickClick(event);"
|
||||
onkeypress="onNicklistKeyPress(event);"/>
|
||||
|
|
|
@ -126,8 +126,25 @@
|
|||
<tooltip id="buddyTooltip" type="buddy"/>
|
||||
|
||||
<menupopup id="contentAreaContextMenu"
|
||||
onpopupshowing="if (event.target != this) return true; gContextMenu = new nsContextMenu(this, window.getTabBrowser()); return gContextMenu.shouldDisplay;"
|
||||
onpopupshowing="if (event.target != this) return true; gContextMenu = new nsContextMenu(this, window.getBrowser()); return gContextMenu.shouldDisplay;"
|
||||
onpopuphiding="if (event.target == this && gContextMenu) { gContextMenu.cleanup(); gContextMenu = null; }">
|
||||
<menuitem id="context-nick-openconv"
|
||||
oncommand="gContextMenu.nickOpenConv();"/>
|
||||
<menuitem id="context-nick-showlogs"
|
||||
oncommand="gContextMenu.nickShowLogs();"/>
|
||||
<menu id="context-nick-addcontact">
|
||||
<menupopup id="context-tags-popup"
|
||||
oncommand="gContextMenu.tagMenu.tag(event, gContextMenu.nickAddContact.bind(gContextMenu));"
|
||||
onpopupshowing="gContextMenu.tagMenu.tagsPopupShowing();">
|
||||
<menuseparator id="context-create-tag-separator"/>
|
||||
<menuitem id="context-create-tag"
|
||||
label="&addNewTagCmd.label;"
|
||||
accesskey="&addNewTagCmd.accesskey;"
|
||||
oncommand="gContextMenu.tagMenu.addNewTag(gContextMenu.nickAddContact.bind(gContextMenu));"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menuseparator id="context-sep-nick"/>
|
||||
|
||||
<menuitem id="context-openlink"
|
||||
label="&openLinkCmd.label;"
|
||||
accesskey="&openLinkCmd.accesskey;"
|
||||
|
|
|
@ -2,12 +2,16 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource:///modules/ibTagMenu.jsm");
|
||||
|
||||
var gContextMenu = null;
|
||||
|
||||
function nsContextMenu(aXulMenu, aBrowser) {
|
||||
this.target = null;
|
||||
this.browser = null;
|
||||
this.conv = null;
|
||||
this.menu = null;
|
||||
this.tagMenu = null;
|
||||
this.onLink = false;
|
||||
this.onMailtoLink = false;
|
||||
this.onSaveableLink = false;
|
||||
|
@ -15,16 +19,23 @@ function nsContextMenu(aXulMenu, aBrowser) {
|
|||
this.linkURL = "";
|
||||
this.linkURI = null;
|
||||
this.linkProtocol = null;
|
||||
this.onNick = false;
|
||||
this.nick = "";
|
||||
this.buddy = null;
|
||||
this.isNickOpenConv = false;
|
||||
this.isNickShowLogs = false;
|
||||
this.isNickAddContact = false;
|
||||
this.isTextSelected = false;
|
||||
this.isContentSelected = false;
|
||||
this.shouldDisplay = true;
|
||||
this.ellipsis = "\u2026";
|
||||
|
||||
try {
|
||||
this.ellipsis =
|
||||
Services.prefs.getComplexValue("intl.ellipsis",
|
||||
Ci.nsIPrefLocalizedString).data;
|
||||
} catch (e) { }
|
||||
} catch (e) {
|
||||
this.ellipsis = "\u2026";
|
||||
}
|
||||
|
||||
// Initialize new menu.
|
||||
this.initMenu(aXulMenu, aBrowser);
|
||||
|
@ -46,11 +57,26 @@ nsContextMenu.prototype = {
|
|||
initMenu: function CM_initMenu(aPopup, aBrowser) {
|
||||
this.menu = aPopup;
|
||||
this.browser = aBrowser;
|
||||
this.conv = this.browser._conv;
|
||||
|
||||
// Get contextual info.
|
||||
let node = document.popupNode;
|
||||
if (node.localName == "listbox") {
|
||||
// Clicked the participant list, but not a listitem.
|
||||
this.shouldDisplay = false;
|
||||
return;
|
||||
}
|
||||
this.setTarget(node);
|
||||
|
||||
let isParticipantList = node.localName == "listitem";
|
||||
let nickActions = this.getNickActions(isParticipantList);
|
||||
this.onNick = nickActions.some(function(action) action.visible);
|
||||
if (isParticipantList && !this.onNick) {
|
||||
// If we're in the participant list, there will be no other entries.
|
||||
this.shouldDisplay = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let actions = [];
|
||||
while (node) {
|
||||
if (node._originalMsg) {
|
||||
|
@ -66,7 +92,7 @@ nsContextMenu.prototype = {
|
|||
|
||||
// Initialize (disable/remove) menu items.
|
||||
// Open/Save/Send link depends on whether we're in a link.
|
||||
var shouldShow = this.onSaveableLink;
|
||||
let shouldShow = this.onSaveableLink;
|
||||
this.showItem("context-openlink", shouldShow);
|
||||
this.showItem("context-sep-open", shouldShow);
|
||||
this.showItem("context-savelink", shouldShow);
|
||||
|
@ -80,7 +106,8 @@ nsContextMenu.prototype = {
|
|||
goUpdateGlobalEditMenuItems();
|
||||
|
||||
this.showItem("context-copy", this.isContentSelected);
|
||||
this.showItem("context-selectall", !this.onLink || this.isContentSelected);
|
||||
this.showItem("context-selectall", (!this.onNick && !this.onLink) ||
|
||||
this.isContentSelected);
|
||||
this.showItem("context-sep-selectall", actions.length);
|
||||
this.showItem("context-sep-messageactions", this.isTextSelected);
|
||||
|
||||
|
@ -91,6 +118,13 @@ nsContextMenu.prototype = {
|
|||
this.showItem("context-copylink", this.onLink && !this.onMailtoLink);
|
||||
this.showItem("context-sep-copylink", this.onLink && this.isContentSelected);
|
||||
|
||||
// Display nick menu items.
|
||||
let isNonNickItems = this.isContentSelected || this.isTextSelected ||
|
||||
this.onLink || actions.length;
|
||||
this.showItem("context-sep-nick", this.onNick && isNonNickItems);
|
||||
for (let action of nickActions)
|
||||
this.showItem(action.id, action.visible);
|
||||
|
||||
// Display action menu items.
|
||||
let before = document.getElementById("context-sep-messageactions");
|
||||
for each (let action in actions) {
|
||||
|
@ -102,18 +136,75 @@ nsContextMenu.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
getNormalizedName: function(aNick) {
|
||||
// Unfortunately there is currently no way to obtain the normalizedName
|
||||
// corresponding to the nick (bug 2115).
|
||||
// Therefore we may sometimes not find existing logs for a nick,
|
||||
// and offer "add contact" despite a buddy already existing.
|
||||
return aNick;
|
||||
},
|
||||
getLogsForNick: function(aNick) {
|
||||
return Services.logs.getLogsForAccountAndName(this.conv.account,
|
||||
this.getNormalizedName(aNick),
|
||||
true);
|
||||
},
|
||||
getNickActions: function(aIsParticipantList) {
|
||||
let bundle = document.getElementById("bundle_instantbird");
|
||||
let nick = this.nick;
|
||||
let actions = [];
|
||||
let addAction = function(aId, aVisible) {
|
||||
let domId = "context-nick-" + aId.toLowerCase();
|
||||
let stringId = "contextmenu.nick" + aId;
|
||||
if (!aIsParticipantList)
|
||||
stringId += ".withNick";
|
||||
document.getElementById(domId).label =
|
||||
bundle.getFormattedString(stringId, [nick]);
|
||||
actions.push({id: domId, visible: aVisible});
|
||||
};
|
||||
|
||||
// Special-case twitter. XXX Drop this when twitter DMs work.
|
||||
let isTwitter = this.conv.account.protocol.id == "prpl-twitter";
|
||||
|
||||
addAction("OpenConv", this.onNick && !isTwitter);
|
||||
addAction("ShowLogs", this.onNick && this.getLogsForNick(nick).hasMoreElements());
|
||||
this.buddy = Services.contacts
|
||||
.getBuddyByNameAndProtocol(this.getNormalizedName(nick),
|
||||
this.conv.account.protocol);
|
||||
let isAddContact = this.onNick && !isTwitter && !this.buddy;
|
||||
if (isAddContact)
|
||||
this.tagMenu = new TagMenu(this, window);
|
||||
addAction("AddContact", isAddContact);
|
||||
|
||||
return actions;
|
||||
},
|
||||
nickOpenConv: function() {
|
||||
let name = this.conv.target.getNormalizedChatBuddyName(this.nick);
|
||||
let newConv = this.conv.account.createConversation(name);
|
||||
Conversations.focusConversation(newConv);
|
||||
},
|
||||
nickAddContact: function(aTag)
|
||||
this.conv.account.addBuddy(aTag, this.nick),
|
||||
nickShowLogs: function() {
|
||||
let nick = this.nick;
|
||||
let enumerator = this.getLogsForNick(nick);
|
||||
if (!enumerator.hasMoreElements())
|
||||
return;
|
||||
window.openDialog("chrome://instantbird/content/viewlog.xul",
|
||||
"Logs", "chrome,resizable", {logs: enumerator}, nick);
|
||||
},
|
||||
|
||||
// Set various context menu attributes based on the state of the world.
|
||||
setTarget: function (aNode) {
|
||||
|
||||
// Initialize contextual info.
|
||||
this.onLink = false;
|
||||
this.linkURL = "";
|
||||
this.linkURI = null;
|
||||
this.linkProtocol = "";
|
||||
|
||||
setTarget: function(aNode) {
|
||||
// Remember the node that was clicked.
|
||||
this.target = aNode;
|
||||
|
||||
// Check if we are in the participant list.
|
||||
if (this.target.localName == "listitem") {
|
||||
this.onNick = true;
|
||||
this.nick = this.target.label;
|
||||
return;
|
||||
}
|
||||
|
||||
// First, do checks for nodes that never have children.
|
||||
// Second, bubble out, looking for items of interest that can have childen.
|
||||
// Always pick the innermost link, background image, etc.
|
||||
|
@ -155,6 +246,13 @@ nsContextMenu.prototype = {
|
|||
this.onMailtoLink = (this.linkProtocol == "mailto");
|
||||
this.onSaveableLink = this.isLinkSaveable(this.link);
|
||||
}
|
||||
|
||||
// Nick?
|
||||
if (!this.onNick && this.conv.isChat &&
|
||||
(elem.classList.contains("ib-nick") || elem.classList.contains("ib-sender"))) {
|
||||
this.nick = elem.textContent;
|
||||
this.onNick = true;
|
||||
}
|
||||
}
|
||||
|
||||
elem = elem.parentNode;
|
||||
|
@ -223,7 +321,7 @@ nsContextMenu.prototype = {
|
|||
},
|
||||
|
||||
// Open linked-to URL in a new window.
|
||||
openLink: function (aURI) {
|
||||
openLink: function(aURI) {
|
||||
Cc["@mozilla.org/uriloader/external-protocol-service;1"].
|
||||
getService(Ci.nsIExternalProtocolService).
|
||||
loadURI(aURI || this.linkURI, window);
|
||||
|
|
|
@ -137,3 +137,17 @@ statusOffline=Your account is disconnected because your status is currently set
|
|||
# This is shown when the user attempts to send a message to a disconnected account.
|
||||
# %1$S is the name of the protocol of the account, %2$S the name of the account.
|
||||
accountDisconnected=Your %1$S account %2$S is disconnected.
|
||||
|
||||
#LOCALIZATION NOTE
|
||||
# These appear in the context menu of the chat participants in the
|
||||
# participant list.
|
||||
contextmenu.nickOpenConv=Private Conversation
|
||||
contextmenu.nickShowLogs=Show Logs
|
||||
contextmenu.nickAddContact=Add Contact…
|
||||
# These appear in the context menu for the nicks of chat participants
|
||||
# highlighted in messages, and extend those for the participant list
|
||||
# by mentioning the nick.
|
||||
# %S is the nick of the chat participant.
|
||||
contextmenu.nickOpenConv.withNick=Private Conversation with %S
|
||||
contextmenu.nickShowLogs.withNick=Show Logs for %S
|
||||
contextmenu.nickAddContact.withNick=Add %S to Contacts…
|
||||
|
|
|
@ -13,6 +13,7 @@ EXTRA_JS_MODULES = \
|
|||
ibInterruptions.jsm \
|
||||
ibNotifications.jsm \
|
||||
ibSounds.jsm \
|
||||
ibTagMenu.jsm \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_PP_JS_MODULES = \
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const EXPORTED_SYMBOLS = ["TagMenu"];
|
||||
|
||||
const Cu = Components.utils;
|
||||
Cu.import("resource:///modules/imServices.jsm");
|
||||
Cu.import("resource:///modules/imXPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "_", function()
|
||||
l10nHelper("chrome://instantbird/locale/instantbird.properties")
|
||||
);
|
||||
|
||||
// If a contact binding is given in aTarget, the menu checkmarks the existing
|
||||
// tags on this contact.
|
||||
function TagMenu(aParent, aWindow, aTarget = null) {
|
||||
this.parent = aParent;
|
||||
this.window = aWindow;
|
||||
if (aWindow)
|
||||
this.document = aWindow.document;
|
||||
this.target = aTarget;
|
||||
}
|
||||
TagMenu.prototype = {
|
||||
document: null,
|
||||
window: null,
|
||||
target: null,
|
||||
tagsPopupShowing: function() {
|
||||
if (!this.parent.onContact && !this.parent.onBuddy && !this.parent.onNick)
|
||||
return;
|
||||
|
||||
let popup = this.document.getElementById("context-tags-popup");
|
||||
let item;
|
||||
while ((item = popup.firstChild) && item.localName != "menuseparator")
|
||||
popup.removeChild(item);
|
||||
|
||||
if (this.target) {
|
||||
var tags = this.target.contact.getTags();
|
||||
var groupId = this.target.group.groupId;
|
||||
}
|
||||
|
||||
let allTags = Services.tags.getTags().reverse();
|
||||
for (let tag of allTags) {
|
||||
item = this.document.createElement("menuitem");
|
||||
item.setAttribute("label", tag.name);
|
||||
let id = tag.id;
|
||||
item.groupId = id;
|
||||
if (this.target) {
|
||||
item.setAttribute("type", "checkbox");
|
||||
if (tags.some(function(t) t.id == id)) {
|
||||
item.setAttribute("checked", "true");
|
||||
if (tags.length == 1)
|
||||
item.setAttribute("disabled", "true"); // can't remove the last tag.
|
||||
}
|
||||
}
|
||||
popup.insertBefore(item, popup.firstChild);
|
||||
}
|
||||
},
|
||||
tag: function(aEvent, aCallback) {
|
||||
let id = aEvent.originalTarget.groupId;
|
||||
if (!id)
|
||||
return false;
|
||||
|
||||
try {
|
||||
return aCallback(Services.tags.getTagById(id));
|
||||
} catch(e) {
|
||||
Cu.reportError(e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
addNewTag: function(aCallback) {
|
||||
let name = {};
|
||||
if (!Services.prompt.prompt(this.window, _("newTagPromptTitle"),
|
||||
_("newTagPromptMessage"), name, null,
|
||||
{value: false}) || !name.value)
|
||||
return false; // the user canceled
|
||||
|
||||
try {
|
||||
// If the tag already exists, createTag will return it.
|
||||
return aCallback(Services.tags.createTag(name.value));
|
||||
} catch(e) {
|
||||
Cu.reportError(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
Загрузка…
Ссылка в новой задаче