Compare commits
62 Commits
Author | SHA1 | Date | |
---|---|---|---|
0195378c10 | |||
bc804c6a53 | |||
76b15f1971 | |||
c4d43c9d5b | |||
e8d3e530de | |||
e145adec58 | |||
e2dad3e477 | |||
27bdbde171 | |||
e9ec27169c | |||
2e24cb634c | |||
beb9046055 | |||
e57301952c | |||
7411279e48 | |||
16acfa85b5 | |||
41ef37f3f0 | |||
00d8538726 | |||
6eeb3f9895 | |||
d19dca6ea5 | |||
2008ccdaa4 | |||
ba2e62de3a | |||
2b62eb254d | |||
31f72b7957 | |||
fdc4616875 | |||
b7de261d25 | |||
ae78a5a026 | |||
fd2cf5d4d7 | |||
9f0997be1a | |||
dbade7f854 | |||
3cdc1e190a | |||
36bede7211 | |||
46689bb700 | |||
13e1a6543c | |||
820ce9e845 | |||
f17806f4e8 | |||
3f5ffc9e10 | |||
aeb0842ab4 | |||
38837ae84c | |||
a4eb6935af | |||
52f1f4c4eb | |||
6c1782a038 | |||
8b8f5f5473 | |||
61d3ed891a | |||
b1abf87320 | |||
9aedfc2799 | |||
ad6240a067 | |||
9539eb076a | |||
c808e7bd83 | |||
13ea388f5e | |||
c46dc0f1a3 | |||
2ae311007d | |||
9344e02bff | |||
40ad836fc3 | |||
e8604a261d | |||
2a41d21a29 | |||
4c62aa067b | |||
49db3074c6 | |||
f5e3b34f30 | |||
f0affa4aec | |||
4f5075ac54 | |||
20f0445b10 | |||
c77c974455 | |||
44397b2d45 |
@@ -44,14 +44,18 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
// CONFIGURATION DATA
|
||||
|
||||
public bool FirstRun { get; set; } = true;
|
||||
public bool AllowDataCollection { get; set; } = false;
|
||||
|
||||
public WindowState BrowserWindow { get; set; } = new WindowState();
|
||||
public WindowState PluginsWindow { get; set; } = new WindowState();
|
||||
|
||||
public bool ExpandLinksOnHover { get; set; } = true;
|
||||
public bool SwitchAccountSelectors { get; set; } = true;
|
||||
public bool BestImageQuality { get; set; } = true;
|
||||
public bool EnableSpellCheck { get; set; } = false;
|
||||
public int VideoPlayerVolume { get; set; } = 50;
|
||||
public bool ExpandLinksOnHover { get; set; } = true;
|
||||
public bool SwitchAccountSelectors { get; set; } = true;
|
||||
public bool OpenSearchInFirstColumn { get; set; } = true;
|
||||
public bool BestImageQuality { get; set; } = true;
|
||||
public bool EnableSpellCheck { get; set; } = false;
|
||||
public int VideoPlayerVolume { get; set; } = 50;
|
||||
private int _zoomLevel = 100;
|
||||
private bool _muteNotifications;
|
||||
|
||||
@@ -105,7 +109,7 @@ namespace TweetDuck.Configuration{
|
||||
set{
|
||||
if (_muteNotifications != value){
|
||||
_muteNotifications = value;
|
||||
MuteToggled?.Invoke(this, new EventArgs());
|
||||
MuteToggled?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,7 +120,7 @@ namespace TweetDuck.Configuration{
|
||||
set{
|
||||
if (_zoomLevel != value){
|
||||
_zoomLevel = value;
|
||||
ZoomLevelChanged?.Invoke(this, new EventArgs());
|
||||
ZoomLevelChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,7 +133,7 @@ namespace TweetDuck.Configuration{
|
||||
set{
|
||||
if (_trayBehavior != value){
|
||||
_trayBehavior = value;
|
||||
TrayBehaviorChanged?.Invoke(this, new EventArgs());
|
||||
TrayBehaviorChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ namespace TweetDuck.Core.Bridge{
|
||||
|
||||
if (environment == Environment.Browser){
|
||||
build.Append("x.switchAccountSelectors=").Append(Bool(Program.UserConfig.SwitchAccountSelectors));
|
||||
build.Append("x.openSearchInFirstColumn=").Append(Bool(Program.UserConfig.OpenSearchInFirstColumn));
|
||||
build.Append("x.muteNotifications=").Append(Bool(Program.UserConfig.MuteNotifications));
|
||||
build.Append("x.hasCustomNotificationSound=").Append(Bool(Program.UserConfig.NotificationSoundPath.Length > 0));
|
||||
build.Append("x.notificationMediaPreviews=").Append(Bool(Program.UserConfig.NotificationMediaPreviews));
|
||||
|
@@ -11,15 +11,22 @@ using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Core.Bridge{
|
||||
sealed class TweetDeckBridge{
|
||||
public static string LastHighlightedTweet = string.Empty;
|
||||
public static string LastHighlightedQuotedTweet = string.Empty;
|
||||
public static string LastHighlightedTweetAuthor = string.Empty;
|
||||
public static string[] LastHighlightedTweetImages = StringUtils.EmptyArray;
|
||||
public static Dictionary<string, string> SessionData = new Dictionary<string, string>(2);
|
||||
public static string FontSizeClass;
|
||||
public static string NotificationHeadContents;
|
||||
|
||||
public static string LastHighlightedTweetUrl = string.Empty;
|
||||
public static string LastHighlightedQuoteUrl = string.Empty;
|
||||
private static string LastHighlightedTweetAuthors = string.Empty;
|
||||
private static string LastHighlightedTweetImages = string.Empty;
|
||||
|
||||
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(){
|
||||
LastHighlightedTweet = LastHighlightedQuotedTweet = LastHighlightedTweetAuthor = string.Empty;
|
||||
LastHighlightedTweetImages = StringUtils.EmptyArray;
|
||||
FontSizeClass = NotificationHeadContents = null;
|
||||
LastHighlightedTweetUrl = LastHighlightedQuoteUrl = LastHighlightedTweetAuthors = LastHighlightedTweetImages = string.Empty;
|
||||
}
|
||||
|
||||
public static void RestoreSessionData(IFrame frame){
|
||||
@@ -43,28 +50,30 @@ namespace TweetDuck.Core.Bridge{
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
public void LoadFontSizeClass(string fsClass){
|
||||
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
|
||||
form.InvokeAsyncSafe(() => {
|
||||
TweetNotification.SetFontSizeClass(fsClass);
|
||||
form.OnIntroductionClosed(showGuide, allowDataCollection);
|
||||
});
|
||||
}
|
||||
|
||||
public void LoadFontSizeClass(string fsClass){
|
||||
form.InvokeAsyncSafe(() => FontSizeClass = fsClass);
|
||||
}
|
||||
|
||||
public void LoadNotificationHeadContents(string headContents){
|
||||
form.InvokeAsyncSafe(() => {
|
||||
TweetNotification.SetHeadTag(headContents);
|
||||
});
|
||||
form.InvokeAsyncSafe(() => NotificationHeadContents = headContents);
|
||||
}
|
||||
|
||||
public void SetLastRightClickInfo(string type, string 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(() => {
|
||||
LastHighlightedTweet = link;
|
||||
LastHighlightedQuotedTweet = quotedLink;
|
||||
LastHighlightedTweetAuthor = author;
|
||||
LastHighlightedTweetImages = imageList.Split(';');
|
||||
LastHighlightedTweetUrl = tweetUrl;
|
||||
LastHighlightedQuoteUrl = quoteUrl;
|
||||
LastHighlightedTweetAuthors = authors;
|
||||
LastHighlightedTweetImages = imageList;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -72,10 +81,10 @@ namespace TweetDuck.Core.Bridge{
|
||||
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(() => {
|
||||
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 +126,12 @@ namespace TweetDuck.Core.Bridge{
|
||||
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
|
||||
}
|
||||
|
||||
public int GetIdleSeconds(){
|
||||
return NativeMethods.GetIdleSeconds();
|
||||
public void OpenBrowser(string url){
|
||||
form.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
|
||||
}
|
||||
|
||||
public void OpenBrowser(string url){
|
||||
BrowserUtils.OpenExternalBrowser(url);
|
||||
public int GetIdleSeconds(){
|
||||
return NativeMethods.GetIdleSeconds();
|
||||
}
|
||||
|
||||
public void Alert(string type, string contents){
|
||||
|
@@ -84,6 +84,8 @@ namespace TweetDuck.Core{
|
||||
this.notification.Show();
|
||||
|
||||
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
|
||||
DialogHandler = new FileDialogHandler(),
|
||||
DragHandler = new DragHandlerBrowser(),
|
||||
MenuHandler = new ContextMenuBrowser(this),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
KeyboardHandler = new KeyboardHandlerBrowser(this),
|
||||
@@ -200,6 +202,7 @@ namespace TweetDuck.Core{
|
||||
UpdateProperties(PropertyBridge.Environment.Browser);
|
||||
TweetDeckBridge.RestoreSessionData(e.Frame);
|
||||
ScriptLoader.ExecuteFile(e.Frame, "code.js");
|
||||
InjectBrowserCSS();
|
||||
ReinjectCustomCSS(Config.CustomBrowserCSS);
|
||||
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser);
|
||||
|
||||
@@ -208,6 +211,10 @@ namespace TweetDuck.Core{
|
||||
if (Program.SystemConfig.EnableBrowserGCReload){
|
||||
memoryUsageTracker.Start(this, e.Browser, Program.SystemConfig.BrowserMemoryThreshold);
|
||||
}
|
||||
|
||||
if (Config.FirstRun){
|
||||
ScriptLoader.ExecuteFile(e.Frame, "introduction.js");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +384,7 @@ namespace TweetDuck.Core{
|
||||
if (isLoaded){
|
||||
if (m.Msg == Program.WindowRestoreMessage){
|
||||
if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
|
||||
trayIcon_ClickRestore(trayIcon, new EventArgs());
|
||||
trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -423,6 +430,10 @@ namespace TweetDuck.Core{
|
||||
|
||||
// javascript calls
|
||||
|
||||
public void InjectBrowserCSS(){
|
||||
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css").TrimEnd());
|
||||
}
|
||||
|
||||
public void ReinjectCustomCSS(string css){
|
||||
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
|
||||
}
|
||||
@@ -436,6 +447,18 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
|
||||
// callback handlers
|
||||
|
||||
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
|
||||
if (Config.FirstRun){
|
||||
Config.FirstRun = false;
|
||||
Config.AllowDataCollection = allowDataCollection;
|
||||
Config.Save();
|
||||
}
|
||||
|
||||
if (showGuide){
|
||||
ShowChildForm(new FormGuide());
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenContextMenu(){
|
||||
contextMenu.Show(this, PointToClient(Cursor.Position));
|
||||
|
@@ -20,7 +20,7 @@ namespace TweetDuck.Core{
|
||||
|
||||
public static void CloseAllDialogs(){
|
||||
foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){
|
||||
if (form is FormSettings || form is FormPlugins || form is FormAbout){
|
||||
if (form is FormSettings || form is FormPlugins || form is FormAbout || form is FormGuide){
|
||||
form.Close();
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
abstract class ContextMenuBase : IContextMenuHandler{
|
||||
@@ -35,25 +36,19 @@ namespace TweetDuck.Core.Handling{
|
||||
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26505;
|
||||
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26506;
|
||||
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
|
||||
|
||||
private readonly Form form;
|
||||
|
||||
private string lastHighlightedTweetAuthor;
|
||||
private string[] lastHighlightedTweetAuthors;
|
||||
private string[] lastHighlightedTweetImageList;
|
||||
|
||||
protected ContextMenuBase(Form form){
|
||||
this.form = form;
|
||||
}
|
||||
|
||||
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
||||
lastHighlightedTweetAuthor = string.Empty;
|
||||
lastHighlightedTweetAuthors = StringUtils.EmptyArray;
|
||||
lastHighlightedTweetImageList = StringUtils.EmptyArray;
|
||||
ContextInfo = default(KeyValuePair<string, string>);
|
||||
}
|
||||
else{
|
||||
lastHighlightedTweetAuthor = TweetDeckBridge.LastHighlightedTweetAuthor;
|
||||
lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImages;
|
||||
lastHighlightedTweetAuthors = TweetDeckBridge.LastHighlightedTweetAuthorsArray;
|
||||
lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImagesArray;
|
||||
}
|
||||
|
||||
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){
|
||||
switch(commandId){
|
||||
case MenuOpenLinkUrl:
|
||||
BrowserUtils.OpenExternalBrowser(parameters.LinkUrl);
|
||||
OpenBrowser(browserControl.AsControl(), IsLink ? ContextInfo.Value : parameters.LinkUrl);
|
||||
break;
|
||||
|
||||
case MenuCopyLinkUrl:
|
||||
SetClipboardText(IsLink ? ContextInfo.Value : parameters.UnfilteredLinkUrl);
|
||||
SetClipboardText(browserControl.AsControl(), IsLink ? ContextInfo.Value : parameters.UnfilteredLinkUrl);
|
||||
break;
|
||||
|
||||
case MenuCopyUsername:
|
||||
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;
|
||||
|
||||
case MenuOpenMediaUrl:
|
||||
BrowserUtils.OpenExternalBrowser(TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
|
||||
OpenBrowser(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
|
||||
break;
|
||||
|
||||
case MenuCopyMediaUrl:
|
||||
SetClipboardText(TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
|
||||
SetClipboardText(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
|
||||
break;
|
||||
|
||||
case MenuSaveMedia:
|
||||
@@ -124,13 +119,13 @@ namespace TweetDuck.Core.Handling{
|
||||
TwitterUtils.DownloadVideo(GetMediaLink(parameters));
|
||||
}
|
||||
else{
|
||||
TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthor, ImageQuality);
|
||||
TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MenuSaveTweetImages:
|
||||
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthor, ImageQuality);
|
||||
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
|
||||
break;
|
||||
|
||||
case MenuOpenDevTools:
|
||||
@@ -149,8 +144,12 @@ namespace TweetDuck.Core.Handling{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void SetClipboardText(string text){
|
||||
form.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
|
||||
protected void OpenBrowser(Control control, string url){
|
||||
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){
|
||||
|
@@ -26,10 +26,10 @@ namespace TweetDuck.Core.Handling{
|
||||
|
||||
private readonly FormBrowser form;
|
||||
|
||||
private string lastHighlightedTweet;
|
||||
private string lastHighlightedQuotedTweet;
|
||||
private string lastHighlightedTweetUrl;
|
||||
private string lastHighlightedQuoteUrl;
|
||||
|
||||
public ContextMenuBrowser(FormBrowser form) : base(form){
|
||||
public ContextMenuBrowser(FormBrowser form){
|
||||
this.form = form;
|
||||
}
|
||||
|
||||
@@ -46,20 +46,20 @@ namespace TweetDuck.Core.Handling{
|
||||
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
|
||||
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
|
||||
lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet;
|
||||
lastHighlightedTweetUrl = TweetDeckBridge.LastHighlightedTweetUrl;
|
||||
lastHighlightedQuoteUrl = TweetDeckBridge.LastHighlightedQuoteUrl;
|
||||
|
||||
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
||||
lastHighlightedTweet = string.Empty;
|
||||
lastHighlightedQuotedTweet = string.Empty;
|
||||
lastHighlightedTweetUrl = 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(MenuCopyTweetUrl, "Copy tweet address");
|
||||
model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard");
|
||||
|
||||
if (!string.IsNullOrEmpty(lastHighlightedQuotedTweet)){
|
||||
if (!string.IsNullOrEmpty(lastHighlightedQuoteUrl)){
|
||||
model.AddSeparator();
|
||||
model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
|
||||
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
||||
@@ -118,11 +118,11 @@ namespace TweetDuck.Core.Handling{
|
||||
return true;
|
||||
|
||||
case MenuOpenTweetUrl:
|
||||
BrowserUtils.OpenExternalBrowser(lastHighlightedTweet);
|
||||
OpenBrowser(form, lastHighlightedTweetUrl);
|
||||
return true;
|
||||
|
||||
case MenuCopyTweetUrl:
|
||||
SetClipboardText(lastHighlightedTweet);
|
||||
SetClipboardText(form, lastHighlightedTweetUrl);
|
||||
return true;
|
||||
|
||||
case MenuScreenshotTweet:
|
||||
@@ -130,11 +130,11 @@ namespace TweetDuck.Core.Handling{
|
||||
return true;
|
||||
|
||||
case MenuOpenQuotedTweetUrl:
|
||||
BrowserUtils.OpenExternalBrowser(lastHighlightedQuotedTweet);
|
||||
OpenBrowser(form, lastHighlightedQuoteUrl);
|
||||
return true;
|
||||
|
||||
case MenuCopyQuotedTweetUrl:
|
||||
SetClipboardText(lastHighlightedQuotedTweet);
|
||||
SetClipboardText(form, lastHighlightedQuoteUrl);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
15
Core/Handling/ContextMenuGuide.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
sealed class ContextMenuGuide : ContextMenuBase{
|
||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||
model.Clear();
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
|
||||
if (HasDevTools){
|
||||
AddSeparator(model);
|
||||
AddDebugMenuItems(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -13,7 +13,7 @@ namespace TweetDuck.Core.Handling{
|
||||
private readonly FormNotificationBase form;
|
||||
private readonly bool enableCustomMenu;
|
||||
|
||||
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu) : base(form){
|
||||
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu){
|
||||
this.form = form;
|
||||
this.enableCustomMenu = enableCustomMenu;
|
||||
}
|
||||
@@ -29,7 +29,7 @@ namespace TweetDuck.Core.Handling{
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
|
||||
if (enableCustomMenu){
|
||||
if (!string.IsNullOrEmpty(form.CurrentChirpId)){
|
||||
if (form.CanViewDetail){
|
||||
model.AddItem(MenuViewDetail, "View detail");
|
||||
}
|
||||
|
||||
@@ -76,11 +76,11 @@ namespace TweetDuck.Core.Handling{
|
||||
return true;
|
||||
|
||||
case MenuCopyTweetUrl:
|
||||
SetClipboardText(form.CurrentTweetUrl);
|
||||
SetClipboardText(form, form.CurrentTweetUrl);
|
||||
return true;
|
||||
|
||||
case MenuCopyQuotedTweetUrl:
|
||||
SetClipboardText(form.CurrentQuoteUrl);
|
||||
SetClipboardText(form, form.CurrentQuoteUrl);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
19
Core/Handling/DragHandlerBrowser.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
sealed class DragHandlerBrowser : IDragHandler{
|
||||
public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask){
|
||||
if (dragData.IsLink){
|
||||
browserControl.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", "link", dragData.LinkUrl);
|
||||
}
|
||||
else{
|
||||
browserControl.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", "unknown");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnDraggableRegionsChanged(IWebBrowser browserControl, IBrowser browser, IList<DraggableRegion> regions){}
|
||||
}
|
||||
}
|
42
Core/Handling/General/FileDialogHandler.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
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){
|
||||
string ext = Path.GetExtension(dialog.FileName);
|
||||
callback.Continue(acceptFilters.FindIndex(filter => filter.Equals(ext, StringComparison.OrdinalIgnoreCase)), dialog.FileNames.ToList());
|
||||
}
|
||||
else{
|
||||
callback.Cancel();
|
||||
}
|
||||
|
||||
callback.Dispose();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
callback.Dispose();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Handling.General{
|
||||
@@ -11,7 +12,7 @@ namespace TweetDuck.Core.Handling.General{
|
||||
case WindowOpenDisposition.NewForegroundTab:
|
||||
case WindowOpenDisposition.NewPopup:
|
||||
case WindowOpenDisposition.NewWindow:
|
||||
BrowserUtils.OpenExternalBrowser(targetUrl);
|
||||
browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl));
|
||||
return true;
|
||||
|
||||
default:
|
||||
|
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Handling.General;
|
||||
@@ -12,6 +13,18 @@ using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
partial class FormNotificationBase : Form{
|
||||
protected static int FontSizeLevel{
|
||||
get{
|
||||
switch(TweetDeckBridge.FontSizeClass){
|
||||
case "largest": return 4;
|
||||
case "large": return 3;
|
||||
case "small": return 1;
|
||||
case "smallest": return 0;
|
||||
default: return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Point PrimaryLocation{
|
||||
get{
|
||||
UserConfig config = Program.UserConfig;
|
||||
@@ -82,9 +95,10 @@ namespace TweetDuck.Core.Notification{
|
||||
private TweetNotification currentNotification;
|
||||
private int pauseCounter;
|
||||
|
||||
public string CurrentChirpId => currentNotification?.ChirpId;
|
||||
public string CurrentTweetUrl => currentNotification?.TweetUrl;
|
||||
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 FreezeTimer { get; set; }
|
||||
@@ -144,7 +158,7 @@ namespace TweetDuck.Core.Notification{
|
||||
|
||||
private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
|
||||
if (e.IsBrowserInitialized){
|
||||
Initialized?.Invoke(this, new EventArgs());
|
||||
Initialized?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
int identifier = browser.GetBrowser().Identifier;
|
||||
Disposed += (sender2, args2) => BrowserProcesses.Forget(identifier);
|
||||
|
@@ -33,7 +33,7 @@ namespace TweetDuck.Core.Notification{
|
||||
|
||||
public bool RequiresResize{
|
||||
get{
|
||||
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != TweetNotification.FontSizeLevel || CanResizeWindow;
|
||||
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != FontSizeLevel || CanResizeWindow;
|
||||
}
|
||||
|
||||
set{
|
||||
@@ -43,7 +43,7 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
else{
|
||||
prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer;
|
||||
prevFontSize = TweetNotification.FontSizeLevel;
|
||||
prevFontSize = FontSizeLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ namespace TweetDuck.Core.Notification{
|
||||
get{
|
||||
switch(Program.UserConfig.NotificationSize){
|
||||
default:
|
||||
return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*TweetNotification.FontSizeLevel));
|
||||
return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*FontSizeLevel));
|
||||
|
||||
case TweetNotification.Size.Custom:
|
||||
return Program.UserConfig.CustomNotificationSize.Width;
|
||||
@@ -64,7 +64,7 @@ namespace TweetDuck.Core.Notification{
|
||||
get{
|
||||
switch(Program.UserConfig.NotificationSize){
|
||||
default:
|
||||
return BrowserUtils.Scale(122, SizeScale*(1.0+0.075*TweetNotification.FontSizeLevel));
|
||||
return BrowserUtils.Scale(122, SizeScale*(1.0+0.075*FontSizeLevel));
|
||||
|
||||
case TweetNotification.Size.Custom:
|
||||
return Program.UserConfig.CustomNotificationSize.Height;
|
||||
|
@@ -1,27 +1,14 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
sealed class TweetNotification{
|
||||
private static string FontSizeClass { get; set; }
|
||||
private static string HeadTag { get; set; }
|
||||
|
||||
private const string DefaultFontSizeClass = "medium";
|
||||
private const string DefaultHeadTag = @"<meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>";
|
||||
private const string CustomCSS = @"body:before{content:none}body{overflow-y:auto}.scroll-styled-v::-webkit-scrollbar{width:7px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}.scroll-styled-v::-webkit-scrollbar-track{border-left:0}#td-skip{opacity:0;cursor:pointer;transition:opacity 0.15s ease}.td-hover #td-skip{opacity:0.75}#td-skip:hover{opacity:1}.media-size-medium{height:calc(100vh - 16px)!important;max-height:240px;border-radius:1px!important}.js-quote-detail .media-size-medium{height:calc(100vh - 28px)!important;}.js-media.margin-vm, .js-media-preview-container.margin-vm{margin-bottom:0!important}";
|
||||
private const string DefaultHeadContents = @"<meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>";
|
||||
|
||||
public static int FontSizeLevel{
|
||||
get{
|
||||
switch(FontSizeClass){
|
||||
case "largest": return 4;
|
||||
case "large": return 3;
|
||||
case "medium": return 2;
|
||||
case "small": return 1;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
private static readonly string CustomCSS = ScriptLoader.LoadResource("styles/notification.css");
|
||||
|
||||
private static string ExampleTweetHTML;
|
||||
|
||||
@@ -39,14 +26,6 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetFontSizeClass(string newFSClass){
|
||||
FontSizeClass = newFSClass;
|
||||
}
|
||||
|
||||
public static void SetHeadTag(string headContents){
|
||||
HeadTag = headContents;
|
||||
}
|
||||
|
||||
public enum Position{
|
||||
TopLeft, TopRight, BottomLeft, BottomRight, Custom
|
||||
}
|
||||
@@ -88,8 +67,8 @@ namespace TweetDuck.Core.Notification{
|
||||
public string GenerateHtml(string bodyClasses = null, bool enableCustomCSS = true){
|
||||
StringBuilder build = new StringBuilder();
|
||||
build.Append("<!DOCTYPE html>");
|
||||
build.Append("<html class='os-windows txt-base-").Append(FontSizeClass ?? DefaultFontSizeClass).Append("'>");
|
||||
build.Append("<head>").Append(HeadTag ?? DefaultHeadTag);
|
||||
build.Append("<html class='os-windows txt-base-").Append(TweetDeckBridge.FontSizeClass ?? DefaultFontSizeClass).Append("'>");
|
||||
build.Append("<head>").Append(TweetDeckBridge.NotificationHeadContents ?? DefaultHeadContents);
|
||||
|
||||
if (enableCustomCSS){
|
||||
build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>");
|
||||
|
3
Core/Other/FormAbout.Designer.cs
generated
@@ -131,11 +131,14 @@ namespace TweetDuck.Core.Other {
|
||||
this.Controls.Add(this.labelDescription);
|
||||
this.Controls.Add(this.pictureLogo);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.HelpButton = true;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "FormAbout";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.HelpButtonClicked += new System.ComponentModel.CancelEventHandler(this.FormAbout_HelpButtonClicked);
|
||||
this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.FormAbout_HelpRequested);
|
||||
((System.ComponentModel.ISupportInitialize)(this.pictureLogo)).EndInit();
|
||||
this.tablePanelLinks.ResumeLayout(false);
|
||||
this.tablePanelLinks.PerformLayout();
|
||||
|
@@ -1,4 +1,6 @@
|
||||
using System.Windows.Forms;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other{
|
||||
@@ -21,5 +23,19 @@ namespace TweetDuck.Core.Other{
|
||||
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe(e.Link.LinkData as string);
|
||||
}
|
||||
|
||||
private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent){
|
||||
ShowGuide();
|
||||
}
|
||||
|
||||
private void FormAbout_HelpButtonClicked(object sender, CancelEventArgs e){
|
||||
e.Cancel = true;
|
||||
ShowGuide();
|
||||
}
|
||||
|
||||
private void ShowGuide(){
|
||||
new FormGuide().Show();
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
44
Core/Other/FormGuide.Designer.cs
generated
Normal file
@@ -0,0 +1,44 @@
|
||||
namespace TweetDuck.Core.Other {
|
||||
partial class FormGuide {
|
||||
/// <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.SuspendLayout();
|
||||
//
|
||||
// FormGuide
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(34)))), ((int)(((byte)(34)))), ((int)(((byte)(34)))));
|
||||
this.ClientSize = new System.Drawing.Size(424, 282);
|
||||
this.Icon = global::TweetDuck.Properties.Resources.icon;
|
||||
this.MinimumSize = new System.Drawing.Size(440, 320);
|
||||
this.Name = "FormGuide";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
71
Core/Other/FormGuide.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Handling.General;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other{
|
||||
sealed partial class FormGuide : Form{
|
||||
private const string GuideUrl = "https://tweetduck.chylex.com/guide/v1/";
|
||||
|
||||
private readonly ChromiumWebBrowser browser;
|
||||
|
||||
public FormGuide(){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName+" Guide";
|
||||
|
||||
FormBrowser owner = FormManager.TryFind<FormBrowser>();
|
||||
|
||||
if (owner != null){
|
||||
Size = new Size(owner.Size.Width*3/4, owner.Size.Height*3/4);
|
||||
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
|
||||
}
|
||||
|
||||
this.browser = new ChromiumWebBrowser(GuideUrl){
|
||||
MenuHandler = new ContextMenuGuide(),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBrowser()
|
||||
};
|
||||
|
||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
browser.FrameLoadStart += browser_FrameLoadStart;
|
||||
|
||||
browser.BrowserSettings.BackgroundColor = (uint)BackColor.ToArgb();
|
||||
browser.Dock = DockStyle.None;
|
||||
browser.Location = ControlExtensions.InvisibleLocation;
|
||||
Controls.Add(browser);
|
||||
|
||||
Disposed += (sender, args) => {
|
||||
Program.UserConfig.ZoomLevelChanged -= Config_ZoomLevelChanged;
|
||||
browser.Dispose();
|
||||
};
|
||||
|
||||
Program.UserConfig.ZoomLevelChanged += Config_ZoomLevelChanged;
|
||||
}
|
||||
|
||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||
if (!e.IsLoading){
|
||||
this.InvokeAsyncSafe(() => {
|
||||
browser.Location = Point.Empty;
|
||||
browser.Dock = DockStyle.Fill;
|
||||
});
|
||||
|
||||
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
|
||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
|
||||
}
|
||||
|
||||
private void Config_ZoomLevelChanged(object sender, EventArgs e){
|
||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
|
||||
}
|
||||
}
|
||||
}
|
@@ -34,8 +34,10 @@ namespace TweetDuck.Core.Other{
|
||||
this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1;
|
||||
|
||||
AddButton("General", () => new TabSettingsGeneral(updates));
|
||||
AddButton("System Tray", () => new TabSettingsTray());
|
||||
AddButton("Notifications", () => new TabSettingsNotifications(browser.CreateNotificationForm(false)));
|
||||
AddButton("Sounds", () => new TabSettingsSounds());
|
||||
AddButton("Feedback", () => new TabSettingsFeedback());
|
||||
AddButton("Advanced", () => new TabSettingsAdvanced(browser.ReinjectCustomCSS));
|
||||
|
||||
SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]);
|
||||
@@ -128,11 +130,16 @@ namespace TweetDuck.Core.Other{
|
||||
tab.Control.OnReady();
|
||||
}
|
||||
|
||||
panelContents.VerticalScroll.Enabled = false; // required to stop animation that would otherwise break everything
|
||||
panelContents.PerformLayout();
|
||||
|
||||
panelContents.SuspendLayout();
|
||||
panelContents.VerticalScroll.Value = 0; // https://gfycat.com/GrotesqueTastyAstarte
|
||||
panelContents.Controls.Clear();
|
||||
panelContents.Controls.Add(tab.Control);
|
||||
panelContents.ResumeLayout(true);
|
||||
|
||||
panelContents.VerticalScroll.Enabled = true;
|
||||
panelContents.Focus();
|
||||
|
||||
currentTab = tab;
|
||||
|
@@ -147,7 +147,15 @@ namespace TweetDuck.Core.Other.Management{
|
||||
}
|
||||
|
||||
private void process_Exited(object sender, EventArgs e){
|
||||
switch(currentProcess.ExitCode){
|
||||
int exitCode = currentProcess.ExitCode;
|
||||
|
||||
currentProcess.Dispose();
|
||||
currentProcess = null;
|
||||
|
||||
currentPipe.Dispose();
|
||||
currentPipe = null;
|
||||
|
||||
switch(exitCode){
|
||||
case 3: // CODE_LAUNCH_FAIL
|
||||
if (FormMessage.Error("Video Playback Error", "Error launching video player, this may be caused by missing Windows Media Player. Do you want to open the video in a browser?", FormMessage.Yes, FormMessage.No)){
|
||||
BrowserUtils.OpenExternalBrowser(lastUrl);
|
||||
@@ -162,18 +170,12 @@ namespace TweetDuck.Core.Other.Management{
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
currentProcess.Dispose();
|
||||
currentProcess = null;
|
||||
|
||||
currentPipe.Dispose();
|
||||
currentPipe = null;
|
||||
|
||||
owner.InvokeAsyncSafe(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;
|
||||
}
|
||||
|
||||
control_Change(this, new EventArgs());
|
||||
control_Change(this, EventArgs.Empty);
|
||||
|
||||
Text = Program.BrandName+" Arguments";
|
||||
}
|
||||
|
134
Core/Other/Settings/TabSettingsFeedback.Designer.cs
generated
Normal file
@@ -0,0 +1,134 @@
|
||||
namespace TweetDuck.Core.Other.Settings {
|
||||
partial class TabSettingsFeedback {
|
||||
/// <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 Component 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.panelFeedback = new System.Windows.Forms.Panel();
|
||||
this.labelDataCollectionLink = new System.Windows.Forms.LinkLabel();
|
||||
this.checkDataCollection = new System.Windows.Forms.CheckBox();
|
||||
this.labelDataCollection = new System.Windows.Forms.Label();
|
||||
this.labelFeedback = new System.Windows.Forms.Label();
|
||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.btnSendFeedback = new System.Windows.Forms.Button();
|
||||
this.panelFeedback.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// panelFeedback
|
||||
//
|
||||
this.panelFeedback.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.panelFeedback.Controls.Add(this.btnSendFeedback);
|
||||
this.panelFeedback.Controls.Add(this.labelDataCollectionLink);
|
||||
this.panelFeedback.Controls.Add(this.checkDataCollection);
|
||||
this.panelFeedback.Controls.Add(this.labelDataCollection);
|
||||
this.panelFeedback.Location = new System.Drawing.Point(9, 31);
|
||||
this.panelFeedback.Name = "panelFeedback";
|
||||
this.panelFeedback.Size = new System.Drawing.Size(322, 80);
|
||||
this.panelFeedback.TabIndex = 1;
|
||||
//
|
||||
// labelDataCollectionLink
|
||||
//
|
||||
this.labelDataCollectionLink.AutoSize = true;
|
||||
this.labelDataCollectionLink.LinkArea = new System.Windows.Forms.LinkArea(1, 10);
|
||||
this.labelDataCollectionLink.LinkBehavior = System.Windows.Forms.LinkBehavior.HoverUnderline;
|
||||
this.labelDataCollectionLink.Location = new System.Drawing.Point(141, 60);
|
||||
this.labelDataCollectionLink.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
|
||||
this.labelDataCollectionLink.Name = "labelDataCollectionLink";
|
||||
this.labelDataCollectionLink.Size = new System.Drawing.Size(66, 17);
|
||||
this.labelDataCollectionLink.TabIndex = 3;
|
||||
this.labelDataCollectionLink.TabStop = true;
|
||||
this.labelDataCollectionLink.Text = "(learn more)";
|
||||
this.labelDataCollectionLink.UseCompatibleTextRendering = true;
|
||||
this.labelDataCollectionLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.labelDataCollectionLink_LinkClicked);
|
||||
//
|
||||
// checkDataCollection
|
||||
//
|
||||
this.checkDataCollection.AutoSize = true;
|
||||
this.checkDataCollection.Location = new System.Drawing.Point(6, 59);
|
||||
this.checkDataCollection.Margin = new System.Windows.Forms.Padding(6, 5, 0, 3);
|
||||
this.checkDataCollection.Name = "checkDataCollection";
|
||||
this.checkDataCollection.Size = new System.Drawing.Size(135, 17);
|
||||
this.checkDataCollection.TabIndex = 2;
|
||||
this.checkDataCollection.Text = "Send Anonymous Data";
|
||||
this.checkDataCollection.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// labelDataCollection
|
||||
//
|
||||
this.labelDataCollection.AutoSize = true;
|
||||
this.labelDataCollection.Location = new System.Drawing.Point(3, 41);
|
||||
this.labelDataCollection.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||
this.labelDataCollection.Name = "labelDataCollection";
|
||||
this.labelDataCollection.Size = new System.Drawing.Size(79, 13);
|
||||
this.labelDataCollection.TabIndex = 1;
|
||||
this.labelDataCollection.Text = "Data Collection";
|
||||
//
|
||||
// labelFeedback
|
||||
//
|
||||
this.labelFeedback.AutoSize = true;
|
||||
this.labelFeedback.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.labelFeedback.Location = new System.Drawing.Point(6, 8);
|
||||
this.labelFeedback.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0);
|
||||
this.labelFeedback.Name = "labelFeedback";
|
||||
this.labelFeedback.Size = new System.Drawing.Size(80, 20);
|
||||
this.labelFeedback.TabIndex = 0;
|
||||
this.labelFeedback.Text = "Feedback";
|
||||
//
|
||||
// btnSendFeedback
|
||||
//
|
||||
this.btnSendFeedback.AutoSize = true;
|
||||
this.btnSendFeedback.Location = new System.Drawing.Point(5, 3);
|
||||
this.btnSendFeedback.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||
this.btnSendFeedback.Name = "btnSendFeedback";
|
||||
this.btnSendFeedback.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
this.btnSendFeedback.Size = new System.Drawing.Size(164, 23);
|
||||
this.btnSendFeedback.TabIndex = 0;
|
||||
this.btnSendFeedback.Text = "Send Feedback / Bug Report";
|
||||
this.btnSendFeedback.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// TabSettingsFeedback
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.Controls.Add(this.panelFeedback);
|
||||
this.Controls.Add(this.labelFeedback);
|
||||
this.Name = "TabSettingsFeedback";
|
||||
this.Size = new System.Drawing.Size(340, 122);
|
||||
this.panelFeedback.ResumeLayout(false);
|
||||
this.panelFeedback.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Panel panelFeedback;
|
||||
private System.Windows.Forms.CheckBox checkDataCollection;
|
||||
private System.Windows.Forms.Label labelDataCollection;
|
||||
private System.Windows.Forms.Label labelFeedback;
|
||||
private System.Windows.Forms.ToolTip toolTip;
|
||||
private System.Windows.Forms.LinkLabel labelDataCollectionLink;
|
||||
private System.Windows.Forms.Button btnSendFeedback;
|
||||
}
|
||||
}
|
30
Core/Other/Settings/TabSettingsFeedback.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings{
|
||||
sealed partial class TabSettingsFeedback : BaseTabSettings{
|
||||
public TabSettingsFeedback(){
|
||||
InitializeComponent();
|
||||
|
||||
checkDataCollection.Checked = Config.AllowDataCollection;
|
||||
}
|
||||
|
||||
public override void OnReady(){
|
||||
btnSendFeedback.Click += btnSendFeedback_Click;
|
||||
checkDataCollection.CheckedChanged += checkDataCollection_CheckedChanged;
|
||||
}
|
||||
|
||||
private void btnSendFeedback_Click(object sender, EventArgs e){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe("https://github.com/chylex/TweetDuck/issues/new");
|
||||
}
|
||||
|
||||
private void checkDataCollection_CheckedChanged(object sender, EventArgs e){
|
||||
Config.AllowDataCollection = checkDataCollection.Checked;
|
||||
}
|
||||
|
||||
private void labelDataCollectionLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe("https://github.com/chylex/TweetDuck/wiki/Send-anonymous-data");
|
||||
}
|
||||
}
|
||||
}
|
136
Core/Other/Settings/TabSettingsGeneral.Designer.cs
generated
@@ -25,29 +25,24 @@
|
||||
private void InitializeComponent() {
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.checkExpandLinks = new System.Windows.Forms.CheckBox();
|
||||
this.comboBoxTrayType = new System.Windows.Forms.ComboBox();
|
||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.checkTrayHighlight = new System.Windows.Forms.CheckBox();
|
||||
this.checkSpellCheck = new System.Windows.Forms.CheckBox();
|
||||
this.checkUpdateNotifications = new System.Windows.Forms.CheckBox();
|
||||
this.btnCheckUpdates = new System.Windows.Forms.Button();
|
||||
this.labelZoomValue = new System.Windows.Forms.Label();
|
||||
this.checkSwitchAccountSelectors = new System.Windows.Forms.CheckBox();
|
||||
this.labelTrayIcon = new System.Windows.Forms.Label();
|
||||
this.checkBestImageQuality = new System.Windows.Forms.CheckBox();
|
||||
this.checkOpenSearchInFirstColumn = new System.Windows.Forms.CheckBox();
|
||||
this.trackBarZoom = new System.Windows.Forms.TrackBar();
|
||||
this.labelZoom = new System.Windows.Forms.Label();
|
||||
this.zoomUpdateTimer = new System.Windows.Forms.Timer(this.components);
|
||||
this.labelUI = new System.Windows.Forms.Label();
|
||||
this.panelUI = new System.Windows.Forms.Panel();
|
||||
this.labelTray = new System.Windows.Forms.Label();
|
||||
this.panelUpdates = new System.Windows.Forms.Panel();
|
||||
this.panelTray = new System.Windows.Forms.Panel();
|
||||
this.labelUpdates = new System.Windows.Forms.Label();
|
||||
this.checkBestImageQuality = new System.Windows.Forms.CheckBox();
|
||||
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit();
|
||||
this.panelUI.SuspendLayout();
|
||||
this.panelUpdates.SuspendLayout();
|
||||
this.panelTray.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// checkExpandLinks
|
||||
@@ -62,37 +57,14 @@
|
||||
this.toolTip.SetToolTip(this.checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead.");
|
||||
this.checkExpandLinks.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// comboBoxTrayType
|
||||
//
|
||||
this.comboBoxTrayType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.comboBoxTrayType.FormattingEnabled = true;
|
||||
this.comboBoxTrayType.Location = new System.Drawing.Point(5, 5);
|
||||
this.comboBoxTrayType.Margin = new System.Windows.Forms.Padding(5, 5, 3, 3);
|
||||
this.comboBoxTrayType.Name = "comboBoxTrayType";
|
||||
this.comboBoxTrayType.Size = new System.Drawing.Size(144, 21);
|
||||
this.comboBoxTrayType.TabIndex = 0;
|
||||
this.toolTip.SetToolTip(this.comboBoxTrayType, "Changes behavior of the Tray icon.\r\nRight-click the icon for an action menu.");
|
||||
//
|
||||
// checkTrayHighlight
|
||||
//
|
||||
this.checkTrayHighlight.AutoSize = true;
|
||||
this.checkTrayHighlight.Location = new System.Drawing.Point(6, 56);
|
||||
this.checkTrayHighlight.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
|
||||
this.checkTrayHighlight.Name = "checkTrayHighlight";
|
||||
this.checkTrayHighlight.Size = new System.Drawing.Size(103, 17);
|
||||
this.checkTrayHighlight.TabIndex = 2;
|
||||
this.checkTrayHighlight.Text = "Enable Highlight";
|
||||
this.toolTip.SetToolTip(this.checkTrayHighlight, "Highlights the tray icon if there are new tweets.\r\nOnly works for columns with popup or audio notifications.\r\nThe icon resets when the main window is restored.");
|
||||
this.checkTrayHighlight.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// checkSpellCheck
|
||||
//
|
||||
this.checkSpellCheck.AutoSize = true;
|
||||
this.checkSpellCheck.Location = new System.Drawing.Point(6, 74);
|
||||
this.checkSpellCheck.Location = new System.Drawing.Point(6, 97);
|
||||
this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||
this.checkSpellCheck.Name = "checkSpellCheck";
|
||||
this.checkSpellCheck.Size = new System.Drawing.Size(119, 17);
|
||||
this.checkSpellCheck.TabIndex = 3;
|
||||
this.checkSpellCheck.TabIndex = 4;
|
||||
this.checkSpellCheck.Text = "Enable Spell Check";
|
||||
this.toolTip.SetToolTip(this.checkSpellCheck, "Underlines words that are spelled incorrectly.");
|
||||
this.checkSpellCheck.UseVisualStyleBackColor = true;
|
||||
@@ -123,11 +95,11 @@
|
||||
// labelZoomValue
|
||||
//
|
||||
this.labelZoomValue.BackColor = System.Drawing.Color.Transparent;
|
||||
this.labelZoomValue.Location = new System.Drawing.Point(147, 123);
|
||||
this.labelZoomValue.Location = new System.Drawing.Point(147, 146);
|
||||
this.labelZoomValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
|
||||
this.labelZoomValue.Name = "labelZoomValue";
|
||||
this.labelZoomValue.Size = new System.Drawing.Size(38, 13);
|
||||
this.labelZoomValue.TabIndex = 6;
|
||||
this.labelZoomValue.TabIndex = 7;
|
||||
this.labelZoomValue.Text = "100%";
|
||||
this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
this.toolTip.SetToolTip(this.labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots.");
|
||||
@@ -144,39 +116,53 @@
|
||||
this.toolTip.SetToolTip(this.checkSwitchAccountSelectors, "When (re)tweeting, click to select a single account or hold Shift to\r\nselect multiple accounts, instead of TweetDeck\'s default behavior.");
|
||||
this.checkSwitchAccountSelectors.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// labelTrayIcon
|
||||
// checkBestImageQuality
|
||||
//
|
||||
this.labelTrayIcon.AutoSize = true;
|
||||
this.labelTrayIcon.Location = new System.Drawing.Point(3, 38);
|
||||
this.labelTrayIcon.Margin = new System.Windows.Forms.Padding(3, 9, 3, 0);
|
||||
this.labelTrayIcon.Name = "labelTrayIcon";
|
||||
this.labelTrayIcon.Size = new System.Drawing.Size(52, 13);
|
||||
this.labelTrayIcon.TabIndex = 1;
|
||||
this.labelTrayIcon.Text = "Tray Icon";
|
||||
this.checkBestImageQuality.AutoSize = true;
|
||||
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 74);
|
||||
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||
this.checkBestImageQuality.Name = "checkBestImageQuality";
|
||||
this.checkBestImageQuality.Size = new System.Drawing.Size(114, 17);
|
||||
this.checkBestImageQuality.TabIndex = 3;
|
||||
this.checkBestImageQuality.Text = "Best Image Quality";
|
||||
this.toolTip.SetToolTip(this.checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
|
||||
this.checkBestImageQuality.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// checkOpenSearchInFirstColumn
|
||||
//
|
||||
this.checkOpenSearchInFirstColumn.AutoSize = true;
|
||||
this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 51);
|
||||
this.checkOpenSearchInFirstColumn.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||
this.checkOpenSearchInFirstColumn.Name = "checkOpenSearchInFirstColumn";
|
||||
this.checkOpenSearchInFirstColumn.Size = new System.Drawing.Size(219, 17);
|
||||
this.checkOpenSearchInFirstColumn.TabIndex = 2;
|
||||
this.checkOpenSearchInFirstColumn.Text = "Add Search Columns Before First Column";
|
||||
this.toolTip.SetToolTip(this.checkOpenSearchInFirstColumn, "By default, TweetDeck adds Search columns at the end.\r\nThis option makes them appear before the first column instead.");
|
||||
this.checkOpenSearchInFirstColumn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// trackBarZoom
|
||||
//
|
||||
this.trackBarZoom.AutoSize = false;
|
||||
this.trackBarZoom.BackColor = System.Drawing.SystemColors.Control;
|
||||
this.trackBarZoom.LargeChange = 25;
|
||||
this.trackBarZoom.Location = new System.Drawing.Point(3, 122);
|
||||
this.trackBarZoom.Location = new System.Drawing.Point(3, 145);
|
||||
this.trackBarZoom.Maximum = 200;
|
||||
this.trackBarZoom.Minimum = 50;
|
||||
this.trackBarZoom.Name = "trackBarZoom";
|
||||
this.trackBarZoom.Size = new System.Drawing.Size(148, 30);
|
||||
this.trackBarZoom.SmallChange = 5;
|
||||
this.trackBarZoom.TabIndex = 5;
|
||||
this.trackBarZoom.TabIndex = 6;
|
||||
this.trackBarZoom.TickFrequency = 25;
|
||||
this.trackBarZoom.Value = 100;
|
||||
//
|
||||
// labelZoom
|
||||
//
|
||||
this.labelZoom.AutoSize = true;
|
||||
this.labelZoom.Location = new System.Drawing.Point(3, 106);
|
||||
this.labelZoom.Location = new System.Drawing.Point(3, 129);
|
||||
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||
this.labelZoom.Name = "labelZoom";
|
||||
this.labelZoom.Size = new System.Drawing.Size(34, 13);
|
||||
this.labelZoom.TabIndex = 4;
|
||||
this.labelZoom.TabIndex = 5;
|
||||
this.labelZoom.Text = "Zoom";
|
||||
//
|
||||
// zoomUpdateTimer
|
||||
@@ -199,6 +185,7 @@
|
||||
//
|
||||
this.panelUI.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.panelUI.Controls.Add(this.checkOpenSearchInFirstColumn);
|
||||
this.panelUI.Controls.Add(this.checkBestImageQuality);
|
||||
this.panelUI.Controls.Add(this.checkExpandLinks);
|
||||
this.panelUI.Controls.Add(this.checkSwitchAccountSelectors);
|
||||
@@ -208,85 +195,46 @@
|
||||
this.panelUI.Controls.Add(this.labelZoomValue);
|
||||
this.panelUI.Location = new System.Drawing.Point(9, 31);
|
||||
this.panelUI.Name = "panelUI";
|
||||
this.panelUI.Size = new System.Drawing.Size(322, 157);
|
||||
this.panelUI.Size = new System.Drawing.Size(322, 179);
|
||||
this.panelUI.TabIndex = 1;
|
||||
//
|
||||
// labelTray
|
||||
//
|
||||
this.labelTray.AutoSize = true;
|
||||
this.labelTray.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.labelTray.Location = new System.Drawing.Point(6, 212);
|
||||
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
||||
this.labelTray.Name = "labelTray";
|
||||
this.labelTray.Size = new System.Drawing.Size(96, 20);
|
||||
this.labelTray.TabIndex = 2;
|
||||
this.labelTray.Text = "System Tray";
|
||||
//
|
||||
// panelUpdates
|
||||
//
|
||||
this.panelUpdates.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.panelUpdates.Controls.Add(this.checkUpdateNotifications);
|
||||
this.panelUpdates.Controls.Add(this.btnCheckUpdates);
|
||||
this.panelUpdates.Location = new System.Drawing.Point(9, 358);
|
||||
this.panelUpdates.Location = new System.Drawing.Point(9, 257);
|
||||
this.panelUpdates.Name = "panelUpdates";
|
||||
this.panelUpdates.Size = new System.Drawing.Size(322, 55);
|
||||
this.panelUpdates.TabIndex = 5;
|
||||
//
|
||||
// panelTray
|
||||
//
|
||||
this.panelTray.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.panelTray.Controls.Add(this.checkTrayHighlight);
|
||||
this.panelTray.Controls.Add(this.comboBoxTrayType);
|
||||
this.panelTray.Controls.Add(this.labelTrayIcon);
|
||||
this.panelTray.Location = new System.Drawing.Point(9, 235);
|
||||
this.panelTray.Name = "panelTray";
|
||||
this.panelTray.Size = new System.Drawing.Size(322, 76);
|
||||
this.panelTray.TabIndex = 3;
|
||||
this.panelUpdates.TabIndex = 3;
|
||||
//
|
||||
// labelUpdates
|
||||
//
|
||||
this.labelUpdates.AutoSize = true;
|
||||
this.labelUpdates.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.labelUpdates.Location = new System.Drawing.Point(6, 335);
|
||||
this.labelUpdates.Location = new System.Drawing.Point(6, 234);
|
||||
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
||||
this.labelUpdates.Name = "labelUpdates";
|
||||
this.labelUpdates.Size = new System.Drawing.Size(70, 20);
|
||||
this.labelUpdates.TabIndex = 4;
|
||||
this.labelUpdates.TabIndex = 2;
|
||||
this.labelUpdates.Text = "Updates";
|
||||
//
|
||||
// checkBestImageQuality
|
||||
//
|
||||
this.checkBestImageQuality.AutoSize = true;
|
||||
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 51);
|
||||
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||
this.checkBestImageQuality.Name = "checkBestImageQuality";
|
||||
this.checkBestImageQuality.Size = new System.Drawing.Size(114, 17);
|
||||
this.checkBestImageQuality.TabIndex = 2;
|
||||
this.checkBestImageQuality.Text = "Best Image Quality";
|
||||
this.toolTip.SetToolTip(this.checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
|
||||
this.checkBestImageQuality.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// TabSettingsGeneral
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.Controls.Add(this.labelUpdates);
|
||||
this.Controls.Add(this.panelTray);
|
||||
this.Controls.Add(this.panelUpdates);
|
||||
this.Controls.Add(this.labelTray);
|
||||
this.Controls.Add(this.panelUI);
|
||||
this.Controls.Add(this.labelUI);
|
||||
this.Name = "TabSettingsGeneral";
|
||||
this.Size = new System.Drawing.Size(340, 422);
|
||||
this.Size = new System.Drawing.Size(340, 322);
|
||||
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit();
|
||||
this.panelUI.ResumeLayout(false);
|
||||
this.panelUI.PerformLayout();
|
||||
this.panelUpdates.ResumeLayout(false);
|
||||
this.panelUpdates.PerformLayout();
|
||||
this.panelTray.ResumeLayout(false);
|
||||
this.panelTray.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
@@ -295,10 +243,7 @@
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.CheckBox checkExpandLinks;
|
||||
private System.Windows.Forms.ComboBox comboBoxTrayType;
|
||||
private System.Windows.Forms.ToolTip toolTip;
|
||||
private System.Windows.Forms.Label labelTrayIcon;
|
||||
private System.Windows.Forms.CheckBox checkTrayHighlight;
|
||||
private System.Windows.Forms.CheckBox checkSpellCheck;
|
||||
private System.Windows.Forms.CheckBox checkUpdateNotifications;
|
||||
private System.Windows.Forms.Button btnCheckUpdates;
|
||||
@@ -309,10 +254,9 @@
|
||||
private System.Windows.Forms.CheckBox checkSwitchAccountSelectors;
|
||||
private System.Windows.Forms.Label labelUI;
|
||||
private System.Windows.Forms.Panel panelUI;
|
||||
private System.Windows.Forms.Label labelTray;
|
||||
private System.Windows.Forms.Panel panelUpdates;
|
||||
private System.Windows.Forms.Panel panelTray;
|
||||
private System.Windows.Forms.Label labelUpdates;
|
||||
private System.Windows.Forms.CheckBox checkBestImageQuality;
|
||||
private System.Windows.Forms.CheckBox checkOpenSearchInFirstColumn;
|
||||
}
|
||||
}
|
||||
|
@@ -14,38 +14,27 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
this.updates.CheckFinished += updates_CheckFinished;
|
||||
Disposed += (sender, args) => this.updates.CheckFinished -= updates_CheckFinished;
|
||||
|
||||
comboBoxTrayType.Items.Add("Disabled");
|
||||
comboBoxTrayType.Items.Add("Display Icon Only");
|
||||
comboBoxTrayType.Items.Add("Minimize to Tray");
|
||||
comboBoxTrayType.Items.Add("Close to Tray");
|
||||
comboBoxTrayType.Items.Add("Combined");
|
||||
comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int)Config.TrayBehavior, 0), comboBoxTrayType.Items.Count-1);
|
||||
|
||||
toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue));
|
||||
trackBarZoom.SetValueSafe(Config.ZoomLevel);
|
||||
labelZoomValue.Text = trackBarZoom.Value+"%";
|
||||
|
||||
checkExpandLinks.Checked = Config.ExpandLinksOnHover;
|
||||
checkSwitchAccountSelectors.Checked = Config.SwitchAccountSelectors;
|
||||
checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn;
|
||||
checkBestImageQuality.Checked = Config.BestImageQuality;
|
||||
checkSpellCheck.Checked = Config.EnableSpellCheck;
|
||||
|
||||
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
|
||||
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
|
||||
|
||||
checkUpdateNotifications.Checked = Config.EnableUpdateCheck;
|
||||
}
|
||||
|
||||
public override void OnReady(){
|
||||
checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
|
||||
checkSwitchAccountSelectors.CheckedChanged += checkSwitchAccountSelectors_CheckedChanged;
|
||||
checkOpenSearchInFirstColumn.CheckedChanged += checkOpenSearchInFirstColumn_CheckedChanged;
|
||||
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
|
||||
checkSpellCheck.CheckedChanged += checkSpellCheck_CheckedChanged;
|
||||
trackBarZoom.ValueChanged += trackBarZoom_ValueChanged;
|
||||
|
||||
comboBoxTrayType.SelectedIndexChanged += comboBoxTrayType_SelectedIndexChanged;
|
||||
checkTrayHighlight.CheckedChanged += checkTrayHighlight_CheckedChanged;
|
||||
|
||||
checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged;
|
||||
btnCheckUpdates.Click += btnCheckUpdates_Click;
|
||||
}
|
||||
@@ -62,6 +51,10 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
Config.SwitchAccountSelectors = checkSwitchAccountSelectors.Checked;
|
||||
}
|
||||
|
||||
private void checkOpenSearchInFirstColumn_CheckedChanged(object sender, EventArgs e){
|
||||
Config.OpenSearchInFirstColumn = checkOpenSearchInFirstColumn.Checked;
|
||||
}
|
||||
|
||||
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e){
|
||||
Config.BestImageQuality = checkBestImageQuality.Checked;
|
||||
}
|
||||
@@ -79,15 +72,6 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
}
|
||||
|
||||
private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e){
|
||||
Config.TrayBehavior = (TrayIcon.Behavior)comboBoxTrayType.SelectedIndex;
|
||||
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
|
||||
}
|
||||
|
||||
private void checkTrayHighlight_CheckedChanged(object sender, EventArgs e){
|
||||
Config.EnableTrayHighlight = checkTrayHighlight.Checked;
|
||||
}
|
||||
|
||||
private void checkUpdateNotifications_CheckedChanged(object sender, EventArgs e){
|
||||
Config.EnableUpdateCheck = checkUpdateNotifications.Checked;
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
labelVolumeValue.Text = trackBarVolume.Value+"%";
|
||||
|
||||
tbCustomSound.Text = Config.NotificationSoundPath;
|
||||
tbCustomSound_TextChanged(tbCustomSound, new EventArgs());
|
||||
tbCustomSound_TextChanged(tbCustomSound, EventArgs.Empty);
|
||||
|
||||
Disposed += (sender, args) => soundNotification.Dispose();
|
||||
}
|
||||
|
116
Core/Other/Settings/TabSettingsTray.Designer.cs
generated
Normal file
@@ -0,0 +1,116 @@
|
||||
namespace TweetDuck.Core.Other.Settings {
|
||||
partial class TabSettingsTray {
|
||||
/// <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 Component 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.panelTray = new System.Windows.Forms.Panel();
|
||||
this.checkTrayHighlight = new System.Windows.Forms.CheckBox();
|
||||
this.comboBoxTrayType = new System.Windows.Forms.ComboBox();
|
||||
this.labelTrayIcon = new System.Windows.Forms.Label();
|
||||
this.labelTray = new System.Windows.Forms.Label();
|
||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.panelTray.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// panelTray
|
||||
//
|
||||
this.panelTray.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.panelTray.Controls.Add(this.checkTrayHighlight);
|
||||
this.panelTray.Controls.Add(this.comboBoxTrayType);
|
||||
this.panelTray.Controls.Add(this.labelTrayIcon);
|
||||
this.panelTray.Location = new System.Drawing.Point(9, 31);
|
||||
this.panelTray.Name = "panelTray";
|
||||
this.panelTray.Size = new System.Drawing.Size(322, 76);
|
||||
this.panelTray.TabIndex = 1;
|
||||
//
|
||||
// checkTrayHighlight
|
||||
//
|
||||
this.checkTrayHighlight.AutoSize = true;
|
||||
this.checkTrayHighlight.Location = new System.Drawing.Point(6, 56);
|
||||
this.checkTrayHighlight.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
|
||||
this.checkTrayHighlight.Name = "checkTrayHighlight";
|
||||
this.checkTrayHighlight.Size = new System.Drawing.Size(103, 17);
|
||||
this.checkTrayHighlight.TabIndex = 2;
|
||||
this.checkTrayHighlight.Text = "Enable Highlight";
|
||||
this.toolTip.SetToolTip(this.checkTrayHighlight, "Highlights the tray icon if there are new tweets.\r\nOnly works for columns with popup or audio notifications.\r\nThe icon resets when the main window is restored.");
|
||||
this.checkTrayHighlight.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// comboBoxTrayType
|
||||
//
|
||||
this.comboBoxTrayType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.comboBoxTrayType.FormattingEnabled = true;
|
||||
this.comboBoxTrayType.Location = new System.Drawing.Point(5, 5);
|
||||
this.comboBoxTrayType.Margin = new System.Windows.Forms.Padding(5, 5, 3, 3);
|
||||
this.comboBoxTrayType.Name = "comboBoxTrayType";
|
||||
this.comboBoxTrayType.Size = new System.Drawing.Size(144, 21);
|
||||
this.comboBoxTrayType.TabIndex = 0;
|
||||
this.toolTip.SetToolTip(this.comboBoxTrayType, "Changes behavior of the Tray icon.\r\nRight-click the icon for an action menu.");
|
||||
//
|
||||
// labelTrayIcon
|
||||
//
|
||||
this.labelTrayIcon.AutoSize = true;
|
||||
this.labelTrayIcon.Location = new System.Drawing.Point(3, 38);
|
||||
this.labelTrayIcon.Margin = new System.Windows.Forms.Padding(3, 9, 3, 0);
|
||||
this.labelTrayIcon.Name = "labelTrayIcon";
|
||||
this.labelTrayIcon.Size = new System.Drawing.Size(52, 13);
|
||||
this.labelTrayIcon.TabIndex = 1;
|
||||
this.labelTrayIcon.Text = "Tray Icon";
|
||||
//
|
||||
// labelTray
|
||||
//
|
||||
this.labelTray.AutoSize = true;
|
||||
this.labelTray.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.labelTray.Location = new System.Drawing.Point(6, 8);
|
||||
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0);
|
||||
this.labelTray.Name = "labelTray";
|
||||
this.labelTray.Size = new System.Drawing.Size(96, 20);
|
||||
this.labelTray.TabIndex = 0;
|
||||
this.labelTray.Text = "System Tray";
|
||||
//
|
||||
// TabSettingsTray
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.Controls.Add(this.panelTray);
|
||||
this.Controls.Add(this.labelTray);
|
||||
this.Name = "TabSettingsTray";
|
||||
this.Size = new System.Drawing.Size(340, 119);
|
||||
this.panelTray.ResumeLayout(false);
|
||||
this.panelTray.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Panel panelTray;
|
||||
private System.Windows.Forms.CheckBox checkTrayHighlight;
|
||||
private System.Windows.Forms.ComboBox comboBoxTrayType;
|
||||
private System.Windows.Forms.Label labelTrayIcon;
|
||||
private System.Windows.Forms.Label labelTray;
|
||||
private System.Windows.Forms.ToolTip toolTip;
|
||||
}
|
||||
}
|
33
Core/Other/Settings/TabSettingsTray.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings{
|
||||
sealed partial class TabSettingsTray : BaseTabSettings{
|
||||
public TabSettingsTray(){
|
||||
InitializeComponent();
|
||||
|
||||
comboBoxTrayType.Items.Add("Disabled");
|
||||
comboBoxTrayType.Items.Add("Display Icon Only");
|
||||
comboBoxTrayType.Items.Add("Minimize to Tray");
|
||||
comboBoxTrayType.Items.Add("Close to Tray");
|
||||
comboBoxTrayType.Items.Add("Combined");
|
||||
comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int)Config.TrayBehavior, 0), comboBoxTrayType.Items.Count-1);
|
||||
|
||||
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
|
||||
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
|
||||
}
|
||||
|
||||
public override void OnReady(){
|
||||
comboBoxTrayType.SelectedIndexChanged += comboBoxTrayType_SelectedIndexChanged;
|
||||
checkTrayHighlight.CheckedChanged += checkTrayHighlight_CheckedChanged;
|
||||
}
|
||||
|
||||
private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e){
|
||||
Config.TrayBehavior = (TrayIcon.Behavior)comboBoxTrayType.SelectedIndex;
|
||||
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
|
||||
}
|
||||
|
||||
private void checkTrayHighlight_CheckedChanged(object sender, EventArgs e){
|
||||
Config.EnableTrayHighlight = checkTrayHighlight.Checked;
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,6 +5,7 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Core.Other;
|
||||
|
||||
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){
|
||||
return (ChromiumWebBrowser)browserControl;
|
||||
}
|
||||
|
||||
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;
|
||||
return scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto;
|
||||
|
||||
if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto){
|
||||
return uri.Host == TwitterTrackingUrl ? UrlCheckResult.Tracking : UrlCheckResult.Fine;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return UrlCheckResult.Invalid;
|
||||
}
|
||||
|
||||
public static void OpenExternalBrowser(string url){
|
||||
if (string.IsNullOrWhiteSpace(url))return;
|
||||
|
||||
if (IsValidUrl(url)){
|
||||
OpenExternalBrowserUnsafe(url);
|
||||
}
|
||||
else{
|
||||
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
|
||||
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);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case UrlCheckResult.Invalid:
|
||||
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 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 readonly string[] DictionaryWords = {
|
||||
|
@@ -21,7 +21,7 @@ namespace TweetDuck{
|
||||
public const string BrandName = "TweetDuck";
|
||||
public const string Website = "https://tweetduck.chylex.com";
|
||||
|
||||
public const string VersionTag = "1.9";
|
||||
public const string VersionTag = "1.10";
|
||||
|
||||
public static readonly bool IsPortable = File.Exists("makeportable");
|
||||
|
||||
|
51
README.md
@@ -1,11 +1,56 @@
|
||||
# Build Instructions
|
||||
|
||||
The program was built using Visual Studio 2017. After opening the solution, make sure you have **CefSharp.WinForms** and **Microsoft.VC120.CRT.JetBrains** included - if not, download them using NuGet.
|
||||
### Setup
|
||||
|
||||
The program was built using Visual Studio 2017. Before opening the solution, please make sure you have the following workloads and components installed (optional components that are not listed can be deselected to save space):
|
||||
* **.NET desktop development**
|
||||
* .NET Framework 4 – 4.6 development tools
|
||||
* **Desktop development with C++**
|
||||
* VC++ 2017 v141 toolset
|
||||
|
||||
After opening the solution, download the following NuGet packages by right-clicking on the solution and selecting **Restore NuGet Packages**, or manually running these commands in the **Package Manager Console**:
|
||||
```
|
||||
PM> Install-Package CefSharp.WinForms -Version 57.0.0
|
||||
PM> Install-Package Microsoft.VC120.CRT.JetBrains
|
||||
```
|
||||
|
||||
When building the `Release` configuration, the folder will only contain files intended for distribution (no debug symbols or other unnecessary files). You may package the files yourself, or use `bld/RUN BUILD.bat` to generate installer files using Inno Setup (make sure the Inno Setup binaries are on your PATH).
|
||||
### Debug
|
||||
|
||||
Built files are available in **bin/x86**, installer files are generated in **bld/Output**. If you decide to release a custom version publicly, please make it clear that it is not the original TweetDuck.
|
||||
It is recommended to create a separate data folder for debugging, otherwise you will not be able to run TweetDuck while debugging the solution.
|
||||
|
||||
To do that, open **TweetDuck Properties**, click the **Debug** tab, make sure your **Configuration** is set to `Active (Debug)` (or just `Debug`), and insert this into the **Command line arguments** field:
|
||||
```
|
||||
-datafolder TweetDuckDebug
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
To make a release build of TweetDuck, open **Batch Build**, tick all `Release` configurations except for the `UnitTest` project (otherwise the build will fail), and click **Rebuild**. Check the status bar to make sure it says **Rebuild All succeeded**; if not, open the **Output** view and see which part of the build failed.
|
||||
|
||||
After the build succeeds, the **bin/x86/Release** folder will contain files intended for distribution (no debug symbols or other unnecessary files). You may package these files yourself, or see the [Installers](#Installers) section for automated installer generation.
|
||||
|
||||
If you decide to release a custom version publicly, please make it clear that it is not an official release of TweetDuck.
|
||||
|
||||
### Installers
|
||||
|
||||
TweetDuck uses **Inno Setup** to automate the creation of installers. First, download and install [InnoSetup QuickStart Pack](http://www.jrsoftware.org/isdl.php) (non-unicode; editor and encryption support not required) and the [Inno Download Plugin](https://code.google.com/archive/p/inno-download-plugin).
|
||||
|
||||
Next, add the Inno Setup installation folder (usually `C:\Program Files (x86)\Inno Setup 5`) into your **PATH** environment variable. You may need to restart File Explorer for the change to take place.
|
||||
|
||||
Now you can generate installers after a build by running **bld/RUN BUILD.bat**. Note that despite the name, this will only package the files, you still need to run the [build](#Build) in Visual Studio!
|
||||
|
||||
After the window closes, three installers will be generated inside the **bld/Output** folder:
|
||||
* **TweetDuck.exe**
|
||||
* This is the main installer that creates entries in the Start Menu & Programs and Features, and an optional desktop icon
|
||||
* **TweetDuck.Update.exe**
|
||||
* This is a lightweight update installer that only contains the most important files that usually change across releases
|
||||
* It will automatically download and apply the full installer if the user's current version of CEF does not match (the download link is in `gen_upd.iss` and points to this repository by default)
|
||||
* **TweetDuck.Portable.exe**
|
||||
* This is a portable installer that does not need administrator privileges
|
||||
* It automatically creates a `makeportable` file in the program folder, which forces TweetDuck to run in portable mode
|
||||
|
||||
Note: There is a small chance you will see a resource error when running `RUN BUILD.bat`. If that happens, close the console window (which will terminate all Inno Setup processes and leave corrupted installer files in the output folder), and run it again.
|
||||
|
||||
### Code Notes
|
||||
|
||||
There are many references to the official TweetDuck website and this repository in the code and installers, so if you plan to release your own version, make sure to search for `tweetduck.chylex.com` and `github.com` in the whole repository and replace them appropriately.
|
||||
|
BIN
Resources/Guide/img/app-menu.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
Resources/Guide/img/column-clear-header.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
Resources/Guide/img/column-clear-preferences.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
Resources/Guide/img/column-preferences.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
Resources/Guide/img/icon.ico
Normal file
After Width: | Height: | Size: 100 KiB |
BIN
Resources/Guide/img/new-tweet-emoji.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Resources/Guide/img/new-tweet-pin.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
Resources/Guide/img/new-tweet-template-advanced.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
Resources/Guide/img/new-tweet-template-basic.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
Resources/Guide/img/options-manage-export.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
Resources/Guide/img/options-manage-reset.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
Resources/Guide/img/options-manage.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
Resources/Guide/img/options-notifications-location.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
Resources/Guide/img/options-notifications-size.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
Resources/Guide/img/options-sounds.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
Resources/Guide/img/settings-dropdown.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
Resources/Guide/img/settings-editdesign.png
Normal file
After Width: | Height: | Size: 23 KiB |
403
Resources/Guide/index.html
Normal file
@@ -0,0 +1,403 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>TweetDuck - Guide</title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="robots" content="index,follow">
|
||||
<meta name="author" content="chylex">
|
||||
<meta name="description" content="TweetDuck guide">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="icon" href="img/icon.ico">
|
||||
<link rel="stylesheet" href="style.css" type="text/css">
|
||||
|
||||
<script type="text/javascript" src="script.js" async></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="guide">
|
||||
<section>
|
||||
|
||||
<article>
|
||||
<h2>General</h2>
|
||||
|
||||
<details>
|
||||
<summary id="main-menu">How to open the main menu</summary>
|
||||
<div>
|
||||
<p>The main menu gives you access to the following items:</p>
|
||||
<img src="img/app-menu.png" alt="">
|
||||
<p>There are two ways to open the main menu:</p>
|
||||
<ul>
|
||||
<li>Click <em>Settings</em> in the left panel, then select <em>TweetDuck</em></li>
|
||||
<li><em>Right-click anywhere</em> and you will either see the listed options, or a <em>TweetDuck</em> entry that contains these options</li>
|
||||
</ul>
|
||||
<img src="img/settings-dropdown.png" alt="">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="customize-theme">How to customize the theme and layout</summary>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Click <em>Settings</em> in the left panel</li>
|
||||
<li>Continue to <em>Edit layout & design</em></li>
|
||||
<li>Now you can customize many aspects of the website; note that unlike the default TweetDeck settings, the column width and font size can be configured here in much greater detail</li>
|
||||
</ol>
|
||||
<img src="img/settings-editdesign.png" alt="">
|
||||
<p>This is done using an official plugin called <em>Edit layout & design</em>, which is enabled by default. If the plugin is disabled, you can still access the default TweetDeck settings:</p>
|
||||
<ol>
|
||||
<li>Click <em>Settings</em> on the bottom left</li>
|
||||
<li>Continue to <em>Settings</em> (again)</li>
|
||||
<li>Here you can customize the theme, column width, and font size</li>
|
||||
</ol>
|
||||
<img src="img/settings-dropdown.png" alt="">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="emoji">How to add emoji to tweets</summary>
|
||||
<div>
|
||||
<p>When writing a new tweet, click the heart icon to open an emoji picker. If you're writing a reply, click the <em>Popout</em> icon first to bring the reply into the large panel.</p>
|
||||
<p>Then you can immediately type into the <em>Search</em> field, which accepts keywords separated by space. Pressing <em>Enter</em> in the search field (when not empty) will insert the first result into your tweet. Pressing <em>Escape</em> closes the emoji picker.</p>
|
||||
<p>You can also use your mouse to scroll through the emoji, click on the emoji to insert them, and change the skin tone.</p>
|
||||
<p>Emoji are provided by an official plugin called <em>Emoji keyboard</em>, which is enabled by default. The heart icon will not show if the plugin is disabled.</p>
|
||||
<img src="img/new-tweet-emoji.png" alt="">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="templates">How to use tweet templates</summary>
|
||||
<div>
|
||||
<p>To create a simple template to use when writing a new tweet or reply:</p>
|
||||
<ol>
|
||||
<li>Click <em>Manage templates</em> in the New Tweet panel; if you're writing a reply, click the <em>Popout</em> icon first to bring the reply into the large panel</li>
|
||||
<li>Click <em>New template</em> on the bottom right</li>
|
||||
<li>Fill in the template name and contents</li>
|
||||
<li>Click <em>Confirm</em> to create the template</li>
|
||||
</ol>
|
||||
<p>After you create a template, it will be added to the list. There are two icons next to each entry:</p>
|
||||
<ul>
|
||||
<li>Click the <em>pencil icon to edit</em> the template</li>
|
||||
<li>Click the <em>cross icon to delete</em> the template</li>
|
||||
</ul>
|
||||
<p>To use the template, <em>click the template name</em> to replace your current tweet text with the template, or click while holding <em>Shift</em> to append the template to your tweet instead. You can use the Shift+click functionality to quickly chain multiple templates.</p>
|
||||
<img src="img/new-tweet-template-basic.png" alt="">
|
||||
<p>When writing a template, you can use special <em>tokens</em> listed in the <em>Advanced</em> section. Here is an example of one of the tokens:</p>
|
||||
<img src="img/new-tweet-template-advanced.png" alt="">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="upload-from-clipboard">How to upload images from clipboard</summary>
|
||||
<div>
|
||||
<p>When writing a tweet/reply, press <em>Ctrl+V</em> to upload an image from clipboard. You can use this to quickly paste a selection from an image editor such as Paint, or after copying an image in your browser.</p>
|
||||
<p>Make sure you're in the tweet input field before you press Ctrl+V, otherwise the keyboard shortcut won't trigger.</p>
|
||||
<p>Note that this will only work when your clipboard contains the image itself; it will not work if you copy a file or URL.</p>
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<h2>Columns</h2>
|
||||
|
||||
<details>
|
||||
<summary id="copy-tweet-links">How to copy links to tweets</summary>
|
||||
<div>
|
||||
<p>When you right-click anywhere inside a tweet, you will be given these options:</p>
|
||||
<ul>
|
||||
<li>Open tweet in browser</li>
|
||||
<li><em>Copy tweet address</em></li>
|
||||
<li>Screenshot tweet to clipboard</li>
|
||||
</ul>
|
||||
<p>If the tweet contains a quote, you will also be able to directly open the quote or copy its address.</p>
|
||||
<p>Note that these options will not appear when you right-click a private message.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="download-media">How to download images and videos</summary>
|
||||
<div>
|
||||
<p>When you right-click an image or a video thumbnail with a purple play button, you will be given these options:</p>
|
||||
<ul>
|
||||
<li>Open image/video in browser</li>
|
||||
<li>Copy image/video address</li>
|
||||
<li><em>Save image/video as...</em></li>
|
||||
</ul>
|
||||
<p>TweetDuck will attempt to fetch the highest quality image when you click any of these options. You can disable that by going to the <em>main menu</em>, selecting <em>Options</em>, and then unchecking <em>Best Image Quality</em>.</p>
|
||||
<p>Whenever possible, the username and quality are included in the filename by default, for convenience. After you select a folder and click Save, the image/video will be downloaded in the background. There is no notification for when the download finishes, but you will be notified if it fails.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="screenshot-tweets">How to take screenshots of individual tweets</summary>
|
||||
<div>
|
||||
<p>When you right-click anywhere inside a tweet, you will be given these options:</p>
|
||||
<ul>
|
||||
<li>Open tweet in browser</li>
|
||||
<li>Copy tweet address</li>
|
||||
<li><em>Screenshot tweet to clipboard</em></li>
|
||||
</ul>
|
||||
<p>Taking a screenshot may take several seconds, especially if it contains large images or previews. After taking a screenshot, you can paste it into an image editor such as Paint.</p>
|
||||
<p>Note that these options will not appear when you right-click a private message.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="clear-columns">How to clear and restore column contents</summary>
|
||||
<div>
|
||||
<p>TweetDeck lets you clear a column, which can help keep things organized by hiding tweets you have already read. To clear a column, click the <em>slider icon</em> on top of the column, and then click the <em>Clear</em> button.</p>
|
||||
<p>If you need to revert this decision and restore a column, normally you would need to delete and re-create it, but in TweetDuck you can simply hold Shift which turns the Clear button into a <em>Restore</em> button.</p>
|
||||
<img src="img/column-clear-preferences.png" alt="">
|
||||
<p>If you clear columns frequently, you can enable an official plugin that lets you clear columns much quicker:</p>
|
||||
<ol>
|
||||
<li>Open the <em>main menu</em>, and select <em>Plugins</em> to open the list of available plugins</li>
|
||||
<li>Find an entry that says <em>Clear columns</em>, it will be somewhere near the bottom as the plugin is disabled by default</li>
|
||||
<li>Click <em>Enable</em> on the right side to enable the plugin and reload the browser</li>
|
||||
</ol>
|
||||
<p>Now you can clear...</p>
|
||||
<ul>
|
||||
<li>...a single column by clicking the <em>droplet icon</em> on top of each column</li>
|
||||
<li>...a single column by holding the <em>1-9 number key</em> and pressing <em>Delete</em></li>
|
||||
<li>...all columns by clicking <em>Clear columns</em> in the left panel</li>
|
||||
<li>...all columns by pressing <em>Alt+Delete</em></li>
|
||||
</ul>
|
||||
<img src="img/column-clear-header.png" alt="">
|
||||
<p>As mentioned before, hold Shift with any of these to restore the columns instead. Note that some keyboard layouts require using the Shift key when pressing number keys; if that is the case for you, please use the numpad or mouse instead.</p>
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<h2>Notifications</h2>
|
||||
|
||||
<details>
|
||||
<summary id="enable-notifications">How to enable desktop or sound notifications</summary>
|
||||
<div>
|
||||
<p>New columns have disabled notifications by default. To enable them, click the <em>slider icon</em> on top of the column, and expand the <em>Preferences</em> section.</p>
|
||||
<p>Now you can toggle either, or both of the notification options:</p>
|
||||
<img src="img/column-preferences.png" alt="">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="move-resize-notifications">How to move or resize desktop notifications</summary>
|
||||
<div>
|
||||
<p>Open the <em>main menu</em>, select <em>Options</em>, and then click the <em>Notifications</em> tab. Here, you can customize many aspects of desktop notifications.</p>
|
||||
<p>Scroll down to the <em>Location</em> section where you can customize where the notification shows up. You can either pick one of the 4 corners of your screen and the distance from the corner, or select <em>Custom</em> and then you'll be able to freely move the example notification window.</p>
|
||||
<img src="img/options-notifications-location.png" alt="">
|
||||
<p>Scroll down to the <em>Size</em> section to customize the size of the notification window. By default, TweetDuck sets the size based on your font size setting, the zoom level you can customize in the General tab, and your system DPI. If you pick <em>Custom</em>, you will be able to freely resize the example notification window.</p>
|
||||
<img src="img/options-notifications-size.png" alt="">
|
||||
<p>Note that moving and resizing the notification only works while you're inside the Options dialog, that is to prevent accidental clicks messing up your settings.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="sound-notifications">How to customize sound notifications</summary>
|
||||
<div>
|
||||
<p>Open the <em>main menu</em>, select <em>Options</em>, and then click the <em>Sounds</em> tab. Here, you can pick a sound file that will be used instead of the default TweetDeck sound notification.</p>
|
||||
<p>Keep in mind that you're only linking to the sound file, so make sure not to delete the file afterwards, otherwise TweetDuck won't find it anymore.</p>
|
||||
<img src="img/options-sounds.png" alt="">
|
||||
<p>If you're unable to select MP3 files or other common audio file types, please ensure that you have Windows Media Player installed on your system, otherwise you will only be able to select basic WAV files.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="mute-notifications">How to temporarily mute all notifications</summary>
|
||||
<div>
|
||||
<p>There are two ways you can mute/unmute notifications:</p>
|
||||
<ul>
|
||||
<li>Open the <em>main menu</em> and click <em>Mute notifications</em></li>
|
||||
<li>If you've enabled the <em>tray icon</em>, right-click it and then click <em>Mute notifications</em></li>
|
||||
</ul>
|
||||
<p>The option persists across restarts – if you mute notifications and then restart TweetDuck, don't forget to unmute the notifications again.</p>
|
||||
<p>Unmuting notifications will display all missed desktop notifications (unless TweetDuck was restarted in the meantime).</p>
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
|
||||
</section>
|
||||
<section>
|
||||
|
||||
<article>
|
||||
<h2>Options</h2>
|
||||
|
||||
<details>
|
||||
<summary id="configure-tweetduck">How to configure TweetDuck</summary>
|
||||
<div>
|
||||
<p>Open the <em>main menu</em> and select <em>Options</em>. Here you can configure various parts of TweetDuck; the dialog is split into several tabs:</p>
|
||||
<ul>
|
||||
<li><em>General</em> tab for user interface, zoom, and update options</li>
|
||||
<li><em>System Tray</em> tab to enable and configure the tray icon</li>
|
||||
<li><em>Notifications</em> tab to configure desktop notifications</li>
|
||||
<li><em>Sounds</em> tab to set a custom sound notification</li>
|
||||
<li><em>Advanced</em> tab for highly technical options</li>
|
||||
</ul>
|
||||
<p>You can move your cursor over most options to display a <em>tooltip with a detailed explanation</em> of what that option does.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="manage-plugins">How to view and manage plugins</summary>
|
||||
<div>
|
||||
<p>TweetDuck has several offical plugins that extend the website and notifications with new functionality.</p>
|
||||
<p>Open the <em>main menu</em> and select <em>Plugins</em> to open the plugin list. Here you can see what each plugin does, and enable/disable them individually. </p>
|
||||
<p>If you want to install a custom plugin, click <em>Open Plugin Folder</em>. A plugin is a folder that contains a <em>.meta</em> file and several others, make sure you copy and paste the folder itself into the opened plugin folder. To verify that you installed it correctly, click <em>Reload All</em> (which also reloads the website) and the plugin should appear.</p>
|
||||
<p>Please, be careful when installing new plugins, and ensure that you get them from trustworthy sources. If you're unsure about a plugin, feel free to <a href="https://github.com/chylex/TweetDuck/issues/new" rel="nofollow">create an issue</a> and upload the plugin there (<a href="https://github.com" rel="nofollow">GitHub</a> account required).</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="manage-profile">How to backup your profile or move it to another computer</summary>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Open the <em>main menu</em>, select <em>Options</em>, and click <em>Manage Options</em> on the bottom left</li>
|
||||
<li>Select <em>Export profile</em> and proceed with <em>Next</em></li>
|
||||
<li>Select items you want to save in your profile (note that <em>Plugin Data</em> includes data from official plugins, such as those that let you customize the website or create tweet templates)</li>
|
||||
<li>Click <em>Export Profile</em></li>
|
||||
</ol>
|
||||
<img src="img/options-manage.png" alt="" style="margin-right:6px">
|
||||
<img src="img/options-manage-export.png" alt="">
|
||||
<p>You can save your profile into a cloud storage (Dropbox, Google Drive, etc.) or an external drive, for example. When you want to restore it, follow the same steps but select <em>Import profile</em> and then select the file instead.</p>
|
||||
<p>When importing a profile, you will be again able to pick which items you want to restore. You can for example export a full profile including your login session, but then only import program options and plugin data if you want to login to a different account.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="restore-options">How to restore default options</summary>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Open the <em>main menu</em>, select <em>Options</em>, and click <em>Manage Options</em> on the bottom left</li>
|
||||
<li>Select <em>Restore defaults</em> and proceed with <em>Next</em></li>
|
||||
<li>Select items you want reset (note that <em>Plugin Data</em> includes data from official plugins, such as those that let you customize the website or create tweet templates)</li>
|
||||
<li>Click <em>Restore Defaults</em></li>
|
||||
</ol>
|
||||
<img src="img/options-manage.png" alt="" style="margin-right:6px">
|
||||
<img src="img/options-manage-reset.png" alt="">
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<h2>Efficiency</h2>
|
||||
|
||||
<details>
|
||||
<summary id="keyboard-shortcuts">How to use keyboard shortcuts</summary>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Click <em>Settings</em> in the left panel</li>
|
||||
<li>Continue to <em>Keyboard shortcuts</em></li>
|
||||
<li>Here you can see most available keyboard shortcuts you can use in the browser window</li>
|
||||
</ol>
|
||||
<img src="img/settings-dropdown.png" alt="">
|
||||
<p>You can also often hold Ctrl or Shift to trigger alternative actions:</p>
|
||||
<ul>
|
||||
<li>When <em>selecting accounts</em>, hold <em>Shift</em> to select multiple accounts (can be configured in the Options)</li>
|
||||
<li>When <em>clearing columns</em>, hold <em>Shift</em> to restore the column instead</li>
|
||||
<li>When <em>clicking video thumbnails</em>, hold <em>Ctrl</em> to open them in your browser</li>
|
||||
</ul>
|
||||
<p>Finally, if you click into a desktop notification window, you can use these keyboard shortcuts:</p>
|
||||
<ul>
|
||||
<li><em>Enter</em> to skip the current notification</li>
|
||||
<li><em>Escape</em> to close the window (skips all notifications in the queue)</li>
|
||||
<li><em>Space</em> to pause/unpause the timer</li>
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="extra-mouse-buttons">How to use the forward / back mouse buttons</summary>
|
||||
<div>
|
||||
<p>If you have a mouse that supports the forward and back buttons, you can use them in both the browser and a desktop notification. All you need to do is move the cursor over the window (even if it's not focused), and press one of the buttons.</p>
|
||||
<p>In the browser:</p>
|
||||
<ul>
|
||||
<li>Press <em>forward</em> over a <em>tweet to open it in detail view</em> (unlike clicking, this will not trigger links or media thumbnails)</li>
|
||||
<li>Press <em>back</em> anywhere to <em>close modal dialogs</em> or the <em>New Tweet panel</em>, or over a <em>column to return back from detail view</em> (if there are no dialogs or panels open, pressing the button outside a column will trigger it for all columns at once)</li>
|
||||
</ul>
|
||||
<p>In the desktop notification:</p>
|
||||
<ul>
|
||||
<li>Press <em>forward</em> to skip the current notification</li>
|
||||
<li>Press <em>back</em> to close the window (skips all notifications in the queue)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="pin-new-tweet">How to keep the New Tweet panel open</summary>
|
||||
<div>
|
||||
<p>Open the New Tweet panel, and click the <em>pin icon</em> on top. When the pin points to the left, the panel will stay open after tweeting or restarting TweetDuck.</p>
|
||||
<img src="img/new-tweet-pin.png" alt="">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="popout-replies">How to instantly popout replies</summary>
|
||||
<div>
|
||||
<p><em>Middle-click the reply icon</em> to instantly open your reply in the New Tweet panel.</p>
|
||||
<p>Middle-clicks are usually done by pressing your mouse wheel as if it was a button. When using a laptop touchpad or certain mice, the ways of triggering a middle click vary.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="reply-account">How to change which account will be pre-selected for replies</summary>
|
||||
<div>
|
||||
<p>By default, TweetDeck pre-selects the account mentioned in the column header. The ability to change this is provided by an official plugin which is disabled by default, as it's a bit more difficult to setup, but it can be very powerful. To enable the plugin:</p>
|
||||
<ol>
|
||||
<li>Open the <em>main menu</em> and select <em>Plugins</em></li>
|
||||
<li>Find an entry that says <em>Custom reply account</em>, it will be somewhere near the bottom as the plugin is disabled by default</li>
|
||||
<li>Click <em>Enable</em> on the right side to enable the plugin</li>
|
||||
</ol>
|
||||
<p>After you enable the plugin, it will use your preferred account for all replies by default. If that's your intention, you can simply enable the plugin and leave it, otherwise continue reading:</p>
|
||||
<ol>
|
||||
<li>Click <em>Configure</em> next to the plugin to open a folder with the configuration file
|
||||
<li>Open <em>configuration.js</em> in a text editor that can edit and save JavaScript or any pure text files, therefore office suits or WordPad are not suitable; if you don't have any specific editor, use Notepad.</li>
|
||||
<li>The configuration file includes very detailed instructions – you can use one of the <em>presets</em>, a <em>specific account</em> for all replies, or use JavaScript to <em>fully customize</em> the reply behavior</li>
|
||||
</ol>
|
||||
<p>After editing the configuration, return back to Plugins and click <em>Reload All</em> on the bottom left. Now you can close Plugins and test if replies work the way you want.</p>
|
||||
<p>Note that this will not affect the Messages column, that one will always pre-select the account which received the private message.</p>
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<h2>Advanced</h2>
|
||||
|
||||
<details>
|
||||
<summary id="dev-tools">How to open Chrome Dev Tools</summary>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Open the <em>main menu</em>, select <em>Options</em>, and click the <em>Advanced</em> tab</li>
|
||||
<li>Click <em>Open Program Folder</em></li>
|
||||
<li>Download <a href="https://github.com/chylex/TweetDuck/raw/master/bld/Resources/devtools_resources.pak" rel="nofollow">devtools_resources.pak</a> and place it into the opened folder</li>
|
||||
<li>Click <em>Restart the Program</em></li>
|
||||
<li>Now, open the <em>main menu</em> again and you should see <em>Open dev tools</em>; you can also right-click inside a notification and see the same option (make sure to pause the notification first by clicking Freeze in the context menu)</li>
|
||||
</ol>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="custom-css">How to customize styles using CSS</summary>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Open the <em>main menu</em>, select <em>Options</em>, and click the <em>Advanced</em> tab</li>
|
||||
<li>Click <em>Edit CSS</em></li>
|
||||
</ol>
|
||||
<p>Now you can write custom CSS into the <em>Browser</em> and <em>Notification</em> sections.</p>
|
||||
<p>Note that the Browser section will immediately take effect as you type. You can also still access the browser and Dev Tools, as the dialog does not block the browser window.</p>
|
||||
<p>Basic knowledge of HTML and CSS is recommended. Mozilla Development Network has a huge library of resources on both <a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/HTML_basics" rel="nofollow">HTML</a> and <a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/CSS_basics" rel="nofollow">CSS</a>.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="plugin-development">How to develop plugins</summary>
|
||||
<div>
|
||||
<p>Before creating a plugin, you should have at least basic knowledge of <a href="https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web" rel="nofollow">web development</a> (namely HTML, CSS, JavaScript), and several JS libraries TweetDeck uses, such as <a href="https://learn.jquery.com" rel="nofollow">jQuery 2</a>, <a href="https://mustache.github.io" rel="nofollow">Mustache</a>, <a href="https://github.com/ded/klass" rel="nofollow">klass</a>, and <a href="https://flightjs.github.io/" rel="nofollow">Flight</a>.</p>
|
||||
<p>Working with the TweetDeck source code involves a lot of reverse-engineering. You can visit <a href="https://github.com/DeckHack/discoveries" rel="nofollow">DeckHack</a> which is working to document its source code, and view <a href="https://github.com/chylex/TweetDuck/tree/master/Resources/Scripts" rel="nofollow">TweetDuck sources</a> which includes all scripts and official plugins.</p>
|
||||
<p>Once you're ready to start creating your own plugins, visit the official <a href="https://github.com/chylex/TweetDuck/wiki/Plugins%EA%9E%89-1.-The-Basics" rel="nofollow">plugin development documentation</a> which will explain the structure of a plugin, and show you all TweetDuck-specific functionality you cannot normally use in browsers.</p>
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
84
Resources/Guide/script.js
Normal file
@@ -0,0 +1,84 @@
|
||||
var init = function(){
|
||||
if (!("open" in document.getElementsByTagName("details")[0])){
|
||||
var elements = document.getElementsByTagName("details");
|
||||
|
||||
var onClick = function(e){
|
||||
var summary = e.target;
|
||||
var parent = e.target.parentElement;
|
||||
var contents = e.target.nextElementSibling;
|
||||
|
||||
if (parent.hasAttribute("open")){
|
||||
parent.removeAttribute("open");
|
||||
summary.setAttribute("aria-expanded", "false");
|
||||
contents.setAttribute("aria-hidden", "true");
|
||||
contents.style.display = "none";
|
||||
}
|
||||
else{
|
||||
parent.setAttribute("open", "");
|
||||
summary.setAttribute("aria-expanded", "true");
|
||||
contents.setAttribute("aria-hidden", "false");
|
||||
contents.style.display = "block";
|
||||
}
|
||||
};
|
||||
|
||||
var onKey = function(e){
|
||||
if (e.keyCode === 13 || e.keyCode === 32){
|
||||
onClick(e);
|
||||
}
|
||||
};
|
||||
|
||||
for(var index = 0; index < elements.length; index++){
|
||||
var ele = elements[index];
|
||||
|
||||
if (ele.childElementCount === 2){
|
||||
var summary = ele.children[0];
|
||||
var contents = ele.children[1];
|
||||
|
||||
ele.style.display = "block";
|
||||
ele.setAttribute("role", "group");
|
||||
|
||||
summary.setAttribute("role", "button");
|
||||
summary.setAttribute("aria-expanded", "false");
|
||||
summary.setAttribute("tabindex", 0);
|
||||
summary.addEventListener("click", onClick);
|
||||
summary.addEventListener("keydown", onKey);
|
||||
|
||||
contents.setAttribute("aria-hidden", "true");
|
||||
contents.style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ("WebkitAppearance" in document.documentElement.style){
|
||||
var elements = document.getElementsByTagName("summary");
|
||||
|
||||
var onMouseDown = function(e){
|
||||
e.target.classList.add("webkit-workaround");
|
||||
};
|
||||
|
||||
var onMouseUp = function(e){
|
||||
e.target.classList.remove("webkit-workaround");
|
||||
e.target.blur();
|
||||
};
|
||||
|
||||
for(var index = 0; index < elements.length; index++){
|
||||
elements[index].addEventListener("mousedown", onMouseDown);
|
||||
elements[index].addEventListener("mouseup", onMouseUp);
|
||||
}
|
||||
}
|
||||
|
||||
if (location.hash.length > 1){
|
||||
var element = document.getElementById(location.hash.substring(1));
|
||||
|
||||
if (element && element.tagName === "SUMMARY"){
|
||||
element.click();
|
||||
element.scrollIntoView(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (document.readyState !== "loading"){
|
||||
init();
|
||||
}
|
||||
else{
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
}
|
132
Resources/Guide/style.css
Normal file
@@ -0,0 +1,132 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, Verdana, sans-serif;
|
||||
background-color: #222;
|
||||
color: #ddd;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#guide {
|
||||
width: 100%;
|
||||
max-width: 1440px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
#guide section {
|
||||
flex: 1 1 0;
|
||||
margin: 0 24px;
|
||||
min-width: 360px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media(max-width: 408px) {
|
||||
#guide section {
|
||||
min-width: calc(100vw - 48px);
|
||||
}
|
||||
}
|
||||
|
||||
#guide h2 {
|
||||
margin: 20px 0 10px;
|
||||
font-size: 32px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#guide details {
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
#guide details[open] {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
#guide summary {
|
||||
display: inline-block;
|
||||
margin: 0 0 11px;
|
||||
padding: 4px 10px;
|
||||
font-size: 19px;
|
||||
font-weight: bold;
|
||||
color: #afdfff;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
#guide summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#guide summary.webkit-workaround {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#guide summary::before {
|
||||
content: "▶";
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
vertical-align: top;
|
||||
margin-right: 6px;
|
||||
padding-top: 2px;
|
||||
transform-origin: 35% 50%;
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
#guide summary:hover, #guide details[open] summary {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#guide details[open] summary::before {
|
||||
transform: rotateZ(90deg) translate(0.5px, -3px);
|
||||
}
|
||||
|
||||
#guide details > div {
|
||||
margin-bottom: 22px;
|
||||
padding: 0 25px 20px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
#guide details > div > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#guide details > div > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
em {
|
||||
color: #f8d88b;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #8bc6f8;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
p, li {
|
||||
line-height: 130%;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 26px;
|
||||
}
|
||||
|
||||
ol {
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 2px groove rgba(255, 255, 255, 0.6);
|
||||
max-width: 100%;
|
||||
}
|
@@ -8,7 +8,7 @@ Edit layout & design
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.1.2
|
||||
1.1.3
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
@@ -108,7 +108,7 @@ enabled(){
|
||||
let menu = $(".js-dropdown-content").children("ul").first();
|
||||
return if menu.length === 0;
|
||||
|
||||
let itemTD = menu.children("[data-std]").first();
|
||||
let itemTD = menu.children("[data-tweetduck]").first();
|
||||
return if itemTD.length === 0;
|
||||
|
||||
if (!itemTD.prev().hasClass("drp-h-divider")){
|
||||
@@ -116,7 +116,7 @@ enabled(){
|
||||
}
|
||||
|
||||
let itemEditDesign = $('<li class="is-selectable"><a href="#" data-action>Edit layout & design</a></li>');
|
||||
itemTD.after(itemEditDesign);
|
||||
itemEditDesign.insertAfter(itemTD);
|
||||
|
||||
itemEditDesign.on("click", "a", this.openEditDesignDialog);
|
||||
|
||||
@@ -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(".avatar { border-radius: "+this.config.avatarRadius+"% !important }");
|
||||
|
||||
let notificationScrollbarColor = null;
|
||||
|
||||
if (this.config.themeColorTweaks){
|
||||
switch(TD.settings.getTheme()){
|
||||
case "dark":
|
||||
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:hover { opacity: 1 }");
|
||||
this.css.insert(".scroll-styled-v:not(.scroll-alt)::-webkit-scrollbar-thumb, .scroll-styled-h:not(.scroll-alt)::-webkit-scrollbar-thumb { background-color: #666 }");
|
||||
notificationScrollbarColor = "666";
|
||||
break;
|
||||
|
||||
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:not(.scroll-alt)::-webkit-scrollbar-thumb, .scroll-styled-h:not(.scroll-alt)::-webkit-scrollbar-thumb { background-color: #d2d6da }");
|
||||
this.css.insert(".app-columns-container.scroll-styled-h::-webkit-scrollbar-thumb:not(:hover) { background-color: #a5aeb5 }");
|
||||
notificationScrollbarColor = "a5aeb5";
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -460,6 +465,9 @@ enabled(){
|
||||
.drawer .btn .icon, .app-header .btn .icon { line-height: 1em !important }
|
||||
.column-header .column-type-icon { bottom: 26px !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 }`;
|
||||
|
||||
document.head.appendChild(this.icons);
|
||||
@@ -511,6 +519,10 @@ ${this.config.revertIcons ? `
|
||||
.icon-user-filled:before{content:"\\f035";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>`);
|
||||
};
|
||||
|
||||
|
@@ -9,7 +9,7 @@ Emoji keyboard
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.3.1
|
||||
1.4
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
@@ -66,6 +66,7 @@ enabled(){
|
||||
this.currentSpanner = null;
|
||||
|
||||
var wasSearchFocused = false;
|
||||
var lastEmojiKeyword, lastEmojiPosition, lastEmojiLength;
|
||||
|
||||
var hideKeyboard = (refocus) => {
|
||||
$(this.currentKeyboard).remove();
|
||||
@@ -81,8 +82,14 @@ enabled(){
|
||||
$(".emoji-keyboard-popup-btn").removeClass("is-selected");
|
||||
|
||||
if (refocus){
|
||||
$(".js-compose-text", ".js-docked-compose").focus();
|
||||
this.composeInput.focus();
|
||||
|
||||
if (lastEmojiKeyword && lastEmojiPosition === 0){
|
||||
document.execCommand("insertText", false, lastEmojiKeyword);
|
||||
}
|
||||
}
|
||||
|
||||
lastEmojiKeyword = null;
|
||||
};
|
||||
|
||||
var generateEmojiHTML = skinTone => {
|
||||
@@ -229,7 +236,7 @@ enabled(){
|
||||
};
|
||||
|
||||
var insertEmoji = (src, alt) => {
|
||||
let input = $(".js-compose-text", ".js-docked-compose");
|
||||
let input = this.composeInput;
|
||||
|
||||
let val = input.val();
|
||||
let posStart = input[0].selectionStart;
|
||||
@@ -241,6 +248,8 @@ enabled(){
|
||||
input[0].selectionStart = posStart+alt.length;
|
||||
input[0].selectionEnd = posStart+alt.length;
|
||||
|
||||
lastEmojiKeyword = null;
|
||||
|
||||
if (wasSearchFocused){
|
||||
$(".emoji-keyboard-search").children("input").focus();
|
||||
}
|
||||
@@ -270,6 +279,92 @@ enabled(){
|
||||
}
|
||||
};
|
||||
|
||||
this.composeInputKeyDownEvent = function(e){
|
||||
if (lastEmojiKeyword && (e.keyCode === 8 || e.keyCode === 27)){ // backspace, escape
|
||||
let ele = $(this)[0];
|
||||
|
||||
if (ele.selectionStart === lastEmojiPosition){
|
||||
ele.selectionStart -= lastEmojiLength; // selects the emoji
|
||||
document.execCommand("insertText", false, lastEmojiKeyword);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
lastEmojiKeyword = null;
|
||||
}
|
||||
};
|
||||
|
||||
this.composeInputKeyPressEvent = function(e){
|
||||
if (String.fromCharCode(e.which) === ':'){
|
||||
let ele = $(this);
|
||||
let val = ele.val();
|
||||
|
||||
let firstColon = val.lastIndexOf(':', ele[0].selectionStart);
|
||||
return if firstColon === -1;
|
||||
|
||||
let search = val.substring(firstColon+1, ele[0].selectionStart);
|
||||
return if !/^[a-z_]+$/i.test(search);
|
||||
|
||||
let keywords = search.split("_").filter(kw => kw.length > 0).map(kw => kw.toLowerCase());
|
||||
return if keywords.length === 0;
|
||||
|
||||
let foundName = me.emojiNames.filter(name => keywords.every(kw => name.includes(kw)));
|
||||
|
||||
if (foundName.length === 0){
|
||||
return;
|
||||
}
|
||||
|
||||
lastEmojiKeyword = `:${search}:`;
|
||||
lastEmojiPosition = lastEmojiLength = 0;
|
||||
|
||||
if (foundName.length === 1){
|
||||
let foundIndex = me.emojiNames.indexOf(foundName[0]);
|
||||
let foundEmoji;
|
||||
|
||||
for(let array of [ me.emojiData1, me.emojiData2[me.selectedSkinTone], me.emojiData3 ]){
|
||||
let realArray = array.filter(ele => ele !== "___");
|
||||
|
||||
if (foundIndex >= realArray.length){
|
||||
foundIndex -= realArray.length;
|
||||
}
|
||||
else{
|
||||
foundEmoji = realArray[foundIndex];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundEmoji){
|
||||
e.preventDefault();
|
||||
|
||||
ele.val(val.substring(0, firstColon)+foundEmoji+val.substring(ele[0].selectionStart));
|
||||
ele[0].selectionEnd = ele[0].selectionStart = firstColon+foundEmoji.length;
|
||||
ele.trigger("change");
|
||||
ele.focus();
|
||||
|
||||
lastEmojiPosition = firstColon+foundEmoji.length;
|
||||
lastEmojiLength = foundEmoji.length;
|
||||
}
|
||||
}
|
||||
else if (foundName.length > 1){
|
||||
e.preventDefault();
|
||||
ele.val(val.substring(0, firstColon)+val.substring(ele[0].selectionStart));
|
||||
ele[0].selectionEnd = ele[0].selectionStart = firstColon;
|
||||
ele.trigger("change");
|
||||
|
||||
if (!me.currentKeyboard){
|
||||
$(".emoji-keyboard-popup-btn").click();
|
||||
}
|
||||
|
||||
$(".emoji-keyboard-search").children("input").focus().val("");
|
||||
document.execCommand("insertText", false, keywords.join(" "));
|
||||
}
|
||||
}
|
||||
else{
|
||||
lastEmojiKeyword = null;
|
||||
}
|
||||
};
|
||||
|
||||
this.composeInputFocusEvent = function(e){
|
||||
wasSearchFocused = false;
|
||||
};
|
||||
@@ -300,15 +395,18 @@ enabled(){
|
||||
|
||||
ready(){
|
||||
this.composeDrawer = $("[data-drawer='compose']");
|
||||
this.composeInput = $(".js-compose-text", ".js-docked-compose");
|
||||
|
||||
this.composePanelScroller = $(".js-compose-scroller", ".js-docked-compose").first().children().first();
|
||||
this.composePanelScroller.on("scroll", this.composerScrollEvent);
|
||||
|
||||
$(".emoji-keyboard-popup-btn").on("click", this.emojiKeyboardButtonClickEvent);
|
||||
$(".js-compose-text", ".js-docked-compose").on("focus", this.composeInputFocusEvent);
|
||||
$(document).on("click", this.documentClickEvent);
|
||||
$(document).on("keydown", this.documentKeyEvent);
|
||||
$(document).on("uiComposeImageAdded", this.uploadFilesEvent);
|
||||
this.composeInput.on("keydown", this.composeInputKeyDownEvent);
|
||||
this.composeInput.on("keypress", this.composeInputKeyPressEvent);
|
||||
this.composeInput.on("focus", this.composeInputFocusEvent);
|
||||
this.composeDrawer.on("uiComposeTweetSending", this.composerSendingEvent);
|
||||
|
||||
// HTML generation
|
||||
@@ -429,10 +527,12 @@ disabled(){
|
||||
$(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonClickEvent);
|
||||
$(".emoji-keyboard-popup-btn").remove();
|
||||
|
||||
$(".js-compose-text", ".js-docked-compose").off("focus", this.composeInputFocusEvent);
|
||||
$(document).off("click", this.documentClickEvent);
|
||||
$(document).off("keydown", this.documentKeyEvent);
|
||||
$(document).off("uiComposeImageAdded", this.uploadFilesEvent);
|
||||
this.composeInput.off("keydown", this.composeInputKeyDownEvent);
|
||||
this.composeInput.off("keypress", this.composeInputKeyPressEvent);
|
||||
this.composeInput.off("focus", this.composeInputFocusEvent);
|
||||
this.composeDrawer.off("uiComposeTweetSending", this.composerSendingEvent);
|
||||
|
||||
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
|
||||
|
@@ -8,7 +8,7 @@ Custom reply account
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.2.1
|
||||
1.2.2
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
@@ -1,12 +1,4 @@
|
||||
{
|
||||
/*
|
||||
* WARNING
|
||||
* -------
|
||||
*
|
||||
* Make sure you are editing 'configuration.js' and not the default configuration file, as the default one will be replaced with each update.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Simple way of configuring the plugin
|
||||
* ------------------------------------
|
||||
|
@@ -11,12 +11,22 @@ function Rewrite-File{
|
||||
Write-Host "Processed" $file.FullName.Substring($dir.Length)
|
||||
}
|
||||
|
||||
ForEach($file in Get-ChildItem -Include *.js -Recurse){
|
||||
ForEach($file in Get-ChildItem -Include *.js -Exclude configuration.default.js -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
$lines = ($lines | % { $_.TrimStart() }) -Replace '^(.*?)((?<=^|[;{}()])\s?//(?:\s.*|$))?$', '$1' -Replace '(?<!\w)return(\s.*?)? if (.*?);', 'if ($2)return$1;'
|
||||
$lines = $lines | % { $_.TrimStart() }
|
||||
$lines = $lines -Replace '^(.*?)((?<=^|[;{}()])\s?//(?:\s.*|$))?$', '$1'
|
||||
$lines = $lines -Replace '(?<!\w)return(\s.*?)? if (.*?);', 'if ($2)return$1;'
|
||||
,$lines | Rewrite-File $file
|
||||
}
|
||||
|
||||
ForEach($file in Get-ChildItem -Include *.css -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
$lines = $lines -Replace '\s*/\*.*?\*/', ''
|
||||
$lines = $lines -Replace '^\s+(.+):\s?(.+?)(?:\s?(!important))?;$', '$1:$2$3;'
|
||||
$lines = $lines -Replace '^(\S.*?) {$', '$1{'
|
||||
@(($lines | Where { $_ -ne '' }) -Join ' ') | Rewrite-File $file
|
||||
}
|
||||
|
||||
ForEach($file in Get-ChildItem -Include *.html -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
,($lines | % { $_.TrimStart() }) | Rewrite-File $file
|
||||
|
@@ -270,7 +270,9 @@
|
||||
tags.push("<style type='text/css'>");
|
||||
tags.push("body { background: "+getClassStyleProperty("column", "background-color")+" }"); // set background color
|
||||
tags.push("a[data-full-url] { word-break: break-all }"); // break long urls
|
||||
tags.push(".txt-base-smallest .badge-verified:before { width: 13px !important; height: 13px !important; background-position: -223px -98px !important; }"); // fix cut off badge icon
|
||||
tags.push(".txt-base-smallest .badge-verified:before { width: 13px !important; height: 13px !important; background-position: -223px -98px !important }"); // fix cut off badge icon
|
||||
tags.push(".media-item, .media-preview { border-radius: 1px !important }"); // square-ify media
|
||||
tags.push(".quoted-tweet { border-radius: 0 !important }"); // square-ify quoted tweets
|
||||
tags.push(".activity-header { align-items: center !important; margin-bottom: 4px }"); // tweak alignment of avatar and text in notifications
|
||||
tags.push(".activity-header .tweet-timestamp { line-height: unset }"); // fix timestamp position in notifications
|
||||
tags.push("</style>");
|
||||
@@ -328,9 +330,8 @@
|
||||
let menu = $(".js-dropdown-content").children("ul").first();
|
||||
return if menu.length === 0;
|
||||
|
||||
menu.children(".drp-h-divider").last().before('<li class="is-selectable" data-std><a href="#" data-action="tweetduck">TweetDuck</a></li>');
|
||||
|
||||
let button = menu.children("[data-std]");
|
||||
let button = $('<li class="is-selectable" data-tweetduck><a href="#" data-action>TweetDuck</a></li>');
|
||||
button.insertBefore(menu.children(".drp-h-divider").last());
|
||||
|
||||
button.on("click", "a", function(){
|
||||
$TD.openContextMenu();
|
||||
@@ -349,29 +350,19 @@
|
||||
// Block: Expand shortened links on hover or display tooltip.
|
||||
//
|
||||
(function(){
|
||||
var cutStart = function(str, search){
|
||||
return str.startsWith(search) ? str.substr(search.length) : str;
|
||||
};
|
||||
|
||||
var prevMouseX = -1, prevMouseY = -1;
|
||||
var tooltipTimer, tooltipDisplayed;
|
||||
|
||||
$(document.body).delegate("a[data-full-url]", "mouseenter mouseleave mousemove", function(e){
|
||||
var me = $(this);
|
||||
|
||||
if (e.type === "mouseenter"){
|
||||
$(document.body).delegate("a[data-full-url]", {
|
||||
mouseenter: function(){
|
||||
let me = $(this);
|
||||
let text = me.text();
|
||||
return if text.charCodeAt(text.length-1) !== 8230; // horizontal ellipsis
|
||||
|
||||
if ($TDX.expandLinksOnHover){
|
||||
tooltipTimer = window.setTimeout(function(){
|
||||
let expanded = me.attr("data-full-url");
|
||||
expanded = cutStart(expanded, "https://");
|
||||
expanded = cutStart(expanded, "http://");
|
||||
expanded = cutStart(expanded, "www.");
|
||||
|
||||
me.attr("td-prev-text", text);
|
||||
me.text(expanded);
|
||||
me.text(me.attr("data-full-url").replace(/^https?:\/\/(www\.)?/, ""));
|
||||
}, 200);
|
||||
}
|
||||
else{
|
||||
@@ -380,14 +371,13 @@
|
||||
tooltipDisplayed = true;
|
||||
}, 400);
|
||||
}
|
||||
}
|
||||
else if (e.type === "mouseleave"){
|
||||
if ($TDX.expandLinksOnHover){
|
||||
let prevText = me.attr("td-prev-text");
|
||||
|
||||
if (prevText){
|
||||
me.text(prevText);
|
||||
}
|
||||
},
|
||||
|
||||
mouseleave: function(){
|
||||
let me = $(this);
|
||||
|
||||
if (me[0].hasAttribute("td-prev-text")){
|
||||
me.text(me.attr("td-prev-text"));
|
||||
}
|
||||
|
||||
window.clearTimeout(tooltipTimer);
|
||||
@@ -396,10 +386,11 @@
|
||||
tooltipDisplayed = false;
|
||||
$TD.displayTooltip(null, false);
|
||||
}
|
||||
}
|
||||
else if (e.type === "mousemove"){
|
||||
},
|
||||
|
||||
mousemove: function(e){
|
||||
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;
|
||||
prevMouseY = e.clientY;
|
||||
}
|
||||
@@ -408,7 +399,51 @@
|
||||
})();
|
||||
|
||||
//
|
||||
// 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 auxclick", function(e){
|
||||
if (e.button === 0 || e.button === 1){ // event.which seems to be borked in auxclick
|
||||
$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(){
|
||||
let me = $(this)[0];
|
||||
@@ -450,29 +485,34 @@
|
||||
return !!highlightedColumnObj;
|
||||
};
|
||||
|
||||
var updateHighlightedTweet = function(ele, obj, link, embeddedLink, author, imageList){
|
||||
var updateHighlightedTweet = function(ele, obj, tweetUrl, quoteUrl, authors, imageList){
|
||||
highlightedTweetEle = ele;
|
||||
highlightedTweetObj = obj;
|
||||
|
||||
if (lastTweet !== link){
|
||||
$TD.setLastHighlightedTweet(link, embeddedLink, author, imageList);
|
||||
lastTweet = link;
|
||||
if (lastTweet !== tweetUrl){
|
||||
$TD.setLastHighlightedTweet(tweetUrl, quoteUrl, authors, imageList);
|
||||
lastTweet = tweetUrl;
|
||||
}
|
||||
};
|
||||
|
||||
app.delegate("section.js-column", "mouseenter mouseleave", function(e){
|
||||
if (e.type === "mouseenter"){
|
||||
var processMedia = function(chirp){
|
||||
return chirp.getMedia().filter(item => !item.isAnimatedGif).map(item => item.entity.media_url_https+":small").join(";");
|
||||
};
|
||||
|
||||
app.delegate("section.js-column", {
|
||||
mouseenter: function(){
|
||||
if (!highlightedColumnObj){
|
||||
updateHighlightedColumn($(this));
|
||||
}
|
||||
}
|
||||
else if (e.type === "mouseleave"){
|
||||
},
|
||||
|
||||
mouseleave: function(){
|
||||
updateHighlightedColumn(null);
|
||||
}
|
||||
});
|
||||
|
||||
app.delegate("article.js-stream-item", "mouseenter mouseleave", function(e){
|
||||
if (e.type === "mouseenter"){
|
||||
app.delegate("article.js-stream-item", {
|
||||
mouseenter: function(){
|
||||
let me = $(this);
|
||||
return if !me[0].hasAttribute("data-account-key") || (!highlightedColumnObj && !updateHighlightedColumn(me.closest("section.js-column")));
|
||||
|
||||
@@ -480,19 +520,19 @@
|
||||
return if !tweet;
|
||||
|
||||
if (tweet.chirpType === TD.services.ChirpBase.TWEET){
|
||||
let link = tweet.getChirpURL();
|
||||
let embedded = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
|
||||
let username = tweet.getMainUser().screenName;
|
||||
let images = tweet.hasImage() ? tweet.getMedia().filter(item => !item.isAnimatedGif).map(item => item.entity.media_url_https+":small").join(";") : "";
|
||||
// TODO maybe handle embedded images too?
|
||||
|
||||
updateHighlightedTweet(me, tweet, link || "", embedded || "", username, images);
|
||||
let tweetUrl = tweet.getChirpURL();
|
||||
let quoteUrl = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
|
||||
let authors = tweet.quotedTweet ? [ tweet.getMainUser().screenName, tweet.quotedTweet.getMainUser().screenName ].join(";") : tweet.getMainUser().screenName;
|
||||
let imageList = tweet.quotedTweet && tweet.quotedTweet.hasImage() ? processMedia(tweet.quotedTweet) : tweet.hasImage() ? processMedia(tweet) : "";
|
||||
|
||||
updateHighlightedTweet(me, tweet, tweetUrl || "", quoteUrl || "", authors, imageList);
|
||||
}
|
||||
else{
|
||||
updateHighlightedTweet(me, tweet, "", "", "", "");
|
||||
}
|
||||
}
|
||||
else if (e.type === "mouseleave"){
|
||||
},
|
||||
|
||||
mouseleave: function(){
|
||||
updateHighlightedTweet(null, null, "", "", "", "");
|
||||
}
|
||||
});
|
||||
@@ -549,6 +589,7 @@
|
||||
|
||||
if (isReply){
|
||||
selectedTweet.find(".is-conversation").removeClass("is-conversation");
|
||||
selectedTweet.find(".thread").remove();
|
||||
}
|
||||
|
||||
selectedTweet.find(".js-poll-link").remove();
|
||||
@@ -635,6 +676,46 @@
|
||||
};
|
||||
})();
|
||||
|
||||
//
|
||||
// Block: Allow drag & drop behavior for dropping links on columns to open their detail view.
|
||||
//
|
||||
(function(){
|
||||
let tweetRegex = /^https?:\/\/twitter\.com\/[A-Za-z0-9_]+\/status\/(\d+)\/?$/;
|
||||
let isDraggingValid = false;
|
||||
|
||||
window.TDGF_onGlobalDragStart = function(type, data){
|
||||
isDraggingValid = type === "link" && tweetRegex.test(data);
|
||||
};
|
||||
|
||||
app.delegate("section.js-column", {
|
||||
dragover: function(e){
|
||||
e.originalEvent.dataTransfer.dropEffect = isDraggingValid ? "move" : "none";
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
},
|
||||
|
||||
drop: function(e){
|
||||
let match = tweetRegex.exec(e.originalEvent.dataTransfer.getData("URL"));
|
||||
|
||||
if (match.length === 2){
|
||||
let column = TD.controller.columnManager.get($(this).attr("data-column"));
|
||||
|
||||
if (column){
|
||||
TD.controller.clients.getPreferredClient().show(match[1], function(chirp){
|
||||
TD.ui.updates.showDetailView(column, chirp, column.findChirp(chirp) || chirp);
|
||||
$(document).trigger("uiGridClearSelection");
|
||||
}, function(){
|
||||
alert("error|Could not retrieve the requested tweet.");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
//
|
||||
// Block: Fix scheduled tweets not showing up sometimes.
|
||||
//
|
||||
@@ -704,9 +785,7 @@
|
||||
|
||||
$(".js-drawer[data-drawer='compose']").delegate(".js-account-list > .js-account-item", "click", onAccountClick);
|
||||
|
||||
if (!ensurePropertyExists(TD, "components", "AccountSelector", "prototype", "refreshPostingAccounts")){
|
||||
return;
|
||||
}
|
||||
return if !ensurePropertyExists(TD, "components", "AccountSelector", "prototype", "refreshPostingAccounts");
|
||||
|
||||
TD.components.AccountSelector.prototype.refreshPostingAccounts = appendToFunction(TD.components.AccountSelector.prototype.refreshPostingAccounts, function(){
|
||||
if (!this.$node.attr("td-account-selector-hook")){
|
||||
@@ -747,74 +826,24 @@
|
||||
// Block: Inject custom CSS and layout into the page.
|
||||
//
|
||||
(function(){
|
||||
let styleOfficial = document.createElement("style");
|
||||
document.head.appendChild(styleOfficial);
|
||||
|
||||
let addRule = (rule) => {
|
||||
styleOfficial.sheet.insertRule(rule, 0);
|
||||
var createStyle = function(id, styles){
|
||||
let ele = document.createElement("style");
|
||||
ele.id = id;
|
||||
ele.innerText = styles;
|
||||
document.head.appendChild(ele);
|
||||
};
|
||||
|
||||
addRule("a[data-full-url] { word-break: break-all; }"); // break long urls
|
||||
addRule(".keyboard-shortcut-list { vertical-align: top; }"); // fix keyboard navigation alignment
|
||||
addRule(".account-inline .username { vertical-align: 10%; }"); // move usernames a bit higher
|
||||
addRule(".character-count-compose { width: 40px !important; }"); // fix strangely wide character count element
|
||||
addRule(".is-video a:not([href*='youtu']) .icon-bg-dot, .is-gif .icon-bg-dot { color: #9f51cf; }"); // change play icon on mp4s
|
||||
|
||||
addRule(".column-nav-link .attribution { position: absolute; }"); // fix cut off account names
|
||||
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(".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(".compose-text-container, .dropdown-menu, .list-item-last, .quoted-tweet { border-radius: 0 !important; }"); // square-ify dropdowns, quoted tweets, and account selectors
|
||||
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-header { padding-left: 0 !important; }"); // fix retweet account selector heading
|
||||
|
||||
addRule(".scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb, .antiscroll-scrollbar { border-radius: 0; }"); // square-ify scroll bars
|
||||
addRule(".antiscroll-scrollbar-vertical { margin-top: 0; }"); // square-ify scroll bars
|
||||
addRule(".antiscroll-scrollbar-horizontal { margin-left: 0; }"); // square-ify scroll bars
|
||||
addRule(".scroll-styled-v:not(.antiscroll-inner)::-webkit-scrollbar { width: 8px; }"); // square-ify scroll bars
|
||||
addRule(".scroll-styled-h:not(.antiscroll-inner)::-webkit-scrollbar { height: 8px; }"); // square-ify scroll bars
|
||||
addRule(".app-columns-container::-webkit-scrollbar { height: 9px !important; }"); // square-ify scroll bars
|
||||
|
||||
addRule(".is-condensed .app-header-inner { padding-top: 10px !important; }"); // add extra padding to menu buttons when condensed
|
||||
addRule(".is-condensed .btn-compose { padding: 8px !important; }"); // fix compose button icon when condensed
|
||||
addRule(".app-header:not(.is-condensed) .nav-user-info { padding: 0 5px; }"); // add padding to user info
|
||||
|
||||
addRule(".app-title { display: none; }"); // hide TweetDeck logo
|
||||
addRule(".nav-user-info { bottom: 10px !important; }"); // move user info
|
||||
addRule(".app-navigator { bottom: 50px !important; }"); // move navigation
|
||||
addRule(".column-navigator-overflow { bottom: 192px !important; }"); // move column list
|
||||
addRule(".app-navigator .tooltip { display: none !important; }"); // hide broken tooltips in the menu
|
||||
|
||||
addRule(".column .column-header { height: 49px !important; }"); // fix one pixel space below column header
|
||||
addRule(".column:not(.is-options-open) .column-header { border-bottom: none; }"); // fix one pixel space below column header
|
||||
addRule(".is-options-open .column-type-icon { bottom: 27px; }"); // fix one pixel space below column header
|
||||
|
||||
addRule(".activity-header { align-items: center !important; margin-bottom: 4px; }"); // tweak alignment of avatar and text in notifications
|
||||
addRule(".activity-header .tweet-timestamp { line-height: unset; }"); // fix timestamp position in notifications
|
||||
addRule(".account-bio.padding-t--5 { padding-top: 2px !important; }"); // decrease padding on follow notifications
|
||||
|
||||
addRule("html[data-td-theme='light'] .stream-item:not(:hover) .js-user-actions-menu { color: #000; border-color: #000; opacity: 0.25; }"); // make follow notification button nicer
|
||||
addRule("html[data-td-theme='dark'] .stream-item:not(:hover) .js-user-actions-menu { color: #fff; border-color: #fff; opacity: 0.25; }"); // make follow notification button nicer
|
||||
|
||||
addRule(".app-columns-container::-webkit-scrollbar-track { border-left: 0; }"); // remove weird border in the column container scrollbar
|
||||
addRule(".app-columns-container { bottom: 0 !important; }"); // move column container scrollbar to bottom to fit updated style
|
||||
|
||||
addRule(".js-column-header .column-header-link { padding: 0; }"); // fix column header tooltip hover box
|
||||
addRule(".js-column-header .column-header-link .icon { padding: 9px 4px; width: calc(1em + 8px); height: 100%; box-sizing: border-box; }"); // fix column header tooltip hover box
|
||||
|
||||
addRule("#td-compose-drawer-pin { margin: 17px 4px 0 0; transition: transform 0.1s ease; fill: #fff; float: right; cursor: pointer; }"); // replace 'stay open' checkbox with a pin icon
|
||||
addRule(".js-docked-compose footer { display: none; }"); // replace 'stay open' checkbox with a pin icon
|
||||
addRule(".compose-content { bottom: 0 !important; }"); // replace 'stay open' checkbox with a pin icon
|
||||
addRule(".js-docked-compose .js-drawer-close { margin: 20px 0 0 !important; }"); // fix close drawer button because twitter is fucking incompetent
|
||||
window.TDGF_injectBrowserCSS = function(styles){
|
||||
if (!document.getElementById("tweetduck-browser-css")){
|
||||
createStyle("tweetduck-browser-css", styles);
|
||||
}
|
||||
};
|
||||
|
||||
window.TDGF_reinjectCustomCSS = function(styles){
|
||||
$("#tweetduck-custom-css").remove();
|
||||
|
||||
if (styles && styles.length){
|
||||
$(document.head).append("<style type='text/css' id='tweetduck-custom-css'>"+styles+"</style>");
|
||||
createStyle("tweetduck-custom-css", styles);
|
||||
}
|
||||
};
|
||||
})();
|
||||
@@ -849,19 +878,38 @@
|
||||
$TD.playVideo(url);
|
||||
};
|
||||
|
||||
app.delegate(".js-gif-play", "click", function(e){
|
||||
let src = !e.ctrlKey && $(this).closest(".js-media-gif-container").find("video").attr("src");
|
||||
var getVideoTweetLink = function(obj){
|
||||
let parent = obj.closest(".js-tweet").first();
|
||||
let link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
|
||||
return link.attr("href");
|
||||
}
|
||||
|
||||
app.delegate(".js-gif-play", {
|
||||
click: function(e){
|
||||
let src = !e.ctrlKey && $(this).closest(".js-media-gif-container").find("video").attr("src");
|
||||
|
||||
if (src){
|
||||
playVideo(src);
|
||||
}
|
||||
else{
|
||||
$TD.openBrowser(getVideoTweetLink($(this)));
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
},
|
||||
|
||||
if (src){
|
||||
playVideo(src);
|
||||
}
|
||||
else{
|
||||
let parent = $(e.target).closest(".js-tweet").first();
|
||||
let link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
|
||||
$TD.openBrowser(link.attr("href"));
|
||||
}
|
||||
mousedown: function(e){
|
||||
if (e.button === 1){
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
e.stopPropagation();
|
||||
mouseup: function(e){
|
||||
if (e.button === 1){
|
||||
$TD.openBrowser(getVideoTweetLink($(this)));
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TD.mustaches["status/media_thumb.mustache"] = TD.mustaches["status/media_thumb.mustache"].replace("is-gif", "is-gif is-paused");
|
||||
@@ -936,6 +984,28 @@
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Make temporary search column appear as the first one and clear the input box.
|
||||
//
|
||||
$(document).on("uiSearchNoTemporaryColumn", function(e, data){
|
||||
if (data.query && data.searchScope !== "users" && !data.columnKey){
|
||||
if ($TDX.openSearchInFirstColumn){
|
||||
let order = TD.controller.columnManager._columnOrder;
|
||||
|
||||
if (order.length > 1){
|
||||
let columnKey = order[order.length-1];
|
||||
|
||||
order.splice(order.length-1, 1);
|
||||
order.splice(1, 0, columnKey);
|
||||
TD.controller.columnManager.move(columnKey, "left");
|
||||
}
|
||||
}
|
||||
|
||||
$(".js-app-search-input").val("");
|
||||
$(".js-perform-search").blur();
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Fix DM reply input box not getting focused after opening a conversation.
|
||||
//
|
||||
@@ -1012,31 +1082,34 @@
|
||||
});
|
||||
};
|
||||
|
||||
window.TDGF_tryRunCleanup = function(){
|
||||
// no modals are visible
|
||||
return false if $("#open-modal").is(":visible") || !$(".js-modals-container").is(":empty");
|
||||
(function(){
|
||||
var lastActivity = Date.now();
|
||||
|
||||
// all columns are in a default state
|
||||
return false if $("section.js-column").is(".is-shifted-1,.is-shifted-2");
|
||||
$(document).click(function(e){
|
||||
lastActivity = Date.now();
|
||||
});
|
||||
|
||||
// all textareas are empty
|
||||
if ($("textarea").is(function(){
|
||||
return $(this).val().length > 0;
|
||||
})){
|
||||
return false;
|
||||
}
|
||||
|
||||
// all columns are scrolled to top
|
||||
if ($(".js-column-scroller").is(function(){
|
||||
return $(this).scrollTop() > 0;
|
||||
})){
|
||||
return false;
|
||||
}
|
||||
|
||||
// cleanup
|
||||
window.TDGF_reload();
|
||||
return true;
|
||||
};
|
||||
window.TDGF_tryRunCleanup = function(){
|
||||
// no recent activity
|
||||
return false if Date.now()-lastActivity < 15e3;
|
||||
|
||||
// no modals are visible
|
||||
return false if $(".js-modal").is(":visible") || !$(".js-modals-container").is(":empty");
|
||||
|
||||
// all columns are in a default state
|
||||
return false if $("section.js-column").is(".is-shifted-1,.is-shifted-2");
|
||||
|
||||
// all textareas are empty
|
||||
return false if Array.prototype.some.call(document.getElementsByTagName("textarea"), ele => ele.value.length > 0);
|
||||
|
||||
// all columns are scrolled to top
|
||||
return false if Array.prototype.some.call(document.getElementsByClassName("js-column-scroller"), ele => ele.scrollTop > 0);
|
||||
|
||||
// cleanup
|
||||
window.TDGF_reload();
|
||||
return true;
|
||||
};
|
||||
})();
|
||||
|
||||
if (window.TD_SESSION && window.TD_SESSION.gc){
|
||||
var state;
|
||||
|
97
Resources/Scripts/introduction.js
Normal file
@@ -0,0 +1,97 @@
|
||||
(function($, $TD){
|
||||
$(document).one("TD.ready", function(){
|
||||
let css = $(`
|
||||
<style>
|
||||
#td-introduction-modal {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#td-introduction-modal .mdl {
|
||||
width: 90%;
|
||||
max-width: 800px;
|
||||
height: 372px;
|
||||
}
|
||||
|
||||
#td-introduction-modal .mdl-header-title {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#td-introduction-modal .mdl-content {
|
||||
padding: 4px 16px 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#td-introduction-modal p {
|
||||
margin: 12px 0;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
#td-introduction-modal p strong {
|
||||
font-weight: normal;
|
||||
text-shadow: 0 0 #000;
|
||||
}
|
||||
|
||||
#td-introduction-modal footer {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
#td-introduction-modal button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
#td-introduction-modal .anondata {
|
||||
float: left;
|
||||
margin: 5px 7px;
|
||||
}
|
||||
|
||||
#td-introduction-modal .anondata input {
|
||||
vertical-align: -10%;
|
||||
}
|
||||
|
||||
#td-introduction-modal .anondata label {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>`).appendTo(document.head);
|
||||
|
||||
let ele = $(`
|
||||
<div id="td-introduction-modal" class="ovl">
|
||||
<div class="mdl is-inverted-dark">
|
||||
<header class="mdl-header">
|
||||
<h3 class="mdl-header-title">Welcome to TweetDuck</h3>
|
||||
<a href="#" class="mdl-dismiss link-normal-dark"><i class="icon icon-close"></i></a>
|
||||
</header>
|
||||
<div class="mdl-inner">
|
||||
<div class="mdl-content">
|
||||
<p>Thank you for downloading TweetDuck!</p>
|
||||
<p><strong>Right-click anywhere</strong> or click <strong>Settings – TweetDuck</strong> in the left panel to open the main menu, where you can access the <strong>Options</strong>, <strong>Plugins</strong>, and more.</p>
|
||||
<p>You can also right-click links, media, tweets, notifications, etc. to access their context menu.</p>
|
||||
<p>If you're using TweetDuck for the first time, check out the <strong>guide</strong> that answers common questions and showcases important features. You can open the main menu, select <strong>About TweetDuck</strong>, and click the help button to view the guide later.</p>
|
||||
<p>Before you continue, please consider helping development by allowing TweetDuck to send anonymous data in the future. You can always disable it in <strong>Options – Feedback</strong>.</p>
|
||||
</div>
|
||||
<footer class="txt-right">
|
||||
<div class="anondata">
|
||||
<input id="td-anonymous-data" type="checkbox" checked>
|
||||
<label for="td-anonymous-data">Send anonymous usage data</label>
|
||||
<label> (<a href="https://github.com/chylex/TweetDuck/wiki/Send-anonymous-data" rel="nofollow">learn more</a>)</label>
|
||||
</div>
|
||||
<button class="btn btn-positive" data-guide><span class="label">Show Guide</span></button>
|
||||
<button class="btn btn-positive"><span class="label">Close</span</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>`).appendTo(".js-app");
|
||||
|
||||
ele.find("button, a.mdl-dismiss").click(function(){
|
||||
let showGuide = $(this)[0].hasAttribute("data-guide");
|
||||
let allowDataCollection = $("#td-anonymous-data").is(":checked");
|
||||
|
||||
ele.fadeOut(200, function(){
|
||||
$TD.onIntroductionClosed(showGuide, allowDataCollection);
|
||||
ele.remove();
|
||||
css.remove();
|
||||
});
|
||||
});
|
||||
});
|
||||
})($, $TD);
|
@@ -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){
|
||||
$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();
|
||||
|
||||
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")){
|
||||
$TD.loadNextNotification();
|
||||
@@ -33,17 +35,13 @@
|
||||
// Block: Allow bypassing of t.co in context menus.
|
||||
//
|
||||
addEventListener(links, "contextmenu", function(e){
|
||||
$TD.setLastRightClickedLink(e.currentTarget.getAttribute("data-full-url") || "");
|
||||
$TD.setLastRightClickInfo("link", e.currentTarget.getAttribute("data-full-url"));
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Expand shortened links on hover or display tooltip.
|
||||
//
|
||||
(function(){
|
||||
var cutStart = function(str, search){
|
||||
return str.startsWith(search) ? str.substr(search.length) : str;
|
||||
};
|
||||
|
||||
var prevMouseX = -1, prevMouseY = -1;
|
||||
var tooltipTimer, tooltipDisplayed;
|
||||
|
||||
@@ -58,13 +56,8 @@
|
||||
|
||||
if ($TDX.expandLinksOnHover){
|
||||
tooltipTimer = window.setTimeout(function(){
|
||||
var expanded = url;
|
||||
expanded = cutStart(expanded, "https://");
|
||||
expanded = cutStart(expanded, "http://");
|
||||
expanded = cutStart(expanded, "www.");
|
||||
|
||||
me.setAttribute("td-prev-text", text);
|
||||
me.innerHTML = expanded;
|
||||
me.innerHTML = url.replace(/^https?:\/\/(www\.)?/, "");
|
||||
}, 200);
|
||||
}
|
||||
else{
|
||||
@@ -110,11 +103,11 @@
|
||||
// Block: Setup a skip button.
|
||||
//
|
||||
if (!document.body.hasAttribute("td-example-notification")){
|
||||
document.body.insertAdjacentHTML("afterbegin", [
|
||||
'<svg id="td-skip" xmlns="http://www.w3.org/2000/svg" width="10" height="17" viewBox="0 0 350 600" style="position:fixed;left:30px;bottom:10px;z-index:1000">',
|
||||
'<path fill="#888" d="M0,151.656l102.208-102.22l247.777,247.775L102.208,544.986L0,442.758l145.546-145.547">',
|
||||
'</svg>'
|
||||
].join(""));
|
||||
document.body.insertAdjacentHTML("afterbegin", `
|
||||
<svg id="td-skip" width="10" height="17" viewBox="0 0 350 600">
|
||||
<path fill="#888" d="M0,151.656l102.208-102.22l247.777,247.775L102.208,544.986L0,442.758l145.546-145.547">
|
||||
</svg>
|
||||
`);
|
||||
|
||||
document.getElementById("td-skip").addEventListener("click", function(){
|
||||
$TD.loadNextNotification();
|
||||
|
239
Resources/Scripts/styles/browser.css
Normal file
@@ -0,0 +1,239 @@
|
||||
/***********************/
|
||||
/* Redesign scrollbars */
|
||||
/***********************/
|
||||
|
||||
.scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb, .antiscroll-scrollbar {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.antiscroll-scrollbar-vertical {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.antiscroll-scrollbar-horizontal {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.scroll-styled-v:not(.antiscroll-inner)::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.scroll-styled-h:not(.antiscroll-inner)::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.app-columns-container::-webkit-scrollbar {
|
||||
height: 9px !important;
|
||||
}
|
||||
|
||||
.app-columns-container::-webkit-scrollbar-track {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.app-columns-container {
|
||||
bottom: 0 !important;
|
||||
}
|
||||
|
||||
/********************/
|
||||
/* Square-ify stuff */
|
||||
/********************/
|
||||
|
||||
.btn, .mdl, .mdl-content, .app-search-fake, .app-search-input, .popover, .lst-modal, .media-item, .media-preview, .tooltip-inner {
|
||||
border-radius: 1px !important;
|
||||
}
|
||||
|
||||
.compose-text-container, .dropdown-menu, .list-item-last, .quoted-tweet, .input-group-button, input, textarea, select, .prf-header, .accs li, .accs img {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/***********************/
|
||||
/* Hide TweetDeck logo */
|
||||
/***********************/
|
||||
|
||||
.app-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-user-info {
|
||||
bottom: 10px !important;
|
||||
}
|
||||
|
||||
.app-navigator {
|
||||
bottom: 50px !important;
|
||||
}
|
||||
|
||||
.column-navigator-overflow {
|
||||
bottom: 192px !important;
|
||||
}
|
||||
|
||||
/*************************************/
|
||||
/* Tweak collapsed left panel layout */
|
||||
/*************************************/
|
||||
|
||||
.is-condensed .app-header-inner {
|
||||
padding-top: 10px !important;
|
||||
}
|
||||
|
||||
.is-condensed .btn-compose {
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
.app-header:not(.is-condensed) .nav-user-info {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
/****************************************/
|
||||
/* Tweak notification layout and design */
|
||||
/****************************************/
|
||||
|
||||
.activity-header {
|
||||
align-items: center !important;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.activity-header .tweet-timestamp {
|
||||
line-height: unset;
|
||||
}
|
||||
|
||||
.account-bio.padding-t--5 {
|
||||
/* follow notification padding */
|
||||
padding-top: 2px !important;
|
||||
}
|
||||
|
||||
html[data-td-theme='light'] .stream-item:not(:hover) .js-user-actions-menu {
|
||||
color: #000;
|
||||
border-color: #000;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
html[data-td-theme='dark'] .stream-item:not(:hover) .js-user-actions-menu {
|
||||
color: #fff;
|
||||
border-color: #fff;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
/***********************/
|
||||
/* Tweaks for features */
|
||||
/***********************/
|
||||
|
||||
a[data-full-url] {
|
||||
/* break long urls */
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.character-count-compose {
|
||||
/* fix strangely wide character count element */
|
||||
width: 40px !important;
|
||||
}
|
||||
|
||||
.is-video a:not([href*='youtu']) .icon-bg-dot, .is-gif .icon-bg-dot {
|
||||
/* change play icon on mp4s */
|
||||
color: #9f51cf;
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
/* Replace 'Stay open' with a pin icon */
|
||||
/***************************************/
|
||||
|
||||
#td-compose-drawer-pin {
|
||||
margin: 17px 4px 0 0;
|
||||
float: right;
|
||||
fill: #fff;
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
.js-docked-compose footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.compose-content {
|
||||
bottom: 0 !important;
|
||||
}
|
||||
|
||||
/*******************************************/
|
||||
/* Fix general visual issues or annoyances */
|
||||
/*******************************************/
|
||||
|
||||
.account-inline .username {
|
||||
/* move usernames a bit higher */
|
||||
vertical-align: 10%;
|
||||
}
|
||||
|
||||
.txt-base-smallest .sprite-verified-mini {
|
||||
/* fix cut off badge when zoomed in */
|
||||
width: 13px !important;
|
||||
height: 13px !important;
|
||||
background-position: -223px -99px !important;
|
||||
}
|
||||
|
||||
.txt-base-smallest .badge-verified:before {
|
||||
/* fix cut off badge in notifications */
|
||||
width: 13px !important;
|
||||
height: 13px !important;
|
||||
background-position: -223px -98px !important;
|
||||
}
|
||||
|
||||
.accs-header {
|
||||
/* fix retweet account selector heading */
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
.keyboard-shortcut-list {
|
||||
/* fix keyboard navigation alignment */
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.tweet-detail-wrapper .js-media-gif-container {
|
||||
/* GIFs in detail view don't trigger the pointer cursor */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/********************************************************************/
|
||||
/* Fix glaring visual issues because twitter is fucking incompetent */
|
||||
/********************************************************************/
|
||||
|
||||
.column-nav-link .attribution {
|
||||
/* fix cut off account names */
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.js-docked-compose .js-drawer-close {
|
||||
/* fix close drawer button position */
|
||||
margin: 20px 0 0 !important;
|
||||
}
|
||||
|
||||
/************************************************/
|
||||
/* Fix tooltips in navigation and column header */
|
||||
/************************************************/
|
||||
|
||||
.app-navigator .tooltip {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.js-column-header .column-header-link {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.js-column-header .column-header-link .icon {
|
||||
width: calc(1em + 8px);
|
||||
height: 100%;
|
||||
padding: 9px 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/*******************************************/
|
||||
/* Fix one pixel space below column header */
|
||||
/*******************************************/
|
||||
|
||||
.column .column-header {
|
||||
height: 49px !important;
|
||||
}
|
||||
|
||||
.column:not(.is-options-open) .column-header {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.is-options-open .column-type-icon {
|
||||
bottom: 27px;
|
||||
}
|
57
Resources/Scripts/styles/notification.css
Normal file
@@ -0,0 +1,57 @@
|
||||
/* General */
|
||||
|
||||
body:before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.scroll-styled-v::-webkit-scrollbar {
|
||||
width: 7px;
|
||||
}
|
||||
|
||||
.scroll-styled-v::-webkit-scrollbar-thumb {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.scroll-styled-v::-webkit-scrollbar-track {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
/* Media */
|
||||
|
||||
.media-size-medium {
|
||||
max-height: 240px;
|
||||
height: calc(100vh - 16px) !important;
|
||||
border-radius: 1px !important;
|
||||
}
|
||||
|
||||
.js-quote-detail .media-size-medium {
|
||||
height: calc(100vh - 28px) !important;
|
||||
}
|
||||
|
||||
.js-media.margin-vm, .js-media-preview-container.margin-vm {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* Skip button */
|
||||
|
||||
#td-skip {
|
||||
position: fixed;
|
||||
left: 30px;
|
||||
bottom: 10px;
|
||||
z-index: 1000;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.td-hover #td-skip {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
#td-skip:hover {
|
||||
opacity: 1;
|
||||
}
|
@@ -39,4 +39,25 @@
|
||||
};
|
||||
|
||||
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">
|
||||
<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')" />
|
||||
@@ -88,6 +88,8 @@
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Core\FormManager.cs" />
|
||||
<Compile Include="Core\Handling\ContextMenuGuide.cs" />
|
||||
<Compile Include="Core\Handling\DragHandlerBrowser.cs" />
|
||||
<Compile Include="Core\Handling\General\BrowserProcessHandler.cs" />
|
||||
<Compile Include="Core\Handling\ContextMenuBase.cs" />
|
||||
<Compile Include="Core\Handling\ContextMenuBrowser.cs" />
|
||||
@@ -97,6 +99,7 @@
|
||||
<Compile Include="Core\FormBrowser.Designer.cs">
|
||||
<DependentUpon>FormBrowser.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Handling\General\FileDialogHandler.cs" />
|
||||
<Compile Include="Core\Handling\KeyboardHandlerBrowser.cs" />
|
||||
<Compile Include="Core\Handling\KeyboardHandlerNotification.cs" />
|
||||
<Compile Include="Core\Handling\RequestHandlerBrowser.cs" />
|
||||
@@ -131,6 +134,12 @@
|
||||
<Compile Include="Core\Other\FormAbout.Designer.cs">
|
||||
<DependentUpon>FormAbout.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\FormGuide.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\FormGuide.Designer.cs">
|
||||
<DependentUpon>FormGuide.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\FormMessage.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -169,6 +178,18 @@
|
||||
<DependentUpon>DialogSettingsRestart.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\Management\BrowserProcesses.cs" />
|
||||
<Compile Include="Core\Other\Settings\TabSettingsFeedback.cs">
|
||||
<SubType>UserControl</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\Settings\TabSettingsFeedback.Designer.cs">
|
||||
<DependentUpon>TabSettingsFeedback.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\Settings\TabSettingsTray.cs">
|
||||
<SubType>UserControl</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\Settings\TabSettingsTray.Designer.cs">
|
||||
<DependentUpon>TabSettingsTray.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Utils\StringUtils.cs" />
|
||||
<Compile Include="Core\Utils\TwitterUtils.cs" />
|
||||
<Compile Include="Data\CombinedFileStream.cs" />
|
||||
@@ -325,12 +346,15 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Resources\Scripts\code.js" />
|
||||
<Content Include="Resources\Scripts\introduction.js" />
|
||||
<Content Include="Resources\Scripts\notification.js" />
|
||||
<Content Include="Resources\Scripts\pages\error.html" />
|
||||
<Content Include="Resources\Scripts\pages\example.html" />
|
||||
<Content Include="Resources\Scripts\plugins.browser.js" />
|
||||
<Content Include="Resources\Scripts\plugins.js" />
|
||||
<Content Include="Resources\Scripts\plugins.notification.js" />
|
||||
<Content Include="Resources\Scripts\styles\browser.css" />
|
||||
<Content Include="Resources\Scripts\styles\notification.css" />
|
||||
<Content Include="Resources\Scripts\twitter.js" />
|
||||
<Content Include="Resources\Scripts\update.js" />
|
||||
</ItemGroup>
|
||||
|
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{
|
||||
[TestMethod]
|
||||
public void TestIsValidUrl(){
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.com")); // base
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://www.google.com")); // www.
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.co.uk")); // co.uk
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com")); // base
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://www.google.com")); // www.
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.co.uk")); // co.uk
|
||||
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("https://google.com")); // https
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("ftp://google.com")); // ftp
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("mailto:someone@google.com")); // mailto
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("https://google.com")); // https
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("ftp://google.com")); // ftp
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("mailto:someone@google.com")); // mailto
|
||||
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.com/")); // trailing slash
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.com/?")); // trailing question mark
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("http://google.com/?a=5&b=x")); // parameters
|
||||
Assert.IsTrue(BrowserUtils.IsValidUrl("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/")); // trailing slash
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/?")); // trailing question mark
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/?a=5&b=x")); // parameters
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/#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" }){
|
||||
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.IsFalse(BrowserUtils.IsValidUrl("explorer.exe")); // file
|
||||
Assert.IsFalse(BrowserUtils.IsValidUrl("://explorer.exe")); // file-sorta
|
||||
Assert.IsFalse(BrowserUtils.IsValidUrl("file://explorer.exe")); // file-proper
|
||||
|
||||
Assert.IsFalse(BrowserUtils.IsValidUrl("")); // empty
|
||||
Assert.IsFalse(BrowserUtils.IsValidUrl("lol")); // random
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Tracking, BrowserUtils.CheckUrl("http://t.co/abc")); // tracking
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Tracking, BrowserUtils.CheckUrl("https://t.co/abc")); // tracking
|
||||
|
||||
Assert.IsFalse(BrowserUtils.IsValidUrl("gopher://nobody.cares")); // lmao rekt
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("explorer")); // file
|
||||
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.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]
|
||||
|
@@ -6,39 +6,39 @@ using TweetDuck.Data;
|
||||
|
||||
namespace UnitTests.Data{
|
||||
[TestClass]
|
||||
public class TestCombinedFileStream{
|
||||
public class TestCombinedFileStream : UnitTestIO{
|
||||
[TestMethod]
|
||||
public void TestNoFiles(){
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_empty"))){
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("empty"))){
|
||||
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());
|
||||
}
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_empty"))){
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenRead("empty"))){
|
||||
Assert.IsNull(cfs.SkipFile());
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestEmptyFiles(){
|
||||
TestUtils.WriteText("cfs_input_empty_1", string.Empty);
|
||||
TestUtils.WriteText("cfs_input_empty_2", string.Empty);
|
||||
File.WriteAllText("input_empty_1", string.Empty);
|
||||
File.WriteAllText("input_empty_2", string.Empty);
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_blank_files"))){
|
||||
cfs.WriteFile("id1", "cfs_input_empty_1");
|
||||
cfs.WriteFile("id2", "cfs_input_empty_2");
|
||||
cfs.WriteFile("id2_clone", "cfs_input_empty_2");
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("blank_files"))){
|
||||
cfs.WriteFile("id1", "input_empty_1");
|
||||
cfs.WriteFile("id2", "input_empty_2");
|
||||
cfs.WriteFile("id2_clone", "input_empty_2");
|
||||
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();
|
||||
string entry2key = cfs.SkipFile();
|
||||
CombinedFileStream.Entry entry3 = cfs.ReadFile();
|
||||
@@ -56,31 +56,29 @@ namespace UnitTests.Data{
|
||||
Assert.AreEqual("id2_clone", entry3.Identifier);
|
||||
CollectionAssert.AreEqual(new string[0], entry3.KeyValue);
|
||||
|
||||
entry1.WriteToFile("cfs_blank_file_1");
|
||||
entry3.WriteToFile("cfs_blank_file_2");
|
||||
TestUtils.DeleteFileOnExit("cfs_blank_file_1");
|
||||
TestUtils.DeleteFileOnExit("cfs_blank_file_2");
|
||||
entry1.WriteToFile("blank_file_1");
|
||||
entry3.WriteToFile("blank_file_2");
|
||||
}
|
||||
|
||||
Assert.IsTrue(File.Exists("cfs_blank_file_1"));
|
||||
Assert.IsTrue(File.Exists("cfs_blank_file_2"));
|
||||
Assert.AreEqual(string.Empty, TestUtils.ReadText("cfs_blank_file_1"));
|
||||
Assert.AreEqual(string.Empty, TestUtils.ReadText("cfs_blank_file_2"));
|
||||
Assert.IsTrue(File.Exists("blank_file_1"));
|
||||
Assert.IsTrue(File.Exists("blank_file_2"));
|
||||
Assert.AreEqual(string.Empty, File.ReadAllText("blank_file_1"));
|
||||
Assert.AreEqual(string.Empty, File.ReadAllText("blank_file_2"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
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"))){
|
||||
cfs.WriteFile(new string[]{ "key1", "a", "bb", "ccc", "dddd" }, "cfs_input_text_1");
|
||||
cfs.WriteFile(new string[]{ "key2", "a", "bb", "ccc", "dddd" }, "cfs_input_text_1");
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("text_files"))){
|
||||
cfs.WriteFile(new string[]{ "key1", "a", "bb", "ccc", "dddd" }, "input_text_1");
|
||||
cfs.WriteFile(new string[]{ "key2", "a", "bb", "ccc", "dddd" }, "input_text_1");
|
||||
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();
|
||||
|
||||
Assert.AreEqual("key2", cfs.SkipFile());
|
||||
@@ -91,68 +89,65 @@ namespace UnitTests.Data{
|
||||
Assert.AreEqual("key1", entry.KeyName);
|
||||
CollectionAssert.AreEqual(new string[]{ "a", "bb", "ccc", "dddd" }, entry.KeyValue);
|
||||
|
||||
entry.WriteToFile("cfs_text_file_1");
|
||||
TestUtils.DeleteFileOnExit("cfs_text_file_1");
|
||||
entry.WriteToFile("text_file_1");
|
||||
}
|
||||
|
||||
Assert.IsTrue(File.Exists("cfs_text_file_1"));
|
||||
Assert.AreEqual("Hello World!"+Environment.NewLine, TestUtils.ReadText("cfs_text_file_1"));
|
||||
Assert.IsTrue(File.Exists("text_file_1"));
|
||||
Assert.AreEqual("Hello World!"+Environment.NewLine, File.ReadAllText("text_file_1"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestEntryWriteWithDirectory(){
|
||||
if (Directory.Exists("cfs_directory")){
|
||||
Directory.Delete("cfs_directory", true);
|
||||
if (Directory.Exists("directory")){
|
||||
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"))){
|
||||
cfs.WriteFile("key1", "cfs_input_dir_1");
|
||||
cfs.WriteFile("key2", "cfs_input_dir_1");
|
||||
cfs.WriteFile("key3", "cfs_input_dir_1");
|
||||
cfs.WriteFile("key4", "cfs_input_dir_1");
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("dir_test"))){
|
||||
cfs.WriteFile("key1", "input_dir_1");
|
||||
cfs.WriteFile("key2", "input_dir_1");
|
||||
cfs.WriteFile("key3", "input_dir_1");
|
||||
cfs.WriteFile("key4", "input_dir_1");
|
||||
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{
|
||||
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.");
|
||||
}catch(DirectoryNotFoundException){}
|
||||
|
||||
cfs.ReadFile().WriteToFile("cfs_directory/cfs_dir_test_file", true);
|
||||
cfs.ReadFile().WriteToFile("cfs_dir_test_file", true);
|
||||
cfs.ReadFile().WriteToFile("cfs_dir_test_file.txt", true);
|
||||
TestUtils.DeleteFileOnExit("cfs_dir_test_file");
|
||||
TestUtils.DeleteFileOnExit("cfs_dir_test_file.txt");
|
||||
cfs.ReadFile().WriteToFile("directory/dir_test_file", true);
|
||||
cfs.ReadFile().WriteToFile("dir_test_file", true);
|
||||
cfs.ReadFile().WriteToFile("dir_test_file.txt", true);
|
||||
}
|
||||
|
||||
Assert.IsTrue(Directory.Exists("cfs_directory"));
|
||||
Assert.IsTrue(File.Exists("cfs_directory/cfs_dir_test_file"));
|
||||
Assert.IsTrue(File.Exists("cfs_dir_test_file"));
|
||||
Assert.IsTrue(File.Exists("cfs_dir_test_file.txt"));
|
||||
Assert.AreEqual("test", TestUtils.ReadText("cfs_directory/cfs_dir_test_file"));
|
||||
Assert.AreEqual("test", TestUtils.ReadText("cfs_dir_test_file"));
|
||||
Assert.AreEqual("test", TestUtils.ReadText("cfs_dir_test_file.txt"));
|
||||
Assert.IsTrue(Directory.Exists("directory"));
|
||||
Assert.IsTrue(File.Exists("directory/dir_test_file"));
|
||||
Assert.IsTrue(File.Exists("dir_test_file"));
|
||||
Assert.IsTrue(File.Exists("dir_test_file.txt"));
|
||||
Assert.AreEqual("test", File.ReadAllText("directory/dir_test_file"));
|
||||
Assert.AreEqual("test", File.ReadAllText("dir_test_file"));
|
||||
Assert.AreEqual("test", File.ReadAllText("dir_test_file.txt"));
|
||||
|
||||
Directory.Delete("cfs_directory", true);
|
||||
Directory.Delete("directory", true);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
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));
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_long_identifier_success"))){
|
||||
cfs.WriteFile(identifier, "cfs_long_identifier_fail_in");
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("long_identifier_success"))){
|
||||
cfs.WriteFile(identifier, "long_identifier_fail_in");
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -160,10 +155,10 @@ namespace UnitTests.Data{
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
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"))){
|
||||
cfs.WriteFile(string.Join("", Enumerable.Repeat("x", 256)), "cfs_long_identifier_fail_in");
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(File.OpenWrite("long_identifier_fail"))){
|
||||
cfs.WriteFile(string.Join("", Enumerable.Repeat("x", 256)), "long_identifier_fail_in");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ using TweetDuck.Data.Serialization;
|
||||
|
||||
namespace UnitTests.Data{
|
||||
[TestClass]
|
||||
public class TestFileSerializer{
|
||||
public class TestFileSerializer : UnitTestIO{
|
||||
private enum TestEnum{
|
||||
A, B, C, D, E
|
||||
}
|
||||
@@ -30,13 +30,11 @@ namespace UnitTests.Data{
|
||||
TestEnum = TestEnum.D
|
||||
};
|
||||
|
||||
serializer.Write("serialized_basic", write);
|
||||
|
||||
Assert.IsTrue(File.Exists("serialized_basic"));
|
||||
TestUtils.DeleteFileOnExit("serialized_basic");
|
||||
serializer.Write("basic_wr", write);
|
||||
Assert.IsTrue(File.Exists("basic_wr"));
|
||||
|
||||
SerializationTestBasic read = new SerializationTestBasic();
|
||||
serializer.Read("serialized_basic", read);
|
||||
serializer.Read("basic_wr", read);
|
||||
|
||||
Assert.IsTrue(read.TestBool);
|
||||
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
@@ -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>
|
||||
</Choose>
|
||||
<ItemGroup>
|
||||
<Compile Include="Configuration\TestUserConfig.cs" />
|
||||
<Compile Include="Core\TestStringUtils.cs" />
|
||||
<Compile Include="Core\TestTwitterUtils.cs" />
|
||||
<Compile Include="Data\TestCombinedFileStream.cs" />
|
||||
@@ -56,7 +57,7 @@
|
||||
<Compile Include="Data\TestInjectedHTML.cs" />
|
||||
<Compile Include="Data\TestTwoKeyDictionary.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TestUtils.cs" />
|
||||
<Compile Include="UnitTestIO.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TweetDuck.csproj">
|
||||
|
@@ -5,7 +5,7 @@ using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Video{
|
||||
static class Program{
|
||||
internal const string Version = "1.2.0.0";
|
||||
internal const string Version = "1.2.1.0";
|
||||
|
||||
// referenced in VideoPlayer
|
||||
// set by task manager -- public const int CODE_PROCESS_KILLED = 1;
|
||||
|