From 442d74d0cb28e0fd20da320a1ace88cffc93e21e Mon Sep 17 00:00:00 2001
From: chylex <contact@chylex.com>
Date: Sun, 27 Aug 2017 18:18:30 +0200
Subject: [PATCH] Refactor context menu handling and make adding new types of
 context easier

---
 Core/Bridge/TweetDeckBridge.cs           |  13 +--
 Core/Handling/ContextMenuBase.cs         | 112 +++++++++++++----------
 Core/Handling/ContextMenuBrowser.cs      |  46 +++++-----
 Core/Handling/ContextMenuNotification.cs |  26 +++---
 Resources/Scripts/code.js                |  13 ++-
 5 files changed, 110 insertions(+), 100 deletions(-)

diff --git a/Core/Bridge/TweetDeckBridge.cs b/Core/Bridge/TweetDeckBridge.cs
index 36409889..5fd2c53a 100644
--- a/Core/Bridge/TweetDeckBridge.cs
+++ b/Core/Bridge/TweetDeckBridge.cs
@@ -3,6 +3,7 @@
 using System.Windows.Forms;
 using CefSharp;
 using TweetDuck.Core.Controls;
+using TweetDuck.Core.Handling;
 using TweetDuck.Core.Notification;
 using TweetDuck.Core.Other;
 using TweetDuck.Core.Utils;
@@ -10,8 +11,6 @@
 
 namespace TweetDuck.Core.Bridge{
     sealed class TweetDeckBridge{
-        public static string LastRightClickedLink = string.Empty;
-        public static string LastRightClickedImage = string.Empty;
         public static string LastHighlightedTweet = string.Empty;
         public static string LastHighlightedQuotedTweet = string.Empty;
         public static string LastHighlightedTweetAuthor = string.Empty;
@@ -19,7 +18,7 @@ sealed class TweetDeckBridge{
         public static Dictionary<string, string> SessionData = new Dictionary<string, string>(2);
 
         public static void ResetStaticProperties(){
-            LastRightClickedLink = LastRightClickedImage = LastHighlightedTweet = LastHighlightedQuotedTweet = LastHighlightedTweetAuthor = string.Empty;
+            LastHighlightedTweet = LastHighlightedQuotedTweet = LastHighlightedTweetAuthor = string.Empty;
             LastHighlightedTweetImages = StringUtils.EmptyArray;
         }
 
@@ -56,12 +55,8 @@ public void LoadNotificationHeadContents(string headContents){
             });
         }
 
-        public void SetLastRightClickedLink(string link){
-            form.InvokeAsyncSafe(() => LastRightClickedLink = link);
-        }
-
-        public void SetLastRightClickedImage(string link){
-            form.InvokeAsyncSafe(() => LastRightClickedImage = link);
+        public void SetLastRightClickInfo(string type, string link){
+            form.InvokeAsyncSafe(() => ContextMenuBase.SetContextInfo(type, link));
         }
 
         public void SetLastHighlightedTweet(string link, string quotedLink, string author, string imageList){
diff --git a/Core/Handling/ContextMenuBase.cs b/Core/Handling/ContextMenuBase.cs
index 6d78b3bd..9765c3e3 100644
--- a/Core/Handling/ContextMenuBase.cs
+++ b/Core/Handling/ContextMenuBase.cs
@@ -6,29 +6,35 @@
 using TweetDuck.Core.Bridge;
 using TweetDuck.Core.Controls;
 using TweetDuck.Core.Utils;
+using System.Collections.Generic;
 
 namespace TweetDuck.Core.Handling{
     abstract class ContextMenuBase : IContextMenuHandler{
         protected static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
 
         private static TwitterUtils.ImageQuality ImageQuality => Program.UserConfig.TwitterImageQuality;
+        
+        private static KeyValuePair<string, string> ContextInfo;
+        private static bool IsLink => ContextInfo.Key == "link";
+        private static bool IsImage => ContextInfo.Key == "image";
+        private static bool IsVideo => ContextInfo.Key == "video";
 
-        private static string GetLink(IContextMenuParams parameters){
-            return string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink;
+        public static void SetContextInfo(string type, string link){
+            ContextInfo = new KeyValuePair<string, string>(string.IsNullOrEmpty(link) ? null : type, link);
         }
 
-        private static string GetImage(IContextMenuParams parameters){
-            return string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedImage) ? parameters.SourceUrl : TweetDeckBridge.LastRightClickedImage;
+        private static string GetMediaLink(IContextMenuParams parameters){
+            return IsImage || IsVideo ? ContextInfo.Value : parameters.SourceUrl;
         }
 
-        private const int MenuOpenLinkUrl = 26500;
-        private const int MenuCopyLinkUrl = 26501;
-        private const int MenuCopyUsername = 26502;
-        private const int MenuOpenImageUrl = 26503;
-        private const int MenuCopyImageUrl = 26504;
-        private const int MenuSaveImage = 26505;
-        private const int MenuSaveAllImages = 26506;
-        private const int MenuOpenDevTools = 26599;
+        private const CefMenuCommand MenuOpenLinkUrl     = (CefMenuCommand)26500;
+        private const CefMenuCommand MenuCopyLinkUrl     = (CefMenuCommand)26501;
+        private const CefMenuCommand MenuCopyUsername    = (CefMenuCommand)26502;
+        private const CefMenuCommand MenuOpenMediaUrl    = (CefMenuCommand)26503;
+        private const CefMenuCommand MenuCopyMediaUrl    = (CefMenuCommand)26504;
+        private const CefMenuCommand MenuSaveMedia       = (CefMenuCommand)26505;
+        private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26506;
+        private const CefMenuCommand MenuOpenDevTools    = (CefMenuCommand)26599;
 
         private readonly Form form;
         
@@ -40,36 +46,46 @@ protected ContextMenuBase(Form form){
         }
 
         public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
-            bool hasTweetImage = !string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedImage);
-            lastHighlightedTweetAuthor = TweetDeckBridge.LastHighlightedTweetAuthor;
-            lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImages;
-
             if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
                 lastHighlightedTweetAuthor = string.Empty;
                 lastHighlightedTweetImageList = StringUtils.EmptyArray;
+                ContextInfo = default(KeyValuePair<string, string>);
+            }
+            else{
+                lastHighlightedTweetAuthor = TweetDeckBridge.LastHighlightedTweetAuthor;
+                lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImages;
             }
 
-            if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal) && !hasTweetImage){
+            bool hasTweetImage = IsImage;
+            bool hasTweetVideo = IsVideo;
+
+            string TextOpen(string name) => "Open "+name+" in browser";
+            string TextCopy(string name) => "Copy "+name+" address";
+            string TextSave(string name) => "Save "+name+" as...";
+            
+            if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal) && !hasTweetImage && !hasTweetVideo){
                 if (TwitterUtils.RegexAccount.IsMatch(parameters.UnfilteredLinkUrl)){
-                    model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open account in browser");
-                    model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy account address");
-                    model.AddItem((CefMenuCommand)MenuCopyUsername, "Copy account username");
+                    model.AddItem(MenuOpenLinkUrl, TextOpen("account"));
+                    model.AddItem(MenuCopyLinkUrl, TextCopy("account"));
+                    model.AddItem(MenuCopyUsername, "Copy account username");
                 }
                 else{
-                    model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open link in browser");
-                    model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy link address");
+                    model.AddItem(MenuOpenLinkUrl, TextOpen("link"));
+                    model.AddItem(MenuCopyLinkUrl, TextCopy("link"));
                 }
 
                 model.AddSeparator();
             }
 
-            if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){
-                model.AddItem((CefMenuCommand)MenuOpenImageUrl, "Open image in browser");
-                model.AddItem((CefMenuCommand)MenuCopyImageUrl, "Copy image address");
-                model.AddItem((CefMenuCommand)MenuSaveImage, "Save image as...");
+            if (hasTweetVideo){
+            }
+            else if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){
+                model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
+                model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
+                model.AddItem(MenuSaveMedia, TextSave("image"));
 
                 if (lastHighlightedTweetImageList.Length > 1){
-                    model.AddItem((CefMenuCommand)MenuSaveAllImages, "Save all images as...");
+                    model.AddItem(MenuSaveTweetImages, TextSave("all images"));
                 }
 
                 model.AddSeparator();
@@ -77,35 +93,36 @@ public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser bro
         }
 
         public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
-            switch((int)commandId){
+            switch(commandId){
                 case MenuOpenLinkUrl:
                     BrowserUtils.OpenExternalBrowser(parameters.LinkUrl);
                     break;
 
                 case MenuCopyLinkUrl:
-                    SetClipboardText(GetLink(parameters));
-                    break;
-
-                case MenuOpenImageUrl:
-                    BrowserUtils.OpenExternalBrowser(TwitterUtils.GetImageLink(GetImage(parameters), ImageQuality));
-                    break;
-
-                case MenuSaveImage:
-                    TwitterUtils.DownloadImage(GetImage(parameters), lastHighlightedTweetAuthor, ImageQuality);
-                    break;
-
-                case MenuSaveAllImages:
-                    TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthor, ImageQuality);
-                    break;
-
-                case MenuCopyImageUrl:
-                    SetClipboardText(TwitterUtils.GetImageLink(GetImage(parameters), ImageQuality));
+                    SetClipboardText(IsLink ? ContextInfo.Value : parameters.UnfilteredLinkUrl);
                     break;
 
                 case MenuCopyUsername:
                     Match match = TwitterUtils.RegexAccount.Match(parameters.UnfilteredLinkUrl);
                     SetClipboardText(match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
                     break;
+
+                case MenuOpenMediaUrl:
+                    BrowserUtils.OpenExternalBrowser(TwitterUtils.GetImageLink(GetMediaLink(parameters), ImageQuality));
+                    break;
+
+                case MenuCopyMediaUrl:
+                    SetClipboardText(TwitterUtils.GetImageLink(GetMediaLink(parameters), ImageQuality));
+                    break;
+
+                case MenuSaveMedia:
+                        TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthor, ImageQuality);
+
+                    break;
+
+                case MenuSaveTweetImages:
+                    TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthor, ImageQuality);
+                    break;
                     
                 case MenuOpenDevTools:
                     browserControl.ShowDevTools();
@@ -116,8 +133,7 @@ public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser br
         }
 
         public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
-            TweetDeckBridge.LastRightClickedLink = string.Empty;
-            TweetDeckBridge.LastRightClickedImage = string.Empty;
+            ContextInfo = default(KeyValuePair<string, string>);
         }
 
         public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
@@ -129,7 +145,7 @@ protected void SetClipboardText(string text){
         }
         
         protected static void AddDebugMenuItems(IMenuModel model){
-            model.AddItem((CefMenuCommand)MenuOpenDevTools, "Open dev tools");
+            model.AddItem(MenuOpenDevTools, "Open dev tools");
         }
 
         protected static void RemoveSeparatorIfLast(IMenuModel model){
diff --git a/Core/Handling/ContextMenuBrowser.cs b/Core/Handling/ContextMenuBrowser.cs
index 69ac45b6..fed0e03e 100644
--- a/Core/Handling/ContextMenuBrowser.cs
+++ b/Core/Handling/ContextMenuBrowser.cs
@@ -6,17 +6,17 @@
 
 namespace TweetDuck.Core.Handling{
     class ContextMenuBrowser : ContextMenuBase{
-        private const int MenuGlobal = 26600;
-        private const int MenuMute = 26601;
-        private const int MenuSettings = 26602;
-        private const int MenuPlugins = 26003;
-        private const int MenuAbout = 26604;
+        private const CefMenuCommand MenuGlobal   = (CefMenuCommand)26600;
+        private const CefMenuCommand MenuMute     = (CefMenuCommand)26601;
+        private const CefMenuCommand MenuSettings = (CefMenuCommand)26602;
+        private const CefMenuCommand MenuPlugins  = (CefMenuCommand)26003;
+        private const CefMenuCommand MenuAbout    = (CefMenuCommand)26604;
         
-        private const int MenuOpenTweetUrl = 26610;
-        private const int MenuCopyTweetUrl = 26611;
-        private const int MenuOpenQuotedTweetUrl = 26612;
-        private const int MenuCopyQuotedTweetUrl = 26613;
-        private const int MenuScreenshotTweet = 26614;
+        private const CefMenuCommand MenuOpenTweetUrl       = (CefMenuCommand)26610;
+        private const CefMenuCommand MenuCopyTweetUrl       = (CefMenuCommand)26611;
+        private const CefMenuCommand MenuOpenQuotedTweetUrl = (CefMenuCommand)26612;
+        private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26613;
+        private const CefMenuCommand MenuScreenshotTweet    = (CefMenuCommand)26614;
 
         private const string TitleReloadBrowser = "Reload browser";
         private const string TitleMuteNotifications = "Mute notifications";
@@ -55,14 +55,14 @@ public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser br
             }
 
             if (!string.IsNullOrEmpty(lastHighlightedTweet) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
-                model.AddItem((CefMenuCommand)MenuOpenTweetUrl, "Open tweet in browser");
-                model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
-                model.AddItem((CefMenuCommand)MenuScreenshotTweet, "Screenshot tweet to clipboard");
+                model.AddItem(MenuOpenTweetUrl, "Open tweet in browser");
+                model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
+                model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard");
 
                 if (!string.IsNullOrEmpty(lastHighlightedQuotedTweet)){
                     model.AddSeparator();
-                    model.AddItem((CefMenuCommand)MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
-                    model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
+                    model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
+                    model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
                 }
 
                 model.AddSeparator();
@@ -71,16 +71,16 @@ public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser br
             if ((parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
                 AddSeparator(model);
 
-                IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu((CefMenuCommand)MenuGlobal, Program.BrandName);
+                IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName);
             
                 globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser);
-                globalMenu.AddCheckItem((CefMenuCommand)MenuMute, TitleMuteNotifications);
-                globalMenu.SetChecked((CefMenuCommand)MenuMute, Program.UserConfig.MuteNotifications);
+                globalMenu.AddCheckItem(MenuMute, TitleMuteNotifications);
+                globalMenu.SetChecked(MenuMute, Program.UserConfig.MuteNotifications);
                 globalMenu.AddSeparator();
 
-                globalMenu.AddItem((CefMenuCommand)MenuSettings, TitleSettings);
-                globalMenu.AddItem((CefMenuCommand)MenuPlugins, TitlePlugins);
-                globalMenu.AddItem((CefMenuCommand)MenuAbout, TitleAboutProgram);
+                globalMenu.AddItem(MenuSettings, TitleSettings);
+                globalMenu.AddItem(MenuPlugins, TitlePlugins);
+                globalMenu.AddItem(MenuAbout, TitleAboutProgram);
 
                 if (HasDevTools){
                     globalMenu.AddSeparator();
@@ -96,8 +96,8 @@ public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser b
                 return true;
             }
 
-            switch((int)commandId){
-                case (int)CefMenuCommand.Reload:
+            switch(commandId){
+                case CefMenuCommand.Reload:
                     form.InvokeAsyncSafe(form.ReloadToTweetDeck);
                     return true;
 
diff --git a/Core/Handling/ContextMenuNotification.cs b/Core/Handling/ContextMenuNotification.cs
index a28cc789..1afdb0be 100644
--- a/Core/Handling/ContextMenuNotification.cs
+++ b/Core/Handling/ContextMenuNotification.cs
@@ -4,11 +4,11 @@
 
 namespace TweetDuck.Core.Handling{
     class ContextMenuNotification : ContextMenuBase{
-        private const int MenuViewDetail = 26600;
-        private const int MenuSkipTweet = 26601;
-        private const int MenuFreeze = 26602;
-        private const int MenuCopyTweetUrl = 26603;
-        private const int MenuCopyQuotedTweetUrl = 26604;
+        private const CefMenuCommand MenuViewDetail         = (CefMenuCommand)26600;
+        private const CefMenuCommand MenuSkipTweet          = (CefMenuCommand)26601;
+        private const CefMenuCommand MenuFreeze             = (CefMenuCommand)26602;
+        private const CefMenuCommand MenuCopyTweetUrl       = (CefMenuCommand)26603;
+        private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26604;
 
         private readonly FormNotificationBase form;
         private readonly bool enableCustomMenu;
@@ -29,17 +29,17 @@ public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser br
             base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
 
             if (enableCustomMenu){
-                model.AddItem((CefMenuCommand)MenuViewDetail, "View detail");
-                model.AddItem((CefMenuCommand)MenuSkipTweet, "Skip tweet");
-                model.AddCheckItem((CefMenuCommand)MenuFreeze, "Freeze");
-                model.SetChecked((CefMenuCommand)MenuFreeze, form.FreezeTimer);
-                model.AddSeparator();
+                model.AddItem(MenuViewDetail, "View detail");
+                model.AddItem(MenuSkipTweet, "Skip tweet");
+                model.AddCheckItem(MenuFreeze, "Freeze");
+                model.SetChecked(MenuFreeze, form.FreezeTimer);
 
                 if (!string.IsNullOrEmpty(form.CurrentTweetUrl)){
-                    model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
+                    model.AddSeparator();
+                    model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
 
                     if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)){
-                        model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
+                        model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
                     }
                 }
             }
@@ -59,7 +59,7 @@ public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser b
                 return true;
             }
 
-            switch((int)commandId){
+            switch(commandId){
                 case MenuSkipTweet:
                     form.InvokeAsyncSafe(form.FinishCurrentNotification);
                     return true;
diff --git a/Resources/Scripts/code.js b/Resources/Scripts/code.js
index 5bd94f84..b5d2f8c8 100644
--- a/Resources/Scripts/code.js
+++ b/Resources/Scripts/code.js
@@ -376,17 +376,16 @@
   // Block: Allow bypassing of t.co and include media previews in context menus.
   //
   $(document.body).delegate("a", "contextmenu", function(){
-    $TD.setLastRightClickedLink($(this).attr("data-full-url") || "");
-  });
-  
-  $(document.body).delegate("a.js-media-image-link", "contextmenu", function(){
     let me = $(this)[0];
     
-    if (me.firstElementChild){
-      $TD.setLastRightClickedImage(me.firstElementChild.getAttribute("src"));
+    if (me.classList.contains("js-media-image-link") && highlightedTweetObj){
+      let media = (highlightedTweetObj.quotedTweet || highlightedTweetObj).getMedia().find(media => media.mediaId === me.getAttribute("data-media-entity-id"));
+      
+        $TD.setLastRightClickInfo("image", media.large());
+    }
     }
     else{
-      $TD.setLastRightClickedImage(me.style.backgroundImage.replace(/url\(['"]?(.*?)['"]?\)/, "$1"));
+      $TD.setLastRightClickInfo("link", me.getAttribute("data-full-url"));
     }
   });