mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 10:32:10 +02:00
Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
52f1f4c4eb | |||
6c1782a038 | |||
8b8f5f5473 | |||
61d3ed891a | |||
b1abf87320 | |||
9aedfc2799 | |||
ad6240a067 | |||
9539eb076a | |||
c808e7bd83 | |||
13ea388f5e | |||
c46dc0f1a3 | |||
2ae311007d | |||
9344e02bff | |||
40ad836fc3 | |||
e8604a261d | |||
2a41d21a29 | |||
4c62aa067b | |||
49db3074c6 | |||
f5e3b34f30 | |||
f0affa4aec | |||
4f5075ac54 | |||
20f0445b10 | |||
c77c974455 | |||
44397b2d45 |
@@ -105,7 +105,7 @@ namespace TweetDuck.Configuration{
|
|||||||
set{
|
set{
|
||||||
if (_muteNotifications != value){
|
if (_muteNotifications != value){
|
||||||
_muteNotifications = value;
|
_muteNotifications = value;
|
||||||
MuteToggled?.Invoke(this, new EventArgs());
|
MuteToggled?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@ namespace TweetDuck.Configuration{
|
|||||||
set{
|
set{
|
||||||
if (_zoomLevel != value){
|
if (_zoomLevel != value){
|
||||||
_zoomLevel = value;
|
_zoomLevel = value;
|
||||||
ZoomLevelChanged?.Invoke(this, new EventArgs());
|
ZoomLevelChanged?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,7 +129,7 @@ namespace TweetDuck.Configuration{
|
|||||||
set{
|
set{
|
||||||
if (_trayBehavior != value){
|
if (_trayBehavior != value){
|
||||||
_trayBehavior = value;
|
_trayBehavior = value;
|
||||||
TrayBehaviorChanged?.Invoke(this, new EventArgs());
|
TrayBehaviorChanged?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,15 +11,18 @@ using TweetDuck.Resources;
|
|||||||
|
|
||||||
namespace TweetDuck.Core.Bridge{
|
namespace TweetDuck.Core.Bridge{
|
||||||
sealed class TweetDeckBridge{
|
sealed class TweetDeckBridge{
|
||||||
public static string LastHighlightedTweet = string.Empty;
|
public static string LastHighlightedTweetUrl = string.Empty;
|
||||||
public static string LastHighlightedQuotedTweet = string.Empty;
|
public static string LastHighlightedQuoteUrl = string.Empty;
|
||||||
public static string LastHighlightedTweetAuthor = string.Empty;
|
private static string LastHighlightedTweetAuthors = string.Empty;
|
||||||
public static string[] LastHighlightedTweetImages = StringUtils.EmptyArray;
|
private static string LastHighlightedTweetImages = string.Empty;
|
||||||
public static Dictionary<string, string> SessionData = new Dictionary<string, string>(2);
|
|
||||||
|
public static string[] LastHighlightedTweetAuthorsArray => LastHighlightedTweetAuthors.Split(';');
|
||||||
|
public static string[] LastHighlightedTweetImagesArray => LastHighlightedTweetImages.Split(';');
|
||||||
|
|
||||||
|
private static readonly Dictionary<string, string> SessionData = new Dictionary<string, string>(2);
|
||||||
|
|
||||||
public static void ResetStaticProperties(){
|
public static void ResetStaticProperties(){
|
||||||
LastHighlightedTweet = LastHighlightedQuotedTweet = LastHighlightedTweetAuthor = string.Empty;
|
LastHighlightedTweetUrl = LastHighlightedQuoteUrl = LastHighlightedTweetAuthors = LastHighlightedTweetImages = string.Empty;
|
||||||
LastHighlightedTweetImages = StringUtils.EmptyArray;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RestoreSessionData(IFrame frame){
|
public static void RestoreSessionData(IFrame frame){
|
||||||
@@ -59,12 +62,12 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
form.InvokeAsyncSafe(() => ContextMenuBase.SetContextInfo(type, link));
|
form.InvokeAsyncSafe(() => ContextMenuBase.SetContextInfo(type, link));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetLastHighlightedTweet(string link, string quotedLink, string author, string imageList){
|
public void SetLastHighlightedTweet(string tweetUrl, string quoteUrl, string authors, string imageList){
|
||||||
form.InvokeAsyncSafe(() => {
|
form.InvokeAsyncSafe(() => {
|
||||||
LastHighlightedTweet = link;
|
LastHighlightedTweetUrl = tweetUrl;
|
||||||
LastHighlightedQuotedTweet = quotedLink;
|
LastHighlightedQuoteUrl = quoteUrl;
|
||||||
LastHighlightedTweetAuthor = author;
|
LastHighlightedTweetAuthors = authors;
|
||||||
LastHighlightedTweetImages = imageList.Split(';');
|
LastHighlightedTweetImages = imageList;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,10 +75,10 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
form.InvokeAsyncSafe(form.OpenContextMenu);
|
form.InvokeAsyncSafe(form.OpenContextMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnTweetPopup(string columnKey, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
|
public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
|
||||||
notification.InvokeAsyncSafe(() => {
|
notification.InvokeAsyncSafe(() => {
|
||||||
form.OnTweetNotification();
|
form.OnTweetNotification();
|
||||||
notification.ShowNotification(new TweetNotification(columnKey, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
|
notification.ShowNotification(new TweetNotification(columnId, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,12 +120,12 @@ namespace TweetDuck.Core.Bridge{
|
|||||||
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
|
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetIdleSeconds(){
|
public void OpenBrowser(string url){
|
||||||
return NativeMethods.GetIdleSeconds();
|
form.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenBrowser(string url){
|
public int GetIdleSeconds(){
|
||||||
BrowserUtils.OpenExternalBrowser(url);
|
return NativeMethods.GetIdleSeconds();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Alert(string type, string contents){
|
public void Alert(string type, string contents){
|
||||||
|
@@ -84,6 +84,7 @@ namespace TweetDuck.Core{
|
|||||||
this.notification.Show();
|
this.notification.Show();
|
||||||
|
|
||||||
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
|
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
|
||||||
|
DialogHandler = new FileDialogHandler(),
|
||||||
MenuHandler = new ContextMenuBrowser(this),
|
MenuHandler = new ContextMenuBrowser(this),
|
||||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||||
KeyboardHandler = new KeyboardHandlerBrowser(this),
|
KeyboardHandler = new KeyboardHandlerBrowser(this),
|
||||||
@@ -377,7 +378,7 @@ namespace TweetDuck.Core{
|
|||||||
if (isLoaded){
|
if (isLoaded){
|
||||||
if (m.Msg == Program.WindowRestoreMessage){
|
if (m.Msg == Program.WindowRestoreMessage){
|
||||||
if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
|
if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
|
||||||
trayIcon_ClickRestore(trayIcon, new EventArgs());
|
trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@@ -7,6 +7,7 @@ using TweetDuck.Core.Bridge;
|
|||||||
using TweetDuck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling{
|
namespace TweetDuck.Core.Handling{
|
||||||
abstract class ContextMenuBase : IContextMenuHandler{
|
abstract class ContextMenuBase : IContextMenuHandler{
|
||||||
@@ -36,24 +37,18 @@ namespace TweetDuck.Core.Handling{
|
|||||||
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26506;
|
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26506;
|
||||||
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
|
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
|
||||||
|
|
||||||
private readonly Form form;
|
private string[] lastHighlightedTweetAuthors;
|
||||||
|
|
||||||
private string lastHighlightedTweetAuthor;
|
|
||||||
private string[] lastHighlightedTweetImageList;
|
private string[] lastHighlightedTweetImageList;
|
||||||
|
|
||||||
protected ContextMenuBase(Form form){
|
|
||||||
this.form = form;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||||
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
||||||
lastHighlightedTweetAuthor = string.Empty;
|
lastHighlightedTweetAuthors = StringUtils.EmptyArray;
|
||||||
lastHighlightedTweetImageList = StringUtils.EmptyArray;
|
lastHighlightedTweetImageList = StringUtils.EmptyArray;
|
||||||
ContextInfo = default(KeyValuePair<string, string>);
|
ContextInfo = default(KeyValuePair<string, string>);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
lastHighlightedTweetAuthor = TweetDeckBridge.LastHighlightedTweetAuthor;
|
lastHighlightedTweetAuthors = TweetDeckBridge.LastHighlightedTweetAuthorsArray;
|
||||||
lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImages;
|
lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImagesArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasTweetImage = IsImage;
|
bool hasTweetImage = IsImage;
|
||||||
@@ -99,24 +94,24 @@ namespace TweetDuck.Core.Handling{
|
|||||||
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
||||||
switch(commandId){
|
switch(commandId){
|
||||||
case MenuOpenLinkUrl:
|
case MenuOpenLinkUrl:
|
||||||
BrowserUtils.OpenExternalBrowser(parameters.LinkUrl);
|
OpenBrowser(browserControl.AsControl(), IsLink ? ContextInfo.Value : parameters.LinkUrl);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuCopyLinkUrl:
|
case MenuCopyLinkUrl:
|
||||||
SetClipboardText(IsLink ? ContextInfo.Value : parameters.UnfilteredLinkUrl);
|
SetClipboardText(browserControl.AsControl(), IsLink ? ContextInfo.Value : parameters.UnfilteredLinkUrl);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuCopyUsername:
|
case MenuCopyUsername:
|
||||||
Match match = TwitterUtils.RegexAccount.Match(parameters.UnfilteredLinkUrl);
|
Match match = TwitterUtils.RegexAccount.Match(parameters.UnfilteredLinkUrl);
|
||||||
SetClipboardText(match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
|
SetClipboardText(browserControl.AsControl(), match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuOpenMediaUrl:
|
case MenuOpenMediaUrl:
|
||||||
BrowserUtils.OpenExternalBrowser(TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
|
OpenBrowser(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuCopyMediaUrl:
|
case MenuCopyMediaUrl:
|
||||||
SetClipboardText(TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
|
SetClipboardText(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuSaveMedia:
|
case MenuSaveMedia:
|
||||||
@@ -124,13 +119,13 @@ namespace TweetDuck.Core.Handling{
|
|||||||
TwitterUtils.DownloadVideo(GetMediaLink(parameters));
|
TwitterUtils.DownloadVideo(GetMediaLink(parameters));
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthor, ImageQuality);
|
TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuSaveTweetImages:
|
case MenuSaveTweetImages:
|
||||||
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthor, ImageQuality);
|
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuOpenDevTools:
|
case MenuOpenDevTools:
|
||||||
@@ -149,8 +144,12 @@ namespace TweetDuck.Core.Handling{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void SetClipboardText(string text){
|
protected void OpenBrowser(Control control, string url){
|
||||||
form.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
|
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void SetClipboardText(Control control, string text){
|
||||||
|
control.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void AddDebugMenuItems(IMenuModel model){
|
protected static void AddDebugMenuItems(IMenuModel model){
|
||||||
|
@@ -26,10 +26,10 @@ namespace TweetDuck.Core.Handling{
|
|||||||
|
|
||||||
private readonly FormBrowser form;
|
private readonly FormBrowser form;
|
||||||
|
|
||||||
private string lastHighlightedTweet;
|
private string lastHighlightedTweetUrl;
|
||||||
private string lastHighlightedQuotedTweet;
|
private string lastHighlightedQuoteUrl;
|
||||||
|
|
||||||
public ContextMenuBrowser(FormBrowser form) : base(form){
|
public ContextMenuBrowser(FormBrowser form){
|
||||||
this.form = form;
|
this.form = form;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,20 +46,20 @@ namespace TweetDuck.Core.Handling{
|
|||||||
|
|
||||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
|
|
||||||
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
|
lastHighlightedTweetUrl = TweetDeckBridge.LastHighlightedTweetUrl;
|
||||||
lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet;
|
lastHighlightedQuoteUrl = TweetDeckBridge.LastHighlightedQuoteUrl;
|
||||||
|
|
||||||
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
||||||
lastHighlightedTweet = string.Empty;
|
lastHighlightedTweetUrl = string.Empty;
|
||||||
lastHighlightedQuotedTweet = string.Empty;
|
lastHighlightedQuoteUrl = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(lastHighlightedTweet) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
|
if (!string.IsNullOrEmpty(lastHighlightedTweetUrl) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
|
||||||
model.AddItem(MenuOpenTweetUrl, "Open tweet in browser");
|
model.AddItem(MenuOpenTweetUrl, "Open tweet in browser");
|
||||||
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
|
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
|
||||||
model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard");
|
model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard");
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(lastHighlightedQuotedTweet)){
|
if (!string.IsNullOrEmpty(lastHighlightedQuoteUrl)){
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
|
model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
|
||||||
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
||||||
@@ -118,11 +118,11 @@ namespace TweetDuck.Core.Handling{
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuOpenTweetUrl:
|
case MenuOpenTweetUrl:
|
||||||
BrowserUtils.OpenExternalBrowser(lastHighlightedTweet);
|
OpenBrowser(form, lastHighlightedTweetUrl);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuCopyTweetUrl:
|
case MenuCopyTweetUrl:
|
||||||
SetClipboardText(lastHighlightedTweet);
|
SetClipboardText(form, lastHighlightedTweetUrl);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuScreenshotTweet:
|
case MenuScreenshotTweet:
|
||||||
@@ -130,11 +130,11 @@ namespace TweetDuck.Core.Handling{
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuOpenQuotedTweetUrl:
|
case MenuOpenQuotedTweetUrl:
|
||||||
BrowserUtils.OpenExternalBrowser(lastHighlightedQuotedTweet);
|
OpenBrowser(form, lastHighlightedQuoteUrl);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuCopyQuotedTweetUrl:
|
case MenuCopyQuotedTweetUrl:
|
||||||
SetClipboardText(lastHighlightedQuotedTweet);
|
SetClipboardText(form, lastHighlightedQuoteUrl);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
private readonly FormNotificationBase form;
|
private readonly FormNotificationBase form;
|
||||||
private readonly bool enableCustomMenu;
|
private readonly bool enableCustomMenu;
|
||||||
|
|
||||||
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu) : base(form){
|
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu){
|
||||||
this.form = form;
|
this.form = form;
|
||||||
this.enableCustomMenu = enableCustomMenu;
|
this.enableCustomMenu = enableCustomMenu;
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ namespace TweetDuck.Core.Handling{
|
|||||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
|
|
||||||
if (enableCustomMenu){
|
if (enableCustomMenu){
|
||||||
if (!string.IsNullOrEmpty(form.CurrentChirpId)){
|
if (form.CanViewDetail){
|
||||||
model.AddItem(MenuViewDetail, "View detail");
|
model.AddItem(MenuViewDetail, "View detail");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,11 +76,11 @@ namespace TweetDuck.Core.Handling{
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuCopyTweetUrl:
|
case MenuCopyTweetUrl:
|
||||||
SetClipboardText(form.CurrentTweetUrl);
|
SetClipboardText(form, form.CurrentTweetUrl);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuCopyQuotedTweetUrl:
|
case MenuCopyQuotedTweetUrl:
|
||||||
SetClipboardText(form.CurrentQuoteUrl);
|
SetClipboardText(form, form.CurrentQuoteUrl);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
40
Core/Handling/General/FileDialogHandler.cs
Normal file
40
Core/Handling/General/FileDialogHandler.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling.General{
|
||||||
|
sealed class FileDialogHandler : IDialogHandler{
|
||||||
|
public bool OnFileDialog(IWebBrowser browserControl, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback){
|
||||||
|
CefFileDialogMode dialogType = mode & CefFileDialogMode.TypeMask;
|
||||||
|
|
||||||
|
if (dialogType == CefFileDialogMode.Open || dialogType == CefFileDialogMode.OpenMultiple){
|
||||||
|
string allFilters = string.Join(";", acceptFilters.Select(filter => "*"+filter));
|
||||||
|
|
||||||
|
using(OpenFileDialog dialog = new OpenFileDialog{
|
||||||
|
AutoUpgradeEnabled = true,
|
||||||
|
DereferenceLinks = true,
|
||||||
|
Multiselect = dialogType == CefFileDialogMode.OpenMultiple,
|
||||||
|
Title = "Open Files",
|
||||||
|
Filter = $"All Supported Formats ({allFilters})|{allFilters}|All Files (*.*)|*.*"
|
||||||
|
}){
|
||||||
|
if (dialog.ShowDialog() == DialogResult.OK){
|
||||||
|
callback.Continue(acceptFilters.FindIndex(filter => filter == Path.GetExtension(dialog.FileName)), dialog.FileNames.ToList());
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
callback.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
callback.Dispose();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Handling.General{
|
namespace TweetDuck.Core.Handling.General{
|
||||||
@@ -11,7 +12,7 @@ namespace TweetDuck.Core.Handling.General{
|
|||||||
case WindowOpenDisposition.NewForegroundTab:
|
case WindowOpenDisposition.NewForegroundTab:
|
||||||
case WindowOpenDisposition.NewPopup:
|
case WindowOpenDisposition.NewPopup:
|
||||||
case WindowOpenDisposition.NewWindow:
|
case WindowOpenDisposition.NewWindow:
|
||||||
BrowserUtils.OpenExternalBrowser(targetUrl);
|
browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@@ -82,9 +82,10 @@ namespace TweetDuck.Core.Notification{
|
|||||||
private TweetNotification currentNotification;
|
private TweetNotification currentNotification;
|
||||||
private int pauseCounter;
|
private int pauseCounter;
|
||||||
|
|
||||||
public string CurrentChirpId => currentNotification?.ChirpId;
|
|
||||||
public string CurrentTweetUrl => currentNotification?.TweetUrl;
|
public string CurrentTweetUrl => currentNotification?.TweetUrl;
|
||||||
public string CurrentQuoteUrl => currentNotification?.QuoteUrl;
|
public string CurrentQuoteUrl => currentNotification?.QuoteUrl;
|
||||||
|
|
||||||
|
public bool CanViewDetail => currentNotification != null && !string.IsNullOrEmpty(currentNotification.ColumnId) && !string.IsNullOrEmpty(currentNotification.ChirpId);
|
||||||
public bool IsPaused => pauseCounter > 0;
|
public bool IsPaused => pauseCounter > 0;
|
||||||
|
|
||||||
public bool FreezeTimer { get; set; }
|
public bool FreezeTimer { get; set; }
|
||||||
@@ -144,7 +145,7 @@ namespace TweetDuck.Core.Notification{
|
|||||||
|
|
||||||
private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
|
private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
|
||||||
if (e.IsBrowserInitialized){
|
if (e.IsBrowserInitialized){
|
||||||
Initialized?.Invoke(this, new EventArgs());
|
Initialized?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
int identifier = browser.GetBrowser().Identifier;
|
int identifier = browser.GetBrowser().Identifier;
|
||||||
Disposed += (sender2, args2) => BrowserProcesses.Forget(identifier);
|
Disposed += (sender2, args2) => BrowserProcesses.Forget(identifier);
|
||||||
|
@@ -128,11 +128,16 @@ namespace TweetDuck.Core.Other{
|
|||||||
tab.Control.OnReady();
|
tab.Control.OnReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
panelContents.VerticalScroll.Enabled = false; // required to stop animation that would otherwise break everything
|
||||||
|
panelContents.PerformLayout();
|
||||||
|
|
||||||
panelContents.SuspendLayout();
|
panelContents.SuspendLayout();
|
||||||
panelContents.VerticalScroll.Value = 0; // https://gfycat.com/GrotesqueTastyAstarte
|
panelContents.VerticalScroll.Value = 0; // https://gfycat.com/GrotesqueTastyAstarte
|
||||||
panelContents.Controls.Clear();
|
panelContents.Controls.Clear();
|
||||||
panelContents.Controls.Add(tab.Control);
|
panelContents.Controls.Add(tab.Control);
|
||||||
panelContents.ResumeLayout(true);
|
panelContents.ResumeLayout(true);
|
||||||
|
|
||||||
|
panelContents.VerticalScroll.Enabled = true;
|
||||||
panelContents.Focus();
|
panelContents.Focus();
|
||||||
|
|
||||||
currentTab = tab;
|
currentTab = tab;
|
||||||
|
@@ -173,7 +173,7 @@ namespace TweetDuck.Core.Other.Management{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void TriggerProcessExitEventUnsafe(){
|
private void TriggerProcessExitEventUnsafe(){
|
||||||
ProcessExited?.Invoke(this, new EventArgs());
|
ProcessExited?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -38,7 +38,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
|||||||
tbDataFolder.TextChanged += control_Change;
|
tbDataFolder.TextChanged += control_Change;
|
||||||
}
|
}
|
||||||
|
|
||||||
control_Change(this, new EventArgs());
|
control_Change(this, EventArgs.Empty);
|
||||||
|
|
||||||
Text = Program.BrandName+" Arguments";
|
Text = Program.BrandName+" Arguments";
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ namespace TweetDuck.Core.Other.Settings{
|
|||||||
labelVolumeValue.Text = trackBarVolume.Value+"%";
|
labelVolumeValue.Text = trackBarVolume.Value+"%";
|
||||||
|
|
||||||
tbCustomSound.Text = Config.NotificationSoundPath;
|
tbCustomSound.Text = Config.NotificationSoundPath;
|
||||||
tbCustomSound_TextChanged(tbCustomSound, new EventArgs());
|
tbCustomSound_TextChanged(tbCustomSound, EventArgs.Empty);
|
||||||
|
|
||||||
Disposed += (sender, args) => soundNotification.Dispose();
|
Disposed += (sender, args) => soundNotification.Dispose();
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ using System.Diagnostics;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using CefSharp.WinForms;
|
||||||
using TweetDuck.Core.Other;
|
using TweetDuck.Core.Other;
|
||||||
|
|
||||||
namespace TweetDuck.Core.Utils{
|
namespace TweetDuck.Core.Utils{
|
||||||
@@ -42,23 +43,46 @@ namespace TweetDuck.Core.Utils{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsValidUrl(string url){
|
public static ChromiumWebBrowser AsControl(this IWebBrowser browserControl){
|
||||||
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
|
return (ChromiumWebBrowser)browserControl;
|
||||||
string scheme = uri.Scheme;
|
|
||||||
return scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
private const string TwitterTrackingUrl = "t.co";
|
||||||
|
|
||||||
|
public enum UrlCheckResult{
|
||||||
|
Invalid, Tracking, Fine
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UrlCheckResult CheckUrl(string url){
|
||||||
|
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
|
||||||
|
string scheme = uri.Scheme;
|
||||||
|
|
||||||
|
if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto){
|
||||||
|
return uri.Host == TwitterTrackingUrl ? UrlCheckResult.Tracking : UrlCheckResult.Fine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return UrlCheckResult.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OpenExternalBrowser(string url){
|
public static void OpenExternalBrowser(string url){
|
||||||
if (string.IsNullOrWhiteSpace(url))return;
|
if (string.IsNullOrWhiteSpace(url))return;
|
||||||
|
|
||||||
if (IsValidUrl(url)){
|
switch(CheckUrl(url)){
|
||||||
|
case UrlCheckResult.Fine:
|
||||||
|
OpenExternalBrowserUnsafe(url);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UrlCheckResult.Tracking:
|
||||||
|
if (FormMessage.Warning("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n"+url, FormMessage.Yes, FormMessage.No)){
|
||||||
OpenExternalBrowserUnsafe(url);
|
OpenExternalBrowserUnsafe(url);
|
||||||
}
|
}
|
||||||
else{
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UrlCheckResult.Invalid:
|
||||||
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
|
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@ namespace TweetDuck.Core.Utils{
|
|||||||
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
|
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
|
||||||
public const string BackgroundColorFix = "let e=document.createElement('style');document.head.appendChild(e);e.innerHTML='body::before{background:#1c6399!important}'";
|
public const string BackgroundColorFix = "let e=document.createElement('style');document.head.appendChild(e);e.innerHTML='body::before{background:#1c6399!important}'";
|
||||||
|
|
||||||
private static readonly Lazy<Regex> RegexAccountLazy = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/([^/]+)/?$", RegexOptions.Compiled), false);
|
private static readonly Lazy<Regex> RegexAccountLazy = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/(?!signup$|tos$|privacy$)([^/]+)/?$", RegexOptions.Compiled), false);
|
||||||
public static Regex RegexAccount => RegexAccountLazy.Value;
|
public static Regex RegexAccount => RegexAccountLazy.Value;
|
||||||
|
|
||||||
public static readonly string[] DictionaryWords = {
|
public static readonly string[] DictionaryWords = {
|
||||||
|
@@ -21,7 +21,7 @@ namespace TweetDuck{
|
|||||||
public const string BrandName = "TweetDuck";
|
public const string BrandName = "TweetDuck";
|
||||||
public const string Website = "https://tweetduck.chylex.com";
|
public const string Website = "https://tweetduck.chylex.com";
|
||||||
|
|
||||||
public const string VersionTag = "1.9";
|
public const string VersionTag = "1.9.1";
|
||||||
|
|
||||||
public static readonly bool IsPortable = File.Exists("makeportable");
|
public static readonly bool IsPortable = File.Exists("makeportable");
|
||||||
|
|
||||||
|
@@ -334,17 +334,22 @@ enabled(){
|
|||||||
this.css.insert(".txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: "+this.config.fontSize+" !important }");
|
this.css.insert(".txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: "+this.config.fontSize+" !important }");
|
||||||
this.css.insert(".avatar { border-radius: "+this.config.avatarRadius+"% !important }");
|
this.css.insert(".avatar { border-radius: "+this.config.avatarRadius+"% !important }");
|
||||||
|
|
||||||
|
let notificationScrollbarColor = null;
|
||||||
|
|
||||||
if (this.config.themeColorTweaks){
|
if (this.config.themeColorTweaks){
|
||||||
switch(TD.settings.getTheme()){
|
switch(TD.settings.getTheme()){
|
||||||
case "dark":
|
case "dark":
|
||||||
this.css.insert(".app-content, .app-columns-container { background-color: #444448 }");
|
this.css.insert(".app-content, .app-columns-container { background-color: #444448 }");
|
||||||
this.css.insert(".column-drag-handle { opacity: 0.5 }");
|
this.css.insert(".column-drag-handle { opacity: 0.5 }");
|
||||||
this.css.insert(".column-drag-handle:hover { opacity: 1 }");
|
this.css.insert(".column-drag-handle:hover { opacity: 1 }");
|
||||||
|
this.css.insert(".scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb { background-color: #666 }");
|
||||||
|
notificationScrollbarColor = "666";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "light":
|
case "light":
|
||||||
this.css.insert(".scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb { background-color: #d2d6da }");
|
this.css.insert(".scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb { background-color: #d2d6da }");
|
||||||
this.css.insert(".app-columns-container.scroll-styled-h::-webkit-scrollbar-thumb:not(:hover) { background-color: #a5aeb5 }");
|
this.css.insert(".app-columns-container.scroll-styled-h::-webkit-scrollbar-thumb:not(:hover) { background-color: #a5aeb5 }");
|
||||||
|
notificationScrollbarColor = "a5aeb5";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -460,6 +465,9 @@ enabled(){
|
|||||||
.drawer .btn .icon, .app-header .btn .icon { line-height: 1em !important }
|
.drawer .btn .icon, .app-header .btn .icon { line-height: 1em !important }
|
||||||
.column-header .column-type-icon { bottom: 26px !important }
|
.column-header .column-type-icon { bottom: 26px !important }
|
||||||
.is-options-open .column-type-icon { bottom: 25px !important }
|
.is-options-open .column-type-icon { bottom: 25px !important }
|
||||||
|
|
||||||
|
.tweet-action-item .icon-favorite-toggle { font-size: 16px !important; }
|
||||||
|
.tweet-action-item .heartsprite { top: -260% !important; left: -260% !important; transform: scale(0.4, 0.39) translateY(0.5px) !important; }
|
||||||
.tweet-footer { margin-top: 6px !important }`;
|
.tweet-footer { margin-top: 6px !important }`;
|
||||||
|
|
||||||
document.head.appendChild(this.icons);
|
document.head.appendChild(this.icons);
|
||||||
@@ -511,6 +519,10 @@ ${this.config.revertIcons ? `
|
|||||||
.icon-user-filled:before{content:"\\f035";font-family:tweetdeckold}
|
.icon-user-filled:before{content:"\\f035";font-family:tweetdeckold}
|
||||||
.icon-user-dd:before{content:"\\f01a";font-family:tweetdeckold}
|
.icon-user-dd:before{content:"\\f01a";font-family:tweetdeckold}
|
||||||
` : ``}
|
` : ``}
|
||||||
|
|
||||||
|
${notificationScrollbarColor ? `
|
||||||
|
.scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb { background-color: #${notificationScrollbarColor} }
|
||||||
|
` : ``}
|
||||||
</style>`);
|
</style>`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -356,10 +356,9 @@
|
|||||||
var prevMouseX = -1, prevMouseY = -1;
|
var prevMouseX = -1, prevMouseY = -1;
|
||||||
var tooltipTimer, tooltipDisplayed;
|
var tooltipTimer, tooltipDisplayed;
|
||||||
|
|
||||||
$(document.body).delegate("a[data-full-url]", "mouseenter mouseleave mousemove", function(e){
|
$(document.body).delegate("a[data-full-url]", {
|
||||||
var me = $(this);
|
mouseenter: function(){
|
||||||
|
let me = $(this);
|
||||||
if (e.type === "mouseenter"){
|
|
||||||
let text = me.text();
|
let text = me.text();
|
||||||
return if text.charCodeAt(text.length-1) !== 8230; // horizontal ellipsis
|
return if text.charCodeAt(text.length-1) !== 8230; // horizontal ellipsis
|
||||||
|
|
||||||
@@ -380,14 +379,13 @@
|
|||||||
tooltipDisplayed = true;
|
tooltipDisplayed = true;
|
||||||
}, 400);
|
}, 400);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
else if (e.type === "mouseleave"){
|
|
||||||
if ($TDX.expandLinksOnHover){
|
|
||||||
let prevText = me.attr("td-prev-text");
|
|
||||||
|
|
||||||
if (prevText){
|
mouseleave: function(){
|
||||||
me.text(prevText);
|
let me = $(this);
|
||||||
}
|
|
||||||
|
if (me[0].hasAttribute("td-prev-text")){
|
||||||
|
me.text(me.attr("td-prev-text"));
|
||||||
}
|
}
|
||||||
|
|
||||||
window.clearTimeout(tooltipTimer);
|
window.clearTimeout(tooltipTimer);
|
||||||
@@ -396,10 +394,11 @@
|
|||||||
tooltipDisplayed = false;
|
tooltipDisplayed = false;
|
||||||
$TD.displayTooltip(null, false);
|
$TD.displayTooltip(null, false);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
else if (e.type === "mousemove"){
|
|
||||||
|
mousemove: function(e){
|
||||||
if (tooltipDisplayed && (prevMouseX !== e.clientX || prevMouseY !== e.clientY)){
|
if (tooltipDisplayed && (prevMouseX !== e.clientX || prevMouseY !== e.clientY)){
|
||||||
$TD.displayTooltip(me.attr("data-full-url"), false);
|
$TD.displayTooltip($(this).attr("data-full-url"), false);
|
||||||
prevMouseX = e.clientX;
|
prevMouseX = e.clientX;
|
||||||
prevMouseY = e.clientY;
|
prevMouseY = e.clientY;
|
||||||
}
|
}
|
||||||
@@ -408,7 +407,49 @@
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Allow bypassing of t.co and include media previews in context menus.
|
// Block: Bypass t.co when clicking links and media.
|
||||||
|
//
|
||||||
|
$(document.body).delegate("a[data-full-url]", "click", function(e){
|
||||||
|
$TD.openBrowser($(this).attr("data-full-url"));
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ensurePropertyExists(TD, "services", "TwitterUser", "prototype", "fromJSONObject")){
|
||||||
|
let prevFunc = TD.services.TwitterUser.prototype.fromJSONObject;
|
||||||
|
|
||||||
|
TD.services.TwitterUser.prototype.fromJSONObject = function(){
|
||||||
|
let obj = prevFunc.apply(this, arguments);
|
||||||
|
let e = arguments[0].entities;
|
||||||
|
|
||||||
|
if (e && e.url && e.url.urls && e.url.urls.length && e.url.urls[0].expanded_url){
|
||||||
|
obj.url = e.url.urls[0].expanded_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ensurePropertyExists(TD, "services", "TwitterMedia", "prototype", "fromMediaEntity")){
|
||||||
|
let prevFunc = TD.services.TwitterMedia.prototype.fromMediaEntity;
|
||||||
|
|
||||||
|
TD.services.TwitterMedia.prototype.fromMediaEntity = function(){
|
||||||
|
let obj = prevFunc.apply(this, arguments);
|
||||||
|
let e = arguments[0];
|
||||||
|
|
||||||
|
if (e.expanded_url){
|
||||||
|
if (obj.url === obj.shortUrl){
|
||||||
|
obj.shortUrl = e.expanded_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.url = e.expanded_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Block: Include additional information in context menus.
|
||||||
//
|
//
|
||||||
$(document.body).delegate("a", "contextmenu", function(){
|
$(document.body).delegate("a", "contextmenu", function(){
|
||||||
let me = $(this)[0];
|
let me = $(this)[0];
|
||||||
@@ -450,29 +491,34 @@
|
|||||||
return !!highlightedColumnObj;
|
return !!highlightedColumnObj;
|
||||||
};
|
};
|
||||||
|
|
||||||
var updateHighlightedTweet = function(ele, obj, link, embeddedLink, author, imageList){
|
var updateHighlightedTweet = function(ele, obj, tweetUrl, quoteUrl, authors, imageList){
|
||||||
highlightedTweetEle = ele;
|
highlightedTweetEle = ele;
|
||||||
highlightedTweetObj = obj;
|
highlightedTweetObj = obj;
|
||||||
|
|
||||||
if (lastTweet !== link){
|
if (lastTweet !== tweetUrl){
|
||||||
$TD.setLastHighlightedTweet(link, embeddedLink, author, imageList);
|
$TD.setLastHighlightedTweet(tweetUrl, quoteUrl, authors, imageList);
|
||||||
lastTweet = link;
|
lastTweet = tweetUrl;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
app.delegate("section.js-column", "mouseenter mouseleave", function(e){
|
var processMedia = function(media){
|
||||||
if (e.type === "mouseenter"){
|
return media.filter(item => !item.isAnimatedGif).map(item => item.entity.media_url_https+":small").join(";");
|
||||||
|
};
|
||||||
|
|
||||||
|
app.delegate("section.js-column", {
|
||||||
|
mouseenter: function(){
|
||||||
if (!highlightedColumnObj){
|
if (!highlightedColumnObj){
|
||||||
updateHighlightedColumn($(this));
|
updateHighlightedColumn($(this));
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
else if (e.type === "mouseleave"){
|
|
||||||
|
mouseleave: function(){
|
||||||
updateHighlightedColumn(null);
|
updateHighlightedColumn(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.delegate("article.js-stream-item", "mouseenter mouseleave", function(e){
|
app.delegate("article.js-stream-item", {
|
||||||
if (e.type === "mouseenter"){
|
mouseenter: function(){
|
||||||
let me = $(this);
|
let me = $(this);
|
||||||
return if !me[0].hasAttribute("data-account-key") || (!highlightedColumnObj && !updateHighlightedColumn(me.closest("section.js-column")));
|
return if !me[0].hasAttribute("data-account-key") || (!highlightedColumnObj && !updateHighlightedColumn(me.closest("section.js-column")));
|
||||||
|
|
||||||
@@ -480,19 +526,19 @@
|
|||||||
return if !tweet;
|
return if !tweet;
|
||||||
|
|
||||||
if (tweet.chirpType === TD.services.ChirpBase.TWEET){
|
if (tweet.chirpType === TD.services.ChirpBase.TWEET){
|
||||||
let link = tweet.getChirpURL();
|
let tweetUrl = tweet.getChirpURL();
|
||||||
let embedded = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
|
let quoteUrl = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
|
||||||
let username = tweet.getMainUser().screenName;
|
let authors = tweet.quotedTweet ? [ tweet.getMainUser().screenName, tweet.quotedTweet.getMainUser().screenName ].join(";") : tweet.getMainUser().screenName;
|
||||||
let images = tweet.hasImage() ? tweet.getMedia().filter(item => !item.isAnimatedGif).map(item => item.entity.media_url_https+":small").join(";") : "";
|
let imageList = tweet.quotedTweet ? processMedia(tweet.quotedTweet.getMedia()) : tweet.hasImage() ? processMedia(tweet.getMedia()) : "";
|
||||||
// TODO maybe handle embedded images too?
|
|
||||||
|
|
||||||
updateHighlightedTweet(me, tweet, link || "", embedded || "", username, images);
|
updateHighlightedTweet(me, tweet, tweetUrl || "", quoteUrl || "", authors, imageList);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
updateHighlightedTweet(me, tweet, "", "", "", "");
|
updateHighlightedTweet(me, tweet, "", "", "", "");
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
else if (e.type === "mouseleave"){
|
|
||||||
|
mouseleave: function(){
|
||||||
updateHighlightedTweet(null, null, "", "", "", "");
|
updateHighlightedTweet(null, null, "", "", "", "");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -704,9 +750,7 @@
|
|||||||
|
|
||||||
$(".js-drawer[data-drawer='compose']").delegate(".js-account-list > .js-account-item", "click", onAccountClick);
|
$(".js-drawer[data-drawer='compose']").delegate(".js-account-list > .js-account-item", "click", onAccountClick);
|
||||||
|
|
||||||
if (!ensurePropertyExists(TD, "components", "AccountSelector", "prototype", "refreshPostingAccounts")){
|
return if !ensurePropertyExists(TD, "components", "AccountSelector", "prototype", "refreshPostingAccounts");
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TD.components.AccountSelector.prototype.refreshPostingAccounts = appendToFunction(TD.components.AccountSelector.prototype.refreshPostingAccounts, function(){
|
TD.components.AccountSelector.prototype.refreshPostingAccounts = appendToFunction(TD.components.AccountSelector.prototype.refreshPostingAccounts, function(){
|
||||||
if (!this.$node.attr("td-account-selector-hook")){
|
if (!this.$node.attr("td-account-selector-hook")){
|
||||||
@@ -764,8 +808,8 @@
|
|||||||
addRule(".txt-base-smallest .sprite-verified-mini { width: 13px !important; height: 13px !important; background-position: -223px -99px !important; }"); // fix cut off badge icon when zoomed in
|
addRule(".txt-base-smallest .sprite-verified-mini { width: 13px !important; height: 13px !important; background-position: -223px -99px !important; }"); // fix cut off badge icon when zoomed in
|
||||||
addRule(".txt-base-smallest .badge-verified:before { width: 13px !important; height: 13px !important; background-position: -223px -98px !important; }"); // fix cut off badge icon in notifications
|
addRule(".txt-base-smallest .badge-verified:before { width: 13px !important; height: 13px !important; background-position: -223px -98px !important; }"); // fix cut off badge icon in notifications
|
||||||
|
|
||||||
addRule(".btn, .mdl, .mdl-content, .app-search-fake, .app-search-input, .popover, .lst-modal, .media-item, .media-preview, .tooltip-inner { border-radius: 1px !important; }"); // square-ify buttons, inputs, dialogs, menus, media previews
|
addRule(".btn, .mdl, .mdl-content, .app-search-fake, .app-search-input, .popover, .lst-modal, .media-item, .media-preview, .tooltip-inner { border-radius: 1px !important; }"); // square-ify buttons, dialogs, menus, media previews
|
||||||
addRule(".compose-text-container, .dropdown-menu, .list-item-last, .quoted-tweet { border-radius: 0 !important; }"); // square-ify dropdowns, quoted tweets, and account selectors
|
addRule(".compose-text-container, .dropdown-menu, .list-item-last, .quoted-tweet, .input-group-button, input, textarea, select { border-radius: 0 !important; }"); // square-ify dropdowns, inputs, quoted tweets, and account selectors
|
||||||
addRule(".prf-header { border-radius: 0; }"); // fix user account header border
|
addRule(".prf-header { border-radius: 0; }"); // fix user account header border
|
||||||
|
|
||||||
addRule(".accs li, .accs img { border-radius: 0 !important; }"); // square-ify retweet account selector
|
addRule(".accs li, .accs img { border-radius: 0 !important; }"); // square-ify retweet account selector
|
||||||
|
@@ -14,14 +14,16 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Hook into links to bypass default open function.
|
// Block: Hook into links to bypass default open function and t.co.
|
||||||
//
|
//
|
||||||
addEventListener(links, "click", function(e){
|
addEventListener(links, "click", function(e){
|
||||||
$TD.openBrowser(e.currentTarget.getAttribute("href"));
|
let ele = e.currentTarget;
|
||||||
|
|
||||||
|
$TD.openBrowser(ele.hasAttribute("data-full-url") ? ele.getAttribute("data-full-url") : ele.getAttribute("href"));
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if ($TDX.skipOnLinkClick){
|
if ($TDX.skipOnLinkClick){
|
||||||
let parentClasses = e.currentTarget.parentNode.classList;
|
let parentClasses = ele.parentNode.classList;
|
||||||
|
|
||||||
if (parentClasses.contains("js-tweet-text") || parentClasses.contains("js-quoted-tweet-text") || parentClasses.contains("js-timestamp")){
|
if (parentClasses.contains("js-tweet-text") || parentClasses.contains("js-quoted-tweet-text") || parentClasses.contains("js-timestamp")){
|
||||||
$TD.loadNextNotification();
|
$TD.loadNextNotification();
|
||||||
@@ -33,7 +35,7 @@
|
|||||||
// Block: Allow bypassing of t.co in context menus.
|
// Block: Allow bypassing of t.co in context menus.
|
||||||
//
|
//
|
||||||
addEventListener(links, "contextmenu", function(e){
|
addEventListener(links, "contextmenu", function(e){
|
||||||
$TD.setLastRightClickedLink(e.currentTarget.getAttribute("data-full-url") || "");
|
$TD.setLastRightClickInfo("link", e.currentTarget.getAttribute("data-full-url"));
|
||||||
});
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@@ -39,4 +39,25 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
setTimeout(injectCSS, 1);
|
setTimeout(injectCSS, 1);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Block: Make login page links external.
|
||||||
|
//
|
||||||
|
if (location.pathname === "/login"){
|
||||||
|
document.addEventListener("DOMContentLoaded", function(){
|
||||||
|
let openLinkExternally = function(e){
|
||||||
|
let href = e.currentTarget.getAttribute("href");
|
||||||
|
$TD.openBrowser(href[0] === '/' ? location.origin+href : href);
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
let links = document.getElementsByTagName("A");
|
||||||
|
|
||||||
|
for(let index = 0; index < links.length; index++){
|
||||||
|
links[index].addEventListener("click", openLinkExternally);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<Import Project="packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.props')" />
|
<Import Project="packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.props')" />
|
||||||
<Import Project="packages\CefSharp.Common.57.0.0\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.57.0.0\build\CefSharp.Common.props')" />
|
<Import Project="packages\CefSharp.Common.57.0.0\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.57.0.0\build\CefSharp.Common.props')" />
|
||||||
@@ -97,6 +97,7 @@
|
|||||||
<Compile Include="Core\FormBrowser.Designer.cs">
|
<Compile Include="Core\FormBrowser.Designer.cs">
|
||||||
<DependentUpon>FormBrowser.cs</DependentUpon>
|
<DependentUpon>FormBrowser.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Core\Handling\General\FileDialogHandler.cs" />
|
||||||
<Compile Include="Core\Handling\KeyboardHandlerBrowser.cs" />
|
<Compile Include="Core\Handling\KeyboardHandlerBrowser.cs" />
|
||||||
<Compile Include="Core\Handling\KeyboardHandlerNotification.cs" />
|
<Compile Include="Core\Handling\KeyboardHandlerNotification.cs" />
|
||||||
<Compile Include="Core\Handling\RequestHandlerBrowser.cs" />
|
<Compile Include="Core\Handling\RequestHandlerBrowser.cs" />
|
||||||
|
138
tests/Configuration/TestUserConfig.cs
Normal file
138
tests/Configuration/TestUserConfig.cs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
using TweetDuck.Core;
|
||||||
|
|
||||||
|
namespace UnitTests.Configuration{
|
||||||
|
[TestClass]
|
||||||
|
public class TestUserConfig : UnitTestIO{
|
||||||
|
private static void WriteTestConfig(string file, bool withBackup){
|
||||||
|
UserConfig cfg = UserConfig.Load(file);
|
||||||
|
cfg.ZoomLevel = 123;
|
||||||
|
cfg.Save();
|
||||||
|
|
||||||
|
if (withBackup){
|
||||||
|
cfg.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Pure] // used to display a warning when not using the return value
|
||||||
|
private static bool CheckTestConfig(string file){
|
||||||
|
return UserConfig.Load(file).ZoomLevel == 123;
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestMissing(){
|
||||||
|
Assert.IsNotNull(UserConfig.Load("missing"));
|
||||||
|
Assert.IsFalse(File.Exists("missing"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestBasic(){
|
||||||
|
Assert.IsFalse(CheckTestConfig("basic"));
|
||||||
|
WriteTestConfig("basic", false);
|
||||||
|
Assert.IsTrue(CheckTestConfig("basic"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestBackupName(){
|
||||||
|
Assert.AreEqual("name.bak", UserConfig.GetBackupFile("name"));
|
||||||
|
Assert.AreEqual("name.cfg.bak", UserConfig.GetBackupFile("name.cfg"));
|
||||||
|
Assert.AreEqual("name.bak.bak", UserConfig.GetBackupFile("name.bak"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestBackupCreate(){
|
||||||
|
WriteTestConfig("nobackup", false);
|
||||||
|
Assert.IsTrue(File.Exists("nobackup"));
|
||||||
|
Assert.IsFalse(File.Exists(UserConfig.GetBackupFile("nobackup")));
|
||||||
|
|
||||||
|
WriteTestConfig("withbackup", true);
|
||||||
|
Assert.IsTrue(File.Exists("withbackup"));
|
||||||
|
Assert.IsTrue(File.Exists(UserConfig.GetBackupFile("withbackup")));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestBackupRestore(){
|
||||||
|
WriteTestConfig("gone", true);
|
||||||
|
Assert.IsTrue(File.Exists("gone"));
|
||||||
|
Assert.IsTrue(File.Exists(UserConfig.GetBackupFile("gone")));
|
||||||
|
File.Delete("gone");
|
||||||
|
Assert.IsTrue(CheckTestConfig("gone"));
|
||||||
|
|
||||||
|
WriteTestConfig("corrupted", true);
|
||||||
|
Assert.IsTrue(File.Exists("corrupted"));
|
||||||
|
Assert.IsTrue(File.Exists(UserConfig.GetBackupFile("corrupted")));
|
||||||
|
File.WriteAllText("corrupted", "oh no corrupt");
|
||||||
|
Assert.IsTrue(CheckTestConfig("corrupted"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestReload(){
|
||||||
|
UserConfig cfg = UserConfig.Load("reloaded");
|
||||||
|
cfg.ZoomLevel = 123;
|
||||||
|
cfg.Save();
|
||||||
|
|
||||||
|
cfg.ZoomLevel = 200;
|
||||||
|
Assert.IsTrue(cfg.Reload());
|
||||||
|
Assert.AreEqual(123, cfg.ZoomLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestReset(){
|
||||||
|
UserConfig cfg = UserConfig.Load("reset");
|
||||||
|
cfg.ZoomLevel = 123;
|
||||||
|
cfg.Save();
|
||||||
|
|
||||||
|
File.Delete("reset");
|
||||||
|
Assert.IsTrue(cfg.Reload());
|
||||||
|
Assert.AreEqual(100, cfg.ZoomLevel);
|
||||||
|
Assert.IsTrue(File.Exists("reset"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestEventsNoTrigger(){
|
||||||
|
void Fail(object sender, EventArgs args) => Assert.Fail();
|
||||||
|
|
||||||
|
UserConfig cfg = UserConfig.Load("events");
|
||||||
|
cfg.MuteNotifications = true;
|
||||||
|
cfg.TrayBehavior = TrayIcon.Behavior.Combined;
|
||||||
|
cfg.ZoomLevel = 99;
|
||||||
|
|
||||||
|
cfg.MuteToggled += Fail;
|
||||||
|
cfg.TrayBehaviorChanged += Fail;
|
||||||
|
cfg.ZoomLevelChanged += Fail;
|
||||||
|
|
||||||
|
cfg.MuteNotifications = true;
|
||||||
|
cfg.TrayBehavior = TrayIcon.Behavior.Combined;
|
||||||
|
cfg.ZoomLevel = 99;
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestEventsTrigger(){
|
||||||
|
int triggers = 0;
|
||||||
|
void Trigger(object sender, EventArgs args) => ++triggers;
|
||||||
|
|
||||||
|
UserConfig cfg = UserConfig.Load("events");
|
||||||
|
cfg.MuteNotifications = false;
|
||||||
|
cfg.TrayBehavior = TrayIcon.Behavior.Disabled;
|
||||||
|
cfg.ZoomLevel = 100;
|
||||||
|
|
||||||
|
cfg.MuteToggled += Trigger;
|
||||||
|
cfg.TrayBehaviorChanged += Trigger;
|
||||||
|
cfg.ZoomLevelChanged += Trigger;
|
||||||
|
|
||||||
|
cfg.MuteNotifications = true;
|
||||||
|
cfg.TrayBehavior = TrayIcon.Behavior.Combined;
|
||||||
|
cfg.ZoomLevel = 99;
|
||||||
|
|
||||||
|
cfg.MuteNotifications = false;
|
||||||
|
cfg.TrayBehavior = TrayIcon.Behavior.Disabled;
|
||||||
|
cfg.ZoomLevel = 100;
|
||||||
|
|
||||||
|
Assert.AreEqual(6, triggers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -6,33 +6,36 @@ namespace UnitTests.Core{
|
|||||||
public class TestBrowserUtils{
|
public class TestBrowserUtils{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestIsValidUrl(){
|
public void TestIsValidUrl(){
|
||||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.com")); // base
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com")); // base
|
||||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://www.google.com")); // www.
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://www.google.com")); // www.
|
||||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.co.uk")); // co.uk
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.co.uk")); // co.uk
|
||||||
|
|
||||||
Assert.IsTrue(BrowserUtils.IsValidUrl("https://google.com")); // https
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("https://google.com")); // https
|
||||||
Assert.IsTrue(BrowserUtils.IsValidUrl("ftp://google.com")); // ftp
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("ftp://google.com")); // ftp
|
||||||
Assert.IsTrue(BrowserUtils.IsValidUrl("mailto:someone@google.com")); // mailto
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("mailto:someone@google.com")); // mailto
|
||||||
|
|
||||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.com/")); // trailing slash
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/")); // trailing slash
|
||||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.com/?")); // trailing question mark
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/?")); // trailing question mark
|
||||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.com/?a=5&b=x")); // parameters
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/?a=5&b=x")); // parameters
|
||||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.com/#hash")); // parameters + hash
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/#hash")); // parameters + hash
|
||||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.com/?a=5&b=x#hash")); // parameters + hash
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/?a=5&b=x#hash")); // parameters + hash
|
||||||
|
|
||||||
foreach(string tld in new string[]{ "accountants", "blackfriday", "cancerresearch", "coffee", "cool", "foo", "travelersinsurance" }){
|
foreach(string tld in new string[]{ "accountants", "blackfriday", "cancerresearch", "coffee", "cool", "foo", "travelersinsurance" }){
|
||||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://test."+tld)); // long and unusual TLDs
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://test."+tld)); // long and unusual TLDs
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.IsFalse(BrowserUtils.IsValidUrl("explorer")); // file
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Tracking, BrowserUtils.CheckUrl("http://t.co/abc")); // tracking
|
||||||
Assert.IsFalse(BrowserUtils.IsValidUrl("explorer.exe")); // file
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Tracking, BrowserUtils.CheckUrl("https://t.co/abc")); // tracking
|
||||||
Assert.IsFalse(BrowserUtils.IsValidUrl("://explorer.exe")); // file-sorta
|
|
||||||
Assert.IsFalse(BrowserUtils.IsValidUrl("file://explorer.exe")); // file-proper
|
|
||||||
|
|
||||||
Assert.IsFalse(BrowserUtils.IsValidUrl("")); // empty
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("explorer")); // file
|
||||||
Assert.IsFalse(BrowserUtils.IsValidUrl("lol")); // random
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("explorer.exe")); // file
|
||||||
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("://explorer.exe")); // file-sorta
|
||||||
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("file://explorer.exe")); // file-proper
|
||||||
|
|
||||||
Assert.IsFalse(BrowserUtils.IsValidUrl("gopher://nobody.cares")); // lmao rekt
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("")); // empty
|
||||||
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("lol")); // random
|
||||||
|
|
||||||
|
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("gopher://nobody.cares")); // lmao rekt
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
@@ -6,39 +6,39 @@ using TweetDuck.Data;
|
|||||||
|
|
||||||
namespace UnitTests.Data{
|
namespace UnitTests.Data{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class TestCombinedFileStream{
|
public class TestCombinedFileStream : UnitTestIO{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestNoFiles(){
|
public void TestNoFiles(){
|
||||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_empty"))){
|
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("empty"))){
|
||||||
cfs.Flush();
|
cfs.Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists("cfs_empty"));
|
Assert.IsTrue(File.Exists("empty"));
|
||||||
|
|
||||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_empty"))){
|
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenRead("empty"))){
|
||||||
Assert.IsNull(cfs.ReadFile());
|
Assert.IsNull(cfs.ReadFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_empty"))){
|
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenRead("empty"))){
|
||||||
Assert.IsNull(cfs.SkipFile());
|
Assert.IsNull(cfs.SkipFile());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestEmptyFiles(){
|
public void TestEmptyFiles(){
|
||||||
TestUtils.WriteText("cfs_input_empty_1", string.Empty);
|
File.WriteAllText("input_empty_1", string.Empty);
|
||||||
TestUtils.WriteText("cfs_input_empty_2", string.Empty);
|
File.WriteAllText("input_empty_2", string.Empty);
|
||||||
|
|
||||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_blank_files"))){
|
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("blank_files"))){
|
||||||
cfs.WriteFile("id1", "cfs_input_empty_1");
|
cfs.WriteFile("id1", "input_empty_1");
|
||||||
cfs.WriteFile("id2", "cfs_input_empty_2");
|
cfs.WriteFile("id2", "input_empty_2");
|
||||||
cfs.WriteFile("id2_clone", "cfs_input_empty_2");
|
cfs.WriteFile("id2_clone", "input_empty_2");
|
||||||
cfs.Flush();
|
cfs.Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists("cfs_blank_files"));
|
Assert.IsTrue(File.Exists("blank_files"));
|
||||||
|
|
||||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_blank_files"))){
|
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenRead("blank_files"))){
|
||||||
CombinedFileStream.Entry entry1 = cfs.ReadFile();
|
CombinedFileStream.Entry entry1 = cfs.ReadFile();
|
||||||
string entry2key = cfs.SkipFile();
|
string entry2key = cfs.SkipFile();
|
||||||
CombinedFileStream.Entry entry3 = cfs.ReadFile();
|
CombinedFileStream.Entry entry3 = cfs.ReadFile();
|
||||||
@@ -56,31 +56,29 @@ namespace UnitTests.Data{
|
|||||||
Assert.AreEqual("id2_clone", entry3.Identifier);
|
Assert.AreEqual("id2_clone", entry3.Identifier);
|
||||||
CollectionAssert.AreEqual(new string[0], entry3.KeyValue);
|
CollectionAssert.AreEqual(new string[0], entry3.KeyValue);
|
||||||
|
|
||||||
entry1.WriteToFile("cfs_blank_file_1");
|
entry1.WriteToFile("blank_file_1");
|
||||||
entry3.WriteToFile("cfs_blank_file_2");
|
entry3.WriteToFile("blank_file_2");
|
||||||
TestUtils.DeleteFileOnExit("cfs_blank_file_1");
|
|
||||||
TestUtils.DeleteFileOnExit("cfs_blank_file_2");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists("cfs_blank_file_1"));
|
Assert.IsTrue(File.Exists("blank_file_1"));
|
||||||
Assert.IsTrue(File.Exists("cfs_blank_file_2"));
|
Assert.IsTrue(File.Exists("blank_file_2"));
|
||||||
Assert.AreEqual(string.Empty, TestUtils.ReadText("cfs_blank_file_1"));
|
Assert.AreEqual(string.Empty, File.ReadAllText("blank_file_1"));
|
||||||
Assert.AreEqual(string.Empty, TestUtils.ReadText("cfs_blank_file_2"));
|
Assert.AreEqual(string.Empty, File.ReadAllText("blank_file_2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestTextFilesAndComplexKeys(){
|
public void TestTextFilesAndComplexKeys(){
|
||||||
TestUtils.WriteText("cfs_input_text_1", "Hello World!"+Environment.NewLine);
|
File.WriteAllText("input_text_1", "Hello World!"+Environment.NewLine);
|
||||||
|
|
||||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_text_files"))){
|
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("text_files"))){
|
||||||
cfs.WriteFile(new string[]{ "key1", "a", "bb", "ccc", "dddd" }, "cfs_input_text_1");
|
cfs.WriteFile(new string[]{ "key1", "a", "bb", "ccc", "dddd" }, "input_text_1");
|
||||||
cfs.WriteFile(new string[]{ "key2", "a", "bb", "ccc", "dddd" }, "cfs_input_text_1");
|
cfs.WriteFile(new string[]{ "key2", "a", "bb", "ccc", "dddd" }, "input_text_1");
|
||||||
cfs.Flush();
|
cfs.Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists("cfs_text_files"));
|
Assert.IsTrue(File.Exists("text_files"));
|
||||||
|
|
||||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_text_files"))){
|
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenRead("text_files"))){
|
||||||
CombinedFileStream.Entry entry = cfs.ReadFile();
|
CombinedFileStream.Entry entry = cfs.ReadFile();
|
||||||
|
|
||||||
Assert.AreEqual("key2", cfs.SkipFile());
|
Assert.AreEqual("key2", cfs.SkipFile());
|
||||||
@@ -91,68 +89,65 @@ namespace UnitTests.Data{
|
|||||||
Assert.AreEqual("key1", entry.KeyName);
|
Assert.AreEqual("key1", entry.KeyName);
|
||||||
CollectionAssert.AreEqual(new string[]{ "a", "bb", "ccc", "dddd" }, entry.KeyValue);
|
CollectionAssert.AreEqual(new string[]{ "a", "bb", "ccc", "dddd" }, entry.KeyValue);
|
||||||
|
|
||||||
entry.WriteToFile("cfs_text_file_1");
|
entry.WriteToFile("text_file_1");
|
||||||
TestUtils.DeleteFileOnExit("cfs_text_file_1");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists("cfs_text_file_1"));
|
Assert.IsTrue(File.Exists("text_file_1"));
|
||||||
Assert.AreEqual("Hello World!"+Environment.NewLine, TestUtils.ReadText("cfs_text_file_1"));
|
Assert.AreEqual("Hello World!"+Environment.NewLine, File.ReadAllText("text_file_1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestEntryWriteWithDirectory(){
|
public void TestEntryWriteWithDirectory(){
|
||||||
if (Directory.Exists("cfs_directory")){
|
if (Directory.Exists("directory")){
|
||||||
Directory.Delete("cfs_directory", true);
|
Directory.Delete("directory", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
TestUtils.WriteText("cfs_input_dir_1", "test");
|
File.WriteAllText("input_dir_1", "test");
|
||||||
|
|
||||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_dir_test"))){
|
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("dir_test"))){
|
||||||
cfs.WriteFile("key1", "cfs_input_dir_1");
|
cfs.WriteFile("key1", "input_dir_1");
|
||||||
cfs.WriteFile("key2", "cfs_input_dir_1");
|
cfs.WriteFile("key2", "input_dir_1");
|
||||||
cfs.WriteFile("key3", "cfs_input_dir_1");
|
cfs.WriteFile("key3", "input_dir_1");
|
||||||
cfs.WriteFile("key4", "cfs_input_dir_1");
|
cfs.WriteFile("key4", "input_dir_1");
|
||||||
cfs.Flush();
|
cfs.Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists("cfs_dir_test"));
|
Assert.IsTrue(File.Exists("dir_test"));
|
||||||
|
|
||||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_dir_test"))){
|
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenRead("dir_test"))){
|
||||||
try{
|
try{
|
||||||
cfs.ReadFile().WriteToFile("cfs_directory/cfs_dir_test_file", false);
|
cfs.ReadFile().WriteToFile("directory/dir_test_file", false);
|
||||||
Assert.Fail("WriteToFile did not trigger an exception.");
|
Assert.Fail("WriteToFile did not trigger an exception.");
|
||||||
}catch(DirectoryNotFoundException){}
|
}catch(DirectoryNotFoundException){}
|
||||||
|
|
||||||
cfs.ReadFile().WriteToFile("cfs_directory/cfs_dir_test_file", true);
|
cfs.ReadFile().WriteToFile("directory/dir_test_file", true);
|
||||||
cfs.ReadFile().WriteToFile("cfs_dir_test_file", true);
|
cfs.ReadFile().WriteToFile("dir_test_file", true);
|
||||||
cfs.ReadFile().WriteToFile("cfs_dir_test_file.txt", true);
|
cfs.ReadFile().WriteToFile("dir_test_file.txt", true);
|
||||||
TestUtils.DeleteFileOnExit("cfs_dir_test_file");
|
|
||||||
TestUtils.DeleteFileOnExit("cfs_dir_test_file.txt");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.IsTrue(Directory.Exists("cfs_directory"));
|
Assert.IsTrue(Directory.Exists("directory"));
|
||||||
Assert.IsTrue(File.Exists("cfs_directory/cfs_dir_test_file"));
|
Assert.IsTrue(File.Exists("directory/dir_test_file"));
|
||||||
Assert.IsTrue(File.Exists("cfs_dir_test_file"));
|
Assert.IsTrue(File.Exists("dir_test_file"));
|
||||||
Assert.IsTrue(File.Exists("cfs_dir_test_file.txt"));
|
Assert.IsTrue(File.Exists("dir_test_file.txt"));
|
||||||
Assert.AreEqual("test", TestUtils.ReadText("cfs_directory/cfs_dir_test_file"));
|
Assert.AreEqual("test", File.ReadAllText("directory/dir_test_file"));
|
||||||
Assert.AreEqual("test", TestUtils.ReadText("cfs_dir_test_file"));
|
Assert.AreEqual("test", File.ReadAllText("dir_test_file"));
|
||||||
Assert.AreEqual("test", TestUtils.ReadText("cfs_dir_test_file.txt"));
|
Assert.AreEqual("test", File.ReadAllText("dir_test_file.txt"));
|
||||||
|
|
||||||
Directory.Delete("cfs_directory", true);
|
Directory.Delete("directory", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestLongIdentifierSuccess(){
|
public void TestLongIdentifierSuccess(){
|
||||||
TestUtils.WriteText("cfs_long_identifier_fail_in", "test");
|
File.WriteAllText("long_identifier_fail_in", "test");
|
||||||
|
|
||||||
string identifier = string.Join("", Enumerable.Repeat("x", 255));
|
string identifier = string.Join("", Enumerable.Repeat("x", 255));
|
||||||
|
|
||||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_long_identifier_success"))){
|
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("long_identifier_success"))){
|
||||||
cfs.WriteFile(identifier, "cfs_long_identifier_fail_in");
|
cfs.WriteFile(identifier, "long_identifier_fail_in");
|
||||||
cfs.Flush();
|
cfs.Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_long_identifier_success"))){
|
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenRead("long_identifier_success"))){
|
||||||
Assert.AreEqual(identifier, cfs.ReadFile().Identifier);
|
Assert.AreEqual(identifier, cfs.ReadFile().Identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,10 +155,10 @@ namespace UnitTests.Data{
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||||
public void TestLongIdentifierFail(){
|
public void TestLongIdentifierFail(){
|
||||||
TestUtils.WriteText("cfs_long_identifier_fail_in", "test");
|
File.WriteAllText("long_identifier_fail_in", "test");
|
||||||
|
|
||||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_long_identifier_fail"))){
|
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("long_identifier_fail"))){
|
||||||
cfs.WriteFile(string.Join("", Enumerable.Repeat("x", 256)), "cfs_long_identifier_fail_in");
|
cfs.WriteFile(string.Join("", Enumerable.Repeat("x", 256)), "long_identifier_fail_in");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ using TweetDuck.Data.Serialization;
|
|||||||
|
|
||||||
namespace UnitTests.Data{
|
namespace UnitTests.Data{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class TestFileSerializer{
|
public class TestFileSerializer : UnitTestIO{
|
||||||
private enum TestEnum{
|
private enum TestEnum{
|
||||||
A, B, C, D, E
|
A, B, C, D, E
|
||||||
}
|
}
|
||||||
@@ -30,13 +30,11 @@ namespace UnitTests.Data{
|
|||||||
TestEnum = TestEnum.D
|
TestEnum = TestEnum.D
|
||||||
};
|
};
|
||||||
|
|
||||||
serializer.Write("serialized_basic", write);
|
serializer.Write("basic_wr", write);
|
||||||
|
Assert.IsTrue(File.Exists("basic_wr"));
|
||||||
Assert.IsTrue(File.Exists("serialized_basic"));
|
|
||||||
TestUtils.DeleteFileOnExit("serialized_basic");
|
|
||||||
|
|
||||||
SerializationTestBasic read = new SerializationTestBasic();
|
SerializationTestBasic read = new SerializationTestBasic();
|
||||||
serializer.Read("serialized_basic", read);
|
serializer.Read("basic_wr", read);
|
||||||
|
|
||||||
Assert.IsTrue(read.TestBool);
|
Assert.IsTrue(read.TestBool);
|
||||||
Assert.AreEqual(-100, read.TestInt);
|
Assert.AreEqual(-100, read.TestInt);
|
||||||
|
@@ -1,65 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
|
|
||||||
namespace UnitTests{
|
|
||||||
public static class TestUtils{
|
|
||||||
private static readonly HashSet<string> CreatedFiles = new HashSet<string>();
|
|
||||||
|
|
||||||
public static void WriteText(string file, string text){
|
|
||||||
DeleteFileOnExit(file);
|
|
||||||
File.WriteAllText(file, text, Encoding.UTF8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void WriteLines(string file, IEnumerable<string> lines){
|
|
||||||
DeleteFileOnExit(file);
|
|
||||||
File.WriteAllLines(file, lines, Encoding.UTF8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static FileStream WriteFile(string file){
|
|
||||||
DeleteFileOnExit(file);
|
|
||||||
return new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ReadText(string file){
|
|
||||||
try{
|
|
||||||
return File.ReadAllText(file, Encoding.UTF8);
|
|
||||||
}catch(Exception){
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<string> ReadLines(string file){
|
|
||||||
try{
|
|
||||||
return File.ReadLines(file, Encoding.UTF8);
|
|
||||||
}catch(Exception){
|
|
||||||
return Enumerable.Empty<string>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static FileStream ReadFile(string file){
|
|
||||||
return new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DeleteFileOnExit(string file){
|
|
||||||
CreatedFiles.Add(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestClass]
|
|
||||||
public static class Cleanup{
|
|
||||||
[AssemblyCleanup]
|
|
||||||
public static void DeleteFilesOnExit(){
|
|
||||||
foreach(string file in CreatedFiles){
|
|
||||||
try{
|
|
||||||
File.Delete(file);
|
|
||||||
}catch(Exception){
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
32
tests/UnitTestIO.cs
Normal file
32
tests/UnitTestIO.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace UnitTests{
|
||||||
|
[TestClass]
|
||||||
|
public class UnitTestIO{
|
||||||
|
private static readonly HashSet<string> CreatedFolders = new HashSet<string>();
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void InitTest(){
|
||||||
|
string folder = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, GetType().Name);
|
||||||
|
CreatedFolders.Add(folder);
|
||||||
|
Directory.CreateDirectory(folder);
|
||||||
|
Directory.SetCurrentDirectory(folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AssemblyCleanup]
|
||||||
|
public static void DeleteFilesOnExit(){
|
||||||
|
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.SetupInformation.ApplicationBase);
|
||||||
|
|
||||||
|
foreach(string folder in CreatedFolders){
|
||||||
|
try{
|
||||||
|
Directory.Delete(folder, true);
|
||||||
|
}catch(Exception){
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -47,6 +47,7 @@
|
|||||||
</Otherwise>
|
</Otherwise>
|
||||||
</Choose>
|
</Choose>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Configuration\TestUserConfig.cs" />
|
||||||
<Compile Include="Core\TestStringUtils.cs" />
|
<Compile Include="Core\TestStringUtils.cs" />
|
||||||
<Compile Include="Core\TestTwitterUtils.cs" />
|
<Compile Include="Core\TestTwitterUtils.cs" />
|
||||||
<Compile Include="Data\TestCombinedFileStream.cs" />
|
<Compile Include="Data\TestCombinedFileStream.cs" />
|
||||||
@@ -56,7 +57,7 @@
|
|||||||
<Compile Include="Data\TestInjectedHTML.cs" />
|
<Compile Include="Data\TestInjectedHTML.cs" />
|
||||||
<Compile Include="Data\TestTwoKeyDictionary.cs" />
|
<Compile Include="Data\TestTwoKeyDictionary.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="TestUtils.cs" />
|
<Compile Include="UnitTestIO.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\TweetDuck.csproj">
|
<ProjectReference Include="..\TweetDuck.csproj">
|
||||||
|
Reference in New Issue
Block a user