Bug 250141 - Introduce deletion of newsgroup messages r=bienvenu, sr=neil
authorJoshua Cranmer <Pidgeot18@gmail.com>
Mon, 06 Sep 2010 08:57:09 -0400
changeset 6754 113880e9f04372ea03887b0ca70aa443d919d966
parent 6753 a42338acc7db45330650c846f591d4ba4ace0898
child 6755 bcacd8aada1d4e97f5ac9f89012d7cc6f82fa300
push idunknown
push userunknown
push dateunknown
reviewersbienvenu, neil
bugs250141
Bug 250141 - Introduce deletion of newsgroup messages r=bienvenu, sr=neil This also makes the distinction between `cancel' and `delete' much more clear.
mail/base/content/SearchDialog.js
mail/base/content/mail3PaneWindowCommands.js
mail/base/content/mailWindowOverlay.js
mail/base/content/mailWindowOverlay.xul
mail/locales/en-US/chrome/messenger/messenger.dtd
mail/locales/en-US/chrome/messenger/news.properties
mailnews/base/public/nsIMsgFolder.idl
mailnews/base/src/nsMsgDBView.cpp
mailnews/news/public/nsIMsgNewsFolder.idl
mailnews/news/src/nsNewsFolder.cpp
mailnews/news/src/nsNewsFolder.h
suite/locales/en-US/chrome/mailnews/messenger.dtd
suite/locales/en-US/chrome/mailnews/news.properties
suite/mailnews/mail3PaneWindowCommands.js
suite/mailnews/mailWindowOverlay.js
suite/mailnews/mailWindowOverlay.xul
suite/mailnews/messageWindow.js
suite/mailnews/search/SearchDialog.js
--- a/mail/base/content/SearchDialog.js
+++ b/mail/base/content/SearchDialog.js
@@ -96,18 +96,17 @@ var nsSearchResultsController =
           case "open_in_folder_button":
             if (GetNumSelectedMessages() != 1)
               enabled = false;
             break;
           case "cmd_delete":
           case "cmd_shiftDelete":
           case "button_delete":
             // this assumes that advanced searches don't cross accounts
-            if (GetNumSelectedMessages() <= 0 ||
-                isNewsURI(gFolderDisplay.view.dbView.getURIForViewIndex(0)))
+            if (GetNumSelectedMessages() <= 0)
               enabled = false;
             break;
           case "saveas_vf_button":
               // need someway to see if there are any search criteria...
               return true;
           case "cmd_selectAll":
             return true;
           default:
@@ -614,47 +613,35 @@ function onSearchButton(event)
 // threadPane.js will be needing this, too
 function GetNumSelectedMessages()
 {
   return gFolderDisplay.treeSelection.count;
 }
 
 function MsgDeleteSelectedMessages(aCommandType)
 {
-    // we don't delete news messages, we just return in that case
-    if (gFolderDisplay.selectedMessageIsNews)
-        return;
-
-    // if mail messages delete
     gFolderDisplay.hintAboutToDeleteMessages();
     gFolderDisplay.doCommand(aCommandType);
 }
 
 function MoveMessageInSearch(destFolder)
 {
   // Get the msg folder we're moving messages into.
   // If the id (uri) is not set, use file-uri which is set for
   // "File Here".
   let destUri = destFolder.getAttribute('id');
   if (destUri.length == 0)
     destUri = destFolder.getAttribute('file-uri');
 
   let destMsgFolder = GetMsgFolderFromUri(destUri).QueryInterface(
                         Components.interfaces.nsIMsgFolder);
 
-  // we don't move news messages, we copy them
-  if (gFolderDisplay.selectedMessageIsNews) {
-    gFolderDisplay.doCommandWithFolder(nsMsgViewCommandType.copyMessages,
-                                       destMsgFolder);
-  }
-  else {
-    gFolderDisplay.hintAboutToDeleteMessages();
-    gFolderDisplay.doCommandWithFolder(nsMsgViewCommandType.moveMessages,
-                                       destMsgFolder);
-  }
+  gFolderDisplay.hintAboutToDeleteMessages();
+  gFolderDisplay.doCommandWithFolder(nsMsgViewCommandType.moveMessages,
+                                     destMsgFolder);
 }
 
 function OpenInFolder()
 {
   MailUtils.displayMessageInFolderTab(gFolderDisplay.selectedMessage);
 }
 
 function saveAsVirtualFolder()
--- a/mail/base/content/mail3PaneWindowCommands.js
+++ b/mail/base/content/mail3PaneWindowCommands.js
@@ -245,16 +245,17 @@ var DefaultController =
       case "cmd_downloadFlagged":
       case "cmd_downloadSelected":
       case "cmd_synchronizeOffline":
         return MailOfflineMgr.isOnline();
 
       case "cmd_watchThread":
       case "cmd_killThread":
       case "cmd_killSubthread":
+      case "cmd_cancel":
         return(gFolderDisplay.selectedMessageIsNews);
 
       default:
         return false;
     }
   },
 
   isCommandEnabled: function(command)
@@ -268,16 +269,21 @@ var DefaultController =
       case "cmd_delete":
         UpdateDeleteCommand();
         // fall through
       case "button_delete":
         UpdateDeleteToolbarButton();
         return gFolderDisplay.getCommandStatus(nsMsgViewCommandType.deleteMsg);
       case "cmd_shiftDelete":
         return gFolderDisplay.getCommandStatus(nsMsgViewCommandType.deleteNoTrash);
+      case "cmd_cancel": {
+        let selectedMessages = gFolderDisplay.selectedMessages;
+        return selectedMessages.length == 1 && selectedMessages[0].folder &&
+               selectedMessages[0].folder.server.type == "nntp";
+      }
       case "cmd_deleteFolder":
         var folders = gFolderTreeView.getSelectedFolders();
         if (folders.length == 1) {
           var folder = folders[0];
           if (folder.server.type == "nntp")
             return false; // Just disable the command for news.
           else
             return CanDeleteFolder(folder);
@@ -616,16 +622,21 @@ var DefaultController =
         // If this is a right-click triggered delete, then do not hint about
         //  the deletion.  Note: The code that swaps the selection back in will
         //  take care of ensuring that this deletion does not make the saved
         //  selection incorrect.
         if (!gRightMouseButtonSavedSelection)
           gFolderDisplay.hintAboutToDeleteMessages();
         gFolderDisplay.doCommand(nsMsgViewCommandType.deleteMsg);
         break;
+      case "cmd_cancel":
+        let message = gFolderDisplay.selectedMessages[0];
+        message.folder.QueryInterface(Components.interfaces.nsIMsgNewsFolder)
+                      .cancelMessage(message, msgWindow);
+        break;
       case "cmd_shiftDelete":
         MarkSelectedMessagesRead(true);
         gFolderDisplay.hintAboutToDeleteMessages();
         gFolderDisplay.doCommand(nsMsgViewCommandType.deleteNoTrash);
         break;
       case "cmd_deleteFolder":
         gFolderTreeController.deleteFolder();
         break;
--- a/mail/base/content/mailWindowOverlay.js
+++ b/mail/base/content/mailWindowOverlay.js
@@ -349,16 +349,17 @@ function InitMessageMenu()
   document.getElementById("replyMainMenu").hidden = isNews;
   document.getElementById("replySenderMainMenu").hidden = !isNews;
 
   // We only kill and watch threads for news.
   document.getElementById("threadItemsSeparator").hidden = !isNews;
   document.getElementById("killThread").hidden = !isNews;
   document.getElementById("killSubthread").hidden = !isNews;
   document.getElementById("watchThread").hidden = !isNews;
+  document.getElementById("menu_cancel").hidden = !isNews;
 
 
   // Disable the move and copy menus if there are no messages selected or if
   // the message is a dummy - e.g. opening a message in the standalone window.
   let messageStoredInternally = selectedMsg && !gMessageDisplay.isDummy;
   // Disable the move menu if we can't delete msgs from the folder.
   let canMove = messageStoredInternally &&
                 gFolderDisplay.canDeleteSelectedMessages;
@@ -1011,19 +1012,17 @@ function UpdateDeleteToolbarButton()
       GetNumSelectedMessages() == 0)
     deleteButtonDeck.selectedIndex = 0;
   else
     deleteButtonDeck.selectedIndex = SelectedMessagesAreDeleted() ? 1 : 0;
 }
 function UpdateDeleteCommand()
 {
   var value = "value";
-  if (gFolderDisplay.selectedMessageIsNews)
-    value += "News";
-  else if (SelectedMessagesAreDeleted())
+  if (SelectedMessagesAreDeleted())
     value += "IMAPDeleted";
   if (GetNumSelectedMessages() < 2)
     value += "Message";
   else
     value += "Messages";
   goSetMenuValue("cmd_delete", value);
   goSetAccessKey("cmd_delete", value + "AccessKey");
 }
@@ -1221,28 +1220,18 @@ function MsgCopyMessage(aDestFolder)
 }
 
 /**
  * Moves the selected messages to the destination folder
  * @param aDestFolder  the destination folder
  */
 function MsgMoveMessage(aDestFolder)
 {
-  // We don't move news messages, we copy them.
-  // XXX this check is incorrect in two ways. For saved searches we could have
-  // cross folder/newsgroup messages, so this check would do the wrong thing.
-  // For global search views, we don't have a msgFolder - however as we don't
-  // index newsgroup messages, we can at least temporarily get away with this.
-  if (gDBView.msgFolder && isNewsURI(gDBView.msgFolder.URI))
-    gDBView.doCommandWithFolder(nsMsgViewCommandType.copyMessages, aDestFolder);
-  else
-  {
-    gFolderDisplay.hintAboutToDeleteMessages();
-    gDBView.doCommandWithFolder(nsMsgViewCommandType.moveMessages, aDestFolder);
-  }
+  gFolderDisplay.hintAboutToDeleteMessages();
+  gDBView.doCommandWithFolder(nsMsgViewCommandType.moveMessages, aDestFolder);
   pref.setCharPref("mail.last_msg_movecopy_target_uri", aDestFolder.URI);
   pref.setBoolPref("mail.last_msg_movecopy_was_move", true);
 }
 
 /**
  * Calls the ComposeMessage function with the desired type, and proper default
  * based on the event that fired it.
  *
@@ -2198,22 +2187,17 @@ function IsGetNextNMessagesEnabled()
 }
 
 function SetUpToolbarButtons(uri)
 {
   var deleteButton = document.getElementById("button-delete");
   if (!deleteButton)
     return;
 
-  // Eventually, we might want to set up the toolbar differently for imap,
-  // pop, and news.  For now, just tweak it based on if it is news or not.
-  if (isNewsURI(uri))
-    deleteButton.setAttribute('hidden', true);
-  else
-    deleteButton.removeAttribute('hidden');
+  deleteButton.removeAttribute('hidden');
 }
 
 function MsgSynchronizeOffline()
 {
   window.openDialog("chrome://messenger/content/msgSynchronize.xul", "",
                     "centerscreen,chrome,modal,titlebar,resizable=yes",
                     {msgWindow:msgWindow});
 }
--- a/mail/base/content/mailWindowOverlay.xul
+++ b/mail/base/content/mailWindowOverlay.xul
@@ -194,24 +194,21 @@
          valueFolder="&deleteFolderCmd.label;"
          valueFolderAccessKey="&deleteFolderCmd.accesskey;"
          valueNewsgroup="&unsubscribeNewsgroupCmd.label;"
          valueNewsgroupAccessKey="&unsubscribeNewsgroupCmd.accesskey;"
          valueMessage="&deleteMsgCmd.label;"
          valueMessageAccessKey="&deleteMsgCmd.accesskey;"
          valueIMAPDeletedMessage="&undeleteMsgCmd.label;"
          valueIMAPDeletedMessageAccessKey="&undeleteMsgCmd.accesskey;"
-         valueNewsMessage="&cancelNewsMsgCmd.label;"
-         valueNewsMessageAccessKey="&cancelNewsMsgCmd.accesskey;"
          valueMessages="&deleteMsgsCmd.label;"
          valueMessagesAccessKey="&deleteMsgsCmd.accesskey;"
          valueIMAPDeletedMessages="&undeleteMsgsCmd.label;"
-         valueIMAPDeletedMessagesAccessKey="&undeleteMsgsCmd.accesskey;"
-         valueNewsMessages="&cancelNewsMsgsCmd.label;"
-         valueNewsMessagesAccessKey="&cancelNewsMsgsCmd.accesskey;"/>
+         valueIMAPDeletedMessagesAccessKey="&undeleteMsgsCmd.accesskey;"/>
+  <command id="cmd_cancel" oncommand="goDoCommand('cmd_cancel')"/>
   <command id="cmd_selectAll"/>
   <command id="cmd_selectThread" oncommand="goDoCommand('cmd_selectThread')"/>
   <command id="cmd_selectFlagged" oncommand="goDoCommand('cmd_selectFlagged')"/>
   <command id="cmd_properties" oncommand="goDoCommand('cmd_properties')"
          valueNewsgroup="&folderPropsNewsgroupCmd.label;"
          valueFolder="&folderPropsFolderCmd.label;"
          valueGeneric="&folderPropsCmd.label;"/>
   <command id="cmd_find" oncommand="goDoCommand('cmd_find')" disabled="true"/>
@@ -1479,16 +1476,19 @@
                           command="cmd_recalculateJunkScore"/>
               </menupopup>
             </menu>
             <menuseparator id="messageMenuAfterMarkSeparator"/>
             <menuitem id="archiveMainMenu" label="&archiveMsgCmd.label;"
                       accesskey="&archiveMsgCmd.accesskey;"
                       key="key_archive"
                       command="cmd_archive"/>
+            <menuitem id="menu_cancel" command="cmd_cancel"
+                      label="&cancelNewsMsgCmd.label;"
+                      accesskey="&cancelNewsMsgCmd.accesskey;"/>
             <menu id="moveMenu"
                   label="&moveMsgToMenu.label;"
                   accesskey="&moveMsgToMenu.accesskey;"
                   oncommand="MsgMoveMessage(event.target._folder)">
               <menupopup type="folder" mode="filing" showFileHereLabel="true"
                          showRecent="true" fileHereLabel="&fileHereMenu.label;"
                          fileHereAccessKey="&fileHereMenu.accesskey;"
                          recentLabel="&moveCopyMsgRecentMenu.label;"
--- a/mail/locales/en-US/chrome/messenger/messenger.dtd
+++ b/mail/locales/en-US/chrome/messenger/messenger.dtd
@@ -68,24 +68,20 @@
 <!ENTITY printSetupCmd.label "Page Setup…">
 <!ENTITY printSetupCmd.accesskey "u">
 
 <!-- Edit Menu -->
 <!ENTITY deleteMsgCmd.label "Delete Message">
 <!ENTITY deleteMsgCmd.accesskey "D">
 <!ENTITY undeleteMsgCmd.label "Undelete Message">
 <!ENTITY undeleteMsgCmd.accesskey "d">
-<!ENTITY cancelNewsMsgCmd.label "Cancel Message">
-<!ENTITY cancelNewsMsgCmd.accesskey "M">
 <!ENTITY deleteMsgsCmd.label "Delete Selected Messages">
 <!ENTITY deleteMsgsCmd.accesskey "D">
 <!ENTITY undeleteMsgsCmd.label "Undelete Selected Messages">
 <!ENTITY undeleteMsgsCmd.accesskey "d">
-<!ENTITY cancelNewsMsgsCmd.label "Cancel Selected Messages">
-<!ENTITY cancelNewsMsgsCmd.accesskey "n">
 <!ENTITY deleteFolderCmd.label "Delete Folder">
 <!ENTITY deleteFolderCmd.accesskey "D">
 <!ENTITY unsubscribeNewsgroupCmd.label "Unsubscribe">
 <!ENTITY unsubscribeNewsgroupCmd.accesskey "n">
 <!ENTITY selectMenu.label "Select">
 <!ENTITY selectMenu.accesskey "S">
 <!ENTITY all.label "All">
 <!ENTITY all.accesskey "A">
@@ -325,16 +321,18 @@ you can use these alternative items. Oth
 <!ENTITY msgMenu.accesskey "M">
 <!ENTITY newMsgCmd.label "New Message">
 <!ENTITY newMsgCmd.accesskey "N">
 <!ENTITY newNewMsgCmd.label "Message">
 <!ENTITY newNewMsgCmd.accesskey "M">
 <!ENTITY archiveMsgCmd.label "Archive">
 <!ENTITY archiveMsgCmd.accesskey "A">
 <!ENTITY archiveMsgCmd.key  "a">
+<!ENTITY cancelNewsMsgCmd.label "Cancel Message">
+<!ENTITY cancelNewsMsgCmd.accesskey "C">
 <!ENTITY replyMsgCmd.label "Reply">
 <!ENTITY replyMsgCmd.accesskey "R">
 <!ENTITY replyMsgCmd.key  "r">
 <!ENTITY replySenderCmd.label "Reply to Sender Only">
 <!ENTITY replySenderCmd.accesskey "R">
 <!ENTITY replyNewsgroupCmd.label "Reply to Newsgroup">
 <!ENTITY replyNewsgroupCmd.accesskey  "y">
 <!ENTITY replyToAllMsgCmd.label "Reply to All">
--- a/mail/locales/en-US/chrome/messenger/news.properties
+++ b/mail/locales/en-US/chrome/messenger/news.properties
@@ -62,18 +62,16 @@ newNewsgroupHeaders=Downloading %1$S of 
 # header being filtered on, %2$S is the number of the current header being
 # downloaded, %3$S is the number of headers to be downloaded, and %4$S is the
 # newsgroup whose headers are being downloaded.
 newNewsgroupFilteringHeaders=Getting headers for filters: %1$S (%2$S/%3$S) on %4$S
 downloadingArticles=Downloading articles %S-%S
 bytesReceived=Downloading newsgroups: %S received (%SKB read at %SKB/sec)
 downloadingArticlesForOffline=Downloading articles %S-%S in %S
 
-onlyCancelOneMessage=You can only cancel one article at a time.
-
 # LOCALIZATION NOTE (autoUnsubscribeText): %1$S is the newsgroup and %2$S is the newsgroup-server it is being removed from.
 autoUnsubscribeText=The newsgroup %1$S does not appear to exist on the host %2$S.  Would you like to unsubscribe from it?
 
 # LOCALIZATION NOTE (autoSubscribeText): %1$S is the newsgroup.
 autoSubscribeText=Would you like to subscribe to %1$S?
 
 # LOCALIZATION NOTE (Error -304): In the following item, don't translate "NNTP"
 # Error - server error
--- a/mailnews/base/public/nsIMsgFolder.idl
+++ b/mailnews/base/public/nsIMsgFolder.idl
@@ -405,16 +405,26 @@ interface nsIMsgFolder : nsISupports {
    */
   boolean isSpecialFolder(in unsigned long flags,
                           [optional] in boolean checkAncestors);
 
   void getExpansionArray(in nsISupportsArray expansionArray);
 
   ACString getUriForMsg(in nsIMsgDBHdr msgHdr);
 
+  /**
+   * Deletes the messages from the folder.
+   *
+   * @param messages      The array of nsIMsgDBHdr objects to be deleted.
+   * @param msgWindow     The standard message window object, for alerts et al.
+   * @param deleteStorage Whether or not the message should be truly deleted, as
+                          opposed to moving to trash.
+   * @param isMove        Whether or not this is a deletion for moving messages.
+   * @param allowUndo     Whether this action should be undoable.
+   */
   void deleteMessages(in nsIArray messages,
                       in nsIMsgWindow msgWindow,
                       in boolean deleteStorage, in boolean isMove,
                       in nsIMsgCopyServiceListener listener, in boolean allowUndo);
 
   void copyMessages(in nsIMsgFolder srcFolder, in nsIArray messages,
                     in boolean isMove, in nsIMsgWindow msgWindow,
                     in nsIMsgCopyServiceListener listener, in boolean isFolder,
--- a/mailnews/base/src/nsMsgDBView.cpp
+++ b/mailnews/base/src/nsMsgDBView.cpp
@@ -2503,19 +2503,17 @@ NS_IMETHODIMP nsMsgDBView::GetCommandSta
     haveSelection = m_currentlyDisplayedViewIndex != nsMsgViewIndex_None;
 
   switch (command)
   {
   case nsMsgViewCommandType::deleteMsg:
   case nsMsgViewCommandType::deleteNoTrash:
     {
       PRBool canDelete;
-      // news folders can't delete (or move messages)
-      // but we use delete for cancel messages.
-      if (m_folder && !mIsNews && NS_SUCCEEDED(m_folder->GetCanDeleteMessages(&canDelete)) && !canDelete)
+      if (m_folder && NS_SUCCEEDED(m_folder->GetCanDeleteMessages(&canDelete)) && !canDelete)
         *selectable_p = PR_FALSE;
       else
         *selectable_p = haveSelection;
     }
     break;
   case nsMsgViewCommandType::applyFilters:
     // disable if no messages
     // XXX todo, check that we have filters, and at least one is enabled
--- a/mailnews/news/public/nsIMsgNewsFolder.idl
+++ b/mailnews/news/public/nsIMsgNewsFolder.idl
@@ -42,17 +42,17 @@
 #include "nsTArray.h"
 %}
 
 interface nsIMsgWindow;
 interface nsINntpIncomingServer;
 
 [ref] native nsMsgKeyArrayRef(nsTArray<nsMsgKey>);
 
-[scriptable, uuid(e56a2366-e66c-4240-a9a1-48c4a0a9ffbc)]
+[scriptable, uuid(86a38356-6ab0-4117-8269-674cbb4f264f)]
 interface nsIMsgNewsFolder : nsISupports {
   attribute ACString groupUsername;
   attribute ACString groupPassword;
 
   readonly attribute AString unicodeName;
   /**|rawName| is an 8-bit string to represent the name of a newsgroup used by 
    * a news server. It's offered for the convenience of callers so that they 
    * don't have to convert |unicodeName| to the server-side name when 
@@ -96,9 +96,20 @@ interface nsIMsgNewsFolder : nsISupports
   void notifyFinishedDownloadinghdrs();
 
   /**
    * Retrieves the database, but does not cache it in mDatabase.
    *
    * This is useful for operations that shouldn't hold open the database.
    */
   nsIMsgDatabase getDatabaseWithoutCache();
+
+  /**
+   * Requests that a message be canceled.
+   *
+   * Note that, before sending the news cancel, this method will check to make
+   * sure that the user has proper permission to cancel the message.
+   *
+   * @param aMsgHdr     The header of the message to be canceled.
+   * @param aMsgWindow  The standard message window object, for error dialogs.
+   */
+  void cancelMessage(in nsIMsgDBHdr aMsgHdr, in nsIMsgWindow aMsgWindow);
 };
--- a/mailnews/news/src/nsNewsFolder.cpp
+++ b/mailnews/news/src/nsNewsFolder.cpp
@@ -396,24 +396,16 @@ nsMsgNewsFolder::GetCanFileMessages(PRBo
 {
   NS_ENSURE_ARG_POINTER(aResult);
   // you can't file messages into a news server or news group
   *aResult = PR_FALSE;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsMsgNewsFolder::GetCanDeleteMessages(PRBool *aCanDeleteMessages)
-{
-  NS_ENSURE_ARG_POINTER(aCanDeleteMessages);
-  *aCanDeleteMessages = PR_FALSE;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsMsgNewsFolder::GetCanCreateSubfolders(PRBool *aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
   *aResult = PR_FALSE;
   // you can't create subfolders on a news server or a news group
   return NS_OK;
 }
 
@@ -786,62 +778,71 @@ NS_IMETHODIMP nsMsgNewsFolder::GetRequir
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP nsMsgNewsFolder::GetSizeOnDisk(PRUint32 *size)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
-/* this is news, so remember that DeleteMessage is really CANCEL. */
 NS_IMETHODIMP
 nsMsgNewsFolder::DeleteMessages(nsIArray *messages, nsIMsgWindow *aMsgWindow,
                                 PRBool deleteStorage, PRBool isMove,
-                                nsIMsgCopyServiceListener* listener, PRBool allowUndo)
+                                nsIMsgCopyServiceListener* listener,
+                                PRBool allowUndo)
 {
   nsresult rv = NS_OK;
 
   NS_ENSURE_ARG_POINTER(messages);
   NS_ENSURE_ARG_POINTER(aMsgWindow);
 
-  PRUint32 count = 0;
-  rv = messages->GetLength(&count);
+  if (!isMove)
+  {
+    nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+    if (notifier)
+      notifier->NotifyMsgsDeleted(messages);
+  }
+
+  rv = GetDatabase();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (count != 1)
+  rv = EnableNotifications(allMessageCountNotifications, PR_FALSE, PR_TRUE);
+  if (NS_SUCCEEDED(rv))
   {
-    nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsCOMPtr<nsIStringBundle> bundle;
-    rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+    PRUint32 count = 0;
+    rv = messages->GetLength(&count);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsString alertText;
-    rv = bundle->GetStringFromName(NS_LITERAL_STRING("onlyCancelOneMessage").get(), getter_Copies(alertText));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsCOMPtr<nsIPrompt> dialog;
-    rv = aMsgWindow->GetPromptDialog(getter_AddRefs(dialog));
-    NS_ENSURE_SUCCESS(rv,rv);
+    for (PRUint32 i = 0; i < count && NS_SUCCEEDED(rv); i++)
+    {
+      nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(messages, i, &rv);
+      if (msgHdr)
+        rv = mDatabase->DeleteHeader(msgHdr, nsnull, PR_TRUE, PR_TRUE);
+    }
+    EnableNotifications(allMessageCountNotifications, PR_TRUE, PR_TRUE);
+  }
+ 
+  if (!isMove) 
+    NotifyFolderEvent(NS_SUCCEEDED(rv) ? mDeleteOrMoveMsgCompletedAtom :
+      mDeleteOrMoveMsgFailedAtom);
 
-    if (dialog)
-    {
-      rv = dialog->Alert(nsnull, alertText.get());
-      NS_ENSURE_SUCCESS(rv,rv);
-    }
-    // return failure, since the cancel failed
-    return NS_ERROR_FAILURE;
-  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgNewsFolder::CancelMessage(nsIMsgDBHdr *msgHdr,
+                                             nsIMsgWindow *aMsgWindow)
+{
+  NS_ENSURE_ARG_POINTER(msgHdr);
+  NS_ENSURE_ARG_POINTER(aMsgWindow);
+
+  nsresult rv;
 
   nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv,rv);
 
-  nsCOMPtr<nsIMsgDBHdr> msgHdr(do_QueryElementAt(messages, 0));
-
   // for cancel, we need to
   // turn "newsmessage://sspitzer@news.mozilla.org/netscape.test#5428"
   // into "news://sspitzer@news.mozilla.org/23423@netscape.com"
 
   nsCOMPtr <nsIMsgIncomingServer> server;
   rv = GetServer(getter_AddRefs(server));
   NS_ENSURE_SUCCESS(rv,rv);
 
--- a/mailnews/news/src/nsNewsFolder.h
+++ b/mailnews/news/src/nsNewsFolder.h
@@ -89,17 +89,16 @@ public:
 
   NS_IMETHOD DeleteMessages(nsIArray *messages,
                       nsIMsgWindow *msgWindow, PRBool deleteStorage, PRBool isMove,
                       nsIMsgCopyServiceListener* listener, PRBool allowUndo);
   NS_IMETHOD GetNewMessages(nsIMsgWindow *aWindow, nsIUrlListener *aListener);
 
   NS_IMETHOD GetCanSubscribe(PRBool *aResult);
   NS_IMETHOD GetCanFileMessages(PRBool *aResult);
-  NS_IMETHOD GetCanDeleteMessages(PRBool *aCanDeleteMessages);
   NS_IMETHOD GetCanCreateSubfolders(PRBool *aResult);
   NS_IMETHOD GetCanRename(PRBool *aResult);
   NS_IMETHOD GetCanCompact(PRBool *aResult);
   NS_IMETHOD OnReadChanged(nsIDBChangeListener * aInstigator);
 
   NS_IMETHOD DownloadMessagesForOffline(nsIArray *messages, nsIMsgWindow *window);
   NS_IMETHOD Compact(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow);
   NS_IMETHOD DownloadAllForOffline(nsIUrlListener *listener, nsIMsgWindow *msgWindow);
--- a/suite/locales/en-US/chrome/mailnews/messenger.dtd
+++ b/suite/locales/en-US/chrome/mailnews/messenger.dtd
@@ -121,18 +121,16 @@
 <!ENTITY undeleteMsgCmd.label "Undelete Message">
 <!ENTITY undeleteMsgCmd.accesskey "d">
 <!ENTITY cancelNewsMsgCmd.label "Cancel Message">
 <!ENTITY cancelNewsMsgCmd.accesskey "n">
 <!ENTITY deleteMsgsCmd.label "Delete Selected Messages">
 <!ENTITY deleteMsgsCmd.accesskey "D">
 <!ENTITY undeleteMsgsCmd.label "Undelete Selected Messages">
 <!ENTITY undeleteMsgsCmd.accesskey "d">
-<!ENTITY cancelNewsMsgsCmd.label "Cancel Selected Messages">
-<!ENTITY cancelNewsMsgsCmd.accesskey "n">
 <!ENTITY deleteFolderCmd.label "Delete Folder">
 <!ENTITY deleteFolderCmd.accesskey "D">
 <!ENTITY unsubscribeNewsgroupCmd.label "Unsubscribe">
 <!ENTITY unsubscribeNewsgroupCmd.accesskey "n">
 <!ENTITY selectMenu.label "Select">
 <!ENTITY selectMenu.accesskey "S">
 <!ENTITY selectThreadCmd.label "Thread">
 <!ENTITY selectThreadCmd.accesskey "T">
--- a/suite/locales/en-US/chrome/mailnews/news.properties
+++ b/suite/locales/en-US/chrome/mailnews/news.properties
@@ -62,18 +62,16 @@ newNewsgroupHeaders=Downloading %1$S of 
 # header being filtered on, %2$S is the number of the current header being
 # downloaded, %3$S is the number of headers to be downloaded, and %4$S is the
 # newsgroup whose headers are being downloaded.
 newNewsgroupFilteringHeaders=Getting headers for filters: %1$S (%2$S/%3$S) on %4$S
 downloadingArticles=Downloading articles %S-%S
 bytesReceived=Downloading newsgroups: %S received (%SKB read at %SKB/sec)
 downloadingArticlesForOffline=Downloading articles %S-%S in %S
 
-onlyCancelOneMessage=You can only cancel one article at a time.
-
 # LOCALIZATION NOTE (autoUnsubscribeText): %1$S is the newsgroup and %2$S is the newsgroup-server it is being removed from.
 autoUnsubscribeText=The newsgroup %1$S does not appear to exist on the host %2$S.  Would you like to unsubscribe from it?
 
 # LOCALIZATION NOTE (autoSubscribeText): %1$S is the newsgroup.
 autoSubscribeText=Would you like to subscribe to %1$S?
 
 # LOCALIZATION NOTE (Error -304): In the following item, don't translate "NNTP"
 # Error - server error
--- a/suite/mailnews/mail3PaneWindowCommands.js
+++ b/suite/mailnews/mail3PaneWindowCommands.js
@@ -241,16 +241,17 @@ var DefaultController =
       case "cmd_downloadFlagged":
       case "cmd_downloadSelected":
       case "cmd_synchronizeOffline":
         return(CheckOnline());
 
       case "cmd_watchThread":
       case "cmd_killThread":
       case "cmd_killSubthread":
+      case "cmd_cancel":
         return(isNewsURI(GetFirstSelectedMessage()));
 
 			default:
 				return false;
 		}
 	},
 
   isCommandEnabled: function(command)
@@ -270,16 +271,19 @@ var DefaultController =
         if (gDBView)
           gDBView.getCommandStatus(nsMsgViewCommandType.deleteMsg, enabled, checkStatus);
         return enabled.value;
       case "cmd_shiftDelete":
       case "button_shiftDelete":
         if (gDBView)
           gDBView.getCommandStatus(nsMsgViewCommandType.deleteNoTrash, enabled, checkStatus);
         return enabled.value;
+      case "cmd_cancel":
+        return GetNumSelectedMessages() == 1 &&
+               gFolderDisplay.selectedMessageIsNews;
       case "button_junk":
         UpdateJunkToolbarButton();
         if (gDBView)
           gDBView.getCommandStatus(nsMsgViewCommandType.junk, enabled, checkStatus);
         return enabled.value;
       case "cmd_killThread":
       case "cmd_killSubthread":
         return GetNumSelectedMessages() > 0;
@@ -552,16 +556,21 @@ var DefaultController =
         MsgDeleteMessage(false);
         UpdateDeleteToolbarButton(false);
         break;
       case "cmd_shiftDelete":
       case "button_shiftDelete":
         MsgDeleteMessage(true);
         UpdateDeleteToolbarButton(false);
         break;
+      case "cmd_cancel":
+        let message = gFolderDisplay.selectedMessage;
+        message.folder.QueryInterface(Components.interfaces.nsIMsgNewsFolder)
+                      .cancelMessage(message, msgWindow);
+        break;
       case "cmd_killThread":
         /* kill thread kills the thread and then does a next unread */
       	GoNextMessage(nsMsgNavigationType.toggleThreadKilled, true);
         break;
       case "cmd_killSubthread":
         GoNextMessage(nsMsgNavigationType.toggleSubthreadKilled, true);
         break;
       case "cmd_watchThread":
--- a/suite/mailnews/mailWindowOverlay.js
+++ b/suite/mailnews/mailWindowOverlay.js
@@ -384,16 +384,20 @@ function InitMessageMenu()
   var killSubthreadMenuItem = document.getElementById("killSubthread");
   if (killSubthreadMenuItem) {
       killSubthreadMenuItem.setAttribute("hidden", isNews ? "" : "true");
   }
   var watchThreadMenuItem = document.getElementById("watchThread");
   if (watchThreadMenuItem) {
       watchThreadMenuItem.setAttribute("hidden", isNews ? "" : "true");
   }
+  var cancelMenuItem = document.getElementById("menu_cancel");
+  if (cancelMenuItem) {
+      cancelMenuItem.setAttribute("hidden", isNews ? "" : "true");
+  }
 
   // Disable the Move and Copy menus if there are no messages selected.
   // Disable the Move menu if we can't delete messages from the folder.
   var moveMenu = document.getElementById("moveMenu");
   var msgFolder = GetLoadedMsgFolder();
   if(moveMenu)
   {
       var enableMenuItem = aMessage && msgFolder && msgFolder.canDeleteMessages;
@@ -869,19 +873,17 @@ function UpdateDeleteToolbarButton(aFold
   // Wallpaper over Bug 491676 by using the attribute instead of the property.
   deleteButtonDeck.setAttribute("selectedIndex", selectedIndex);
 }
 
 function UpdateDeleteCommand()
 {
   var value = "value";
   var uri = GetFirstSelectedMessage();
-  if (IsNewsMessage(uri))
-    value += "News";
-  else if (SelectedMessagesAreDeleted())
+  if (SelectedMessagesAreDeleted())
     value += "IMAPDeleted";
   if (GetNumSelectedMessages() < 2)
     value += "Message";
   else
     value += "Messages";
   goSetMenuValue("cmd_delete", value);
   goSetAccessKey("cmd_delete", value + "AccessKey");
 }
@@ -1080,24 +1082,18 @@ function MsgCopyMessage(destFolder)
 }
 
 function MsgMoveMessage(destFolder)
 {
   try {
     // get the msg folder we're moving messages into
     var destUri = destFolder.getAttribute('id');
     let destMsgFolder = GetMsgFolderFromUri(destUri);
-    // we don't move news messages, we copy them
-    if (isNewsURI(gDBView.msgFolder.URI)) {
-      gDBView.doCommandWithFolder(nsMsgViewCommandType.copyMessages, destMsgFolder);
-    }
-    else {
-      SetNextMessageAfterDelete();
-      gDBView.doCommandWithFolder(nsMsgViewCommandType.moveMessages, destMsgFolder);
-    }
+    SetNextMessageAfterDelete();
+    gDBView.doCommandWithFolder(nsMsgViewCommandType.moveMessages, destMsgFolder);
   }
   catch (ex) {
     dump("MsgMoveMessage failed: " + ex + "\n");
   }
 }
 
 /**
  * Calls the ComposeMessage function with the desired type and proper default
--- a/suite/mailnews/mailWindowOverlay.xul
+++ b/suite/mailnews/mailWindowOverlay.xul
@@ -194,24 +194,20 @@
          valueFolder="&deleteFolderCmd.label;"
          valueFolderAccessKey="&deleteFolderCmd.accesskey;"
          valueNewsgroup="&unsubscribeNewsgroupCmd.label;"
          valueNewsgroupAccessKey="&unsubscribeNewsgroupCmd.accesskey;"
          valueMessage="&deleteMsgCmd.label;"
          valueMessageAccessKey="&deleteMsgCmd.accesskey;"
          valueIMAPDeletedMessage="&undeleteMsgCmd.label;"
          valueIMAPDeletedMessageAccessKey="&undeleteMsgCmd.accesskey;"
-         valueNewsMessage="&cancelNewsMsgCmd.label;"
-         valueNewsMessageAccessKey="&cancelNewsMsgCmd.accesskey;"
          valueMessages="&deleteMsgsCmd.label;"
          valueMessagesAccessKey="&deleteMsgsCmd.accesskey;"
          valueIMAPDeletedMessages="&undeleteMsgsCmd.label;"
-         valueIMAPDeletedMessagesAccessKey="&undeleteMsgsCmd.accesskey;"
-         valueNewsMessages="&cancelNewsMsgsCmd.label;"
-         valueNewsMessagesAccessKey="&cancelNewsMsgsCmd.accesskey;"/>
+         valueIMAPDeletedMessagesAccessKey="&undeleteMsgsCmd.accesskey;"/>
   <command id="cmd_selectAll"/>
   <command id="cmd_selectThread" oncommand="goDoCommand('cmd_selectThread')"/>
   <command id="cmd_selectFlagged" oncommand="goDoCommand('cmd_selectFlagged')"/>
   <command id="cmd_properties" oncommand="goDoCommand('cmd_properties')"
          valueNewsgroup="&folderPropsNewsgroupCmd.label;"
          valueFolder="&folderPropsFolderCmd.label;"
          valueGeneric="&folderPropsCmd.label;"/>
   <command id="cmd_find" oncommand="goDoCommand('cmd_find')" disabled="true"/>
@@ -256,16 +252,17 @@
   <command id="cmd_replySenderAndGroup" oncommand="goDoCommand('cmd_replySenderAndGroup')"/>
   <command id="cmd_replyAllRecipients" oncommand="goDoCommand('cmd_replyAllRecipients')"/>
   <command id="cmd_forward" oncommand="goDoCommand('cmd_forward')"/>
   <command id="cmd_forwardInline" oncommand="goDoCommand('cmd_forwardInline')"/>
   <command id="cmd_forwardAttachment" oncommand="goDoCommand('cmd_forwardAttachment')"/>
   <command id="cmd_editAsNew" oncommand="goDoCommand('cmd_editAsNew')"/>
   <command id="cmd_openMessage" oncommand="goDoCommand('cmd_openMessage')"/>
   <command id="cmd_createFilterFromMenu" oncommand="goDoCommand('cmd_createFilterFromMenu')"/>
+  <command id="cmd_cancel" oncommand="goDoCommand('cmd_cancel')"/>
   <command id="cmd_killThread" oncommand="goDoCommand('cmd_killThread')"/>
   <command id="cmd_killSubthread" oncommand="goDoCommand('cmd_killSubthread')"/>
   <command id="cmd_watchThread" oncommand="goDoCommand('cmd_watchThread')"/>
 </commandset>
 
 <commandset id="mailToolbarItems"
             commandupdater="true"
             events="mail-toolbar"
@@ -1859,16 +1856,20 @@
       </menupopup>
     </menu>
     <menuseparator id="messageMenuAfterMarkSeparator"/>
     <menuitem id="createFilter"
               label="&createFilter.label;"
               accesskey="&createFilter.accesskey;"
               command="cmd_createFilterFromMenu"/>
     <menuseparator id="threadItemsSeparator"/>
+    <menuitem id="menu_cancel"
+              label="&cancelNewsMsgCmd.label;"
+              accesskey="&cancelNewsMsgCmd.accesskey;"
+              command="cmd_cancel"/>
     <menuitem id="killThread"
               label="&killThreadMenu.label;"
               accesskey="&killThreadMenu.accesskey;"
               key="key_killThread" command="cmd_killThread"/>
     <menuitem id="killSubthread"
               label="&killSubthreadMenu.label;"
               accesskey="&killSubthreadMenu.accesskey;"
               key="key_killSubthread" command="cmd_killSubthread"/>
--- a/suite/mailnews/messageWindow.js
+++ b/suite/mailnews/messageWindow.js
@@ -711,17 +711,17 @@ var MessageWindowController =
         // fall through
       case "button_delete":
         if (command == "button_delete")
           UpdateDeleteToolbarButton(false);
         // fall through
       case "cmd_shiftDelete":
       case "button_shiftDelete":
         loadedFolder = GetLoadedMsgFolder();
-        return gCurrentMessageUri && loadedFolder && (loadedFolder.canDeleteMessages || isNewsURI(gCurrentFolderUri));
+        return gCurrentMessageUri && loadedFolder && loadedFolder.canDeleteMessages;
       case "button_junk":
         UpdateJunkToolbarButton();
         // fall through
       case "cmd_markAsJunk":
 			case "cmd_markAsNotJunk":
         if (gDBView)
           gDBView.getCommandStatus(nsMsgViewCommandType.junk, enabled, checkStatus);
         return enabled.value;
--- a/suite/mailnews/search/SearchDialog.js
+++ b/suite/mailnews/search/SearchDialog.js
@@ -94,17 +94,17 @@ var nsSearchResultsController =
           case "goto_folder_button":
             if (GetNumSelectedMessages() != 1)
               enabled = false;
             break;
           case "cmd_delete":
           case "cmd_shiftDelete":
           case "button_delete":
             // this assumes that advanced searches don't cross accounts
-            if (GetNumSelectedMessages() <= 0 || isNewsURI(gSearchView.getURIForViewIndex(0)))
+            if (GetNumSelectedMessages() <= 0)
               enabled = false;
             break;
           case "saveas_vf_button":
               // need someway to see if there are any search criteria...
               return true;
           case "cmd_selectAll":
             return GetDBView() != null;              
           default:
@@ -607,21 +607,16 @@ function GetNumSelectedMessages()
 
 function GetDBView()
 {
     return gSearchView;
 }
 
 function MsgDeleteSelectedMessages(aCommandType)
 {
-    // we don't delete news messages, we just return in that case
-    if (isNewsURI(gSearchView.getURIForViewIndex(0))) 
-        return;
-
-    // if mail messages delete
     SetNextMessageAfterDelete();
     gSearchView.doCommand(aCommandType);
 }
 
 function SetNextMessageAfterDelete()
 {
   gNextMessageViewIndexAfterDelete = gSearchView.msgToSelectAfterDelete;
 }
@@ -736,24 +731,18 @@ function MoveMessageInSearch(destFolder)
         // "File Here"
         var destUri = destFolder.getAttribute('id');
         if (destUri.length == 0) { 
           destUri = destFolder.getAttribute('file-uri')
         }
 
         var destMsgFolder = GetMsgFolderFromUri(destUri).QueryInterface(Components.interfaces.nsIMsgFolder);
 
-        // we don't move news messages, we copy them
-        if (isNewsURI(gSearchView.getURIForViewIndex(0))) {
-          gSearchView.doCommandWithFolder(nsMsgViewCommandType.copyMessages, destMsgFolder);
-        }
-        else {
-            SetNextMessageAfterDelete();
-            gSearchView.doCommandWithFolder(nsMsgViewCommandType.moveMessages, destMsgFolder);
-        } 
+        SetNextMessageAfterDelete();
+        gSearchView.doCommandWithFolder(nsMsgViewCommandType.moveMessages, destMsgFolder);
     }
     catch (ex) {
         dump("MsgMoveMessage failed: " + ex + "\n");
     }   
 }
 
 function GoToFolder()
 {