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

Compare commits

...

51 Commits

Author SHA1 Message Date
1d78bd2655 Release 1.13.4.1 2018-04-24 18:16:13 +02:00
9250f1907c Quick semi-temporary fix for removed column.isOfType 2018-04-24 18:10:22 +02:00
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
80 changed files with 1662 additions and 835 deletions

View File

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

View File

@@ -50,6 +50,7 @@ namespace TweetDuck.Configuration{
public bool IgnoreTrackingUrlWarning { get; set; } = false;
public bool EnableSmoothScrolling { get; set; } = true;
public string BrowserPath { get; set; } = null;
public string SearchEngineUrl { get; set; } = null;
private int _zoomLevel = 100;
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

@@ -117,8 +117,8 @@ namespace TweetDuck.Core.Bridge{
});
}
public void ScreenshotTweet(string html, int width, int height){
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
public void ScreenshotTweet(string html, int width){
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width));
}
public void PlayVideo(string url, string username){

View File

@@ -15,6 +15,7 @@ using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events;
using TweetDuck.Updates;
using TweetDuck.Updates.Events;
namespace TweetDuck.Core{
sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{
@@ -38,8 +39,8 @@ namespace TweetDuck.Core{
}
public string UpdateInstallerPath { get; private set; }
public PluginManager PluginManager => plugins;
private bool ignoreUpdateCheckError;
public AnalyticsFile AnalyticsFile => analytics?.File ?? AnalyticsFile.Dummy;
private readonly TweetDeckBrowser browser;
@@ -78,8 +79,9 @@ namespace TweetDuck.Core{
Disposed += (sender, args) => {
Config.MuteToggled -= Config_MuteToggled;
Config.TrayBehaviorChanged -= Config_TrayBehaviorChanged;
browser.Dispose();
updates.Dispose();
contextMenu.Dispose();
notificationScreenshotManager?.Dispose();
@@ -96,6 +98,7 @@ namespace TweetDuck.Core{
UpdateTrayIcon();
this.updates = new UpdateHandler(browser, updaterSettings);
this.updates.CheckFinished += updates_CheckFinished;
this.updates.UpdateAccepted += updates_UpdateAccepted;
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){
this.InvokeAsyncSafe(() => {
FormManager.CloseAllDialogs();
@@ -241,13 +266,21 @@ namespace TweetDuck.Core{
Config.DismissedUpdate = null;
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(){
#if DEBUG
Resources.ScriptLoader.HotSwap();
#endif
ignoreUpdateCheckError = false;
browser.ReloadToTweetDeck();
AnalyticsFile.BrowserReloads.Trigger();
}
public void AddSearchColumn(string query){
browser.AddSearchColumn(query);
}
public void TriggerTweetScreenshot(){
browser.TriggerTweetScreenshot();
}
@@ -456,12 +498,12 @@ namespace TweetDuck.Core{
AnalyticsFile.TweetDetails.Trigger();
}
public void OnTweetScreenshotReady(string html, int width, int height){
public void OnTweetScreenshotReady(string html, int width){
if (notificationScreenshotManager == null){
notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
}
notificationScreenshotManager.Trigger(html, width, height);
notificationScreenshotManager.Trigger(html, width);
AnalyticsFile.TweetScreenshots.Trigger();
}

View File

@@ -11,13 +11,14 @@ using TweetDuck.Core.Management;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Other;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Resources;
namespace TweetDuck.Core.Handling{
abstract class ContextMenuBase : IContextMenuHandler{
public static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
private static TwitterUtils.ImageQuality ImageQuality => Program.UserConfig.TwitterImageQuality;
private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand)26500;
private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand)26501;
private const CefMenuCommand MenuCopyUsername = (CefMenuCommand)26502;
@@ -26,6 +27,7 @@ namespace TweetDuck.Core.Handling{
private const CefMenuCommand MenuCopyMediaUrl = (CefMenuCommand)26505;
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26506;
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26507;
private const CefMenuCommand MenuSearchInBrowser = (CefMenuCommand)26508;
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
protected ContextInfo.LinkInfo LastLink { get; private set; }
@@ -52,6 +54,11 @@ namespace TweetDuck.Core.Handling{
LastChirp = TweetDeckBridge.ContextInfo.Chirp;
}
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
model.AddItem(MenuSearchInBrowser, "Search in browser");
model.AddSeparator();
}
bool hasTweetImage = LastLink.IsImage;
bool hasTweetVideo = LastLink.IsVideo;
@@ -132,7 +139,7 @@ namespace TweetDuck.Core.Handling{
}
string url = LastLink.GetMediaSource(parameters);
string file = Path.Combine(BrowserCache.CacheFolder, TwitterUtils.GetImageFileName(url));
string file = Path.Combine(BrowserCache.CacheFolder, TwitterUtils.GetImageFileName(url) ?? Path.GetRandomFileName());
if (File.Exists(file)){
ViewImage(file);
@@ -165,6 +172,12 @@ namespace TweetDuck.Core.Handling{
control.InvokeAsyncSafe(analytics.AnalyticsFile.DownloadedImages.Trigger);
TwitterUtils.DownloadImages(LastChirp.Images, LastChirp.Authors.LastOrDefault(), ImageQuality);
break;
case MenuSearchInBrowser:
string query = parameters.SelectionText;
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalSearch(query));
DeselectAll(frame);
break;
case MenuOpenDevTools:
browserControl.ShowDevTools();
@@ -182,6 +195,10 @@ namespace TweetDuck.Core.Handling{
return false;
}
protected void DeselectAll(IFrame frame){
ScriptLoader.ExecuteScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect");
}
protected void OpenBrowser(Control control, string url){
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
}
@@ -189,6 +206,10 @@ namespace TweetDuck.Core.Handling{
protected void SetClipboardText(Control control, string text){
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){
model.AddItem(MenuOpenDevTools, "Open dev tools");

View File

@@ -1,6 +1,7 @@
using CefSharp;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling{
sealed class ContextMenuBrowser : ContextMenuBase{
@@ -16,6 +17,7 @@ namespace TweetDuck.Core.Handling{
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26613;
private const CefMenuCommand MenuScreenshotTweet = (CefMenuCommand)26614;
private const CefMenuCommand MenuInputApplyROT13 = (CefMenuCommand)26615;
private const CefMenuCommand MenuSearchInColumn = (CefMenuCommand)26616;
private const string TitleReloadBrowser = "Reload browser";
private const string TitleMuteNotifications = "Mute notifications";
@@ -30,14 +32,17 @@ namespace TweetDuck.Core.Handling{
}
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.Forward);
model.Remove(CefMenuCommand.Print);
model.Remove(CefMenuCommand.ViewSource);
RemoveSeparatorIfLast(model);
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
if (parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
if (isSelecting){
if (isEditing){
model.AddSeparator();
model.AddItem(MenuInputApplyROT13, "Apply ROT13");
}
@@ -47,7 +52,11 @@ namespace TweetDuck.Core.Handling{
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
if (!string.IsNullOrEmpty(LastChirp.TweetUrl) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
if (isSelecting && !isEditing && TwitterUtils.IsTweetDeckWebsite(frame)){
InsertSelectionSearchItem(model, MenuSearchInColumn, "Search in a column");
}
if (!string.IsNullOrEmpty(LastChirp.TweetUrl) && !isSelecting && !isEditing){
model.AddItem(MenuOpenTweetUrl, "Open tweet in browser");
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard");
@@ -61,7 +70,7 @@ namespace TweetDuck.Core.Handling{
model.AddSeparator();
}
if ((parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
if (!isSelecting && !isEditing){
AddSeparator(model);
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName);
@@ -135,6 +144,12 @@ namespace TweetDuck.Core.Handling{
case MenuInputApplyROT13:
form.InvokeAsyncSafe(form.ApplyROT13);
return true;
case MenuSearchInColumn:
string query = parameters.SelectionText;
form.InvokeAsyncSafe(() => form.AddSearchColumn(query));
DeselectAll(frame);
break;
}
return false;

View File

@@ -1,7 +1,6 @@
using System.Drawing;
using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other;
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){
((ChromiumWebBrowser)browserControl).InvokeSafe(() => {
browserControl.AsControl().InvokeSafe(() => {
FormMessage form;
TextBox input = null;

View File

@@ -11,7 +11,7 @@ namespace TweetDuck.Core.Management{
}
public void SetLink(string type, string url){
Link = new LinkInfo(string.IsNullOrEmpty(url) ? null : type, url);
Link = string.IsNullOrEmpty(url) ? new LinkInfo() : new LinkInfo(type, url);
}
public void SetChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){

View File

@@ -23,7 +23,6 @@ namespace TweetDuck.Core.Management{
}
public bool IsRestarting { get; private set; }
public Exception LastException { get; private set; }
private readonly string file;
private readonly PluginManager plugins;
@@ -67,7 +66,7 @@ namespace TweetDuck.Core.Management{
return true;
}catch(Exception e){
LastException = e;
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, e);
return false;
}
}
@@ -100,8 +99,7 @@ namespace TweetDuck.Core.Management{
}
}
}
}catch(Exception e){
LastException = e;
}catch(Exception){
items = Items.None;
}
@@ -164,12 +162,12 @@ namespace TweetDuck.Core.Management{
}
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;
}catch(Exception e){
LastException = e;
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing TweetDuck profile.", true, e);
return false;
}
}
@@ -199,15 +197,23 @@ namespace TweetDuck.Core.Management{
}
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
return Directory.Exists(root) ? Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{
Full = fullPath,
Relative = fullPath.Substring(root.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) // strip leading separator character
}) : Enumerable.Empty<PathInfo>();
if (Directory.Exists(root)){
int rootLength = root.Length;
return Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo(fullPath, rootLength));
}
else{
return Enumerable.Empty<PathInfo>();
}
}
private sealed class PathInfo{
public string Full { get; set; }
public string Relative { get; set; }
public string Full { get; }
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{
sealed class VideoPlayer : IDisposable{
public bool Running{
get{
if (currentProcess == null){
return false;
}
currentProcess.Refresh();
return !currentProcess.HasExited;
}
}
public bool Running => currentInstance != null && currentInstance.Running;
public event EventHandler ProcessExited;
private readonly FormBrowser owner;
private string lastUrl;
private string lastUsername;
private Process currentProcess;
private DuplexPipe.Server currentPipe;
private Instance currentInstance;
private bool isClosing;
public VideoPlayer(FormBrowser owner){
@@ -40,38 +28,43 @@ namespace TweetDuck.Core.Management{
Destroy();
isClosing = false;
}
lastUrl = url;
lastUsername = username;
try{
currentPipe = DuplexPipe.CreateServer();
currentPipe.DataIn += currentPipe_DataIn;
DuplexPipe.Server pipe = DuplexPipe.CreateServer();
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"),
Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\" \"{currentPipe.GenerateToken()}\"",
Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\" \"{pipe.GenerateToken()}\"",
UseShellExecute = false,
RedirectStandardOutput = true
})) != null){
currentProcess.EnableRaisingEvents = true;
currentProcess.Exited += process_Exited;
currentProcess.BeginOutputReadLine();
currentProcess.OutputDataReceived += process_OutputDataReceived;
}
currentInstance = new Instance(process, pipe, url, username);
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){
Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e);
}
}
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(() => {
switch(e.Key){
case "vol":
@@ -83,16 +76,16 @@ namespace TweetDuck.Core.Management{
break;
case "download":
owner.AnalyticsFile.DownloadedVideos.Trigger();
TwitterUtils.DownloadVideo(lastUrl, lastUsername);
if (currentInstance != null){
owner.AnalyticsFile.DownloadedVideos.Trigger();
TwitterUtils.DownloadVideo(currentInstance.Url, currentInstance.Username);
}
break;
case "rip":
currentPipe.Dispose();
currentPipe = null;
currentProcess.Dispose();
currentProcess = null;
currentInstance?.Dispose();
currentInstance = null;
isClosing = false;
TriggerProcessExitEventUnsafe();
@@ -102,15 +95,15 @@ namespace TweetDuck.Core.Management{
}
public void Close(){
if (currentProcess != null){
if (currentInstance != null){
if (isClosing){
Destroy();
isClosing = false;
}
else{
isClosing = true;
currentProcess.Exited -= process_Exited;
currentPipe.Write("die");
currentInstance.Process.Exited -= process_Exited;
currentInstance.Pipe.Write("die");
}
}
}
@@ -123,26 +116,17 @@ namespace TweetDuck.Core.Management{
}
private void Destroy(){
if (currentProcess != null){
try{
currentProcess.Kill();
}catch{
// kill me instead then
}
currentProcess.Dispose();
currentProcess = null;
currentPipe.Dispose();
currentPipe = null;
if (currentInstance != null){
currentInstance.KillAndDispose();
currentInstance = null;
TriggerProcessExitEventUnsafe();
}
}
private void owner_FormClosing(object sender, FormClosingEventArgs e){
if (currentProcess != null){
currentProcess.Exited -= process_Exited;
if (currentInstance != null){
currentInstance.Process.Exited -= process_Exited;
}
}
@@ -153,25 +137,27 @@ namespace TweetDuck.Core.Management{
}
private void process_Exited(object sender, EventArgs e){
int exitCode = currentProcess.ExitCode;
if (currentInstance == null){
return;
}
currentProcess.Dispose();
currentProcess = null;
int exitCode = currentInstance.Process.ExitCode;
string url = currentInstance.Url;
currentPipe.Dispose();
currentPipe = null;
currentInstance.Dispose();
currentInstance = null;
switch(exitCode){
case 3: // CODE_LAUNCH_FAIL
if (FormMessage.Error("Video Playback Error", "Error launching video player, this may be caused by missing Windows Media Player. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
BrowserUtils.OpenExternalBrowser(lastUrl);
BrowserUtils.OpenExternalBrowser(url);
}
break;
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)){
BrowserUtils.OpenExternalBrowser(lastUrl);
BrowserUtils.OpenExternalBrowser(url);
}
break;
@@ -183,5 +169,42 @@ namespace TweetDuck.Core.Management{
private void TriggerProcessExitEventUnsafe(){
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;
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
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");

View File

@@ -196,7 +196,7 @@ namespace TweetDuck.Core.Notification{
}
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){
@@ -217,7 +217,9 @@ namespace TweetDuck.Core.Notification{
}
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(){

View File

@@ -5,6 +5,7 @@ using System.Windows.Forms;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Other.Interfaces;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins;

View File

@@ -3,7 +3,7 @@ using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
@@ -15,23 +15,37 @@ namespace TweetDuck.Core.Notification.Screenshot{
protected override bool CanDragWindow => false;
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.width = width;
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback));
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new ScreenshotBridge(this, SetScreenshotHeight, callback));
browser.LoadingStateChanged += (sender, args) => {
if (!args.IsLoading){
using(IFrame frame = args.Browser.MainFrame){
ScriptLoader.ExecuteScript(frame, "window.setTimeout($TD_NotificationScreenshot.trigger, document.getElementsByTagName('iframe').length ? 267 : 67)", "gen:screenshot");
}
if (args.IsLoading){
return;
}
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){
string html = tweet.GenerateHtml("td-screenshot", false);
string html = tweet.GenerateHtml("td-screenshot");
foreach(InjectedHTML injection in plugins.NotificationInjections){
html = injection.Inject(html);
@@ -40,12 +54,16 @@ namespace TweetDuck.Core.Notification.Screenshot{
return html;
}
public void LoadNotificationForScreenshot(TweetNotification tweet, int width, int height){
LoadTweet(tweet);
private void SetScreenshotHeight(int height){
SetNotificationSize(width, height);
}
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);
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;
}
public void Trigger(string html, int width, int height){
public void Trigger(string html, int width){
if (screenshot != null){
return;
}
screenshot = new FormNotificationScreenshotable(Callback, owner, plugins);
screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty), width, height);
screenshot = new FormNotificationScreenshotable(Callback, owner, plugins, html, width);
screenshot.Show();
timeout.Start();

View File

@@ -8,7 +8,7 @@ using TweetDuck.Resources;
namespace TweetDuck.Core.Notification{
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 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 TweetNotification Example(string html, int characters){
@@ -53,17 +53,14 @@ namespace TweetDuck.Core.Notification{
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();
build.Append("<!DOCTYPE html>");
build.Append(TweetDeckBridge.NotificationHeadLayout ?? DefaultHeadLayout);
if (enableCustomCSS){
build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>");
build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>");
if (!string.IsNullOrEmpty(Program.UserConfig.CustomNotificationCSS)){
build.Append("<style type='text/css'>").Append(Program.UserConfig.CustomNotificationCSS).Append("</style>");
}
if (!string.IsNullOrEmpty(Program.UserConfig.CustomNotificationCSS)){
build.Append("<style type='text/css'>").Append(Program.UserConfig.CustomNotificationCSS).Append("</style>");
}
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
@@ -67,6 +67,10 @@ namespace TweetDuck.Core.Other.Analytics{
this.file = file;
}
private AnalyticsFile(){
this.file = null;
}
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))){
counter.SetOwner(this);

View File

@@ -58,11 +58,8 @@ namespace TweetDuck.Core.Other{
InitializeComponent();
Text = Program.BrandName+" Guide";
if (owner != null){
Size = new Size(owner.Size.Width*3/4, owner.Size.Height*3/4);
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){
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.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.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnClose.Size = new System.Drawing.Size(49, 23);
@@ -52,7 +52,7 @@
this.panelContents.Location = new System.Drawing.Point(135, 12);
this.panelContents.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3);
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;
//
// panelButtons
@@ -63,14 +63,14 @@
this.panelButtons.Location = new System.Drawing.Point(12, 12);
this.panelButtons.Margin = new System.Windows.Forms.Padding(3, 3, 0, 3);
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;
//
// btnManageOptions
//
this.btnManageOptions.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
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.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnManageOptions.Size = new System.Drawing.Size(101, 23);
@@ -83,7 +83,7 @@
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
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.panelContents);
this.Controls.Add(this.panelButtons);

View File

@@ -36,7 +36,7 @@ namespace TweetDuck.Core.Other{
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("System Tray", () => new TabSettingsTray());
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){
if (sender is ComboBox cb && cb.DroppedDown){
return; // prevents comboboxes from closing when MouseLeave event triggers during opening animation
}
panelContents.Focus();
}

View File

@@ -1,7 +1,7 @@
using System;
using CefSharp;
namespace TweetDuck.Core{
namespace TweetDuck.Core.Other.Interfaces{
interface ITweetDeckBrowser{
bool IsTweetDeckWebsite { get; }

View File

@@ -42,7 +42,9 @@
//
// 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.Location = new System.Drawing.Point(0, 16);
this.textBoxBrowserCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
@@ -124,7 +126,9 @@
//
// 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.Location = new System.Drawing.Point(0, 16);
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;
}
}
else{
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing TweetDuck profile.", true, importManager.LastException);
}
DialogResult = DialogResult.OK;
Close();
@@ -191,12 +188,8 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
Program.UserConfig.Save();
Program.SystemConfig.Save();
ProfileManager manager = new ProfileManager(file, plugins);
if (!manager.Export(SelectedItems)){
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, manager.LastException);
}
new ProfileManager(file, plugins).Export(SelectedItems);
DialogResult = DialogResult.OK;
Close();

View File

@@ -28,21 +28,22 @@
this.btnRestart = new System.Windows.Forms.Button();
this.cbLogging = new System.Windows.Forms.CheckBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.cbDebugUpdates = new System.Windows.Forms.CheckBox();
this.tbDataFolder = new System.Windows.Forms.TextBox();
this.tbShortcutTarget = new System.Windows.Forms.TextBox();
this.labelDataFolder = new System.Windows.Forms.Label();
this.labelShortcutTarget = new System.Windows.Forms.Label();
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.flowPanel.SuspendLayout();
this.SuspendLayout();
//
// 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(215, 163);
this.btnCancel.Location = new System.Drawing.Point(215, 139);
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 = 9;
this.btnCancel.TabIndex = 2;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
@@ -50,11 +51,11 @@
// btnRestart
//
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.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnRestart.Size = new System.Drawing.Size(57, 23);
this.btnRestart.TabIndex = 8;
this.btnRestart.TabIndex = 1;
this.btnRestart.Text = "Restart";
this.btnRestart.UseVisualStyleBackColor = true;
this.btnRestart.Click += new System.EventHandler(this.btnRestart_Click);
@@ -62,7 +63,7 @@
// cbLogging
//
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.Size = new System.Drawing.Size(64, 17);
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.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
//
this.tbDataFolder.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tbDataFolder.Location = new System.Drawing.Point(15, 83);
this.tbDataFolder.Location = new System.Drawing.Point(3, 51);
this.tbDataFolder.Name = "tbDataFolder";
this.tbDataFolder.Size = new System.Drawing.Size(257, 20);
this.tbDataFolder.TabIndex = 5;
this.tbDataFolder.Size = new System.Drawing.Size(260, 20);
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" +
"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)
| System.Windows.Forms.AnchorStyles.Right)));
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.ReadOnly = true;
this.tbShortcutTarget.Size = new System.Drawing.Size(257, 20);
this.tbShortcutTarget.TabIndex = 7;
this.tbShortcutTarget.Size = new System.Drawing.Size(260, 20);
this.tbShortcutTarget.TabIndex = 4;
this.tbShortcutTarget.Click += new System.EventHandler(this.tbShortcutTarget_Click);
//
// labelDataFolder
//
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.Name = "labelDataFolder";
this.labelDataFolder.Size = new System.Drawing.Size(62, 13);
this.labelDataFolder.TabIndex = 4;
this.labelDataFolder.TabIndex = 1;
this.labelDataFolder.Text = "Data Folder";
//
// labelShortcutTarget
//
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.Name = "labelShortcutTarget";
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)";
//
// 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
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 198);
this.Controls.Add(this.tbShortcutTarget);
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.ClientSize = new System.Drawing.Size(284, 174);
this.Controls.Add(this.flowPanel);
this.Controls.Add(this.btnRestart);
this.Controls.Add(this.btnCancel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
@@ -143,8 +144,9 @@
this.Name = "DialogSettingsRestart";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
@@ -154,10 +156,10 @@
private System.Windows.Forms.Button btnRestart;
private System.Windows.Forms.CheckBox cbLogging;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.CheckBox cbDebugUpdates;
private System.Windows.Forms.Label labelDataFolder;
private System.Windows.Forms.TextBox tbDataFolder;
private System.Windows.Forms.TextBox tbShortcutTarget;
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();
cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging);
cbDebugUpdates.Checked = currentArgs.HasFlag(Arguments.ArgDebugUpdates);
cbLogging.CheckedChanged += control_Change;
cbDebugUpdates.CheckedChanged += control_Change;
if (Program.IsPortable){
tbDataFolder.Text = "Not available in portable version";
@@ -36,10 +33,6 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
if (cbLogging.Checked){
Args.AddFlag(Arguments.ArgLogging);
}
if (cbDebugUpdates.Checked){
Args.AddFlag(Arguments.ArgDebugUpdates);
}
if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled){
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

@@ -44,6 +44,8 @@
this.checkSmoothScrolling = new System.Windows.Forms.CheckBox();
this.labelBrowserPath = new System.Windows.Forms.Label();
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();
this.panelZoom.SuspendLayout();
this.flowPanel.SuspendLayout();
@@ -63,21 +65,21 @@
// checkUpdateNotifications
//
this.checkUpdateNotifications.AutoSize = true;
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 363);
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 415);
this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkUpdateNotifications.Name = "checkUpdateNotifications";
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.UseVisualStyleBackColor = true;
//
// btnCheckUpdates
//
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 386);
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 438);
this.btnCheckUpdates.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnCheckUpdates.Name = "btnCheckUpdates";
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.UseVisualStyleBackColor = true;
//
@@ -99,7 +101,7 @@
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkBestImageQuality.Name = "checkBestImageQuality";
this.checkBestImageQuality.Size = new System.Drawing.Size(114, 17);
this.checkBestImageQuality.TabIndex = 5;
this.checkBestImageQuality.TabIndex = 4;
this.checkBestImageQuality.Text = "Best Image Quality";
this.checkBestImageQuality.UseVisualStyleBackColor = true;
//
@@ -110,7 +112,7 @@
this.checkOpenSearchInFirstColumn.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkOpenSearchInFirstColumn.Name = "checkOpenSearchInFirstColumn";
this.checkOpenSearchInFirstColumn.Size = new System.Drawing.Size(219, 17);
this.checkOpenSearchInFirstColumn.TabIndex = 3;
this.checkOpenSearchInFirstColumn.TabIndex = 2;
this.checkOpenSearchInFirstColumn.Text = "Add Search Columns Before First Column";
this.checkOpenSearchInFirstColumn.UseVisualStyleBackColor = true;
//
@@ -132,11 +134,11 @@
// labelZoom
//
this.labelZoom.AutoSize = true;
this.labelZoom.Location = new System.Drawing.Point(3, 268);
this.labelZoom.Location = new System.Drawing.Point(3, 320);
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelZoom.Name = "labelZoom";
this.labelZoom.Size = new System.Drawing.Size(34, 13);
this.labelZoom.TabIndex = 11;
this.labelZoom.TabIndex = 12;
this.labelZoom.Text = "Zoom";
//
// zoomUpdateTimer
@@ -160,11 +162,11 @@
this.panelZoom.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.panelZoom.Controls.Add(this.trackBarZoom);
this.panelZoom.Controls.Add(this.labelZoomValue);
this.panelZoom.Location = new System.Drawing.Point(0, 281);
this.panelZoom.Location = new System.Drawing.Point(0, 333);
this.panelZoom.Margin = new System.Windows.Forms.Padding(0);
this.panelZoom.Name = "panelZoom";
this.panelZoom.Size = new System.Drawing.Size(322, 36);
this.panelZoom.TabIndex = 12;
this.panelZoom.TabIndex = 13;
//
// checkAnimatedAvatars
//
@@ -173,7 +175,7 @@
this.checkAnimatedAvatars.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkAnimatedAvatars.Name = "checkAnimatedAvatars";
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.UseVisualStyleBackColor = true;
//
@@ -181,11 +183,11 @@
//
this.labelUpdates.AutoSize = true;
this.labelUpdates.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelUpdates.Location = new System.Drawing.Point(0, 337);
this.labelUpdates.Location = new System.Drawing.Point(0, 389);
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelUpdates.Name = "labelUpdates";
this.labelUpdates.Size = new System.Drawing.Size(70, 20);
this.labelUpdates.TabIndex = 13;
this.labelUpdates.TabIndex = 14;
this.labelUpdates.Text = "Updates";
//
// flowPanel
@@ -203,6 +205,8 @@
this.flowPanel.Controls.Add(this.checkSmoothScrolling);
this.flowPanel.Controls.Add(this.labelBrowserPath);
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.panelZoom);
this.flowPanel.Controls.Add(this.labelUpdates);
@@ -211,7 +215,7 @@
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(322, 415);
this.flowPanel.Size = new System.Drawing.Size(322, 462);
this.flowPanel.TabIndex = 0;
this.flowPanel.WrapContents = false;
//
@@ -222,7 +226,7 @@
this.checkKeepLikeFollowDialogsOpen.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkKeepLikeFollowDialogsOpen.Name = "checkKeepLikeFollowDialogsOpen";
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.UseVisualStyleBackColor = true;
//
@@ -234,7 +238,7 @@
this.labelBrowserSettings.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelBrowserSettings.Name = "labelBrowserSettings";
this.labelBrowserSettings.Size = new System.Drawing.Size(130, 20);
this.labelBrowserSettings.TabIndex = 7;
this.labelBrowserSettings.TabIndex = 6;
this.labelBrowserSettings.Text = "Browser Settings";
//
// checkSmoothScrolling
@@ -244,7 +248,7 @@
this.checkSmoothScrolling.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkSmoothScrolling.Name = "checkSmoothScrolling";
this.checkSmoothScrolling.Size = new System.Drawing.Size(105, 17);
this.checkSmoothScrolling.TabIndex = 8;
this.checkSmoothScrolling.TabIndex = 7;
this.checkSmoothScrolling.Text = "Smooth Scrolling";
this.checkSmoothScrolling.UseVisualStyleBackColor = true;
//
@@ -255,7 +259,7 @@
this.labelBrowserPath.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelBrowserPath.Name = "labelBrowserPath";
this.labelBrowserPath.Size = new System.Drawing.Size(95, 13);
this.labelBrowserPath.TabIndex = 9;
this.labelBrowserPath.TabIndex = 8;
this.labelBrowserPath.Text = "Open Links With...";
//
// comboBoxBrowserPath
@@ -266,7 +270,27 @@
this.comboBoxBrowserPath.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.comboBoxBrowserPath.Name = "comboBoxBrowserPath";
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
//
@@ -274,7 +298,7 @@
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.flowPanel);
this.Name = "TabSettingsGeneral";
this.Size = new System.Drawing.Size(340, 433);
this.Size = new System.Drawing.Size(340, 480);
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit();
this.panelZoom.ResumeLayout(false);
this.flowPanel.ResumeLayout(false);
@@ -305,5 +329,7 @@
private System.Windows.Forms.ComboBox comboBoxBrowserPath;
private System.Windows.Forms.Label labelBrowserSettings;
private System.Windows.Forms.CheckBox checkSmoothScrolling;
private System.Windows.Forms.Label labelSearchEngine;
private System.Windows.Forms.ComboBox comboBoxSearchEngine;
}
}

View File

@@ -4,23 +4,29 @@ using System.Linq;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils;
using TweetDuck.Updates;
using TweetDuck.Updates.Events;
namespace TweetDuck.Core.Other.Settings{
sealed partial class TabSettingsGeneral : BaseTabSettings{
private readonly FormBrowser browser;
private readonly Action reloadColumns;
private readonly UpdateHandler updates;
private int updateCheckEventId = -1;
private readonly int browserListIndexDefault;
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();
this.browser = browser;
this.reloadColumns = reloadColumns;
this.updates = updates;
this.updates.CheckFinished += updates_CheckFinished;
Disposed += (sender, args) => this.updates.CheckFinished -= updates_CheckFinished;
@@ -55,6 +61,14 @@ namespace TweetDuck.Core.Other.Settings{
browserListIndexCustom = comboBoxBrowserPath.Items.Add("(custom program...)");
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);
labelZoomValue.Text = trackBarZoom.Value+"%";
@@ -70,6 +84,7 @@ namespace TweetDuck.Core.Other.Settings{
checkSmoothScrolling.CheckedChanged += checkSmoothScrolling_CheckedChanged;
comboBoxBrowserPath.SelectedIndexChanged += comboBoxBrowserPath_SelectedIndexChanged;
comboBoxSearchEngine.SelectedIndexChanged += comboBoxSearchEngine_SelectedIndexChanged;
trackBarZoom.ValueChanged += trackBarZoom_ValueChanged;
checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged;
@@ -98,7 +113,7 @@ namespace TweetDuck.Core.Other.Settings{
private void checkAnimatedAvatars_CheckedChanged(object sender, EventArgs e){
Config.EnableAnimatedImages = checkAnimatedAvatars.Checked;
BrowserProcessHandler.UpdatePrefs().ContinueWith(task => browser.ReloadColumns());
BrowserProcessHandler.UpdatePrefs().ContinueWith(task => reloadColumns());
}
private void checkSmoothScrolling_CheckedChanged(object sender, EventArgs e){
@@ -134,18 +149,50 @@ namespace TweetDuck.Core.Other.Settings{
if (dialog.ShowDialog() == DialogResult.OK){
Config.BrowserPath = dialog.FileName;
}
else{
comboBoxBrowserPath.SelectedIndexChanged -= comboBoxBrowserPath_SelectedIndexChanged;
UpdateBrowserPathSelection();
comboBoxBrowserPath.SelectedIndexChanged += comboBoxBrowserPath_SelectedIndexChanged;
}
}
comboBoxBrowserPath.SelectedIndexChanged -= comboBoxBrowserPath_SelectedIndexChanged;
UpdateBrowserPathSelection();
comboBoxBrowserPath.SelectedIndexChanged += comboBoxBrowserPath_SelectedIndexChanged;
}
else{
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){
if (trackBarZoom.AlignValueToTick()){
zoomUpdateTimer.Stop();
@@ -170,14 +217,18 @@ namespace TweetDuck.Core.Other.Settings{
}
}
private void updates_CheckFinished(object sender, UpdateEventArgs e){
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
this.InvokeAsyncSafe(() => {
if (e.EventId == updateCheckEventId){
btnCheckUpdates.Enabled = true;
if (!e.IsUpdateAvailable){
FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK);
}
e.Result.Handle(update => {
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);
});
}
});
}
@@ -186,5 +237,19 @@ namespace TweetDuck.Core.Other.Settings{
Config.ZoomLevel = trackBarZoom.Value;
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){
Config.SpellCheckLanguage = (comboBoxSpellCheckLanguage.SelectedItem as LocaleUtils.Item)?.Code;
Config.SpellCheckLanguage = (comboBoxSpellCheckLanguage.SelectedItem as LocaleUtils.Item)?.Code ?? "en-US";
PromptRestart();
}
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.Drawing;
using System.Text;
using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;
@@ -8,6 +9,7 @@ using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Other.Interfaces;
using TweetDuck.Core.Utils;
using TweetDuck.Resources;
@@ -130,7 +132,7 @@ namespace TweetDuck.Core{
}
if (TwitterUtils.IsTwitterWebsite(frame)){
ScriptLoader.ExecuteFile(frame, "twitter.js");
ScriptLoader.ExecuteFile(frame, "twitter.js", browser);
}
frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride);
@@ -143,7 +145,9 @@ namespace TweetDuck.Core{
if (frame.IsMain && TwitterUtils.IsTweetDeckWebsite(frame)){
UpdateProperties();
TweetDeckBridge.RestoreSessionData(frame);
ScriptLoader.ExecuteFile(frame, "code.js");
ScriptLoader.ExecuteFile(frame, "code.js", browser);
ScriptLoader.ExecuteFile(frame, "update.js", browser);
InjectBrowserCSS();
ReinjectCustomCSS(Program.UserConfig.CustomBrowserCSS);
UserConfig_SoundNotificationInfoChanged(null, EventArgs.Empty);
@@ -151,7 +155,7 @@ namespace TweetDuck.Core{
TweetDeckBridge.ResetStaticProperties();
if (Program.UserConfig.FirstRun){
ScriptLoader.ExecuteFile(frame, "introduction.js");
ScriptLoader.ExecuteFile(frame, "introduction.js", browser);
}
}
}
@@ -211,7 +215,7 @@ namespace TweetDuck.Core{
}
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){
@@ -226,6 +230,10 @@ namespace TweetDuck.Core{
browser.ExecuteScriptAsync("TDGF_showTweetDetail", columnId, chirpId, fallbackUrl);
}
public void AddSearchColumn(string query){
browser.ExecuteScriptAsync("TDGF_performSearch", query);
}
public void TriggerTweetScreenshot(){
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
}
@@ -241,5 +249,9 @@ namespace TweetDuck.Core{
public void 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){
string file = Path.GetFileName(new Uri(url).AbsolutePath);
return string.IsNullOrEmpty(file) ? null : 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){
Dictionary<K2, V> innerDict;
return dict.TryGetValue(outerKey, out innerDict) && innerDict.ContainsKey(innerKey);
return dict.TryGetValue(outerKey, out Dictionary<K2, V> innerDict) && innerDict.ContainsKey(innerKey);
}
public int Count(){

View File

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

View File

@@ -1,5 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace TweetDuck.Plugins.Enums{
[Flags]
@@ -45,5 +48,50 @@ namespace TweetDuck.Plugins.Enums{
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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using TweetDuck.Plugins.Enums;
namespace TweetDuck.Plugins{
sealed class Plugin{
private const string VersionWildcard = "*";
private static readonly Version AppVersion = new Version(Program.VersionTag);
public string Identifier { get; }
public PluginGroup Group { get; }
public PluginEnvironment Environments { get; }
public string Name => metadata["NAME"];
public string Description => metadata["DESCRIPTION"];
public string Author => metadata["AUTHOR"];
public string Version => metadata["VERSION"];
public string Website => metadata["WEBSITE"];
public string ConfigFile => metadata["CONFIGFILE"];
public string ConfigDefault => metadata["CONFIGDEFAULT"];
public string RequiredVersion => metadata["REQUIRES"];
public string Name { get; }
public string Description { get; }
public string Author { get; }
public string Version { get; }
public string Website { get; }
public string ConfigFile { get; }
public string ConfigDefault { get; }
public Version RequiredVersion { get; }
public bool CanRun { get; private set; }
public bool CanRun { get; }
public bool HasConfig{
get => ConfigFile.Length > 0 && GetFullPathIfSafe(PluginFolder.Data, ConfigFile).Length > 0;
@@ -42,41 +40,25 @@ namespace TweetDuck.Plugins{
private readonly string pathRoot;
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){
this.pathRoot = path;
this.pathData = Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name);
private Plugin(PluginGroup group, string identifier, string pathRoot, string pathData, Builder builder){
this.pathRoot = pathRoot;
this.pathData = pathData;
this.Identifier = group.GetIdentifierPrefix()+name;
this.Group = group;
this.Environments = environments;
}
this.Identifier = identifier;
this.Environments = builder.Environments;
private void OnMetadataLoaded(){
CanRun = CheckRequiredVersion(RequiredVersion);
this.Name = builder.Name;
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;
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);
}
}
this.CanRun = AppVersion >= RequiredVersion;
}
public string GetScriptPath(PluginEnvironment environment){
@@ -132,78 +114,94 @@ namespace TweetDuck.Plugins{
return obj is Plugin plugin && plugin.Identifier.Equals(Identifier);
}
// Static
private static readonly Version AppVersion = new Version(Program.VersionTag);
private static readonly string[] EndTag = { "[END]" };
// Builder
public static Plugin CreateFromFolder(string path, PluginGroup group){
Plugin plugin = new Plugin(path, Path.GetFileName(path), group, LoadEnvironments(path));
LoadMetadata(path, plugin);
return plugin;
}
public sealed class Builder{
private static readonly Version DefaultRequiredVersion = new Version(0, 0, 0, 0);
private static PluginEnvironment LoadEnvironments(string path){
PluginEnvironment environments = PluginEnvironment.None;
public string Name { get; private set; }
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)){
environments |= PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal));
public PluginEnvironment Environments { get; private set; } = PluginEnvironment.None;
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){
throw new ArgumentException("Plugin has no script files");
}
return environments;
}
private static void LoadMetadata(string path, Plugin plugin){
string metaFile = Path.Combine(path, ".meta");
if (!File.Exists(metaFile)){
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);
}
public void SetFromTag(string tag, string value){
switch(tag){
case "NAME": this.Name = value; break;
case "DESCRIPTION": this.Description = value; break;
case "AUTHOR": this.Author = value; break;
case "VERSION": this.Version = value; break;
case "WEBSITE": this.Website = value; break;
case "CONFIGFILE": this.ConfigFile = value; break;
case "CONFIGDEFAULT": this.ConfigDefault = value; break;
case "REQUIRES": SetRequiredVersion(value); break;
default: throw new FormatException("Invalid metadata tag: "+tag);
}
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{
throw new FormatException("Missing metadata tag before value: "+line);
throw new FormatException("Plugin contains invalid minimum version: "+versionStr);
}
}
if (plugin.Name.Length == 0){
throw new FormatException("Plugin is missing a name in the .meta file");
public Plugin BuildAndSetup(){
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.IO;
using System.Linq;
using TweetDuck.Core;
using TweetDuck.Core.Other.Interfaces;
using TweetDuck.Data;
using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events;
@@ -12,11 +12,15 @@ using TweetDuck.Resources;
namespace TweetDuck.Plugins{
sealed class PluginManager{
private static readonly Dictionary<PluginEnvironment, string> PluginSetupScripts = new Dictionary<PluginEnvironment, string>(4){
{ PluginEnvironment.None, ScriptLoader.LoadResource("plugins.js") },
{ PluginEnvironment.Browser, ScriptLoader.LoadResource("plugins.browser.js") },
{ PluginEnvironment.Notification, ScriptLoader.LoadResource("plugins.notification.js") }
};
private static IReadOnlyDictionary<PluginEnvironment, string> LoadSetupScripts(){
return PluginEnvironmentExtensions.Map(
ScriptLoader.LoadResource("plugins.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 PathCustomPlugins => Path.Combine(rootPath, "user");
@@ -132,7 +136,7 @@ namespace TweetDuck.Plugins{
Plugin plugin;
try{
plugin = Plugin.CreateFromFolder(fullDir, group);
plugin = PluginLoader.FromFolder(fullDir, group);
}catch(Exception e){
loadErrors.Add(group.GetIdentifierPrefix()+Path.GetFileName(fullDir)+": "+e.Message);
continue;

View File

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

Binary file not shown.

Binary file not shown.

View File

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

View File

@@ -12,7 +12,7 @@ enabled(){
};
var resetColumn = (columnName) => {
var col = TD.controller.columnManager.get(columnName);
let col = TD.controller.columnManager.get(columnName);
col.model.setClearedTimestamp(0);
col.reloadTweets();
};
@@ -38,7 +38,7 @@ enabled(){
$(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 +81,7 @@ enabled(){
// add column buttons and keyboard shortcut info to UI
replaceMustache("column/column_header.mustache", "</header>", [
'{{^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>',
'</a>',
'{{/isTemporary}}',
@@ -98,11 +98,15 @@ enabled(){
// load custom style
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-type-message .column-title { margin-right: 115px !important; }");
css.insert(".mark-all-read-link { right: 59px !important; }");
css.insert(".open-compose-dm-link { right: 90px !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(){
@@ -113,18 +117,22 @@ ready(){
$(document).on("keyup", this.eventKeyUp);
// add clear all button
$("nav.app-navigator").first().append([
'<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">',
'<div class="obj-left margin-l--2"><i class="icon icon-medium icon-clear-timeline"></i></div>',
'<div id="clear-columns-btn-all" class="nbfc padding-ts hide-condensed txt-size--16">Clear columns</div>',
'</a></nav>'
].join(""));
const generateButton = (idParent, idButton) => {
return `
<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 class="obj-left margin-l--2"><i class="icon icon-medium icon-clear-timeline"></i></div>
<div id="${idButton}" class="nbfc padding-ts hide-condensed txt-size--16 app-nav-link-text">Clear columns</div>
</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
var tooltipEvents = $._data($(".js-header-action")[0]).events;
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
[version]
1.2.4
1.2.5
[website]
https://tweetduck.chylex.com

View File

@@ -610,7 +610,7 @@ ${this.config.revertIcons ? `
${currentTheme === "black" ? `
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 }
` : ``}

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 .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-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-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-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-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-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-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-negative:hover{background-color:#A01744}
html.dark .is-inverted-dark .btn-negative:focus{background-color:#A01744}
html.dark .is-inverted-dark .btn-negative:active,html.dark .is-inverted-dark .btn-negative.is-selected{background-color:#A01744}
html.dark .is-inverted-dark .btn-tertiary:hover{background-color:#F5F8FA}
html.dark .is-inverted-dark .btn-tertiary:focus{background-color:#F5F8FA}
html.dark .is-inverted-dark .btn-tertiary:active,html.dark .is-inverted-dark .btn-tertiary.is-selected{background-color:#F5F8FA}
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-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 .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: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: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-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: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: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 .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:hover,html.dark .s-following .follow-btn:hover{color:#fff;background-color:#005FD1;border:1px solid #005FD1}
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-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 [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 .btn-fav.s-favorited:hover,html.dark .s-following .follow-btn:hover{color:#fff;background-color:#A01744;border:#A01744}
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-on-blue{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: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-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: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-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[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 .input-group-button{border:1px solid #e1e8ed}
html.dark .account-profile-header{background-color:#1DA1F2}
html.dark .account-settings-bt{border-top: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 .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 .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 .btn-neutral,html.dark .inline-reply .character-count{color:#fff}
html.dark .reply-triangle{border-color:transparent transparent #485865}

View File

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

View File

@@ -59,7 +59,19 @@ Remove unsupported emoji:
> remove copyright
> remove registered 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
> remove eye in speech bubble

View File

@@ -1354,18 +1354,7 @@
2755;white exclamation mark
2757;exclamation mark
3030;wavy dash
0023 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
1F4AF;hundred points
1F520;input latin uppercase

View File

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

View File

@@ -12,10 +12,16 @@ enabled(){
if (configuration.useAdvancedSelector){
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");
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");
@@ -34,8 +40,8 @@ enabled(){
columnAccount = "";
}
try{
query = configuration.customSelector(column.getColumnType(), columnTitle, columnAccount, column, section.hasClass("column-temp"));
try{ // TODO isOfType is removed
query = configuration.customSelector(columnTitle, columnAccount, column, section.hasClass("column-temp"));
}catch(e){
$TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector threw an error: "+e.message);
return;

View File

@@ -30,14 +30,19 @@
* 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
* unused and have misleading names (for example, Home columns are 'col_timeline' instead of 'col_home'):
* col_timeline, col_interactions, col_mentions, col_followers, col_search, col_list,
* col_customtimeline, col_messages, col_usertweets, col_favorites, col_activity,
* col_dataminr, col_home, col_me, col_inbox, col_scheduled, col_unknown
* In order to check the column type, use the 'column.isOfType' function. It is recommended to always put it
* last in an 'if' statement, because it is much more demanding than checking the title/account.
*
* Here is the full list of column types, note that some are unused and have misleading names.
* (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:
* 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'),
@@ -61,16 +66,16 @@
useAdvancedSelector: false,
customSelector: function(type, title, account, column, isTemporary){
customSelector: function(title, account, column, isTemporary){
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,
// search columns are normally linked to the preferred account
// so this forces the @TryTweetDuck account to be used instead
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,
// but I want to reply to tweets from my official account
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"
Set-Location $dir
function Check-Carriage-Return{
Param([Parameter(Mandatory = $True, Position = 1)] $fname)
try{
Write-Host "------------------------------"
$file = @(Get-ChildItem -Include $fname -Recurse)[0]
if ((Get-Content -Path $file.FullName -Raw).Contains("`r")){
Throw "$fname cannot contain carriage return"
if ($version.Equals("")){
$version = (Get-Item (Join-Path $targetDir "TweetDuck.exe")).VersionInfo.FileVersion
}
Write-Host "Verified" $file.FullName.Substring($dir.Length)
}
function Rewrite-File{
[CmdletBinding()]
Param([Parameter(Mandatory = $True, ValueFromPipeline = $True)][array] $lines, [Parameter(Mandatory = $True, Position = 1)] $file)
Write-Host "TweetDuck version" $version
Write-Host "------------------------------"
# Cleanup
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")
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 = $lines | % { $_.TrimStart() }
$lines = $lines -Replace '^(.*?)((?<=^|[;{}()])\s?//(?:\s.*|$))?$', '$1'
@@ -34,7 +79,7 @@ try{
,$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 = $lines -Replace '\s*/\*.*?\*/', ''
$lines = $lines -Replace '^\s+(.+):\s?(.+?)(?:\s?(!important))?;$', '$1:$2$3;'
@@ -42,10 +87,12 @@ try{
@(($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 | % { $_.TrimStart() }) | Rewrite-File $file
}
Write-Host "------------------------------"
}catch{
Write-Host "Encountered an error while running PostBuild.ps1 on line" $_.InvocationInfo.ScriptLineNumber
Write-Host $_

View File

@@ -2,36 +2,155 @@
using System;
using System.IO;
using System.Text;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other;
#if DEBUG
using System.Diagnostics;
using System.Reflection;
using TweetDuck.Core;
using TweetDuck.Plugins;
#endif
namespace TweetDuck.Resources{
static class ScriptLoader{
private const string UrlPrefix = "td:";
public static string LoadResource(string name, bool silent = false){
public static string LoadResource(string name, bool silent = false, Control sync = null){
try{
return File.ReadAllText(Path.Combine(Program.ScriptPath, name), Encoding.UTF8);
}catch(Exception ex){
if (!silent){
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);
string path = Program.ScriptPath;
#if DEBUG
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;
}
}
public static void ExecuteFile(IFrame frame, string file){
ExecuteScript(frame, LoadResource(file), GetRootIdentifier(file));
public static bool ExecuteFile(IFrame frame, string file, Control sync = null){
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){
if (script != null){
frame.ExecuteJavaScriptAsync(script, UrlPrefix+identifier, 1);
frame.ExecuteJavaScriptAsync(script, "td:"+identifier, 1);
}
}
public static string GetRootIdentifier(string 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

@@ -25,26 +25,23 @@
const app = $(document.body).children(".js-app");
//
// Constant: Column types mapped to their titles.
// Constant: Column icon classes mapped to their titles.
//
const columnTypes = {
"col_home": "Home",
"col_timeline" : "Home",
"col_mentions": "Mentions",
"col_me": "Mentions",
"col_inbox": "Messages",
"col_messages": "Messages",
"col_interactions": "Notifications",
"col_followers": "Followers",
"col_activity": "Activity",
"col_favorites": "Likes",
"col_usertweets": "User",
"col_search": "Search",
"col_list": "List",
"col_customtimeline": "Timeline",
"col_dataminr": "Dataminr",
"col_livevideo": "Live video",
"col_scheduled": "Scheduled"
const columnTitles = {
"icon-home": "Home",
"icon-mention": "Mentions",
"icon-message": "Messages",
"icon-notifications": "Notifications",
"icon-follow": "Followers",
"icon-activity": "Activity",
"icon-favorite": "Likes",
"icon-user": "User",
"icon-search": "Search",
"icon-list": "List",
"icon-custom-timeline": "Timeline",
"icon-dataminr": "Dataminr",
"icon-play-video": "Live video",
"icon-schedule": "Scheduled"
};
//
@@ -98,6 +95,23 @@
return value;
};
//
// Function: Attempts to retrieve the column icon class. Returns undefined on failure.
//
const getColumnIconClass = function(column){
if (ensurePropertyExists(column, "ui", "_$chirpContainer")){
return column.ui._$chirpContainer.closest(".js-column").attr("data-td-icon");
}
};
//
// Function: Retrieves column name and caches it.
//
const getColumnName = function(column){
let cached = column._tduck_icon || (column._tduck_icon = getColumnIconClass(column));
return columnTitles[cached] || "";
};
//
// Function: Event callback for a new tweet.
//
@@ -220,7 +234,7 @@
let tweetUrl = source ? source.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()){
@@ -280,10 +294,12 @@
// Block: Hook into settings object to detect when the settings change, and update html attributes and notification layout.
//
(function(){
const refreshSettings = function(){
const refreshSettings = function(){
let fontSizeName = TD.settings.getFontSize();
let themeName = TD.settings.getTheme();
let columnBackground = getClassStyleProperty("column", "background-color");
let tags = [
"<html "+Array.prototype.map.call(document.documentElement.attributes, ele => `${ele.name}="${ele.value}"`).join(" ")+"><head>"
];
@@ -292,23 +308,7 @@
tags.push($(this)[0].outerHTML);
});
tags.push("<style type='text/css'>");
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>");
tags.push("<style type='text/css'>body { background: "+columnBackground+" !important }</style>");
doc.setAttribute("data-td-font", fontSizeName);
doc.setAttribute("data-td-theme", themeName);
@@ -656,85 +656,52 @@
// Block: Screenshot tweet to clipboard.
//
(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(){
if (selectedTweet){
let tweetWidth = Math.floor(selectedTweet.width());
let parent = selectedTweet.parent();
return if !highlightedTweetObj || !highlightedColumnObj;
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");
let isReply = !isDetail && (parent.hasClass("js-replies-to") || parent.hasClass("js-replies-before"));
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
if (ele.length){
ele[0].style.setProperty("margin-bottom", "2px", "important");
break;
}
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);
};
})();
@@ -1191,11 +1158,35 @@
}
}
$(".js-app-search-input").val("");
$(".js-perform-search").blur();
if (!("tweetduck" in data)){
$(".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.
//
@@ -1333,6 +1324,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.
//
@@ -1392,6 +1388,8 @@
window.gc && window.gc();
window.location.reload();
});
window.TDGF_reload = function(){}; // redefine to prevent reloading multiple times
};
if (window.TD_SESSION && window.TD_SESSION.gc){

View File

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

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;
}
.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;
}
@@ -195,6 +195,11 @@ a[data-full-url] {
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 */
/***************************************/
@@ -256,11 +261,6 @@ html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
background-position: -223px -97px !important;
}
.accs-header {
/* fix retweet account selector heading */
padding-left: 0 !important;
}
.keyboard-shortcut-list {
/* fix keyboard navigation alignment */
vertical-align: top !important;
@@ -271,12 +271,6 @@ html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
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 {
/* the custom timeline input shadow is behaving super weird when focused */
box-shadow: none !important;
@@ -356,18 +350,18 @@ html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
/* 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%;
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%;
margin-left: 4px !important;
padding-top: 1px;
}
.column-type-message.is-shifted-1 .username {
[data-td-icon="icon-message"].is-shifted-1 .username {
vertical-align: bottom;
}

View File

@@ -2,10 +2,22 @@
/* General */
/***********/
body {
body.td-notification {
overflow-y: auto !important;
}
body::before {
content: none !important;
}
.column {
background: transparent !important;
}
/***********************/
/* Redesign scrollbars */
/***********************/
.scroll-styled-v::-webkit-scrollbar {
width: 7px !important;
}
@@ -18,9 +30,41 @@ body {
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 {
vertical-align: 10% !important;
@@ -30,10 +74,22 @@ body {
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 {
position: absolute;
left: 21px;
@@ -55,13 +111,13 @@ body {
/* Media */
/*********/
.media-size-medium {
.td-notification .media-size-medium {
max-height: 240px;
height: calc(100vh - 20px) !important;
border-radius: 1px !important;
}
.js-quote-detail .media-size-medium {
.td-notification .js-quote-detail .media-size-medium {
height: calc(100vh - 28px) !important;
}

View File

@@ -1,33 +1,9 @@
(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.
//
var displayNotification = function(version, download, changelog){
var displayNotification = function(version, changelog){
// styles
var css = $("#tweetduck-update-css");
@@ -167,7 +143,7 @@
<div id='tweetduck-changelog'>
<div id='tweetduck-changelog-box'>
<h2>TweetDuck Update ${version}</h2>
${changelog}
${markdown(atob(changelog))}
</div>
</div>
`).appendTo(document.body).css("display", "none");
@@ -219,17 +195,11 @@
buttonDiv.children(".tdu-btn-download").click(function(){
hide();
if (download){
$TDU.onUpdateAccepted();
}
else{
$TDU.openBrowser(updateDownloadFallback);
}
$TDU.onUpdateAccepted();
});
buttonDiv.children(".tdu-btn-later").click(function(){
clearTimeout(updateCheckTimeoutID);
$TDU.onUpdateDelayed();
slide();
});
@@ -245,15 +215,6 @@
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.
//
@@ -273,33 +234,6 @@
.replace(/\n\r?\n\r?/g, "<br>");
};
//
// Function: Runs an update check and updates all DOM elements appropriately.
//
var runUpdateCheck = function(eventID, versionTag, dismissedVersionTag, allowPre){
clearTimeout(updateCheckTimeoutID);
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: Check updates on startup.
//
@@ -310,5 +244,5 @@
//
// Block: Setup global functions.
//
window.TDUF_runUpdateCheck = runUpdateCheck;
window.TDUF_displayNotification = displayNotification;
})($, $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>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>7</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Management" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
@@ -107,7 +109,7 @@
<Compile Include="Core\Handling\RequestHandlerBase.cs" />
<Compile Include="Core\Handling\RequestHandlerBrowser.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">
<SubType>Form</SubType>
@@ -170,6 +172,12 @@
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsAnalytics.Designer.cs">
<DependentUpon>DialogSettingsAnalytics.cs</DependentUpon>
</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">
<SubType>Form</SubType>
</Compile>
@@ -245,13 +253,14 @@
<Compile Include="Core\Other\Settings\TabSettingsNotifications.Designer.cs">
<DependentUpon>TabSettingsNotifications.cs</DependentUpon>
</Compile>
<Compile Include="Core\Bridge\CallbackBridge.cs" />
<Compile Include="Core\Notification\Screenshot\ScreenshotBridge.cs" />
<Compile Include="Data\CommandLineArgs.cs" />
<Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
<Compile Include="Data\ResourceLink.cs" />
<Compile Include="Data\Result.cs" />
<Compile Include="Data\Serialization\FileSerializer.cs" />
<Compile Include="Data\InjectedHTML.cs" />
<Compile Include="Data\Serialization\ITypeConverter.cs" />
@@ -283,6 +292,7 @@
<Compile Include="Plugins\Enums\PluginEnvironment.cs" />
<Compile Include="Plugins\Enums\PluginGroup.cs" />
<Compile Include="Plugins\Events\PluginErrorEventArgs.cs" />
<Compile Include="Plugins\PluginLoader.cs" />
<Compile Include="Plugins\PluginManager.cs" />
<Compile Include="Plugins\PluginScriptGenerator.cs" />
<Compile Include="Properties\Resources.Designer.cs">
@@ -291,6 +301,7 @@
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Reporter.cs" />
<Compile Include="Updates\Events\UpdateCheckEventArgs.cs" />
<Compile Include="Updates\FormUpdateDownload.cs">
<SubType>Form</SubType>
</Compile>
@@ -306,13 +317,14 @@
<Compile Include="Core\Management\BrowserCache.cs" />
<Compile Include="Core\Utils\BrowserUtils.cs" />
<Compile Include="Core\Utils\NativeMethods.cs" />
<Compile Include="Updates\UpdateCheckClient.cs" />
<Compile Include="Updates\UpdateDownloadStatus.cs" />
<Compile Include="Updates\UpdateHandler.cs" />
<Compile Include="Updates\UpdateInfo.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Resources\ScriptLoader.cs" />
<Compile Include="Updates\UpdateEventArgs.cs" />
<Compile Include="Updates\Events\UpdateEventArgs.cs" />
<Compile Include="Updates\UpdaterSettings.cs" />
</ItemGroup>
<ItemGroup>
@@ -395,37 +407,20 @@
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>xcopy "$(ProjectDir)LICENSE.md" "$(TargetDir)" /Y
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"
<PostBuildEvent>rmdir "$(ProjectDir)bin\Debug"
rmdir "$(ProjectDir)bin\Release"
attrib +R "$(TargetDir)locales\en-US.pak"
del /Q /A:-R "$(TargetDir)locales\*.pak"
attrib -R "$(TargetDir)locales\en-US.pak"
rmdir "$(TargetDir)scripts" /S /Q
rmdir "$(TargetDir)plugins" /S /Q
powershell -ExecutionPolicy Unrestricted -File "$(ProjectDir)Resources\PostBuild.ps1" "$(TargetDir)\" "$(ProjectDir)\"
if $(ConfigurationName) == Debug (
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
mkdir "$(TargetDir)plugins\user\.debug"
xcopy "$(ProjectDir)Resources\Plugins\.debug\*" "$(TargetDir)plugins\user\.debug\" /E /Y
)
powershell -ExecutionPolicy Unrestricted -File "$(ProjectDir)Resources\PostBuild.ps1" "$(TargetDir)\"</PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
<Target Name="AfterBuild" Condition="$(ConfigurationName) == Release">
<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.Windows.Forms;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils;
namespace TweetDuck.Updates{
sealed partial class FormUpdateDownload : Form{
@@ -22,21 +20,11 @@ namespace TweetDuck.Updates{
}
private void timerDownloadCheck_Tick(object sender, EventArgs e){
if (updateInfo.DownloadStatus == UpdateDownloadStatus.Done){
if (updateInfo.DownloadStatus.IsFinished()){
timerDownloadCheck.Stop();
DialogResult = DialogResult.OK;
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{
None = 0,
InProgress,
Done,
Failed
Canceled,
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,36 +1,67 @@
using CefSharp;
using System;
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using TweetDuck.Core;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
using TweetDuck.Resources;
using TweetDuck.Core.Other.Interfaces;
using TweetDuck.Data;
using TweetDuck.Updates.Events;
namespace TweetDuck.Updates{
sealed class UpdateHandler{
sealed class UpdateHandler : IDisposable{
public const int CheckCodeUpdatesDisabled = -1;
public const int CheckCodeNotOnTweetDeck = -2;
private readonly ITweetDeckBrowser browser;
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> UpdateDelayed;
public event EventHandler<UpdateEventArgs> UpdateDismissed;
public event EventHandler<UpdateEventArgs> CheckFinished;
public event EventHandler<UpdateCheckEventArgs> CheckFinished;
private ushort lastEventId;
private UpdateInfo lastUpdateInfo;
public UpdateHandler(ITweetDeckBrowser browser, UpdaterSettings settings){
this.browser = browser;
this.settings = settings;
this.client = new UpdateCheckClient(settings);
this.browser = browser;
this.browser.RegisterBridge("$TDU", new Bridge(this));
browser.OnFrameLoaded(OnFrameLoaded);
browser.RegisterBridge("$TDU", new Bridge(this));
this.timer = new Timer();
this.timer.Tick += timer_Tick;
}
private void OnFrameLoaded(IFrame frame){
ScriptLoader.ExecuteFile(frame, "update.js");
public void Dispose(){
timer.Dispose();
}
private void timer_Tick(object sender, EventArgs e){
timer.Stop();
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){
@@ -42,17 +73,24 @@ namespace TweetDuck.Updates{
if (!browser.IsTweetDeckWebsite){
return CheckCodeNotOnTweetDeck;
}
int nextEventId = unchecked(++lastEventId);
Task<UpdateInfo> checkTask = client.Check();
browser.ExecuteFunction("TDUF_runUpdateCheck", (int)unchecked(++lastEventId), Program.VersionTag, settings.DismissedUpdate ?? string.Empty, settings.AllowPreReleases);
return lastEventId;
checkTask.ContinueWith(task => HandleUpdateCheckSuccessful(nextEventId, task.Result), TaskContinuationOptions.OnlyOnRanToCompletion);
checkTask.ContinueWith(task => HandleUpdateCheckFailed(nextEventId, task.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted);
return nextEventId;
}
return CheckCodeUpdatesDisabled;
}
public void BeginUpdateDownload(Form ownerForm, UpdateInfo updateInfo, Action<UpdateInfo> onSuccess){
if (updateInfo.DownloadStatus == UpdateDownloadStatus.Done){
onSuccess(updateInfo);
public void BeginUpdateDownload(Form ownerForm, UpdateInfo updateInfo, Action<UpdateInfo> onFinished){
UpdateDownloadStatus status = updateInfo.DownloadStatus;
if (status == UpdateDownloadStatus.Done || status == UpdateDownloadStatus.AssetMissing){
onFinished(updateInfo);
}
else{
FormUpdateDownload downloadForm = new FormUpdateDownload(updateInfo);
@@ -63,14 +101,12 @@ namespace TweetDuck.Updates{
};
downloadForm.FormClosed += (sender, args) => {
if (downloadForm.DialogResult != DialogResult.OK){
updateInfo.CancelDownload();
}
downloadForm.Dispose();
if (downloadForm.DialogResult == DialogResult.OK){ // success or manual download
onSuccess(updateInfo);
}
else{
ownerForm.Show();
}
onFinished(updateInfo);
};
downloadForm.Show();
@@ -84,17 +120,39 @@ namespace TweetDuck.Updates{
}
}
private void TriggerUpdateAcceptedEvent(UpdateEventArgs args){
UpdateAccepted?.Invoke(this, args);
private void HandleUpdateCheckSuccessful(int eventId, UpdateInfo info){
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){
settings.DismissedUpdate = args.UpdateInfo.VersionTag;
UpdateDismissed?.Invoke(this, args);
private void HandleUpdateCheckFailed(int eventId, Exception exception){
CheckFinished?.Invoke(this, new UpdateCheckEventArgs(eventId, new Result<UpdateInfo>(exception)));
}
private void TriggerCheckFinishedEvent(UpdateEventArgs args){
CheckFinished?.Invoke(this, args);
private void TriggerUpdateAcceptedEvent(){
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{
@@ -108,31 +166,16 @@ namespace TweetDuck.Updates{
owner.Check(false);
}
public void OnUpdateCheckFinished(int eventId, string versionTag, string downloadUrl){
if (versionTag != null && (owner.lastUpdateInfo == null || owner.lastUpdateInfo.VersionTag != versionTag)){
owner.CleanupDownload();
owner.lastUpdateInfo = new UpdateInfo(owner.settings, eventId, versionTag, downloadUrl);
owner.lastUpdateInfo.BeginSilentDownload();
}
owner.TriggerCheckFinishedEvent(new UpdateEventArgs(eventId, owner.lastUpdateInfo));
public void OnUpdateAccepted(){
owner.TriggerUpdateAcceptedEvent();
}
public void OnUpdateAccepted(){
if (owner.lastUpdateInfo != null){
owner.TriggerUpdateAcceptedEvent(new UpdateEventArgs(owner.lastUpdateInfo));
}
public void OnUpdateDelayed(){
owner.TriggerUpdateDelayedEvent();
}
public void OnUpdateDismissed(){
if (owner.lastUpdateInfo != null){
owner.TriggerUpdateDismissedEvent(new UpdateEventArgs(owner.lastUpdateInfo));
owner.CleanupDownload();
}
}
public void OpenBrowser(string url){
BrowserUtils.OpenExternalBrowser(url);
owner.TriggerUpdateDismissedEvent();
}
}
}

View File

@@ -5,40 +5,43 @@ using TweetDuck.Core.Utils;
namespace TweetDuck.Updates{
sealed class UpdateInfo{
public int EventId { get; }
public string VersionTag { get; }
public string ReleaseNotes { get; }
public string InstallerPath { get; }
public bool IsUpdateNew => VersionTag != Program.VersionTag;
public bool IsUpdateDismissed => VersionTag == settings.DismissedUpdate;
public UpdateDownloadStatus DownloadStatus { get; private set; }
public Exception DownloadError { get; private set; }
private readonly string installerFolder;
private readonly UpdaterSettings settings;
private readonly string downloadUrl;
private WebClient currentDownload;
public UpdateInfo(UpdaterSettings settings, int eventId, string versionTag, string downloadUrl){
this.installerFolder = settings.InstallerDownloadFolder;
public UpdateInfo(UpdaterSettings settings, string versionTag, string releaseNotes, string downloadUrl){
this.settings = settings;
this.downloadUrl = downloadUrl;
this.EventId = eventId;
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(){
if (DownloadStatus == UpdateDownloadStatus.None || DownloadStatus == UpdateDownloadStatus.Failed){
DownloadStatus = UpdateDownloadStatus.InProgress;
try{
Directory.CreateDirectory(installerFolder);
}catch(Exception e){
DownloadError = e;
DownloadStatus = UpdateDownloadStatus.Failed;
if (string.IsNullOrEmpty(downloadUrl)){
DownloadError = new InvalidDataException("Missing installer asset.");
DownloadStatus = UpdateDownloadStatus.AssetMissing;
return;
}
if (string.IsNullOrEmpty(downloadUrl)){
DownloadError = new UriFormatException("Could not determine URL of the update installer");
try{
Directory.CreateDirectory(settings.InstallerDownloadFolder);
}catch(Exception e){
DownloadError = e;
DownloadStatus = UpdateDownloadStatus.Failed;
return;
}
@@ -68,5 +71,18 @@ namespace TweetDuck.Updates{
// 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{
sealed class UpdaterSettings{
public bool AllowPreReleases { get; set; }
public string InstallerDownloadFolder { get; }
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

@@ -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">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@@ -20,6 +20,7 @@
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<LangVersion>7</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
@@ -29,6 +30,7 @@
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<LangVersion>7</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />

View File

@@ -4,5 +4,4 @@
<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.WinForms" version="64.0.0-CI2508" targetFramework="net452" xmlns="" />
<package id="Microsoft.VC120.CRT.JetBrains" version="12.0.21005.2" targetFramework="net452" xmlns="" />
</packages>

View File

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

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">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -22,6 +22,7 @@
<PlatformTarget>x86</PlatformTarget>
<OutputPath>bin\x86\Debug\</OutputPath>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>7</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<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">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@@ -22,6 +22,7 @@
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
<LangVersion>7</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
@@ -33,6 +34,7 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
<GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
<LangVersion>7</LangVersion>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>Resources\icon.ico</ApplicationIcon>