1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-09-14 10:32:10 +02:00

Compare commits

..

64 Commits

Author SHA1 Message Date
a63e210b88 Release 1.13.4 2018-04-15 19:59:35 +02:00
06bd65b7f8 Fix wrong behavior when an update is canceled during download & multiple check errors in some cases 2018-04-15 19:01:39 +02:00
b6c17eb05e Remove unused selectors and classes from styles & add a related TODO note 2018-04-15 18:08:37 +02:00
a3d40fdc2b Push a quick utility to detect unused selectors and classes 2018-04-15 18:08:03 +02:00
c064ef7a30 Improve screenshot reliability 2018-04-15 16:24:26 +02:00
762717da1e Move clear-columns plugin nav button next to 'Add column' button & add isClearable safeguard 2018-04-15 14:39:59 +02:00
b7d3758bea Add error handling when checking updates, and remove unnecessary TODO 2018-04-15 14:03:11 +02:00
d20541fd24 Fix clear-columns plugin to hide the Clear button on scheduled & collection columns 2018-04-14 20:28:48 +02:00
2c2f860f26 Fix issues caused by recent TweetDeck update (notifications, column styles, reply account)
Closes #211
2018-04-14 19:40:51 +02:00
d1db3aa673 Remove command line argument for pre-releases & reorganize restart dialog 2018-04-11 10:39:17 +02:00
cedc52cdf5 Move update notification trigger code to TweetDeckBrowser 2018-04-11 10:01:55 +02:00
33f8eafbcf Remove unused VC120 NuGet package 2018-04-11 09:59:10 +02:00
ad45cf8c72 Begin rewriting update checker to run within C# 2018-04-11 09:59:00 +02:00
f99d035621 Add a Result class that acts as an Either monad for a value or exception 2018-04-10 19:45:41 +02:00
f3072caea8 Fix broken element resizing in the Edit CSS dialog 2018-04-07 13:42:36 +02:00
1410974292 Release 1.13.3 2018-04-07 11:56:36 +02:00
44413fa96c Swap order of 'Search in' items in selection context menu 2018-04-07 11:15:14 +02:00
342a4b4067 Minor code formatting tweaks 2018-04-07 03:46:10 +02:00
4356dde92d Fix wrong c# language version setting for Release builds 2018-04-06 16:36:27 +02:00
21e64a18d8 Fix screenshots to work properly with combinations of DPI and zoom settings 2018-04-06 07:28:57 +02:00
5a305a6740 Fix wrong screenshot size when browser zoom is not 100% 2018-04-06 03:05:06 +02:00
44595bad40 Refactor plugin loading and validation 2018-04-05 21:34:35 +02:00
7fc9edc9cb Fix wrong namespace in update event classes 2018-04-05 09:58:52 +02:00
93e191f522 Reorganize hot swap code & add support for hot swapping plugins 2018-04-05 03:34:11 +02:00
8d8355e792 Rewrite PluginManager setup scripts to use a custom array-based dictionary 2018-04-05 02:09:51 +02:00
a5379d290c Add resource hotswap for easier debugging 2018-04-04 23:13:44 +02:00
caea8d4315 Move most of post build event (copying, cleanup) to PostBuild.ps1 2018-04-04 20:05:59 +02:00
24224ab4c6 Increase height of Options form to avoid scrollbars in General tab 2018-04-04 07:43:00 +02:00
4dbc02360c Add context menu options to search selected text in a column or browser
Closes #209
2018-04-04 05:22:41 +02:00
aa7a29af0c Fix combo box issues (closing while opening, minor browser selection bug) 2018-04-04 03:33:14 +02:00
296d0c6199 Fix ScriptLoader showing multiple errors at once sometimes & change error title 2018-04-03 23:46:00 +02:00
812a034e8d Include version header in ScriptLoader files to detect failed installs 2018-04-03 23:44:43 +02:00
e9de789b79 Minor refactoring, including removal of unnecessary enableCustomCSS parameter 2018-04-03 20:49:21 +02:00
cfbc1b9575 Enable custom CSS in screenshots and move styles from code.js to notification.css 2018-04-03 20:48:33 +02:00
e39e85e4dd Add 'td-notification' body class to desktop notifications & update notification.css 2018-04-03 20:26:54 +02:00
3f0b161cd0 Move screenshot height calculation to the screenshot window 2018-04-03 18:26:33 +02:00
ebe3868720 Fix ScriptLoader crash when showing error message from another thread 2018-04-03 18:19:39 +02:00
ffd0f5e986 Rewrite screenshot rendering to fix current and future visual issues 2018-04-03 02:05:22 +02:00
217535a3ba Make td-notification-padded styles available in screenshots 2018-04-03 02:02:15 +02:00
7abfbea2da Fix "Replying to" user link not using black theme in screenshots
Closes #208
2018-04-03 01:10:53 +02:00
86ffeaac9a Remove no longer supported keycap emoji from the emoji keyboard
Closes #207
2018-04-03 00:04:18 +02:00
ab915b7115 Move accounts above hashtags in search results 2018-04-02 23:26:51 +02:00
705b5d38cf Add design files for logo and video player buttons 2018-04-01 19:35:12 +02:00
fc2acb00b3 Add a batch file to build update installer only 2018-03-16 18:50:37 +01:00
5add8a1d0e Move ITweetDeckBrowser and refactor some things 2018-03-16 18:48:41 +01:00
063d3a2637 Remove unnecessary null fallback in SetClipboardText 2018-03-09 14:43:59 +01:00
f1f90a2ee3 Refactor code to avoid nulls (#206)
* Ensure potential nulls have a fallback value & add/remove null checks

* Refactor update check code to avoid nulls

* Refactor ProfileManager exception handling to avoid nulls

* Refactor a few more various classes and fix nulls in ContextInfo

* Force c#7 everywhere and revert usage of newer features from cherry-picked commits

* Remove unused #pragma declaration
2018-03-07 14:37:03 +01:00
ed317a4e46 Refactor VideoPlayer 2018-03-06 21:17:22 +01:00
cca16f3bb1 Release 1.13.2 2018-03-06 18:38:24 +01:00
aba156cb3b Fix typo from refactoring breaking context menu for some links 2018-03-02 06:00:53 +01:00
cd4e4d7095 Fix hashtags and search links being recognized as account links 2018-03-02 05:59:20 +01:00
8fbb639430 Refactor & optimize context menu, send last tweet info only on right-click 2018-03-02 05:24:45 +01:00
d5bf8ec558 Fix missing image/video context menu for tweets that have both media and a quote 2018-03-02 00:15:28 +01:00
b6cff40f1e Warn when checking updates outside TweetDeck & fix visual and unlikely issues 2018-02-28 11:03:55 +01:00
833e42f455 Add a IsTweetDeckWebsite bool to ITweetDeckBrowser 2018-02-28 07:24:40 +01:00
8134843dad Fix background color & twitter.com hooks not applying quickly enough sometimes 2018-02-28 02:34:29 +01:00
1f92d5e633 Remove 'Shift Selects Multiple Accounts' option & fix refocusing after switching account 2018-02-28 01:43:44 +01:00
dc51c0ae85 Remove unnecessary console logging in debug builds 2018-02-26 17:45:56 +01:00
45c79643d6 Why the fuck is TLS 1.2 disabled by default in .NET on some computers 2018-02-25 23:15:00 +01:00
9041bfc627 Tweak search input font size and icon position 2018-02-21 22:17:51 +01:00
0b3b3dd0be Fix a crash when downloading tweet images with no username
Happens when someone accidentally or through dev tools gets to
twitter.com and tries downloading an image.
2018-02-21 19:48:00 +01:00
89e92dab59 Fix middle-clicking on links in desktop notifications not skipping them or stripping t.co 2018-02-19 17:31:28 +01:00
8c168c9ad7 Fix emoji keyboard button size & tweak composer button layout 2018-02-17 12:21:26 +01:00
9f63357a92 Pre-check desktop icon option in installer when not updating 2018-02-17 10:52:19 +01:00
89 changed files with 1894 additions and 1001 deletions

View File

@@ -6,7 +6,6 @@ namespace TweetDuck.Configuration{
// public args // public args
public const string ArgDataFolder = "-datafolder"; public const string ArgDataFolder = "-datafolder";
public const string ArgLogging = "-log"; public const string ArgLogging = "-log";
public const string ArgDebugUpdates = "-debugupdates";
// internal args // internal args
public const string ArgRestart = "-restart"; public const string ArgRestart = "-restart";

View File

@@ -42,7 +42,6 @@ namespace TweetDuck.Configuration{
public WindowState PluginsWindow { get; set; } = new WindowState(); public WindowState PluginsWindow { get; set; } = new WindowState();
public bool ExpandLinksOnHover { get; set; } = true; public bool ExpandLinksOnHover { get; set; } = true;
public bool SwitchAccountSelectors { get; set; } = true;
public bool OpenSearchInFirstColumn { get; set; } = true; public bool OpenSearchInFirstColumn { get; set; } = true;
public bool KeepLikeFollowDialogsOpen { get; set; } = true; public bool KeepLikeFollowDialogsOpen { get; set; } = true;
public bool BestImageQuality { get; set; } = true; public bool BestImageQuality { get; set; } = true;
@@ -51,6 +50,7 @@ namespace TweetDuck.Configuration{
public bool IgnoreTrackingUrlWarning { get; set; } = false; public bool IgnoreTrackingUrlWarning { get; set; } = false;
public bool EnableSmoothScrolling { get; set; } = true; public bool EnableSmoothScrolling { get; set; } = true;
public string BrowserPath { get; set; } = null; public string BrowserPath { get; set; } = null;
public string SearchEngineUrl { get; set; } = null;
private int _zoomLevel = 100; private int _zoomLevel = 100;
private bool _muteNotifications; private bool _muteNotifications;

View File

@@ -1,19 +0,0 @@
using System;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
namespace TweetDuck.Core.Bridge{
sealed class CallbackBridge{
private readonly Control owner;
private readonly Action safeCallback;
public CallbackBridge(Control owner, Action safeCallback){
this.owner = owner;
this.safeCallback = safeCallback;
}
public void Trigger(){
owner.InvokeSafe(safeCallback);
}
}
}

View File

@@ -15,7 +15,6 @@ namespace TweetDuck.Core.Bridge{
build.Append("x.expandLinksOnHover=").Append(Bool(Program.UserConfig.ExpandLinksOnHover)); build.Append("x.expandLinksOnHover=").Append(Bool(Program.UserConfig.ExpandLinksOnHover));
if (environment == Environment.Browser){ 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.openSearchInFirstColumn=").Append(Bool(Program.UserConfig.OpenSearchInFirstColumn));
build.Append("x.keepLikeFollowDialogsOpen=").Append(Bool(Program.UserConfig.KeepLikeFollowDialogsOpen)); build.Append("x.keepLikeFollowDialogsOpen=").Append(Bool(Program.UserConfig.KeepLikeFollowDialogsOpen));
build.Append("x.muteNotifications=").Append(Bool(Program.UserConfig.MuteNotifications)); build.Append("x.muteNotifications=").Append(Bool(Program.UserConfig.MuteNotifications));

View File

@@ -3,7 +3,7 @@ using System.Text;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using CefSharp;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling; using TweetDuck.Core.Management;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
@@ -13,20 +13,12 @@ namespace TweetDuck.Core.Bridge{
class TweetDeckBridge{ class TweetDeckBridge{
public static string FontSize { get; private set; } public static string FontSize { get; private set; }
public static string NotificationHeadLayout { get; private set; } public static string NotificationHeadLayout { get; private set; }
public static readonly ContextInfo ContextInfo = new ContextInfo();
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); private static readonly Dictionary<string, string> SessionData = new Dictionary<string, string>(2);
public static void ResetStaticProperties(){ public static void ResetStaticProperties(){
FontSize = NotificationHeadLayout = null; FontSize = NotificationHeadLayout = null;
LastHighlightedTweetUrl = LastHighlightedQuoteUrl = LastHighlightedTweetAuthors = LastHighlightedTweetImages = string.Empty;
} }
public static void RestoreSessionData(IFrame frame){ public static void RestoreSessionData(IFrame frame){
@@ -72,13 +64,8 @@ namespace TweetDuck.Core.Bridge{
}); });
} }
public void SetLastHighlightedTweet(string tweetUrl, string quoteUrl, string authors, string imageList){ public void SetRightClickedChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
form.InvokeAsyncSafe(() => { ContextInfo.SetChirp(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
LastHighlightedTweetUrl = tweetUrl;
LastHighlightedQuoteUrl = quoteUrl;
LastHighlightedTweetAuthors = authors;
LastHighlightedTweetImages = imageList;
});
} }
public void DisplayTooltip(string text){ public void DisplayTooltip(string text){
@@ -112,8 +99,8 @@ namespace TweetDuck.Core.Bridge{
// Global // Global
public void SetLastRightClickInfo(string type, string link){ public void SetLastRightClickInfo(string type, string url){
form.InvokeAsyncSafe(() => ContextMenuBase.SetContextInfo(type, link)); ContextInfo.SetLink(type, url);
} }
public void OnTweetPopup(string columnId, 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){
@@ -130,8 +117,8 @@ namespace TweetDuck.Core.Bridge{
}); });
} }
public void ScreenshotTweet(string html, int width, int height){ public void ScreenshotTweet(string html, int width){
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height)); form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width));
} }
public void PlayVideo(string url, string username){ public void PlayVideo(string url, string username){

View File

@@ -15,6 +15,7 @@ using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events; using TweetDuck.Plugins.Events;
using TweetDuck.Updates; using TweetDuck.Updates;
using TweetDuck.Updates.Events;
namespace TweetDuck.Core{ namespace TweetDuck.Core{
sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{ sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{
@@ -38,8 +39,8 @@ namespace TweetDuck.Core{
} }
public string UpdateInstallerPath { get; private set; } public string UpdateInstallerPath { get; private set; }
private bool ignoreUpdateCheckError;
public PluginManager PluginManager => plugins;
public AnalyticsFile AnalyticsFile => analytics?.File ?? AnalyticsFile.Dummy; public AnalyticsFile AnalyticsFile => analytics?.File ?? AnalyticsFile.Dummy;
private readonly TweetDeckBrowser browser; private readonly TweetDeckBrowser browser;
@@ -78,8 +79,9 @@ namespace TweetDuck.Core{
Disposed += (sender, args) => { Disposed += (sender, args) => {
Config.MuteToggled -= Config_MuteToggled; Config.MuteToggled -= Config_MuteToggled;
Config.TrayBehaviorChanged -= Config_TrayBehaviorChanged; Config.TrayBehaviorChanged -= Config_TrayBehaviorChanged;
browser.Dispose(); browser.Dispose();
updates.Dispose();
contextMenu.Dispose(); contextMenu.Dispose();
notificationScreenshotManager?.Dispose(); notificationScreenshotManager?.Dispose();
@@ -96,6 +98,7 @@ namespace TweetDuck.Core{
UpdateTrayIcon(); UpdateTrayIcon();
this.updates = new UpdateHandler(browser, updaterSettings); this.updates = new UpdateHandler(browser, updaterSettings);
this.updates.CheckFinished += updates_CheckFinished;
this.updates.UpdateAccepted += updates_UpdateAccepted; this.updates.UpdateAccepted += updates_UpdateAccepted;
this.updates.UpdateDismissed += updates_UpdateDismissed; this.updates.UpdateDismissed += updates_UpdateDismissed;
@@ -233,6 +236,28 @@ namespace TweetDuck.Core{
} }
} }
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
this.InvokeAsyncSafe(() => {
e.Result.Handle(update => {
if (!update.IsUpdateDismissed){
if (update.IsUpdateNew){
browser.ShowUpdateNotification(update.VersionTag, update.ReleaseNotes);
}
else{
updates.StartTimer();
}
}
}, ex => {
if (!ignoreUpdateCheckError){
Program.Reporter.HandleException("Update Check Error", "An error occurred while checking for updates.", true, ex);
updates.StartTimer();
}
});
ignoreUpdateCheckError = true;
});
}
private void updates_UpdateAccepted(object sender, UpdateEventArgs e){ private void updates_UpdateAccepted(object sender, UpdateEventArgs e){
this.InvokeAsyncSafe(() => { this.InvokeAsyncSafe(() => {
FormManager.CloseAllDialogs(); FormManager.CloseAllDialogs();
@@ -241,13 +266,21 @@ namespace TweetDuck.Core{
Config.DismissedUpdate = null; Config.DismissedUpdate = null;
Config.Save(); Config.Save();
} }
updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
if (update.DownloadStatus == UpdateDownloadStatus.Done){
UpdateInstallerPath = update.InstallerPath;
}
ForceClose(); updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
UpdateDownloadStatus status = update.DownloadStatus;
if (status == UpdateDownloadStatus.Done){
UpdateInstallerPath = update.InstallerPath;
ForceClose();
}
else if (status != UpdateDownloadStatus.Canceled && FormMessage.Error("Update Has Failed", "Could not automatically download the update: "+(update.DownloadError?.Message ?? "unknown error")+"\n\nWould you like to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)){
BrowserUtils.OpenExternalBrowser(Program.Website);
ForceClose();
}
else{
Show();
}
}); });
}); });
} }
@@ -298,10 +331,19 @@ namespace TweetDuck.Core{
} }
public void ReloadToTweetDeck(){ public void ReloadToTweetDeck(){
#if DEBUG
Resources.ScriptLoader.HotSwap();
#endif
ignoreUpdateCheckError = false;
browser.ReloadToTweetDeck(); browser.ReloadToTweetDeck();
AnalyticsFile.BrowserReloads.Trigger(); AnalyticsFile.BrowserReloads.Trigger();
} }
public void AddSearchColumn(string query){
browser.AddSearchColumn(query);
}
public void TriggerTweetScreenshot(){ public void TriggerTweetScreenshot(){
browser.TriggerTweetScreenshot(); browser.TriggerTweetScreenshot();
} }
@@ -456,12 +498,12 @@ namespace TweetDuck.Core{
AnalyticsFile.TweetDetails.Trigger(); AnalyticsFile.TweetDetails.Trigger();
} }
public void OnTweetScreenshotReady(string html, int width, int height){ public void OnTweetScreenshotReady(string html, int width){
if (notificationScreenshotManager == null){ if (notificationScreenshotManager == null){
notificationScreenshotManager = new TweetScreenshotManager(this, plugins); notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
} }
notificationScreenshotManager.Trigger(html, width, height); notificationScreenshotManager.Trigger(html, width);
AnalyticsFile.TweetScreenshots.Trigger(); AnalyticsFile.TweetScreenshots.Trigger();
} }

View File

@@ -3,15 +3,15 @@ using System.IO;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using CefSharp;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Management; using TweetDuck.Core.Management;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Core.Other.Analytics; using TweetDuck.Core.Other.Analytics;
using TweetDuck.Resources;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling{
abstract class ContextMenuBase : IContextMenuHandler{ abstract class ContextMenuBase : IContextMenuHandler{
@@ -19,19 +19,6 @@ namespace TweetDuck.Core.Handling{
private static TwitterUtils.ImageQuality ImageQuality => Program.UserConfig.TwitterImageQuality; private static TwitterUtils.ImageQuality ImageQuality => Program.UserConfig.TwitterImageQuality;
private static KeyValuePair<string, string> ContextInfo;
private static bool IsLink => ContextInfo.Key == "link";
private static bool IsImage => ContextInfo.Key == "image";
private static bool IsVideo => ContextInfo.Key == "video";
public static void SetContextInfo(string type, string link){
ContextInfo = new KeyValuePair<string, string>(string.IsNullOrEmpty(link) ? null : type, link);
}
private static string GetMediaLink(IContextMenuParams parameters){
return IsImage || IsVideo ? ContextInfo.Value : parameters.SourceUrl;
}
private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand)26500; private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand)26500;
private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand)26501; private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand)26501;
private const CefMenuCommand MenuCopyUsername = (CefMenuCommand)26502; private const CefMenuCommand MenuCopyUsername = (CefMenuCommand)26502;
@@ -40,10 +27,11 @@ namespace TweetDuck.Core.Handling{
private const CefMenuCommand MenuCopyMediaUrl = (CefMenuCommand)26505; private const CefMenuCommand MenuCopyMediaUrl = (CefMenuCommand)26505;
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26506; private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26506;
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26507; private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26507;
private const CefMenuCommand MenuSearchInBrowser = (CefMenuCommand)26508;
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599; private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
private string[] lastHighlightedTweetAuthors; protected ContextInfo.LinkInfo LastLink { get; private set; }
private string[] lastHighlightedTweetImageList; protected ContextInfo.ChirpInfo LastChirp { get; private set; }
private readonly AnalyticsFile.IProvider analytics; private readonly AnalyticsFile.IProvider analytics;
@@ -51,19 +39,28 @@ namespace TweetDuck.Core.Handling{
this.analytics = analytics; this.analytics = analytics;
} }
private void ResetContextInfo(){
LastLink = default(ContextInfo.LinkInfo);
LastChirp = default(ContextInfo.ChirpInfo);
TweetDeckBridge.ContextInfo.Reset();
}
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){ if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
lastHighlightedTweetAuthors = StringUtils.EmptyArray; ResetContextInfo();
lastHighlightedTweetImageList = StringUtils.EmptyArray;
ContextInfo = default(KeyValuePair<string, string>);
} }
else{ else{
lastHighlightedTweetAuthors = TweetDeckBridge.LastHighlightedTweetAuthorsArray; LastLink = TweetDeckBridge.ContextInfo.Link;
lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImagesArray; LastChirp = TweetDeckBridge.ContextInfo.Chirp;
} }
bool hasTweetImage = IsImage; if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
bool hasTweetVideo = IsVideo; model.AddItem(MenuSearchInBrowser, "Search in browser");
model.AddSeparator();
}
bool hasTweetImage = LastLink.IsImage;
bool hasTweetVideo = LastLink.IsVideo;
string TextOpen(string name) => "Open "+name+" in browser"; string TextOpen(string name) => "Open "+name+" in browser";
string TextCopy(string name) => "Copy "+name+" address"; string TextCopy(string name) => "Copy "+name+" address";
@@ -95,7 +92,7 @@ namespace TweetDuck.Core.Handling{
model.AddItem(MenuCopyMediaUrl, TextCopy("image")); model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
model.AddItem(MenuSaveMedia, TextSave("image")); model.AddItem(MenuSaveMedia, TextSave("image"));
if (lastHighlightedTweetImageList.Length > 1){ if (LastChirp.Images.Length > 1){
model.AddItem(MenuSaveTweetImages, TextSave("all images")); model.AddItem(MenuSaveTweetImages, TextSave("all images"));
} }
@@ -108,11 +105,11 @@ namespace TweetDuck.Core.Handling{
switch(commandId){ switch(commandId){
case MenuOpenLinkUrl: case MenuOpenLinkUrl:
OpenBrowser(control, IsLink ? ContextInfo.Value : parameters.LinkUrl); OpenBrowser(control, LastLink.GetUrl(parameters, true));
break; break;
case MenuCopyLinkUrl: case MenuCopyLinkUrl:
SetClipboardText(control, IsLink ? ContextInfo.Value : parameters.UnfilteredLinkUrl); SetClipboardText(control, LastLink.GetUrl(parameters, false));
break; break;
case MenuCopyUsername: case MenuCopyUsername:
@@ -122,35 +119,37 @@ namespace TweetDuck.Core.Handling{
break; break;
case MenuOpenMediaUrl: case MenuOpenMediaUrl:
OpenBrowser(control, TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality)); OpenBrowser(control, TwitterUtils.GetMediaLink(LastLink.GetMediaSource(parameters), ImageQuality));
break; break;
case MenuCopyMediaUrl: case MenuCopyMediaUrl:
SetClipboardText(control, TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality)); SetClipboardText(control, TwitterUtils.GetMediaLink(LastLink.GetMediaSource(parameters), ImageQuality));
break; break;
case MenuViewImage: case MenuViewImage:
string url = GetMediaLink(parameters); void ViewImage(string path){
string file = Path.Combine(BrowserCache.CacheFolder, TwitterUtils.GetImageFileName(url)); string ext = Path.GetExtension(path);
void ViewFile(){
string ext = Path.GetExtension(file);
if (TwitterUtils.ValidImageExtensions.Contains(ext)){ if (TwitterUtils.ValidImageExtensions.Contains(ext)){
WindowsUtils.OpenAssociatedProgram(file); WindowsUtils.OpenAssociatedProgram(path);
} }
else{ else{
FormMessage.Error("Image Download", "Invalid file extension "+ext, FormMessage.OK); FormMessage.Error("Image Download", "Invalid file extension "+ext, FormMessage.OK);
} }
} }
string url = LastLink.GetMediaSource(parameters);
string file = Path.Combine(BrowserCache.CacheFolder, TwitterUtils.GetImageFileName(url) ?? Path.GetRandomFileName());
if (File.Exists(file)){ if (File.Exists(file)){
ViewFile(); ViewImage(file);
} }
else{ else{
control.InvokeAsyncSafe(analytics.AnalyticsFile.ViewedImages.Trigger); control.InvokeAsyncSafe(analytics.AnalyticsFile.ViewedImages.Trigger);
BrowserUtils.DownloadFileAsync(TwitterUtils.GetMediaLink(url, ImageQuality), file, ViewFile, ex => { BrowserUtils.DownloadFileAsync(TwitterUtils.GetMediaLink(url, ImageQuality), file, () => {
ViewImage(file);
}, ex => {
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK); FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
}); });
} }
@@ -158,20 +157,26 @@ namespace TweetDuck.Core.Handling{
break; break;
case MenuSaveMedia: case MenuSaveMedia:
if (IsVideo){ if (LastLink.IsVideo){
control.InvokeAsyncSafe(analytics.AnalyticsFile.DownloadedVideos.Trigger); control.InvokeAsyncSafe(analytics.AnalyticsFile.DownloadedVideos.Trigger);
TwitterUtils.DownloadVideo(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault()); TwitterUtils.DownloadVideo(LastLink.GetMediaSource(parameters), LastChirp.Authors.LastOrDefault());
} }
else{ else{
control.InvokeAsyncSafe(analytics.AnalyticsFile.DownloadedImages.Trigger); control.InvokeAsyncSafe(analytics.AnalyticsFile.DownloadedImages.Trigger);
TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality); TwitterUtils.DownloadImage(LastLink.GetMediaSource(parameters), LastChirp.Authors.LastOrDefault(), ImageQuality);
} }
break; break;
case MenuSaveTweetImages: case MenuSaveTweetImages:
control.InvokeAsyncSafe(analytics.AnalyticsFile.DownloadedImages.Trigger); control.InvokeAsyncSafe(analytics.AnalyticsFile.DownloadedImages.Trigger);
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality); TwitterUtils.DownloadImages(LastChirp.Images, LastChirp.Authors.LastOrDefault(), ImageQuality);
break;
case MenuSearchInBrowser:
string query = parameters.SelectionText;
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalSearch(query));
DeselectAll(frame);
break; break;
case MenuOpenDevTools: case MenuOpenDevTools:
@@ -183,13 +188,17 @@ namespace TweetDuck.Core.Handling{
} }
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){ public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
ContextInfo = default(KeyValuePair<string, string>); ResetContextInfo();
} }
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){ public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
return false; return false;
} }
protected void DeselectAll(IFrame frame){
ScriptLoader.ExecuteScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect");
}
protected void OpenBrowser(Control control, string url){ protected void OpenBrowser(Control control, string url){
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url)); control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
} }
@@ -197,6 +206,10 @@ namespace TweetDuck.Core.Handling{
protected void SetClipboardText(Control control, string text){ protected void SetClipboardText(Control control, string text){
control.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText)); control.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
} }
protected static void InsertSelectionSearchItem(IMenuModel model, CefMenuCommand insertCommand, string insertLabel){
model.InsertItemAt(model.GetIndexOf(MenuSearchInBrowser)+1, insertCommand, insertLabel);
}
protected static void AddDebugMenuItems(IMenuModel model){ protected static void AddDebugMenuItems(IMenuModel model){
model.AddItem(MenuOpenDevTools, "Open dev tools"); model.AddItem(MenuOpenDevTools, "Open dev tools");

View File

@@ -1,6 +1,5 @@
using CefSharp; using CefSharp;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
@@ -18,6 +17,7 @@ namespace TweetDuck.Core.Handling{
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26613; private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26613;
private const CefMenuCommand MenuScreenshotTweet = (CefMenuCommand)26614; private const CefMenuCommand MenuScreenshotTweet = (CefMenuCommand)26614;
private const CefMenuCommand MenuInputApplyROT13 = (CefMenuCommand)26615; private const CefMenuCommand MenuInputApplyROT13 = (CefMenuCommand)26615;
private const CefMenuCommand MenuSearchInColumn = (CefMenuCommand)26616;
private const string TitleReloadBrowser = "Reload browser"; private const string TitleReloadBrowser = "Reload browser";
private const string TitleMuteNotifications = "Mute notifications"; private const string TitleMuteNotifications = "Mute notifications";
@@ -26,23 +26,23 @@ namespace TweetDuck.Core.Handling{
private const string TitleAboutProgram = "About "+Program.BrandName; private const string TitleAboutProgram = "About "+Program.BrandName;
private readonly FormBrowser form; private readonly FormBrowser form;
private string lastHighlightedTweetUrl;
private string lastHighlightedQuoteUrl;
public ContextMenuBrowser(FormBrowser form) : base(form){ public ContextMenuBrowser(FormBrowser form) : base(form){
this.form = form; this.form = form;
} }
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
bool isSelecting = parameters.TypeFlags.HasFlag(ContextMenuType.Selection);
bool isEditing = parameters.TypeFlags.HasFlag(ContextMenuType.Editable);
model.Remove(CefMenuCommand.Back); model.Remove(CefMenuCommand.Back);
model.Remove(CefMenuCommand.Forward); model.Remove(CefMenuCommand.Forward);
model.Remove(CefMenuCommand.Print); model.Remove(CefMenuCommand.Print);
model.Remove(CefMenuCommand.ViewSource); model.Remove(CefMenuCommand.ViewSource);
RemoveSeparatorIfLast(model); RemoveSeparatorIfLast(model);
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){ if (isSelecting){
if (parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){ if (isEditing){
model.AddSeparator(); model.AddSeparator();
model.AddItem(MenuInputApplyROT13, "Apply ROT13"); model.AddItem(MenuInputApplyROT13, "Apply ROT13");
} }
@@ -52,20 +52,16 @@ namespace TweetDuck.Core.Handling{
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model); base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
lastHighlightedTweetUrl = TweetDeckBridge.LastHighlightedTweetUrl; if (isSelecting && !isEditing && TwitterUtils.IsTweetDeckWebsite(frame)){
lastHighlightedQuoteUrl = TweetDeckBridge.LastHighlightedQuoteUrl; InsertSelectionSearchItem(model, MenuSearchInColumn, "Search in a column");
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
lastHighlightedTweetUrl = string.Empty;
lastHighlightedQuoteUrl = string.Empty;
} }
if (!string.IsNullOrEmpty(lastHighlightedTweetUrl) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){ if (!string.IsNullOrEmpty(LastChirp.TweetUrl) && !isSelecting && !isEditing){
model.AddItem(MenuOpenTweetUrl, "Open tweet in browser"); model.AddItem(MenuOpenTweetUrl, "Open tweet in browser");
model.AddItem(MenuCopyTweetUrl, "Copy tweet address"); model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard"); model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard");
if (!string.IsNullOrEmpty(lastHighlightedQuoteUrl)){ if (!string.IsNullOrEmpty(LastChirp.QuoteUrl)){
model.AddSeparator(); model.AddSeparator();
model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser"); model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address"); model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
@@ -74,7 +70,7 @@ namespace TweetDuck.Core.Handling{
model.AddSeparator(); model.AddSeparator();
} }
if ((parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){ if (!isSelecting && !isEditing){
AddSeparator(model); AddSeparator(model);
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName); IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName);
@@ -126,11 +122,11 @@ namespace TweetDuck.Core.Handling{
return true; return true;
case MenuOpenTweetUrl: case MenuOpenTweetUrl:
OpenBrowser(form, lastHighlightedTweetUrl); OpenBrowser(form, LastChirp.TweetUrl);
return true; return true;
case MenuCopyTweetUrl: case MenuCopyTweetUrl:
SetClipboardText(form, lastHighlightedTweetUrl); SetClipboardText(form, LastChirp.TweetUrl);
return true; return true;
case MenuScreenshotTweet: case MenuScreenshotTweet:
@@ -138,16 +134,22 @@ namespace TweetDuck.Core.Handling{
return true; return true;
case MenuOpenQuotedTweetUrl: case MenuOpenQuotedTweetUrl:
OpenBrowser(form, lastHighlightedQuoteUrl); OpenBrowser(form, LastChirp.QuoteUrl);
return true; return true;
case MenuCopyQuotedTweetUrl: case MenuCopyQuotedTweetUrl:
SetClipboardText(form, lastHighlightedQuoteUrl); SetClipboardText(form, LastChirp.QuoteUrl);
return true; return true;
case MenuInputApplyROT13: case MenuInputApplyROT13:
form.InvokeAsyncSafe(form.ApplyROT13); form.InvokeAsyncSafe(form.ApplyROT13);
return true; return true;
case MenuSearchInColumn:
string query = parameters.SelectionText;
form.InvokeAsyncSafe(() => form.AddSearchColumn(query));
DeselectAll(frame);
break;
} }
return false; return false;

View File

@@ -1,7 +1,6 @@
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using CefSharp;
using CefSharp.WinForms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
@@ -28,7 +27,7 @@ namespace TweetDuck.Core.Handling.General{
} }
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){ bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){
((ChromiumWebBrowser)browserControl).InvokeSafe(() => { browserControl.AsControl().InvokeSafe(() => {
FormMessage form; FormMessage form;
TextBox input = null; TextBox input = null;

View File

@@ -0,0 +1,68 @@
using CefSharp;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Management{
sealed class ContextInfo{
public LinkInfo Link { get; private set; }
public ChirpInfo Chirp { get; private set; }
public ContextInfo(){
Reset();
}
public void SetLink(string type, string url){
Link = string.IsNullOrEmpty(url) ? new LinkInfo() : new LinkInfo(type, url);
}
public void SetChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
Chirp = new ChirpInfo(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
}
public void Reset(){
Link = new LinkInfo();
Chirp = new ChirpInfo();
}
// Data structures
public struct LinkInfo{
public bool IsLink => type == "link";
public bool IsImage => type == "image";
public bool IsVideo => type == "video";
public string GetUrl(IContextMenuParams parameters, bool safe){
return IsLink ? url : (safe ? parameters.LinkUrl : parameters.UnfilteredLinkUrl);
}
public string GetMediaSource(IContextMenuParams parameters){
return IsImage || IsVideo ? url : parameters.SourceUrl;
}
private readonly string type;
private readonly string url;
public LinkInfo(string type, string url){
this.type = type;
this.url = url;
}
}
public struct ChirpInfo{
public string TweetUrl { get; }
public string QuoteUrl { get; }
public string[] Authors => chirpAuthors?.Split(';') ?? StringUtils.EmptyArray;
public string[] Images => chirpImages?.Split(';') ?? StringUtils.EmptyArray;
private readonly string chirpAuthors;
private readonly string chirpImages;
public ChirpInfo(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
this.TweetUrl = tweetUrl;
this.QuoteUrl = quoteUrl;
this.chirpAuthors = chirpAuthors;
this.chirpImages = chirpImages;
}
}
}
}

View File

@@ -23,7 +23,6 @@ namespace TweetDuck.Core.Management{
} }
public bool IsRestarting { get; private set; } public bool IsRestarting { get; private set; }
public Exception LastException { get; private set; }
private readonly string file; private readonly string file;
private readonly PluginManager plugins; private readonly PluginManager plugins;
@@ -67,7 +66,7 @@ namespace TweetDuck.Core.Management{
return true; return true;
}catch(Exception e){ }catch(Exception e){
LastException = e; Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, e);
return false; return false;
} }
} }
@@ -100,8 +99,7 @@ namespace TweetDuck.Core.Management{
} }
} }
} }
}catch(Exception e){ }catch(Exception){
LastException = e;
items = Items.None; items = Items.None;
} }
@@ -164,12 +162,12 @@ namespace TweetDuck.Core.Management{
} }
if (missingPlugins.Count > 0){ if (missingPlugins.Count > 0){
FormMessage.Information("Importing TweetDuck Profile", "Detected missing plugins when importing plugin data:\n"+string.Join("\n", missingPlugins), FormMessage.OK); FormMessage.Information("Profile Import", "Detected missing plugins when importing plugin data:\n"+string.Join("\n", missingPlugins), FormMessage.OK);
} }
return true; return true;
}catch(Exception e){ }catch(Exception e){
LastException = e; Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing TweetDuck profile.", true, e);
return false; return false;
} }
} }
@@ -199,15 +197,23 @@ namespace TweetDuck.Core.Management{
} }
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){ private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
return Directory.Exists(root) ? Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{ if (Directory.Exists(root)){
Full = fullPath, int rootLength = root.Length;
Relative = fullPath.Substring(root.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) // strip leading separator character return Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo(fullPath, rootLength));
}) : Enumerable.Empty<PathInfo>(); }
else{
return Enumerable.Empty<PathInfo>();
}
} }
private sealed class PathInfo{ private sealed class PathInfo{
public string Full { get; set; } public string Full { get; }
public string Relative { get; set; } public string Relative { get; }
public PathInfo(string fullPath, int rootLength){
this.Full = fullPath;
this.Relative = fullPath.Substring(rootLength).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); // strip leading separator character
}
} }
} }

View File

@@ -9,25 +9,13 @@ using TweetLib.Communication;
namespace TweetDuck.Core.Management{ namespace TweetDuck.Core.Management{
sealed class VideoPlayer : IDisposable{ sealed class VideoPlayer : IDisposable{
public bool Running{ public bool Running => currentInstance != null && currentInstance.Running;
get{
if (currentProcess == null){
return false;
}
currentProcess.Refresh();
return !currentProcess.HasExited;
}
}
public event EventHandler ProcessExited; public event EventHandler ProcessExited;
private readonly FormBrowser owner; private readonly FormBrowser owner;
private string lastUrl;
private string lastUsername;
private Process currentProcess; private Instance currentInstance;
private DuplexPipe.Server currentPipe;
private bool isClosing; private bool isClosing;
public VideoPlayer(FormBrowser owner){ public VideoPlayer(FormBrowser owner){
@@ -40,38 +28,43 @@ namespace TweetDuck.Core.Management{
Destroy(); Destroy();
isClosing = false; isClosing = false;
} }
lastUrl = url;
lastUsername = username;
try{ try{
currentPipe = DuplexPipe.CreateServer(); DuplexPipe.Server pipe = DuplexPipe.CreateServer();
currentPipe.DataIn += currentPipe_DataIn; pipe.DataIn += pipe_DataIn;
if ((currentProcess = Process.Start(new ProcessStartInfo{ Process process;
if ((process = Process.Start(new ProcessStartInfo{
FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"), FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"),
Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\" \"{currentPipe.GenerateToken()}\"", Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\" \"{pipe.GenerateToken()}\"",
UseShellExecute = false, UseShellExecute = false,
RedirectStandardOutput = true RedirectStandardOutput = true
})) != null){ })) != null){
currentProcess.EnableRaisingEvents = true; currentInstance = new Instance(process, pipe, url, username);
currentProcess.Exited += process_Exited;
currentProcess.BeginOutputReadLine();
currentProcess.OutputDataReceived += process_OutputDataReceived;
}
currentPipe.DisposeToken(); process.EnableRaisingEvents = true;
process.Exited += process_Exited;
process.BeginOutputReadLine();
process.OutputDataReceived += process_OutputDataReceived;
pipe.DisposeToken();
}
else{
pipe.DataIn -= pipe_DataIn;
pipe.Dispose();
}
}catch(Exception e){ }catch(Exception e){
Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e); Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e);
} }
} }
public void SendKeyEvent(Keys key){ public void SendKeyEvent(Keys key){
currentPipe?.Write("key", ((int)key).ToString()); currentInstance?.Pipe.Write("key", ((int)key).ToString());
} }
private void currentPipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){ private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
owner.InvokeSafe(() => { owner.InvokeSafe(() => {
switch(e.Key){ switch(e.Key){
case "vol": case "vol":
@@ -83,16 +76,16 @@ namespace TweetDuck.Core.Management{
break; break;
case "download": case "download":
owner.AnalyticsFile.DownloadedVideos.Trigger(); if (currentInstance != null){
TwitterUtils.DownloadVideo(lastUrl, lastUsername); owner.AnalyticsFile.DownloadedVideos.Trigger();
TwitterUtils.DownloadVideo(currentInstance.Url, currentInstance.Username);
}
break; break;
case "rip": case "rip":
currentPipe.Dispose(); currentInstance?.Dispose();
currentPipe = null; currentInstance = null;
currentProcess.Dispose();
currentProcess = null;
isClosing = false; isClosing = false;
TriggerProcessExitEventUnsafe(); TriggerProcessExitEventUnsafe();
@@ -102,15 +95,15 @@ namespace TweetDuck.Core.Management{
} }
public void Close(){ public void Close(){
if (currentProcess != null){ if (currentInstance != null){
if (isClosing){ if (isClosing){
Destroy(); Destroy();
isClosing = false; isClosing = false;
} }
else{ else{
isClosing = true; isClosing = true;
currentProcess.Exited -= process_Exited; currentInstance.Process.Exited -= process_Exited;
currentPipe.Write("die"); currentInstance.Pipe.Write("die");
} }
} }
} }
@@ -123,26 +116,17 @@ namespace TweetDuck.Core.Management{
} }
private void Destroy(){ private void Destroy(){
if (currentProcess != null){ if (currentInstance != null){
try{ currentInstance.KillAndDispose();
currentProcess.Kill(); currentInstance = null;
}catch{
// kill me instead then
}
currentProcess.Dispose();
currentProcess = null;
currentPipe.Dispose();
currentPipe = null;
TriggerProcessExitEventUnsafe(); TriggerProcessExitEventUnsafe();
} }
} }
private void owner_FormClosing(object sender, FormClosingEventArgs e){ private void owner_FormClosing(object sender, FormClosingEventArgs e){
if (currentProcess != null){ if (currentInstance != null){
currentProcess.Exited -= process_Exited; currentInstance.Process.Exited -= process_Exited;
} }
} }
@@ -153,25 +137,27 @@ namespace TweetDuck.Core.Management{
} }
private void process_Exited(object sender, EventArgs e){ private void process_Exited(object sender, EventArgs e){
int exitCode = currentProcess.ExitCode; if (currentInstance == null){
return;
}
currentProcess.Dispose(); int exitCode = currentInstance.Process.ExitCode;
currentProcess = null; string url = currentInstance.Url;
currentPipe.Dispose(); currentInstance.Dispose();
currentPipe = null; currentInstance = null;
switch(exitCode){ switch(exitCode){
case 3: // CODE_LAUNCH_FAIL 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 your browser?", FormMessage.Yes, FormMessage.No)){ 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 your browser?", FormMessage.Yes, FormMessage.No)){
BrowserUtils.OpenExternalBrowser(lastUrl); BrowserUtils.OpenExternalBrowser(url);
} }
break; break;
case 4: // CODE_MEDIA_ERROR case 4: // CODE_MEDIA_ERROR
if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){ if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
BrowserUtils.OpenExternalBrowser(lastUrl); BrowserUtils.OpenExternalBrowser(url);
} }
break; break;
@@ -183,5 +169,42 @@ namespace TweetDuck.Core.Management{
private void TriggerProcessExitEventUnsafe(){ private void TriggerProcessExitEventUnsafe(){
ProcessExited?.Invoke(this, EventArgs.Empty); ProcessExited?.Invoke(this, EventArgs.Empty);
} }
private sealed class Instance : IDisposable{
public bool Running{
get{
Process.Refresh();
return !Process.HasExited;
}
}
public Process Process { get; }
public DuplexPipe.Server Pipe { get; }
public string Url { get; }
public string Username { get; }
public Instance(Process process, DuplexPipe.Server pipe, string url, string username){
this.Process = process;
this.Pipe = pipe;
this.Url = url;
this.Username = username;
}
public void KillAndDispose(){
try{
Process.Kill();
}catch{
// kill me instead then
}
Dispose();
}
public void Dispose(){
Process.Dispose();
Pipe.Dispose();
}
}
} }
} }

View File

@@ -24,7 +24,7 @@ namespace TweetDuck.Core.Notification.Example{
private readonly TweetNotification exampleNotification; private readonly TweetNotification exampleNotification;
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){ public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){
string exampleTweetHTML = ScriptLoader.LoadResource("pages/example.html", true).Replace("{avatar}", TweetNotification.AppLogo.Url); string exampleTweetHTML = ScriptLoader.LoadResource("pages/example.html", true)?.Replace("{avatar}", TweetNotification.AppLogo.Url) ?? string.Empty;
#if DEBUG #if DEBUG
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>"); exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");

View File

@@ -134,13 +134,7 @@ namespace TweetDuck.Core.Notification{
this.browser.Dock = DockStyle.None; this.browser.Dock = DockStyle.None;
this.browser.ClientSize = ClientSize; this.browser.ClientSize = ClientSize;
this.browser.IsBrowserInitializedChanged += browser_IsBrowserInitializedChanged; this.browser.IsBrowserInitializedChanged += browser_IsBrowserInitializedChanged;
#if DEBUG
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif
DpiScale = this.GetDPIScale();
browser.SetupResourceHandler(TwitterUtils.TweetDeckURL, this.resourceHandler); browser.SetupResourceHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
browser.SetupResourceHandler(TweetNotification.AppLogo); browser.SetupResourceHandler(TweetNotification.AppLogo);
@@ -151,6 +145,8 @@ namespace TweetDuck.Core.Notification{
this.owner.FormClosed -= owner_FormClosed; this.owner.FormClosed -= owner_FormClosed;
}; };
DpiScale = this.GetDPIScale();
// ReSharper disable once VirtualMemberCallInContructor // ReSharper disable once VirtualMemberCallInContructor
UpdateTitle(); UpdateTitle();
} }
@@ -200,7 +196,7 @@ namespace TweetDuck.Core.Notification{
} }
protected virtual string GetTweetHTML(TweetNotification tweet){ protected virtual string GetTweetHTML(TweetNotification tweet){
return tweet.GenerateHtml(IsCursorOverBrowser ? "td-hover" : string.Empty); return tweet.GenerateHtml(IsCursorOverBrowser ? "td-notification td-hover" : "td-notification");
} }
protected virtual void LoadTweet(TweetNotification tweet){ protected virtual void LoadTweet(TweetNotification tweet){
@@ -221,7 +217,9 @@ namespace TweetDuck.Core.Notification{
} }
public void ShowTweetDetail(){ public void ShowTweetDetail(){
owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl); if (currentNotification != null){
owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl);
}
} }
public void MoveToVisibleLocation(){ public void MoveToVisibleLocation(){

View File

@@ -5,6 +5,7 @@ using System.Windows.Forms;
using TweetDuck.Core.Bridge; using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling; using TweetDuck.Core.Handling;
using TweetDuck.Core.Other.Interfaces;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data; using TweetDuck.Data;
using TweetDuck.Plugins; using TweetDuck.Plugins;
@@ -92,6 +93,10 @@ namespace TweetDuck.Core.Notification{
Disposed += (sender, args) => StopMouseHook(true); Disposed += (sender, args) => StopMouseHook(true);
} }
// implementation of ITweetDeckBrowser
bool ITweetDeckBrowser.IsTweetDeckWebsite => IsNotificationVisible;
void ITweetDeckBrowser.RegisterBridge(string name, object obj){ void ITweetDeckBrowser.RegisterBridge(string name, object obj){
browser.RegisterAsyncJsObject(name, obj); browser.RegisterAsyncJsObject(name, obj);
} }

View File

@@ -3,7 +3,7 @@ using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using CefSharp;
using TweetDuck.Core.Bridge; using TweetDuck.Core.Controls;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data; using TweetDuck.Data;
@@ -15,23 +15,37 @@ namespace TweetDuck.Core.Notification.Screenshot{
protected override bool CanDragWindow => false; protected override bool CanDragWindow => false;
private readonly PluginManager plugins; private readonly PluginManager plugins;
private readonly int width;
public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager) : base(owner, false){ public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager, string html, int width) : base(owner, false){
this.plugins = pluginManager; this.plugins = pluginManager;
this.width = width;
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback)); browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new ScreenshotBridge(this, SetScreenshotHeight, callback));
browser.LoadingStateChanged += (sender, args) => { browser.LoadingStateChanged += (sender, args) => {
if (!args.IsLoading){ if (args.IsLoading){
using(IFrame frame = args.Browser.MainFrame){ return;
ScriptLoader.ExecuteScript(frame, "window.setTimeout($TD_NotificationScreenshot.trigger, document.getElementsByTagName('iframe').length ? 267 : 67)", "gen:screenshot"); }
}
string script = ScriptLoader.LoadResource("screenshot.js", true);
if (script == null){
this.InvokeAsyncSafe(callback);
return;
}
using(IFrame frame = args.Browser.MainFrame){
ScriptLoader.ExecuteScript(frame, script.Replace("{width}", ClientSize.Width.ToString()), "screenshot");
} }
}; };
SetScreenshotHeight(1);
LoadTweet(new TweetNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty));
} }
protected override string GetTweetHTML(TweetNotification tweet){ protected override string GetTweetHTML(TweetNotification tweet){
string html = tweet.GenerateHtml("td-screenshot", false); string html = tweet.GenerateHtml("td-screenshot");
foreach(InjectedHTML injection in plugins.NotificationInjections){ foreach(InjectedHTML injection in plugins.NotificationInjections){
html = injection.Inject(html); html = injection.Inject(html);
@@ -40,12 +54,16 @@ namespace TweetDuck.Core.Notification.Screenshot{
return html; return html;
} }
public void LoadNotificationForScreenshot(TweetNotification tweet, int width, int height){ private void SetScreenshotHeight(int height){
LoadTweet(tweet);
SetNotificationSize(width, height); SetNotificationSize(width, height);
} }
public void TakeScreenshot(){ public void TakeScreenshot(){
if (ClientSize.Height == 0){
FormMessage.Error("Screenshot Failed", "Could not detect screenshot size.", FormMessage.OK);
return;
}
IntPtr context = NativeMethods.GetDC(this.Handle); IntPtr context = NativeMethods.GetDC(this.Handle);
if (context == IntPtr.Zero){ if (context == IntPtr.Zero){

View File

@@ -0,0 +1,26 @@
using System;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
namespace TweetDuck.Core.Notification.Screenshot{
sealed class ScreenshotBridge{
private readonly Control owner;
private readonly Action<int> safeSetHeight;
private readonly Action safeTriggerScreenshot;
public ScreenshotBridge(Control owner, Action<int> safeSetHeight, Action safeTriggerScreenshot){
this.owner = owner;
this.safeSetHeight = safeSetHeight;
this.safeTriggerScreenshot = safeTriggerScreenshot;
}
public void SetHeight(int tweetHeight){
owner.InvokeSafe(() => safeSetHeight(tweetHeight));
}
public void TriggerScreenshot(){
owner.InvokeSafe(safeTriggerScreenshot);
}
}
}

View File

@@ -37,13 +37,12 @@ namespace TweetDuck.Core.Notification.Screenshot{
screenshot = null; screenshot = null;
} }
public void Trigger(string html, int width, int height){ public void Trigger(string html, int width){
if (screenshot != null){ if (screenshot != null){
return; return;
} }
screenshot = new FormNotificationScreenshotable(Callback, owner, plugins); screenshot = new FormNotificationScreenshotable(Callback, owner, plugins, html, width);
screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty), width, height);
screenshot.Show(); screenshot.Show();
timeout.Start(); timeout.Start();

View File

@@ -8,7 +8,7 @@ using TweetDuck.Resources;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
sealed class TweetNotification{ sealed class TweetNotification{
private const string DefaultHeadLayout = @"<html id='tduck' class='os-windows txt-size--14' data-td-font='medium' data-td-theme='dark'><head><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 DefaultHeadLayout = @"<html id='tduck' class='os-windows txt-size--14' data-td-font='medium' data-td-theme='dark'><head><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 static readonly string CustomCSS = ScriptLoader.LoadResource("styles/notification.css"); private static readonly string CustomCSS = ScriptLoader.LoadResource("styles/notification.css") ?? string.Empty;
public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandler.FromByteArray(Properties.Resources.avatar, "image/png")); public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandler.FromByteArray(Properties.Resources.avatar, "image/png"));
public static TweetNotification Example(string html, int characters){ public static TweetNotification Example(string html, int characters){
@@ -53,17 +53,14 @@ namespace TweetDuck.Core.Notification{
return 2000+Math.Max(1000, value*characters); return 2000+Math.Max(1000, value*characters);
} }
public string GenerateHtml(string bodyClasses = null, bool enableCustomCSS = true){ public string GenerateHtml(string bodyClasses){
StringBuilder build = new StringBuilder(); StringBuilder build = new StringBuilder();
build.Append("<!DOCTYPE html>"); build.Append("<!DOCTYPE html>");
build.Append(TweetDeckBridge.NotificationHeadLayout ?? DefaultHeadLayout); build.Append(TweetDeckBridge.NotificationHeadLayout ?? DefaultHeadLayout);
build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>");
if (enableCustomCSS){
build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>");
if (!string.IsNullOrEmpty(Program.UserConfig.CustomNotificationCSS)){ if (!string.IsNullOrEmpty(Program.UserConfig.CustomNotificationCSS)){
build.Append("<style type='text/css'>").Append(Program.UserConfig.CustomNotificationCSS).Append("</style>"); build.Append("<style type='text/css'>").Append(Program.UserConfig.CustomNotificationCSS).Append("</style>");
}
} }
build.Append("</head>"); build.Append("</head>");

View File

@@ -21,7 +21,7 @@ namespace TweetDuck.Core.Other.Analytics{
}); });
} }
public static readonly AnalyticsFile Dummy = new AnalyticsFile(null); public static readonly AnalyticsFile Dummy = new AnalyticsFile();
// STATE PROPERTIES // STATE PROPERTIES
@@ -67,6 +67,10 @@ namespace TweetDuck.Core.Other.Analytics{
this.file = file; this.file = file;
} }
private AnalyticsFile(){
this.file = null;
}
private void SetupProperties(){ private void SetupProperties(){
foreach(Counter counter in GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.PropertyType == typeof(Counter)).Select(prop => (Counter)prop.GetValue(this))){ foreach(Counter counter in GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.PropertyType == typeof(Counter)).Select(prop => (Counter)prop.GetValue(this))){
counter.SetOwner(this); counter.SetOwner(this);

View File

@@ -42,7 +42,7 @@ namespace TweetDuck.Core.Other.Analytics{
{ "Clear Cache Threshold" , Exact(SysConfig.ClearCacheThreshold) }, { "Clear Cache Threshold" , Exact(SysConfig.ClearCacheThreshold) },
0, 0,
{ "Expand Links" , Bool(UserConfig.ExpandLinksOnHover) }, { "Expand Links" , Bool(UserConfig.ExpandLinksOnHover) },
{ "Switch Account Selectors" , Bool(UserConfig.SwitchAccountSelectors) }, { "Switch Account Selectors" , Bool(false) }, // TODO remove in next major update
{ "Search In First Column" , Bool(UserConfig.OpenSearchInFirstColumn) }, { "Search In First Column" , Bool(UserConfig.OpenSearchInFirstColumn) },
{ "Keep Like Follow Dialogs Open" , Bool(UserConfig.KeepLikeFollowDialogsOpen) }, { "Keep Like Follow Dialogs Open" , Bool(UserConfig.KeepLikeFollowDialogsOpen) },
{ "Best Image Quality" , Bool(UserConfig.BestImageQuality) }, { "Best Image Quality" , Bool(UserConfig.BestImageQuality) },

View File

@@ -58,11 +58,8 @@ namespace TweetDuck.Core.Other{
InitializeComponent(); InitializeComponent();
Text = Program.BrandName+" Guide"; Text = Program.BrandName+" Guide";
Size = new Size(owner.Size.Width*3/4, owner.Size.Height*3/4);
if (owner != null){ VisibleChanged += (sender, args) => this.MoveToCenter(owner);
Size = new Size(owner.Size.Width*3/4, owner.Size.Height*3/4);
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
}
this.browser = new ChromiumWebBrowser(url){ this.browser = new ChromiumWebBrowser(url){
MenuHandler = new ContextMenuGuide(owner), MenuHandler = new ContextMenuGuide(owner),

View File

@@ -33,7 +33,7 @@
// //
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnClose.AutoSize = true; this.btnClose.AutoSize = true;
this.btnClose.Location = new System.Drawing.Point(449, 483); this.btnClose.Location = new System.Drawing.Point(449, 504);
this.btnClose.Name = "btnClose"; this.btnClose.Name = "btnClose";
this.btnClose.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.btnClose.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnClose.Size = new System.Drawing.Size(49, 23); this.btnClose.Size = new System.Drawing.Size(49, 23);
@@ -52,7 +52,7 @@
this.panelContents.Location = new System.Drawing.Point(135, 12); this.panelContents.Location = new System.Drawing.Point(135, 12);
this.panelContents.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.panelContents.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3);
this.panelContents.Name = "panelContents"; this.panelContents.Name = "panelContents";
this.panelContents.Size = new System.Drawing.Size(363, 465); this.panelContents.Size = new System.Drawing.Size(363, 486);
this.panelContents.TabIndex = 1; this.panelContents.TabIndex = 1;
// //
// panelButtons // panelButtons
@@ -63,14 +63,14 @@
this.panelButtons.Location = new System.Drawing.Point(12, 12); this.panelButtons.Location = new System.Drawing.Point(12, 12);
this.panelButtons.Margin = new System.Windows.Forms.Padding(3, 3, 0, 3); this.panelButtons.Margin = new System.Windows.Forms.Padding(3, 3, 0, 3);
this.panelButtons.Name = "panelButtons"; this.panelButtons.Name = "panelButtons";
this.panelButtons.Size = new System.Drawing.Size(124, 465); this.panelButtons.Size = new System.Drawing.Size(124, 486);
this.panelButtons.TabIndex = 0; this.panelButtons.TabIndex = 0;
// //
// btnManageOptions // btnManageOptions
// //
this.btnManageOptions.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.btnManageOptions.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnManageOptions.AutoSize = true; this.btnManageOptions.AutoSize = true;
this.btnManageOptions.Location = new System.Drawing.Point(12, 483); this.btnManageOptions.Location = new System.Drawing.Point(12, 504);
this.btnManageOptions.Name = "btnManageOptions"; this.btnManageOptions.Name = "btnManageOptions";
this.btnManageOptions.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.btnManageOptions.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnManageOptions.Size = new System.Drawing.Size(101, 23); this.btnManageOptions.Size = new System.Drawing.Size(101, 23);
@@ -83,7 +83,7 @@
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(510, 518); this.ClientSize = new System.Drawing.Size(510, 539);
this.Controls.Add(this.btnManageOptions); this.Controls.Add(this.btnManageOptions);
this.Controls.Add(this.panelContents); this.Controls.Add(this.panelContents);
this.Controls.Add(this.panelButtons); this.Controls.Add(this.panelButtons);

View File

@@ -36,7 +36,7 @@ namespace TweetDuck.Core.Other{
this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1; this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1;
AddButton("General", () => new TabSettingsGeneral(this.browser, updates)); AddButton("General", () => new TabSettingsGeneral(this.browser.ReloadColumns, updates));
AddButton("Locales", () => new TabSettingsLocales()); AddButton("Locales", () => new TabSettingsLocales());
AddButton("System Tray", () => new TabSettingsTray()); AddButton("System Tray", () => new TabSettingsTray());
AddButton("Notifications", () => new TabSettingsNotifications(new FormNotificationExample(this.browser, this.plugins))); AddButton("Notifications", () => new TabSettingsNotifications(new FormNotificationExample(this.browser, this.plugins)));
@@ -156,6 +156,10 @@ namespace TweetDuck.Core.Other{
} }
private void control_MouseLeave(object sender, EventArgs e){ private void control_MouseLeave(object sender, EventArgs e){
if (sender is ComboBox cb && cb.DroppedDown){
return; // prevents comboboxes from closing when MouseLeave event triggers during opening animation
}
panelContents.Focus(); panelContents.Focus();
} }

View File

@@ -1,8 +1,10 @@
using System; using System;
using CefSharp; using CefSharp;
namespace TweetDuck.Core{ namespace TweetDuck.Core.Other.Interfaces{
interface ITweetDeckBrowser{ interface ITweetDeckBrowser{
bool IsTweetDeckWebsite { get; }
void RegisterBridge(string name, object obj); void RegisterBridge(string name, object obj);
void OnFrameLoaded(Action<IFrame> callback); void OnFrameLoaded(Action<IFrame> callback);
void ExecuteFunction(string name, params object[] args); void ExecuteFunction(string name, params object[] args);

View File

@@ -42,7 +42,9 @@
// //
// textBoxBrowserCSS // textBoxBrowserCSS
// //
this.textBoxBrowserCSS.Dock = System.Windows.Forms.DockStyle.Bottom; this.textBoxBrowserCSS.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBoxBrowserCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.textBoxBrowserCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.textBoxBrowserCSS.Location = new System.Drawing.Point(0, 16); this.textBoxBrowserCSS.Location = new System.Drawing.Point(0, 16);
this.textBoxBrowserCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0); this.textBoxBrowserCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
@@ -124,7 +126,9 @@
// //
// textBoxNotificationCSS // textBoxNotificationCSS
// //
this.textBoxNotificationCSS.Dock = System.Windows.Forms.DockStyle.Bottom; this.textBoxNotificationCSS.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBoxNotificationCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.textBoxNotificationCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.textBoxNotificationCSS.Location = new System.Drawing.Point(0, 16); this.textBoxNotificationCSS.Location = new System.Drawing.Point(0, 16);
this.textBoxNotificationCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0); this.textBoxNotificationCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);

View File

@@ -164,9 +164,6 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
ShouldReloadBrowser = true; ShouldReloadBrowser = true;
} }
} }
else{
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing TweetDuck profile.", true, importManager.LastException);
}
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
Close(); Close();
@@ -191,12 +188,8 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
Program.UserConfig.Save(); Program.UserConfig.Save();
Program.SystemConfig.Save(); Program.SystemConfig.Save();
ProfileManager manager = new ProfileManager(file, plugins); new ProfileManager(file, plugins).Export(SelectedItems);
if (!manager.Export(SelectedItems)){
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, manager.LastException);
}
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
Close(); Close();

View File

@@ -28,21 +28,22 @@
this.btnRestart = new System.Windows.Forms.Button(); this.btnRestart = new System.Windows.Forms.Button();
this.cbLogging = new System.Windows.Forms.CheckBox(); this.cbLogging = new System.Windows.Forms.CheckBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.cbDebugUpdates = new System.Windows.Forms.CheckBox();
this.tbDataFolder = new System.Windows.Forms.TextBox(); this.tbDataFolder = new System.Windows.Forms.TextBox();
this.tbShortcutTarget = new System.Windows.Forms.TextBox(); this.tbShortcutTarget = new System.Windows.Forms.TextBox();
this.labelDataFolder = new System.Windows.Forms.Label(); this.labelDataFolder = new System.Windows.Forms.Label();
this.labelShortcutTarget = new System.Windows.Forms.Label(); this.labelShortcutTarget = new System.Windows.Forms.Label();
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.flowPanel.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// btnCancel // btnCancel
// //
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.Location = new System.Drawing.Point(215, 163); this.btnCancel.Location = new System.Drawing.Point(215, 139);
this.btnCancel.Name = "btnCancel"; this.btnCancel.Name = "btnCancel";
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnCancel.Size = new System.Drawing.Size(56, 23); this.btnCancel.Size = new System.Drawing.Size(56, 23);
this.btnCancel.TabIndex = 9; this.btnCancel.TabIndex = 2;
this.btnCancel.Text = "Cancel"; this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true; this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
@@ -50,11 +51,11 @@
// btnRestart // btnRestart
// //
this.btnRestart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnRestart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnRestart.Location = new System.Drawing.Point(152, 163); this.btnRestart.Location = new System.Drawing.Point(152, 139);
this.btnRestart.Name = "btnRestart"; this.btnRestart.Name = "btnRestart";
this.btnRestart.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.btnRestart.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnRestart.Size = new System.Drawing.Size(57, 23); this.btnRestart.Size = new System.Drawing.Size(57, 23);
this.btnRestart.TabIndex = 8; this.btnRestart.TabIndex = 1;
this.btnRestart.Text = "Restart"; this.btnRestart.Text = "Restart";
this.btnRestart.UseVisualStyleBackColor = true; this.btnRestart.UseVisualStyleBackColor = true;
this.btnRestart.Click += new System.EventHandler(this.btnRestart_Click); this.btnRestart.Click += new System.EventHandler(this.btnRestart_Click);
@@ -62,7 +63,7 @@
// cbLogging // cbLogging
// //
this.cbLogging.AutoSize = true; this.cbLogging.AutoSize = true;
this.cbLogging.Location = new System.Drawing.Point(12, 12); this.cbLogging.Location = new System.Drawing.Point(3, 3);
this.cbLogging.Name = "cbLogging"; this.cbLogging.Name = "cbLogging";
this.cbLogging.Size = new System.Drawing.Size(64, 17); this.cbLogging.Size = new System.Drawing.Size(64, 17);
this.cbLogging.TabIndex = 0; this.cbLogging.TabIndex = 0;
@@ -70,25 +71,12 @@
this.toolTip.SetToolTip(this.cbLogging, "Logging JavaScript output into TD_Console.txt file in the data folder."); this.toolTip.SetToolTip(this.cbLogging, "Logging JavaScript output into TD_Console.txt file in the data folder.");
this.cbLogging.UseVisualStyleBackColor = true; this.cbLogging.UseVisualStyleBackColor = true;
// //
// cbDebugUpdates
//
this.cbDebugUpdates.AutoSize = true;
this.cbDebugUpdates.Location = new System.Drawing.Point(12, 35);
this.cbDebugUpdates.Name = "cbDebugUpdates";
this.cbDebugUpdates.Size = new System.Drawing.Size(127, 17);
this.cbDebugUpdates.TabIndex = 1;
this.cbDebugUpdates.Text = "Pre-Release Updates";
this.toolTip.SetToolTip(this.cbDebugUpdates, "Allows updating to pre-releases.");
this.cbDebugUpdates.UseVisualStyleBackColor = true;
//
// tbDataFolder // tbDataFolder
// //
this.tbDataFolder.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.tbDataFolder.Location = new System.Drawing.Point(3, 51);
| System.Windows.Forms.AnchorStyles.Right)));
this.tbDataFolder.Location = new System.Drawing.Point(15, 83);
this.tbDataFolder.Name = "tbDataFolder"; this.tbDataFolder.Name = "tbDataFolder";
this.tbDataFolder.Size = new System.Drawing.Size(257, 20); this.tbDataFolder.Size = new System.Drawing.Size(260, 20);
this.tbDataFolder.TabIndex = 5; this.tbDataFolder.TabIndex = 2;
this.toolTip.SetToolTip(this.tbDataFolder, "Path to the data folder. Must be either an absolute path,\r\nor a simple folder nam" + this.toolTip.SetToolTip(this.tbDataFolder, "Path to the data folder. Must be either an absolute path,\r\nor a simple folder nam" +
"e that will be created in LocalAppData."); "e that will be created in LocalAppData.");
// //
@@ -97,44 +85,57 @@
this.tbShortcutTarget.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.tbShortcutTarget.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.tbShortcutTarget.Cursor = System.Windows.Forms.Cursors.Hand; this.tbShortcutTarget.Cursor = System.Windows.Forms.Cursors.Hand;
this.tbShortcutTarget.Location = new System.Drawing.Point(15, 134); this.tbShortcutTarget.Location = new System.Drawing.Point(3, 102);
this.tbShortcutTarget.Name = "tbShortcutTarget"; this.tbShortcutTarget.Name = "tbShortcutTarget";
this.tbShortcutTarget.ReadOnly = true; this.tbShortcutTarget.ReadOnly = true;
this.tbShortcutTarget.Size = new System.Drawing.Size(257, 20); this.tbShortcutTarget.Size = new System.Drawing.Size(260, 20);
this.tbShortcutTarget.TabIndex = 7; this.tbShortcutTarget.TabIndex = 4;
this.tbShortcutTarget.Click += new System.EventHandler(this.tbShortcutTarget_Click); this.tbShortcutTarget.Click += new System.EventHandler(this.tbShortcutTarget_Click);
// //
// labelDataFolder // labelDataFolder
// //
this.labelDataFolder.AutoSize = true; this.labelDataFolder.AutoSize = true;
this.labelDataFolder.Location = new System.Drawing.Point(12, 67); this.labelDataFolder.Location = new System.Drawing.Point(3, 35);
this.labelDataFolder.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.labelDataFolder.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelDataFolder.Name = "labelDataFolder"; this.labelDataFolder.Name = "labelDataFolder";
this.labelDataFolder.Size = new System.Drawing.Size(62, 13); this.labelDataFolder.Size = new System.Drawing.Size(62, 13);
this.labelDataFolder.TabIndex = 4; this.labelDataFolder.TabIndex = 1;
this.labelDataFolder.Text = "Data Folder"; this.labelDataFolder.Text = "Data Folder";
// //
// labelShortcutTarget // labelShortcutTarget
// //
this.labelShortcutTarget.AutoSize = true; this.labelShortcutTarget.AutoSize = true;
this.labelShortcutTarget.Location = new System.Drawing.Point(12, 118); this.labelShortcutTarget.Location = new System.Drawing.Point(3, 86);
this.labelShortcutTarget.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.labelShortcutTarget.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelShortcutTarget.Name = "labelShortcutTarget"; this.labelShortcutTarget.Name = "labelShortcutTarget";
this.labelShortcutTarget.Size = new System.Drawing.Size(155, 13); this.labelShortcutTarget.Size = new System.Drawing.Size(155, 13);
this.labelShortcutTarget.TabIndex = 6; this.labelShortcutTarget.TabIndex = 3;
this.labelShortcutTarget.Text = "Shortcut Target (click to select)"; this.labelShortcutTarget.Text = "Shortcut Target (click to select)";
// //
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.cbLogging);
this.flowPanel.Controls.Add(this.labelDataFolder);
this.flowPanel.Controls.Add(this.tbDataFolder);
this.flowPanel.Controls.Add(this.labelShortcutTarget);
this.flowPanel.Controls.Add(this.tbShortcutTarget);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Margin = new System.Windows.Forms.Padding(0);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(266, 127);
this.flowPanel.TabIndex = 0;
this.flowPanel.WrapContents = false;
//
// DialogSettingsRestart // DialogSettingsRestart
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 198); this.ClientSize = new System.Drawing.Size(284, 174);
this.Controls.Add(this.tbShortcutTarget); this.Controls.Add(this.flowPanel);
this.Controls.Add(this.labelShortcutTarget);
this.Controls.Add(this.tbDataFolder);
this.Controls.Add(this.labelDataFolder);
this.Controls.Add(this.cbDebugUpdates);
this.Controls.Add(this.cbLogging);
this.Controls.Add(this.btnRestart); this.Controls.Add(this.btnRestart);
this.Controls.Add(this.btnCancel); this.Controls.Add(this.btnCancel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
@@ -143,8 +144,9 @@
this.Name = "DialogSettingsRestart"; this.Name = "DialogSettingsRestart";
this.ShowIcon = false; this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.ResumeLayout(false); this.ResumeLayout(false);
this.PerformLayout();
} }
@@ -154,10 +156,10 @@
private System.Windows.Forms.Button btnRestart; private System.Windows.Forms.Button btnRestart;
private System.Windows.Forms.CheckBox cbLogging; private System.Windows.Forms.CheckBox cbLogging;
private System.Windows.Forms.ToolTip toolTip; private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.CheckBox cbDebugUpdates;
private System.Windows.Forms.Label labelDataFolder; private System.Windows.Forms.Label labelDataFolder;
private System.Windows.Forms.TextBox tbDataFolder; private System.Windows.Forms.TextBox tbDataFolder;
private System.Windows.Forms.TextBox tbShortcutTarget; private System.Windows.Forms.TextBox tbShortcutTarget;
private System.Windows.Forms.Label labelShortcutTarget; private System.Windows.Forms.Label labelShortcutTarget;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
} }
} }

View File

@@ -11,10 +11,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
InitializeComponent(); InitializeComponent();
cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging); cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging);
cbDebugUpdates.Checked = currentArgs.HasFlag(Arguments.ArgDebugUpdates);
cbLogging.CheckedChanged += control_Change; cbLogging.CheckedChanged += control_Change;
cbDebugUpdates.CheckedChanged += control_Change;
if (Program.IsPortable){ if (Program.IsPortable){
tbDataFolder.Text = "Not available in portable version"; tbDataFolder.Text = "Not available in portable version";
@@ -36,10 +33,6 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
if (cbLogging.Checked){ if (cbLogging.Checked){
Args.AddFlag(Arguments.ArgLogging); Args.AddFlag(Arguments.ArgLogging);
} }
if (cbDebugUpdates.Checked){
Args.AddFlag(Arguments.ArgDebugUpdates);
}
if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled){ if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled){
Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text); Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text);

View File

@@ -0,0 +1,103 @@
namespace TweetDuck.Core.Other.Settings.Dialogs {
partial class DialogSettingsSearchEngine {
/// <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.textBoxUrl = new System.Windows.Forms.TextBox();
this.btnCancel = new System.Windows.Forms.Button();
this.btnApply = new System.Windows.Forms.Button();
this.labelInfo = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// textBoxUrl
//
this.textBoxUrl.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBoxUrl.Location = new System.Drawing.Point(12, 28);
this.textBoxUrl.Name = "textBoxUrl";
this.textBoxUrl.Size = new System.Drawing.Size(310, 20);
this.textBoxUrl.TabIndex = 1;
//
// btnCancel
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.Location = new System.Drawing.Point(204, 56);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnCancel.Size = new System.Drawing.Size(56, 23);
this.btnCancel.TabIndex = 3;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
//
// btnApply
//
this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnApply.Location = new System.Drawing.Point(266, 56);
this.btnApply.Name = "btnApply";
this.btnApply.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnApply.Size = new System.Drawing.Size(56, 23);
this.btnApply.TabIndex = 2;
this.btnApply.Text = "Apply";
this.btnApply.UseVisualStyleBackColor = true;
this.btnApply.Click += new System.EventHandler(this.btnApply_Click);
//
// labelInfo
//
this.labelInfo.AutoSize = true;
this.labelInfo.Location = new System.Drawing.Point(12, 9);
this.labelInfo.Margin = new System.Windows.Forms.Padding(3, 0, 3, 3);
this.labelInfo.Name = "labelInfo";
this.labelInfo.Size = new System.Drawing.Size(264, 13);
this.labelInfo.TabIndex = 0;
this.labelInfo.Text = "The search query will be added at the end of the URL.";
//
// DialogSettingsSearchEngine
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(334, 91);
this.Controls.Add(this.labelInfo);
this.Controls.Add(this.btnApply);
this.Controls.Add(this.btnCancel);
this.Controls.Add(this.textBoxUrl);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "DialogSettingsSearchEngine";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox textBoxUrl;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.Button btnApply;
private System.Windows.Forms.Label labelInfo;
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Windows.Forms;
namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsSearchEngine : Form{
public string Url => textBoxUrl.Text;
public DialogSettingsSearchEngine(){
InitializeComponent();
Text = Program.BrandName+" Options - Custom Search Engine";
textBoxUrl.Text = Program.UserConfig.SearchEngineUrl ?? "";
textBoxUrl.Select(textBoxUrl.Text.Length, 0);
}
private void btnApply_Click(object sender, EventArgs e){
DialogResult = DialogResult.OK;
Close();
}
private void btnCancel_Click(object sender, EventArgs e){
DialogResult = DialogResult.Cancel;
Close();
}
}
}

View File

@@ -29,7 +29,6 @@
this.checkUpdateNotifications = new System.Windows.Forms.CheckBox(); this.checkUpdateNotifications = new System.Windows.Forms.CheckBox();
this.btnCheckUpdates = new System.Windows.Forms.Button(); this.btnCheckUpdates = new System.Windows.Forms.Button();
this.labelZoomValue = new System.Windows.Forms.Label(); this.labelZoomValue = new System.Windows.Forms.Label();
this.checkSwitchAccountSelectors = new System.Windows.Forms.CheckBox();
this.checkBestImageQuality = new System.Windows.Forms.CheckBox(); this.checkBestImageQuality = new System.Windows.Forms.CheckBox();
this.checkOpenSearchInFirstColumn = new System.Windows.Forms.CheckBox(); this.checkOpenSearchInFirstColumn = new System.Windows.Forms.CheckBox();
this.trackBarZoom = new System.Windows.Forms.TrackBar(); this.trackBarZoom = new System.Windows.Forms.TrackBar();
@@ -45,6 +44,8 @@
this.checkSmoothScrolling = new System.Windows.Forms.CheckBox(); this.checkSmoothScrolling = new System.Windows.Forms.CheckBox();
this.labelBrowserPath = new System.Windows.Forms.Label(); this.labelBrowserPath = new System.Windows.Forms.Label();
this.comboBoxBrowserPath = new System.Windows.Forms.ComboBox(); this.comboBoxBrowserPath = new System.Windows.Forms.ComboBox();
this.labelSearchEngine = new System.Windows.Forms.Label();
this.comboBoxSearchEngine = new System.Windows.Forms.ComboBox();
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit();
this.panelZoom.SuspendLayout(); this.panelZoom.SuspendLayout();
this.flowPanel.SuspendLayout(); this.flowPanel.SuspendLayout();
@@ -64,21 +65,21 @@
// checkUpdateNotifications // checkUpdateNotifications
// //
this.checkUpdateNotifications.AutoSize = true; this.checkUpdateNotifications.AutoSize = true;
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 386); this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 415);
this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3); this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkUpdateNotifications.Name = "checkUpdateNotifications"; this.checkUpdateNotifications.Name = "checkUpdateNotifications";
this.checkUpdateNotifications.Size = new System.Drawing.Size(165, 17); this.checkUpdateNotifications.Size = new System.Drawing.Size(165, 17);
this.checkUpdateNotifications.TabIndex = 14; this.checkUpdateNotifications.TabIndex = 15;
this.checkUpdateNotifications.Text = "Check Updates Automatically"; this.checkUpdateNotifications.Text = "Check Updates Automatically";
this.checkUpdateNotifications.UseVisualStyleBackColor = true; this.checkUpdateNotifications.UseVisualStyleBackColor = true;
// //
// btnCheckUpdates // btnCheckUpdates
// //
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 409); this.btnCheckUpdates.Location = new System.Drawing.Point(5, 438);
this.btnCheckUpdates.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); this.btnCheckUpdates.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnCheckUpdates.Name = "btnCheckUpdates"; this.btnCheckUpdates.Name = "btnCheckUpdates";
this.btnCheckUpdates.Size = new System.Drawing.Size(144, 23); this.btnCheckUpdates.Size = new System.Drawing.Size(144, 23);
this.btnCheckUpdates.TabIndex = 15; this.btnCheckUpdates.TabIndex = 16;
this.btnCheckUpdates.Text = "Check Updates Now"; this.btnCheckUpdates.Text = "Check Updates Now";
this.btnCheckUpdates.UseVisualStyleBackColor = true; this.btnCheckUpdates.UseVisualStyleBackColor = true;
// //
@@ -93,36 +94,25 @@
this.labelZoomValue.Text = "100%"; this.labelZoomValue.Text = "100%";
this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight; this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
// //
// checkSwitchAccountSelectors
//
this.checkSwitchAccountSelectors.AutoSize = true;
this.checkSwitchAccountSelectors.Location = new System.Drawing.Point(6, 49);
this.checkSwitchAccountSelectors.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkSwitchAccountSelectors.Name = "checkSwitchAccountSelectors";
this.checkSwitchAccountSelectors.Size = new System.Drawing.Size(172, 17);
this.checkSwitchAccountSelectors.TabIndex = 2;
this.checkSwitchAccountSelectors.Text = "Shift Selects Multiple Accounts";
this.checkSwitchAccountSelectors.UseVisualStyleBackColor = true;
//
// checkBestImageQuality // checkBestImageQuality
// //
this.checkBestImageQuality.AutoSize = true; this.checkBestImageQuality.AutoSize = true;
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 118); this.checkBestImageQuality.Location = new System.Drawing.Point(6, 95);
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkBestImageQuality.Name = "checkBestImageQuality"; this.checkBestImageQuality.Name = "checkBestImageQuality";
this.checkBestImageQuality.Size = new System.Drawing.Size(114, 17); this.checkBestImageQuality.Size = new System.Drawing.Size(114, 17);
this.checkBestImageQuality.TabIndex = 5; this.checkBestImageQuality.TabIndex = 4;
this.checkBestImageQuality.Text = "Best Image Quality"; this.checkBestImageQuality.Text = "Best Image Quality";
this.checkBestImageQuality.UseVisualStyleBackColor = true; this.checkBestImageQuality.UseVisualStyleBackColor = true;
// //
// checkOpenSearchInFirstColumn // checkOpenSearchInFirstColumn
// //
this.checkOpenSearchInFirstColumn.AutoSize = true; this.checkOpenSearchInFirstColumn.AutoSize = true;
this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 72); this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 49);
this.checkOpenSearchInFirstColumn.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); this.checkOpenSearchInFirstColumn.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkOpenSearchInFirstColumn.Name = "checkOpenSearchInFirstColumn"; this.checkOpenSearchInFirstColumn.Name = "checkOpenSearchInFirstColumn";
this.checkOpenSearchInFirstColumn.Size = new System.Drawing.Size(219, 17); this.checkOpenSearchInFirstColumn.Size = new System.Drawing.Size(219, 17);
this.checkOpenSearchInFirstColumn.TabIndex = 3; this.checkOpenSearchInFirstColumn.TabIndex = 2;
this.checkOpenSearchInFirstColumn.Text = "Add Search Columns Before First Column"; this.checkOpenSearchInFirstColumn.Text = "Add Search Columns Before First Column";
this.checkOpenSearchInFirstColumn.UseVisualStyleBackColor = true; this.checkOpenSearchInFirstColumn.UseVisualStyleBackColor = true;
// //
@@ -144,11 +134,11 @@
// labelZoom // labelZoom
// //
this.labelZoom.AutoSize = true; this.labelZoom.AutoSize = true;
this.labelZoom.Location = new System.Drawing.Point(3, 291); this.labelZoom.Location = new System.Drawing.Point(3, 320);
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelZoom.Name = "labelZoom"; this.labelZoom.Name = "labelZoom";
this.labelZoom.Size = new System.Drawing.Size(34, 13); this.labelZoom.Size = new System.Drawing.Size(34, 13);
this.labelZoom.TabIndex = 11; this.labelZoom.TabIndex = 12;
this.labelZoom.Text = "Zoom"; this.labelZoom.Text = "Zoom";
// //
// zoomUpdateTimer // zoomUpdateTimer
@@ -172,20 +162,20 @@
this.panelZoom.Anchor = System.Windows.Forms.AnchorStyles.Top; this.panelZoom.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.panelZoom.Controls.Add(this.trackBarZoom); this.panelZoom.Controls.Add(this.trackBarZoom);
this.panelZoom.Controls.Add(this.labelZoomValue); this.panelZoom.Controls.Add(this.labelZoomValue);
this.panelZoom.Location = new System.Drawing.Point(0, 304); this.panelZoom.Location = new System.Drawing.Point(0, 333);
this.panelZoom.Margin = new System.Windows.Forms.Padding(0); this.panelZoom.Margin = new System.Windows.Forms.Padding(0);
this.panelZoom.Name = "panelZoom"; this.panelZoom.Name = "panelZoom";
this.panelZoom.Size = new System.Drawing.Size(322, 36); this.panelZoom.Size = new System.Drawing.Size(322, 36);
this.panelZoom.TabIndex = 12; this.panelZoom.TabIndex = 13;
// //
// checkAnimatedAvatars // checkAnimatedAvatars
// //
this.checkAnimatedAvatars.AutoSize = true; this.checkAnimatedAvatars.AutoSize = true;
this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 141); this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 118);
this.checkAnimatedAvatars.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); this.checkAnimatedAvatars.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkAnimatedAvatars.Name = "checkAnimatedAvatars"; this.checkAnimatedAvatars.Name = "checkAnimatedAvatars";
this.checkAnimatedAvatars.Size = new System.Drawing.Size(145, 17); this.checkAnimatedAvatars.Size = new System.Drawing.Size(145, 17);
this.checkAnimatedAvatars.TabIndex = 6; this.checkAnimatedAvatars.TabIndex = 5;
this.checkAnimatedAvatars.Text = "Enable Animated Avatars"; this.checkAnimatedAvatars.Text = "Enable Animated Avatars";
this.checkAnimatedAvatars.UseVisualStyleBackColor = true; this.checkAnimatedAvatars.UseVisualStyleBackColor = true;
// //
@@ -193,11 +183,11 @@
// //
this.labelUpdates.AutoSize = true; 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.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(0, 360); this.labelUpdates.Location = new System.Drawing.Point(0, 389);
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0); this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelUpdates.Name = "labelUpdates"; this.labelUpdates.Name = "labelUpdates";
this.labelUpdates.Size = new System.Drawing.Size(70, 20); this.labelUpdates.Size = new System.Drawing.Size(70, 20);
this.labelUpdates.TabIndex = 13; this.labelUpdates.TabIndex = 14;
this.labelUpdates.Text = "Updates"; this.labelUpdates.Text = "Updates";
// //
// flowPanel // flowPanel
@@ -207,7 +197,6 @@
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelUI); this.flowPanel.Controls.Add(this.labelUI);
this.flowPanel.Controls.Add(this.checkExpandLinks); this.flowPanel.Controls.Add(this.checkExpandLinks);
this.flowPanel.Controls.Add(this.checkSwitchAccountSelectors);
this.flowPanel.Controls.Add(this.checkOpenSearchInFirstColumn); this.flowPanel.Controls.Add(this.checkOpenSearchInFirstColumn);
this.flowPanel.Controls.Add(this.checkKeepLikeFollowDialogsOpen); this.flowPanel.Controls.Add(this.checkKeepLikeFollowDialogsOpen);
this.flowPanel.Controls.Add(this.checkBestImageQuality); this.flowPanel.Controls.Add(this.checkBestImageQuality);
@@ -216,6 +205,8 @@
this.flowPanel.Controls.Add(this.checkSmoothScrolling); this.flowPanel.Controls.Add(this.checkSmoothScrolling);
this.flowPanel.Controls.Add(this.labelBrowserPath); this.flowPanel.Controls.Add(this.labelBrowserPath);
this.flowPanel.Controls.Add(this.comboBoxBrowserPath); this.flowPanel.Controls.Add(this.comboBoxBrowserPath);
this.flowPanel.Controls.Add(this.labelSearchEngine);
this.flowPanel.Controls.Add(this.comboBoxSearchEngine);
this.flowPanel.Controls.Add(this.labelZoom); this.flowPanel.Controls.Add(this.labelZoom);
this.flowPanel.Controls.Add(this.panelZoom); this.flowPanel.Controls.Add(this.panelZoom);
this.flowPanel.Controls.Add(this.labelUpdates); this.flowPanel.Controls.Add(this.labelUpdates);
@@ -224,18 +215,18 @@
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown; this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9); this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel"; this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(322, 438); this.flowPanel.Size = new System.Drawing.Size(322, 462);
this.flowPanel.TabIndex = 0; this.flowPanel.TabIndex = 0;
this.flowPanel.WrapContents = false; this.flowPanel.WrapContents = false;
// //
// checkKeepLikeFollowDialogsOpen // checkKeepLikeFollowDialogsOpen
// //
this.checkKeepLikeFollowDialogsOpen.AutoSize = true; this.checkKeepLikeFollowDialogsOpen.AutoSize = true;
this.checkKeepLikeFollowDialogsOpen.Location = new System.Drawing.Point(6, 95); this.checkKeepLikeFollowDialogsOpen.Location = new System.Drawing.Point(6, 72);
this.checkKeepLikeFollowDialogsOpen.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); this.checkKeepLikeFollowDialogsOpen.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkKeepLikeFollowDialogsOpen.Name = "checkKeepLikeFollowDialogsOpen"; this.checkKeepLikeFollowDialogsOpen.Name = "checkKeepLikeFollowDialogsOpen";
this.checkKeepLikeFollowDialogsOpen.Size = new System.Drawing.Size(176, 17); this.checkKeepLikeFollowDialogsOpen.Size = new System.Drawing.Size(176, 17);
this.checkKeepLikeFollowDialogsOpen.TabIndex = 4; this.checkKeepLikeFollowDialogsOpen.TabIndex = 3;
this.checkKeepLikeFollowDialogsOpen.Text = "Keep Like/Follow Dialogs Open"; this.checkKeepLikeFollowDialogsOpen.Text = "Keep Like/Follow Dialogs Open";
this.checkKeepLikeFollowDialogsOpen.UseVisualStyleBackColor = true; this.checkKeepLikeFollowDialogsOpen.UseVisualStyleBackColor = true;
// //
@@ -243,43 +234,63 @@
// //
this.labelBrowserSettings.AutoSize = true; this.labelBrowserSettings.AutoSize = true;
this.labelBrowserSettings.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelBrowserSettings.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelBrowserSettings.Location = new System.Drawing.Point(0, 181); this.labelBrowserSettings.Location = new System.Drawing.Point(0, 158);
this.labelBrowserSettings.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0); this.labelBrowserSettings.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelBrowserSettings.Name = "labelBrowserSettings"; this.labelBrowserSettings.Name = "labelBrowserSettings";
this.labelBrowserSettings.Size = new System.Drawing.Size(130, 20); this.labelBrowserSettings.Size = new System.Drawing.Size(130, 20);
this.labelBrowserSettings.TabIndex = 7; this.labelBrowserSettings.TabIndex = 6;
this.labelBrowserSettings.Text = "Browser Settings"; this.labelBrowserSettings.Text = "Browser Settings";
// //
// checkSmoothScrolling // checkSmoothScrolling
// //
this.checkSmoothScrolling.AutoSize = true; this.checkSmoothScrolling.AutoSize = true;
this.checkSmoothScrolling.Location = new System.Drawing.Point(6, 207); this.checkSmoothScrolling.Location = new System.Drawing.Point(6, 184);
this.checkSmoothScrolling.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3); this.checkSmoothScrolling.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkSmoothScrolling.Name = "checkSmoothScrolling"; this.checkSmoothScrolling.Name = "checkSmoothScrolling";
this.checkSmoothScrolling.Size = new System.Drawing.Size(105, 17); this.checkSmoothScrolling.Size = new System.Drawing.Size(105, 17);
this.checkSmoothScrolling.TabIndex = 8; this.checkSmoothScrolling.TabIndex = 7;
this.checkSmoothScrolling.Text = "Smooth Scrolling"; this.checkSmoothScrolling.Text = "Smooth Scrolling";
this.checkSmoothScrolling.UseVisualStyleBackColor = true; this.checkSmoothScrolling.UseVisualStyleBackColor = true;
// //
// labelBrowserPath // labelBrowserPath
// //
this.labelBrowserPath.AutoSize = true; this.labelBrowserPath.AutoSize = true;
this.labelBrowserPath.Location = new System.Drawing.Point(3, 239); this.labelBrowserPath.Location = new System.Drawing.Point(3, 216);
this.labelBrowserPath.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.labelBrowserPath.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelBrowserPath.Name = "labelBrowserPath"; this.labelBrowserPath.Name = "labelBrowserPath";
this.labelBrowserPath.Size = new System.Drawing.Size(95, 13); this.labelBrowserPath.Size = new System.Drawing.Size(95, 13);
this.labelBrowserPath.TabIndex = 9; this.labelBrowserPath.TabIndex = 8;
this.labelBrowserPath.Text = "Open Links With..."; this.labelBrowserPath.Text = "Open Links With...";
// //
// comboBoxBrowserPath // comboBoxBrowserPath
// //
this.comboBoxBrowserPath.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxBrowserPath.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxBrowserPath.FormattingEnabled = true; this.comboBoxBrowserPath.FormattingEnabled = true;
this.comboBoxBrowserPath.Location = new System.Drawing.Point(5, 255); this.comboBoxBrowserPath.Location = new System.Drawing.Point(5, 232);
this.comboBoxBrowserPath.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); this.comboBoxBrowserPath.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.comboBoxBrowserPath.Name = "comboBoxBrowserPath"; this.comboBoxBrowserPath.Name = "comboBoxBrowserPath";
this.comboBoxBrowserPath.Size = new System.Drawing.Size(173, 21); this.comboBoxBrowserPath.Size = new System.Drawing.Size(173, 21);
this.comboBoxBrowserPath.TabIndex = 10; this.comboBoxBrowserPath.TabIndex = 9;
//
// labelSearchEngine
//
this.labelSearchEngine.AutoSize = true;
this.labelSearchEngine.Location = new System.Drawing.Point(3, 268);
this.labelSearchEngine.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelSearchEngine.Name = "labelSearchEngine";
this.labelSearchEngine.Size = new System.Drawing.Size(77, 13);
this.labelSearchEngine.TabIndex = 10;
this.labelSearchEngine.Text = "Search Engine";
//
// comboBoxSearchEngine
//
this.comboBoxSearchEngine.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxSearchEngine.FormattingEnabled = true;
this.comboBoxSearchEngine.Location = new System.Drawing.Point(5, 284);
this.comboBoxSearchEngine.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.comboBoxSearchEngine.Name = "comboBoxSearchEngine";
this.comboBoxSearchEngine.Size = new System.Drawing.Size(173, 21);
this.comboBoxSearchEngine.TabIndex = 11;
// //
// TabSettingsGeneral // TabSettingsGeneral
// //
@@ -287,7 +298,7 @@
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.flowPanel); this.Controls.Add(this.flowPanel);
this.Name = "TabSettingsGeneral"; this.Name = "TabSettingsGeneral";
this.Size = new System.Drawing.Size(340, 456); this.Size = new System.Drawing.Size(340, 480);
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit();
this.panelZoom.ResumeLayout(false); this.panelZoom.ResumeLayout(false);
this.flowPanel.ResumeLayout(false); this.flowPanel.ResumeLayout(false);
@@ -306,7 +317,6 @@
private System.Windows.Forms.Label labelZoomValue; private System.Windows.Forms.Label labelZoomValue;
private System.Windows.Forms.TrackBar trackBarZoom; private System.Windows.Forms.TrackBar trackBarZoom;
private System.Windows.Forms.Timer zoomUpdateTimer; private System.Windows.Forms.Timer zoomUpdateTimer;
private System.Windows.Forms.CheckBox checkSwitchAccountSelectors;
private System.Windows.Forms.Label labelUI; private System.Windows.Forms.Label labelUI;
private System.Windows.Forms.Panel panelZoom; private System.Windows.Forms.Panel panelZoom;
private System.Windows.Forms.Label labelUpdates; private System.Windows.Forms.Label labelUpdates;
@@ -319,5 +329,7 @@
private System.Windows.Forms.ComboBox comboBoxBrowserPath; private System.Windows.Forms.ComboBox comboBoxBrowserPath;
private System.Windows.Forms.Label labelBrowserSettings; private System.Windows.Forms.Label labelBrowserSettings;
private System.Windows.Forms.CheckBox checkSmoothScrolling; private System.Windows.Forms.CheckBox checkSmoothScrolling;
private System.Windows.Forms.Label labelSearchEngine;
private System.Windows.Forms.ComboBox comboBoxSearchEngine;
} }
} }

View File

@@ -4,29 +4,34 @@ using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling.General; using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Updates; using TweetDuck.Updates;
using TweetDuck.Updates.Events;
namespace TweetDuck.Core.Other.Settings{ namespace TweetDuck.Core.Other.Settings{
sealed partial class TabSettingsGeneral : BaseTabSettings{ sealed partial class TabSettingsGeneral : BaseTabSettings{
private readonly FormBrowser browser; private readonly Action reloadColumns;
private readonly UpdateHandler updates; private readonly UpdateHandler updates;
private int updateCheckEventId = -1; private int updateCheckEventId = -1;
private readonly int browserListIndexDefault; private readonly int browserListIndexDefault;
private readonly int browserListIndexCustom; private readonly int browserListIndexCustom;
public TabSettingsGeneral(FormBrowser browser, UpdateHandler updates){ private readonly int searchEngineIndexDefault;
private readonly int searchEngineIndexCustom;
public TabSettingsGeneral(Action reloadColumns, UpdateHandler updates){
InitializeComponent(); InitializeComponent();
this.browser = browser; this.reloadColumns = reloadColumns;
this.updates = updates; this.updates = updates;
this.updates.CheckFinished += updates_CheckFinished; this.updates.CheckFinished += updates_CheckFinished;
Disposed += (sender, args) => this.updates.CheckFinished -= updates_CheckFinished; Disposed += (sender, args) => this.updates.CheckFinished -= updates_CheckFinished;
toolTip.SetToolTip(checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead."); toolTip.SetToolTip(checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead.");
toolTip.SetToolTip(checkSwitchAccountSelectors, "When (re)tweeting, click to select a single account or hold Shift to\r\nselect multiple accounts, instead of TweetDeck\'s default behavior.");
toolTip.SetToolTip(checkOpenSearchInFirstColumn, "By default, TweetDeck adds Search columns at the end.\r\nThis option makes them appear before the first column instead."); toolTip.SetToolTip(checkOpenSearchInFirstColumn, "By default, TweetDeck adds Search columns at the end.\r\nThis option makes them appear before the first column instead.");
toolTip.SetToolTip(checkKeepLikeFollowDialogsOpen, "Allows liking and following from multiple accounts at once,\r\ninstead of automatically closing the dialog after taking an action."); toolTip.SetToolTip(checkKeepLikeFollowDialogsOpen, "Allows liking and following from multiple accounts at once,\r\ninstead of automatically closing the dialog after taking an action.");
toolTip.SetToolTip(checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL)."); toolTip.SetToolTip(checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
@@ -41,7 +46,6 @@ namespace TweetDuck.Core.Other.Settings{
toolTip.SetToolTip(btnCheckUpdates, "Forces an update check, even for updates that had been dismissed."); toolTip.SetToolTip(btnCheckUpdates, "Forces an update check, even for updates that had been dismissed.");
checkExpandLinks.Checked = Config.ExpandLinksOnHover; checkExpandLinks.Checked = Config.ExpandLinksOnHover;
checkSwitchAccountSelectors.Checked = Config.SwitchAccountSelectors;
checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn; checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn;
checkKeepLikeFollowDialogsOpen.Checked = Config.KeepLikeFollowDialogsOpen; checkKeepLikeFollowDialogsOpen.Checked = Config.KeepLikeFollowDialogsOpen;
checkBestImageQuality.Checked = Config.BestImageQuality; checkBestImageQuality.Checked = Config.BestImageQuality;
@@ -57,6 +61,14 @@ namespace TweetDuck.Core.Other.Settings{
browserListIndexCustom = comboBoxBrowserPath.Items.Add("(custom program...)"); browserListIndexCustom = comboBoxBrowserPath.Items.Add("(custom program...)");
UpdateBrowserPathSelection(); UpdateBrowserPathSelection();
comboBoxSearchEngine.Items.Add(new SearchEngine("DuckDuckGo", "https://duckduckgo.com/?q="));
comboBoxSearchEngine.Items.Add(new SearchEngine("Google", "https://www.google.com/search?q="));
comboBoxSearchEngine.Items.Add(new SearchEngine("Bing", "https://www.bing.com/search?q="));
comboBoxSearchEngine.Items.Add(new SearchEngine("Yahoo", "https://search.yahoo.com/search?p="));
searchEngineIndexDefault = comboBoxSearchEngine.Items.Add("(no engine set)");
searchEngineIndexCustom = comboBoxSearchEngine.Items.Add("(custom url...)");
UpdateSearchEngineSelection();
trackBarZoom.SetValueSafe(Config.ZoomLevel); trackBarZoom.SetValueSafe(Config.ZoomLevel);
labelZoomValue.Text = trackBarZoom.Value+"%"; labelZoomValue.Text = trackBarZoom.Value+"%";
@@ -65,7 +77,6 @@ namespace TweetDuck.Core.Other.Settings{
public override void OnReady(){ public override void OnReady(){
checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged; checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
checkSwitchAccountSelectors.CheckedChanged += checkSwitchAccountSelectors_CheckedChanged;
checkOpenSearchInFirstColumn.CheckedChanged += checkOpenSearchInFirstColumn_CheckedChanged; checkOpenSearchInFirstColumn.CheckedChanged += checkOpenSearchInFirstColumn_CheckedChanged;
checkKeepLikeFollowDialogsOpen.CheckedChanged += checkKeepLikeFollowDialogsOpen_CheckedChanged; checkKeepLikeFollowDialogsOpen.CheckedChanged += checkKeepLikeFollowDialogsOpen_CheckedChanged;
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged; checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
@@ -73,6 +84,7 @@ namespace TweetDuck.Core.Other.Settings{
checkSmoothScrolling.CheckedChanged += checkSmoothScrolling_CheckedChanged; checkSmoothScrolling.CheckedChanged += checkSmoothScrolling_CheckedChanged;
comboBoxBrowserPath.SelectedIndexChanged += comboBoxBrowserPath_SelectedIndexChanged; comboBoxBrowserPath.SelectedIndexChanged += comboBoxBrowserPath_SelectedIndexChanged;
comboBoxSearchEngine.SelectedIndexChanged += comboBoxSearchEngine_SelectedIndexChanged;
trackBarZoom.ValueChanged += trackBarZoom_ValueChanged; trackBarZoom.ValueChanged += trackBarZoom_ValueChanged;
checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged; checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged;
@@ -87,10 +99,6 @@ namespace TweetDuck.Core.Other.Settings{
Config.ExpandLinksOnHover = checkExpandLinks.Checked; Config.ExpandLinksOnHover = checkExpandLinks.Checked;
} }
private void checkSwitchAccountSelectors_CheckedChanged(object sender, EventArgs e){
Config.SwitchAccountSelectors = checkSwitchAccountSelectors.Checked;
}
private void checkOpenSearchInFirstColumn_CheckedChanged(object sender, EventArgs e){ private void checkOpenSearchInFirstColumn_CheckedChanged(object sender, EventArgs e){
Config.OpenSearchInFirstColumn = checkOpenSearchInFirstColumn.Checked; Config.OpenSearchInFirstColumn = checkOpenSearchInFirstColumn.Checked;
} }
@@ -105,7 +113,7 @@ namespace TweetDuck.Core.Other.Settings{
private void checkAnimatedAvatars_CheckedChanged(object sender, EventArgs e){ private void checkAnimatedAvatars_CheckedChanged(object sender, EventArgs e){
Config.EnableAnimatedImages = checkAnimatedAvatars.Checked; Config.EnableAnimatedImages = checkAnimatedAvatars.Checked;
BrowserProcessHandler.UpdatePrefs().ContinueWith(task => browser.ReloadColumns()); BrowserProcessHandler.UpdatePrefs().ContinueWith(task => reloadColumns());
} }
private void checkSmoothScrolling_CheckedChanged(object sender, EventArgs e){ private void checkSmoothScrolling_CheckedChanged(object sender, EventArgs e){
@@ -141,18 +149,50 @@ namespace TweetDuck.Core.Other.Settings{
if (dialog.ShowDialog() == DialogResult.OK){ if (dialog.ShowDialog() == DialogResult.OK){
Config.BrowserPath = dialog.FileName; Config.BrowserPath = dialog.FileName;
} }
else{
comboBoxBrowserPath.SelectedIndexChanged -= comboBoxBrowserPath_SelectedIndexChanged;
UpdateBrowserPathSelection();
comboBoxBrowserPath.SelectedIndexChanged += comboBoxBrowserPath_SelectedIndexChanged;
}
} }
comboBoxBrowserPath.SelectedIndexChanged -= comboBoxBrowserPath_SelectedIndexChanged;
UpdateBrowserPathSelection();
comboBoxBrowserPath.SelectedIndexChanged += comboBoxBrowserPath_SelectedIndexChanged;
} }
else{ else{
Config.BrowserPath = (comboBoxBrowserPath.SelectedItem as WindowsUtils.Browser)?.Path; // default browser item is a string and casts to null Config.BrowserPath = (comboBoxBrowserPath.SelectedItem as WindowsUtils.Browser)?.Path; // default browser item is a string and casts to null
} }
} }
private void comboBoxSearchEngine_SelectedIndexChanged(object sender, EventArgs e){
if (comboBoxSearchEngine.SelectedIndex == searchEngineIndexCustom){
using(DialogSettingsSearchEngine dialog = new DialogSettingsSearchEngine()){
if (dialog.ShowDialog() == DialogResult.OK){
Config.SearchEngineUrl = dialog.Url.Trim();
}
}
comboBoxSearchEngine.SelectedIndexChanged -= comboBoxSearchEngine_SelectedIndexChanged;
UpdateSearchEngineSelection();
comboBoxSearchEngine.SelectedIndexChanged += comboBoxSearchEngine_SelectedIndexChanged;
}
else{
Config.SearchEngineUrl = (comboBoxSearchEngine.SelectedItem as SearchEngine)?.Url; // default search engine item is a string and casts to null
}
}
private void UpdateSearchEngineSelection(){
if (string.IsNullOrEmpty(Config.SearchEngineUrl)){
comboBoxSearchEngine.SelectedIndex = searchEngineIndexDefault;
}
else{
SearchEngine engineInfo = comboBoxSearchEngine.Items.OfType<SearchEngine>().FirstOrDefault(engine => engine.Url == Config.SearchEngineUrl);
if (engineInfo == null){
comboBoxSearchEngine.SelectedIndex = searchEngineIndexCustom;
}
else{
comboBoxSearchEngine.SelectedItem = engineInfo;
}
}
}
private void trackBarZoom_ValueChanged(object sender, EventArgs e){ private void trackBarZoom_ValueChanged(object sender, EventArgs e){
if (trackBarZoom.AlignValueToTick()){ if (trackBarZoom.AlignValueToTick()){
zoomUpdateTimer.Stop(); zoomUpdateTimer.Stop();
@@ -170,16 +210,25 @@ namespace TweetDuck.Core.Other.Settings{
btnCheckUpdates.Enabled = false; btnCheckUpdates.Enabled = false;
updateCheckEventId = updates.Check(true); updateCheckEventId = updates.Check(true);
if (updateCheckEventId == UpdateHandler.CheckCodeNotOnTweetDeck){
FormMessage.Error("Update Check", "Updates can only be checked once TweetDeck is fully loaded.", FormMessage.OK);
btnCheckUpdates.Enabled = true;
}
} }
private void updates_CheckFinished(object sender, UpdateEventArgs e){ private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
this.InvokeAsyncSafe(() => { this.InvokeAsyncSafe(() => {
if (e.EventId == updateCheckEventId){ if (e.EventId == updateCheckEventId){
btnCheckUpdates.Enabled = true; btnCheckUpdates.Enabled = true;
if (!e.IsUpdateAvailable){ e.Result.Handle(update => {
FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK); if (!update.IsUpdateNew){
} FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK);
}
}, ex => {
Program.Reporter.HandleException("Update Check Error", "An error occurred while checking for updates.", true, ex);
});
} }
}); });
} }
@@ -188,5 +237,19 @@ namespace TweetDuck.Core.Other.Settings{
Config.ZoomLevel = trackBarZoom.Value; Config.ZoomLevel = trackBarZoom.Value;
zoomUpdateTimer.Stop(); zoomUpdateTimer.Stop();
} }
private sealed class SearchEngine{
private string Name { get; }
public string Url { get; }
public SearchEngine(string name, string url){
Name = name;
Url = url;
}
public override int GetHashCode() => Name.GetHashCode();
public override bool Equals(object obj) => obj is SearchEngine other && Name == other.Name;
public override string ToString() => Name;
}
} }
} }

View File

@@ -42,12 +42,12 @@ namespace TweetDuck.Core.Other.Settings{
} }
private void comboBoxSpellCheckLanguage_SelectedValueChanged(object sender, EventArgs e){ private void comboBoxSpellCheckLanguage_SelectedValueChanged(object sender, EventArgs e){
Config.SpellCheckLanguage = (comboBoxSpellCheckLanguage.SelectedItem as LocaleUtils.Item)?.Code; Config.SpellCheckLanguage = (comboBoxSpellCheckLanguage.SelectedItem as LocaleUtils.Item)?.Code ?? "en-US";
PromptRestart(); PromptRestart();
} }
private void comboBoxTranslationTarget_SelectedValueChanged(object sender, EventArgs e){ private void comboBoxTranslationTarget_SelectedValueChanged(object sender, EventArgs e){
Config.TranslationTarget = (comboBoxTranslationTarget.SelectedItem as LocaleUtils.Item)?.Code; Config.TranslationTarget = (comboBoxTranslationTarget.SelectedItem as LocaleUtils.Item)?.Code ?? "en";
} }
} }
} }

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.Text;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using CefSharp;
using CefSharp.WinForms; using CefSharp.WinForms;
@@ -8,6 +9,7 @@ using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling; using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General; using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Other.Interfaces;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Resources; using TweetDuck.Resources;
@@ -22,6 +24,10 @@ namespace TweetDuck.Core{
public bool IsTweetDeckWebsite{ public bool IsTweetDeckWebsite{
get{ get{
if (!Ready){
return false;
}
using(IFrame frame = browser.GetBrowser().MainFrame){ using(IFrame frame = browser.GetBrowser().MainFrame){
return TwitterUtils.IsTweetDeckWebsite(frame); return TwitterUtils.IsTweetDeckWebsite(frame);
} }
@@ -43,10 +49,6 @@ namespace TweetDuck.Core{
RequestHandler = new RequestHandlerBrowser() RequestHandler = new RequestHandlerBrowser()
}; };
#if DEBUG
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif
this.browser.LoadingStateChanged += browser_LoadingStateChanged; this.browser.LoadingStateChanged += browser_LoadingStateChanged;
this.browser.FrameLoadStart += browser_FrameLoadStart; this.browser.FrameLoadStart += browser_FrameLoadStart;
this.browser.FrameLoadEnd += browser_FrameLoadEnd; this.browser.FrameLoadEnd += browser_FrameLoadEnd;
@@ -122,14 +124,18 @@ namespace TweetDuck.Core{
} }
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){ private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
if (e.Frame.IsMain){ IFrame frame = e.Frame;
if (frame.IsMain){
if (Program.UserConfig.ZoomLevel != 100){ if (Program.UserConfig.ZoomLevel != 100){
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel); BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
} }
if (TwitterUtils.IsTwitterWebsite(e.Frame)){ if (TwitterUtils.IsTwitterWebsite(frame)){
ScriptLoader.ExecuteFile(e.Frame, "twitter.js"); ScriptLoader.ExecuteFile(frame, "twitter.js", browser);
} }
frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride);
} }
} }
@@ -137,11 +143,11 @@ namespace TweetDuck.Core{
IFrame frame = e.Frame; IFrame frame = e.Frame;
if (frame.IsMain && TwitterUtils.IsTweetDeckWebsite(frame)){ if (frame.IsMain && TwitterUtils.IsTweetDeckWebsite(frame)){
frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride);
UpdateProperties(); UpdateProperties();
TweetDeckBridge.RestoreSessionData(frame); TweetDeckBridge.RestoreSessionData(frame);
ScriptLoader.ExecuteFile(frame, "code.js"); ScriptLoader.ExecuteFile(frame, "code.js", browser);
ScriptLoader.ExecuteFile(frame, "update.js", browser);
InjectBrowserCSS(); InjectBrowserCSS();
ReinjectCustomCSS(Program.UserConfig.CustomBrowserCSS); ReinjectCustomCSS(Program.UserConfig.CustomBrowserCSS);
UserConfig_SoundNotificationInfoChanged(null, EventArgs.Empty); UserConfig_SoundNotificationInfoChanged(null, EventArgs.Empty);
@@ -149,7 +155,7 @@ namespace TweetDuck.Core{
TweetDeckBridge.ResetStaticProperties(); TweetDeckBridge.ResetStaticProperties();
if (Program.UserConfig.FirstRun){ if (Program.UserConfig.FirstRun){
ScriptLoader.ExecuteFile(frame, "introduction.js"); ScriptLoader.ExecuteFile(frame, "introduction.js", browser);
} }
} }
} }
@@ -209,7 +215,7 @@ namespace TweetDuck.Core{
} }
public void InjectBrowserCSS(){ public void InjectBrowserCSS(){
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css").TrimEnd()); browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css", false, browser)?.TrimEnd() ?? string.Empty);
} }
public void ReinjectCustomCSS(string css){ public void ReinjectCustomCSS(string css){
@@ -224,6 +230,10 @@ namespace TweetDuck.Core{
browser.ExecuteScriptAsync("TDGF_showTweetDetail", columnId, chirpId, fallbackUrl); browser.ExecuteScriptAsync("TDGF_showTweetDetail", columnId, chirpId, fallbackUrl);
} }
public void AddSearchColumn(string query){
browser.ExecuteScriptAsync("TDGF_performSearch", query);
}
public void TriggerTweetScreenshot(){ public void TriggerTweetScreenshot(){
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()"); browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
} }
@@ -239,5 +249,9 @@ namespace TweetDuck.Core{
public void ApplyROT13(){ public void ApplyROT13(){
browser.ExecuteScriptAsync("TDGF_applyROT13()"); browser.ExecuteScriptAsync("TDGF_applyROT13()");
} }
public void ShowUpdateNotification(string versionTag, string releaseNotes){
browser.ExecuteScriptAsync("TDUF_displayNotification", versionTag, Convert.ToBase64String(Encoding.GetEncoding("iso-8859-1").GetBytes(releaseNotes)));
}
} }
} }

View File

@@ -127,6 +127,33 @@ namespace TweetDuck.Core.Utils{
} }
} }
public static void OpenExternalSearch(string query){
if (string.IsNullOrWhiteSpace(query))return;
string searchUrl = Program.UserConfig.SearchEngineUrl;
if (string.IsNullOrEmpty(searchUrl)){
if (FormMessage.Question("Search Options", "You have not configured a default search engine yet, would you like to do it now?", FormMessage.Yes, FormMessage.No)){
bool wereSettingsOpen = FormManager.TryFind<FormSettings>() != null;
FormManager.TryFind<FormBrowser>()?.OpenSettings();
if (wereSettingsOpen)return;
FormSettings settings = FormManager.TryFind<FormSettings>();
if (settings == null)return;
settings.FormClosed += (sender, args) => {
if (args.CloseReason == CloseReason.UserClosing && Program.UserConfig.SearchEngineUrl != searchUrl){
OpenExternalSearch(query);
}
};
}
}
else{
OpenExternalBrowser(searchUrl+Uri.EscapeUriString(query));
}
}
public static string GetFileNameFromUrl(string url){ public static string GetFileNameFromUrl(string url){
string file = Path.GetFileName(new Uri(url).AbsolutePath); string file = Path.GetFileName(new Uri(url).AbsolutePath);
return string.IsNullOrEmpty(file) ? null : file; return string.IsNullOrEmpty(file) ? null : file;
@@ -137,6 +164,8 @@ namespace TweetDuck.Core.Utils{
} }
public static WebClient CreateWebClient(){ public static WebClient CreateWebClient(){
WindowsUtils.EnsureTLS12();
WebClient client = new WebClient{ Proxy = null }; WebClient client = new WebClient{ Proxy = null };
client.Headers[HttpRequestHeader.UserAgent] = HeaderUserAgent; client.Headers[HttpRequestHeader.UserAgent] = HeaderUserAgent;
return client; return client;
@@ -172,11 +201,5 @@ namespace TweetDuck.Core.Utils{
public static void SetZoomLevel(IBrowser browser, int percentage){ public static void SetZoomLevel(IBrowser browser, int percentage){
browser.GetHost().SetZoomLevel(Math.Log(percentage/100.0, 1.2)); browser.GetHost().SetZoomLevel(Math.Log(percentage/100.0, 1.2));
} }
#if DEBUG
public static void HandleConsoleMessage(object sender, ConsoleMessageEventArgs e){
Debug.WriteLine("[Console] {0} ({1}:{2})", e.Message, e.Source, e.Line);
}
#endif
} }
} }

View File

@@ -13,11 +13,11 @@ namespace TweetDuck.Core.Utils{
public const string TweetDeckURL = "https://tweetdeck.twitter.com"; public const string TweetDeckURL = "https://tweetdeck.twitter.com";
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153); public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
public const string BackgroundColorOverride = "let e=document.createElement('style');document.head.appendChild(e);e.innerHTML='body,body::before{background:#1c6399!important}'"; public const string BackgroundColorOverride = "setTimeout(function f(){let h=document.head;if(!h){setTimeout(f,5);return;}let e=document.createElement('style');e.innerHTML='body,body::before{background:#1c6399!important}';h.appendChild(e);},1)";
public static readonly ResourceLink LoadingSpinner = new ResourceLink("https://ton.twimg.com/tduck/spinner", ResourceHandler.FromByteArray(Properties.Resources.spinner, "image/apng")); public static readonly ResourceLink LoadingSpinner = new ResourceLink("https://ton.twimg.com/tduck/spinner", ResourceHandler.FromByteArray(Properties.Resources.spinner, "image/apng"));
private static readonly Lazy<Regex> RegexAccountLazy = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/(?!signup$|tos$|privacy$)([^/]+)/?$", RegexOptions.Compiled), false); private static readonly Lazy<Regex> RegexAccountLazy = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/(?!signup$|tos$|privacy$|search$|search-)([^/?]+)/?$", RegexOptions.Compiled), false);
public static Regex RegexAccount => RegexAccountLazy.Value; public static Regex RegexAccount => RegexAccountLazy.Value;
public static readonly string[] DictionaryWords = { public static readonly string[] DictionaryWords = {
@@ -41,8 +41,8 @@ namespace TweetDuck.Core.Utils{
} }
private static string ExtractMediaBaseLink(string url){ private static string ExtractMediaBaseLink(string url){
int dot = url.LastIndexOf('/'); int slash = url.LastIndexOf('/');
return dot == -1 ? url : StringUtils.ExtractBefore(url, ':', dot); return slash == -1 ? url : StringUtils.ExtractBefore(url, ':', slash);
} }
public static string GetMediaLink(string url, ImageQuality quality){ public static string GetMediaLink(string url, ImageQuality quality){
@@ -91,7 +91,7 @@ namespace TweetDuck.Core.Utils{
AutoUpgradeEnabled = true, AutoUpgradeEnabled = true,
OverwritePrompt = urls.Length == 1, OverwritePrompt = urls.Length == 1,
Title = "Save Image", Title = "Save Image",
FileName = $"{string.Join(" ", fileNameParts.Where(part => part.Length > 0))}{ext}", FileName = $"{string.Join(" ", fileNameParts.Where(part => !string.IsNullOrEmpty(part)))}{ext}",
Filter = (urls.Length == 1 ? "Image" : "Images")+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}") Filter = (urls.Length == 1 ? "Image" : "Images")+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
}){ }){
if (dialog.ShowDialog() == DialogResult.OK){ if (dialog.ShowDialog() == DialogResult.OK){

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Net;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
@@ -16,6 +17,8 @@ namespace TweetDuck.Core.Utils{
public static int CurrentProcessID { get; } public static int CurrentProcessID { get; }
public static bool ShouldAvoidToolWindow { get; } public static bool ShouldAvoidToolWindow { get; }
private static bool HasMicrosoftBeenBroughtTo2008Yet;
static WindowsUtils(){ static WindowsUtils(){
using(Process me = Process.GetCurrentProcess()){ using(Process me = Process.GetCurrentProcess()){
@@ -25,6 +28,14 @@ namespace TweetDuck.Core.Utils{
Version ver = Environment.OSVersion.Version; Version ver = Environment.OSVersion.Version;
ShouldAvoidToolWindow = ver.Major == 6 && ver.Minor == 2; // windows 8/10 ShouldAvoidToolWindow = ver.Major == 6 && ver.Minor == 2; // windows 8/10
} }
public static void EnsureTLS12(){
if (!HasMicrosoftBeenBroughtTo2008Yet){
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
ServicePointManager.SecurityProtocol &= ~(SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11);
HasMicrosoftBeenBroughtTo2008Yet = true;
}
}
public static void CreateDirectoryForFile(string file){ public static void CreateDirectoryForFile(string file){
string dir = Path.GetDirectoryName(file); string dir = Path.GetDirectoryName(file);

36
Data/Result.cs Normal file
View File

@@ -0,0 +1,36 @@
using System;
namespace TweetDuck.Data{
sealed class Result<T>{
public bool HasValue => exception == null;
public T Value => HasValue ? value : throw new InvalidOperationException("Requested value from a failed result.");
public Exception Exception => exception ?? throw new InvalidOperationException("Requested exception from a successful result.");
private readonly T value;
private readonly Exception exception;
public Result(T value){
this.value = value;
this.exception = null;
}
public Result(Exception exception){
this.value = default(T);
this.exception = exception ?? throw new ArgumentNullException(nameof(exception));
}
public void Handle(Action<T> onSuccess, Action<Exception> onException){
if (HasValue){
onSuccess(value);
}
else{
onException(exception);
}
}
public Result<R> Select<R>(Func<T, R> map){
return HasValue ? new Result<R>(map(value)) : new Result<R>(exception);
}
}
}

View File

@@ -62,8 +62,7 @@ namespace TweetDuck.Data{
} }
public bool Contains(K1 outerKey, K2 innerKey){ public bool Contains(K1 outerKey, K2 innerKey){
Dictionary<K2, V> innerDict; return dict.TryGetValue(outerKey, out Dictionary<K2, V> innerDict) && innerDict.ContainsKey(innerKey);
return dict.TryGetValue(outerKey, out innerDict) && innerDict.ContainsKey(innerKey);
} }
public int Count(){ public int Count(){

View File

@@ -36,7 +36,7 @@ namespace TweetDuck.Plugins.Controls{
labelDescription.Visible = false; labelDescription.Visible = false;
} }
panelDescription_Resize(panelDescription, null); panelDescription_Resize(panelDescription, EventArgs.Empty);
} }
private void panelDescription_Resize(object sender, EventArgs e){ private void panelDescription_Resize(object sender, EventArgs e){

View File

@@ -1,5 +1,8 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace TweetDuck.Plugins.Enums{ namespace TweetDuck.Plugins.Enums{
[Flags] [Flags]
@@ -45,5 +48,50 @@ namespace TweetDuck.Plugins.Enums{
default: return string.Empty; default: return string.Empty;
} }
} }
public static IReadOnlyDictionary<PluginEnvironment, T> Map<T>(T forNone, T forBrowser, T forNotification){
return new PluginEnvironmentDictionary<T>(forNone, forBrowser, forNotification);
}
[SuppressMessage("ReSharper", "MemberHidesStaticFromOuterClass")]
private sealed class PluginEnvironmentDictionary<T> : IReadOnlyDictionary<PluginEnvironment, T>{
private const int TotalKeys = 3;
public IEnumerable<PluginEnvironment> Keys => Enum.GetValues(typeof(PluginEnvironment)).Cast<PluginEnvironment>();
public IEnumerable<T> Values => data;
public int Count => TotalKeys;
public T this[PluginEnvironment key] => data[(int)key];
private readonly T[] data;
public PluginEnvironmentDictionary(T forNone, T forBrowser, T forNotification){
this.data = new T[TotalKeys];
this.data[(int)PluginEnvironment.None] = forNone;
this.data[(int)PluginEnvironment.Browser] = forBrowser;
this.data[(int)PluginEnvironment.Notification] = forNotification;
}
public bool ContainsKey(PluginEnvironment key){
return key >= 0 && (int)key < TotalKeys;
}
public bool TryGetValue(PluginEnvironment key, out T value){
if (ContainsKey(key)){
value = this[key];
return true;
}
else{
value = default(T);
return false;
}
}
public IEnumerator<KeyValuePair<PluginEnvironment, T>> GetEnumerator(){
return Keys.Select(key => new KeyValuePair<PluginEnvironment, T>(key, this[key])).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
} }
} }

View File

@@ -1,28 +1,26 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Enums;
namespace TweetDuck.Plugins{ namespace TweetDuck.Plugins{
sealed class Plugin{ sealed class Plugin{
private const string VersionWildcard = "*"; private const string VersionWildcard = "*";
private static readonly Version AppVersion = new Version(Program.VersionTag);
public string Identifier { get; } public string Identifier { get; }
public PluginGroup Group { get; } public PluginGroup Group { get; }
public PluginEnvironment Environments { get; } public PluginEnvironment Environments { get; }
public string Name => metadata["NAME"]; public string Name { get; }
public string Description => metadata["DESCRIPTION"]; public string Description { get; }
public string Author => metadata["AUTHOR"]; public string Author { get; }
public string Version => metadata["VERSION"]; public string Version { get; }
public string Website => metadata["WEBSITE"]; public string Website { get; }
public string ConfigFile => metadata["CONFIGFILE"]; public string ConfigFile { get; }
public string ConfigDefault => metadata["CONFIGDEFAULT"]; public string ConfigDefault { get; }
public string RequiredVersion => metadata["REQUIRES"]; public Version RequiredVersion { get; }
public bool CanRun { get; private set; } public bool CanRun { get; }
public bool HasConfig{ public bool HasConfig{
get => ConfigFile.Length > 0 && GetFullPathIfSafe(PluginFolder.Data, ConfigFile).Length > 0; get => ConfigFile.Length > 0 && GetFullPathIfSafe(PluginFolder.Data, ConfigFile).Length > 0;
@@ -42,41 +40,25 @@ namespace TweetDuck.Plugins{
private readonly string pathRoot; private readonly string pathRoot;
private readonly string pathData; private readonly string pathData;
private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(8){
{ "NAME", "" },
{ "DESCRIPTION", "" },
{ "AUTHOR", "(anonymous)" },
{ "VERSION", "(unknown)" },
{ "WEBSITE", "" },
{ "CONFIGFILE", "" },
{ "CONFIGDEFAULT", "" },
{ "REQUIRES", VersionWildcard }
};
private Plugin(string path, string name, PluginGroup group, PluginEnvironment environments){ private Plugin(PluginGroup group, string identifier, string pathRoot, string pathData, Builder builder){
this.pathRoot = path; this.pathRoot = pathRoot;
this.pathData = Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name); this.pathData = pathData;
this.Identifier = group.GetIdentifierPrefix()+name;
this.Group = group; this.Group = group;
this.Environments = environments; this.Identifier = identifier;
} this.Environments = builder.Environments;
private void OnMetadataLoaded(){ this.Name = builder.Name;
CanRun = CheckRequiredVersion(RequiredVersion); this.Description = builder.Description;
this.Author = builder.Author;
this.Version = builder.Version;
this.Website = builder.Website;
this.ConfigFile = builder.ConfigFile;
this.ConfigDefault = builder.ConfigDefault;
this.RequiredVersion = builder.RequiredVersion;
string configPath = ConfigPath, defaultConfigPath = DefaultConfigPath; this.CanRun = AppVersion >= RequiredVersion;
if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
string dataFolder = GetPluginFolder(PluginFolder.Data);
try{
Directory.CreateDirectory(dataFolder);
File.Copy(defaultConfigPath, configPath, false);
}catch(Exception e){
throw new IOException("Could not generate a configuration file for '"+Identifier+"' plugin: "+e.Message, e);
}
}
} }
public string GetScriptPath(PluginEnvironment environment){ public string GetScriptPath(PluginEnvironment environment){
@@ -132,78 +114,94 @@ namespace TweetDuck.Plugins{
return obj is Plugin plugin && plugin.Identifier.Equals(Identifier); return obj is Plugin plugin && plugin.Identifier.Equals(Identifier);
} }
// Static // Builder
private static readonly Version AppVersion = new Version(Program.VersionTag);
private static readonly string[] EndTag = { "[END]" };
public static Plugin CreateFromFolder(string path, PluginGroup group){ public sealed class Builder{
Plugin plugin = new Plugin(path, Path.GetFileName(path), group, LoadEnvironments(path)); private static readonly Version DefaultRequiredVersion = new Version(0, 0, 0, 0);
LoadMetadata(path, plugin);
return plugin;
}
private static PluginEnvironment LoadEnvironments(string path){ public string Name { get; private set; }
PluginEnvironment environments = PluginEnvironment.None; public string Description { get; private set; } = string.Empty;
public string Author { get; private set; } = "(anonymous)";
public string Version { get; private set; } = "(unknown)";
public string Website { get; private set; } = string.Empty;
public string ConfigFile { get; private set; } = string.Empty;
public string ConfigDefault { get; private set; } = string.Empty;
public Version RequiredVersion { get; private set; } = DefaultRequiredVersion;
foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){ public PluginEnvironment Environments { get; private set; } = PluginEnvironment.None;
environments |= PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal));
private readonly PluginGroup group;
private readonly string pathRoot;
private readonly string pathData;
private readonly string identifier;
public Builder(PluginGroup group, string name, string pathRoot, string pathData){
this.group = group;
this.pathRoot = pathRoot;
this.pathData = pathData;
this.identifier = group.GetIdentifierPrefix()+name;
} }
if (environments == PluginEnvironment.None){ public void SetFromTag(string tag, string value){
throw new ArgumentException("Plugin has no script files"); switch(tag){
} case "NAME": this.Name = value; break;
case "DESCRIPTION": this.Description = value; break;
return environments; case "AUTHOR": this.Author = value; break;
} case "VERSION": this.Version = value; break;
case "WEBSITE": this.Website = value; break;
private static void LoadMetadata(string path, Plugin plugin){ case "CONFIGFILE": this.ConfigFile = value; break;
string metaFile = Path.Combine(path, ".meta"); case "CONFIGDEFAULT": this.ConfigDefault = value; break;
case "REQUIRES": SetRequiredVersion(value); break;
if (!File.Exists(metaFile)){ default: throw new FormatException("Invalid metadata tag: "+tag);
throw new ArgumentException("Missing .meta file");
}
string currentTag = null, currentContents = string.Empty;
foreach(string line in File.ReadAllLines(metaFile, Encoding.UTF8).Concat(EndTag).Select(line => line.TrimEnd()).Where(line => line.Length > 0)){
if (line[0] == '[' && line[line.Length-1] == ']'){
if (currentTag != null){
plugin.metadata[currentTag] = currentContents;
}
currentTag = line.Substring(1, line.Length-2).ToUpper();
currentContents = string.Empty;
if (line.Equals(EndTag[0])){
break;
}
if (!plugin.metadata.ContainsKey(currentTag)){
throw new FormatException("Invalid metadata tag: "+currentTag);
}
} }
else if (currentTag != null){ }
currentContents = currentContents.Length == 0 ? line : currentContents+Environment.NewLine+line;
public void AddEnvironment(PluginEnvironment environment){
this.Environments |= environment;
}
private void SetRequiredVersion(string versionStr){
if (System.Version.TryParse(versionStr, out Version version)){
this.RequiredVersion = version;
}
else if (versionStr == VersionWildcard){
this.RequiredVersion = DefaultRequiredVersion;
} }
else{ else{
throw new FormatException("Missing metadata tag before value: "+line); throw new FormatException("Plugin contains invalid minimum version: "+versionStr);
} }
} }
if (plugin.Name.Length == 0){ public Plugin BuildAndSetup(){
throw new FormatException("Plugin is missing a name in the .meta file"); Plugin plugin = new Plugin(group, identifier, pathRoot, pathData, this);
if (plugin.Name.Length == 0){
throw new InvalidOperationException("Plugin is missing a name in the .meta file");
}
if (plugin.Environments == PluginEnvironment.None){
throw new InvalidOperationException("Plugin has no script files");
}
// setup
string configPath = plugin.ConfigPath, defaultConfigPath = plugin.DefaultConfigPath;
if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
string dataFolder = plugin.GetPluginFolder(PluginFolder.Data);
try{
Directory.CreateDirectory(dataFolder);
File.Copy(defaultConfigPath, configPath, false);
}catch(Exception e){
throw new IOException($"Could not generate a configuration file for '{plugin.Identifier}' plugin: {e.Message}", e);
}
}
// done
return plugin;
} }
if (plugin.RequiredVersion.Length == 0 || !(plugin.RequiredVersion == VersionWildcard || System.Version.TryParse(plugin.RequiredVersion, out Version _))){
throw new FormatException("Plugin contains invalid version: "+plugin.RequiredVersion);
}
plugin.OnMetadataLoaded();
}
private static bool CheckRequiredVersion(string requires){
return requires == VersionWildcard || AppVersion >= new Version(requires);
} }
} }
} }

56
Plugins/PluginLoader.cs Normal file
View File

@@ -0,0 +1,56 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using TweetDuck.Plugins.Enums;
namespace TweetDuck.Plugins{
static class PluginLoader{
private static readonly string[] EndTag = { "[END]" };
public static Plugin FromFolder(string path, PluginGroup group){
string name = Path.GetFileName(path);
if (string.IsNullOrEmpty(name)){
throw new ArgumentException("Could not extract directory name from path: "+path);
}
Plugin.Builder builder = new Plugin.Builder(group, name, path, Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name));
foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){
builder.AddEnvironment(PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal)));
}
string metaFile = Path.Combine(path, ".meta");
if (!File.Exists(metaFile)){
throw new ArgumentException("Plugin is missing a .meta file");
}
string currentTag = null, currentContents = string.Empty;
foreach(string line in File.ReadAllLines(metaFile, Encoding.UTF8).Concat(EndTag).Select(line => line.TrimEnd()).Where(line => line.Length > 0)){
if (line[0] == '[' && line[line.Length-1] == ']'){
if (currentTag != null){
builder.SetFromTag(currentTag, currentContents);
}
currentTag = line.Substring(1, line.Length-2).ToUpper();
currentContents = string.Empty;
if (line.Equals(EndTag[0])){
break;
}
}
else if (currentTag != null){
currentContents = currentContents.Length == 0 ? line : currentContents+Environment.NewLine+line;
}
else{
throw new FormatException("Missing metadata tag before value: "+line);
}
}
return builder.BuildAndSetup();
}
}
}

View File

@@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using TweetDuck.Core; using TweetDuck.Core.Other.Interfaces;
using TweetDuck.Data; using TweetDuck.Data;
using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events; using TweetDuck.Plugins.Events;
@@ -12,11 +12,15 @@ using TweetDuck.Resources;
namespace TweetDuck.Plugins{ namespace TweetDuck.Plugins{
sealed class PluginManager{ sealed class PluginManager{
private static readonly Dictionary<PluginEnvironment, string> PluginSetupScripts = new Dictionary<PluginEnvironment, string>(4){ private static IReadOnlyDictionary<PluginEnvironment, string> LoadSetupScripts(){
{ PluginEnvironment.None, ScriptLoader.LoadResource("plugins.js") }, return PluginEnvironmentExtensions.Map(
{ PluginEnvironment.Browser, ScriptLoader.LoadResource("plugins.browser.js") }, ScriptLoader.LoadResource("plugins.js"),
{ PluginEnvironment.Notification, ScriptLoader.LoadResource("plugins.notification.js") } ScriptLoader.LoadResource("plugins.browser.js"),
}; ScriptLoader.LoadResource("plugins.notification.js")
);
}
private static readonly IReadOnlyDictionary<PluginEnvironment, string> PluginSetupScripts = LoadSetupScripts();
public string PathOfficialPlugins => Path.Combine(rootPath, "official"); public string PathOfficialPlugins => Path.Combine(rootPath, "official");
public string PathCustomPlugins => Path.Combine(rootPath, "user"); public string PathCustomPlugins => Path.Combine(rootPath, "user");
@@ -132,7 +136,7 @@ namespace TweetDuck.Plugins{
Plugin plugin; Plugin plugin;
try{ try{
plugin = Plugin.CreateFromFolder(fullDir, group); plugin = PluginLoader.FromFolder(fullDir, group);
}catch(Exception e){ }catch(Exception e){
loadErrors.Add(group.GetIdentifierPrefix()+Path.GetFileName(fullDir)+": "+e.Message); loadErrors.Add(group.GetIdentifierPrefix()+Path.GetFileName(fullDir)+": "+e.Message);
continue; continue;

View File

@@ -20,7 +20,7 @@ namespace TweetDuck{
public const string BrandName = "TweetDuck"; public const string BrandName = "TweetDuck";
public const string Website = "https://tweetduck.chylex.com"; public const string Website = "https://tweetduck.chylex.com";
public const string VersionTag = "1.13.1"; public const string VersionTag = "1.13.4";
public static readonly bool IsPortable = File.Exists("makeportable"); public static readonly bool IsPortable = File.Exists("makeportable");
@@ -151,10 +151,9 @@ namespace TweetDuck{
Application.ApplicationExit += (sender, args) => ExitCleanup(); Application.ApplicationExit += (sender, args) => ExitCleanup();
UpdaterSettings updaterSettings = new UpdaterSettings{ UpdaterSettings updaterSettings = new UpdaterSettings(InstallerPath){
AllowPreReleases = Arguments.HasFlag(Arguments.ArgDebugUpdates), AllowPreReleases = Arguments.HasFlag(Arguments.ArgDebugUpdates),
DismissedUpdate = UserConfig.DismissedUpdate, DismissedUpdate = UserConfig.DismissedUpdate
InstallerDownloadFolder = InstallerPath
}; };
FormBrowser mainForm = new FormBrowser(updaterSettings); FormBrowser mainForm = new FormBrowser(updaterSettings);

Binary file not shown.

Binary file not shown.

View File

@@ -9,7 +9,7 @@ Clear columns
chylex chylex
[version] [version]
1.1.1 1.2
[website] [website]
https://tweetduck.chylex.com https://tweetduck.chylex.com

View File

@@ -7,12 +7,17 @@ constructor(){
enabled(){ enabled(){
// prepare variables and functions // prepare variables and functions
var clearColumn = (columnName) => { var clearColumn = (columnName) => {
TD.controller.columnManager.get(columnName).clear(); let col = TD.controller.columnManager.get(columnName);
return if !col.isClearable();
col.clear();
TD.controller.stats.columnActionClick("clear"); TD.controller.stats.columnActionClick("clear");
}; };
var resetColumn = (columnName) => { var resetColumn = (columnName) => {
var col = TD.controller.columnManager.get(columnName); let col = TD.controller.columnManager.get(columnName);
return if !col.isClearable();
col.model.setClearedTimestamp(0); col.model.setClearedTimestamp(0);
col.reloadTweets(); col.reloadTweets();
}; };
@@ -38,7 +43,7 @@ enabled(){
$(document).off("mousemove", this.eventKeyUp); $(document).off("mousemove", this.eventKeyUp);
} }
$("#clear-columns-btn-all").text(pressed ? "Restore columns" : "Clear columns"); $("#clear-columns-btn-all-1,#clear-columns-btn-all-2").text(pressed ? "Restore columns" : "Clear columns");
} }
}; };
@@ -81,7 +86,7 @@ enabled(){
// add column buttons and keyboard shortcut info to UI // add column buttons and keyboard shortcut info to UI
replaceMustache("column/column_header.mustache", "</header>", [ replaceMustache("column/column_header.mustache", "</header>", [
'{{^isTemporary}}', '{{^isTemporary}}',
'<a class="column-header-link" href="#" data-action="td-clearcolumns-dosingle" style="right:34px">', '<a class="column-header-link td-clear-column-shortcut" href="#" data-action="td-clearcolumns-dosingle" style="right:34px">',
'<i class="icon icon-clear-timeline js-show-tip" data-placement="bottom" data-original-title="Clear column (hold Shift to restore)"></i>', '<i class="icon icon-clear-timeline js-show-tip" data-placement="bottom" data-original-title="Clear column (hold Shift to restore)"></i>',
'</a>', '</a>',
'{{/isTemporary}}', '{{/isTemporary}}',
@@ -98,11 +103,15 @@ enabled(){
// load custom style // load custom style
var css = window.TDPF_createCustomStyle(this); var css = window.TDPF_createCustomStyle(this);
css.insert(".js-app-add-column.is-hidden + #clear-columns-btn-all-parent-1 { display: none; }");
css.insert(".column-navigator-overflow #clear-columns-btn-all-parent-2 { display: none; }");
css.insert(".column-title { margin-right: 60px !important; }"); css.insert(".column-title { margin-right: 60px !important; }");
css.insert(".column-type-message .column-title { margin-right: 115px !important; }");
css.insert(".mark-all-read-link { right: 59px !important; }"); css.insert(".mark-all-read-link { right: 59px !important; }");
css.insert(".open-compose-dm-link { right: 90px !important; }"); css.insert(".open-compose-dm-link { right: 90px !important; }");
css.insert("button[data-action='clear'].btn-options-tray { display: none !important; }"); css.insert("button[data-action='clear'].btn-options-tray { display: none !important; }");
css.insert("[data-td-icon='icon-message'] .column-title { margin-right: 115px !important; }");
css.insert("[data-td-icon='icon-schedule'] .td-clear-column-shortcut { display: none; }");
css.insert("[data-td-icon='icon-custom-timeline'] .td-clear-column-shortcut { display: none; }");
} }
ready(){ ready(){
@@ -113,18 +122,22 @@ ready(){
$(document).on("keyup", this.eventKeyUp); $(document).on("keyup", this.eventKeyUp);
// add clear all button // add clear all button
$("nav.app-navigator").first().append([ const generateButton = (idParent, idButton) => {
'<a id="clear-columns-btn-all-parent" class="js-header-action link-clean cf app-nav-link padding-h--10" data-title="Clear columns (hold Shift to restore)" data-action="td-clearcolumns-doall">', return `
'<div class="obj-left margin-l--2"><i class="icon icon-medium icon-clear-timeline"></i></div>', <a id="${idParent}" class="js-header-action link-clean cf app-nav-link padding-h--10" data-title="Clear columns (hold Shift to restore)" data-action="td-clearcolumns-doall">
'<div id="clear-columns-btn-all" class="nbfc padding-ts hide-condensed txt-size--16">Clear columns</div>', <div class="obj-left margin-l--2"><i class="icon icon-medium icon-clear-timeline"></i></div>
'</a></nav>' <div id="${idButton}" class="nbfc padding-ts hide-condensed txt-size--16 app-nav-link-text">Clear columns</div>
].join("")); </a>`;
};
$(".js-app-add-column").first().after(generateButton("clear-columns-btn-all-parent-1", "clear-columns-btn-all-1"));
$(".js-column-nav-list").first().append(generateButton("clear-columns-btn-all-parent-2", "clear-columns-btn-all-2"));
// setup tooltip handling // setup tooltip handling
var tooltipEvents = $._data($(".js-header-action")[0]).events; var tooltipEvents = $._data($(".js-header-action")[0]).events;
if (tooltipEvents.mouseover && tooltipEvents.mouseover.length && tooltipEvents.mouseout && tooltipEvents.mouseout.length){ if (tooltipEvents.mouseover && tooltipEvents.mouseover.length && tooltipEvents.mouseout && tooltipEvents.mouseout.length){
$("#clear-columns-btn-all-parent").on("mouseover", tooltipEvents.mouseover[0].handler).on("mouseout", tooltipEvents.mouseout[0].handler); $("#clear-columns-btn-all-parent-1,#clear-columns-btn-all-parent-2").on("mouseover", tooltipEvents.mouseover[0].handler).on("mouseout", tooltipEvents.mouseout[0].handler);
} }
} }

View File

@@ -8,7 +8,7 @@ Edit layout & design
chylex chylex
[version] [version]
1.2.4 1.2.5
[website] [website]
https://tweetduck.chylex.com https://tweetduck.chylex.com

View File

@@ -610,7 +610,7 @@ ${this.config.revertIcons ? `
${currentTheme === "black" ? ` ${currentTheme === "black" ? `
html.dark a, html.dark a:hover, html.dark a:focus, html.dark a:active { color: #8bd } html.dark a, html.dark a:hover, html.dark a:focus, html.dark a:active { color: #8bd }
#tduck-show-thread { color: #8bd !important } #tduck-show-thread, .other-replies-link { color: #8bd !important }
.quoted-tweet { border-color: #292f33 !important } .quoted-tweet { border-color: #292f33 !important }
` : ``} ` : ``}

View File

@@ -92,63 +92,31 @@ html.dark .btn[disabled],html.dark .btn[disabled]:hover,html.dark .btn[disabled]
html.dark .btn-on-dark:focus{box-shadow:0 0 0 1px #292F33,0 0 0 3px #71C9F8} html.dark .btn-on-dark:focus{box-shadow:0 0 0 1px #292F33,0 0 0 3px #71C9F8}
html.dark .mdl-content .btn-on-dark:focus{box-shadow:0 0 0 1px #fff,0 0 0 3px #71C9F8} html.dark .mdl-content .btn-on-dark:focus{box-shadow:0 0 0 1px #fff,0 0 0 3px #71C9F8}
html.dark .is-inverted-dark .btn:hover,html.dark .is-inverted-dark .btn:focus,html.dark .is-inverted-dark .btn:active,html.dark .is-inverted-dark .btn.is-selected{background-color:#F2F9FF} html.dark .is-inverted-dark .btn:hover,html.dark .is-inverted-dark .btn:focus,html.dark .is-inverted-dark .btn:active,html.dark .is-inverted-dark .btn.is-selected{background-color:#F2F9FF}
html.dark .is-inverted-dark .btn-positive:hover,html.dark .is-inverted-dark .btn-positive-alt:hover,html.dark .is-inverted-dark .btn-fav.s-favorited:hover,html.dark .is-inverted-dark .s-following .follow-btn:hover,html.dark .s-following .is-inverted-dark .follow-btn:hover{background-color:#005FD1} html.dark .is-inverted-dark .btn-fav.s-favorited:hover,html.dark .is-inverted-dark .s-following .follow-btn:hover,html.dark .s-following .is-inverted-dark .follow-btn:hover{background-color:#005FD1}
html.dark .is-inverted-dark .btn-positive:focus,html.dark .is-inverted-dark .btn-positive-alt:focus,html.dark .is-inverted-dark .btn-fav.s-favorited:focus,html.dark .is-inverted-dark .s-following .follow-btn:focus,html.dark .s-following .is-inverted-dark .follow-btn:focus{background-color:#005FD1} html.dark .is-inverted-dark .btn-fav.s-favorited:focus,html.dark .is-inverted-dark .s-following .follow-btn:focus,html.dark .s-following .is-inverted-dark .follow-btn:focus{background-color:#005FD1}
html.dark .is-inverted-dark .btn-positive:active,html.dark .is-inverted-dark .btn-positive-alt:active,html.dark .is-inverted-dark .btn-fav.s-favorited:active,html.dark .is-inverted-dark .s-following .follow-btn:active,html.dark .s-following .is-inverted-dark .follow-btn:active,html.dark .is-inverted-dark .btn-positive.is-selected,html.dark .is-inverted-dark .is-selected.btn-positive-alt,html.dark .is-inverted-dark .is-selected.btn-fav.s-favorited,html.dark .is-inverted-dark .s-following .is-selected.follow-btn,html.dark .s-following .is-inverted-dark .is-selected.follow-btn{background-color:#005FD1} html.dark .is-inverted-dark .btn-fav.s-favorited:active,html.dark .is-inverted-dark .s-following .follow-btn:active,html.dark .s-following .is-inverted-dark .follow-btn:active,html.dark .is-inverted-dark .is-selected.btn-fav.s-favorited,html.dark .is-inverted-dark .s-following .is-selected.follow-btn,html.dark .s-following .is-inverted-dark .is-selected.follow-btn{background-color:#005FD1}
html.dark .is-inverted-dark .btn-positive-alt:hover,html.dark .is-inverted-dark .btn-fav.s-favorited:hover,html.dark .is-inverted-dark .s-following .follow-btn:hover,html.dark .s-following .is-inverted-dark .follow-btn:hover{background-color:#A01744} html.dark .is-inverted-dark .btn-fav.s-favorited:hover,html.dark .is-inverted-dark .s-following .follow-btn:hover,html.dark .s-following .is-inverted-dark .follow-btn:hover{background-color:#A01744}
html.dark .is-inverted-dark .btn-positive-alt:focus,html.dark .is-inverted-dark .btn-fav.s-favorited:focus,html.dark .is-inverted-dark .s-following .follow-btn:focus,html.dark .s-following .is-inverted-dark .follow-btn:focus{background-color:#A01744} html.dark .is-inverted-dark .btn-fav.s-favorited:focus,html.dark .is-inverted-dark .s-following .follow-btn:focus,html.dark .s-following .is-inverted-dark .follow-btn:focus{background-color:#A01744}
html.dark .is-inverted-dark .btn-positive-alt:active,html.dark .is-inverted-dark .btn-fav.s-favorited:active,html.dark .is-inverted-dark .s-following .follow-btn:active,html.dark .s-following .is-inverted-dark .follow-btn:active,html.dark .is-inverted-dark .btn-positive-alt.is-selected,html.dark .is-inverted-dark .is-selected.btn-fav.s-favorited,html.dark .is-inverted-dark .s-following .is-selected.follow-btn,html.dark .s-following .is-inverted-dark .is-selected.follow-btn{background-color:#A01744} html.dark .is-inverted-dark .btn-fav.s-favorited:active,html.dark .is-inverted-dark .s-following .follow-btn:active,html.dark .s-following .is-inverted-dark .follow-btn:active,html.dark .is-inverted-dark .is-selected.btn-fav.s-favorited,html.dark .is-inverted-dark .s-following .is-selected.follow-btn,html.dark .s-following .is-inverted-dark .is-selected.follow-btn{background-color:#A01744}
html.dark .is-inverted-dark .btn-negative:hover{background-color:#A01744} html.dark .btn-fav.s-favorited,html.dark .s-following .follow-btn{color:#fff;background-color:#1DA1F2;border:1px solid #1DA1F2}
html.dark .is-inverted-dark .btn-negative:focus{background-color:#A01744} html.dark .btn-fav.s-favorited:hover,html.dark .s-following .follow-btn:hover{color:#fff;background-color:#005FD1;border:1px solid #005FD1}
html.dark .is-inverted-dark .btn-negative:active,html.dark .is-inverted-dark .btn-negative.is-selected{background-color:#A01744} html.dark .btn-fav.s-favorited:focus,html.dark .s-following .follow-btn:focus{color:#fff;background-color:#005FD1;border:1px solid #005FD1;box-shadow:0 0 0 1px #fff,0 0 0 3px #71C9F8}
html.dark .is-inverted-dark .btn-tertiary:hover{background-color:#F5F8FA} html.dark .btn-fav.s-favorited:active,html.dark .s-following .follow-btn:active,html.dark .is-selected.btn-fav.s-favorited,html.dark .s-following .is-selected.follow-btn{color:#fff;background-color:#005FD1;border:1px solid #005FD1}
html.dark .is-inverted-dark .btn-tertiary:focus{background-color:#F5F8FA} html.dark [disabled].btn-fav.s-favorited,html.dark .s-following [disabled].follow-btn,html.dark [disabled].btn-fav.s-favorited:hover,html.dark .s-following [disabled].follow-btn:hover,html.dark [disabled].btn-fav.s-favorited:active,html.dark .s-following [disabled].follow-btn:active,html.dark .is-disabled.btn-fav.s-favorited,html.dark .s-following .is-disabled.follow-btn,html.dark .is-disabled.btn-fav.s-favorited:hover,html.dark .s-following .is-disabled.follow-btn:hover,html.dark .is-disabled.btn-fav.s-favorited:focus,html.dark .s-following .is-disabled.follow-btn:focus,html.dark .is-disabled.btn-fav.s-favorited:active,html.dark .s-following .is-disabled.follow-btn:active{color:#fff;background-color:#1DA1F2;border:1px solid #1DA1F2}
html.dark .is-inverted-dark .btn-tertiary:active,html.dark .is-inverted-dark .btn-tertiary.is-selected{background-color:#F5F8FA} html.dark .btn-fav.s-favorited:hover,html.dark .s-following .follow-btn:hover{color:#fff;background-color:#A01744;border:#A01744}
html.dark .btn-positive,html.dark .btn-positive-alt,html.dark .btn-fav.s-favorited,html.dark .s-following .follow-btn{color:#fff;background-color:#1DA1F2;border:1px solid #1DA1F2} html.dark .btn-fav.s-favorited:active,html.dark .s-following .follow-btn:active,html.dark .is-selected.btn-fav.s-favorited,html.dark .s-following .is-selected.follow-btn{color:#fff;background-color:#A01744;border:#A01744}
html.dark .btn-positive:hover,html.dark .btn-positive-alt:hover,html.dark .btn-fav.s-favorited:hover,html.dark .s-following .follow-btn:hover{color:#fff;background-color:#005FD1;border:1px solid #005FD1}
html.dark .btn-positive:focus,html.dark .btn-positive-alt:focus,html.dark .btn-fav.s-favorited:focus,html.dark .s-following .follow-btn:focus{color:#fff;background-color:#005FD1;border:1px solid #005FD1;box-shadow:0 0 0 1px #fff,0 0 0 3px #71C9F8}
html.dark .btn-positive:active,html.dark .btn-positive-alt:active,html.dark .btn-fav.s-favorited:active,html.dark .s-following .follow-btn:active,html.dark .btn-positive.is-selected,html.dark .is-selected.btn-positive-alt,html.dark .is-selected.btn-fav.s-favorited,html.dark .s-following .is-selected.follow-btn{color:#fff;background-color:#005FD1;border:1px solid #005FD1}
html.dark .btn-positive[disabled],html.dark [disabled].btn-positive-alt,html.dark [disabled].btn-fav.s-favorited,html.dark .s-following [disabled].follow-btn,html.dark .btn-positive[disabled]:hover,html.dark [disabled].btn-positive-alt:hover,html.dark [disabled].btn-fav.s-favorited:hover,html.dark .s-following [disabled].follow-btn:hover,html.dark .btn-positive[disabled]:active,html.dark [disabled].btn-positive-alt:active,html.dark [disabled].btn-fav.s-favorited:active,html.dark .s-following [disabled].follow-btn:active,html.dark .btn-positive.is-disabled,html.dark .is-disabled.btn-positive-alt,html.dark .is-disabled.btn-fav.s-favorited,html.dark .s-following .is-disabled.follow-btn,html.dark .btn-positive.is-disabled:hover,html.dark .is-disabled.btn-positive-alt:hover,html.dark .is-disabled.btn-fav.s-favorited:hover,html.dark .s-following .is-disabled.follow-btn:hover,html.dark .btn-positive.is-disabled:focus,html.dark .is-disabled.btn-positive-alt:focus,html.dark .is-disabled.btn-fav.s-favorited:focus,html.dark .s-following .is-disabled.follow-btn:focus,html.dark .btn-positive.is-disabled:active,html.dark .is-disabled.btn-positive-alt:active,html.dark .is-disabled.btn-fav.s-favorited:active,html.dark .s-following .is-disabled.follow-btn:active{color:#fff;background-color:#1DA1F2;border:1px solid #1DA1F2}
html.dark .btn-compose{color:#fff;background-color:#2b7bb9}
html.dark .btn-compose:hover{color:#fff;background-color:#2b7bb9}
html.dark .btn-compose:focus{color:#fff;background-color:#2b7bb9}
html.dark .btn-compose:active,html.dark .btn-compose.is-selected{color:#fff;background-color:#2b7bb9}
html.dark .btn-positive-alt:hover,html.dark .btn-fav.s-favorited:hover,html.dark .s-following .follow-btn:hover{color:#fff;background-color:#A01744;border:#A01744}
html.dark .btn-positive-alt:active,html.dark .btn-fav.s-favorited:active,html.dark .s-following .follow-btn:active,html.dark .btn-positive-alt.is-selected,html.dark .is-selected.btn-fav.s-favorited,html.dark .s-following .is-selected.follow-btn{color:#fff;background-color:#A01744;border:#A01744}
html.dark .btn-negative{border-color:#E0245E;color:#fff;background-color:#E0245E}
html.dark .btn-negative:hover{color:#fff;background-color:#A01744;border-color:#A01744}
html.dark .btn-negative:focus{color:#fff;background-color:#A01744;border-color:#A01744;box-shadow:0 0 0 1px #fff,0 0 0 3px #F6809A}
html.dark .btn-negative:active,html.dark .btn-negative.is-selected{color:#fff;background-color:#A01744;border-color:#A01744}
html.dark .btn-negative[disabled],html.dark .btn-negative[disabled]:hover,html.dark .btn-negative[disabled]:active,html.dark .btn-negative.is-disabled,html.dark .btn-negative.is-disabled:hover,html.dark .btn-negative.is-disabled:focus,html.dark .btn-negative.is-disabled:active{border-color:#E0245E;color:#fff;background-color:#E0245E}
html.dark .btn-tertiary{border-color:#657786;color:#657786}
html.dark .btn-tertiary:hover{color:#657786;background-color:#F5F8FA;border-color:#657786}
html.dark .btn-tertiary:focus{color:#657786;background-color:#F5F8FA;border-color:#657786;box-shadow:0 0 0 1px #fff,0 0 0 3px #CCD6DD}
html.dark .btn-tertiary:active,html.dark .btn-tertiary.is-selected{color:#657786;background-color:#F5F8FA;border-color:#657786}
html.dark .btn-tertiary[disabled],html.dark .btn-tertiary[disabled]:hover,html.dark .btn-tertiary[disabled]:active,html.dark .btn-tertiary.is-disabled,html.dark .btn-tertiary.is-disabled:hover,html.dark .btn-tertiary.is-disabled:focus,html.dark .btn-tertiary.is-disabled:active{color:#AAB8C2;border-color:#e1e8ed;background-color:#eaeaea}
html.dark .btn-on-blue{color:#fff;background-color:#66757f} html.dark .btn-on-blue{color:#fff;background-color:#66757f}
html.dark .btn-on-blue:hover{color:#fff;background-color:#66757f} html.dark .btn-on-blue:hover{color:#fff;background-color:#66757f}
html.dark .btn-on-blue:focus{color:#fff;background-color:#66757f;box-shadow:0 0 2px 3px #50a5e6} html.dark .btn-on-blue:focus{color:#fff;background-color:#66757f;box-shadow:0 0 2px 3px #50a5e6}
html.dark .btn-on-blue:active,html.dark .btn-on-blue.is-selected{color:#fff;background-color:#434c51} html.dark .btn-on-blue:active,html.dark .btn-on-blue.is-selected{color:#fff;background-color:#434c51}
html.dark .btn-on-blue[disabled],html.dark .btn-on-blue[disabled]:hover,html.dark .btn-on-blue[disabled]:active,html.dark .btn-on-blue.is-disabled,html.dark .btn-on-blue.is-disabled:hover,html.dark .btn-on-blue.is-disabled:focus,html.dark .btn-on-blue.is-disabled:active{color:#fff;background-color:#66757f} html.dark .btn-on-blue[disabled],html.dark .btn-on-blue[disabled]:hover,html.dark .btn-on-blue[disabled]:active,html.dark .btn-on-blue.is-disabled,html.dark .btn-on-blue.is-disabled:hover,html.dark .btn-on-blue.is-disabled:focus,html.dark .btn-on-blue.is-disabled:active{color:#fff;background-color:#66757f}
html.dark .btn-neutral-negative{color:#d29b9a}
html.dark .btn-neutral-negative:hover,html.dark .btn-neutral-negative:focus{color:#d29b9a}
html.dark .btn-neutral-negative[disabled],html.dark .btn-neutral-negative[disabled]:hover,html.dark .btn-neutral-negative[disabled]:active,html.dark .btn-neutral-negative.is-disabled,html.dark .btn-neutral-negative.is-disabled:hover,html.dark .btn-neutral-negative.is-disabled:focus,html.dark .btn-neutral-negative.is-disabled:active{color:#d29b9a}
html.dark .btn-neutral-positive{color:#8bd}
html.dark .btn-neutral-positive:hover,html.dark .btn-neutral-positive:focus{color:#8bd}
html.dark .btn-neutral-positive[disabled],html.dark .btn-neutral-positive[disabled]:hover,html.dark .btn-neutral-positive[disabled]:active,html.dark .btn-neutral-positive.is-disabled,html.dark .btn-neutral-positive.is-disabled:hover,html.dark .btn-neutral-positive.is-disabled:focus,html.dark .btn-neutral-positive.is-disabled:active{color:#8bd}
html.dark .btn-options-tray{color:#e1e8ed} html.dark .btn-options-tray{color:#e1e8ed}
html.dark .btn-options-tray:hover,html.dark .btn-options-tray:focus{color:#8bd} html.dark .btn-options-tray:hover,html.dark .btn-options-tray:focus{color:#8bd}
html.dark .btn-options-tray[disabled],html.dark .btn-options-tray[disabled]:hover,html.dark .btn-options-tray[disabled]:active,html.dark .btn-options-tray.is-disabled,html.dark .btn-options-tray.is-disabled:hover,html.dark .btn-options-tray.is-disabled:focus,html.dark .btn-options-tray.is-disabled:active{color:#8bd} html.dark .btn-options-tray[disabled],html.dark .btn-options-tray[disabled]:hover,html.dark .btn-options-tray[disabled]:active,html.dark .btn-options-tray.is-disabled,html.dark .btn-options-tray.is-disabled:hover,html.dark .btn-options-tray.is-disabled:focus,html.dark .btn-options-tray.is-disabled:active{color:#8bd}
html.dark .btn-bg-positive{background-color:rgba(102,117,127,0.5)} html.dark .btn-bg-positive{background-color:rgba(102,117,127,0.5)}
html.dark .btn-bg-positive:hover,html.dark .btn-bg-positive:focus{background-color:rgba(102,117,127,0.5)} html.dark .btn-bg-positive:hover,html.dark .btn-bg-positive:focus{background-color:rgba(102,117,127,0.5)}
html.dark .btn-bg-positive[disabled],html.dark .btn-bg-positive[disabled]:hover,html.dark .btn-bg-positive[disabled]:active,html.dark .btn-bg-positive.is-disabled,html.dark .btn-bg-positive.is-disabled:hover,html.dark .btn-bg-positive.is-disabled:focus,html.dark .btn-bg-positive.is-disabled:active{background-color:rgba(102,117,127,0.5)} html.dark .btn-bg-positive[disabled],html.dark .btn-bg-positive[disabled]:hover,html.dark .btn-bg-positive[disabled]:active,html.dark .btn-bg-positive.is-disabled,html.dark .btn-bg-positive.is-disabled:hover,html.dark .btn-bg-positive.is-disabled:focus,html.dark .btn-bg-positive.is-disabled:active{background-color:rgba(102,117,127,0.5)}
html.dark .btn-bg-negative{background-color:#5d5457}
html.dark .btn-bg-negative:hover,html.dark .btn-bg-negative:focus{background-color:#5d5457}
html.dark .btn-bg-negative[disabled],html.dark .btn-bg-negative[disabled]:hover,html.dark .btn-bg-negative[disabled]:active,html.dark .btn-bg-negative.is-disabled,html.dark .btn-bg-negative.is-disabled:hover,html.dark .btn-bg-negative.is-disabled:focus,html.dark .btn-bg-negative.is-disabled:active{background-color:#5d5457}
html.dark .btn-bg-white{background-color:#fff;color:#55acee}
html.dark .btn-bg-white:hover,html.dark .btn-bg-white:focus{background-color:#fff;color:#55acee}
html.dark .follow-btn .icon,html.dark .follow-btn .Icon{color:#1DA1F2} html.dark .follow-btn .icon,html.dark .follow-btn .Icon{color:#1DA1F2}
html.dark .input-group-button{border:1px solid #e1e8ed}
html.dark .account-profile-header{background-color:#1DA1F2} html.dark .account-profile-header{background-color:#1DA1F2}
html.dark .account-settings-bt{border-top:1px solid #e1e8ed} html.dark .account-settings-bt{border-top:1px solid #e1e8ed}
html.dark .account-settings-bb{border-bottom:1px solid #e1e8ed} html.dark .account-settings-bb{border-bottom:1px solid #e1e8ed}
@@ -440,12 +408,6 @@ html.dark .char-count:disabled{color:#777}
html.dark .over-char-count:disabled{color:#be1931} html.dark .over-char-count:disabled{color:#be1931}
html.dark .cmp-replyto{background-color:#eaeaea;border-top:1px solid #ddd} html.dark .cmp-replyto{background-color:#eaeaea;border-top:1px solid #ddd}
html.dark .s-link-added.s-photo-added p:last-child{border-top:1px solid #ddd} html.dark .s-link-added.s-photo-added p:last-child{border-top:1px solid #ddd}
html.dark .accs li{background:#eaeaea;border:1px solid #e1e8ed}
html.dark .accs li:hover{background:#e1e8ed}
html.dark .accs .icon,html.dark .accs .Icon{color:#999}
html.dark .accs .acc-selected{background-color:#55acee;border:1px solid #e1e8ed}
html.dark .accs .acc-selected i{color:#fff}
html.dark .accs .acc-selected:hover{border-color:#e1e8ed;background-color:#50a5e6}
html.dark .inline-reply{background-color:#485865;color:#fff} html.dark .inline-reply{background-color:#485865;color:#fff}
html.dark .inline-reply .btn-neutral,html.dark .inline-reply .character-count{color:#fff} html.dark .inline-reply .btn-neutral,html.dark .inline-reply .character-count{color:#fff}
html.dark .reply-triangle{border-color:transparent transparent #485865} html.dark .reply-triangle{border-color:transparent transparent #485865}

View File

@@ -9,7 +9,7 @@ Emoji keyboard
chylex chylex
[version] [version]
1.4.2 1.4.4
[website] [website]
https://tweetduck.chylex.com https://tweetduck.chylex.com

View File

@@ -32,6 +32,7 @@ enabled(){
this.css = window.TDPF_createCustomStyle(this); this.css = window.TDPF_createCustomStyle(this);
this.css.insert(".emoji-keyboard { position: absolute; width: 15.35em; background-color: white; border-radius: 1px; font-size: 24px; z-index: 9999 }"); this.css.insert(".emoji-keyboard { position: absolute; width: 15.35em; background-color: white; border-radius: 1px; font-size: 24px; z-index: 9999 }");
this.css.insert(".emoji-keyboard-popup-btn .icon { vertical-align: -4px !important }");
this.css.insert(".emoji-keyboard-list { height: 10.14em; padding: 0.1em; box-sizing: border-box; overflow-y: auto }"); this.css.insert(".emoji-keyboard-list { height: 10.14em; padding: 0.1em; box-sizing: border-box; overflow-y: auto }");
this.css.insert(".emoji-keyboard-list .separator { height: 26px }"); this.css.insert(".emoji-keyboard-list .separator { height: 26px }");
@@ -49,7 +50,7 @@ enabled(){
// layout // layout
var buttonHTML = '<button class="needsclick btn btn-on-blue txt-left padding-v--9 emoji-keyboard-popup-btn"><i class="icon icon-heart"></i></button>'; var buttonHTML = '<button class="needsclick btn btn-on-blue txt-left padding-v--6 padding-h--8 emoji-keyboard-popup-btn"><i class="icon icon-heart"></i></button>';
this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"]; this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="cf margin-t--12 margin-b--30">', '<div class="cf margin-t--12 margin-b--30">'+buttonHTML); TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="cf margin-t--12 margin-b--30">', '<div class="cf margin-t--12 margin-b--30">'+buttonHTML);

View File

@@ -59,7 +59,19 @@ Remove unsupported emoji:
> remove copyright > remove copyright
> remove registered trademark > remove registered trademark
> remove trademark > remove trademark
0023 FE0F 20E3;keycap > remove keycap #
002A FE0F 20E3;keycap *
> remove keycap 0
> remove keycap 1
> remove keycap 2
> remove keycap 3
> remove keycap 4
> remove keycap 5
> remove keycap 6
> remove keycap 7
> remove keycap 8
> remove keycap 9
1F51F;keycap 10
1F441;eye 1F441;eye
> remove eye in speech bubble > remove eye in speech bubble

View File

@@ -1354,18 +1354,7 @@
2755;white exclamation mark 2755;white exclamation mark
2757;exclamation mark 2757;exclamation mark
3030;wavy dash 3030;wavy dash
0023 FE0F 20E3;keycap #
002A FE0F 20E3;keycap * 002A FE0F 20E3;keycap *
0030 FE0F 20E3;keycap 0
0031 FE0F 20E3;keycap 1
0032 FE0F 20E3;keycap 2
0033 FE0F 20E3;keycap 3
0034 FE0F 20E3;keycap 4
0035 FE0F 20E3;keycap 5
0036 FE0F 20E3;keycap 6
0037 FE0F 20E3;keycap 7
0038 FE0F 20E3;keycap 8
0039 FE0F 20E3;keycap 9
1F51F;keycap 10 1F51F;keycap 10
1F4AF;hundred points 1F4AF;hundred points
1F520;input latin uppercase 1F520;input latin uppercase

View File

@@ -8,7 +8,7 @@ Custom reply account
chylex chylex
[version] [version]
1.2.4 1.3
[website] [website]
https://tweetduck.chylex.com https://tweetduck.chylex.com

View File

@@ -12,10 +12,16 @@ enabled(){
if (configuration.useAdvancedSelector){ if (configuration.useAdvancedSelector){
if (configuration.customSelector){ if (configuration.customSelector){
if (configuration.customSelector.toString().startsWith("function (column){")){ let customSelectorDef = configuration.customSelector.toString();
if (customSelectorDef.startsWith("function (column){")){
$TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector needs to be updated due to TweetDeck changes, please read the default configuration file for the updated guide"); $TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector needs to be updated due to TweetDeck changes, please read the default configuration file for the updated guide");
return; return;
} }
else if (customSelectorDef.startsWith("function (type,")){
$TD.alert("warning", "Plugin reply-account has invalid configuration: the type parameter is no longer present due to TweetDeck changes, please read the default configuration file for the updated guide");
return;
}
var section = data.element.closest("section.js-column"); var section = data.element.closest("section.js-column");
@@ -35,7 +41,7 @@ enabled(){
} }
try{ try{
query = configuration.customSelector(column.getColumnType(), columnTitle, columnAccount, column, section.hasClass("column-temp")); query = configuration.customSelector(columnTitle, columnAccount, column, section.hasClass("column-temp"));
}catch(e){ }catch(e){
$TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector threw an error: "+e.message); $TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector threw an error: "+e.message);
return; return;

View File

@@ -30,14 +30,19 @@
* https://tweetduck.chylex.com/guide/#dev-tools * https://tweetduck.chylex.com/guide/#dev-tools
* *
* *
* The 'type' parameter is TweetDeck column type. Here is the full list of column types, note that some are * In order to check the column type, use the 'column.isOfType' function. It is recommended to always put it
* unused and have misleading names (for example, Home columns are 'col_timeline' instead of 'col_home'): * last in an 'if' statement, because it is much more demanding than checking the title/account.
* col_timeline, col_interactions, col_mentions, col_followers, col_search, col_list, *
* col_customtimeline, col_messages, col_usertweets, col_favorites, col_activity, * Here is the full list of column types, note that some are unused and have misleading names.
* col_dataminr, col_home, col_me, col_inbox, col_scheduled, col_unknown * (for example, Home columns are 'col_timeline' instead of 'col_home')
*
* col_activity, col_customtimeline, col_dataminr, col_favorites, col_followers, col_home,
* col_inbox, col_interactions, col_list, col_livevideo, col_me, col_mentions,
* col_messages, col_scheduled, col_search, col_timeline, col_usertweets, col_unknown
* *
* If you want to see your current column types, run this in your browser console: * If you want to see your current column types, run this in your browser console:
* TD.controller.columnManager.getAllOrdered().map(obj => obj.getColumnType()); *
* (c=>c.columnManager.getAllOrdered().map(o=>Object.keys(c.stats.columnNamespaces).find(t=>o.isOfType(t))).map(t=>t==""+void 0?"col_unknown":t))(TD.controller)
* *
* *
* The 'title' parameter is the column title. Some are fixed (such as 'Home' or 'Notifications'), * The 'title' parameter is the column title. Some are fixed (such as 'Home' or 'Notifications'),
@@ -61,16 +66,16 @@
useAdvancedSelector: false, useAdvancedSelector: false,
customSelector: function(type, title, account, column, isTemporary){ customSelector: function(title, account, column, isTemporary){
console.info(arguments); // Prints all arguments into the console console.info(arguments); // Prints all arguments into the console
if (type === "col_search" && title === "TweetDuck"){ if (title === "TweetDuck" && column.isOfType("col_search")){
// This is a search column that looks for 'TweetDuck' in the tweets, // This is a search column that looks for 'TweetDuck' in the tweets,
// search columns are normally linked to the preferred account // search columns are normally linked to the preferred account
// so this forces the @TryTweetDuck account to be used instead // so this forces the @TryTweetDuck account to be used instead
return "@TryTweetDuck"; return "@TryTweetDuck";
} }
else if (type === "col_timeline" && account === "@chylexcz"){ else if (account === "@chylexcz" && column.isOfType("col_timeline")){
// This is a Home column of my test account @chylexcz, // This is a Home column of my test account @chylexcz,
// but I want to reply to tweets from my official account // but I want to reply to tweets from my official account
return "@chylexmc"; return "@chylexmc";

View File

@@ -1,32 +1,77 @@
Param([Parameter(Mandatory = $True, Position = 1)][string] $dir) Param(
[Parameter(Mandatory = $True, Position = 1)][string] $targetDir,
[Parameter(Mandatory = $True, Position = 2)][string] $projectDir,
[Parameter(Position = 3)][string] $version = ""
)
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
Set-Location $dir try{
Write-Host "------------------------------"
function Check-Carriage-Return{
Param([Parameter(Mandatory = $True, Position = 1)] $fname)
$file = @(Get-ChildItem -Include $fname -Recurse)[0] if ($version.Equals("")){
$version = (Get-Item (Join-Path $targetDir "TweetDuck.exe")).VersionInfo.FileVersion
if ((Get-Content -Path $file.FullName -Raw).Contains("`r")){
Throw "$fname cannot contain carriage return"
} }
Write-Host "Verified" $file.FullName.Substring($dir.Length) Write-Host "TweetDuck version" $version
}
Write-Host "------------------------------"
function Rewrite-File{
[CmdletBinding()] # Cleanup
Param([Parameter(Mandatory = $True, ValueFromPipeline = $True)][array] $lines, [Parameter(Mandatory = $True, Position = 1)] $file)
if (Test-Path (Join-Path $targetDir "locales")){
Remove-Item -Path (Join-Path $targetDir "locales\*.pak") -Exclude "en-US.pak"
}
# Copy resources
Copy-Item (Join-Path $projectDir "bld\Resources\CEFSHARP-LICENSE.txt") -Destination $targetDir -Force
Copy-Item (Join-Path $projectDir "LICENSE.md") -Destination (Join-Path $targetDir "LICENSE.txt") -Force
New-Item -ItemType directory -Path $targetDir -Name "scripts" | Out-Null
New-Item -ItemType directory -Path $targetDir -Name "plugins" | Out-Null
New-Item -ItemType directory -Path $targetDir -Name "plugins\official" | Out-Null
New-Item -ItemType directory -Path $targetDir -Name "plugins\user" | Out-Null
Copy-Item (Join-Path $projectDir "Resources\Scripts\*") -Destination (Join-Path $targetDir "scripts") -Recurse
Copy-Item (Join-Path $projectDir "Resources\Plugins\*") -Exclude ".debug", "emoji-instructions.txt" -Destination (Join-Path $targetDir "plugins\official") -Recurse
# Post processing
function Check-Carriage-Return{
Param(
[Parameter(Mandatory = $True, Position = 1)] $fname
)
$file = @(Get-ChildItem -Path $targetDir -Include $fname -Recurse)[0]
if ((Get-Content -Path $file.FullName -Raw).Contains("`r")){
Throw "$fname cannot contain carriage return"
}
Write-Host "Verified" $file.FullName.Substring($targetDir.Length)
}
function Rewrite-File{
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True)][array] $lines,
[Parameter(Mandatory = $True, Position = 1)] $file
)
$relativePath = $file.FullName.Substring($targetDir.Length)
if ($relativePath.StartsWith("scripts\")){
$lines = (,("#" + $version) + $lines)
}
$lines | Where { $_ -ne '' } | Set-Content -Path $file.FullName
Write-Host "Processed" $relativePath
}
$lines | Where { $_ -ne '' } | Set-Content -Path $file.FullName
Write-Host "Processed" $file.FullName.Substring($dir.Length)
}
try{
Check-Carriage-Return("emoji-ordering.txt") Check-Carriage-Return("emoji-ordering.txt")
ForEach($file in Get-ChildItem -Filter *.js -Exclude configuration.default.js -Recurse){ ForEach($file in Get-ChildItem -Path $targetDir -Filter "*.js" -Exclude "configuration.default.js" -Recurse){
$lines = Get-Content -Path $file.FullName $lines = Get-Content -Path $file.FullName
$lines = $lines | % { $_.TrimStart() } $lines = $lines | % { $_.TrimStart() }
$lines = $lines -Replace '^(.*?)((?<=^|[;{}()])\s?//(?:\s.*|$))?$', '$1' $lines = $lines -Replace '^(.*?)((?<=^|[;{}()])\s?//(?:\s.*|$))?$', '$1'
@@ -34,7 +79,7 @@ try{
,$lines | Rewrite-File $file ,$lines | Rewrite-File $file
} }
ForEach($file in Get-ChildItem -Filter *.css -Recurse){ ForEach($file in Get-ChildItem -Path $targetDir -Filter "*.css" -Recurse){
$lines = Get-Content -Path $file.FullName $lines = Get-Content -Path $file.FullName
$lines = $lines -Replace '\s*/\*.*?\*/', '' $lines = $lines -Replace '\s*/\*.*?\*/', ''
$lines = $lines -Replace '^\s+(.+):\s?(.+?)(?:\s?(!important))?;$', '$1:$2$3;' $lines = $lines -Replace '^\s+(.+):\s?(.+?)(?:\s?(!important))?;$', '$1:$2$3;'
@@ -42,10 +87,12 @@ try{
@(($lines | Where { $_ -ne '' }) -Join ' ') | Rewrite-File $file @(($lines | Where { $_ -ne '' }) -Join ' ') | Rewrite-File $file
} }
ForEach($file in Get-ChildItem -Filter *.html -Recurse){ ForEach($file in Get-ChildItem -Path $targetDir -Filter "*.html" -Recurse){
$lines = Get-Content -Path $file.FullName $lines = Get-Content -Path $file.FullName
,($lines | % { $_.TrimStart() }) | Rewrite-File $file ,($lines | % { $_.TrimStart() }) | Rewrite-File $file
} }
Write-Host "------------------------------"
}catch{ }catch{
Write-Host "Encountered an error while running PostBuild.ps1 on line" $_.InvocationInfo.ScriptLineNumber Write-Host "Encountered an error while running PostBuild.ps1 on line" $_.InvocationInfo.ScriptLineNumber
Write-Host $_ Write-Host $_

View File

@@ -2,36 +2,155 @@
using System; using System;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
#if DEBUG
using System.Diagnostics;
using System.Reflection;
using TweetDuck.Core;
using TweetDuck.Plugins;
#endif
namespace TweetDuck.Resources{ namespace TweetDuck.Resources{
static class ScriptLoader{ static class ScriptLoader{
private const string UrlPrefix = "td:"; public static string LoadResource(string name, bool silent = false, Control sync = null){
public static string LoadResource(string name, bool silent = false){
try{ try{
return File.ReadAllText(Path.Combine(Program.ScriptPath, name), Encoding.UTF8); string path = Program.ScriptPath;
}catch(Exception ex){
if (!silent){ #if DEBUG
FormMessage.Error("TweetDuck Has Failed :(", "Unfortunately, TweetDuck could not load the "+name+" file. The program will continue running with limited functionality.\n\n"+ex.Message, FormMessage.OK); if (Directory.Exists(HotSwapTargetDir)){
path = Path.Combine(HotSwapTargetDir, "scripts");
Debug.WriteLine("Hot swap active, redirecting "+name);
}
#endif
string contents = File.ReadAllText(Path.Combine(path, name), Encoding.UTF8);
int separator;
// first line can be either:
// #<version>\r\n
// #<version>\n
if (contents[0] != '#'){
ShowLoadError(silent, sync, $"File {name} appears to be corrupted, please try reinstalling the app.");
separator = 0;
}
else{
separator = contents.IndexOf('\n');
string fileVersion = contents.Substring(1, separator-1).TrimEnd();
if (fileVersion != Program.VersionTag){
ShowLoadError(silent, sync, $"File {name} is made for a different version of TweetDuck ({fileVersion}) and may not function correctly in this version, please try reinstalling the app.");
}
} }
return contents.Substring(separator).TrimStart();
}catch(Exception ex){
ShowLoadError(silent, sync, $"Could not load {name}. The program will continue running with limited functionality.\n\n{ex.Message}");
return null; return null;
} }
} }
public static void ExecuteFile(IFrame frame, string file){ public static bool ExecuteFile(IFrame frame, string file, Control sync = null){
ExecuteScript(frame, LoadResource(file), GetRootIdentifier(file)); string script = LoadResource(file, sync == null, sync);
ExecuteScript(frame, script, GetRootIdentifier(file));
return script != null;
} }
public static void ExecuteScript(IFrame frame, string script, string identifier){ public static void ExecuteScript(IFrame frame, string script, string identifier){
if (script != null){ if (script != null){
frame.ExecuteJavaScriptAsync(script, UrlPrefix+identifier, 1); frame.ExecuteJavaScriptAsync(script, "td:"+identifier, 1);
} }
} }
public static string GetRootIdentifier(string file){ public static string GetRootIdentifier(string file){
return "root:"+Path.GetFileNameWithoutExtension(file); return "root:"+Path.GetFileNameWithoutExtension(file);
} }
private static void ShowLoadError(bool silent, Control sync, string message){
if (silent){
return;
}
if (sync == null){
FormMessage.Error("Resource Error", message, FormMessage.OK);
}
else{
sync.InvokeSafe(() => FormMessage.Error("Resource Error", message, FormMessage.OK));
}
}
#if DEBUG
private static readonly string HotSwapProjectRoot = FixPathSlash(Path.GetFullPath(Path.Combine(Program.ProgramPath, "../../../")));
private static readonly string HotSwapTargetDir = FixPathSlash(Path.Combine(HotSwapProjectRoot, "bin", "tmp"));
private static readonly string HotSwapRebuildScript = Path.Combine(HotSwapProjectRoot, "Resources", "PostBuild.ps1");
static ScriptLoader(){
if (File.Exists(HotSwapRebuildScript)){
Debug.WriteLine("Activating resource hot swap");
ResetHotSwap();
Application.ApplicationExit += (sender, args) => ResetHotSwap();
}
}
public static void HotSwap(){
if (!File.Exists(HotSwapRebuildScript)){
Debug.WriteLine("Failed resource hot swap, missing rebuild script: "+HotSwapRebuildScript);
return;
}
ResetHotSwap();
Directory.CreateDirectory(HotSwapTargetDir);
using(Process process = Process.Start(new ProcessStartInfo{
FileName = "powershell",
Arguments = $"-ExecutionPolicy Unrestricted -File \"{HotSwapRebuildScript}\" \"{HotSwapTargetDir}\\\" \"{HotSwapProjectRoot}\\\" \"{Program.VersionTag}\"",
WindowStyle = ProcessWindowStyle.Hidden
})){
// ReSharper disable once PossibleNullReferenceException
if (!process.WaitForExit(8000)){
Debug.WriteLine("Failed resource hot swap, script did not finish in time");
return;
}
else if (process.ExitCode != 0){
Debug.WriteLine("Failed resource hot swap, script exited with code "+process.ExitCode);
return;
}
}
// Force update plugin manager setup scripts
string newPluginRoot = Path.Combine(HotSwapTargetDir, "plugins");
const BindingFlags flagsInstance = BindingFlags.Instance | BindingFlags.NonPublic;
const BindingFlags flagsStatic = BindingFlags.Static | BindingFlags.NonPublic;
Type typePluginManager = typeof(PluginManager);
Type typeFormBrowser = typeof(FormBrowser);
// ReSharper disable PossibleNullReferenceException
object pluginSetupScripts = typePluginManager.GetMethod("LoadSetupScripts", flagsStatic).Invoke(null, new object[0]);
typePluginManager.GetField("PluginSetupScripts", flagsStatic).SetValue(null, pluginSetupScripts);
object instPluginManager = typeFormBrowser.GetField("plugins", flagsInstance).GetValue(FormManager.TryFind<FormBrowser>());
typePluginManager.GetField("rootPath", flagsInstance).SetValue(instPluginManager, newPluginRoot);
Debug.WriteLine("Reloading hot swapped plugins...");
((PluginManager)instPluginManager).Reload();
// ReSharper restore PossibleNullReferenceException
}
private static void ResetHotSwap(){
try{
Directory.Delete(HotSwapTargetDir, true);
}catch(DirectoryNotFoundException){}
}
private static string FixPathSlash(string path){
return path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)+'\\';
}
#endif
} }
} }

View File

@@ -98,6 +98,13 @@
return value; return value;
}; };
//
// Function: Retrieves column name
const getColumnName = function(column){
let cached = column._tduck_type || (column._tduck_type = Object.keys(columnTypes).find(type => column.isOfType(type)));
return columnTypes[cached] || "";
};
// //
// Function: Event callback for a new tweet. // Function: Event callback for a new tweet.
// //
@@ -220,7 +227,7 @@
let tweetUrl = source ? source.getChirpURL() : ""; let tweetUrl = source ? source.getChirpURL() : "";
let quoteUrl = source && source.quotedTweet ? source.quotedTweet.getChirpURL() : ""; let quoteUrl = source && source.quotedTweet ? source.quotedTweet.getChirpURL() : "";
$TD.onTweetPopup(column.model.privateState.apiid, chirpId, columnTypes[column.getColumnType()] || "", html.html(), duration, tweetUrl, quoteUrl); $TD.onTweetPopup(column.model.privateState.apiid, chirpId, getColumnName(column), html.html(), duration, tweetUrl, quoteUrl);
} }
if (column.model.getHasSound()){ if (column.model.getHasSound()){
@@ -280,10 +287,12 @@
// Block: Hook into settings object to detect when the settings change, and update html attributes and notification layout. // Block: Hook into settings object to detect when the settings change, and update html attributes and notification layout.
// //
(function(){ (function(){
const refreshSettings = function(){ const refreshSettings = function(){
let fontSizeName = TD.settings.getFontSize(); let fontSizeName = TD.settings.getFontSize();
let themeName = TD.settings.getTheme(); let themeName = TD.settings.getTheme();
let columnBackground = getClassStyleProperty("column", "background-color");
let tags = [ let tags = [
"<html "+Array.prototype.map.call(document.documentElement.attributes, ele => `${ele.name}="${ele.value}"`).join(" ")+"><head>" "<html "+Array.prototype.map.call(document.documentElement.attributes, ele => `${ele.name}="${ele.value}"`).join(" ")+"><head>"
]; ];
@@ -292,23 +301,7 @@
tags.push($(this)[0].outerHTML); tags.push($(this)[0].outerHTML);
}); });
tags.push("<style type='text/css'>"); tags.push("<style type='text/css'>body { background: "+columnBackground+" !important }</style>");
tags.push("body { background: "+getClassStyleProperty("column", "background-color")+" !important }"); // set background color
tags.push("body::before { content: none !important }"); // remove background gradient
tags.push(".column { background: transparent !important }"); // remove background color from columns
tags.push("a[data-full-url] { word-break: break-all !important }"); // break long urls
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.has-source-avatar { margin-bottom: 4px !important }"); // tweak distance between avatar and text
tags.push(".activity-header .tweet-timestamp { line-height: unset !important }"); // fix timestamp position
tags.push(".activity-header .icon-user-filled { vertical-align: sub !important; }"); // fix follow icon position
tags.push("#tduck-show-thread { display: inline-block !important; cursor: pointer }");
if (fontSizeName === "smallest"){
tags.push(".badge-verified:before { width: 13px !important; height: 13px !important; background-position: -223px -98px !important }"); // fix cut off badge icon
}
tags.push("</style>");
doc.setAttribute("data-td-font", fontSizeName); doc.setAttribute("data-td-font", fontSizeName);
doc.setAttribute("data-td-theme", themeName); doc.setAttribute("data-td-theme", themeName);
@@ -542,7 +535,8 @@
let me = $(this)[0]; let me = $(this)[0];
if (me.classList.contains("js-media-image-link") && highlightedTweetObj){ if (me.classList.contains("js-media-image-link") && highlightedTweetObj){
let media = (highlightedTweetObj.quotedTweet || highlightedTweetObj).getMedia().find(media => media.mediaId === me.getAttribute("data-media-entity-id")); let tweet = highlightedTweetObj.hasMedia() ? highlightedTweetObj : highlightedTweetObj.quotedTweet;
let media = tweet.getMedia().find(media => media.mediaId === me.getAttribute("data-media-entity-id"));
if ((media.isVideo && media.service === "twitter") || media.isAnimatedGif){ if ((media.isVideo && media.service === "twitter") || media.isAnimatedGif){
$TD.setLastRightClickInfo("video", media.chooseVideoVariant().url); $TD.setLastRightClickInfo("video", media.chooseVideoVariant().url);
@@ -594,22 +588,15 @@
// Block: Update highlighted column and tweet for context menu and other functionality. // Block: Update highlighted column and tweet for context menu and other functionality.
// //
(function(){ (function(){
var lastTweet = "";
const updateHighlightedColumn = function(ele){ const updateHighlightedColumn = function(ele){
highlightedColumnEle = ele; highlightedColumnEle = ele;
highlightedColumnObj = ele ? TD.controller.columnManager.get(ele.attr("data-column")) : null; highlightedColumnObj = ele ? TD.controller.columnManager.get(ele.attr("data-column")) : null;
return !!highlightedColumnObj; return !!highlightedColumnObj;
}; };
const updateHighlightedTweet = function(ele, obj, tweetUrl, quoteUrl, authors, imageList){ const updateHighlightedTweet = function(ele, obj){
highlightedTweetEle = ele; highlightedTweetEle = ele;
highlightedTweetObj = obj; highlightedTweetObj = obj;
if (lastTweet !== tweetUrl){
$TD.setLastHighlightedTweet(tweetUrl, quoteUrl, authors, imageList);
lastTweet = tweetUrl;
}
}; };
const processMedia = function(chirp){ const processMedia = function(chirp){
@@ -625,6 +612,19 @@
mouseleave: function(){ mouseleave: function(){
updateHighlightedColumn(null); updateHighlightedColumn(null);
},
contextmenu: function(){
let tweet = highlightedTweetObj;
if (tweet && tweet.chirpType === TD.services.ChirpBase.TWEET){
let tweetUrl = tweet.getChirpURL();
let quoteUrl = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
let chirpAuthors = tweet.quotedTweet ? [ tweet.getMainUser().screenName, tweet.quotedTweet.getMainUser().screenName ].join(";") : tweet.getMainUser().screenName;
let chirpImages = tweet.hasImage() ? processMedia(tweet) : tweet.quotedTweet && tweet.quotedTweet.hasImage() ? processMedia(tweet.quotedTweet) : "";
$TD.setRightClickedChirp(tweetUrl || "", quoteUrl || "", chirpAuthors, chirpImages);
}
} }
}); });
@@ -636,21 +636,11 @@
let tweet = highlightedColumnObj.findChirp(me.attr("data-tweet-id")) || highlightedColumnObj.findChirp(me.attr("data-key")); let tweet = highlightedColumnObj.findChirp(me.attr("data-tweet-id")) || highlightedColumnObj.findChirp(me.attr("data-key"));
return if !tweet; return if !tweet;
if (tweet.chirpType === TD.services.ChirpBase.TWEET){ updateHighlightedTweet(me, tweet);
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, "", "", "", "");
}
}, },
mouseleave: function(){ mouseleave: function(){
updateHighlightedTweet(null, null, "", "", "", ""); updateHighlightedTweet(null, null);
} }
}); });
})(); })();
@@ -659,85 +649,52 @@
// Block: Screenshot tweet to clipboard. // Block: Screenshot tweet to clipboard.
// //
(function(){ (function(){
var selectedTweet;
const setImportantProperty = function(obj, property, value){
if (obj.length === 1){
obj[0].style.setProperty(property, value, "important");
}
};
app.delegate("article.js-stream-item", "contextmenu", function(){
selectedTweet = $(this);
});
window.TDGF_triggerScreenshot = function(){ window.TDGF_triggerScreenshot = function(){
if (selectedTweet){ return if !highlightedTweetObj || !highlightedColumnObj;
let tweetWidth = Math.floor(selectedTweet.width());
let parent = selectedTweet.parent(); let chirp = highlightedColumnObj.findChirp(highlightedTweetEle.attr("data-key")) || highlightedTweetObj;
let columnWidth = highlightedColumnEle.width();
let html = $(chirp.render({
withFooter: false,
withTweetActions: false,
isInConvo: false,
isFavorite: false,
isRetweeted: false, // keeps retweet mark above tweet
isPossiblySensitive: false,
mediaPreviewSize: highlightedColumnObj.getMediaPreviewSize()
}));
html.find("footer").last().remove(); // apparently withTweetActions breaks for certain tweets, nice
html.find(".td-screenshot-remove").remove();
html.find("p.link-complex-target").filter(function(){
return $(this).text() === "Show this thread";
}).first().remove();
html.addClass($(document.documentElement).attr("class"));
html.addClass($(document.body).attr("class"));
html.css("background-color", getClassStyleProperty("column", "background-color"));
html.css("border", "none");
for(let selector of [ ".js-quote-detail", ".js-media-preview-container", ".js-media" ]){
let ele = html.find(selector);
let isDetail = parent.hasClass("js-tweet-detail"); if (ele.length){
let isReply = !isDetail && (parent.hasClass("js-replies-to") || parent.hasClass("js-replies-before")); ele[0].style.setProperty("margin-bottom", "2px", "important");
break;
selectedTweet = selectedTweet.clone();
let container = selectedTweet.children().first();
container.addClass($(document.documentElement).attr("class"));
container.addClass($(document.body).attr("class"));
container.css("padding-bottom", "0");
setImportantProperty(selectedTweet.find(".js-tweet-text"), "margin-bottom", "8px");
setImportantProperty(selectedTweet.find(".js-quote-detail"), "margin-bottom", "10px");
setImportantProperty(selectedTweet.find(".js-poll-link").next(), "margin-bottom", "8px");
if (isDetail){
if (selectedTweet.find("[class*='media-grid-']").length > 0){
setImportantProperty(selectedTweet.find(".js-tweet-media"), "margin-bottom", "10px");
}
else{
setImportantProperty(selectedTweet.find(".js-tweet-media"), "margin-bottom", "6px");
}
setImportantProperty(selectedTweet.find(".js-media-preview-container"), "margin-bottom", "4px");
selectedTweet.find(".js-translate-call-to-action").first().remove();
selectedTweet.find(".js-tweet").first().nextAll().remove();
selectedTweet.find("footer").last().prevUntil(":not(.txt-mute)").addBack().remove(); // footer, date, location
} }
else{
setImportantProperty(selectedTweet.find(".js-media-preview-container").filter(function(){
return $(this).closest(".js-quote-detail").length === 0;
}), "margin-bottom", "10px");
selectedTweet.find("footer").last().remove();
}
if (isReply){
selectedTweet.find(".is-conversation").removeClass("is-conversation");
selectedTweet.find(".thread").remove();
}
selectedTweet.find("p.link-complex-target").filter(function(){
return $(this).text() === "Show this thread";
}).first().remove();
selectedTweet.find(".js-poll-link").remove();
selectedTweet.find(".td-screenshot-remove").remove();
let testTweet = selectedTweet.clone().css({
position: "absolute",
left: "-999px",
width: tweetWidth+"px"
}).appendTo(document.body);
let testTweetAvatar = testTweet.find(".tweet-avatar").first();
let avatarBottom = testTweetAvatar.offset().top+testTweetAvatar.height();
let realHeight = Math.floor(Math.max(testTweet.height(), avatarBottom+10));
testTweet.remove();
selectedTweet.find(".js-stream-item-content").first().css("height", "100vh");
$TD.screenshotTweet(selectedTweet.html(), tweetWidth, realHeight);
} }
let type = chirp.getChirpType();
if ((type.startsWith("favorite") || type.startsWith("retweet")) && chirp.isAboutYou()){
html.addClass("td-notification-padded");
}
$TD.screenshotTweet(html[0].outerHTML, columnWidth);
}; };
})(); })();
@@ -928,26 +885,15 @@
})(); })();
// //
// Block: Swap shift key functionality for selecting accounts, and refocus the textbox afterwards. // Block: Refocus the textbox after switching accounts.
// //
onAppReady.push(function(){ onAppReady.push(function(){
const onAccountClick = function(e){ const refocusInput = function(){
if ($TDX.switchAccountSelectors){
e.shiftKey = !e.shiftKey;
}
$(".js-compose-text", ".js-docked-compose").focus(); $(".js-compose-text", ".js-docked-compose").focus();
}; };
$(".js-account-list", ".js-docked-compose").delegate(".js-account-item", "click", onAccountClick); $(".js-account-list", ".js-docked-compose").delegate(".js-account-item", "click", function(e){
setTimeout(refocusInput, 0);
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")){
this.$node.attr("td-account-selector-hook", "1");
this.$node.delegate(".js-account-item", "click", onAccountClick);
}
}); });
}); });
@@ -1205,11 +1151,35 @@
} }
} }
$(".js-app-search-input").val(""); if (!("tweetduck" in data)){
$(".js-perform-search").blur(); $(".js-app-search-input").val("");
$(".js-perform-search").blur();
}
} }
}); });
//
// Block: Setup global function to add a search column with the specified query.
//
onAppReady.push(function(){
let context = $._data(document, "events")["uiSearchInputSubmit"][0].handler.context;
window.TDGF_performSearch = function(query){
context.performSearch({ query, tweetduck: true });
};
});
//
// Block: Reorder search results to move accounts above hashtags.
//
onAppReady.push(function(){
let container = $(".js-search-in-popover");
let hashtags = $(".js-typeahead-topic-list", container);
$(".js-typeahead-user-list", container).insertBefore(hashtags);
hashtags.addClass("list-divider");
});
// //
// Block: Make submitting search queries while holding Ctrl or middle-clicking the search icon open the search externally. // Block: Make submitting search queries while holding Ctrl or middle-clicking the search icon open the search externally.
// //
@@ -1347,6 +1317,11 @@
}; };
} }
//
// Block: Fix columns missing any identifiable attributes to allow individual styles.
//
TD.mustaches["column.mustache"] = TD.mustaches["column.mustache"].replace("{{columnclass}}\"", "{{columnclass}}\" data-td-icon=\"{{columniconclass}}\"");
// //
// Block: Remove column mouse wheel handler, which allows smooth scrolling inside columns, and horizontally scrolling column container when holding Shift. // Block: Remove column mouse wheel handler, which allows smooth scrolling inside columns, and horizontally scrolling column container when holding Shift.
// //
@@ -1406,6 +1381,8 @@
window.gc && window.gc(); window.gc && window.gc();
window.location.reload(); window.location.reload();
}); });
window.TDGF_reload = function(){}; // redefine to prevent reloading multiple times
}; };
if (window.TD_SESSION && window.TD_SESSION.gc){ if (window.TD_SESSION && window.TD_SESSION.gc){

View File

@@ -98,7 +98,7 @@
</footer> </footer>
</div> </div>
</div> </div>
</div>`).appendTo(".js-app"); </div>`).appendTo(".js-app"); /* TODO btn-positive is removed, check all files again */
let tdUser = null; let tdUser = null;
let loadTweetDuckUser = (onSuccess, onError) => { let loadTweetDuckUser = (onSuccess, onError) => {

View File

@@ -14,22 +14,29 @@
}; };
// //
// Block: Hook into links to bypass default open function and t.co. // Block: Hook into links to bypass default open function and t.co, and handle skipping notification when opening links.
// //
addEventListener(links, "click", function(e){ (function(){
let ele = e.currentTarget; const onLinkClick = function(e){
if (e.button === 0 || e.button === 1){
$TD.openBrowser(ele.hasAttribute("data-full-url") ? ele.getAttribute("data-full-url") : ele.getAttribute("href")); let ele = e.currentTarget;
e.preventDefault();
$TD.openBrowser(ele.hasAttribute("data-full-url") ? ele.getAttribute("data-full-url") : ele.getAttribute("href"));
if ($TDX.skipOnLinkClick){ e.preventDefault();
let parentClasses = ele.parentNode.classList;
if ($TDX.skipOnLinkClick){
if (parentClasses.contains("js-tweet-text") || parentClasses.contains("js-quoted-tweet-text") || parentClasses.contains("js-timestamp")){ let parentClasses = ele.parentNode.classList;
$TD.loadNextNotification();
if (parentClasses.contains("js-tweet-text") || parentClasses.contains("js-quoted-tweet-text") || parentClasses.contains("js-timestamp")){
$TD.loadNextNotification();
}
}
} }
} };
});
addEventListener(links, "click", onLinkClick);
addEventListener(links, "auxclick", onLinkClick);
})();
// //
// Block: Allow bypassing of t.co in context menus. // Block: Allow bypassing of t.co in context menus.

View File

@@ -0,0 +1,15 @@
(function($TD){
let ele = document.getElementsByTagName("article")[0];
ele.style.width = "{width}px";
ele.style.position = "absolute";
let contentHeight = ele.offsetHeight;
ele.style.position = "static";
let avatar = ele.querySelector(".tweet-avatar");
let avatarBottom = avatar ? avatar.getBoundingClientRect().bottom : 0;
$TD.setHeight(Math.floor(Math.max(contentHeight, avatarBottom+9))).then(() => {
setTimeout($TD.triggerScreenshot, document.getElementsByTagName("iframe").length ? 267 : 67);
});
})($TD_NotificationScreenshot);

View File

@@ -50,7 +50,7 @@ button, .btn, .mdl, .mdl-content, .popover, .lst-modal, .tooltip-inner {
border-radius: 0 !important; border-radius: 0 !important;
} }
.dropdown-menu, .list-item-last, .quoted-tweet, .input-group-button, input, textarea, select, .prf-header, .accs li, .accs img { .dropdown-menu, .list-item-last, .quoted-tweet, input, textarea, select, .prf-header {
border-radius: 0 !important; border-radius: 0 !important;
} }
@@ -67,9 +67,13 @@ button {
border-color: #2B7BB9 !important; border-color: #2B7BB9 !important;
} }
#tduck .js-compose-scroller button > i { #tduck .js-compose-scroller button.full-width {
padding: 7px 10px !important;
}
#tduck .js-compose-scroller button.full-width > i {
font-size: 20px !important; font-size: 20px !important;
vertical-align: -4px !important; vertical-align: -5px !important;
} }
.js-show-drawer { .js-show-drawer {
@@ -84,6 +88,14 @@ button {
content: "New "; content: "New ";
} }
#tduck .app-header .search-input-control input {
font-size: 13px !important;
}
#tduck .app-header .search-input-perform-search {
right: 8px !important;
}
.js-account-safeguard-checkbox, .js-account-safeguard-checkbox label { .js-account-safeguard-checkbox, .js-account-safeguard-checkbox label {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
@@ -183,6 +195,11 @@ a[data-full-url] {
color: #9f51cf !important; color: #9f51cf !important;
} }
.js-typeahead-user-list.is-hidden + .js-typeahead-topic-list {
/* hide custom search result divider when not needed */
border-top: none !important;
}
/***************************************/ /***************************************/
/* Replace 'Stay open' with a pin icon */ /* Replace 'Stay open' with a pin icon */
/***************************************/ /***************************************/
@@ -244,11 +261,6 @@ html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
background-position: -223px -97px !important; background-position: -223px -97px !important;
} }
.accs-header {
/* fix retweet account selector heading */
padding-left: 0 !important;
}
.keyboard-shortcut-list { .keyboard-shortcut-list {
/* fix keyboard navigation alignment */ /* fix keyboard navigation alignment */
vertical-align: top !important; vertical-align: top !important;
@@ -259,12 +271,6 @@ html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
cursor: pointer; cursor: pointer;
} }
.inline-reply .btn-square, .rpl-actions .btn-square {
/* remove effects from buttons under reply input... this keeps happening for some stupid reason */
background: transparent !important;
box-shadow: none !important;
}
.js-add-to-customtimeline-input { .js-add-to-customtimeline-input {
/* the custom timeline input shadow is behaving super weird when focused */ /* the custom timeline input shadow is behaving super weird when focused */
box-shadow: none !important; box-shadow: none !important;
@@ -344,18 +350,18 @@ html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
/* Fix cut off usernames in Messages column */ /* Fix cut off usernames in Messages column */
/********************************************/ /********************************************/
.column-type-message.is-shifted-1 .column-title-container { [data-td-icon="icon-message"].is-shifted-1 .column-title-container {
height: 100%; height: 100%;
border-bottom-color: transparent; border-bottom-color: transparent;
} }
#tduck .column-type-message.is-shifted-1 .column-title-items { #tduck [data-td-icon="icon-message"].is-shifted-1 .column-title-items {
height: 100%; height: 100%;
margin-left: 4px !important; margin-left: 4px !important;
padding-top: 1px; padding-top: 1px;
} }
.column-type-message.is-shifted-1 .username { [data-td-icon="icon-message"].is-shifted-1 .username {
vertical-align: bottom; vertical-align: bottom;
} }

View File

@@ -2,10 +2,22 @@
/* General */ /* General */
/***********/ /***********/
body { body.td-notification {
overflow-y: auto !important; overflow-y: auto !important;
} }
body::before {
content: none !important;
}
.column {
background: transparent !important;
}
/***********************/
/* Redesign scrollbars */
/***********************/
.scroll-styled-v::-webkit-scrollbar { .scroll-styled-v::-webkit-scrollbar {
width: 7px !important; width: 7px !important;
} }
@@ -18,9 +30,41 @@ body {
border-left: 0 !important; border-left: 0 !important;
} }
/*************/ /********************/
/* Usernames */ /* Square-ify stuff */
/*************/ /********************/
.media-item, .media-preview {
border-radius: 1px !important;
}
.quoted-tweet {
border-radius: 0 !important;
}
/***********************/
/* Tweaks for features */
/***********************/
a[data-full-url] {
word-break: break-all !important;
}
#tduck-show-thread {
display: inline-block !important;
cursor: pointer;
}
/*******************************************/
/* Fix general visual issues or annoyances */
/*******************************************/
html[data-td-font='smallest'] .badge-verified:before {
/* fix cut off badge icon */
width: 13px !important;
height: 13px !important;
background-position: -223px -98px !important;
}
.account-inline .username { .account-inline .username {
vertical-align: 10% !important; vertical-align: 10% !important;
@@ -30,10 +74,22 @@ body {
vertical-align: -10% !important; vertical-align: -10% !important;
} }
/************************************/ /****************************************/
/* Favorite & retweet notifications */ /* Tweak notification layout and design */
/************************************/ /****************************************/
.activity-header.has-source-avatar {
margin-bottom: 4px !important;
}
.activity-header .tweet-timestamp {
line-height: unset !important;
}
.activity-header .icon-user-filled {
vertical-align: sub !important;
}
.td-notification-padded .item-img { .td-notification-padded .item-img {
position: absolute; position: absolute;
left: 21px; left: 21px;
@@ -55,13 +111,13 @@ body {
/* Media */ /* Media */
/*********/ /*********/
.media-size-medium { .td-notification .media-size-medium {
max-height: 240px; max-height: 240px;
height: calc(100vh - 20px) !important; height: calc(100vh - 20px) !important;
border-radius: 1px !important; border-radius: 1px !important;
} }
.js-quote-detail .media-size-medium { .td-notification .js-quote-detail .media-size-medium {
height: calc(100vh - 28px) !important; height: calc(100vh - 28px) !important;
} }

View File

@@ -4,7 +4,7 @@
// //
var injectCSS = function(){ var injectCSS = function(){
if (!document.head){ if (!document.head){
setTimeout(injectCSS, 25); setTimeout(injectCSS, 5);
return; return;
} }
@@ -15,7 +15,7 @@
style.sheet.insertRule(rule, 0); style.sheet.insertRule(rule, 0);
}; };
addRule("body { overflow: hidden !important; background-color: #1c6399 !important; }"); // remove scrollbar and change background addRule("body { overflow: hidden !important; }"); // remove scrollbar
addRule(".page-canvas { box-shadow: 0 0 150px rgba(255, 255, 255, 0.3) !important; }"); // change page box shadow addRule(".page-canvas { box-shadow: 0 0 150px rgba(255, 255, 255, 0.3) !important; }"); // change page box shadow
addRule(".topbar { display: none !important; }"); // hide top bar addRule(".topbar { display: none !important; }"); // hide top bar

View File

@@ -1,33 +1,9 @@
(function($, $TDU){ (function($, $TDU){
//
// Variable: Current timeout ID for update checking.
//
var updateCheckTimeoutID;
//
// Constant: Update exe file name.
//
const updateFileName = "TweetDuck.Update.exe";
//
// Constant: Url that returns JSON data about latest version.
//
const updateCheckUrlLatest = "https://api.github.com/repos/chylex/TweetDuck/releases/latest";
//
// Constant: Url that returns JSON data about all versions, including prereleases.
//
const updateCheckUrlAll = "https://api.github.com/repos/chylex/TweetDuck/releases";
//
// Constant: Fallback url in case the update installer file is missing.
//
const updateDownloadFallback = "https://tweetduck.chylex.com";
// //
// Function: Creates the update notification element. Removes the old one if already exists. // Function: Creates the update notification element. Removes the old one if already exists.
// //
var displayNotification = function(version, download, changelog){ var displayNotification = function(version, changelog){
// styles // styles
var css = $("#tweetduck-update-css"); var css = $("#tweetduck-update-css");
@@ -167,7 +143,7 @@
<div id='tweetduck-changelog'> <div id='tweetduck-changelog'>
<div id='tweetduck-changelog-box'> <div id='tweetduck-changelog-box'>
<h2>TweetDuck Update ${version}</h2> <h2>TweetDuck Update ${version}</h2>
${changelog} ${markdown(atob(changelog))}
</div> </div>
</div> </div>
`).appendTo(document.body).css("display", "none"); `).appendTo(document.body).css("display", "none");
@@ -219,17 +195,11 @@
buttonDiv.children(".tdu-btn-download").click(function(){ buttonDiv.children(".tdu-btn-download").click(function(){
hide(); hide();
$TDU.onUpdateAccepted();
if (download){
$TDU.onUpdateAccepted();
}
else{
$TDU.openBrowser(updateDownloadFallback);
}
}); });
buttonDiv.children(".tdu-btn-later").click(function(){ buttonDiv.children(".tdu-btn-later").click(function(){
clearTimeout(updateCheckTimeoutID); $TDU.onUpdateDelayed();
slide(); slide();
}); });
@@ -245,15 +215,6 @@
return ele; return ele;
}; };
//
// Function: Returns milliseconds until the start of the next hour, with an extra offset in seconds that can skip an hour if the clock would roll over too soon.
//
var getTimeUntilNextHour = function(extra){
var now = new Date();
var offset = new Date(+now+extra*1000);
return new Date(offset.getFullYear(), offset.getMonth(), offset.getDate(), offset.getHours()+1, 0, 0)-now;
};
// //
// Function: Ghetto-converts markdown to HTML. // Function: Ghetto-converts markdown to HTML.
// //
@@ -274,34 +235,14 @@
}; };
// //
// Function: Runs an update check and updates all DOM elements appropriately. // Block: Check updates on startup.
// //
var runUpdateCheck = function(eventID, versionTag, dismissedVersionTag, allowPre){ $(document).one("TD.ready", function(){
clearTimeout(updateCheckTimeoutID); $TDU.triggerUpdateCheck();
updateCheckTimeoutID = setTimeout($TDU.triggerUpdateCheck, getTimeUntilNextHour(60*30)); // 30 minute offset });
$.getJSON(allowPre ? updateCheckUrlAll : updateCheckUrlLatest, function(response){
var release = allowPre ? response[0] : response;
var tagName = release.tag_name;
var hasUpdate = tagName !== versionTag && tagName !== dismissedVersionTag && release.assets.length > 0;
if (hasUpdate){
var obj = release.assets.find(asset => asset.name === updateFileName) || { browser_download_url: "" };
displayNotification(tagName, obj.browser_download_url, markdown(release.body));
if (eventID){ // ignore undefined and 0
$TDU.onUpdateCheckFinished(eventID, tagName, obj.browser_download_url);
}
}
else if (eventID){ // ignore undefined and 0
$TDU.onUpdateCheckFinished(eventID, null, null);
}
});
};
// //
// Block: Setup global functions. // Block: Setup global functions.
// //
window.TDUF_runUpdateCheck = runUpdateCheck; window.TDUF_displayNotification = displayNotification;
})($, $TDU); })($, $TDU);

View File

@@ -0,0 +1,26 @@
using System.Text.RegularExpressions;
HashSet<string> ReadSelectors(string file){
return new HashSet<string>(
File.ReadAllLines(file)
.Where(line => line.Contains('{'))
.Select(line => line.Substring(0, line.IndexOf('{')).Trim())
.SelectMany(lines => lines.Split(new char[]{ ',', ' ' }, StringSplitOptions.RemoveEmptyEntries))
);
}
HashSet<string> ExtractClasses(HashSet<string> selectors){
return new HashSet<string>(
selectors.SelectMany(selector => Regex.Matches(selector, @"\.[a-zA-Z0-9_-]+").Cast<Match>().Select(match => match.Value))
);
}
void PrintAll(IEnumerable<string> data){
foreach(string line in data){
Print(line);
}
}
void PrintMissing(HashSet<string> all, HashSet<string> subset){
PrintAll(subset.Where(ele => !all.Contains(ele)));
}

View File

@@ -62,12 +62,14 @@
<DefineConstants> <DefineConstants>
</DefineConstants> </DefineConstants>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<LangVersion>7</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Drawing" /> <Reference Include="System.Drawing" />
<Reference Include="System.Management" /> <Reference Include="System.Management" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Windows.Forms" /> <Reference Include="System.Windows.Forms" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -107,7 +109,8 @@
<Compile Include="Core\Handling\RequestHandlerBase.cs" /> <Compile Include="Core\Handling\RequestHandlerBase.cs" />
<Compile Include="Core\Handling\RequestHandlerBrowser.cs" /> <Compile Include="Core\Handling\RequestHandlerBrowser.cs" />
<Compile Include="Core\Handling\ResourceHandlerNotification.cs" /> <Compile Include="Core\Handling\ResourceHandlerNotification.cs" />
<Compile Include="Core\ITweetDeckBrowser.cs" /> <Compile Include="Core\Other\Interfaces\ITweetDeckBrowser.cs" />
<Compile Include="Core\Management\ContextInfo.cs" />
<Compile Include="Core\Notification\Example\FormNotificationExample.cs"> <Compile Include="Core\Notification\Example\FormNotificationExample.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@@ -169,6 +172,12 @@
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsAnalytics.Designer.cs"> <Compile Include="Core\Other\Settings\Dialogs\DialogSettingsAnalytics.Designer.cs">
<DependentUpon>DialogSettingsAnalytics.cs</DependentUpon> <DependentUpon>DialogSettingsAnalytics.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsSearchEngine.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsSearchEngine.Designer.cs">
<DependentUpon>DialogSettingsSearchEngine.cs</DependentUpon>
</Compile>
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsCSS.cs"> <Compile Include="Core\Other\Settings\Dialogs\DialogSettingsCSS.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@@ -244,13 +253,14 @@
<Compile Include="Core\Other\Settings\TabSettingsNotifications.Designer.cs"> <Compile Include="Core\Other\Settings\TabSettingsNotifications.Designer.cs">
<DependentUpon>TabSettingsNotifications.cs</DependentUpon> <DependentUpon>TabSettingsNotifications.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Core\Bridge\CallbackBridge.cs" /> <Compile Include="Core\Notification\Screenshot\ScreenshotBridge.cs" />
<Compile Include="Data\CommandLineArgs.cs" /> <Compile Include="Data\CommandLineArgs.cs" />
<Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs"> <Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" /> <Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
<Compile Include="Data\ResourceLink.cs" /> <Compile Include="Data\ResourceLink.cs" />
<Compile Include="Data\Result.cs" />
<Compile Include="Data\Serialization\FileSerializer.cs" /> <Compile Include="Data\Serialization\FileSerializer.cs" />
<Compile Include="Data\InjectedHTML.cs" /> <Compile Include="Data\InjectedHTML.cs" />
<Compile Include="Data\Serialization\ITypeConverter.cs" /> <Compile Include="Data\Serialization\ITypeConverter.cs" />
@@ -282,6 +292,7 @@
<Compile Include="Plugins\Enums\PluginEnvironment.cs" /> <Compile Include="Plugins\Enums\PluginEnvironment.cs" />
<Compile Include="Plugins\Enums\PluginGroup.cs" /> <Compile Include="Plugins\Enums\PluginGroup.cs" />
<Compile Include="Plugins\Events\PluginErrorEventArgs.cs" /> <Compile Include="Plugins\Events\PluginErrorEventArgs.cs" />
<Compile Include="Plugins\PluginLoader.cs" />
<Compile Include="Plugins\PluginManager.cs" /> <Compile Include="Plugins\PluginManager.cs" />
<Compile Include="Plugins\PluginScriptGenerator.cs" /> <Compile Include="Plugins\PluginScriptGenerator.cs" />
<Compile Include="Properties\Resources.Designer.cs"> <Compile Include="Properties\Resources.Designer.cs">
@@ -290,6 +301,7 @@
<DependentUpon>Resources.resx</DependentUpon> <DependentUpon>Resources.resx</DependentUpon>
</Compile> </Compile>
<Compile Include="Reporter.cs" /> <Compile Include="Reporter.cs" />
<Compile Include="Updates\Events\UpdateCheckEventArgs.cs" />
<Compile Include="Updates\FormUpdateDownload.cs"> <Compile Include="Updates\FormUpdateDownload.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@@ -305,13 +317,14 @@
<Compile Include="Core\Management\BrowserCache.cs" /> <Compile Include="Core\Management\BrowserCache.cs" />
<Compile Include="Core\Utils\BrowserUtils.cs" /> <Compile Include="Core\Utils\BrowserUtils.cs" />
<Compile Include="Core\Utils\NativeMethods.cs" /> <Compile Include="Core\Utils\NativeMethods.cs" />
<Compile Include="Updates\UpdateCheckClient.cs" />
<Compile Include="Updates\UpdateDownloadStatus.cs" /> <Compile Include="Updates\UpdateDownloadStatus.cs" />
<Compile Include="Updates\UpdateHandler.cs" /> <Compile Include="Updates\UpdateHandler.cs" />
<Compile Include="Updates\UpdateInfo.cs" /> <Compile Include="Updates\UpdateInfo.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Resources\ScriptLoader.cs" /> <Compile Include="Resources\ScriptLoader.cs" />
<Compile Include="Updates\UpdateEventArgs.cs" /> <Compile Include="Updates\Events\UpdateEventArgs.cs" />
<Compile Include="Updates\UpdaterSettings.cs" /> <Compile Include="Updates\UpdaterSettings.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -394,37 +407,20 @@
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>
<PostBuildEvent>xcopy "$(ProjectDir)LICENSE.md" "$(TargetDir)" /Y <PostBuildEvent>rmdir "$(ProjectDir)bin\Debug"
del "$(TargetDir)LICENSE.txt"
ren "$(TargetDir)LICENSE.md" "LICENSE.txt"
xcopy "$(ProjectDir)bld\Resources\CEFSHARP-LICENSE.txt" "$(TargetDir)" /Y
rmdir "$(TargetDir)scripts" /S /Q
mkdir "$(TargetDir)scripts"
xcopy "$(ProjectDir)Resources\Scripts\*" "$(TargetDir)scripts\" /E /Y
rmdir "$(TargetDir)plugins" /S /Q
mkdir "$(TargetDir)plugins"
mkdir "$(TargetDir)plugins\official"
mkdir "$(TargetDir)plugins\user"
xcopy "$(ProjectDir)Resources\Plugins\*" "$(TargetDir)plugins\official\" /E /Y
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
del "$(TargetDir)plugins\official\emoji-keyboard\emoji-instructions.txt"
rmdir "$(ProjectDir)bin\Debug"
rmdir "$(ProjectDir)bin\Release" rmdir "$(ProjectDir)bin\Release"
attrib +R "$(TargetDir)locales\en-US.pak" rmdir "$(TargetDir)scripts" /S /Q
del /Q /A:-R "$(TargetDir)locales\*.pak" rmdir "$(TargetDir)plugins" /S /Q
attrib -R "$(TargetDir)locales\en-US.pak"
powershell -ExecutionPolicy Unrestricted -File "$(ProjectDir)Resources\PostBuild.ps1" "$(TargetDir)\" "$(ProjectDir)\"
if $(ConfigurationName) == Debug ( if $(ConfigurationName) == Debug (
rmdir "$(TargetDir)plugins\official\.debug" /S /Q rmdir "$(TargetDir)plugins\official\.debug" /S /Q
mkdir "$(TargetDir)plugins\user\.debug" mkdir "$(TargetDir)plugins\user\.debug"
xcopy "$(ProjectDir)Resources\Plugins\.debug\*" "$(TargetDir)plugins\user\.debug\" /E /Y xcopy "$(ProjectDir)Resources\Plugins\.debug\*" "$(TargetDir)plugins\user\.debug\" /E /Y
) )
</PostBuildEvent>
powershell -ExecutionPolicy Unrestricted -File "$(ProjectDir)Resources\PostBuild.ps1" "$(TargetDir)\"</PostBuildEvent>
</PropertyGroup> </PropertyGroup>
<Target Name="AfterBuild" Condition="$(ConfigurationName) == Release"> <Target Name="AfterBuild" Condition="$(ConfigurationName) == Release">
<Exec Command="del &quot;$(TargetDir)*.pdb&quot;" /> <Exec Command="del &quot;$(TargetDir)*.pdb&quot;" />

View File

@@ -0,0 +1,14 @@
using System;
using TweetDuck.Data;
namespace TweetDuck.Updates.Events{
sealed class UpdateCheckEventArgs : EventArgs{
public int EventId { get; }
public Result<UpdateInfo> Result { get; }
public UpdateCheckEventArgs(int eventId, Result<UpdateInfo> result){
this.EventId = eventId;
this.Result = result;
}
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace TweetDuck.Updates.Events{
sealed class UpdateEventArgs : EventArgs{
public UpdateInfo UpdateInfo { get; }
public UpdateEventArgs(UpdateInfo updateInfo){
this.UpdateInfo = updateInfo;
}
}
}

View File

@@ -1,7 +1,5 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils;
namespace TweetDuck.Updates{ namespace TweetDuck.Updates{
sealed partial class FormUpdateDownload : Form{ sealed partial class FormUpdateDownload : Form{
@@ -22,21 +20,11 @@ namespace TweetDuck.Updates{
} }
private void timerDownloadCheck_Tick(object sender, EventArgs e){ private void timerDownloadCheck_Tick(object sender, EventArgs e){
if (updateInfo.DownloadStatus == UpdateDownloadStatus.Done){ if (updateInfo.DownloadStatus.IsFinished()){
timerDownloadCheck.Stop(); timerDownloadCheck.Stop();
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
Close(); Close();
} }
else if (updateInfo.DownloadStatus == UpdateDownloadStatus.Failed){
timerDownloadCheck.Stop();
if (FormMessage.Error("Update Has Failed", "Could not download the update: "+(updateInfo.DownloadError?.Message ?? "unknown error")+"\n\nDo you want to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)){
BrowserUtils.OpenExternalBrowser(Program.Website);
DialogResult = DialogResult.OK;
}
Close();
}
} }
} }
} }

View File

@@ -0,0 +1,63 @@
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web.Script.Serialization;
using TweetDuck.Core.Utils;
using JsonObject = System.Collections.Generic.IDictionary<string, object>;
namespace TweetDuck.Updates{
sealed class UpdateCheckClient{
private const string ApiLatestRelease = "https://api.github.com/repos/chylex/TweetDuck/releases/latest";
private const string UpdaterAssetName = "TweetDuck.Update.exe";
private readonly UpdaterSettings settings;
public UpdateCheckClient(UpdaterSettings settings){
this.settings = settings;
}
public Task<UpdateInfo> Check(){
TaskCompletionSource<UpdateInfo> result = new TaskCompletionSource<UpdateInfo>();
WebClient client = BrowserUtils.CreateWebClient();
client.Headers[HttpRequestHeader.Accept] = "application/vnd.github.v3+json";
client.DownloadStringTaskAsync(ApiLatestRelease).ContinueWith(task => {
if (task.IsCanceled){
result.SetCanceled();
}
else if (task.IsFaulted){
result.SetException(task.Exception.InnerException);
}
else{
try{
result.SetResult(ParseFromJson(task.Result));
}catch(Exception e){
result.SetException(e);
}
}
});
return result.Task;
}
private UpdateInfo ParseFromJson(string json){
bool IsUpdaterAsset(JsonObject obj){
return UpdaterAssetName == (string)obj["name"];
}
string AssetDownloadUrl(JsonObject obj){
return (string)obj["browser_download_url"];
}
JsonObject root = (JsonObject)new JavaScriptSerializer().DeserializeObject(json);
string versionTag = (string)root["tag_name"];
string releaseNotes = (string)root["body"];
string downloadUrl = ((Array)root["assets"]).Cast<JsonObject>().Where(IsUpdaterAsset).Select(AssetDownloadUrl).FirstOrDefault();
return new UpdateInfo(settings, versionTag, releaseNotes, downloadUrl);
}
}
}

View File

@@ -2,7 +2,15 @@
public enum UpdateDownloadStatus{ public enum UpdateDownloadStatus{
None = 0, None = 0,
InProgress, InProgress,
Done, Canceled,
Failed AssetMissing,
Failed,
Done
}
public static class UpdateDownloadStatusExtensions{
public static bool IsFinished(this UpdateDownloadStatus status){
return status == UpdateDownloadStatus.AssetMissing || status == UpdateDownloadStatus.Done || status == UpdateDownloadStatus.Failed;
}
} }
} }

View File

@@ -1,20 +0,0 @@
using System;
namespace TweetDuck.Updates{
sealed class UpdateEventArgs : EventArgs{
public int EventId { get; }
public UpdateInfo UpdateInfo { get; }
public bool IsUpdateAvailable => UpdateInfo != null;
public UpdateEventArgs(int eventId, UpdateInfo updateInfo){
this.EventId = eventId;
this.UpdateInfo = updateInfo;
}
public UpdateEventArgs(UpdateInfo updateInfo){
this.EventId = updateInfo.EventId;
this.UpdateInfo = updateInfo;
}
}
}

View File

@@ -1,52 +1,96 @@
using CefSharp; using System;
using System; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Other.Interfaces;
using TweetDuck.Resources; using TweetDuck.Data;
using TweetDuck.Updates.Events;
namespace TweetDuck.Updates{ namespace TweetDuck.Updates{
sealed class UpdateHandler{ sealed class UpdateHandler : IDisposable{
private readonly ITweetDeckBrowser browser; public const int CheckCodeUpdatesDisabled = -1;
public const int CheckCodeNotOnTweetDeck = -2;
private readonly UpdaterSettings settings; private readonly UpdaterSettings settings;
private readonly UpdateCheckClient client;
private readonly ITweetDeckBrowser browser;
private readonly Timer timer;
public event EventHandler<UpdateEventArgs> UpdateAccepted; public event EventHandler<UpdateEventArgs> UpdateAccepted;
public event EventHandler<UpdateEventArgs> UpdateDelayed;
public event EventHandler<UpdateEventArgs> UpdateDismissed; public event EventHandler<UpdateEventArgs> UpdateDismissed;
public event EventHandler<UpdateEventArgs> CheckFinished; public event EventHandler<UpdateCheckEventArgs> CheckFinished;
private int lastEventId; private ushort lastEventId;
private UpdateInfo lastUpdateInfo; private UpdateInfo lastUpdateInfo;
public UpdateHandler(ITweetDeckBrowser browser, UpdaterSettings settings){ public UpdateHandler(ITweetDeckBrowser browser, UpdaterSettings settings){
this.browser = browser;
this.settings = settings; this.settings = settings;
this.client = new UpdateCheckClient(settings);
this.browser = browser;
this.browser.RegisterBridge("$TDU", new Bridge(this));
browser.OnFrameLoaded(OnFrameLoaded); this.timer = new Timer();
browser.RegisterBridge("$TDU", new Bridge(this)); this.timer.Tick += timer_Tick;
} }
private void OnFrameLoaded(IFrame frame){ public void Dispose(){
ScriptLoader.ExecuteFile(frame, "update.js"); timer.Dispose();
}
private void timer_Tick(object sender, EventArgs e){
timer.Stop();
Check(false); Check(false);
} }
public void StartTimer(){
if (timer.Enabled){
return;
}
timer.Stop();
if (Program.UserConfig.EnableUpdateCheck){
DateTime now = DateTime.Now;
TimeSpan nextHour = now.AddSeconds(60*(60-now.Minute)-now.Second)-now;
if (nextHour.TotalMinutes < 15){
nextHour = nextHour.Add(TimeSpan.FromHours(1));
}
timer.Interval = (int)Math.Ceiling(nextHour.TotalMilliseconds);
timer.Start();
}
}
public int Check(bool force){ public int Check(bool force){
if (Program.UserConfig.EnableUpdateCheck || force){ if (Program.UserConfig.EnableUpdateCheck || force){
if (force){ if (force){
settings.DismissedUpdate = null; settings.DismissedUpdate = null;
} }
if (!browser.IsTweetDeckWebsite){
return CheckCodeNotOnTweetDeck;
}
int nextEventId = unchecked(++lastEventId);
Task<UpdateInfo> checkTask = client.Check();
browser.ExecuteFunction("TDUF_runUpdateCheck", ++lastEventId, Program.VersionTag, settings.DismissedUpdate ?? string.Empty, settings.AllowPreReleases); checkTask.ContinueWith(task => HandleUpdateCheckSuccessful(nextEventId, task.Result), TaskContinuationOptions.OnlyOnRanToCompletion);
return lastEventId; checkTask.ContinueWith(task => HandleUpdateCheckFailed(nextEventId, task.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted);
return nextEventId;
} }
return 0; return CheckCodeUpdatesDisabled;
} }
public void BeginUpdateDownload(Form ownerForm, UpdateInfo updateInfo, Action<UpdateInfo> onSuccess){ public void BeginUpdateDownload(Form ownerForm, UpdateInfo updateInfo, Action<UpdateInfo> onFinished){
if (updateInfo.DownloadStatus == UpdateDownloadStatus.Done){ UpdateDownloadStatus status = updateInfo.DownloadStatus;
onSuccess(updateInfo);
if (status == UpdateDownloadStatus.Done || status == UpdateDownloadStatus.AssetMissing){
onFinished(updateInfo);
} }
else{ else{
FormUpdateDownload downloadForm = new FormUpdateDownload(updateInfo); FormUpdateDownload downloadForm = new FormUpdateDownload(updateInfo);
@@ -57,14 +101,12 @@ namespace TweetDuck.Updates{
}; };
downloadForm.FormClosed += (sender, args) => { downloadForm.FormClosed += (sender, args) => {
if (downloadForm.DialogResult != DialogResult.OK){
updateInfo.CancelDownload();
}
downloadForm.Dispose(); downloadForm.Dispose();
onFinished(updateInfo);
if (downloadForm.DialogResult == DialogResult.OK){ // success or manual download
onSuccess(updateInfo);
}
else{
ownerForm.Show();
}
}; };
downloadForm.Show(); downloadForm.Show();
@@ -78,17 +120,39 @@ namespace TweetDuck.Updates{
} }
} }
private void TriggerUpdateAcceptedEvent(UpdateEventArgs args){ private void HandleUpdateCheckSuccessful(int eventId, UpdateInfo info){
UpdateAccepted?.Invoke(this, args); if (info.IsUpdateNew && !info.IsUpdateDismissed){
CleanupDownload();
lastUpdateInfo = info;
lastUpdateInfo.BeginSilentDownload();
}
CheckFinished?.Invoke(this, new UpdateCheckEventArgs(eventId, new Result<UpdateInfo>(info)));
} }
private void TriggerUpdateDismissedEvent(UpdateEventArgs args){ private void HandleUpdateCheckFailed(int eventId, Exception exception){
settings.DismissedUpdate = args.UpdateInfo.VersionTag; CheckFinished?.Invoke(this, new UpdateCheckEventArgs(eventId, new Result<UpdateInfo>(exception)));
UpdateDismissed?.Invoke(this, args);
} }
private void TriggerCheckFinishedEvent(UpdateEventArgs args){ private void TriggerUpdateAcceptedEvent(){
CheckFinished?.Invoke(this, args); if (lastUpdateInfo != null){
UpdateAccepted?.Invoke(this, new UpdateEventArgs(lastUpdateInfo));
}
}
private void TriggerUpdateDelayedEvent(){
if (lastUpdateInfo != null){
UpdateDelayed?.Invoke(this, new UpdateEventArgs(lastUpdateInfo));
}
}
private void TriggerUpdateDismissedEvent(){
if (lastUpdateInfo != null){
settings.DismissedUpdate = lastUpdateInfo.VersionTag;
UpdateDismissed?.Invoke(this, new UpdateEventArgs(lastUpdateInfo));
CleanupDownload();
}
} }
public sealed class Bridge{ public sealed class Bridge{
@@ -102,31 +166,16 @@ namespace TweetDuck.Updates{
owner.Check(false); owner.Check(false);
} }
public void OnUpdateCheckFinished(int eventId, string versionTag, string downloadUrl){ public void OnUpdateAccepted(){
if (versionTag != null && (owner.lastUpdateInfo == null || owner.lastUpdateInfo.VersionTag != versionTag)){ owner.TriggerUpdateAcceptedEvent();
owner.CleanupDownload();
owner.lastUpdateInfo = new UpdateInfo(owner.settings, eventId, versionTag, downloadUrl);
owner.lastUpdateInfo.BeginSilentDownload();
}
owner.TriggerCheckFinishedEvent(new UpdateEventArgs(eventId, owner.lastUpdateInfo));
} }
public void OnUpdateAccepted(){ public void OnUpdateDelayed(){
if (owner.lastUpdateInfo != null){ owner.TriggerUpdateDelayedEvent();
owner.TriggerUpdateAcceptedEvent(new UpdateEventArgs(owner.lastUpdateInfo));
}
} }
public void OnUpdateDismissed(){ public void OnUpdateDismissed(){
if (owner.lastUpdateInfo != null){ owner.TriggerUpdateDismissedEvent();
owner.TriggerUpdateDismissedEvent(new UpdateEventArgs(owner.lastUpdateInfo));
owner.CleanupDownload();
}
}
public void OpenBrowser(string url){
BrowserUtils.OpenExternalBrowser(url);
} }
} }
} }

View File

@@ -5,40 +5,43 @@ using TweetDuck.Core.Utils;
namespace TweetDuck.Updates{ namespace TweetDuck.Updates{
sealed class UpdateInfo{ sealed class UpdateInfo{
public int EventId { get; }
public string VersionTag { get; } public string VersionTag { get; }
public string ReleaseNotes { get; }
public string InstallerPath { get; } public string InstallerPath { get; }
public bool IsUpdateNew => VersionTag != Program.VersionTag;
public bool IsUpdateDismissed => VersionTag == settings.DismissedUpdate;
public UpdateDownloadStatus DownloadStatus { get; private set; } public UpdateDownloadStatus DownloadStatus { get; private set; }
public Exception DownloadError { get; private set; } public Exception DownloadError { get; private set; }
private readonly string installerFolder; private readonly UpdaterSettings settings;
private readonly string downloadUrl; private readonly string downloadUrl;
private WebClient currentDownload; private WebClient currentDownload;
public UpdateInfo(UpdaterSettings settings, int eventId, string versionTag, string downloadUrl){ public UpdateInfo(UpdaterSettings settings, string versionTag, string releaseNotes, string downloadUrl){
this.installerFolder = settings.InstallerDownloadFolder; this.settings = settings;
this.downloadUrl = downloadUrl; this.downloadUrl = downloadUrl;
this.EventId = eventId;
this.VersionTag = versionTag; this.VersionTag = versionTag;
this.InstallerPath = Path.Combine(installerFolder, "TweetDuck."+versionTag+".exe"); this.ReleaseNotes = releaseNotes;
this.InstallerPath = Path.Combine(settings.InstallerDownloadFolder, "TweetDuck."+versionTag+".exe");
} }
public void BeginSilentDownload(){ public void BeginSilentDownload(){
if (DownloadStatus == UpdateDownloadStatus.None || DownloadStatus == UpdateDownloadStatus.Failed){ if (DownloadStatus == UpdateDownloadStatus.None || DownloadStatus == UpdateDownloadStatus.Failed){
DownloadStatus = UpdateDownloadStatus.InProgress; DownloadStatus = UpdateDownloadStatus.InProgress;
try{ if (string.IsNullOrEmpty(downloadUrl)){
Directory.CreateDirectory(installerFolder); DownloadError = new InvalidDataException("Missing installer asset.");
}catch(Exception e){ DownloadStatus = UpdateDownloadStatus.AssetMissing;
DownloadError = e;
DownloadStatus = UpdateDownloadStatus.Failed;
return; return;
} }
if (string.IsNullOrEmpty(downloadUrl)){ try{
DownloadError = new UriFormatException("Could not determine URL of the update installer"); Directory.CreateDirectory(settings.InstallerDownloadFolder);
}catch(Exception e){
DownloadError = e;
DownloadStatus = UpdateDownloadStatus.Failed; DownloadStatus = UpdateDownloadStatus.Failed;
return; return;
} }
@@ -68,5 +71,18 @@ namespace TweetDuck.Updates{
// rip // rip
} }
} }
public void CancelDownload(){
DeleteInstaller();
DownloadStatus = UpdateDownloadStatus.Canceled;
}
public override bool Equals(object obj){
return obj is UpdateInfo info && VersionTag == info.VersionTag;
}
public override int GetHashCode(){
return VersionTag.GetHashCode();
}
} }
} }

View File

@@ -1,7 +1,11 @@
namespace TweetDuck.Updates{ namespace TweetDuck.Updates{
sealed class UpdaterSettings{ sealed class UpdaterSettings{
public bool AllowPreReleases { get; set; } public string InstallerDownloadFolder { get; }
public string DismissedUpdate { get; set; } public string DismissedUpdate { get; set; }
public string InstallerDownloadFolder { get; set; }
public UpdaterSettings(string installerDownloadFolder){
this.InstallerDownloadFolder = installerDownloadFolder;
}
} }
} }

1
bld/UPDATE ONLY.bat Normal file
View File

@@ -0,0 +1 @@
start "" /B "ISCC.exe" /Q "gen_upd.iss"

View File

@@ -106,6 +106,15 @@ begin
Result := (PageID = wpSelectDir) and (UpdatePath <> '') Result := (PageID = wpSelectDir) and (UpdatePath <> '')
end; end;
{ Check the desktop icon task if not updating. }
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpSelectTasks then
begin
WizardForm.TasksList.Checked[WizardForm.TasksList.Items.Count-1] := (UpdatePath = '');
end;
end;
{ Install VC++ if downloaded. } { Install VC++ if downloaded. }
procedure CurStepChanged(CurStep: TSetupStep); procedure CurStepChanged(CurStep: TSetupStep);
begin begin

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
@@ -20,6 +20,7 @@
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<LangVersion>7</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath> <OutputPath>bin\x86\Release\</OutputPath>
@@ -29,6 +30,7 @@
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<LangVersion>7</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />

View File

@@ -4,5 +4,4 @@
<package id="cef.redist.x86" version="3.3282.1731" targetFramework="net452" xmlns="" /> <package id="cef.redist.x86" version="3.3282.1731" targetFramework="net452" xmlns="" />
<package id="CefSharp.Common" version="64.0.0-CI2508" targetFramework="net452" xmlns="" /> <package id="CefSharp.Common" version="64.0.0-CI2508" targetFramework="net452" xmlns="" />
<package id="CefSharp.WinForms" version="64.0.0-CI2508" targetFramework="net452" xmlns="" /> <package id="CefSharp.WinForms" version="64.0.0-CI2508" targetFramework="net452" xmlns="" />
<package id="Microsoft.VC120.CRT.JetBrains" version="12.0.21005.2" targetFramework="net452" xmlns="" />
</packages> </packages>

View File

@@ -15,10 +15,12 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
<OutputPath>bin\x86\Debug\</OutputPath> <OutputPath>bin\x86\Debug\</OutputPath>
<LangVersion>7</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
<OutputPath>bin\x86\Release\</OutputPath> <OutputPath>bin\x86\Release\</OutputPath>
<LangVersion>7</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<StartupObject /> <StartupObject />

View File

@@ -21,7 +21,19 @@ namespace UnitTests.Core{
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/")); Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc/status")); Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc/status"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://nottwitter.com/chylexmc")); Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://nottwitter.com/chylexmc"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/chylexmc?"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("www.twitter.com/chylexmc")); Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("www.twitter.com/chylexmc"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/signup"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/tos"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/privacy"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/search"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/search?query"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/search-home"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/search-advanced"));
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/tosser"));
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/searching"));
} }
[TestMethod] [TestMethod]

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -22,6 +22,7 @@
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
<OutputPath>bin\x86\Debug\</OutputPath> <OutputPath>bin\x86\Debug\</OutputPath>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<LangVersion>7</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
@@ -22,6 +22,7 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit> <Prefer32Bit>true</Prefer32Bit>
<LangVersion>7</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath> <OutputPath>bin\x86\Release\</OutputPath>
@@ -33,6 +34,7 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit> <Prefer32Bit>true</Prefer32Bit>
<GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies> <GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
<LangVersion>7</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<ApplicationIcon>Resources\icon.ico</ApplicationIcon> <ApplicationIcon>Resources\icon.ico</ApplicationIcon>