diff --git a/Core/FormBrowser.cs b/Core/FormBrowser.cs
index fa94e2cc..abc88fee 100644
--- a/Core/FormBrowser.cs
+++ b/Core/FormBrowser.cs
@@ -18,6 +18,7 @@ namespace TweetDick.Core{
private readonly ChromiumWebBrowser browser;
private readonly TweetDeckBridge bridge;
+ private readonly FormNotification notification;
public FormBrowser(){
InitializeComponent();
@@ -29,6 +30,9 @@ namespace TweetDick.Core{
browser.RegisterJsObject("$TD",bridge);
Controls.Add(browser);
+
+ notification = new FormNotification(this);
+ notification.Show(this);
}
protected override void WndProc(ref Message m){
@@ -97,5 +101,9 @@ namespace TweetDick.Core{
public void OpenAbout(){
// TODO
}
+
+ public void OnTweetPopup(string tweetHtml, string tweetColumn){
+ notification.ShowNotification(new TweetNotification(tweetHtml));
+ }
}
-}
+}
\ No newline at end of file
diff --git a/Core/FormNotification.Designer.cs b/Core/FormNotification.Designer.cs
new file mode 100644
index 00000000..c68cb224
--- /dev/null
+++ b/Core/FormNotification.Designer.cs
@@ -0,0 +1,54 @@
+namespace TweetDick.Core {
+ partial class FormNotification {
+ /// <summary>
+ /// Required designer variable.
+ /// </summary>
+ private System.ComponentModel.IContainer components = null;
+
+ /// <summary>
+ /// Clean up any resources being used.
+ /// </summary>
+ /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+ protected override void Dispose(bool disposing) {
+ if (disposing && (components != null)) {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ /// <summary>
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ /// </summary>
+ private void InitializeComponent() {
+ this.components = new System.ComponentModel.Container();
+ this.timer = new System.Windows.Forms.Timer(this.components);
+ this.SuspendLayout();
+ //
+ // timer
+ //
+ this.timer.Tick += new System.EventHandler(this.timer_Tick);
+ //
+ // FormNotification
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(284, 118);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow;
+ this.Location = new System.Drawing.Point(32000, 32000);
+ this.Name = "FormNotification";
+ this.ShowInTaskbar = false;
+ this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
+ this.Text = "TweetDick";
+ this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormNotification_FormClosing);
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.Timer timer;
+ }
+}
\ No newline at end of file
diff --git a/Core/FormNotification.cs b/Core/FormNotification.cs
new file mode 100644
index 00000000..504c5361
--- /dev/null
+++ b/Core/FormNotification.cs
@@ -0,0 +1,86 @@
+using System.Windows.Forms;
+using CefSharp;
+using CefSharp.WinForms;
+using System.Drawing;
+using System;
+using System.Text;
+using System.Collections.Generic;
+using TweetDick.Core.Handling;
+
+namespace TweetDick.Core{
+ partial class FormNotification : Form{
+ private readonly FormBrowser owner;
+ private readonly ChromiumWebBrowser browser;
+
+ private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4);
+
+ public FormNotification(FormBrowser owner){
+ InitializeComponent();
+
+ this.owner = owner;
+
+ browser = new ChromiumWebBrowser(""){ MenuHandler = new MenuHandlerEmpty() };
+ Controls.Add(browser);
+ }
+
+ public void ShowNotification(TweetNotification notification){
+ Screen screen = Screen.FromControl(owner);
+ Location = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-16-Width,screen.WorkingArea.Y+16);
+
+ tweetQueue.Enqueue(notification);
+
+ if (!timer.Enabled){
+ LoadNextNotification();
+ }
+ }
+
+ public void HideNotification(){
+ browser.Load("about:blank");
+ Location = new Point(32000,32000);
+ }
+
+ private void LoadNextNotification(){
+ TweetNotification tweet = tweetQueue.Dequeue();
+
+ browser.Load("about:blank");
+ browser.LoadHtml(tweet.GenerateHtml(),"http://tweetdeck.twitter.com/");
+
+ timer.Stop();
+ timer.Interval = 5000;
+ timer.Start();
+ }
+
+ private void timer_Tick(object sender, EventArgs e){
+ if (tweetQueue.Count > 0){
+ LoadNextNotification();
+ }
+ else{
+ HideNotification();
+ }
+ }
+
+ private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){
+ if (e.CloseReason == CloseReason.UserClosing){
+ HideNotification();
+ tweetQueue.Clear();
+ e.Cancel = true;
+ }
+ }
+
+ private class MenuHandlerEmpty : IContextMenuHandler{
+ public void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
+ model.Clear();
+ }
+
+ public bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
+ return false;
+ }
+
+ public void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){}
+
+ public bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
+ return false;
+ }
+ }
+ }
+}
diff --git a/Core/Handling/TweetDeckBridge.cs b/Core/Handling/TweetDeckBridge.cs
index f68e0c5d..0dc6ffc6 100644
--- a/Core/Handling/TweetDeckBridge.cs
+++ b/Core/Handling/TweetDeckBridge.cs
@@ -6,12 +6,30 @@
this.form = form;
}
+ public void LoadFontSizeClass(string fsClass){
+ form.InvokeSafe(() => {
+ TweetNotification.SetFontSizeClass(fsClass);
+ });
+ }
+
+ public void LoadNotificationHeadContents(string headContents){
+ form.InvokeSafe(() => {
+ TweetNotification.SetHeadTag(headContents);
+ });
+ }
+
public void OpenSettingsMenu(){
form.InvokeSafe(() => {
form.OpenSettings();
});
}
+ public void OnTweetPopup(string tweetHtml, string tweetColumn){
+ form.InvokeSafe(() => {
+ form.OnTweetPopup(tweetHtml,tweetColumn);
+ });
+ }
+
public void Log(string data){
System.Diagnostics.Debug.WriteLine(data);
}
diff --git a/Core/Handling/TweetNotification.cs b/Core/Handling/TweetNotification.cs
new file mode 100644
index 00000000..f9f30acd
--- /dev/null
+++ b/Core/Handling/TweetNotification.cs
@@ -0,0 +1,34 @@
+using System.Text;
+
+namespace TweetDick.Core.Handling{
+ sealed class TweetNotification{
+ private static string FontSizeClass { get; set; }
+ private static string HeadTag { get; set; }
+
+ public static void SetFontSizeClass(string newFSClass){
+ FontSizeClass = newFSClass;
+ }
+
+ public static void SetHeadTag(string headContents){
+ HeadTag = headContents;
+ }
+
+ private readonly string html;
+
+ public TweetNotification(string html){
+ this.html = html;
+ }
+
+ public string GenerateHtml(){
+ StringBuilder build = new StringBuilder();
+ build.Append("<!DOCTYPE html>");
+ build.Append("<html class='os-windows ").Append(FontSizeClass).Append("'>");
+ build.Append("<head>").Append(HeadTag).Append("</head>");
+ build.Append("<body class='hearty'><div class='app-columns-container'><div class='column' style='width:100%'>");
+ build.Append(html);
+ build.Append("</div></div></body>");
+ build.Append("</html>");
+ return build.ToString();
+ }
+ }
+}
diff --git a/Resources/code.js b/Resources/code.js
index 3e9c0cd3..6c0a9777 100644
--- a/Resources/code.js
+++ b/Resources/code.js
@@ -1,9 +1,19 @@
(function($,$TD){
+ //
+ // Constant: List of valid font size classes.
+ //
+ var fontSizeClasses = [ "txt-base-smallest", "txt-base-small", "txt-base-medium", "txt-base-large", "txt-base-largest" ];
+
//
// Variable: Says whether TweetDick events was initialized.
//
var isInitialized = false;
+ //
+ // Variable: Previous font size class in the <html> tag.
+ //
+ var prevFontSizeClass;
+
//
// Function: Initializes TweetDick events. Called after the website app is loaded.
//
@@ -29,9 +39,94 @@
},0);
});
+ // Tweet notifications
+ (function(){
+ var columns = $(".js-app-columns").first();
+
+ var refreshColumnObservers = function(){
+ columns.children().each(function(){
+ registerTweetObserverForColumn($(this));
+ });
+ };
+
+ new MutationObserver(refreshColumnObservers).observe(columns[0],{
+ childList: true
+ });
+
+ refreshColumnObservers();
+ })();
+
isInitialized = true;
};
+ //
+ // Function: Registers an observer to a TweetDeck column, which reports new tweets.
+ //
+ var registerTweetObserverForColumn = function(column){
+ if (column[0].hasAttribute("data-tweetdick-observed"))return;
+
+ var mid = column;
+ mid = mid.children().first();
+ mid = mid.children().first();
+ mid = mid.children(".js-column-content").first();
+ mid = mid.children(".js-column-scroller").first();
+
+ var container = mid.children(".js-chirp-container").first();
+ if (container.length == 0)return;
+
+ new MutationObserver(function(mutations){
+ // TODO check if popups are enabled first
+
+ Array.prototype.forEach.call(mutations,function(mutation){
+ Array.prototype.forEach.call(mutation.addedNodes,function(node){
+ if (node.tagName != "ARTICLE")return;
+
+ onNewTweet(column,node);
+ });
+ });
+ }).observe(container[0],{
+ childList: true
+ });
+
+ column[0].setAttribute("data-tweetdick-observed","");
+ };
+
+ //
+ // Function: Event callback for a new tweet.
+ //
+ var onNewTweet = function(column, tweet){
+ var html = $(tweet.outerHTML);
+ html.find("footer:first").remove();
+
+ $TD.onTweetPopup(html.html(),""); // TODO
+ };
+
+ //
+ // Function: Retrieves the font size using <html> class attribute.
+ //
+ var getFontSizeClass = function(){
+ for(var index = 0; index < fontSizeClasses.length; index++){
+ if (document.documentElement.classList.contains(fontSizeClasses[index])){
+ return fontSizeClasses[index];
+ }
+ }
+
+ return fontSizeClasses[0];
+ };
+
+ //
+ // Function: Retrieves the tags to be put into <head> for notification HTML code.
+ //
+ var getNotificationHeadContents = function(){
+ var tags = [];
+
+ $(document.head).children("link[rel='stylesheet'],meta[charset],meta[http-equiv]").each(function(){
+ tags.push($(this)[0].outerHTML);
+ });
+
+ return tags.join("");
+ };
+
//
// Block: Observe the app <div> element and initialize TweetDick whenever possible.
//
@@ -48,4 +143,31 @@
attributes: true,
attributeFilter: [ "class" ]
});
+
+ //
+ // Block: Observe changes in <html> class to update font size.
+ //
+ new MutationObserver(function(mutations){
+ var fsClass = getFontSizeClass();
+
+ if (fsClass != prevFontSizeClass){
+ prevFontSizeClass = fsClass;
+ $TD.loadFontSizeClass(fsClass);
+ }
+ }).observe(document.documentElement,{
+ attributes: true,
+ attributeFilter: [ "class" ]
+ });
+
+ //
+ // Block: Observe stylesheet swapping.
+ //
+ new MutationObserver(function(mutations){
+ $TD.loadNotificationHeadContents(getNotificationHeadContents());
+ }).observe(document.head.querySelector("[http-equiv='Default-Style']"),{
+ attributes: true,
+ attributeFilter: [ "content" ]
+ });
+
+ $TD.loadNotificationHeadContents(getNotificationHeadContents());
})($,$TD);
diff --git a/TweetDick.csproj b/TweetDick.csproj
index 8197786b..13c9dbb9 100644
--- a/TweetDick.csproj
+++ b/TweetDick.csproj
@@ -86,6 +86,13 @@
<Compile Include="Core\FormBrowser.Designer.cs">
<DependentUpon>FormBrowser.cs</DependentUpon>
</Compile>
+ <Compile Include="Core\FormNotification.cs">
+ <SubType>Form</SubType>
+ </Compile>
+ <Compile Include="Core\FormNotification.Designer.cs">
+ <DependentUpon>FormNotification.cs</DependentUpon>
+ </Compile>
+ <Compile Include="Core\Handling\TweetNotification.cs" />
<Compile Include="Core\RichTextLabel.cs">
<SubType>Component</SubType>
</Compile>