From 69cd96a37ca2d84fc1e18c685ccd1b04bcde55f1 Mon Sep 17 00:00:00 2001
From: chylex <contact@chylex.com>
Date: Tue, 22 Aug 2017 11:59:34 +0200
Subject: [PATCH] Add 'View detail' context menu item in notifications
 (currently loaded tweets only)

---
 Core/Bridge/TweetDeckBridge.cs                |  4 +--
 Core/FormBrowser.cs                           | 25 +++++++++++++++++++
 Core/Handling/ContextMenuNotification.cs      | 17 ++++++++-----
 Core/Notification/FormNotificationBase.cs     | 21 +++++++++++-----
 .../FormNotificationScreenshotable.cs         |  2 +-
 .../Screenshot/TweetScreenshotManager.cs      |  2 +-
 Core/Notification/TweetNotification.cs        | 16 ++++++++----
 Resources/Scripts/code.js                     | 16 +++++++++++-
 8 files changed, 81 insertions(+), 22 deletions(-)

diff --git a/Core/Bridge/TweetDeckBridge.cs b/Core/Bridge/TweetDeckBridge.cs
index ca141234..36409889 100644
--- a/Core/Bridge/TweetDeckBridge.cs
+++ b/Core/Bridge/TweetDeckBridge.cs
@@ -77,10 +77,10 @@ public void OpenContextMenu(){
             form.InvokeAsyncSafe(form.OpenContextMenu);
         }
 
-        public void OnTweetPopup(string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
+        public void OnTweetPopup(string columnKey, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
             notification.InvokeAsyncSafe(() => {
                 form.OnTweetNotification();
-                notification.ShowNotification(new TweetNotification(columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
+                notification.ShowNotification(new TweetNotification(columnKey, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
             });
         }
 
diff --git a/Core/FormBrowser.cs b/Core/FormBrowser.cs
index b13cb46c..eb4a57b1 100644
--- a/Core/FormBrowser.cs
+++ b/Core/FormBrowser.cs
@@ -542,6 +542,31 @@ public void HideVideoOverlay(){
             browser.ExecuteScriptAsync("$('#td-video-player-overlay').remove()");
         }
 
+        public void ShowTweetDetail(string columnKey, string chirpId){
+            Activate();
+
+            using(IFrame frame = browser.GetBrowser().MainFrame){
+                if (!TwitterUtils.IsTweetDeckWebsite(frame)){
+                    FormMessage.Error("View Tweet Detail", "TweetDeck is not currently loaded.", FormMessage.OK);
+                    return;
+                }
+            }
+
+            browser.EvaluateScriptAsync("window.TDGF_showTweetDetail", columnKey, chirpId).ContinueWith(task => {
+                JavascriptResponse response = task.Result;
+                
+                if (response.Success){
+                    switch(response.Result as int? ?? -1){
+                        case 0: this.InvokeAsyncSafe(notification.FinishCurrentNotification); return;
+                        case 1: FormMessage.Error("View Tweet Detail", "The column which contained the tweet no longer exists.", FormMessage.OK); return;
+                        case 2: FormMessage.Error("View Tweet Detail", "The tweet has been unloaded.", FormMessage.OK); return; // TODO load the tweet 
+                    }
+                }
+
+                FormMessage.Error("View Tweet Detail", "An unknown error occurred when trying to view tweet detail.", FormMessage.OK);
+            });
+        }
+
         public void OnTweetScreenshotReady(string html, int width, int height){
             if (notificationScreenshotManager == null){
                 notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
diff --git a/Core/Handling/ContextMenuNotification.cs b/Core/Handling/ContextMenuNotification.cs
index 9d9f0071..a28cc789 100644
--- a/Core/Handling/ContextMenuNotification.cs
+++ b/Core/Handling/ContextMenuNotification.cs
@@ -4,10 +4,11 @@
 
 namespace TweetDuck.Core.Handling{
     class ContextMenuNotification : ContextMenuBase{
-        private const int MenuSkipTweet = 26600;
-        private const int MenuFreeze = 26601;
-        private const int MenuCopyTweetUrl = 26602;
-        private const int MenuCopyQuotedTweetUrl = 26603;
+        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 readonly FormNotificationBase form;
         private readonly bool enableCustomMenu;
@@ -28,6 +29,7 @@ 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);
@@ -39,12 +41,11 @@ public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser br
                     if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)){
                         model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
                     }
-
-                    model.AddSeparator();
                 }
             }
             
             if (HasDevTools){
+                model.AddSeparator();
                 AddDebugMenuItems(model);
             }
 
@@ -67,6 +68,10 @@ public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser b
                     form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer);
                     return true;
 
+                case MenuViewDetail:
+                    form.InvokeSafe(() => form.ShowTweetDetail());
+                    return true;
+
                 case MenuCopyTweetUrl:
                     SetClipboardText(form.CurrentTweetUrl);
                     return true;
diff --git a/Core/Notification/FormNotificationBase.cs b/Core/Notification/FormNotificationBase.cs
index 3bbd29a4..fd1fd3f2 100644
--- a/Core/Notification/FormNotificationBase.cs
+++ b/Core/Notification/FormNotificationBase.cs
@@ -73,13 +73,15 @@ public bool CanResizeWindow{
 
         protected double SizeScale => dpiScale*Program.UserConfig.ZoomMultiplier;
 
-        protected readonly Form owner;
+        protected readonly FormBrowser owner;
         protected readonly ChromiumWebBrowser browser;
         
         private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
         private readonly float dpiScale;
 
-        private string currentColumn;
+        private string currentColumnKey;
+        private string currentChirpId;
+        private string currentColumnTitle;
         private int pauseCounter;
 
         public bool IsPaused => pauseCounter > 0;
@@ -91,7 +93,7 @@ public bool CanResizeWindow{
 
         public event EventHandler Initialized;
 
-        public FormNotificationBase(Form owner, bool enableContextMenu){
+        public FormNotificationBase(FormBrowser owner, bool enableContextMenu){
             InitializeComponent();
 
             this.owner = owner;
@@ -158,7 +160,7 @@ public virtual void HideNotification(bool loadBlank){
             }
 
             Location = ControlExtensions.InvisibleLocation;
-            currentColumn = null;
+            currentColumnTitle = null;
         }
 
         public virtual void FinishCurrentNotification(){}
@@ -183,7 +185,10 @@ protected virtual string GetTweetHTML(TweetNotification tweet){
         protected virtual void LoadTweet(TweetNotification tweet){
             CurrentTweetUrl = tweet.TweetUrl;
             CurrentQuoteUrl = tweet.QuoteUrl;
-            currentColumn = tweet.Column;
+
+            currentColumnKey = tweet.ColumnKey;
+            currentChirpId = tweet.ChirpId;
+            currentColumnTitle = tweet.ColumnTitle;
 
             resourceHandler.SetHTML(GetTweetHTML(tweet));
             browser.Load(TwitterUtils.TweetDeckURL);
@@ -198,7 +203,11 @@ protected virtual void OnNotificationReady(){
         }
 
         protected virtual void UpdateTitle(){
-            Text = string.IsNullOrEmpty(currentColumn) || !Program.UserConfig.DisplayNotificationColumn ? Program.BrandName : Program.BrandName+" - "+currentColumn;
+            Text = string.IsNullOrEmpty(currentColumnTitle) || !Program.UserConfig.DisplayNotificationColumn ? Program.BrandName : Program.BrandName+" - "+currentColumnTitle;
+        }
+
+        public void ShowTweetDetail(){
+            owner.ShowTweetDetail(currentColumnKey, currentChirpId);
         }
 
         public void MoveToVisibleLocation(){
diff --git a/Core/Notification/Screenshot/FormNotificationScreenshotable.cs b/Core/Notification/Screenshot/FormNotificationScreenshotable.cs
index 18946020..423632c7 100644
--- a/Core/Notification/Screenshot/FormNotificationScreenshotable.cs
+++ b/Core/Notification/Screenshot/FormNotificationScreenshotable.cs
@@ -14,7 +14,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
     sealed class FormNotificationScreenshotable : FormNotificationBase{
         private readonly PluginManager plugins;
 
-        public FormNotificationScreenshotable(Action callback, Form owner, PluginManager pluginManager) : base(owner, false){
+        public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager) : base(owner, false){
             this.plugins = pluginManager;
 
             browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback));
diff --git a/Core/Notification/Screenshot/TweetScreenshotManager.cs b/Core/Notification/Screenshot/TweetScreenshotManager.cs
index f6d6aafd..0aed4661 100644
--- a/Core/Notification/Screenshot/TweetScreenshotManager.cs
+++ b/Core/Notification/Screenshot/TweetScreenshotManager.cs
@@ -46,7 +46,7 @@ public void Trigger(string html, int width, int height){
                 CanMoveWindow = () => false
             };
 
-            screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, html, 0, string.Empty, string.Empty), width, height);
+            screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty), width, height);
             screenshot.Show();
             timeout.Start();
 
diff --git a/Core/Notification/TweetNotification.cs b/Core/Notification/TweetNotification.cs
index ade3f23e..f48206d1 100644
--- a/Core/Notification/TweetNotification.cs
+++ b/Core/Notification/TweetNotification.cs
@@ -35,7 +35,7 @@ public static TweetNotification ExampleTweet{
                     #endif
                 }
 
-                return new TweetNotification("Home", ExampleTweetHTML, 95, string.Empty, string.Empty, true);
+                return new TweetNotification(string.Empty, string.Empty, "Home", ExampleTweetHTML, 95, string.Empty, string.Empty, true);
             }
         }
 
@@ -55,7 +55,10 @@ public enum Size{
             Auto, Custom
         }
 
-        public string Column { get; }
+        public string ColumnKey { get; }
+        public string ChirpId { get; }
+
+        public string ColumnTitle { get; }
         public string TweetUrl { get; }
         public string QuoteUrl { get; }
         
@@ -63,10 +66,13 @@ public enum Size{
         private readonly int characters;
         private readonly bool isExample;
 
-        public TweetNotification(string column, string html, int characters, string tweetUrl, string quoteUrl) : this(column, html, characters, tweetUrl, quoteUrl, false){}
+        public TweetNotification(string columnKey, string chirpId, string title, string html, int characters, string tweetUrl, string quoteUrl) : this(columnKey, chirpId, title, html, characters, tweetUrl, quoteUrl, false){}
 
-        private TweetNotification(string column, string html, int characters, string tweetUrl, string quoteUrl, bool isExample){
-            this.Column = column;
+        private TweetNotification(string columnKey, string chirpId, string title, string html, int characters, string tweetUrl, string quoteUrl, bool isExample){
+            this.ColumnKey = columnKey;
+            this.ChirpId = chirpId;
+
+            this.ColumnTitle = title;
             this.TweetUrl = tweetUrl;
             this.QuoteUrl = quoteUrl;
 
diff --git a/Resources/Scripts/code.js b/Resources/Scripts/code.js
index c9d7a110..e38fa045 100644
--- a/Resources/Scripts/code.js
+++ b/Resources/Scripts/code.js
@@ -199,7 +199,7 @@
         let tweetUrl = source ? source.getChirpURL() : "";
         let quoteUrl = source && source.quotedTweet ? source.quotedTweet.getChirpURL() : "";
 
-        $TD.onTweetPopup(columnTypes[column.getColumnType()] || "", html.html(), duration, tweetUrl, quoteUrl);
+        $TD.onTweetPopup(column.model.privateState.key, tweet.id, columnTypes[column.getColumnType()] || "", html.html(), duration, tweetUrl, quoteUrl);
       }
 
       if (column.model.getHasSound()){
@@ -208,6 +208,20 @@
     };
   })();
   
+  window.TDGF_showTweetDetail = function(columnKey, tweetId){
+    let column = TD.controller.columnManager.get(columnKey);
+    return 1 if !column; // column no longer exists
+    
+    let chirp = column.findMostInterestingChirp(tweetId);
+    return 2 if !chirp; // TODO figure out -- tweet is no longer in cache
+    
+    TD.ui.updates.showDetailView(column, chirp, column.findChirp(chirp));
+    TD.controller.columnManager.showColumn(columnKey);
+    
+    $(document).trigger("uiGridClearSelection");
+    return 0;
+  };
+  
   //
   // Function: Retrieves the tags to be put into <head> for notification HTML code.
   //