mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 19:32:10 +02:00
Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
a391d8ee83 | |||
48c38f6e1d | |||
37c5fba162 | |||
23e99b1d44 | |||
8432240a47 | |||
a4bab743d6 | |||
60766789ab | |||
ca014f881c | |||
886eabe26c | |||
65b7167b5f | |||
abbdde851e | |||
54ac54aba6 | |||
184340f400 | |||
93dd6813e8 | |||
b689b08711 | |||
1479a097d6 | |||
e4967ea46d | |||
3f28f18fb4 | |||
1b90e0f65e | |||
756ed649e6 | |||
fbc423e2a7 | |||
f04cdb6a13 |
@@ -6,7 +6,7 @@ using TweetDuck.Data.Serialization;
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class SystemConfig{
|
||||
private static readonly FileSerializer<SystemConfig> Serializer = new FileSerializer<SystemConfig>{
|
||||
OnReadUnknownProperty = (obj, property, value) => false
|
||||
// HandleUnknownProperties = (obj, data) => {}
|
||||
};
|
||||
|
||||
public static readonly bool IsHardwareAccelerationSupported = File.Exists(Path.Combine(Program.ProgramPath, "libEGL.dll")) &&
|
||||
@@ -16,6 +16,9 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
private bool _hardwareAcceleration = true;
|
||||
|
||||
public bool EnableBrowserGCReload { get; set; } = true;
|
||||
public int BrowserMemoryThreshold { get; set; } = 400;
|
||||
|
||||
// SPECIAL PROPERTIES
|
||||
|
||||
public bool HardwareAcceleration{
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using TweetDuck.Core;
|
||||
@@ -10,9 +11,26 @@ using TweetDuck.Data.Serialization;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class UserConfig{
|
||||
private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>{
|
||||
OnReadUnknownProperty = (obj, property, value) => false
|
||||
};
|
||||
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);
|
||||
@@ -77,9 +95,6 @@ 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;
|
||||
|
@@ -8,25 +8,25 @@ namespace TweetDuck.Core.Bridge{
|
||||
|
||||
public static string GenerateScript(Environment environment){
|
||||
string Bool(bool value){
|
||||
return value ? "true," : "false,";
|
||||
return value ? "true;" : "false;";
|
||||
}
|
||||
|
||||
StringBuilder build = new StringBuilder().Append("window.$TDX={");
|
||||
StringBuilder build = new StringBuilder().Append("(function(x){");
|
||||
|
||||
build.Append("expandLinksOnHover:").Append(Bool(Program.UserConfig.ExpandLinksOnHover));
|
||||
build.Append("x.expandLinksOnHover=").Append(Bool(Program.UserConfig.ExpandLinksOnHover));
|
||||
|
||||
if (environment == Environment.Browser){
|
||||
build.Append("switchAccountSelectors:").Append(Bool(Program.UserConfig.SwitchAccountSelectors));
|
||||
build.Append("muteNotifications:").Append(Bool(Program.UserConfig.MuteNotifications));
|
||||
build.Append("hasCustomNotificationSound:").Append(Bool(Program.UserConfig.NotificationSoundPath.Length > 0));
|
||||
build.Append("notificationMediaPreviews:").Append(Bool(Program.UserConfig.NotificationMediaPreviews));
|
||||
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 (environment == Environment.Notification){
|
||||
build.Append("skipOnLinkClick:").Append(Bool(Program.UserConfig.NotificationSkipOnLinkClick));
|
||||
build.Append("x.skipOnLinkClick=").Append(Bool(Program.UserConfig.NotificationSkipOnLinkClick));
|
||||
}
|
||||
|
||||
return build.Append("}").ToString();
|
||||
return build.Append("})(window.$TDX=window.$TDX||{})").ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,12 @@
|
||||
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{
|
||||
@@ -10,13 +14,28 @@ namespace TweetDuck.Core.Bridge{
|
||||
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 = LastRightClickedImage = 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;
|
||||
private readonly FormNotificationMain notification;
|
||||
|
||||
@@ -45,10 +64,11 @@ namespace TweetDuck.Core.Bridge{
|
||||
form.InvokeAsyncSafe(() => LastRightClickedImage = link);
|
||||
}
|
||||
|
||||
public void SetLastHighlightedTweet(string link, string quotedLink, string imageList){
|
||||
public void SetLastHighlightedTweet(string link, string quotedLink, string author, string imageList){
|
||||
form.InvokeAsyncSafe(() => {
|
||||
LastHighlightedTweet = link;
|
||||
LastHighlightedQuotedTweet = quotedLink;
|
||||
LastHighlightedTweetAuthor = author;
|
||||
LastHighlightedTweetImages = imageList.Split(';');
|
||||
});
|
||||
}
|
||||
@@ -80,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);
|
||||
}
|
||||
|
@@ -204,6 +204,7 @@ namespace TweetDuck.Core{
|
||||
e.Frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorFix);
|
||||
|
||||
UpdateProperties(PropertyBridge.Environment.Browser);
|
||||
TweetDeckBridge.RestoreSessionData(e.Frame);
|
||||
ScriptLoader.ExecuteFile(e.Frame, "code.js");
|
||||
ReinjectCustomCSS(Config.CustomBrowserCSS);
|
||||
|
||||
@@ -215,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,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){
|
||||
@@ -428,7 +429,7 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
|
||||
public void ReloadToTweetDeck(){
|
||||
browser.ExecuteScriptAsync($"gc&&gc();window.location.href='{TwitterUtils.TweetDeckURL}'");
|
||||
browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUtils.TweetDeckURL}'");
|
||||
}
|
||||
|
||||
// callback handlers
|
||||
@@ -457,8 +458,8 @@ 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();
|
||||
|
@@ -32,6 +32,7 @@ namespace TweetDuck.Core.Handling{
|
||||
|
||||
private readonly Form form;
|
||||
|
||||
private string lastHighlightedTweetAuthor;
|
||||
private string[] lastHighlightedTweetImageList;
|
||||
|
||||
protected ContextMenuBase(Form form){
|
||||
@@ -40,9 +41,11 @@ namespace TweetDuck.Core.Handling{
|
||||
|
||||
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||
bool hasTweetImage = !string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedImage);
|
||||
lastHighlightedTweetAuthor = TweetDeckBridge.LastHighlightedTweetAuthor;
|
||||
lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImages;
|
||||
|
||||
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
||||
lastHighlightedTweetAuthor = string.Empty;
|
||||
lastHighlightedTweetImageList = StringUtils.EmptyArray;
|
||||
}
|
||||
|
||||
@@ -88,11 +91,11 @@ namespace TweetDuck.Core.Handling{
|
||||
break;
|
||||
|
||||
case MenuSaveImage:
|
||||
TwitterUtils.DownloadImage(GetImage(parameters), ImageQuality);
|
||||
TwitterUtils.DownloadImage(GetImage(parameters), lastHighlightedTweetAuthor, ImageQuality);
|
||||
break;
|
||||
|
||||
case MenuSaveAllImages:
|
||||
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, ImageQuality);
|
||||
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthor, ImageQuality);
|
||||
break;
|
||||
|
||||
case MenuCopyImageUrl:
|
||||
|
@@ -67,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;
|
||||
|
22
Core/Other/Settings/TabSettingsAdvanced.Designer.cs
generated
22
Core/Other/Settings/TabSettingsAdvanced.Designer.cs
generated
@@ -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
|
||||
|
@@ -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){
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
using CefSharp;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Other;
|
||||
@@ -52,23 +53,32 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
}
|
||||
|
||||
public static void DownloadImage(string url, ImageQuality quality){
|
||||
DownloadImages(new string[]{ url }, quality);
|
||||
public static void DownloadImage(string url, string username, ImageQuality quality){
|
||||
DownloadImages(new string[]{ url }, username, quality);
|
||||
}
|
||||
|
||||
public static void DownloadImages(string[] urls, ImageQuality quality){
|
||||
public static void DownloadImages(string[] urls, string username, ImageQuality quality){
|
||||
if (urls.Length == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
string file = BrowserUtils.GetFileNameFromUrl(ExtractImageBaseLink(urls[0]));
|
||||
string firstImageLink = GetImageLink(urls[0], quality);
|
||||
int qualityIndex = firstImageLink.LastIndexOf(':');
|
||||
|
||||
string file = BrowserUtils.GetFileNameFromUrl(ExtractImageBaseLink(firstImageLink));
|
||||
string ext = Path.GetExtension(file); // includes dot
|
||||
|
||||
string[] fileNameParts = {
|
||||
username,
|
||||
Path.ChangeExtension(file, null),
|
||||
qualityIndex == -1 ? string.Empty : firstImageLink.Substring(qualityIndex+1)
|
||||
};
|
||||
|
||||
using(SaveFileDialog dialog = new SaveFileDialog{
|
||||
AutoUpgradeEnabled = true,
|
||||
OverwritePrompt = urls.Length == 1,
|
||||
Title = "Save image",
|
||||
FileName = file,
|
||||
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){
|
||||
@@ -77,14 +87,14 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
|
||||
if (urls.Length == 1){
|
||||
BrowserUtils.DownloadFileAsync(GetImageLink(urls[0], quality), dialog.FileName, null, OnFailure);
|
||||
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);
|
||||
BrowserUtils.DownloadFileAsync(GetImageLink(urls[index], quality), $"{pathBase} {index+1}{pathExt}", null, OnFailure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -12,8 +12,8 @@ namespace TweetDuck.Data.Serialization{
|
||||
|
||||
private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter();
|
||||
|
||||
public delegate bool OnReadUnknownPropertyHandler(T obj, string property, string value);
|
||||
public OnReadUnknownPropertyHandler OnReadUnknownProperty { get; set; }
|
||||
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;
|
||||
@@ -51,6 +51,8 @@ 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() <= 1){
|
||||
throw new FormatException("Input appears to be a binary file.");
|
||||
@@ -78,11 +80,19 @@ namespace TweetDuck.Data.Serialization{
|
||||
throw new SerializationException($"Invalid file format, cannot convert value: {value} (property: {property})");
|
||||
}
|
||||
}
|
||||
else if (!(OnReadUnknownProperty?.Invoke(obj, property, value) ?? false)){
|
||||
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{
|
||||
|
@@ -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.4";
|
||||
public const string VersionFull = "1.8.4";
|
||||
public const string VersionTag = "1.8.4.1";
|
||||
public const string VersionFull = "1.8.4.1";
|
||||
|
||||
public static readonly Version Version = new Version(VersionTag);
|
||||
public static readonly bool IsPortable = File.Exists("makeportable");
|
||||
@@ -115,8 +115,8 @@ namespace TweetDuck{
|
||||
}
|
||||
}
|
||||
|
||||
ReloadConfig();
|
||||
SystemConfig = SystemConfig.Load(SystemConfigFilePath);
|
||||
ReloadConfig();
|
||||
|
||||
if (Arguments.HasFlag(Arguments.ArgImportCookies)){
|
||||
ExportManager.ImportCookies();
|
||||
|
@@ -14,7 +14,7 @@ enabled(){
|
||||
revertIcons: true,
|
||||
smallComposeTextSize: false,
|
||||
optimizeAnimations: true,
|
||||
avatarRadius: 10
|
||||
avatarRadius: 2
|
||||
};
|
||||
|
||||
this.firstTimeLoad = null;
|
||||
@@ -459,7 +459,8 @@ enabled(){
|
||||
.icon-list-filled:before{content:"\\f014";font-family:tweetdeckold}
|
||||
.icon-user-filled:before{content:"\\f035";font-family:tweetdeckold}
|
||||
|
||||
.column-type-icon { bottom: 26px !important }
|
||||
.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 }`;
|
||||
|
||||
|
@@ -122,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>
|
||||
|
@@ -108,7 +108,7 @@
|
||||
recentTweetTimer = window.setTimeout(() => {
|
||||
recentTweetTimer = null;
|
||||
recentTweets.clear();
|
||||
}, 10000);
|
||||
}, 20000);
|
||||
};
|
||||
|
||||
let checkRecentTweet = id => {
|
||||
@@ -165,6 +165,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
@@ -388,12 +391,12 @@
|
||||
return !!highlightedColumnObj;
|
||||
};
|
||||
|
||||
var updateHighlightedTweet = function(ele, obj, link, embeddedLink, imageList){
|
||||
var updateHighlightedTweet = function(ele, obj, link, embeddedLink, author, imageList){
|
||||
highlightedTweetEle = ele;
|
||||
highlightedTweetObj = obj;
|
||||
|
||||
if (lastTweet !== link){
|
||||
$TD.setLastHighlightedTweet(link, embeddedLink, imageList);
|
||||
$TD.setLastHighlightedTweet(link, embeddedLink, author, imageList);
|
||||
lastTweet = link;
|
||||
}
|
||||
};
|
||||
@@ -423,18 +426,19 @@
|
||||
if (tweet.chirpType === TD.services.ChirpBase.TWEET){
|
||||
let link = tweet.getChirpURL();
|
||||
let embedded = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
|
||||
let username = tweet.getMainUser().screenName;
|
||||
let images = tweet.hasImage() ? tweet.getMedia().filter(item => !item.isAnimatedGif).map(item => item.entity.media_url_https+":small").join(";") : "";
|
||||
// TODO maybe handle embedded images too?
|
||||
|
||||
updateHighlightedTweet(me, tweet, link || "", embedded || "", images);
|
||||
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, "", "", "", "");
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -518,7 +522,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();
|
||||
@@ -702,10 +711,13 @@
|
||||
addRule(".column-nav-link .attribution { position: absolute; }"); // fix cut off account names
|
||||
addRule(".txt-base-smallest .sprite-verified-mini { width: 13px !important; height: 13px !important; background-position: -223px -99px !important; }"); // fix cut off badge icon when zoomed in
|
||||
|
||||
addRule(".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, and media previews
|
||||
addRule(".dropdown-menu, .list-item-last { border-radius: 0 !important; }"); // square-ify dropdowns
|
||||
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
|
||||
|
||||
addRule(".accs li, .accs img { border-radius: 0 !important; }"); // square-ify retweet account selector
|
||||
addRule(".accs-header { padding-left: 0 !important; }"); // fix retweet account selector heading
|
||||
|
||||
addRule(".scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb, .antiscroll-scrollbar { border-radius: 0; }"); // square-ify scroll bars
|
||||
addRule(".antiscroll-scrollbar-vertical { margin-top: 0; }"); // square-ify scroll bars
|
||||
addRule(".antiscroll-scrollbar-horizontal { margin-left: 0; }"); // square-ify scroll bars
|
||||
@@ -730,6 +742,9 @@
|
||||
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
|
||||
|
||||
@@ -845,8 +860,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(){
|
||||
@@ -873,11 +899,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.
|
||||
//
|
||||
@@ -902,6 +979,8 @@
|
||||
onAppReady.forEach(func => func());
|
||||
onAppReady = null;
|
||||
|
||||
delete window.TD_SESSION;
|
||||
|
||||
if (window.TD_PLUGINS){
|
||||
window.TD_PLUGINS.onReady();
|
||||
}
|
||||
|
@@ -98,7 +98,7 @@
|
||||
//
|
||||
window.TDPF_requestReload = function(){
|
||||
if (!isReloading){
|
||||
window.setTimeout(() => location.reload(), 1);
|
||||
window.setTimeout(window.TDGF_reload, 1);
|
||||
isReloading = true;
|
||||
}
|
||||
};
|
||||
|
@@ -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, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.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>");
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user