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

Compare commits

...

79 Commits

Author SHA1 Message Date
f832e04e9e Remove unnecessary resx files and cleanup csproj 2017-08-10 15:09:15 +02:00
fc760b9a0c Fix another case in duplicate DM notifications 2017-08-10 13:46:13 +02:00
9addff0521 Exclude emoji-instructions.txt from build 2017-08-10 13:45:32 +02:00
dcaa3aab19 Work around duplicate DM notifications and rewrite recent tweet check 2017-08-10 00:51:38 +02:00
628785c68c Move _postbuild.bat to an MSBuild target directly in the project file 2017-08-10 00:20:58 +02:00
a5aa396fda Fix image quality setting not working in columns with large previews 2017-08-09 18:48:36 +02:00
f53a9f05e3 Fix image download filename for avatars and add more unit tests 2017-08-07 14:48:20 +02:00
7749b14156 Increment emoji keyboard plugin version 2017-08-06 20:32:13 +02:00
c15f339718 Fix emoji keyboard not disappearing after pressing ctrl+enter to tweet 2017-08-06 20:31:44 +02:00
775f590bfa Release 1.8.5 2017-08-06 15:58:28 +02:00
76408ea56f Increment verison of edit-design and emoji-keyboard plugins 2017-08-06 15:58:23 +02:00
a391d8ee83 Fix image pasting allowing more than 1 image in DMs 2017-08-05 21:52:38 +02:00
48c38f6e1d Include tweet author and quality in image download filename 2017-08-05 21:32:07 +02:00
37c5fba162 Change text color of sound notification file option for invalid paths 2017-08-05 19:50:30 +02:00
23e99b1d44 Update GC memory threshold defaults, also GC reload is enabled by default 2017-08-05 19:42:10 +02:00
8432240a47 Update HW acceleration & GC reload tooltips to note they won't be exported 2017-08-05 19:37:14 +02:00
a4bab743d6 Remove notification warning in GC reload option tooltip 2017-08-05 19:34:20 +02:00
60766789ab Move GC reload options to SystemConfig 2017-08-05 19:27:20 +02:00
ca014f881c Rewrite unknown property handling in FileSerializer 2017-08-05 19:23:42 +02:00
886eabe26c Show notifications that were missed during a browser reload 2017-08-05 18:43:57 +02:00
65b7167b5f Rewrite browser reload to save column notification state in session data 2017-08-05 18:36:17 +02:00
abbdde851e Make quoted tweets and RT account selectors square, fix RT account selector heading 2017-08-05 18:30:42 +02:00
54ac54aba6 Add session data that persists across browser reloads 2017-08-05 18:08:22 +02:00
184340f400 Increase delay for clearing recent notifications to prevent duplicates 2017-08-05 17:06:02 +02:00
93dd6813e8 Fix old icon alignment in 'Add column' dialog 2017-08-05 14:44:49 +02:00
b689b08711 Make follow notification button less visible when not hovered 2017-08-05 12:56:18 +02:00
1479a097d6 Fix alignment of old icons on buttons after TweetDeck update 2017-08-05 02:39:23 +02:00
e4967ea46d Add paragraphs and level 1-2 headings to update notification markdown renderer 2017-08-01 17:33:47 +02:00
3f28f18fb4 Release 1.8.4.1 2017-08-01 17:11:34 +02:00
1b90e0f65e Slightly increase default notification height 2017-08-01 17:05:31 +02:00
756ed649e6 Change default avatar shape to square, rename 'Default' to 'Legacy' 2017-08-01 17:03:29 +02:00
fbc423e2a7 Fix like/retweet notifications having invisible space with notification media previews disabled 2017-08-01 16:59:25 +02:00
f04cdb6a13 Fix PropertyBridge not updating properly 2017-08-01 16:58:46 +02:00
63b58b1cfe Release 1.8.4 2017-08-01 15:07:03 +02:00
77e656d8e4 Tweak JS prompt dialog layout on high DPI 2017-08-01 15:06:09 +02:00
a673957bd0 Tweak JS prompt dialog layout 2017-08-01 14:54:21 +02:00
c99a0c9974 Add Layout & Design plugin button to the TweetDeck settings modal 2017-08-01 13:45:44 +02:00
0fb06d0ff2 Remove reply revert option from edit-design plugin 2017-08-01 12:28:52 +02:00
c51eebfe22 Add new unit tests for TwitterUtils and CombinedFileStream 2017-07-31 22:27:02 +02:00
a51b34b48f Move CommandLineArgsParser code to CommandLineArgs 2017-07-31 22:26:48 +02:00
1b239bada1 Delay screenshots again due to iframes 2017-07-31 21:17:31 +02:00
50ab1a6ac3 Improve login/logout page design 2017-07-31 20:29:07 +02:00
f181f1fadc Refactor PropertyBridge 2017-07-31 19:58:23 +02:00
c686349922 Refactor Program (tweak properties, move locking code) 2017-07-31 18:04:04 +02:00
5f44a1f4ad Fix semicolons in code.js 2017-07-31 14:58:42 +02:00
a968938832 Move square scrollbars from edit-design plugin to code.js 2017-07-31 14:55:31 +02:00
8d67f3dfdc Move code.js notification setup and fix dropdown border radius 2017-07-31 14:42:26 +02:00
973ae8cb5d Move twitter account regex to TwitterUtils 2017-07-31 14:31:32 +02:00
a4747b0d7b Add JS dialog handler to notifications 2017-07-31 14:25:00 +02:00
f07640cc84 Reorganize CEF handlers 2017-07-31 14:24:42 +02:00
c235c55b19 Add option to show media previews in notification 2017-07-31 14:12:24 +02:00
485ef684be Prevent notification keyboard controls from triggering in dev tools 2017-07-31 13:36:44 +02:00
7caca22e57 Remove 'TweetDuck' from JS dialog captions 2017-07-31 01:42:22 +02:00
f1d9e32bf5 Add keyboard controls to notifications
Closes #153
2017-07-31 01:23:57 +02:00
23d5fa3107 Tweak emoji keyboard border radius and character count width 2017-07-30 23:58:35 +02:00
4e7d8aba1c Improve FormMessage to match MessageBox closer and look better on high DPI 2017-07-30 23:50:24 +02:00
98ba871a71 Fix back mouse button ignoring columns inside User modals
Closes #155
2017-07-30 21:38:38 +02:00
3ff23c0264 Remove unnecessary TweetDeck logo CSS rule 2017-07-30 21:29:02 +02:00
e21f89477b Fix ISerializedObject not being removed from unit tests and csproj file 2017-07-30 21:28:26 +02:00
f177f514f5 Fix column type icons jumping when opening column settings 2017-07-30 21:19:03 +02:00
af30f3b348 Square-ify many elements of TweetDeck (buttons, inputs, dialogs, menus, previews) 2017-07-30 21:15:39 +02:00
82df618429 Fix code.js after refactoring CSS insertion 2017-07-30 21:13:45 +02:00
bb3538e270 Refocus tweet textarea after selecting a different account
Closes #156
2017-07-30 20:36:17 +02:00
71925e1126 Refactor parts of code.js (make code shorter, use 'let') 2017-07-30 20:19:59 +02:00
93c1cbd231 Update SystemConfig to use FileSerializer and migrate old files 2017-07-30 19:54:28 +02:00
894b890fe5 Tweak serialization code and remove ISerializedObject 2017-07-30 19:28:03 +02:00
8e9e8f7fad Fix magic number and add a comment 2017-07-30 19:02:30 +02:00
2a0461a76f Add safeguards for accessing TweetDeckBridge.LastHighlightedTweetImages 2017-07-21 12:43:10 +02:00
85f923a6fc Add StringUtils.EmptyArray and use it instead of new string[0] 2017-07-21 12:37:30 +02:00
b35e4d4d01 Add "Save all images as..." context menu option for tweets with multiple images 2017-07-21 12:14:15 +02:00
cb24a859f4 Fix file type description in Save image dialog 2017-07-21 11:16:47 +02:00
b1ef00746f Hide open/copy link context menu items for media previews 2017-07-21 11:07:40 +02:00
aebe82e3a7 Add context menu for image previews that use background-image 2017-07-21 10:46:28 +02:00
7c87856b4d Show waiting cursor while taking a tweet screenshot 2017-07-20 16:29:39 +02:00
d1b1dd539f Add an option to use :orig image links in context menu 2017-07-17 05:39:59 +02:00
55eea88ace Add twitter image link & download methods to TwitterUtils 2017-07-17 05:10:06 +02:00
a70f64e1f6 Move some stuff from BrowserUtils to a new TwitterUtils class 2017-07-17 02:09:20 +02:00
fa0cb120a7 Add a 'Close' button to the modal dialog in the template plugin
Closes #143
2017-07-13 05:57:12 +02:00
e3080d07dc Ensure plugin config exists after first run, fixes profile export crash
Closes #147
2017-07-13 05:21:22 +02:00
65 changed files with 1091 additions and 872 deletions

View File

@@ -2,28 +2,26 @@
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Threading;
using TweetDuck.Core.Utils;
namespace TweetDuck.Configuration{
sealed class LockManager{
private const int RetryDelay = 250;
public enum Result{
Success, HasProcess, Fail
}
public Process LockingProcess { get; private set; }
private readonly string file;
private FileStream lockStream;
private Process lockingProcess;
public LockManager(string file){
this.file = file;
}
private void CreateLockFileStream(){
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
WriteIntToStream(lockStream, WindowsUtils.CurrentProcessID);
lockStream.Flush(true);
}
// Lock file
private bool ReleaseLockFileStream(){
if (lockStream != null){
@@ -37,8 +35,10 @@ namespace TweetDuck.Configuration{
}
private Result TryCreateLockFile(){
if (lockStream != null){
throw new InvalidOperationException("Lock file already exists.");
void CreateLockFileStream(){
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
lockStream.Write(BitConverter.GetBytes(WindowsUtils.CurrentProcessID), 0, sizeof(int));
lockStream.Flush(true);
}
try{
@@ -60,6 +60,8 @@ namespace TweetDuck.Configuration{
}
}
// Lock management
public Result Lock(){
if (lockStream != null){
return Result.Success;
@@ -72,7 +74,9 @@ namespace TweetDuck.Configuration{
int pid;
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
pid = ReadIntFromStream(fileStream);
byte[] bytes = new byte[sizeof(int)];
fileStream.Read(bytes, 0, bytes.Length);
pid = BitConverter.ToInt32(bytes, 0);
}
try{
@@ -80,7 +84,7 @@ namespace TweetDuck.Configuration{
using(Process currentProcess = Process.GetCurrentProcess()){
if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){
LockingProcess = foundProcess;
lockingProcess = foundProcess;
}
else{
foundProcess.Close();
@@ -91,7 +95,7 @@ namespace TweetDuck.Configuration{
// Process.MainModule can throw exceptions in some cases
}
return LockingProcess == null ? Result.Fail : Result.HasProcess;
return lockingProcess == null ? Result.Fail : Result.HasProcess;
}catch{
return Result.Fail;
}
@@ -100,45 +104,72 @@ namespace TweetDuck.Configuration{
return initialResult;
}
public bool Unlock(){
bool result = true;
public Result LockWait(int timeout){
for(int elapsed = 0; elapsed < timeout; elapsed += RetryDelay){
Result result = Lock();
if (result == Result.HasProcess){
Thread.Sleep(RetryDelay);
}
else{
return result;
}
}
return Lock();
}
public bool Unlock(){
if (ReleaseLockFileStream()){
try{
File.Delete(file);
}catch(Exception e){
Program.Reporter.Log(e.ToString());
result = false;
return false;
}
}
return result;
return true;
}
// Locking process
public bool RestoreLockingProcess(int failTimeout){
if (lockingProcess != null){
if (lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, Program.WindowRestoreMessage, new UIntPtr((uint)lockingProcess.Id), IntPtr.Zero);
if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){
return true;
}
}
}
return false;
}
public bool CloseLockingProcess(int closeTimeout, int killTimeout){
if (LockingProcess != null){
if (lockingProcess != null){
try{
if (LockingProcess.CloseMainWindow()){
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, 250);
if (lockingProcess.CloseMainWindow()){
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, RetryDelay);
}
if (!LockingProcess.HasExited){
LockingProcess.Kill();
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, 250);
if (!lockingProcess.HasExited){
lockingProcess.Kill();
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, RetryDelay);
}
if (LockingProcess.HasExited){
LockingProcess.Dispose();
LockingProcess = null;
if (lockingProcess.HasExited){
lockingProcess.Dispose();
lockingProcess = null;
return true;
}
}catch(Exception ex){
if (ex is InvalidOperationException || ex is Win32Exception){
if (LockingProcess != null){
LockingProcess.Refresh();
bool hasExited = LockingProcess.HasExited;
LockingProcess.Dispose();
if (lockingProcess != null){
bool hasExited = CheckLockingProcessExited();
lockingProcess.Dispose();
return hasExited;
}
}
@@ -150,21 +181,8 @@ namespace TweetDuck.Configuration{
}
private bool CheckLockingProcessExited(){
LockingProcess.Refresh();
return LockingProcess.HasExited;
}
// Utility functions
private static void WriteIntToStream(Stream stream, int value){
byte[] id = BitConverter.GetBytes(value);
stream.Write(id, 0, id.Length);
}
private static int ReadIntFromStream(Stream stream){
byte[] bytes = new byte[4];
stream.Read(bytes, 0, 4);
return BitConverter.ToInt32(bytes, 0);
lockingProcess.Refresh();
return lockingProcess.HasExited;
}
}
}

View File

@@ -1,43 +1,43 @@
using System;
using System.IO;
using TweetDuck.Core.Utils;
using TweetDuck.Data.Serialization;
namespace TweetDuck.Configuration{
sealed class SystemConfig{
private static readonly FileSerializer<SystemConfig> Serializer = new FileSerializer<SystemConfig>{
// HandleUnknownProperties = (obj, data) => {}
};
public static readonly bool IsHardwareAccelerationSupported = File.Exists(Path.Combine(Program.ProgramPath, "libEGL.dll")) &&
File.Exists(Path.Combine(Program.ProgramPath, "libGLESv2.dll"));
// CONFIGURATION DATA
private bool _hardwareAcceleration = true;
public bool EnableBrowserGCReload { get; set; } = true;
public int BrowserMemoryThreshold { get; set; } = 400;
// SPECIAL PROPERTIES
public bool HardwareAcceleration{
get => hardwareAcceleration && IsHardwareAccelerationSupported;
set => hardwareAcceleration = value;
get => _hardwareAcceleration && IsHardwareAccelerationSupported;
set => _hardwareAcceleration = value;
}
// END OF CONFIG
private readonly string file;
private bool hardwareAcceleration;
private SystemConfig(string file){
this.file = file;
HardwareAcceleration = true;
}
private void WriteToStream(Stream stream){
stream.WriteByte((byte)(HardwareAcceleration ? 1 : 0));
}
private void ReadFromStream(Stream stream){
HardwareAcceleration = stream.ReadByte() > 0;
}
public bool Save(){
try{
WindowsUtils.CreateDirectoryForFile(file);
using(Stream stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)){
WriteToStream(stream);
}
Serializer.Write(file, this);
return true;
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not save the system configuration file.", true, e);
@@ -49,11 +49,20 @@ namespace TweetDuck.Configuration{
SystemConfig config = new SystemConfig(file);
try{
using(Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)){
config.ReadFromStream(stream);
}
Serializer.Read(file, config);
return config;
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){
}catch(FormatException){
try{
using(Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)){
config.HardwareAcceleration = stream.ReadByte() > 0;
}
config.Save();
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not update the system configuration file.", true, e);
}
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not open the system configuration file. If you continue, you will lose system specific configuration such as Hardware Acceleration.", true, e);
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using TweetDuck.Core;
@@ -9,8 +10,27 @@ using TweetDuck.Data;
using TweetDuck.Data.Serialization;
namespace TweetDuck.Configuration{
sealed class UserConfig : ISerializedObject{
private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>();
sealed class UserConfig{
private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>{ HandleUnknownProperties = HandleUnknownProperties };
private static void HandleUnknownProperties(UserConfig obj, Dictionary<string, string> data){
if (data.TryGetValue("EnableBrowserGCReload", out string propGCReload) && data.TryGetValue("BrowserMemoryThreshold", out string propMemThreshold)){
if (bool.TryParse(propGCReload, out bool isGCReloadEnabled) && isGCReloadEnabled && int.TryParse(propMemThreshold, out int memThreshold)){
// SystemConfig initialization was moved before UserConfig to allow for this
// TODO remove the migration soon
Program.SystemConfig.EnableBrowserGCReload = true;
Program.SystemConfig.BrowserMemoryThreshold = memThreshold;
Program.SystemConfig.Save();
}
}
data.Remove("EnableBrowserGCReload");
data.Remove("BrowserMemoryThreshold");
if (data.Count == 0){
obj.Save();
}
}
static UserConfig(){
Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
@@ -39,6 +59,7 @@ namespace TweetDuck.Configuration{
public bool ExpandLinksOnHover { get; set; } = true;
public bool SwitchAccountSelectors { get; set; } = true;
public bool BestImageQuality { get; set; } = true;
public bool EnableSpellCheck { get; set; } = false;
private int _zoomLevel = 100;
private bool _muteNotifications;
@@ -50,6 +71,7 @@ namespace TweetDuck.Configuration{
public string DismissedUpdate { get; set; } = null;
public bool DisplayNotificationColumn { get; set; } = false;
public bool NotificationMediaPreviews { get; set; } = true;
public bool NotificationSkipOnLinkClick { get; set; } = false;
public bool NotificationNonIntrusiveMode { get; set; } = true;
public int NotificationIdlePauseSeconds { get; set; } = 0;
@@ -73,14 +95,13 @@ namespace TweetDuck.Configuration{
public string CustomBrowserCSS { get; set; } = null;
public string CustomNotificationCSS { get; set; } = null;
public bool EnableBrowserGCReload { get; set; } = false;
public int BrowserMemoryThreshold { get; set; } = 350;
// SPECIAL PROPERTIES
public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
public TwitterUtils.ImageQuality TwitterImageQuality => BestImageQuality ? TwitterUtils.ImageQuality.Orig : TwitterUtils.ImageQuality.Default;
public string NotificationSoundPath{
get => string.IsNullOrEmpty(_notificationSoundPath) ? string.Empty : _notificationSoundPath;
set => _notificationSoundPath = value;
@@ -127,16 +148,14 @@ namespace TweetDuck.Configuration{
public event EventHandler ZoomLevelChanged;
public event EventHandler TrayBehaviorChanged;
// END OF CONFIG
private readonly string file;
public UserConfig(string file){ // TODO make private after removing UserConfigLegacy
this.file = file;
}
bool ISerializedObject.OnReadUnknownProperty(string property, string value){
return false;
}
public bool Save(){
try{
WindowsUtils.CreateDirectoryForFile(file);

View File

@@ -1,45 +1,32 @@
using System;
using System.Text;
using System.Text;
namespace TweetDuck.Core.Bridge{
static class PropertyBridge{
[Flags]
public enum Properties{
ExpandLinksOnHover = 1,
MuteNotifications = 2,
HasCustomNotificationSound = 4,
SkipOnLinkClick = 8,
SwitchAccountSelectors = 16,
AllBrowser = ExpandLinksOnHover | SwitchAccountSelectors | MuteNotifications | HasCustomNotificationSound,
AllNotification = ExpandLinksOnHover | SkipOnLinkClick
public enum Environment{
Browser, Notification
}
public static string GenerateScript(Properties properties){
StringBuilder build = new StringBuilder();
build.Append("(function(c){");
if (properties.HasFlag(Properties.ExpandLinksOnHover)){
build.Append("c.expandLinksOnHover=").Append(Program.UserConfig.ExpandLinksOnHover ? "true;" : "false;");
public static string GenerateScript(Environment environment){
string Bool(bool value){
return value ? "true;" : "false;";
}
if (properties.HasFlag(Properties.SwitchAccountSelectors)){
build.Append("c.switchAccountSelectors=").Append(Program.UserConfig.SwitchAccountSelectors ? "true;" : "false;");
StringBuilder build = new StringBuilder().Append("(function(x){");
build.Append("x.expandLinksOnHover=").Append(Bool(Program.UserConfig.ExpandLinksOnHover));
if (environment == Environment.Browser){
build.Append("x.switchAccountSelectors=").Append(Bool(Program.UserConfig.SwitchAccountSelectors));
build.Append("x.muteNotifications=").Append(Bool(Program.UserConfig.MuteNotifications));
build.Append("x.hasCustomNotificationSound=").Append(Bool(Program.UserConfig.NotificationSoundPath.Length > 0));
build.Append("x.notificationMediaPreviews=").Append(Bool(Program.UserConfig.NotificationMediaPreviews));
}
if (properties.HasFlag(Properties.MuteNotifications)){
build.Append("c.muteNotifications=").Append(Program.UserConfig.MuteNotifications ? "true;" : "false;");
if (environment == Environment.Notification){
build.Append("x.skipOnLinkClick=").Append(Bool(Program.UserConfig.NotificationSkipOnLinkClick));
}
if (properties.HasFlag(Properties.HasCustomNotificationSound)){
build.Append("c.hasCustomNotificationSound=").Append(Program.UserConfig.NotificationSoundPath.Length > 0 ? "true;" : "false;");
}
if (properties.HasFlag(Properties.SkipOnLinkClick)){
build.Append("c.skipOnLinkClick=").Append(Program.UserConfig.NotificationSkipOnLinkClick ? "true;" : "false;");
}
build.Append("})(window.$TDX=window.$TDX||{})");
return build.ToString();
return build.Append("})(window.$TDX=window.$TDX||{})").ToString();
}
}
}

View File

@@ -1,17 +1,39 @@
using System.Windows.Forms;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils;
using TweetDuck.Resources;
namespace TweetDuck.Core.Bridge{
sealed class TweetDeckBridge{
public static string LastRightClickedLink = string.Empty;
public static string LastRightClickedImage = string.Empty;
public static string LastHighlightedTweet = string.Empty;
public static string LastHighlightedQuotedTweet = string.Empty;
public static string LastHighlightedTweetAuthor = string.Empty;
public static string[] LastHighlightedTweetImages = StringUtils.EmptyArray;
public static Dictionary<string, string> SessionData = new Dictionary<string, string>(2);
public static void ResetStaticProperties(){
LastRightClickedLink = LastHighlightedTweet = LastHighlightedQuotedTweet = string.Empty;
LastRightClickedLink = LastRightClickedImage = LastHighlightedTweet = LastHighlightedQuotedTweet = LastHighlightedTweetAuthor = string.Empty;
LastHighlightedTweetImages = StringUtils.EmptyArray;
}
public static void RestoreSessionData(IFrame frame){
if (SessionData.Count > 0){
StringBuilder build = new StringBuilder().Append("window.TD_SESSION={");
foreach(KeyValuePair<string, string> kvp in SessionData){
build.Append(kvp.Key).Append(":'").Append(kvp.Value.Replace("'", "\\'")).Append("',");
}
ScriptLoader.ExecuteScript(frame, build.Append("}").ToString(), "gen:session");
SessionData.Clear();
}
}
private readonly FormBrowser form;
@@ -38,10 +60,16 @@ namespace TweetDuck.Core.Bridge{
form.InvokeAsyncSafe(() => LastRightClickedLink = link);
}
public void SetLastHighlightedTweet(string link, string quotedLink){
public void SetLastRightClickedImage(string link){
form.InvokeAsyncSafe(() => LastRightClickedImage = link);
}
public void SetLastHighlightedTweet(string link, string quotedLink, string author, string imageList){
form.InvokeAsyncSafe(() => {
LastHighlightedTweet = link;
LastHighlightedQuotedTweet = quotedLink;
LastHighlightedTweetAuthor = author;
LastHighlightedTweetImages = imageList.Split(';');
});
}
@@ -72,6 +100,12 @@ namespace TweetDuck.Core.Bridge{
}
}
public void SetSessionData(string key, string value){
form.InvokeSafe(() => { // do not use InvokeAsyncSafe, return only after invocation
SessionData.Add(key, value);
});
}
public void LoadNextNotification(){
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
}

View File

@@ -38,7 +38,7 @@
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = TweetDuck.Core.Utils.BrowserUtils.BackgroundColor;
this.BackColor = TweetDuck.Core.Utils.TwitterUtils.BackgroundColor;
this.ClientSize = new System.Drawing.Size(324, 386);
this.Icon = Properties.Resources.icon;
this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;

View File

@@ -8,6 +8,7 @@ using TweetDuck.Configuration;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Notification.Screenshot;
using TweetDuck.Core.Other;
@@ -25,6 +26,23 @@ namespace TweetDuck.Core{
sealed partial class FormBrowser : Form{
private static UserConfig Config => Program.UserConfig;
public bool IsWaiting{
set{
if (value){
browser.Enabled = false;
Cursor = Cursors.WaitCursor;
}
else{
browser.Enabled = true;
Cursor = Cursors.Default;
if (Focused){ // re-focus browser only if the window or a child is activated
browser.Focus();
}
}
}
}
public string UpdateInstallerPath { get; private set; }
private readonly ChromiumWebBrowser browser;
@@ -81,7 +99,7 @@ namespace TweetDuck.Core{
this.browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(this, notification));
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
browser.BrowserSettings.BackgroundColor = (uint)BrowserUtils.BackgroundColor.ToArgb();
browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb();
browser.Dock = DockStyle.None;
browser.Location = ControlExtensions.InvisibleLocation;
Controls.Add(browser);
@@ -158,7 +176,7 @@ namespace TweetDuck.Core{
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading){
foreach(string word in BrowserUtils.DictionaryWords){
foreach(string word in TwitterUtils.DictionaryWords){
browser.AddWordToDictionary(word);
}
@@ -175,17 +193,18 @@ namespace TweetDuck.Core{
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
}
if (BrowserUtils.IsTwitterWebsite(e.Frame)){
if (TwitterUtils.IsTwitterWebsite(e.Frame)){
ScriptLoader.ExecuteFile(e.Frame, "twitter.js");
}
}
}
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
e.Frame.ExecuteJavaScriptAsync(BrowserUtils.BackgroundColorFix);
if (e.Frame.IsMain && TwitterUtils.IsTweetDeckWebsite(e.Frame)){
e.Frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorFix);
UpdateProperties(PropertyBridge.Properties.AllBrowser);
UpdateProperties(PropertyBridge.Environment.Browser);
TweetDeckBridge.RestoreSessionData(e.Frame);
ScriptLoader.ExecuteFile(e.Frame, "code.js");
ReinjectCustomCSS(Config.CustomBrowserCSS);
@@ -197,8 +216,8 @@ namespace TweetDuck.Core{
TweetDeckBridge.ResetStaticProperties();
if (Config.EnableBrowserGCReload){
memoryUsageTracker.Start(this, e.Browser, Config.BrowserMemoryThreshold);
if (Program.SystemConfig.EnableBrowserGCReload){
memoryUsageTracker.Start(this, e.Browser, Program.SystemConfig.BrowserMemoryThreshold);
}
}
}
@@ -225,6 +244,10 @@ namespace TweetDuck.Core{
if (!isLoaded)return;
trayIcon.HasNotifications = false;
if (!browser.Enabled){ // when taking a screenshot, the window is unfocused and
browser.Enabled = true; // the browser is disabled; if the user clicks back into
} // the window, enable the browser again
}
private void FormBrowser_LocationChanged(object sender, EventArgs e){
@@ -282,7 +305,7 @@ namespace TweetDuck.Core{
}
private void Config_MuteToggled(object sender, EventArgs e){
UpdateProperties(PropertyBridge.Properties.MuteNotifications);
UpdateProperties(PropertyBridge.Environment.Browser);
}
private void Config_ZoomLevelChanged(object sender, EventArgs e){
@@ -305,7 +328,7 @@ namespace TweetDuck.Core{
}
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
browser.GetBrowser().Reload();
ReloadToTweetDeck();
}
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
@@ -401,12 +424,12 @@ namespace TweetDuck.Core{
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
}
public void UpdateProperties(PropertyBridge.Properties properties){
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(properties));
public void UpdateProperties(PropertyBridge.Environment environment){
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(environment));
}
public void ReloadToTweetDeck(){
browser.ExecuteScriptAsync($"gc&&gc();window.location.href='{BrowserUtils.TweetDeckURL}'");
browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUtils.TweetDeckURL}'");
}
// callback handlers
@@ -435,14 +458,14 @@ namespace TweetDuck.Core{
trayIcon.HasNotifications = false;
}
if (Config.EnableBrowserGCReload){
memoryUsageTracker.Start(this, browser.GetBrowser(), Config.BrowserMemoryThreshold);
if (Program.SystemConfig.EnableBrowserGCReload){
memoryUsageTracker.Start(this, browser.GetBrowser(), Program.SystemConfig.BrowserMemoryThreshold);
}
else{
memoryUsageTracker.Stop();
}
UpdateProperties(PropertyBridge.Properties.ExpandLinksOnHover | PropertyBridge.Properties.SwitchAccountSelectors | PropertyBridge.Properties.HasCustomNotificationSound);
UpdateProperties(PropertyBridge.Environment.Browser);
notification.RequiresResize = true;
form.Dispose();

View File

@@ -1,35 +1,56 @@
using CefSharp;
using System;
using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling{
abstract class ContextMenuBase : IContextMenuHandler{
private static readonly Lazy<Regex> RegexTwitterAccount = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/([^/]+)/?$", RegexOptions.Compiled), false);
protected static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
private static TwitterUtils.ImageQuality ImageQuality => Program.UserConfig.TwitterImageQuality;
private static string GetLink(IContextMenuParams parameters){
return string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink;
}
private static string GetImage(IContextMenuParams parameters){
return string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedImage) ? parameters.SourceUrl : TweetDeckBridge.LastRightClickedImage;
}
private const int MenuOpenLinkUrl = 26500;
private const int MenuCopyLinkUrl = 26501;
private const int MenuCopyUsername = 26502;
private const int MenuOpenImage = 26503;
private const int MenuSaveImage = 26504;
private const int MenuCopyImageUrl = 26505;
private const int MenuOpenImageUrl = 26503;
private const int MenuCopyImageUrl = 26504;
private const int MenuSaveImage = 26505;
private const int MenuSaveAllImages = 26506;
private const int MenuOpenDevTools = 26599;
private readonly Form form;
private string lastHighlightedTweetAuthor;
private string[] lastHighlightedTweetImageList;
protected ContextMenuBase(Form form){
this.form = form;
}
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){
if (RegexTwitterAccount.Value.IsMatch(parameters.UnfilteredLinkUrl)){
bool hasTweetImage = !string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedImage);
lastHighlightedTweetAuthor = TweetDeckBridge.LastHighlightedTweetAuthor;
lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImages;
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
lastHighlightedTweetAuthor = string.Empty;
lastHighlightedTweetImageList = StringUtils.EmptyArray;
}
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal) && !hasTweetImage){
if (TwitterUtils.RegexAccount.IsMatch(parameters.UnfilteredLinkUrl)){
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open account in browser");
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy account address");
model.AddItem((CefMenuCommand)MenuCopyUsername, "Copy account username");
@@ -42,10 +63,15 @@ namespace TweetDuck.Core.Handling{
model.AddSeparator();
}
if (parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents){
model.AddItem((CefMenuCommand)MenuOpenImage, "Open image in browser");
model.AddItem((CefMenuCommand)MenuSaveImage, "Save image as...");
if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){
model.AddItem((CefMenuCommand)MenuOpenImageUrl, "Open image in browser");
model.AddItem((CefMenuCommand)MenuCopyImageUrl, "Copy image address");
model.AddItem((CefMenuCommand)MenuSaveImage, "Save image as...");
if (lastHighlightedTweetImageList.Length > 1){
model.AddItem((CefMenuCommand)MenuSaveAllImages, "Save all images as...");
}
model.AddSeparator();
}
}
@@ -57,42 +83,27 @@ namespace TweetDuck.Core.Handling{
break;
case MenuCopyLinkUrl:
SetClipboardText(string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink);
SetClipboardText(GetLink(parameters));
break;
case MenuOpenImage:
BrowserUtils.OpenExternalBrowser(parameters.SourceUrl);
case MenuOpenImageUrl:
BrowserUtils.OpenExternalBrowser(TwitterUtils.GetImageLink(GetImage(parameters), ImageQuality));
break;
case MenuSaveImage:
string fileName = GetImageFileName(parameters.SourceUrl);
string extension = Path.GetExtension(fileName);
string saveTarget;
using(SaveFileDialog dialog = new SaveFileDialog{
AutoUpgradeEnabled = true,
OverwritePrompt = true,
Title = "Save image",
FileName = fileName,
Filter = "Image ("+(string.IsNullOrEmpty(extension) ? "unknown" : extension)+")|*.*"
}){
saveTarget = dialog.ShowDialog() == DialogResult.OK ? dialog.FileName : null;
}
if (saveTarget != null){
BrowserUtils.DownloadFileAsync(parameters.SourceUrl, saveTarget, null, ex => {
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
});
}
TwitterUtils.DownloadImage(GetImage(parameters), lastHighlightedTweetAuthor, ImageQuality);
break;
case MenuSaveAllImages:
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthor, ImageQuality);
break;
case MenuCopyImageUrl:
SetClipboardText(parameters.SourceUrl);
SetClipboardText(TwitterUtils.GetImageLink(GetImage(parameters), ImageQuality));
break;
case MenuCopyUsername:
Match match = RegexTwitterAccount.Value.Match(parameters.UnfilteredLinkUrl);
Match match = TwitterUtils.RegexAccount.Match(parameters.UnfilteredLinkUrl);
SetClipboardText(match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
break;
@@ -104,7 +115,10 @@ namespace TweetDuck.Core.Handling{
return false;
}
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){}
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
TweetDeckBridge.LastRightClickedLink = string.Empty;
TweetDeckBridge.LastRightClickedImage = string.Empty;
}
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
return false;
@@ -129,17 +143,5 @@ namespace TweetDuck.Core.Handling{
model.AddSeparator();
}
}
private static string GetImageFileName(string url){
// twimg adds a colon after file extension
int dot = url.LastIndexOf('.');
if (dot != -1){
url = StringUtils.ExtractBefore(url, ':', dot);
}
// return file name
return BrowserUtils.GetFileNameFromUrl(url) ?? "unknown";
}
}
}

View File

@@ -49,7 +49,7 @@ namespace TweetDuck.Core.Handling{
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet;
if (!BrowserUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
lastHighlightedTweet = string.Empty;
lastHighlightedQuotedTweet = string.Empty;
}

View File

@@ -1,7 +1,7 @@
using CefSharp;
using System;
using System;
using CefSharp;
namespace TweetDuck.Core.Handling{
namespace TweetDuck.Core.Handling.General{
class BrowserProcessHandler : IBrowserProcessHandler{
void IBrowserProcessHandler.OnContextInitialized(){
using(IRequestContext ctx = Cef.GetGlobalRequestContext()){

View File

@@ -1,11 +1,12 @@
using CefSharp;
using CefSharp.WinForms;
using System.Drawing;
using System.Drawing;
using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling {
namespace TweetDuck.Core.Handling.General{
class JavaScriptDialogHandler : IJsDialogHandler{
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){
((ChromiumWebBrowser)browserControl).InvokeSafe(() => {
@@ -13,23 +14,25 @@ namespace TweetDuck.Core.Handling {
TextBox input = null;
if (dialogType == CefJsDialogType.Alert){
form = new FormMessage("TweetDuck Browser Message", messageText, MessageBoxIcon.None);
form = new FormMessage("Browser Message", messageText, MessageBoxIcon.None);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
}
else if (dialogType == CefJsDialogType.Confirm){
form = new FormMessage("TweetDuck Browser Confirmation", messageText, MessageBoxIcon.None);
form = new FormMessage("Browser Confirmation", messageText, MessageBoxIcon.None);
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel);
form.AddButton(FormMessage.Yes, ControlType.Focused);
}
else if (dialogType == CefJsDialogType.Prompt){
form = new FormMessage("TweetDuck Browser Prompt", messageText, MessageBoxIcon.None);
form = new FormMessage("Browser Prompt", messageText, MessageBoxIcon.None);
form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
float dpiScale = form.GetDPIScale();
input = new TextBox{
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
Location = new Point(27, form.ActionPanelY-46),
Size = new Size(form.ClientSize.Width-54, 20)
Location = new Point(BrowserUtils.Scale(22, dpiScale), form.ActionPanelY-BrowserUtils.Scale(46, dpiScale)),
Size = new Size(form.ClientSize.Width-BrowserUtils.Scale(44, dpiScale), 20)
};
form.Controls.Add(input);

View File

@@ -1,7 +1,7 @@
using CefSharp;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling{
namespace TweetDuck.Core.Handling.General{
class LifeSpanHandler : ILifeSpanHandler{
public bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser){
newBrowser = null;

View File

@@ -1,8 +1,8 @@
using System.Security.Cryptography.X509Certificates;
using CefSharp;
namespace TweetDuck.Core.Handling{
abstract class RequestHandler : IRequestHandler{
namespace TweetDuck.Core.Handling.General{
abstract class RequestHandlerBase : IRequestHandler{
// Browser
public virtual void OnRenderViewReady(IWebBrowser browserControl, IBrowser browser){}

View File

@@ -0,0 +1,38 @@
using CefSharp;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
namespace TweetDuck.Core.Handling {
sealed class KeyboardHandlerNotification : IKeyboardHandler{
private readonly FormNotificationBase notification;
public KeyboardHandlerNotification(FormNotificationBase notification){
this.notification = notification;
}
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){
if (type == KeyType.RawKeyDown && !browser.FocusedFrame.Url.StartsWith("chrome-devtools://")){
switch((Keys)windowsKeyCode){
case Keys.Enter:
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
return true;
case Keys.Escape:
notification.InvokeAsyncSafe(() => notification.HideNotification(true));
return true;
case Keys.Space:
notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer);
return true;
}
}
return false;
}
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
return false;
}
}
}

View File

@@ -1,7 +1,8 @@
using CefSharp;
using TweetDuck.Core.Handling.General;
namespace TweetDuck.Core.Handling{
class RequestHandlerBrowser : RequestHandler{
class RequestHandlerBrowser : RequestHandlerBase{
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
browser.Reload();
}

View File

@@ -6,6 +6,7 @@ using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Notification{
@@ -97,6 +98,7 @@ namespace TweetDuck.Core.Notification{
this.browser = new ChromiumWebBrowser("about:blank"){
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler()
};
@@ -111,7 +113,7 @@ namespace TweetDuck.Core.Notification{
this.dpiScale = this.GetDPIScale();
DefaultResourceHandlerFactory handlerFactory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory;
handlerFactory.RegisterHandler(BrowserUtils.TweetDeckURL, this.resourceHandler);
handlerFactory.RegisterHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
Controls.Add(browser);
@@ -183,7 +185,7 @@ namespace TweetDuck.Core.Notification{
currentColumn = tweet.Column;
resourceHandler.SetHTML(GetTweetHTML(tweet));
browser.Load(BrowserUtils.TweetDeckURL);
browser.Load(TwitterUtils.TweetDeckURL);
}
protected virtual void SetNotificationSize(int width, int height){

View File

@@ -4,6 +4,7 @@ using System.Drawing;
using System.Windows.Forms;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins;
@@ -66,7 +67,7 @@ namespace TweetDuck.Core.Notification{
get{
switch(Program.UserConfig.NotificationSize){
default:
return BrowserUtils.Scale(118, SizeScale*(1.0+0.075*TweetNotification.FontSizeLevel));
return BrowserUtils.Scale(122, SizeScale*(1.0+0.075*TweetNotification.FontSizeLevel));
case TweetNotification.Size.Custom:
return Program.UserConfig.CustomNotificationSize.Height;
@@ -83,6 +84,8 @@ namespace TweetDuck.Core.Notification{
this.plugins = pluginManager;
browser.KeyboardHandler = new KeyboardHandlerNotification(this);
browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(owner, this));
browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
@@ -164,7 +167,7 @@ namespace TweetDuck.Core.Notification{
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){
e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Properties.AllNotification));
e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
ScriptLoader.ExecuteScript(e.Frame, NotificationJS, NotificationScriptIdentifier);
if (plugins.HasAnyPlugin(PluginEnvironment.Notification)){

View File

@@ -2,6 +2,7 @@
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils;
@@ -18,9 +19,11 @@ namespace TweetDuck.Core.Notification.Screenshot{
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback));
browser.FrameLoadEnd += (sender, args) => {
if (args.Frame.IsMain && browser.Address != "about:blank"){
ScriptLoader.ExecuteScript(args.Frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 67)", "gen:screenshot");
browser.LoadingStateChanged += (sender, args) => {
if (!args.IsLoading){
using(IFrame frame = args.Browser.MainFrame){
ScriptLoader.ExecuteScript(frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 129)", "gen:screenshot");
}
}
};
}

View File

@@ -8,14 +8,14 @@ using TweetDuck.Plugins;
namespace TweetDuck.Core.Notification.Screenshot{
sealed class TweetScreenshotManager : IDisposable{
private readonly Form owner;
private readonly FormBrowser owner;
private readonly PluginManager plugins;
private readonly Timer timeout;
private readonly Timer disposer;
private FormNotificationScreenshotable screenshot;
public TweetScreenshotManager(Form owner, PluginManager pluginManager){
public TweetScreenshotManager(FormBrowser owner, PluginManager pluginManager){
this.owner = owner;
this.plugins = pluginManager;
@@ -28,8 +28,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
private void timeout_Tick(object sender, EventArgs e){
timeout.Stop();
screenshot.Location = ControlExtensions.InvisibleLocation;
disposer.Start();
OnFinished();
}
private void disposer_Tick(object sender, EventArgs e){
@@ -50,6 +49,10 @@ namespace TweetDuck.Core.Notification.Screenshot{
screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, html, 0, string.Empty, string.Empty), width, height);
screenshot.Show();
timeout.Start();
#if !(DEBUG && NO_HIDE_SCREENSHOTS)
owner.IsWaiting = true;
#endif
}
private void Callback(){
@@ -61,14 +64,19 @@ namespace TweetDuck.Core.Notification.Screenshot{
screenshot.TakeScreenshot();
#if !(DEBUG && NO_HIDE_SCREENSHOTS)
screenshot.Location = ControlExtensions.InvisibleLocation;
disposer.Start();
OnFinished();
#else
screenshot.MoveToVisibleLocation();
screenshot.FormClosed += (sender, args) => disposer.Start();
#endif
}
private void OnFinished(){
screenshot.Location = ControlExtensions.InvisibleLocation;
owner.IsWaiting = false;
disposer.Start();
}
public void Dispose(){
timeout.Dispose();
disposer.Dispose();

View File

@@ -9,7 +9,7 @@ namespace TweetDuck.Core.Notification{
private const string DefaultFontSizeClass = "medium";
private const string DefaultHeadTag = @"<meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>";
private const string CustomCSS = @"body:before{content:none}body{overflow-y:auto}.scroll-styled-v::-webkit-scrollbar{width:7px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}.scroll-styled-v::-webkit-scrollbar-track{border-left:0}#td-skip{opacity:0;cursor:pointer;transition:opacity 0.15s ease}.td-hover #td-skip{opacity:0.75}#td-skip:hover{opacity:1}";
private const string CustomCSS = @"body:before{content:none}body{overflow-y:auto}.scroll-styled-v::-webkit-scrollbar{width:7px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}.scroll-styled-v::-webkit-scrollbar-track{border-left:0}#td-skip{opacity:0;cursor:pointer;transition:opacity 0.15s ease}.td-hover #td-skip{opacity:0.75}#td-skip:hover{opacity:1}.media-size-medium{height:calc(100vh - 16px)!important;max-height:240px;border-radius:1px!important}.js-quote-detail .media-size-medium{height:calc(100vh - 28px)!important;}.js-media.margin-vm, .js-media-preview-container.margin-vm{margin-bottom:0!important}";
public static int FontSizeLevel{
get{

View File

@@ -87,7 +87,7 @@ namespace TweetDuck.Core.Other{
this.prevLabelWidth = labelMessage.Width;
this.prevLabelHeight = labelMessage.Height;
this.minFormWidth = BrowserUtils.Scale(40, dpiScale);
this.minFormWidth = BrowserUtils.Scale(42, dpiScale);
switch(messageIcon){
case MessageBoxIcon.Information:
@@ -108,7 +108,7 @@ namespace TweetDuck.Core.Other{
default:
icon = null;
labelMessage.Location = new Point(labelMessage.Location.X-38, labelMessage.Location.Y);
labelMessage.Location = new Point(BrowserUtils.Scale(19, dpiScale), labelMessage.Location.Y); // 19 instead of 9 due to larger height
break;
}
@@ -176,7 +176,7 @@ namespace TweetDuck.Core.Other{
private void RecalculateButtonLocation(){
int dist = ButtonDistance;
int start = ClientWidth-dist-BrowserUtils.Scale(1, dpiScale);
int start = ClientWidth-dist;
for(int index = 0; index < buttonCount; index++){
Control control = panelActions.Controls[index];
@@ -201,7 +201,7 @@ namespace TweetDuck.Core.Other{
prevLabelHeight -= labelOffset;
}
realFormWidth = ClientWidth-(icon == null ? 50 : 0)+labelMessage.Width-prevLabelWidth;
realFormWidth = ClientWidth-(icon == null ? BrowserUtils.Scale(50, dpiScale) : 0)+labelMessage.Width-prevLabelWidth;
ClientWidth = Math.Max(realFormWidth, minFormWidth);
Height += labelMessage.Height-prevLabelHeight;
@@ -212,7 +212,7 @@ namespace TweetDuck.Core.Other{
protected override void OnPaint(PaintEventArgs e){
if (icon != null){
e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), BrowserUtils.Scale(26, dpiScale));
e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1+BrowserUtils.Scale(25, dpiScale));
}
base.OnPaint(e);

View File

@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -2,6 +2,7 @@
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsCefArgs : Form{
@@ -30,7 +31,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
return;
}
int count = CommandLineArgsParser.ReadCefArguments(CefArgs).Count;
int count = CommandLineArgs.ReadCefArguments(CefArgs).Count;
string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "All current arguments will be removed. Continue?" : count+(count == 1 ? " argument was" : " arguments were")+" detected. Continue?";
if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)){

View File

@@ -68,7 +68,7 @@
this.checkHardwareAcceleration.Size = new System.Drawing.Size(134, 17);
this.checkHardwareAcceleration.TabIndex = 0;
this.checkHardwareAcceleration.Text = "Hardware Acceleration";
this.toolTip.SetToolTip(this.checkHardwareAcceleration, "Uses your graphics card to improve performance.\r\nDisable if you experience issues with rendering.");
this.toolTip.SetToolTip(this.checkHardwareAcceleration, "Uses graphics card to improve performance. Disable if you experience\r\nvisual glitches. This option will not be exported in a profile.");
this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
//
// btnEditCefArgs
@@ -142,26 +142,14 @@
0,
0});
this.numMemoryThreshold.Location = new System.Drawing.Point(202, 82);
this.numMemoryThreshold.Maximum = new decimal(new int[] {
3000,
0,
0,
0});
this.numMemoryThreshold.Minimum = new decimal(new int[] {
200,
0,
0,
0});
this.numMemoryThreshold.Maximum = 2000;
this.numMemoryThreshold.Minimum = 200;
this.numMemoryThreshold.Name = "numMemoryThreshold";
this.numMemoryThreshold.Size = new System.Drawing.Size(97, 20);
this.numMemoryThreshold.TabIndex = 4;
this.numMemoryThreshold.TextSuffix = " MB";
this.toolTip.SetToolTip(this.numMemoryThreshold, "Minimum amount of memory usage by the browser process to trigger the cleanup.\r\nThis is not a limit, the usage is allowed to exceed this value.");
this.numMemoryThreshold.Value = new decimal(new int[] {
350,
0,
0,
0});
this.numMemoryThreshold.Value = 400;
//
// checkBrowserGCReload
//
@@ -172,7 +160,7 @@
this.checkBrowserGCReload.Size = new System.Drawing.Size(190, 17);
this.checkBrowserGCReload.TabIndex = 3;
this.checkBrowserGCReload.Text = "Enable Browser Memory Threshold";
this.toolTip.SetToolTip(this.checkBrowserGCReload, "Automatically reloads TweetDeck to save memory. This option only works\r\nif the browser is in a \'default state\', i.e. all modals and drawers are closed,\r\nand all columns are scrolled to top. Some notifications may be lost.");
this.toolTip.SetToolTip(this.checkBrowserGCReload, "Automatically reloads TweetDeck to save memory. This option only works if\r\nthe browser is in a \'default state\', i.e. all modals and drawers are closed, and\r\nall columns are scrolled to top. This option will not be exported in a profile.");
this.checkBrowserGCReload.UseVisualStyleBackColor = true;
//
// labelApp

View File

@@ -8,6 +8,8 @@ using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other.Settings{
partial class TabSettingsAdvanced : BaseTabSettings{
private static SystemConfig SysConfig => Program.SystemConfig;
private readonly Action<string> reinjectBrowserCSS;
public TabSettingsAdvanced(Action<string> reinjectBrowserCSS){
@@ -16,16 +18,16 @@ namespace TweetDuck.Core.Other.Settings{
this.reinjectBrowserCSS = reinjectBrowserCSS;
if (SystemConfig.IsHardwareAccelerationSupported){
checkHardwareAcceleration.Checked = Program.SystemConfig.HardwareAcceleration;
checkHardwareAcceleration.Checked = SysConfig.HardwareAcceleration;
}
else{
checkHardwareAcceleration.Enabled = false;
checkHardwareAcceleration.Checked = false;
}
checkBrowserGCReload.Checked = Config.EnableBrowserGCReload;
checkBrowserGCReload.Checked = SysConfig.EnableBrowserGCReload;
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
numMemoryThreshold.SetValueSafe(Config.BrowserMemoryThreshold);
numMemoryThreshold.SetValueSafe(SysConfig.BrowserMemoryThreshold);
BrowserCache.CalculateCacheSize(bytes => this.InvokeSafe(() => {
if (bytes == -1L){
@@ -53,6 +55,10 @@ namespace TweetDuck.Core.Other.Settings{
btnRestartArgs.Click += btnRestartArgs_Click;
}
public override void OnClosing(){
SysConfig.Save();
}
private void btnClearCache_Click(object sender, EventArgs e){
btnClearCache.Enabled = false;
BrowserCache.SetClearOnExit();
@@ -60,18 +66,17 @@ namespace TweetDuck.Core.Other.Settings{
}
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e){
Program.SystemConfig.HardwareAcceleration = checkHardwareAcceleration.Checked;
Program.SystemConfig.Save();
PromptRestart();
SysConfig.HardwareAcceleration = checkHardwareAcceleration.Checked;
PromptRestart(); // calls OnClosing
}
private void checkBrowserGCReload_CheckedChanged(object sender, EventArgs e){
Config.EnableBrowserGCReload = checkBrowserGCReload.Checked;
SysConfig.EnableBrowserGCReload = checkBrowserGCReload.Checked;
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
}
private void numMemoryThreshold_ValueChanged(object sender, EventArgs e){
Config.BrowserMemoryThreshold = (int)numMemoryThreshold.Value;
SysConfig.BrowserMemoryThreshold = (int)numMemoryThreshold.Value;
}
private void btnEditCefArgs_Click(object sender, EventArgs e){

View File

@@ -43,6 +43,7 @@
this.panelUpdates = new System.Windows.Forms.Panel();
this.panelTray = new System.Windows.Forms.Panel();
this.labelUpdates = new System.Windows.Forms.Label();
this.checkBestImageQuality = new System.Windows.Forms.CheckBox();
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit();
this.panelUI.SuspendLayout();
this.panelUpdates.SuspendLayout();
@@ -87,11 +88,11 @@
// checkSpellCheck
//
this.checkSpellCheck.AutoSize = true;
this.checkSpellCheck.Location = new System.Drawing.Point(6, 51);
this.checkSpellCheck.Location = new System.Drawing.Point(6, 74);
this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkSpellCheck.Name = "checkSpellCheck";
this.checkSpellCheck.Size = new System.Drawing.Size(119, 17);
this.checkSpellCheck.TabIndex = 2;
this.checkSpellCheck.TabIndex = 3;
this.checkSpellCheck.Text = "Enable Spell Check";
this.toolTip.SetToolTip(this.checkSpellCheck, "Underlines words that are spelled incorrectly.");
this.checkSpellCheck.UseVisualStyleBackColor = true;
@@ -122,11 +123,11 @@
// labelZoomValue
//
this.labelZoomValue.BackColor = System.Drawing.Color.Transparent;
this.labelZoomValue.Location = new System.Drawing.Point(141, 100);
this.labelZoomValue.Location = new System.Drawing.Point(141, 123);
this.labelZoomValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelZoomValue.Name = "labelZoomValue";
this.labelZoomValue.Size = new System.Drawing.Size(38, 13);
this.labelZoomValue.TabIndex = 5;
this.labelZoomValue.TabIndex = 6;
this.labelZoomValue.Text = "100%";
this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
this.toolTip.SetToolTip(this.labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots.");
@@ -158,24 +159,24 @@
this.trackBarZoom.AutoSize = false;
this.trackBarZoom.BackColor = System.Drawing.SystemColors.Control;
this.trackBarZoom.LargeChange = 25;
this.trackBarZoom.Location = new System.Drawing.Point(3, 99);
this.trackBarZoom.Location = new System.Drawing.Point(3, 122);
this.trackBarZoom.Maximum = 200;
this.trackBarZoom.Minimum = 50;
this.trackBarZoom.Name = "trackBarZoom";
this.trackBarZoom.Size = new System.Drawing.Size(148, 30);
this.trackBarZoom.SmallChange = 5;
this.trackBarZoom.TabIndex = 4;
this.trackBarZoom.TabIndex = 5;
this.trackBarZoom.TickFrequency = 25;
this.trackBarZoom.Value = 100;
//
// labelZoom
//
this.labelZoom.AutoSize = true;
this.labelZoom.Location = new System.Drawing.Point(3, 83);
this.labelZoom.Location = new System.Drawing.Point(3, 106);
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 = 3;
this.labelZoom.TabIndex = 4;
this.labelZoom.Text = "Zoom";
//
// zoomUpdateTimer
@@ -198,6 +199,7 @@
//
this.panelUI.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelUI.Controls.Add(this.checkBestImageQuality);
this.panelUI.Controls.Add(this.checkExpandLinks);
this.panelUI.Controls.Add(this.checkSwitchAccountSelectors);
this.panelUI.Controls.Add(this.checkSpellCheck);
@@ -206,14 +208,14 @@
this.panelUI.Controls.Add(this.trackBarZoom);
this.panelUI.Location = new System.Drawing.Point(9, 31);
this.panelUI.Name = "panelUI";
this.panelUI.Size = new System.Drawing.Size(322, 134);
this.panelUI.Size = new System.Drawing.Size(322, 157);
this.panelUI.TabIndex = 1;
//
// labelTray
//
this.labelTray.AutoSize = true;
this.labelTray.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelTray.Location = new System.Drawing.Point(5, 189);
this.labelTray.Location = new System.Drawing.Point(6, 212);
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelTray.Name = "labelTray";
this.labelTray.Size = new System.Drawing.Size(96, 20);
@@ -226,7 +228,7 @@
| System.Windows.Forms.AnchorStyles.Right)));
this.panelUpdates.Controls.Add(this.checkUpdateNotifications);
this.panelUpdates.Controls.Add(this.btnCheckUpdates);
this.panelUpdates.Location = new System.Drawing.Point(9, 335);
this.panelUpdates.Location = new System.Drawing.Point(9, 358);
this.panelUpdates.Name = "panelUpdates";
this.panelUpdates.Size = new System.Drawing.Size(322, 55);
this.panelUpdates.TabIndex = 5;
@@ -238,7 +240,7 @@
this.panelTray.Controls.Add(this.checkTrayHighlight);
this.panelTray.Controls.Add(this.comboBoxTrayType);
this.panelTray.Controls.Add(this.labelTrayIcon);
this.panelTray.Location = new System.Drawing.Point(9, 212);
this.panelTray.Location = new System.Drawing.Point(9, 235);
this.panelTray.Name = "panelTray";
this.panelTray.Size = new System.Drawing.Size(322, 76);
this.panelTray.TabIndex = 3;
@@ -247,13 +249,25 @@
//
this.labelUpdates.AutoSize = true;
this.labelUpdates.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelUpdates.Location = new System.Drawing.Point(6, 312);
this.labelUpdates.Location = new System.Drawing.Point(6, 335);
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelUpdates.Name = "labelUpdates";
this.labelUpdates.Size = new System.Drawing.Size(70, 20);
this.labelUpdates.TabIndex = 4;
this.labelUpdates.Text = "Updates";
//
// checkBestImageQuality
//
this.checkBestImageQuality.AutoSize = true;
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 51);
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkBestImageQuality.Name = "checkBestImageQuality";
this.checkBestImageQuality.Size = new System.Drawing.Size(114, 17);
this.checkBestImageQuality.TabIndex = 2;
this.checkBestImageQuality.Text = "Best Image Quality";
this.toolTip.SetToolTip(this.checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
this.checkBestImageQuality.UseVisualStyleBackColor = true;
//
// TabSettingsGeneral
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -265,7 +279,7 @@
this.Controls.Add(this.panelUI);
this.Controls.Add(this.labelUI);
this.Name = "TabSettingsGeneral";
this.Size = new System.Drawing.Size(340, 400);
this.Size = new System.Drawing.Size(340, 422);
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit();
this.panelUI.ResumeLayout(false);
this.panelUI.PerformLayout();
@@ -299,5 +313,6 @@
private System.Windows.Forms.Panel panelUpdates;
private System.Windows.Forms.Panel panelTray;
private System.Windows.Forms.Label labelUpdates;
private System.Windows.Forms.CheckBox checkBestImageQuality;
}
}

View File

@@ -28,6 +28,7 @@ namespace TweetDuck.Core.Other.Settings{
checkExpandLinks.Checked = Config.ExpandLinksOnHover;
checkSwitchAccountSelectors.Checked = Config.SwitchAccountSelectors;
checkBestImageQuality.Checked = Config.BestImageQuality;
checkSpellCheck.Checked = Config.EnableSpellCheck;
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
@@ -37,6 +38,7 @@ namespace TweetDuck.Core.Other.Settings{
public override void OnReady(){
checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
checkSwitchAccountSelectors.CheckedChanged += checkSwitchAccountSelectors_CheckedChanged;
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
checkSpellCheck.CheckedChanged += checkSpellCheck_CheckedChanged;
trackBarZoom.ValueChanged += trackBarZoom_ValueChanged;
@@ -59,6 +61,10 @@ namespace TweetDuck.Core.Other.Settings{
Config.SwitchAccountSelectors = checkSwitchAccountSelectors.Checked;
}
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e){
Config.BestImageQuality = checkBestImageQuality.Checked;
}
private void checkSpellCheck_CheckedChanged(object sender, EventArgs e){
Config.EnableSpellCheck = checkSpellCheck.Checked;
PromptRestart();

View File

@@ -63,6 +63,7 @@
this.labelSize = new System.Windows.Forms.Label();
this.panelMiscellaneous = new System.Windows.Forms.Panel();
this.durationUpdateTimer = new System.Windows.Forms.Timer(this.components);
this.checkMediaPreviews = new System.Windows.Forms.CheckBox();
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).BeginInit();
this.tableLayoutDurationButtons.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).BeginInit();
@@ -273,11 +274,11 @@
// checkSkipOnLinkClick
//
this.checkSkipOnLinkClick.AutoSize = true;
this.checkSkipOnLinkClick.Location = new System.Drawing.Point(6, 28);
this.checkSkipOnLinkClick.Location = new System.Drawing.Point(6, 51);
this.checkSkipOnLinkClick.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkSkipOnLinkClick.Name = "checkSkipOnLinkClick";
this.checkSkipOnLinkClick.Size = new System.Drawing.Size(113, 17);
this.checkSkipOnLinkClick.TabIndex = 1;
this.checkSkipOnLinkClick.TabIndex = 2;
this.checkSkipOnLinkClick.Text = "Skip On Link Click";
this.toolTip.SetToolTip(this.checkSkipOnLinkClick, "Skips current notification when a link\r\ninside the notification is clicked.");
this.checkSkipOnLinkClick.UseVisualStyleBackColor = true;
@@ -297,32 +298,32 @@
// labelIdlePause
//
this.labelIdlePause.AutoSize = true;
this.labelIdlePause.Location = new System.Drawing.Point(3, 83);
this.labelIdlePause.Location = new System.Drawing.Point(3, 106);
this.labelIdlePause.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelIdlePause.Name = "labelIdlePause";
this.labelIdlePause.Size = new System.Drawing.Size(89, 13);
this.labelIdlePause.TabIndex = 3;
this.labelIdlePause.TabIndex = 4;
this.labelIdlePause.Text = "Pause When Idle";
//
// comboBoxIdlePause
//
this.comboBoxIdlePause.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxIdlePause.FormattingEnabled = true;
this.comboBoxIdlePause.Location = new System.Drawing.Point(5, 99);
this.comboBoxIdlePause.Location = new System.Drawing.Point(5, 122);
this.comboBoxIdlePause.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.comboBoxIdlePause.Name = "comboBoxIdlePause";
this.comboBoxIdlePause.Size = new System.Drawing.Size(144, 21);
this.comboBoxIdlePause.TabIndex = 4;
this.comboBoxIdlePause.TabIndex = 5;
this.toolTip.SetToolTip(this.comboBoxIdlePause, "Pauses new notifications after going idle for a set amount of time.");
//
// checkNonIntrusive
//
this.checkNonIntrusive.AutoSize = true;
this.checkNonIntrusive.Location = new System.Drawing.Point(6, 51);
this.checkNonIntrusive.Location = new System.Drawing.Point(6, 74);
this.checkNonIntrusive.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkNonIntrusive.Name = "checkNonIntrusive";
this.checkNonIntrusive.Size = new System.Drawing.Size(128, 17);
this.checkNonIntrusive.TabIndex = 2;
this.checkNonIntrusive.TabIndex = 3;
this.checkNonIntrusive.Text = "Non-Intrusive Popups";
this.toolTip.SetToolTip(this.checkNonIntrusive, "When not idle and the cursor is within the notification window area,\r\nit will be delayed until the cursor moves away to prevent accidental clicks.");
this.checkNonIntrusive.UseVisualStyleBackColor = true;
@@ -389,6 +390,7 @@
//
this.panelGeneral.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelGeneral.Controls.Add(this.checkMediaPreviews);
this.panelGeneral.Controls.Add(this.checkColumnName);
this.panelGeneral.Controls.Add(this.checkSkipOnLinkClick);
this.panelGeneral.Controls.Add(this.checkNonIntrusive);
@@ -396,7 +398,7 @@
this.panelGeneral.Controls.Add(this.comboBoxIdlePause);
this.panelGeneral.Location = new System.Drawing.Point(9, 31);
this.panelGeneral.Name = "panelGeneral";
this.panelGeneral.Size = new System.Drawing.Size(322, 126);
this.panelGeneral.Size = new System.Drawing.Size(322, 149);
this.panelGeneral.TabIndex = 1;
//
// labelScrollSpeedValue
@@ -437,7 +439,7 @@
//
this.labelLocation.AutoSize = true;
this.labelLocation.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelLocation.Location = new System.Drawing.Point(6, 372);
this.labelLocation.Location = new System.Drawing.Point(6, 395);
this.labelLocation.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelLocation.Name = "labelLocation";
this.labelLocation.Size = new System.Drawing.Size(70, 20);
@@ -458,7 +460,7 @@
this.panelLocation.Controls.Add(this.radioLocBL);
this.panelLocation.Controls.Add(this.radioLocCustom);
this.panelLocation.Controls.Add(this.radioLocBR);
this.panelLocation.Location = new System.Drawing.Point(9, 395);
this.panelLocation.Location = new System.Drawing.Point(9, 418);
this.panelLocation.Name = "panelLocation";
this.panelLocation.Size = new System.Drawing.Size(322, 165);
this.panelLocation.TabIndex = 5;
@@ -473,7 +475,7 @@
this.panelTimer.Controls.Add(this.checkTimerCountDown);
this.panelTimer.Controls.Add(this.labelDurationValue);
this.panelTimer.Controls.Add(this.trackBarDuration);
this.panelTimer.Location = new System.Drawing.Point(9, 204);
this.panelTimer.Location = new System.Drawing.Point(9, 227);
this.panelTimer.Name = "panelTimer";
this.panelTimer.Size = new System.Drawing.Size(322, 144);
this.panelTimer.TabIndex = 3;
@@ -492,7 +494,7 @@
//
this.labelTimer.AutoSize = true;
this.labelTimer.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelTimer.Location = new System.Drawing.Point(6, 181);
this.labelTimer.Location = new System.Drawing.Point(6, 204);
this.labelTimer.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelTimer.Name = "labelTimer";
this.labelTimer.Size = new System.Drawing.Size(48, 20);
@@ -503,7 +505,7 @@
//
this.labelSize.AutoSize = true;
this.labelSize.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelSize.Location = new System.Drawing.Point(6, 584);
this.labelSize.Location = new System.Drawing.Point(6, 607);
this.labelSize.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelSize.Name = "labelSize";
this.labelSize.Size = new System.Drawing.Size(40, 20);
@@ -519,7 +521,7 @@
this.panelMiscellaneous.Controls.Add(this.labelScrollSpeedValue);
this.panelMiscellaneous.Controls.Add(this.trackBarScrollSpeed);
this.panelMiscellaneous.Controls.Add(this.labelScrollSpeed);
this.panelMiscellaneous.Location = new System.Drawing.Point(9, 607);
this.panelMiscellaneous.Location = new System.Drawing.Point(9, 630);
this.panelMiscellaneous.Name = "panelMiscellaneous";
this.panelMiscellaneous.Size = new System.Drawing.Size(322, 92);
this.panelMiscellaneous.TabIndex = 7;
@@ -529,6 +531,18 @@
this.durationUpdateTimer.Interval = 200;
this.durationUpdateTimer.Tick += new System.EventHandler(this.durationUpdateTimer_Tick);
//
// checkMediaPreviews
//
this.checkMediaPreviews.AutoSize = true;
this.checkMediaPreviews.Location = new System.Drawing.Point(6, 28);
this.checkMediaPreviews.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkMediaPreviews.Name = "checkMediaPreviews";
this.checkMediaPreviews.Size = new System.Drawing.Size(131, 17);
this.checkMediaPreviews.TabIndex = 1;
this.checkMediaPreviews.Text = "Show Media Previews";
this.toolTip.SetToolTip(this.checkMediaPreviews, "Shows image and video thumbnails in the notification window.");
this.checkMediaPreviews.UseVisualStyleBackColor = true;
//
// TabSettingsNotifications
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -542,7 +556,7 @@
this.Controls.Add(this.labelGeneral);
this.Controls.Add(this.panelTimer);
this.Name = "TabSettingsNotifications";
this.Size = new System.Drawing.Size(340, 708);
this.Size = new System.Drawing.Size(340, 731);
this.ParentChanged += new System.EventHandler(this.TabSettingsNotifications_ParentChanged);
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).EndInit();
this.tableLayoutDurationButtons.ResumeLayout(false);
@@ -601,5 +615,6 @@
private System.Windows.Forms.Timer durationUpdateTimer;
private System.Windows.Forms.RadioButton radioSizeCustom;
private System.Windows.Forms.RadioButton radioSizeAuto;
private System.Windows.Forms.CheckBox checkMediaPreviews;
}
}

View File

@@ -63,6 +63,7 @@ namespace TweetDuck.Core.Other.Settings{
checkNotificationTimer.Checked = Config.DisplayNotificationTimer;
checkTimerCountDown.Enabled = checkNotificationTimer.Checked;
checkTimerCountDown.Checked = Config.NotificationTimerCountDown;
checkMediaPreviews.Checked = Config.NotificationMediaPreviews;
checkSkipOnLinkClick.Checked = Config.NotificationSkipOnLinkClick;
checkNonIntrusive.Checked = Config.NotificationNonIntrusiveMode;
@@ -96,6 +97,7 @@ namespace TweetDuck.Core.Other.Settings{
checkColumnName.CheckedChanged += checkColumnName_CheckedChanged;
checkNotificationTimer.CheckedChanged += checkNotificationTimer_CheckedChanged;
checkTimerCountDown.CheckedChanged += checkTimerCountDown_CheckedChanged;
checkMediaPreviews.CheckedChanged += checkMediaPreviews_CheckedChanged;
checkSkipOnLinkClick.CheckedChanged += checkSkipOnLinkClick_CheckedChanged;
checkNonIntrusive.CheckedChanged += checkNonIntrusive_CheckedChanged;
@@ -218,6 +220,10 @@ namespace TweetDuck.Core.Other.Settings{
notification.ShowNotificationForSettings(true);
}
private void checkMediaPreviews_CheckedChanged(object sender, EventArgs e){
Config.NotificationMediaPreviews = checkMediaPreviews.Checked;
}
private void checkSkipOnLinkClick_CheckedChanged(object sender, EventArgs e){
Config.NotificationSkipOnLinkClick = checkSkipOnLinkClick.Checked;
}

View File

@@ -34,7 +34,7 @@ namespace TweetDuck.Core.Other.Settings{
private void tbCustomSound_TextChanged(object sender, EventArgs e){
bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text);
tbCustomSound.ForeColor = isEmpty || File.Exists(tbCustomSound.Text) ? SystemColors.WindowText : Color.Maroon;
tbCustomSound.ForeColor = isEmpty || File.Exists(tbCustomSound.Text) ? SystemColors.WindowText : Color.Red;
btnPlaySound.Enabled = !isEmpty;
btnResetSound.Enabled = !isEmpty;
}

View File

@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Net;
using System.Windows.Forms;
@@ -10,8 +9,6 @@ using TweetDuck.Core.Other;
namespace TweetDuck.Core.Utils{
static class BrowserUtils{
public const string TweetDeckURL = "https://tweetdeck.twitter.com";
public static string HeaderAcceptLanguage{
get{
string culture = Program.Culture.Name;
@@ -27,13 +24,6 @@ namespace TweetDuck.Core.Utils{
public static string HeaderUserAgent => Program.BrandName+" "+Application.ProductVersion;
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
public const string BackgroundColorFix = "let e=document.createElement('style');document.head.appendChild(e);e.innerHTML='body::before{background:#1c6399!important}'";
public static readonly string[] DictionaryWords = {
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
};
public static void SetupCefArgs(IDictionary<string, string> args){
if (!Program.SystemConfig.HardwareAcceleration){
args["disable-gpu"] = "1";
@@ -117,14 +107,6 @@ namespace TweetDuck.Core.Utils{
browser.GetHost().SetZoomLevel(Math.Log(percentage/100.0, 1.2));
}
public static bool IsTweetDeckWebsite(IFrame frame){
return frame.Url.Contains("//tweetdeck.twitter.com/");
}
public static bool IsTwitterWebsite(IFrame frame){
return frame.Url.Contains("//twitter.com/");
}
#if DEBUG
public static void HandleConsoleMessage(object sender, ConsoleMessageEventArgs e){
Debug.WriteLine("[Console] {0} ({1}:{2})", e.Message, e.Source, e.Line);

View File

@@ -1,39 +0,0 @@
using System;
using System.Text.RegularExpressions;
using TweetDuck.Data;
namespace TweetDuck.Core.Utils{
static class CommandLineArgsParser{
private static readonly Lazy<Regex> SplitRegex = new Lazy<Regex>(() => new Regex(@"([^=\s]+(?:=(?:\S*""[^""]*?""\S*|\S*))?)", RegexOptions.Compiled), false);
public static CommandLineArgs ReadCefArguments(string argumentString){
CommandLineArgs args = new CommandLineArgs();
if (string.IsNullOrWhiteSpace(argumentString)){
return args;
}
foreach(Match match in SplitRegex.Value.Matches(argumentString)){
string matchValue = match.Value;
int indexEquals = matchValue.IndexOf('=');
string key, value;
if (indexEquals == -1){
key = matchValue.TrimStart('-');
value = "1";
}
else{
key = matchValue.Substring(0, indexEquals).TrimStart('-');
value = matchValue.Substring(indexEquals+1).Trim('"');
}
if (key.Length != 0){
args.SetValue(key, value);
}
}
return args;
}
}
}

View File

@@ -4,6 +4,8 @@ using System.Text.RegularExpressions;
namespace TweetDuck.Core.Utils{
static class StringUtils{
public static readonly string[] EmptyArray = new string[0];
public static string ExtractBefore(string str, char search, int startIndex = 0){
int index = str.IndexOf(search, startIndex);
return index == -1 ? str : str.Substring(0, index);

106
Core/Utils/TwitterUtils.cs Normal file
View File

@@ -0,0 +1,106 @@
using System;
using CefSharp;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using TweetDuck.Core.Other;
namespace TweetDuck.Core.Utils{
static class TwitterUtils{
public const string TweetDeckURL = "https://tweetdeck.twitter.com";
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
public const string BackgroundColorFix = "let e=document.createElement('style');document.head.appendChild(e);e.innerHTML='body::before{background:#1c6399!important}'";
private static readonly Lazy<Regex> RegexAccountLazy = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/([^/]+)/?$", RegexOptions.Compiled), false);
public static Regex RegexAccount => RegexAccountLazy.Value;
public static readonly string[] DictionaryWords = {
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
};
public enum ImageQuality{
Default, Orig
}
public static bool IsTweetDeckWebsite(IFrame frame){
return frame.Url.Contains("//tweetdeck.twitter.com/");
}
public static bool IsTwitterWebsite(IFrame frame){
return frame.Url.Contains("//twitter.com/");
}
private static string ExtractImageBaseLink(string url){
int dot = url.LastIndexOf('/');
return dot == -1 ? url : StringUtils.ExtractBefore(url, ':', dot);
}
public static string GetImageLink(string url, ImageQuality quality){
if (quality == ImageQuality.Orig){
string result = ExtractImageBaseLink(url);
if (result != url || url.Contains("//pbs.twimg.com/media/")){
result += ":orig";
}
return result;
}
else{
return url;
}
}
public static void DownloadImage(string url, string username, ImageQuality quality){
DownloadImages(new string[]{ url }, username, quality);
}
public static void DownloadImages(string[] urls, string username, ImageQuality quality){
if (urls.Length == 0){
return;
}
string firstImageLink = GetImageLink(urls[0], quality);
int qualityIndex = firstImageLink.IndexOf(':', firstImageLink.LastIndexOf('/'));
string file = BrowserUtils.GetFileNameFromUrl(ExtractImageBaseLink(firstImageLink));
string ext = Path.GetExtension(file); // includes dot
string[] fileNameParts = qualityIndex == -1 ? new string[]{
Path.ChangeExtension(file, null)
} : new string[]{
username,
Path.ChangeExtension(file, null),
firstImageLink.Substring(qualityIndex+1)
};
using(SaveFileDialog dialog = new SaveFileDialog{
AutoUpgradeEnabled = true,
OverwritePrompt = urls.Length == 1,
Title = "Save image",
FileName = $"{string.Join(" ", fileNameParts.Where(part => part.Length > 0))}{ext}",
Filter = (urls.Length == 1 ? "Image" : "Images")+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
}){
if (dialog.ShowDialog() == DialogResult.OK){
void OnFailure(Exception ex){
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
}
if (urls.Length == 1){
BrowserUtils.DownloadFileAsync(firstImageLink, dialog.FileName, null, OnFailure);
}
else{
string pathBase = Path.ChangeExtension(dialog.FileName, null);
string pathExt = Path.GetExtension(dialog.FileName);
for(int index = 0; index < urls.Length; index++){
BrowserUtils.DownloadFileAsync(GetImageLink(urls[index], quality), $"{pathBase} {index+1}{pathExt}", null, OnFailure);
}
}
}
}
}
}
}

View File

@@ -103,7 +103,7 @@ namespace TweetDuck.Data{
public string[] KeyValue{
get{
int index = Identifier.IndexOf(KeySeparator);
return index == -1 ? new string[0] : Identifier.Substring(index+1).Split(KeySeparator);
return index == -1 ? StringUtils.EmptyArray : Identifier.Substring(index+1).Split(KeySeparator);
}
}

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace TweetDuck.Data{
sealed class CommandLineArgs{
@@ -32,6 +33,36 @@ namespace TweetDuck.Data{
}
}
public static CommandLineArgs ReadCefArguments(string argumentString){
CommandLineArgs args = new CommandLineArgs();
if (string.IsNullOrWhiteSpace(argumentString)){
return args;
}
foreach(Match match in Regex.Matches(argumentString, @"([^=\s]+(?:=(?:\S*""[^""]*?""\S*|\S*))?)")){
string matchValue = match.Value;
int indexEquals = matchValue.IndexOf('=');
string key, value;
if (indexEquals == -1){
key = matchValue.TrimStart('-');
value = "1";
}
else{
key = matchValue.Substring(0, indexEquals).TrimStart('-');
value = matchValue.Substring(indexEquals+1).Trim('"');
}
if (key.Length != 0){
args.SetValue(key, value);
}
}
return args;
}
private readonly HashSet<string> flags = new HashSet<string>();
private readonly Dictionary<string, string> values = new Dictionary<string, string>();

View File

@@ -6,12 +6,15 @@ using System.Reflection;
using System.Runtime.Serialization;
namespace TweetDuck.Data.Serialization{
sealed class FileSerializer<T> where T : ISerializedObject{
sealed class FileSerializer<T>{
private const string NewLineReal = "\r\n";
private const string NewLineCustom = "\r~\n";
private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter();
public delegate void HandleUnknownPropertiesHandler(T obj, Dictionary<string, string> data);
public HandleUnknownPropertiesHandler HandleUnknownProperties { get; set; }
private readonly Dictionary<string, PropertyInfo> props;
private readonly Dictionary<Type, ITypeConverter> converters;
@@ -48,8 +51,10 @@ namespace TweetDuck.Data.Serialization{
}
public void Read(string file, T obj){
Dictionary<string, string> unknownProperties = new Dictionary<string, string>(4);
using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))){
if (reader.Peek() == 0){
if (reader.Peek() <= 1){
throw new FormatException("Input appears to be a binary file.");
}
@@ -75,11 +80,19 @@ namespace TweetDuck.Data.Serialization{
throw new SerializationException($"Invalid file format, cannot convert value: {value} (property: {property})");
}
}
else if (!obj.OnReadUnknownProperty(property, value)){
throw new SerializationException($"Invalid file format, unknown property: {property}+");
else{
unknownProperties[property] = value;
}
}
}
if (unknownProperties.Count > 0){
HandleUnknownProperties?.Invoke(obj, unknownProperties);
if (unknownProperties.Count > 0){
throw new SerializationException($"Invalid file format, unknown properties: {string.Join(", ", unknownProperties.Keys)}+");
}
}
}
private class BasicTypeConverter : ITypeConverter{

View File

@@ -1,5 +0,0 @@
namespace TweetDuck.Data.Serialization{
interface ISerializedObject{
bool OnReadUnknownProperty(string property, string value);
}
}

View File

@@ -2,11 +2,8 @@
namespace TweetDuck.Data.Serialization{
sealed class SingleTypeConverter<T> : ITypeConverter{
public delegate string FuncConvertToString<U>(U value);
public delegate U FuncConvertToObject<U>(string value);
public FuncConvertToString<T> ConvertToString { get; set; }
public FuncConvertToObject<T> ConvertToObject { get; set; }
public Func<T, string> ConvertToString { get; set; }
public Func<string, T> ConvertToObject { get; set; }
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
try{

View File

@@ -42,7 +42,7 @@ namespace TweetDuck.Plugins{
private readonly string pathRoot;
private readonly string pathData;
private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(4){
private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(8){
{ "NAME", "" },
{ "DESCRIPTION", "" },
{ "AUTHOR", "(anonymous)" },

View File

@@ -40,6 +40,7 @@ namespace TweetDuck.Plugins{
}
}
}catch(FileNotFoundException){
Save(file);
}catch(DirectoryNotFoundException){
}catch(Exception e){
Program.Reporter.HandleException("Plugin Configuration Error", "Could not read the plugin configuration file. If you continue, the list of disabled plugins will be reset to default.", true, e);

View File

@@ -8,7 +8,7 @@ using System.Threading;
using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Core;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Other;
using TweetDuck.Core.Other.Settings.Export;
using TweetDuck.Core.Utils;
@@ -22,8 +22,8 @@ namespace TweetDuck{
public const string BrandName = "TweetDuck";
public const string Website = "https://tweetduck.chylex.com";
public const string VersionTag = "1.8.3";
public const string VersionFull = "1.8.3.0";
public const string VersionTag = "1.8.5";
public const string VersionFull = "1.8.5.0";
public static readonly Version Version = new Version(VersionTag);
public static readonly bool IsPortable = File.Exists("makeportable");
@@ -34,13 +34,13 @@ namespace TweetDuck{
public static readonly string ScriptPath = Path.Combine(ProgramPath, "scripts");
public static readonly string PluginPath = Path.Combine(ProgramPath, "plugins");
public static readonly string UserConfigFilePath = Path.Combine(StoragePath, "TD_UserConfig.cfg");
public static readonly string SystemConfigFilePath = Path.Combine(StoragePath, "TD_SystemConfig.cfg");
public static readonly string PluginConfigFilePath = Path.Combine(StoragePath, "TD_PluginConfig.cfg");
public static readonly string PluginDataPath = Path.Combine(StoragePath, "TD_Plugins");
private static readonly string InstallerPath = Path.Combine(StoragePath, "TD_Updates");
public static string UserConfigFilePath => Path.Combine(StoragePath, "TD_UserConfig.cfg");
public static string SystemConfigFilePath => Path.Combine(StoragePath, "TD_SystemConfig.cfg");
public static string PluginConfigFilePath => Path.Combine(StoragePath, "TD_PluginConfig.cfg");
private static string ErrorLogFilePath => Path.Combine(StoragePath, "TD_Log.txt");
private static string ConsoleLogFilePath => Path.Combine(StoragePath, "TD_Console.txt");
@@ -80,60 +80,43 @@ namespace TweetDuck{
}
if (Arguments.HasFlag(Arguments.ArgRestart)){
for(int attempt = 0; attempt < 21; attempt++){
LockManager.Result lockResult = LockManager.Lock();
LockManager.Result lockResult = LockManager.LockWait(10000);
if (lockResult == LockManager.Result.Success){
break;
}
else if (lockResult == LockManager.Result.Fail){
while(lockResult != LockManager.Result.Success){
if (lockResult == LockManager.Result.Fail){
FormMessage.Error("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", FormMessage.OK);
return;
}
else if (attempt == 20){
if (FormMessage.Warning("TweetDuck Cannot Restart", "TweetDuck is taking too long to close.", FormMessage.Retry, FormMessage.Exit)){
attempt /= 2;
continue;
}
else if (!FormMessage.Warning("TweetDuck Cannot Restart", "TweetDuck is taking too long to close.", FormMessage.Retry, FormMessage.Exit)){
return;
}
else Thread.Sleep(500);
lockResult = LockManager.LockWait(5000);
}
}
else{
LockManager.Result lockResult = LockManager.Lock();
if (lockResult == LockManager.Result.HasProcess){
if (LockManager.LockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, new UIntPtr((uint)LockManager.LockingProcess.Id), IntPtr.Zero);
if (WindowsUtils.TrySleepUntil(() => {
LockManager.LockingProcess.Refresh();
return LockManager.LockingProcess.HasExited || (LockManager.LockingProcess.MainWindowHandle != IntPtr.Zero && LockManager.LockingProcess.Responding);
}, 2000, 250)){
return;
}
}
if (FormMessage.Error("TweetDuck is Already Running", "Another instance of TweetDuck is already running.\nDo you want to close it?", FormMessage.Yes, FormMessage.No)){
if (!LockManager.RestoreLockingProcess(2000) && FormMessage.Error("TweetDuck is Already Running", "Another instance of TweetDuck is already running.\nDo you want to close it?", FormMessage.Yes, FormMessage.No)){
if (!LockManager.CloseLockingProcess(10000, 5000)){
FormMessage.Error("TweetDuck Has Failed :(", "Could not close the other process.", FormMessage.OK);
return;
}
LockManager.Lock();
lockResult = LockManager.Lock();
}
else return;
}
else if (lockResult != LockManager.Result.Success){
if (lockResult != LockManager.Result.Success){
FormMessage.Error("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", FormMessage.OK);
return;
}
}
ReloadConfig();
SystemConfig = SystemConfig.Load(SystemConfigFilePath);
ReloadConfig();
if (Arguments.HasFlag(Arguments.ArgImportCookies)){
ExportManager.ImportCookies();
@@ -157,7 +140,7 @@ namespace TweetDuck{
#endif
};
CommandLineArgsParser.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
CommandLineArgs.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs);
Cef.EnableHighDPISupport();

View File

@@ -6,6 +6,6 @@ PM> Install-Package CefSharp.WinForms -Version 57.0.0
PM> Install-Package Microsoft.VC120.CRT.JetBrains
```
After building, run either `_postbuild.bat` if you want to package the files yourself, or `bld/RUN BUILD.bat` to generate installer files using Inno Setup (make sure the Inno Setup binaries are on your PATH).
When building the `Release` configuration, the folder will only contain files intended for distribution (no debug symbols or other unnecessary files). You may package the files yourself, or use `bld/RUN BUILD.bat` to generate installer files using Inno Setup (make sure the Inno Setup binaries are on your PATH).
Built files are then available in **bin/x86**, installer files are generated in **bld/Output**. If you decide to release a custom version publicly, please make it clear that it is not the original TweetDuck.
Built files are available in **bin/x86**, installer files are generated in **bld/Output**. If you decide to release a custom version publicly, please make it clear that it is not the original TweetDuck.

View File

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

View File

@@ -1,9 +1,3 @@
constructor(){
super({
requiresPageReload: true
})
}
enabled(){
// elements & data
this.css = null;
@@ -16,13 +10,11 @@ enabled(){
fontSize: "12px",
hideTweetActions: true,
moveTweetActionsToRight: true,
revertReplies: false,
themeColorTweaks: true,
roundedScrollBars: false,
revertIcons: true,
smallComposeTextSize: false,
optimizeAnimations: true,
avatarRadius: 10
avatarRadius: 2
};
this.firstTimeLoad = null;
@@ -60,7 +52,6 @@ enabled(){
this.firstTimeLoad = obj === null;
this.onStageReady();
this.injectDeciderReplyHook(obj && obj.revertReplies);
};
if (this.$$wasLoadedBefore){
@@ -129,9 +120,7 @@ enabled(){
let itemEditDesign = $('<li class="is-selectable"><a href="#" data-action>Edit layout &amp; design</a></li>');
itemTD.after(itemEditDesign);
itemEditDesign.on("click", "a", function(){
new customDesignModal();
});
itemEditDesign.on("click", "a", this.openEditDesignDialog);
itemEditDesign.hover(function(){
$(this).addClass("is-selected");
@@ -249,18 +238,7 @@ enabled(){
}
});
// decider injections
this.injectDeciderReplyHook = enable => {
let prevFunc = TD.decider.updateFromBackend;
TD.decider.updateFromBackend = function(data){
data["simplified_replies"] = !enable;
return prevFunc.apply(this, arguments);
};
TD.decider.updateForGuestId();
this.$requiresReload = enable;
};
this.openEditDesignDialog = () => new customDesignModal();
// animation optimization
this.optimizations = null;
@@ -353,7 +331,7 @@ enabled(){
}
this.css.insert("#general_settings .cf { display: none !important }");
this.css.insert("#general_settings .divider-bar::after { display: inline-block; padding-top: 10px; line-height: 17px; content: 'Use the new | Edit layout & design | option in the Settings to modify TweetDeck theme, column width, font size, and other features.' }");
this.css.insert("#settings-modal .js-setting-list li:nth-child(3) { border-bottom: 1px solid #ccd6dd }");
this.css.insert(".txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: "+this.config.fontSize+" !important }");
this.css.insert(".avatar { border-radius: "+this.config.avatarRadius+"% !important }");
@@ -389,22 +367,6 @@ enabled(){
this.css.insert(".compose-text { font-size: 12px !important; height: 120px !important }");
}
if (!this.config.roundedScrollBars){
this.css.insert(".scroll-styled-v:not(.antiscroll-inner)::-webkit-scrollbar { width: 8px }");
this.css.insert(".scroll-styled-h:not(.antiscroll-inner)::-webkit-scrollbar { height: 8px }");
this.css.insert(".scroll-styled-v::-webkit-scrollbar-thumb { border-radius: 0 }");
this.css.insert(".scroll-styled-h::-webkit-scrollbar-thumb { border-radius: 0 }");
this.css.insert(".antiscroll-scrollbar { border-radius: 0 }");
this.css.insert(".antiscroll-scrollbar-vertical { margin-top: 0 }");
this.css.insert(".antiscroll-scrollbar-horizontal { margin-left: 0 }");
this.css.insert(".app-columns-container::-webkit-scrollbar { height: 9px !important }");
}
if (this.config.revertReplies){
this.css.insert(".activity-header + .tweet .tweet-context { margin-left: -35px }");
this.css.insert(".activity-header + .tweet .tweet-context .obj-left { margin-right: 5px }");
}
if (this.config.revertIcons){
this.icons = document.createElement("style");
this.icons.innerHTML = `
@@ -497,7 +459,9 @@ enabled(){
.icon-list-filled:before{content:"\\f014";font-family:tweetdeckold}
.icon-user-filled:before{content:"\\f035";font-family:tweetdeckold}
.drawer .btn .icon, .app-header .btn .icon { line-height: 1em !important }
.column-header .column-type-icon { bottom: 26px !important }
.is-options-open .column-type-icon { bottom: 25px !important }
.tweet-footer { margin-top: 6px !important }`;
document.head.appendChild(this.icons);
@@ -540,11 +504,6 @@ enabled(){
.txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: ${this.config.fontSize} !important }
.avatar { border-radius: ${this.config.avatarRadius}% !important }
${this.config.revertReplies ? `
.activity-header + .tweet .tweet-context { margin-left: -35px }
.activity-header + .tweet .tweet-context .obj-left { margin-right: 5px }
` : ``}
${this.config.revertIcons ? `
@font-face { font-family: 'tweetdeckold'; src: url(\"https://ton.twimg.com/tweetdeck-web/web/assets/fonts/tweetdeck-regular-webfont.5f4ea87976.woff\") format(\"woff\"); font-weight: normal; font-style: normal }
.icon-reply:before{content:"\\f006";font-family:tweetdeckold}
@@ -575,6 +534,32 @@ ready(){
// modal
$("[data-action='settings-menu']").on("click", this.onSettingsMenuClickedEvent);
$(".js-app").append('<div id="td-design-plugin-modal" class="js-modal settings-modal ovl scroll-v scroll-styled-v"></div>');
// global settings override
var me = this;
this.prevFuncSettingsGetInfo = TD.components.GlobalSettings.prototype.getInfo;
this.prevFuncSettingsSwitchTab = TD.components.GlobalSettings.prototype.switchTab;
TD.components.GlobalSettings.prototype.getInfo = function(){
let data = me.prevFuncSettingsGetInfo.apply(this, arguments);
data.tabs.push({
title: "Layout & Design",
action: "tdp-edit-design"
});
return data;
};
TD.components.GlobalSettings.prototype.switchTab = function(tab){
if (tab === "tdp-edit-design"){
me.openEditDesignDialog();
}
else{
return me.prevFuncSettingsSwitchTab.apply(this, arguments);
}
};
}
disabled(){
@@ -598,6 +583,9 @@ disabled(){
$(window).off("focus", this.onWindowFocusEvent);
$(window).off("blur", this.onWindowBlurEvent);
TD.components.GlobalSettings.prototype.getInfo = this.prevFuncSettingsGetInfo;
TD.components.GlobalSettings.prototype.switchTab = this.prevFuncSettingsSwitchTab;
$("[data-action='settings-menu']").off("click", this.onSettingsMenuClickedEvent);
$("#td-design-plugin-modal").remove();
}

View File

@@ -88,10 +88,6 @@
<input data-td-key="moveTweetActionsToRight" class="js-theme-checkbox touch-larger-label" type="checkbox">
Tweet actions on the right side
</label>
<label class="checkbox">
<input data-td-key="revertReplies" data-td-reload class="js-theme-checkbox touch-larger-label" type="checkbox">
Revert reply style (reloads page)
</label>
<!-- DESIGN -->
@@ -102,10 +98,6 @@
<input data-td-key="themeColorTweaks" class="js-theme-checkbox touch-larger-label" type="checkbox">
Theme color tweaks
</label>
<label class="checkbox">
<input data-td-key="roundedScrollBars" class="js-theme-checkbox touch-larger-label" type="checkbox">
Rounded scroll bars
</label>
<label class="checkbox">
<input data-td-key="revertIcons" class="js-theme-checkbox touch-larger-label" type="checkbox">
Revert icon design
@@ -130,7 +122,7 @@
</div>
<div class="td-avatar-shape-item-outer" data-td-key="avatarRadius" data-td-value="10">
<div class="td-avatar-shape" style="border-radius:10%"></div>
<label>Default</label>
<label>Legacy</label>
</div>
<div class="td-avatar-shape-item-outer" data-td-key="avatarRadius" data-td-value="30">
<div class="td-avatar-shape" style="border-radius:30%"></div>

View File

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

View File

@@ -29,13 +29,13 @@ enabled(){
// styles
this.css = window.TDPF_createCustomStyle(this);
this.css.insert(".emoji-keyboard { position: absolute; width: 15.35em; background-color: white; border-radius: 2px 2px 3px 3px; font-size: 24px; z-index: 9999 }");
this.css.insert(".emoji-keyboard { position: absolute; width: 15.35em; background-color: white; border-radius: 1px; font-size: 24px; z-index: 9999 }");
this.css.insert(".emoji-keyboard-list { height: 10.14em; padding: 0.1em; box-sizing: border-box; overflow-y: auto }");
this.css.insert(".emoji-keyboard-list .separator { height: 26px }");
this.css.insert(".emoji-keyboard-list img { padding: 0.1em !important; width: 1em; height: 1em; vertical-align: -0.1em; cursor: pointer }");
this.css.insert(".emoji-keyboard-search { height: auto; padding: 4px 10px 8px; background-color: #292f33; border-radius: 2px 2px 0 0 }");
this.css.insert(".emoji-keyboard-search { height: auto; padding: 4px 10px 8px; background-color: #292f33; border-radius: 1px 1px 0 0 }");
this.css.insert(".emoji-keyboard-search input { width: 100%; border-radius: 1px; }");
this.css.insert(".emoji-keyboard-skintones { height: 1.3em; text-align: center; background-color: #292f33; border-radius: 0 0 2px 2px }");
this.css.insert(".emoji-keyboard-skintones { height: 1.3em; text-align: center; background-color: #292f33; border-radius: 0 0 1px 1px }");
this.css.insert(".emoji-keyboard-skintones div { width: 0.8em; height: 0.8em; margin: 0.25em 0.1em; border-radius: 50%; display: inline-block; box-sizing: border-box; cursor: pointer }");
this.css.insert(".emoji-keyboard-skintones .sel { border: 2px solid rgba(0, 0, 0, 0.35); box-shadow: 0 0 2px 0 rgba(255, 255, 255, 0.65), 0 0 1px 0 rgba(255, 255, 255, 0.4) inset }");
this.css.insert(".emoji-keyboard-skintones :hover { border: 2px solid rgba(0, 0, 0, 0.25); box-shadow: 0 0 1px 0 rgba(255, 255, 255, 0.65), 0 0 1px 0 rgba(255, 255, 255, 0.25) inset }");
@@ -225,6 +225,10 @@ enabled(){
}
};
this.composerSendingEvent = function(e){
hideKeyboard();
};
this.documentClickEvent = function(e){
if (me.currentKeyboard && !e.target.classList.contains("js-compose-text")){
hideKeyboard();
@@ -246,6 +250,8 @@ enabled(){
}
ready(){
this.composeDrawer = $("[data-drawer='compose']");
this.composePanelScroller = $(".js-compose-scroller", ".js-docked-compose").first().children().first();
this.composePanelScroller.on("scroll", this.composerScrollEvent);
@@ -253,6 +259,7 @@ ready(){
$(document).on("click", this.documentClickEvent);
$(document).on("keydown", this.documentKeyEvent);
$(document).on("uiComposeImageAdded", this.uploadFilesEvent);
this.composeDrawer.on("uiComposeTweetSending", this.composerSendingEvent);
// HTML generation
@@ -373,5 +380,7 @@ disabled(){
$(document).off("click", this.documentClickEvent);
$(document).off("keydown", this.documentKeyEvent);
$(document).off("uiComposeImageAdded", this.uploadFilesEvent);
this.composeDrawer.off("uiComposeTweetSending", this.composerSendingEvent);
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
}

View File

@@ -8,7 +8,7 @@ Templates
chylex
[version]
1.0
1.0.1
[website]
https://tweetduck.chylex.com

View File

@@ -53,8 +53,10 @@ enabled(){
this.css.insert(".templates-modal-wrap { width: 100%; height: 100%; padding: 49px; position: absolute; z-index: 999; box-sizing: border-box; background-color: rgba(0, 0, 0, 0.5); }");
this.css.insert(".templates-modal { width: 100%; height: 100%; background-color: #fff; display: flex; }");
this.css.insert(".templates-modal > div { display: flex; flex-direction: column; }");
this.css.insert(".templates-modal-bottom { flex: 0 0 auto; padding: 16px; text-align: right; }");
this.css.insert(".templates-modal-bottom button { margin-left: 4px; }");
this.css.insert(".templates-modal-bottom { flex: 0 0 auto; padding: 16px; }");
this.css.insert(".template-list .templates-modal-bottom { display: flex; justify-content: space-between; }");
this.css.insert(".template-editor .templates-modal-bottom { text-align: right; }");
this.css.insert(".template-list { height: 100%; flex: 1 1 auto; }");
this.css.insert(".template-list ul { list-style-type: none; font-size: 24px; color: #222; flex: 1 1 auto; padding: 12px; overflow-y: auto; }");
@@ -265,6 +267,7 @@ enabled(){
<ul></ul>
<div class="templates-modal-bottom">
<button data-action="close" class="btn" style="background-color:#d2d2d2;border-color:#ccd0d2"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Close</span></button>
<button data-action="new-template" class="btn btn-positive"><i class="icon icon-plus icon-small padding-rs"></i><span class="label">New Template</span></button>
</div>
</div>
@@ -298,7 +301,7 @@ enabled(){
<div class="templates-modal-bottom">
<button data-action="editor-cancel" class="btn"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Cancel</span></button>
<button data-action="editor-confirm" class="btn btn-positive"><i class="icon icon-check icon-small padding-rs"></i><span class="label">Confirm</span></button>
<button data-action="editor-confirm" class="btn btn-positive" style="margin-left:4px"><i class="icon icon-check icon-small padding-rs"></i><span class="label">Confirm</span></button>
</div>
</div>
</div>
@@ -392,6 +395,10 @@ enabled(){
toggleEditor();
onTemplatesUpdated(true);
break;
case "close":
hideTemplateModal();
break;
}
$(this).blur();

View File

@@ -56,7 +56,7 @@
//
var appendToFunction = function(func, extension){
return function(){
var res = func.apply(this, arguments);
let res = func.apply(this, arguments);
extension.apply(this, arguments);
return res;
};
@@ -66,7 +66,7 @@
// Function: Returns true if an object has a specified property, otherwise returns false without throwing an error.
//
var ensurePropertyExists = function(obj, ...chain){
for(var index = 0; index < chain.length; index++){
for(let index = 0; index < chain.length; index++){
if (!obj.hasOwnProperty(chain[index])){
$TD.crashDebug("Missing property "+chain[index]+" in chain [obj]."+chain.join("."));
return false;
@@ -97,52 +97,84 @@
// Function: Event callback for a new tweet.
//
var onNewTweet = (function(){
let recentMessages = new Set();
let recentTweets = new Set();
let recentTweetTimer = null;
let startRecentTweetTimer = () => {
if (recentTweetTimer){
window.clearTimeout(recentTweetTimer);
}
recentTweetTimer = window.setTimeout(() => {
recentTweetTimer = null;
recentTweets.clear();
}, 10000);
let resetRecentTweets = () => {
recentTweetTimer = null;
recentTweets.clear();
};
let checkRecentTweet = id => {
if (recentTweets.size > 50){
recentTweets.clear();
}
else if (recentTweets.has(id)){
let startRecentTweetTimer = () => {
recentTweetTimer && window.clearTimeout(recentTweetTimer);
recentTweetTimer = window.setTimeout(resetRecentTweets, 20000);
};
let checkTweetCache = (set, id) => {
if (set.has(id)){
return true;
}
recentTweets.add(id);
startRecentTweetTimer();
if (set.size > 50){
set.clear();
}
set.add(id);
return false;
};
let fixMedia = (html, media) => {
return html.find(".js-media a[data-media-entity-id='"+media.mediaId+"']").css("background-image", 'url("'+media.thumb()+'")').removeClass("is-zoomable");
};
return function(column, tweet){
if (checkRecentTweet(tweet.id)){
if (tweet instanceof TD.services.TwitterConversation || tweet instanceof TD.services.TwitterConversationMessageEvent){
if (checkTweetCache(recentMessages, tweet.id)){
return;
}
}
else if (checkTweetCache(recentTweets, tweet.id)){
return;
}
startRecentTweetTimer();
if (column.model.getHasNotification()){
let previews = $TDX.notificationMediaPreviews;
let html = $(tweet.render({
withFooter: false,
withTweetActions: false,
withMediaPreview: true,
isMediaPreviewOff: true,
isMediaPreviewSmall: false,
isMediaPreviewLarge: false
isMediaPreviewOff: !previews,
isMediaPreviewSmall: previews,
isMediaPreviewLarge: false,
isMediaPreviewCompact: false,
isMediaPreviewInQuoted: previews,
thumbSizeClass: "media-size-medium"
}));
html.css("border", "0");
html.find("footer").last().remove(); // apparently withTweetActions breaks for certain tweets, nice
html.find(".js-media").last().remove(); // and quoted tweets still show media previews, nice nice
html.find(".js-quote-detail").removeClass("is-actionable"); // prevent quoted tweets from changing the cursor
html.find(".js-quote-detail").removeClass("is-actionable margin-b--8"); // prevent quoted tweets from changing the cursor and reduce bottom margin
if (previews){
html.find(".reverse-image-search").remove();
for(let media of tweet.getMedia()){
fixMedia(html, media);
}
if (tweet.quotedTweet){
for(let media of tweet.quotedTweet.getMedia()){
fixMedia(html, media).addClass("media-size-medium");
}
}
}
else if (tweet instanceof TD.services.TwitterActionOnTweet){
html.find(".js-media").remove();
}
html.find("a[href='#']").each(function(){ // remove <a> tags around links that don't lead anywhere (such as account names the tweet replied to)
this.outerHTML = this.innerHTML;
@@ -210,6 +242,9 @@
onAppReady.push(function(){
document.documentElement.setAttribute("data-td-theme", TD.settings.getTheme());
$TD.loadFontSizeClass(TD.settings.getFontSize());
$TD.loadNotificationHeadContents(getNotificationHeadContents());
});
//
@@ -237,12 +272,12 @@
onAppReady.push(function(){
$("[data-action='settings-menu']").click(function(){
setTimeout(function(){
var menu = $(".js-dropdown-content").children("ul").first();
let menu = $(".js-dropdown-content").children("ul").first();
if (menu.length === 0)return;
menu.children(".drp-h-divider").last().before('<li class="is-selectable" data-std><a href="#" data-action="tweetduck">TweetDuck</a></li>');
var button = menu.children("[data-std]");
let button = menu.children("[data-std]");
button.on("click", "a", function(){
$TD.openContextMenu();
@@ -272,7 +307,7 @@
var me = $(this);
if (e.type === "mouseenter"){
var text = me.text();
let text = me.text();
if (text.charCodeAt(text.length-1) !== 8230){ // horizontal ellipsis
return;
@@ -280,7 +315,7 @@
if ($TDX.expandLinksOnHover){
tooltipTimer = window.setTimeout(function(){
var expanded = me.attr("data-full-url");
let expanded = me.attr("data-full-url");
expanded = cutStart(expanded, "https://");
expanded = cutStart(expanded, "http://");
expanded = cutStart(expanded, "www.");
@@ -298,7 +333,7 @@
}
else if (e.type === "mouseleave"){
if ($TDX.expandLinksOnHover){
var prevText = me.attr("td-prev-text");
let prevText = me.attr("td-prev-text");
if (prevText){
me.text(prevText);
@@ -323,17 +358,28 @@
})();
//
// Block: Allow bypassing of t.co in context menus.
// Block: Allow bypassing of t.co and include media previews in context menus.
//
$(document.body).delegate("a", "contextmenu", function(){
$TD.setLastRightClickedLink($(this).attr("data-full-url") || "");
});
$(document.body).delegate("a.js-media-image-link", "contextmenu", function(){
let me = $(this)[0];
if (me.firstElementChild){
$TD.setLastRightClickedImage(me.firstElementChild.getAttribute("src"));
}
else{
$TD.setLastRightClickedImage(me.style.backgroundImage.replace(/url\(['"]?(.*?)['"]?\)/, "$1"));
}
});
//
// Block: Hook into the notification sound effect.
//
(function(){
var soundEle = document.getElementById("update-sound");
let soundEle = document.getElementById("update-sound");
soundEle.play = prependToFunction(soundEle.play, function(){
return $TDX.muteNotifications || $TDX.hasCustomNotificationSound;
@@ -352,12 +398,12 @@
return !!highlightedColumnObj;
};
var updateHighlightedTweet = function(ele, obj, link, embeddedLink){
var updateHighlightedTweet = function(ele, obj, link, embeddedLink, author, imageList){
highlightedTweetEle = ele;
highlightedTweetObj = obj;
if (lastTweet !== link){
$TD.setLastHighlightedTweet(link, embeddedLink);
$TD.setLastHighlightedTweet(link, embeddedLink, author, imageList);
lastTweet = link;
}
};
@@ -375,28 +421,31 @@
app.delegate("article.js-stream-item", "mouseenter mouseleave", function(e){
if (e.type === "mouseenter"){
var me = $(this);
let me = $(this);
if (!me[0].hasAttribute("data-account-key") || (!highlightedColumnObj && !updateHighlightedColumn(me.closest("section.js-column")))){
return;
}
var tweet = highlightedColumnObj.findChirp(me.attr("data-tweet-id")) || highlightedColumnObj.findChirp(me.attr("data-key"));
let tweet = highlightedColumnObj.findChirp(me.attr("data-tweet-id")) || highlightedColumnObj.findChirp(me.attr("data-key"));
if (tweet){
if (tweet.chirpType === TD.services.ChirpBase.TWEET){
var link = tweet.getChirpURL();
var embedded = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
let link = tweet.getChirpURL();
let embedded = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
let username = tweet.getMainUser().screenName;
let images = tweet.hasImage() ? tweet.getMedia().filter(item => !item.isAnimatedGif).map(item => item.entity.media_url_https+":small").join(";") : "";
// TODO maybe handle embedded images too?
updateHighlightedTweet(me, tweet, link || "", embedded || "");
updateHighlightedTweet(me, tweet, link || "", embedded || "", username, images);
}
else{
updateHighlightedTweet(me, tweet, "", "");
updateHighlightedTweet(me, tweet, "", "", "", "");
}
}
}
else if (e.type === "mouseleave"){
updateHighlightedTweet(null, null, "", "");
updateHighlightedTweet(null, null, "", "", "", "");
}
});
})();
@@ -419,11 +468,11 @@
window.TDGF_triggerScreenshot = function(){
if (selectedTweet){
var tweetWidth = Math.floor(selectedTweet.width());
var parent = selectedTweet.parent();
let tweetWidth = Math.floor(selectedTweet.width());
let parent = selectedTweet.parent();
var isDetail = parent.hasClass("js-tweet-detail");
var isReply = !isDetail && (parent.hasClass("js-replies-to") || parent.hasClass("js-replies-before"));
let isDetail = parent.hasClass("js-tweet-detail");
let isReply = !isDetail && (parent.hasClass("js-replies-to") || parent.hasClass("js-replies-before"));
selectedTweet = selectedTweet.clone();
selectedTweet.children().first().addClass($(document.documentElement).attr("class")).css("padding-bottom", "0");
@@ -457,13 +506,13 @@
selectedTweet.find(".js-poll-link").remove();
selectedTweet.find(".td-screenshot-remove").remove();
var testTweet = selectedTweet.clone().css({
let testTweet = selectedTweet.clone().css({
position: "absolute",
left: "-999px",
width: tweetWidth+"px"
}).appendTo(document.body);
var realHeight = Math.floor(testTweet.height());
let realHeight = Math.floor(testTweet.height());
testTweet.remove();
$TD.screenshotTweet(selectedTweet.html(), tweetWidth, realHeight);
@@ -480,7 +529,12 @@
app.delegate(".js-compose-text,.js-reply-tweetbox", "paste", function(e){
for(let item of e.originalEvent.clipboardData.items){
if (item.type.startsWith("image/")){
$(this).closest(".rpl").find(".js-reply-popout").click(); // popout direct messages
if (!$(this).closest(".rpl").find(".js-reply-popout").click().length){ // popout direct messages
if ($(".js-add-image-button").is(".is-disabled")){ // tweetdeck does not check upload count properly
return;
}
}
uploader.addFilesToUpload([ item.getAsFile() ]);
$(".js-compose-text", ".js-docked-compose").focus();
@@ -499,31 +553,33 @@
};
var tryCloseModal1 = function(){
var modal = $("#open-modal");
let modal = $("#open-modal");
return modal.is(":visible") && tryClickSelector("a.mdl-dismiss", modal);
};
var tryCloseModal2 = function(){
var modal = $(".js-modals-container");
let modal = $(".js-modals-container");
return modal.length && tryClickSelector("a.mdl-dismiss", modal);
};
var tryCloseHighlightedColumn = function(){
if (highlightedColumnEle){
var column = highlightedColumnEle.closest(".js-column");
let column = highlightedColumnEle.closest(".js-column");
return (column.is(".is-shifted-2") && tryClickSelector(".js-tweet-social-proof-back", column)) || (column.is(".is-shifted-1") && tryClickSelector(".js-column-back", column));
}
};
window.TDGF_onMouseClickExtra = function(button){
if (button === 1){ // back button
tryClickSelector(".is-shifted-2 .js-tweet-social-proof-back", ".js-modal-panel") ||
tryClickSelector(".is-shifted-1 .js-column-back", ".js-modal-panel") ||
tryCloseModal1() ||
tryCloseModal2() ||
tryClickSelector(".js-inline-compose-close") ||
tryCloseHighlightedColumn() ||
tryClickSelector(".js-app-content.is-open .js-drawer-close:visible") ||
tryClickSelector(".is-shifted-2 .js-tweet-social-proof-back, .is-shifted-2 .js-dm-participants-back") ||
$(".js-column-back").click();
$(".is-shifted-1 .js-column-back").click();
}
else if (button === 2){ // forward button
if (highlightedTweetEle){
@@ -538,7 +594,7 @@
//
$(document).on("dataTweetSent", function(e, data){
if (data.response.state && data.response.state === "scheduled"){
var column = Object.values(TD.controller.columnManager.getAll()).find(column => column.model.state.type === "scheduled");
let column = Object.values(TD.controller.columnManager.getAll()).find(column => column.model.state.type === "scheduled");
if (column){
setTimeout(function(){
@@ -591,16 +647,18 @@
})();
//
// Block: Swap shift key functionality for selecting accounts.
// Block: Swap shift key functionality for selecting accounts, and refocus the textbox afterwards.
//
onAppReady.push(function(){
var toggleEventShiftKey = function(e){
var onAccountClick = function(e){
if ($TDX.switchAccountSelectors){
e.shiftKey = !e.shiftKey;
}
$(".js-compose-text", ".js-docked-compose").focus();
};
$(".js-drawer[data-drawer='compose']").delegate(".js-account-list > .js-account-item", "click", toggleEventShiftKey);
$(".js-drawer[data-drawer='compose']").delegate(".js-account-list > .js-account-item", "click", onAccountClick);
if (!ensurePropertyExists(TD, "components", "AccountSelector", "prototype", "refreshPostingAccounts")){
return;
@@ -609,7 +667,7 @@
TD.components.AccountSelector.prototype.refreshPostingAccounts = appendToFunction(TD.components.AccountSelector.prototype.refreshPostingAccounts, function(){
if (!this.$node.attr("td-account-selector-hook")){
this.$node.attr("td-account-selector-hook", "1");
this.$node.delegate(".js-account-item", "click", toggleEventShiftKey);
this.$node.delegate(".js-account-item", "click", onAccountClick);
}
});
});
@@ -645,41 +703,63 @@
// Block: Inject custom CSS and layout into the page.
//
(function(){
var styleOfficial = document.createElement("style");
let styleOfficial = document.createElement("style");
document.head.appendChild(styleOfficial);
styleOfficial.sheet.insertRule("a[data-full-url] { word-break: break-all; }", 0); // break long urls
styleOfficial.sheet.insertRule(".column-nav-link .attribution { position: absolute; }", 0); // fix cut off account names
styleOfficial.sheet.insertRule(".txt-base-smallest .sprite-verified-mini { width: 13px !important; height: 13px !important; background-position: -223px -99px !important; }", 0); // fix cut off badge icon when zoomed in
styleOfficial.sheet.insertRule(".keyboard-shortcut-list { vertical-align: top; }", 0); // fix keyboard navigation alignment
styleOfficial.sheet.insertRule(".sprite-logo { background-position: -5px -46px !important; }", 0); // fix TweetDeck logo on certain zoom levels
styleOfficial.sheet.insertRule(".app-navigator .tooltip { display: none !important; }", 0); // hide broken tooltips in the menu
styleOfficial.sheet.insertRule(".account-inline .username { vertical-align: 10%; }", 0); // move usernames a bit higher
let addRule = (rule) => {
styleOfficial.sheet.insertRule(rule, 0);
};
styleOfficial.sheet.insertRule(".btn-compose, .app-search-fake, .app-search-input { border-radius: 1px; }", 0); // use consistent menu button radius
styleOfficial.sheet.insertRule(".is-condensed .app-header-inner { padding-top: 10px !important; }", 0); // add extra padding to menu buttons when condensed
styleOfficial.sheet.insertRule(".is-condensed .btn-compose { padding: 8px !important; }", 0); // fix compose button icon when condensed
styleOfficial.sheet.insertRule(".app-header:not(.is-condensed) .nav-user-info { padding: 0 5px; }", 0); // add padding to user info
addRule("a[data-full-url] { word-break: break-all; }"); // break long urls
addRule(".keyboard-shortcut-list { vertical-align: top; }"); // fix keyboard navigation alignment
addRule(".account-inline .username { vertical-align: 10%; }"); // move usernames a bit higher
addRule(".character-count-compose { width: 40px !important; }"); // fix strangely wide character count element
styleOfficial.sheet.insertRule(".app-title { display: none; }", 0); // hide TweetDeck logo
styleOfficial.sheet.insertRule(".nav-user-info { bottom: 10px !important; }", 0); // move user info
styleOfficial.sheet.insertRule(".app-navigator { bottom: 50px !important; }", 0); // move navigation
styleOfficial.sheet.insertRule(".column-navigator-overflow { bottom: 192px !important; }", 0); // move column list
addRule(".column-nav-link .attribution { position: absolute; }"); // fix cut off account names
addRule(".txt-base-smallest .sprite-verified-mini { width: 13px !important; height: 13px !important; background-position: -223px -99px !important; }"); // fix cut off badge icon when zoomed in
styleOfficial.sheet.insertRule(".column .column-header { height: 49px !important; }", 0); // fix one pixel space below column header
styleOfficial.sheet.insertRule(".column:not(.is-options-open) .column-header { border-bottom: none; }", 0); // fix one pixel space below column header
addRule(".btn, .mdl, .mdl-content, .app-search-fake, .app-search-input, .popover, .lst-modal, .media-item { border-radius: 1px !important; }"); // square-ify buttons, inputs, dialogs, menus, media previews
addRule(".dropdown-menu, .list-item-last, .quoted-tweet { border-radius: 0 !important; }"); // square-ify dropdowns, quoted tweets, and account selectors
addRule(".prf-header { border-radius: 0; }"); // fix user account header border
styleOfficial.sheet.insertRule(".activity-header { align-items: center !important; margin-bottom: 4px; }", 0); // tweak alignment of avatar and text in notifications
styleOfficial.sheet.insertRule(".activity-header .tweet-timestamp { line-height: unset }", 0); // fix timestamp position in notifications
addRule(".accs li, .accs img { border-radius: 0 !important; }"); // square-ify retweet account selector
addRule(".accs-header { padding-left: 0 !important; }"); // fix retweet account selector heading
styleOfficial.sheet.insertRule(".app-columns-container::-webkit-scrollbar-track { border-left: 0; }", 0); // remove weird border in the column container scrollbar
styleOfficial.sheet.insertRule(".app-columns-container { bottom: 0 !important; }", 0); // move column container scrollbar to bottom to fit updated style
addRule(".scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb, .antiscroll-scrollbar { border-radius: 0; }"); // square-ify scroll bars
addRule(".antiscroll-scrollbar-vertical { margin-top: 0; }"); // square-ify scroll bars
addRule(".antiscroll-scrollbar-horizontal { margin-left: 0; }"); // square-ify scroll bars
addRule(".scroll-styled-v:not(.antiscroll-inner)::-webkit-scrollbar { width: 8px; }"); // square-ify scroll bars
addRule(".scroll-styled-h:not(.antiscroll-inner)::-webkit-scrollbar { height: 8px; }"); // square-ify scroll bars
addRule(".app-columns-container::-webkit-scrollbar { height: 9px !important; }"); // square-ify scroll bars
styleOfficial.sheet.insertRule(".js-column-header .column-header-link { padding: 0; }", 0); // fix column header tooltip hover box
styleOfficial.sheet.insertRule(".js-column-header .column-header-link .icon { padding: 9px 4px; width: calc(1em + 8px); height: 100%; box-sizing: border-box; }", 0); // fix column header tooltip hover box
addRule(".is-condensed .app-header-inner { padding-top: 10px !important; }"); // add extra padding to menu buttons when condensed
addRule(".is-condensed .btn-compose { padding: 8px !important; }"); // fix compose button icon when condensed
addRule(".app-header:not(.is-condensed) .nav-user-info { padding: 0 5px; }"); // add padding to user info
styleOfficial.sheet.insertRule(".is-video a:not([href*='youtu']), .is-gif .js-media-gif-container { cursor: alias; }", 0); // change cursor on unsupported videos
styleOfficial.sheet.insertRule(".is-video a:not([href*='youtu']) .icon-bg-dot, .is-gif .icon-bg-dot { color: #bd3d37; }", 0); // change play icon color on unsupported videos
addRule(".app-title { display: none; }"); // hide TweetDeck logo
addRule(".nav-user-info { bottom: 10px !important; }"); // move user info
addRule(".app-navigator { bottom: 50px !important; }"); // move navigation
addRule(".column-navigator-overflow { bottom: 192px !important; }"); // move column list
addRule(".app-navigator .tooltip { display: none !important; }"); // hide broken tooltips in the menu
addRule(".column .column-header { height: 49px !important; }"); // fix one pixel space below column header
addRule(".column:not(.is-options-open) .column-header { border-bottom: none; }"); // fix one pixel space below column header
addRule(".is-options-open .column-type-icon { bottom: 27px; }"); // fix one pixel space below column header
addRule(".activity-header { align-items: center !important; margin-bottom: 4px; }"); // tweak alignment of avatar and text in notifications
addRule(".activity-header .tweet-timestamp { line-height: unset; }"); // fix timestamp position in notifications
addRule("html[data-td-theme='light'] .stream-item:not(:hover) .js-user-actions-menu { color: #000; border-color: #000; opacity: 0.25; }"); // make follow notification button nicer
addRule("html[data-td-theme='dark'] .stream-item:not(:hover) .js-user-actions-menu { color: #fff; border-color: #fff; opacity: 0.25; }"); // make follow notification button nicer
addRule(".app-columns-container::-webkit-scrollbar-track { border-left: 0; }"); // remove weird border in the column container scrollbar
addRule(".app-columns-container { bottom: 0 !important; }"); // move column container scrollbar to bottom to fit updated style
addRule(".js-column-header .column-header-link { padding: 0; }"); // fix column header tooltip hover box
addRule(".js-column-header .column-header-link .icon { padding: 9px 4px; width: calc(1em + 8px); height: 100%; box-sizing: border-box; }"); // fix column header tooltip hover box
addRule(".is-video a:not([href*='youtu']), .is-gif .js-media-gif-container { cursor: alias; }"); // change cursor on unsupported videos
addRule(".is-video a:not([href*='youtu']) .icon-bg-dot, .is-gif .icon-bg-dot { color: #bd3d37; }"); // change play icon color on unsupported videos
window.TDGF_reinjectCustomCSS = function(styles){
$("#tweetduck-custom-css").remove();
@@ -719,7 +799,7 @@
!ensurePropertyExists(TD, "ui", "Column", "prototype", "playGifIfNotManuallyPaused"))return;
TD.components.MediaGallery.prototype._loadTweet = appendToFunction(TD.components.MediaGallery.prototype._loadTweet, function(){
var media = this.chirp.getMedia().find(media => media.mediaId === this.clickedMediaEntityId);
let media = this.chirp.getMedia().find(media => media.mediaId === this.clickedMediaEntityId);
if (media && media.isVideo && media.service !== "youtube"){
$TD.openBrowser(this.clickedLink);
@@ -738,8 +818,8 @@
TD.mustaches["status/media_thumb.mustache"] = TD.mustaches["status/media_thumb.mustache"].replace("is-gif", "is-gif is-paused");
app.delegate(".js-gif-play", "click", function(e){
var parent = $(e.target).closest(".js-tweet").first();
var link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
let parent = $(e.target).closest(".js-tweet").first();
let link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
$TD.openBrowser(link.attr("href"));
e.stopPropagation();
@@ -750,7 +830,7 @@
// Block: Fix youtu.be previews not showing up for https links.
//
if (ensurePropertyExists(TD, "services", "TwitterMedia")){
var media = TD.services.TwitterMedia;
let media = TD.services.TwitterMedia;
if (!ensurePropertyExists(media, "YOUTUBE_TINY_RE") ||
!ensurePropertyExists(media, "YOUTUBE_LONG_RE") ||
@@ -787,8 +867,19 @@
}
//
// Block: Memory cleanup check and execution.
// Block: Custom reload function with memory cleanup.
//
window.TDGF_reload = function(){
let session = TD.storage.feedController.getAll()
.filter(feed => !!feed.getTopSortIndex())
.reduce((obj, feed) => (obj[feed.privateState.key] = feed.getTopSortIndex(), obj), {});
$TD.setSessionData("gc", JSON.stringify(session)).then(() => {
window.gc && window.gc();
window.location.reload();
});
};
window.TDGF_tryRunCleanup = function(){
// all textareas are empty
if ($("textarea").is(function(){
@@ -815,11 +906,62 @@
}
// cleanup
window.gc && window.gc();
window.location.reload();
window.TDGF_reload();
return true;
};
if (window.TD_SESSION && window.TD_SESSION.gc){
var state;
try{
state = JSON.parse(window.TD_SESSION.gc);
}catch(err){
$TD.crashDebug("Invalid session gc data: "+window.TD_SESSION.gc);
state = {};
}
var showMissedNotifications = function(){
let tweets = [];
let columns = {};
let tmp = new TD.services.ChirpBase;
for(let column of Object.values(TD.controller.columnManager.getAll())){
for(let feed of column.getFeeds()){
if (feed.privateState.key in state){
tmp.sortIndex = state[feed.privateState.key];
for(let tweet of [].concat.apply([], column.updateArray.map(function(chirp){
return chirp.getUnreadChirps(tmp);
}))){
tweets.push(tweet);
columns[tweet.id] = column;
}
}
}
}
tweets.sort(TD.util.chirpReverseColumnSort);
for(let tweet of tweets){
onNewTweet(columns[tweet.id], tweet);
}
};
$(document).one("dataColumnsLoaded", function(){
let columns = Object.values(TD.controller.columnManager.getAll());
let remaining = columns.length;
for(let column of columns){
column.ui.getChirpContainer().one("dataColumnFeedUpdated", () => {
if (--remaining === 0){
setTimeout(showMissedNotifications, 1);
}
});
}
});
}
//
// Block: Disable TweetDeck metrics.
//
@@ -842,9 +984,9 @@
//
$(document).one("TD.ready", function(){
onAppReady.forEach(func => func());
onAppReady = null;
$TD.loadFontSizeClass(TD.settings.getFontSize());
$TD.loadNotificationHeadContents(getNotificationHeadContents());
delete window.TD_SESSION;
if (window.TD_PLUGINS){
window.TD_PLUGINS.onReady();

View File

@@ -98,7 +98,7 @@
//
window.TDPF_requestReload = function(){
if (!isReloading){
window.setTimeout(() => location.reload(), 1);
window.setTimeout(window.TDGF_reload, 1);
isReloading = true;
}
};

View File

@@ -11,22 +11,30 @@
var style = document.createElement("style");
document.head.appendChild(style);
style.sheet.insertRule("body { overflow: hidden !important; }", 0); // remove scrollbar
style.sheet.insertRule(".topbar { display: none !important; }", 0); // hide top bar
style.sheet.insertRule(".page-canvas, .buttons, .btn, input { border-radius: 0 !important; }", 0); // sharpen borders
style.sheet.insertRule("input { padding: 5px 8px 4px !important; }", 0); // tweak input padding
let addRule = (rule) => {
style.sheet.insertRule(rule, 0);
};
style.sheet.insertRule("#doc { width: 100%; height: 100%; margin: 0; position: absolute; display: table; }", 0); // center everything
style.sheet.insertRule("#page-outer { display: table-cell; vertical-align: middle; }", 0); // center everything
style.sheet.insertRule("#page-container { padding: 0 20px !important; width: 100% !important; box-sizing: border-box !important; }", 0); // center everything
style.sheet.insertRule(".page-canvas { margin: 0 auto !important; }", 0); // center everything
addRule("body { overflow: hidden !important; background-color: #1c6399 !important; }"); // remove scrollbar and change background
addRule(".page-canvas { box-shadow: 0 0 150px rgba(255, 255, 255, 0.3) !important; }"); // change page box shadow
addRule(".topbar { display: none !important; }"); // hide top bar
addRule(".page-canvas, .buttons, .btn, input { border-radius: 0 !important; }"); // sharpen borders
addRule("input { padding: 5px 8px 4px !important; }"); // tweak input padding
addRule("button[type='submit'] { border: 1px solid rgba(0, 0, 0, 0.3) !important; border-radius: 0 !important; }"); // style buttons
addRule("#doc { width: 100%; height: 100%; margin: 0; position: absolute; display: table; }"); // center everything
addRule("#page-outer { display: table-cell; vertical-align: middle; }"); // center everything
addRule("#page-container { padding: 0 20px !important; width: 100% !important; box-sizing: border-box !important; }"); // center everything
addRule(".page-canvas { margin: 0 auto !important; }"); // center everything
if (location.pathname === "/logout"){
style.sheet.insertRule(".page-canvas { width: auto !important; max-width: 888px; }", 0); // fix min width
style.sheet.insertRule(".signout-wrapper { width: auto !important; }", 0); // fix min width
style.sheet.insertRule(".btn { margin: 0 4px !important; }", 0); // add margin around buttons
style.sheet.insertRule(".btn.cancel { border: 1px solid #bbc1c5 !important; }", 0); // add border to cancel button
style.sheet.insertRule(".aside p { display: none; }", 0); // hide text below the logout dialog
addRule(".page-canvas { width: auto !important; max-width: 888px; }"); // fix min width
addRule(".signout-wrapper { width: auto !important; margin: 0 auto !important; }"); // fix min width and margins
addRule(".signout { margin: 60px 0 54px !important; }"); // fix dialog margins
addRule(".buttons { padding-bottom: 0 !important; }"); // fix dialog margins
addRule(".aside { display: none; }"); // hide elements around logout dialog
addRule(".buttons button, .buttons a { display: inline-block; margin: 0 4px !important; border: 1px solid rgba(0, 0, 0, 0.3) !important; border-radius: 0 !important; }"); // style buttons
}
};

View File

@@ -131,11 +131,15 @@
}
#tweetduck-changelog p {
margin: 0 0 2px 30px;
margin: 8px 8px 0 6px;
}
#tweetduck-changelog p.li {
margin: 0 8px 2px 30px;
display: list-item;
}
#tweetduck-changelog p.l2 {
#tweetduck-changelog .l2 {
margin-left: 50px;
}
@@ -268,13 +272,15 @@
return md.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/^##? (.*?)$/gm, "<h2>$1</h2>")
.replace(/^### (.*?)$/gm, "<h3>$1</h3>")
.replace(/^- (.*?)$/gm, "<p>$1</p>")
.replace(/^ - (.*?)$/gm, "<p class='l2'>$1</p>")
.replace(/^- (.*?)$/gm, "<p class='li'>$1</p>")
.replace(/^ - (.*?)$/gm, "<p class='li l2'>$1</p>")
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
.replace(/\*(.*?)\*/g, "<em>$1</em>")
.replace(/`(.*?)`/g, "<code>$1</code>")
.replace(/\[(.*?)\]\((.*?)\)/g, "<a href='$2'>$1</a>")
.replace(/^([a-z0-9].*?)$/gmi, "<p>$1</p>")
.replace(/\n\r?\n/g, "<br>");
};

View File

@@ -88,7 +88,7 @@
<Compile Include="Core\Controls\NumericUpDownEx.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Core\Handling\BrowserProcessHandler.cs" />
<Compile Include="Core\Handling\General\BrowserProcessHandler.cs" />
<Compile Include="Core\Handling\ContextMenuBase.cs" />
<Compile Include="Core\Handling\ContextMenuBrowser.cs" />
<Compile Include="Core\FormBrowser.cs">
@@ -97,8 +97,9 @@
<Compile Include="Core\FormBrowser.Designer.cs">
<DependentUpon>FormBrowser.cs</DependentUpon>
</Compile>
<Compile Include="Core\Handling\KeyboardHandlerNotification.cs" />
<Compile Include="Core\Handling\RequestHandlerBrowser.cs" />
<Compile Include="Core\Handling\RequestHandler.cs" />
<Compile Include="Core\Handling\General\RequestHandlerBase.cs" />
<Compile Include="Core\Handling\ResourceHandlerNotification.cs" />
<Compile Include="Core\Notification\FormNotificationMain.cs">
<SubType>Form</SubType>
@@ -113,8 +114,8 @@
<DependentUpon>FormNotificationBase.cs</DependentUpon>
</Compile>
<Compile Include="Core\Handling\ContextMenuNotification.cs" />
<Compile Include="Core\Handling\JavaScriptDialogHandler.cs" />
<Compile Include="Core\Handling\LifeSpanHandler.cs" />
<Compile Include="Core\Handling\General\JavaScriptDialogHandler.cs" />
<Compile Include="Core\Handling\General\LifeSpanHandler.cs" />
<Compile Include="Core\Notification\FormNotificationTweet.cs">
<SubType>Form</SubType>
</Compile>
@@ -167,6 +168,7 @@
</Compile>
<Compile Include="Core\Utils\BrowserProcesses.cs" />
<Compile Include="Core\Utils\StringUtils.cs" />
<Compile Include="Core\Utils\TwitterUtils.cs" />
<Compile Include="Data\CombinedFileStream.cs" />
<Compile Include="Core\Other\Settings\Export\ExportFileFlags.cs" />
<Compile Include="Core\Other\Settings\Export\ExportManager.cs" />
@@ -199,14 +201,12 @@
</Compile>
<Compile Include="Core\Bridge\CallbackBridge.cs" />
<Compile Include="Data\CommandLineArgs.cs" />
<Compile Include="Core\Utils\CommandLineArgsParser.cs" />
<Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
<Compile Include="Data\Serialization\FileSerializer.cs" />
<Compile Include="Data\InjectedHTML.cs" />
<Compile Include="Data\Serialization\ISerializedObject.cs" />
<Compile Include="Data\Serialization\ITypeConverter.cs" />
<Compile Include="Data\Serialization\SingleTypeConverter.cs" />
<Compile Include="Data\TwoKeyDictionary.cs" />
@@ -296,15 +296,11 @@
<ItemGroup>
<EmbeddedResource Include="Core\FormBrowser.resx">
<DependentUpon>FormBrowser.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Core\Other\FormAbout.resx">
<DependentUpon>FormAbout.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Core\Other\FormPlugins.resx">
<DependentUpon>FormPlugins.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Core\Other\FormSettings.resx">
<DependentUpon>FormSettings.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Updates\FormUpdateDownload.resx">
<DependentUpon>FormUpdateDownload.cs</DependentUpon>
@@ -352,8 +348,7 @@
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>del "$(TargetPath).config"
xcopy "$(ProjectDir)LICENSE.md" "$(TargetDir)" /Y
<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
@@ -371,6 +366,7 @@ rmdir "$(ProjectDir)bin\Debug"
rmdir "$(ProjectDir)bin\Release"
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
del "$(TargetDir)plugins\official\emoji-keyboard\emoji-instructions.txt"
if $(ConfigurationName) == Debug (
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
@@ -378,7 +374,6 @@ if $(ConfigurationName) == Debug (
xcopy "$(ProjectDir)Resources\Plugins\.debug\*" "$(TargetDir)plugins\user\.debug\" /E /Y
)</PostBuildEvent>
</PropertyGroup>
<Import Project="packages\cef.redist.x64.3.2987.1601\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.2987.1601\build\cef.redist.x64.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
@@ -390,14 +385,15 @@ if $(ConfigurationName) == Debug (
<Error Condition="!Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.props'))" />
<Error Condition="!Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets'))" />
</Target>
<Import Project="packages\cef.redist.x64.3.2987.1601\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.2987.1601\build\cef.redist.x64.targets')" />
<Import Project="packages\cef.redist.x86.3.2987.1601\build\cef.redist.x86.targets" Condition="Exists('packages\cef.redist.x86.3.2987.1601\build\cef.redist.x86.targets')" />
<Import Project="packages\CefSharp.Common.57.0.0\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.57.0.0\build\CefSharp.Common.targets')" />
<Import Project="packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.57.0.0\build\CefSharp.WinForms.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
<Target Name="AfterBuild" Condition="$(ConfigurationName) == Release">
<Exec Command="del &quot;$(TargetDir)*.pdb&quot;" />
<Exec Command="del &quot;$(TargetDir)*.xml&quot;" />
<Delete Files="$(TargetDir)CefSharp.BrowserSubprocess.exe" />
<Delete Files="$(TargetDir)devtools_resources.pak" />
<Delete Files="$(TargetDir)widevinecdmadapter.dll" />
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26430.12
VisualStudioVersion = 15.0.26430.16
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck", "TweetDuck.csproj", "{2389A7CD-E0D3-4706-8294-092929A33A2D}"
EndProject

View File

@@ -30,7 +30,7 @@ namespace TweetDuck.Updates{
}
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
if (e.Frame.IsMain && TwitterUtils.IsTweetDeckWebsite(e.Frame)){
ScriptLoader.ExecuteFile(e.Frame, "update.js");
Check(false);
}

View File

@@ -1,6 +0,0 @@
del "bin\x86\Release\*.xml"
del "bin\x86\Release\*.pdb"
del "bin\x86\Release\CefSharp.BrowserSubprocess.exe"
del "bin\x86\Release\devtools_resources.pak"
del "bin\x86\Release\d3dcompiler_43.dll"
del "bin\x86\Release\widevinecdmadapter.dll"

View File

@@ -1,33 +0,0 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
namespace UnitTests.Core{
[TestClass]
public class TestCommandLineArgsParser{
[TestMethod]
public void TestEmptyString(){
Assert.AreEqual(0, CommandLineArgsParser.ReadCefArguments("").Count);
Assert.AreEqual(0, CommandLineArgsParser.ReadCefArguments(" ").Count);
}
[TestMethod]
public void TestValidString(){
CommandLineArgs args = CommandLineArgsParser.ReadCefArguments("--aaa --bbb --first-value=123 --SECOND-VALUE=\"a b c d e\"\r\n--ccc");
// cef has no flags, flag arguments have a value of 1
// the processing removes all dashes in front of each key
Assert.AreEqual(5, args.Count);
Assert.IsTrue(args.HasValue("aaa"));
Assert.IsTrue(args.HasValue("bbb"));
Assert.IsTrue(args.HasValue("ccc"));
Assert.IsTrue(args.HasValue("first-value"));
Assert.IsTrue(args.HasValue("second-value"));
Assert.AreEqual("1", args.GetValue("aaa", string.Empty));
Assert.AreEqual("1", args.GetValue("bbb", string.Empty));
Assert.AreEqual("1", args.GetValue("ccc", string.Empty));
Assert.AreEqual("123", args.GetValue("first-value", string.Empty));
Assert.AreEqual("a b c d e", args.GetValue("second-value", string.Empty));
}
}
}

View File

@@ -0,0 +1,45 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Core.Utils;
namespace UnitTests.Core{
[TestClass]
public class TestTwitterUtils{
[TestMethod]
public void TestAccountRegex(){
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc"));
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/chylexmc"));
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc/"));
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/chylexmc/"));
Assert.AreEqual("chylexmc", TwitterUtils.RegexAccount.Match("http://twitter.com/chylexmc").Groups[1].Value);
Assert.AreEqual("123", TwitterUtils.RegexAccount.Match("http://twitter.com/123").Groups[1].Value);
Assert.AreEqual("_", TwitterUtils.RegexAccount.Match("http://twitter.com/_").Groups[1].Value);
Assert.AreEqual("Abc_123", TwitterUtils.RegexAccount.Match("http://twitter.com/Abc_123").Groups[1].Value);
Assert.AreEqual("Abc_123", TwitterUtils.RegexAccount.Match("https://twitter.com/Abc_123/").Groups[1].Value);
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc/status"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://nottwitter.com/chylexmc"));
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("www.twitter.com/chylexmc"));
}
[TestMethod]
public void TestImageQualityLink(){
Assert.AreEqual("https://pbs.twimg.com/profile_images/123", TwitterUtils.GetImageLink("https://pbs.twimg.com/profile_images/123", TwitterUtils.ImageQuality.Default));
Assert.AreEqual("https://pbs.twimg.com/profile_images/123", TwitterUtils.GetImageLink("https://pbs.twimg.com/profile_images/123", TwitterUtils.ImageQuality.Orig));
Assert.AreEqual("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.GetImageLink("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.ImageQuality.Default));
Assert.AreEqual("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.GetImageLink("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.ImageQuality.Orig));
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg", TwitterUtils.ImageQuality.Default));
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg", TwitterUtils.ImageQuality.Orig));
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.ImageQuality.Default));
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.ImageQuality.Orig));
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.ImageQuality.Default));
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetImageLink("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.ImageQuality.Orig));
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Data;
@@ -139,5 +140,31 @@ namespace UnitTests.Data{
Directory.Delete("cfs_directory", true);
}
[TestMethod]
public void TestLongIdentifierSuccess(){
TestUtils.WriteText("cfs_long_identifier_fail_in", "test");
string identifier = string.Join("", Enumerable.Repeat("x", 255));
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_long_identifier_success"))){
cfs.WriteFile(identifier, "cfs_long_identifier_fail_in");
cfs.Flush();
}
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_long_identifier_success"))){
Assert.AreEqual(identifier, cfs.ReadFile().Identifier);
}
}
[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void TestLongIdentifierFail(){
TestUtils.WriteText("cfs_long_identifier_fail_in", "test");
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_long_identifier_fail"))){
cfs.WriteFile(string.Join("", Enumerable.Repeat("x", 256)), "cfs_long_identifier_fail_in");
}
}
}
}

View File

@@ -153,5 +153,30 @@ namespace UnitTests.Data{
Assert.IsTrue(args.HasValue("-value"));
Assert.AreEqual("Here is some text!", args.GetValue("-value", string.Empty));
}
[TestMethod]
public void TestCefEmptyString(){
Assert.AreEqual(0, CommandLineArgs.ReadCefArguments("").Count);
Assert.AreEqual(0, CommandLineArgs.ReadCefArguments(" ").Count);
}
[TestMethod]
public void TestCefValidString(){
CommandLineArgs args = CommandLineArgs.ReadCefArguments("--aaa --bbb --first-value=123 --SECOND-VALUE=\"a b c d e\"\r\n--ccc");
// cef has no flags, flag arguments have a value of 1
// the processing removes all dashes in front of each key
Assert.AreEqual(5, args.Count);
Assert.IsTrue(args.HasValue("aaa"));
Assert.IsTrue(args.HasValue("bbb"));
Assert.IsTrue(args.HasValue("ccc"));
Assert.IsTrue(args.HasValue("first-value"));
Assert.IsTrue(args.HasValue("second-value"));
Assert.AreEqual("1", args.GetValue("aaa", string.Empty));
Assert.AreEqual("1", args.GetValue("bbb", string.Empty));
Assert.AreEqual("1", args.GetValue("ccc", string.Empty));
Assert.AreEqual("123", args.GetValue("first-value", string.Empty));
Assert.AreEqual("a b c d e", args.GetValue("second-value", string.Empty));
}
}
}

View File

@@ -10,16 +10,12 @@ namespace UnitTests.Data{
A, B, C, D, E
}
private class SerializationTestBasic : ISerializedObject{
private class SerializationTestBasic{
public bool TestBool { get; set; }
public int TestInt { get; set; }
public string TestString { get; set; }
public string TestStringNull { get; set; }
public TestEnum TestEnum { get; set; }
bool ISerializedObject.OnReadUnknownProperty(string property, string value){
return false;
}
}
[TestMethod]

View File

@@ -48,10 +48,10 @@
</Choose>
<ItemGroup>
<Compile Include="Core\TestStringUtils.cs" />
<Compile Include="Core\TestTwitterUtils.cs" />
<Compile Include="Data\TestCombinedFileStream.cs" />
<Compile Include="Core\TestBrowserUtils.cs" />
<Compile Include="Data\TestCommandLineArgs.cs" />
<Compile Include="Core\TestCommandLineArgsParser.cs" />
<Compile Include="Data\TestFileSerializer.cs" />
<Compile Include="Data\TestInjectedHTML.cs" />
<Compile Include="Data\TestTwoKeyDictionary.cs" />