mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 19:32:10 +02:00
Compare commits
75 Commits
Author | SHA1 | Date | |
---|---|---|---|
4fdf7fc958 | |||
42a5e72f19 | |||
f7359ebc8a | |||
f395ac53dc | |||
1113e0b559 | |||
5e3bd31862 | |||
11d978dad1 | |||
f7961024d7 | |||
72973a8707 | |||
68254f48d5 | |||
eac4f30c50 | |||
25680fa980 | |||
ff5e1da14d | |||
95afff7879 | |||
50bd526025 | |||
108a0fefc3 | |||
dd8c5d27be | |||
b2937bc776 | |||
4d8e764211 | |||
544b8664fd | |||
d0610865bd | |||
ebc0b51590 | |||
4487f1169e | |||
85559b6083 | |||
1056273c57 | |||
61af2ebc8b | |||
9121c86656 | |||
1ccefe853a | |||
aca438b837 | |||
7210c29cd8 | |||
26d90c0c9b | |||
a03b222a95 | |||
7944a24d3c | |||
cc8459c759 | |||
10074ff92c | |||
173f25bebc | |||
31680fc4ae | |||
e937d43614 | |||
20e29a7975 | |||
ef815dabce | |||
1fb133e6b8 | |||
50b58cd6a6 | |||
01485d7ef9 | |||
b17c6a5ac7 | |||
d2ed2b4a00 | |||
710a7524a1 | |||
2be46464d6 | |||
8d536a6734 | |||
250d502238 | |||
e8de7266d0 | |||
9414f372d7 | |||
b0f9de67cf | |||
9b082e114e | |||
816a5334ac | |||
15a4e10da9 | |||
01b9302b0c | |||
442126a11a | |||
a9c140c0fc | |||
97ad7a3e68 | |||
7d737eefb6 | |||
4ac05b38d3 | |||
651bbbb672 | |||
52da4d8687 | |||
36063ae76a | |||
2fcec2d2cd | |||
762a7fdfb7 | |||
cd7aeaeed2 | |||
6f414d312c | |||
1b5304efb7 | |||
d59375308f | |||
8c9509a906 | |||
fb86d8f3a8 | |||
50e909cb3d | |||
2f54edf7e7 | |||
c251603e1e |
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
patreon: chylex
|
||||
ko_fi: chylex
|
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using TweetDuck.Data;
|
||||
using TweetLib.Core.Collections;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
static class Arguments{
|
||||
@@ -7,6 +7,7 @@ namespace TweetDuck.Configuration{
|
||||
public const string ArgDataFolder = "-datafolder";
|
||||
public const string ArgLogging = "-log";
|
||||
public const string ArgIgnoreGDPR = "-nogdpr";
|
||||
public const string ArgFreeze = "-freeze";
|
||||
|
||||
// internal args
|
||||
public const string ArgRestart = "-restart";
|
||||
@@ -21,8 +22,8 @@ namespace TweetDuck.Configuration{
|
||||
return Current.HasFlag(flag);
|
||||
}
|
||||
|
||||
public static string GetValue(string key, string defaultValue){
|
||||
return Current.GetValue(key, defaultValue);
|
||||
public static string GetValue(string key){
|
||||
return Current.GetValue(key);
|
||||
}
|
||||
|
||||
public static CommandLineArgs GetCurrentClean(){
|
||||
|
@@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using TweetDuck.Configuration.Instance;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Data.Serialization;
|
||||
using TweetLib.Core.Features.Configuration;
|
||||
using TweetLib.Core.Features.Plugins.Config;
|
||||
using TweetLib.Core.Serialization.Converters;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class ConfigManager{
|
||||
sealed class ConfigManager : IConfigManager{
|
||||
public UserConfig User { get; }
|
||||
public SystemConfig System { get; }
|
||||
public PluginConfig Plugins { get; }
|
||||
@@ -16,7 +16,7 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
private readonly FileConfigInstance<UserConfig> infoUser;
|
||||
private readonly FileConfigInstance<SystemConfig> infoSystem;
|
||||
private readonly PluginConfigInstance infoPlugins;
|
||||
private readonly PluginConfigInstance<PluginConfig> infoPlugins;
|
||||
|
||||
private readonly IConfigInstance<BaseConfig>[] infoList;
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace TweetDuck.Configuration{
|
||||
infoList = new IConfigInstance<BaseConfig>[]{
|
||||
infoUser = new FileConfigInstance<UserConfig>(Program.UserConfigFilePath, User, "program options"),
|
||||
infoSystem = new FileConfigInstance<SystemConfig>(Program.SystemConfigFilePath, System, "system options"),
|
||||
infoPlugins = new PluginConfigInstance(Program.PluginConfigFilePath, Plugins)
|
||||
infoPlugins = new PluginConfigInstance<PluginConfig>(Program.PluginConfigFilePath, Plugins)
|
||||
};
|
||||
|
||||
// TODO refactor further
|
||||
@@ -70,59 +70,13 @@ namespace TweetDuck.Configuration{
|
||||
infoPlugins.Reload();
|
||||
}
|
||||
|
||||
private void TriggerProgramRestartRequested(){
|
||||
void IConfigManager.TriggerProgramRestartRequested(){
|
||||
ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private IConfigInstance<BaseConfig> GetInstanceInfo(BaseConfig instance){
|
||||
IConfigInstance<BaseConfig> IConfigManager.GetInstanceInfo(BaseConfig instance){
|
||||
Type instanceType = instance.GetType();
|
||||
return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null
|
||||
}
|
||||
|
||||
public abstract class BaseConfig{
|
||||
private readonly ConfigManager configManager;
|
||||
|
||||
protected BaseConfig(ConfigManager configManager){
|
||||
this.configManager = configManager;
|
||||
}
|
||||
|
||||
// Management
|
||||
|
||||
public void Save(){
|
||||
configManager.GetInstanceInfo(this).Save();
|
||||
}
|
||||
|
||||
public void Reload(){
|
||||
configManager.GetInstanceInfo(this).Reload();
|
||||
}
|
||||
|
||||
public void Reset(){
|
||||
configManager.GetInstanceInfo(this).Reset();
|
||||
}
|
||||
|
||||
// Construction methods
|
||||
|
||||
public T ConstructWithDefaults<T>() where T : BaseConfig{
|
||||
return ConstructWithDefaults(configManager) as T;
|
||||
}
|
||||
|
||||
protected abstract BaseConfig ConstructWithDefaults(ConfigManager configManager);
|
||||
|
||||
// Utility methods
|
||||
|
||||
protected void UpdatePropertyWithEvent<T>(ref T field, T value, EventHandler eventHandler){
|
||||
if (!EqualityComparer<T>.Default.Equals(field, value)){
|
||||
field = value;
|
||||
eventHandler?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
protected void UpdatePropertyWithRestartRequest<T>(ref T field, T value){
|
||||
if (!EqualityComparer<T>.Default.Equals(field, value)){
|
||||
field = value;
|
||||
configManager.TriggerProgramRestartRequested();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,69 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace TweetDuck.Configuration.Instance{
|
||||
class PluginConfigInstance : IConfigInstance<PluginConfig>{
|
||||
public PluginConfig Instance { get; }
|
||||
|
||||
private readonly string filename;
|
||||
|
||||
public PluginConfigInstance(string filename, PluginConfig instance){
|
||||
this.filename = filename;
|
||||
this.Instance = instance;
|
||||
}
|
||||
|
||||
public void Load(){
|
||||
try{
|
||||
using(StreamReader reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8)){
|
||||
string line = reader.ReadLine();
|
||||
|
||||
if (line == "#Disabled"){
|
||||
HashSet<string> newDisabled = new HashSet<string>();
|
||||
|
||||
while((line = reader.ReadLine()) != null){
|
||||
newDisabled.Add(line);
|
||||
}
|
||||
|
||||
Instance.ReloadSilently(newDisabled);
|
||||
}
|
||||
}
|
||||
}catch(FileNotFoundException){
|
||||
}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);
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(){
|
||||
try{
|
||||
using(StreamWriter writer = new StreamWriter(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){
|
||||
writer.WriteLine("#Disabled");
|
||||
|
||||
foreach(string identifier in Instance.DisabledPlugins){
|
||||
writer.WriteLine(identifier);
|
||||
}
|
||||
}
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Plugin Configuration Error", "Could not save the plugin configuration file.", true, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reload(){
|
||||
Load();
|
||||
}
|
||||
|
||||
public void Reset(){
|
||||
try{
|
||||
File.Delete(filename);
|
||||
Instance.ReloadSilently(Instance.ConstructWithDefaults<PluginConfig>().DisabledPlugins);
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Plugin Configuration Error", "Could not delete the plugin configuration file.", true, e);
|
||||
return;
|
||||
}
|
||||
|
||||
Reload();
|
||||
}
|
||||
}
|
||||
}
|
@@ -124,7 +124,7 @@ namespace TweetDuck.Configuration{
|
||||
try{
|
||||
File.Delete(file);
|
||||
}catch(Exception e){
|
||||
Program.Reporter.Log(e.ToString());
|
||||
Program.Reporter.LogImportant(e.ToString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -1,21 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Events;
|
||||
using TweetLib.Core.Features.Configuration;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.Plugins.Config;
|
||||
using TweetLib.Core.Features.Plugins.Events;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class PluginConfig : ConfigManager.BaseConfig, IPluginConfig{
|
||||
sealed class PluginConfig : BaseConfig, IPluginConfig{
|
||||
private static readonly string[] DefaultDisabled = {
|
||||
"official/clear-columns",
|
||||
"official/reply-account"
|
||||
};
|
||||
|
||||
// CONFIGURATION
|
||||
// CONFIGURATION DATA
|
||||
|
||||
public IEnumerable<string> DisabledPlugins => disabled;
|
||||
private readonly HashSet<string> disabled = new HashSet<string>(DefaultDisabled);
|
||||
|
||||
// EVENTS
|
||||
|
||||
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
|
||||
|
||||
// END OF CONFIG
|
||||
|
||||
public PluginConfig(IConfigManager configManager) : base(configManager){}
|
||||
|
||||
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
|
||||
return new PluginConfig(configManager);
|
||||
}
|
||||
|
||||
// INTERFACE IMPLEMENTATION
|
||||
|
||||
IEnumerable<string> IPluginConfig.DisabledPlugins => disabled;
|
||||
|
||||
void IPluginConfig.Reset(IEnumerable<string> newDisabledPlugins){
|
||||
disabled.Clear();
|
||||
disabled.UnionWith(newDisabledPlugins);
|
||||
}
|
||||
|
||||
public void SetEnabled(Plugin plugin, bool enabled){
|
||||
if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))){
|
||||
PluginChangedState?.Invoke(this, new PluginChangedStateEventArgs(plugin, enabled));
|
||||
@@ -26,20 +47,5 @@ namespace TweetDuck.Configuration{
|
||||
public bool IsEnabled(Plugin plugin){
|
||||
return !disabled.Contains(plugin.Identifier);
|
||||
}
|
||||
|
||||
public void ReloadSilently(IEnumerable<string> newDisabled){
|
||||
disabled.Clear();
|
||||
disabled.UnionWith(newDisabled);
|
||||
}
|
||||
|
||||
private readonly HashSet<string> disabled = new HashSet<string>(DefaultDisabled);
|
||||
|
||||
// END OF CONFIG
|
||||
|
||||
public PluginConfig(ConfigManager configManager) : base(configManager){}
|
||||
|
||||
protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
|
||||
return new PluginConfig(configManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,7 @@
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class SystemConfig : ConfigManager.BaseConfig{
|
||||
using TweetLib.Core.Features.Configuration;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class SystemConfig : BaseConfig{
|
||||
|
||||
// CONFIGURATION DATA
|
||||
|
||||
@@ -17,9 +19,9 @@
|
||||
|
||||
// END OF CONFIG
|
||||
|
||||
public SystemConfig(ConfigManager configManager) : base(configManager){}
|
||||
public SystemConfig(IConfigManager configManager) : base(configManager){}
|
||||
|
||||
protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
|
||||
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
|
||||
return new SystemConfig(configManager);
|
||||
}
|
||||
}
|
||||
|
@@ -3,11 +3,12 @@ using System.Drawing;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetLib.Core.Features.Configuration;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class UserConfig : ConfigManager.BaseConfig{
|
||||
sealed class UserConfig : BaseConfig{
|
||||
|
||||
// CONFIGURATION DATA
|
||||
|
||||
@@ -18,6 +19,7 @@ namespace TweetDuck.Configuration{
|
||||
public Size PluginsWindowSize { get; set; } = Size.Empty;
|
||||
|
||||
public bool ExpandLinksOnHover { get; set; } = true;
|
||||
public bool FocusDmInput { get; set; } = true;
|
||||
public bool OpenSearchInFirstColumn { get; set; } = true;
|
||||
public bool KeepLikeFollowDialogsOpen { get; set; } = true;
|
||||
public bool BestImageQuality { get; set; } = true;
|
||||
@@ -78,7 +80,7 @@ namespace TweetDuck.Configuration{
|
||||
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
|
||||
public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty;
|
||||
|
||||
public TwitterUtils.ImageQuality TwitterImageQuality => BestImageQuality ? TwitterUtils.ImageQuality.Orig : TwitterUtils.ImageQuality.Default;
|
||||
public ImageQuality TwitterImageQuality => BestImageQuality ? ImageQuality.Best : ImageQuality.Default;
|
||||
|
||||
public string NotificationSoundPath{
|
||||
get => _notificationSoundPath ?? string.Empty;
|
||||
@@ -134,9 +136,9 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
// END OF CONFIG
|
||||
|
||||
public UserConfig(ConfigManager configManager) : base(configManager){}
|
||||
public UserConfig(IConfigManager configManager) : base(configManager){}
|
||||
|
||||
protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
|
||||
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
|
||||
return new UserConfig(configManager);
|
||||
}
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ namespace TweetDuck.Core.Bridge{
|
||||
build.Append("x.expandLinksOnHover=").Append(Bool(config.ExpandLinksOnHover));
|
||||
|
||||
if (environment == Environment.Browser){
|
||||
build.Append("x.focusDmInput=").Append(Bool(config.FocusDmInput));
|
||||
build.Append("x.openSearchInFirstColumn=").Append(Bool(config.OpenSearchInFirstColumn));
|
||||
build.Append("x.keepLikeFollowDialogsOpen=").Append(Bool(config.KeepLikeFollowDialogsOpen));
|
||||
build.Append("x.muteNotifications=").Append(Bool(config.MuteNotifications));
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System.Windows.Forms;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Management;
|
||||
using TweetDuck.Core.Notification;
|
||||
@@ -6,6 +7,7 @@ using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Bridge{
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
class TweetDeckBridge{
|
||||
public static string FontSize { get; private set; }
|
||||
public static string NotificationHeadLayout { get; private set; }
|
||||
@@ -117,14 +119,12 @@ namespace TweetDuck.Core.Bridge{
|
||||
}
|
||||
|
||||
public void Alert(string type, string contents){
|
||||
MessageBoxIcon icon;
|
||||
|
||||
switch(type){
|
||||
case "error": icon = MessageBoxIcon.Error; break;
|
||||
case "warning": icon = MessageBoxIcon.Warning; break;
|
||||
case "info": icon = MessageBoxIcon.Information; break;
|
||||
default: icon = MessageBoxIcon.None; break;
|
||||
}
|
||||
MessageBoxIcon icon = type switch{
|
||||
"error" => MessageBoxIcon.Error,
|
||||
"warning" => MessageBoxIcon.Warning,
|
||||
"info" => MessageBoxIcon.Information,
|
||||
_ => MessageBoxIcon.None
|
||||
};
|
||||
|
||||
FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK);
|
||||
}
|
||||
|
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Updates;
|
||||
using TweetLib.Core.Features.Updates;
|
||||
|
||||
namespace TweetDuck.Core.Bridge{
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
class UpdateBridge{
|
||||
private readonly UpdateHandler updates;
|
||||
private readonly Control sync;
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core.Bridge;
|
||||
@@ -14,9 +15,10 @@ using TweetDuck.Core.Other.Analytics;
|
||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Events;
|
||||
using TweetDuck.Resources;
|
||||
using TweetDuck.Updates;
|
||||
using TweetLib.Core.Features.Plugins.Events;
|
||||
using TweetLib.Core.Features.Updates;
|
||||
|
||||
namespace TweetDuck.Core{
|
||||
sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{
|
||||
@@ -63,7 +65,7 @@ namespace TweetDuck.Core{
|
||||
|
||||
Text = Program.BrandName;
|
||||
|
||||
this.plugins = new PluginManager(Program.Config.Plugins, Program.PluginPath);
|
||||
this.plugins = new PluginManager(this, Program.Config.Plugins, Program.PluginPath, Program.PluginDataPath);
|
||||
this.plugins.Reloaded += plugins_Reloaded;
|
||||
this.plugins.Executed += plugins_Executed;
|
||||
this.plugins.Reload();
|
||||
@@ -71,7 +73,7 @@ namespace TweetDuck.Core{
|
||||
this.notification = new FormNotificationTweet(this, plugins);
|
||||
this.notification.Show();
|
||||
|
||||
this.updates = new UpdateHandler(Program.InstallerPath);
|
||||
this.updates = new UpdateHandler(new UpdateCheckClient(Program.InstallerPath), TaskScheduler.FromCurrentSynchronizationContext());
|
||||
this.updates.CheckFinished += updates_CheckFinished;
|
||||
|
||||
this.updateBridge = new UpdateBridge(updates, this);
|
||||
@@ -494,7 +496,7 @@ namespace TweetDuck.Core{
|
||||
FormManager.TryFind<FormSettings>()?.Close();
|
||||
|
||||
using(DialogSettingsManage dialog = new DialogSettingsManage(plugins, true)){
|
||||
if (dialog.ShowDialog() == DialogResult.OK && !dialog.IsRestarting){
|
||||
if (!dialog.IsDisposed && dialog.ShowDialog() == DialogResult.OK && !dialog.IsRestarting){ // needs disposal check because the dialog may be closed in constructor
|
||||
BrowserProcessHandler.UpdatePrefs();
|
||||
FormManager.TryFind<FormPlugins>()?.Close();
|
||||
plugins.Reload(); // also reloads the browser
|
||||
|
@@ -17,8 +17,6 @@ namespace TweetDuck.Core{
|
||||
else return false;
|
||||
}
|
||||
|
||||
public static bool HasAnyDialogs => Application.OpenForms.OfType<IAppDialog>().Any();
|
||||
|
||||
public static void CloseAllDialogs(){
|
||||
foreach(IAppDialog dialog in Application.OpenForms.OfType<IAppDialog>().Reverse()){
|
||||
((Form)dialog).Close();
|
||||
|
@@ -12,12 +12,14 @@ using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
using TweetDuck.Resources;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
abstract class ContextMenuBase : IContextMenuHandler{
|
||||
protected static UserConfig Config => Program.Config.User;
|
||||
|
||||
private static TwitterUtils.ImageQuality ImageQuality => Config.TwitterImageQuality;
|
||||
private static ImageQuality ImageQuality => Config.TwitterImageQuality;
|
||||
|
||||
private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand)26500;
|
||||
private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand)26501;
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
@@ -9,7 +8,7 @@ namespace TweetDuck.Core.Handling.General{
|
||||
sealed class FileDialogHandler : IDialogHandler{
|
||||
public bool OnFileDialog(IWebBrowser browserControl, IBrowser browser, CefFileDialogMode mode, CefFileDialogFlags flags, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback){
|
||||
if (mode == CefFileDialogMode.Open || mode == CefFileDialogMode.OpenMultiple){
|
||||
string allFilters = string.Join(";", acceptFilters.Select(filter => "*"+filter));
|
||||
string allFilters = string.Join(";", acceptFilters.SelectMany(ParseFileType).Where(filter => !string.IsNullOrEmpty(filter)).Select(filter => "*" + filter));
|
||||
|
||||
using(OpenFileDialog dialog = new OpenFileDialog{
|
||||
AutoUpgradeEnabled = true,
|
||||
@@ -19,8 +18,8 @@ namespace TweetDuck.Core.Handling.General{
|
||||
Filter = $"All Supported Formats ({allFilters})|{allFilters}|All Files (*.*)|*.*"
|
||||
}){
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
string ext = Path.GetExtension(dialog.FileName);
|
||||
callback.Continue(acceptFilters.FindIndex(filter => filter.Equals(ext, StringComparison.OrdinalIgnoreCase)), dialog.FileNames.ToList());
|
||||
string ext = Path.GetExtension(dialog.FileName)?.ToLower();
|
||||
callback.Continue(acceptFilters.FindIndex(filter => ParseFileType(filter).Contains(ext)), dialog.FileNames.ToList());
|
||||
}
|
||||
else{
|
||||
callback.Cancel();
|
||||
@@ -36,5 +35,27 @@ namespace TweetDuck.Core.Handling.General{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> ParseFileType(string type){
|
||||
if (string.IsNullOrEmpty(type)){
|
||||
return new string[0];
|
||||
}
|
||||
|
||||
if (type[0] == '.'){
|
||||
return new string[]{ type };
|
||||
}
|
||||
|
||||
switch(type){
|
||||
case "image/jpeg": return new string[]{ ".jpg", ".jpeg" };
|
||||
case "image/png": return new string[]{ ".png" };
|
||||
case "image/gif": return new string[]{ ".gif" };
|
||||
case "image/webp": return new string[]{ ".webp" };
|
||||
case "video/mp4": return new string[]{ ".mp4" };
|
||||
case "video/quicktime": return new string[]{ ".mov", ".qt" };
|
||||
}
|
||||
|
||||
System.Diagnostics.Debugger.Break();
|
||||
return new string[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -12,15 +12,17 @@ namespace TweetDuck.Core.Handling.General{
|
||||
int pipe = text.IndexOf('|');
|
||||
|
||||
if (pipe != -1){
|
||||
switch(text.Substring(0, pipe)){
|
||||
case "error": icon = MessageBoxIcon.Error; break;
|
||||
case "warning": icon = MessageBoxIcon.Warning; break;
|
||||
case "info": icon = MessageBoxIcon.Information; break;
|
||||
case "question": icon = MessageBoxIcon.Question; break;
|
||||
default: return new FormMessage(caption, text, icon);
|
||||
}
|
||||
icon = text.Substring(0, pipe) switch{
|
||||
"error" => MessageBoxIcon.Error,
|
||||
"warning" => MessageBoxIcon.Warning,
|
||||
"info" => MessageBoxIcon.Information,
|
||||
"question" => MessageBoxIcon.Question,
|
||||
_ => MessageBoxIcon.None
|
||||
};
|
||||
|
||||
text = text.Substring(pipe+1);
|
||||
if (icon != MessageBoxIcon.None){
|
||||
text = text.Substring(pipe + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return new FormMessage(caption, text, icon);
|
||||
|
@@ -4,11 +4,15 @@ using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Handling.General{
|
||||
sealed class LifeSpanHandler : ILifeSpanHandler{
|
||||
private static bool IsPopupAllowed(string url){
|
||||
return url.StartsWith("https://twitter.com/teams/authorize?");
|
||||
}
|
||||
|
||||
public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposition targetDisposition, string targetUrl){
|
||||
switch(targetDisposition){
|
||||
case WindowOpenDisposition.NewBackgroundTab:
|
||||
case WindowOpenDisposition.NewForegroundTab:
|
||||
case WindowOpenDisposition.NewPopup:
|
||||
case WindowOpenDisposition.NewPopup when !IsPopupAllowed(targetUrl):
|
||||
case WindowOpenDisposition.NewWindow:
|
||||
browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl));
|
||||
return true;
|
||||
|
47
Core/Handling/KeyboardHandlerBase.cs
Normal file
47
Core/Handling/KeyboardHandlerBase.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
class KeyboardHandlerBase : IKeyboardHandler{
|
||||
protected virtual bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){
|
||||
if (modifiers == (CefEventFlags.ControlDown | CefEventFlags.ShiftDown) && key == Keys.I){
|
||||
if (BrowserUtils.HasDevTools){
|
||||
browser.ShowDevTools();
|
||||
}
|
||||
else{
|
||||
browserControl.AsControl().InvokeSafe(() => {
|
||||
string extraMessage;
|
||||
|
||||
if (Program.IsPortable){
|
||||
extraMessage = "Please download the portable installer, select the folder with your current installation of TweetDuck Portable, and tick 'Install dev tools' during the installation process.";
|
||||
}
|
||||
else{
|
||||
extraMessage = "Please download the installer, and tick 'Install dev tools' during the installation process. The installer will automatically find and update your current installation of TweetDuck.";
|
||||
}
|
||||
|
||||
FormMessage.Information("Dev Tools", "You do not have dev tools installed. "+extraMessage, FormMessage.OK);
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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://")){
|
||||
return HandleRawKey(browserControl, browser, (Keys)windowsKeyCode, modifiers);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,19 +2,19 @@
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
sealed class KeyboardHandlerBrowser : IKeyboardHandler{
|
||||
sealed class KeyboardHandlerBrowser : KeyboardHandlerBase{
|
||||
private readonly FormBrowser form;
|
||||
|
||||
public KeyboardHandlerBrowser(FormBrowser form){
|
||||
this.form = form;
|
||||
}
|
||||
|
||||
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){
|
||||
return type == KeyType.RawKeyDown && form.ProcessBrowserKey((Keys)windowsKeyCode);
|
||||
}
|
||||
protected override bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){
|
||||
if (base.HandleRawKey(browserControl, browser, key, modifiers)){
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
|
||||
return false;
|
||||
return form.ProcessBrowserKey(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Notification;
|
||||
|
||||
namespace TweetDuck.Core.Handling {
|
||||
sealed class KeyboardHandlerNotification : IKeyboardHandler{
|
||||
sealed class KeyboardHandlerNotification : KeyboardHandlerBase{
|
||||
private readonly FormNotificationBase notification;
|
||||
|
||||
public KeyboardHandlerNotification(FormNotificationBase notification){
|
||||
@@ -15,31 +15,30 @@ namespace TweetDuck.Core.Handling {
|
||||
notification.InvokeAsyncSafe(notification.AnalyticsFile.NotificationKeyboardShortcuts.Trigger);
|
||||
}
|
||||
|
||||
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);
|
||||
TriggerKeyboardShortcutAnalytics();
|
||||
return true;
|
||||
|
||||
case Keys.Escape:
|
||||
notification.InvokeAsyncSafe(notification.HideNotification);
|
||||
TriggerKeyboardShortcutAnalytics();
|
||||
return true;
|
||||
|
||||
case Keys.Space:
|
||||
notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer);
|
||||
TriggerKeyboardShortcutAnalytics();
|
||||
return true;
|
||||
}
|
||||
protected override bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){
|
||||
if (base.HandleRawKey(browserControl, browser, key, modifiers)){
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
switch(key){
|
||||
case Keys.Enter:
|
||||
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
|
||||
TriggerKeyboardShortcutAnalytics();
|
||||
return true;
|
||||
|
||||
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
|
||||
return false;
|
||||
case Keys.Escape:
|
||||
notification.InvokeAsyncSafe(notification.HideNotification);
|
||||
TriggerKeyboardShortcutAnalytics();
|
||||
return true;
|
||||
|
||||
case Keys.Space:
|
||||
notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer);
|
||||
TriggerKeyboardShortcutAnalytics();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,38 @@
|
||||
using System.Collections.Specialized;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using CefSharp;
|
||||
using CefSharp.Handler;
|
||||
using TweetDuck.Core.Handling.General;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
class RequestHandlerBase : DefaultRequestHandler{
|
||||
private static readonly Regex TweetDeckResourceUrl = new Regex(@"/dist/(.*?)\.(.*?)\.(css|js)$", RegexOptions.Compiled);
|
||||
private static readonly SortedList<string, string> TweetDeckHashes = new SortedList<string, string>(4);
|
||||
|
||||
public static void LoadResourceRewriteRules(string rules){
|
||||
if (string.IsNullOrEmpty(rules)){
|
||||
return;
|
||||
}
|
||||
|
||||
TweetDeckHashes.Clear();
|
||||
|
||||
foreach(string rule in rules.Replace(" ", "").ToLower().Split(',')){
|
||||
var (key, hash) = StringUtils.SplitInTwo(rule, '=') ?? throw new ArgumentException("A rule must have one '=' character: " + rule);
|
||||
|
||||
if (hash.All(chr => char.IsDigit(chr) || (chr >= 'a' && chr <= 'f'))){
|
||||
TweetDeckHashes.Add(key, hash);
|
||||
}
|
||||
else{
|
||||
throw new ArgumentException("Invalid hash characters: " + rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly bool autoReload;
|
||||
|
||||
public RequestHandlerBase(bool autoReload){
|
||||
@@ -26,6 +53,26 @@ namespace TweetDuck.Core.Handling{
|
||||
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
||||
}
|
||||
|
||||
public override bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
|
||||
if ((request.ResourceType == ResourceType.Script || request.ResourceType == ResourceType.Stylesheet) && TweetDeckHashes.Count > 0){
|
||||
string url = request.Url;
|
||||
Match match = TweetDeckResourceUrl.Match(url);
|
||||
|
||||
if (match.Success && TweetDeckHashes.TryGetValue($"{match.Groups[1]}.{match.Groups[3]}", out string hash)){
|
||||
if (match.Groups[2].Value == hash){
|
||||
Program.Reporter.LogVerbose("[RequestHandlerBase] Accepting " + url);
|
||||
}
|
||||
else{
|
||||
Program.Reporter.LogVerbose("[RequestHandlerBase] Replacing " + url + " hash with " + hash);
|
||||
request.Url = TweetDeckResourceUrl.Replace(url, $"/dist/$1.{hash}.$3");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnResourceResponse(browserControl, browser, frame, request, response);
|
||||
}
|
||||
|
||||
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
|
||||
if (autoReload){
|
||||
browser.Reload();
|
||||
|
@@ -1,26 +1,35 @@
|
||||
// Uncomment to force TweetDeck to load a predefined version of the vendor/bundle scripts
|
||||
// #define FREEZE_TWEETDECK_SCRIPTS
|
||||
|
||||
using System.Collections.Specialized;
|
||||
using System.Collections.Specialized;
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Handling.Filters;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
#if FREEZE_TWEETDECK_SCRIPTS
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
#endif
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
sealed class RequestHandlerBrowser : RequestHandlerBase{
|
||||
private const string UrlVendorResource = "/dist/vendor";
|
||||
private const string UrlLoadingSpinner = "/backgrounds/spinner_blue";
|
||||
|
||||
public string BlockNextUserNavUrl { get; set; }
|
||||
|
||||
public RequestHandlerBrowser() : base(true){}
|
||||
|
||||
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
|
||||
if (request.ResourceType == ResourceType.Script && request.Url.Contains("analytics.")){
|
||||
callback.Dispose();
|
||||
return CefReturnValue.Cancel;
|
||||
if (request.ResourceType == ResourceType.MainFrame){
|
||||
if (request.Url.EndsWith("//twitter.com/")){
|
||||
request.Url = TwitterUtils.TweetDeckURL; // redirect plain twitter.com requests, fixes bugs with login 2FA
|
||||
}
|
||||
}
|
||||
else if (request.ResourceType == ResourceType.Script){
|
||||
string url = request.Url;
|
||||
|
||||
if (url.Contains("analytics.")){
|
||||
callback.Dispose();
|
||||
return CefReturnValue.Cancel;
|
||||
}
|
||||
else if (url.Contains(UrlVendorResource)){
|
||||
NameValueCollection headers = request.Headers;
|
||||
headers["Accept-Encoding"] = "identity";
|
||||
request.Headers = headers;
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
||||
@@ -36,58 +45,18 @@ namespace TweetDuck.Core.Handling{
|
||||
return base.OnBeforeBrowse(browserControl, browser, frame, request, userGesture, isRedirect);
|
||||
}
|
||||
|
||||
#if FREEZE_TWEETDECK_SCRIPTS
|
||||
private static readonly Regex TweetDeckScriptUrl = new Regex(@"/dist/(.*?)\.(.*?)\.js$", RegexOptions.Compiled);
|
||||
|
||||
private static readonly SortedList<string, string> TweetDeckHashes = new SortedList<string, string>(2){
|
||||
{ "vendor", "942c0a20e8" },
|
||||
{ "bundle", "1bd75b5854" }
|
||||
};
|
||||
#endif
|
||||
|
||||
public override bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
|
||||
if (request.ResourceType == ResourceType.Image && request.Url.Contains("/backgrounds/spinner_blue")){
|
||||
if (request.ResourceType == ResourceType.Image && request.Url.Contains(UrlLoadingSpinner)){
|
||||
request.Url = TwitterUtils.LoadingSpinner.Url;
|
||||
return true;
|
||||
}
|
||||
#if FREEZE_TWEETDECK_SCRIPTS
|
||||
else if (request.ResourceType == ResourceType.Script){
|
||||
Match match = TweetDeckScriptUrl.Match(request.Url);
|
||||
|
||||
if (match.Success && TweetDeckHashes.TryGetValue(match.Groups[1].Value, out string hash)){
|
||||
if (match.Groups[2].Value == hash){
|
||||
System.Diagnostics.Debug.WriteLine($"accepting {request.Url}");
|
||||
}
|
||||
else{
|
||||
System.Diagnostics.Debug.WriteLine($"rewriting {request.Url} to {hash}");
|
||||
request.Url = TweetDeckScriptUrl.Replace(request.Url, "/dist/$1."+hash+".js");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return base.OnResourceResponse(browserControl, browser, frame, request, response);
|
||||
}
|
||||
|
||||
public override IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
|
||||
if (request.ResourceType == ResourceType.Script && request.Url.Contains("/dist/vendor")){
|
||||
NameValueCollection headers = response.ResponseHeaders;
|
||||
|
||||
if (int.TryParse(headers["x-ton-expected-size"], out int totalBytes)){
|
||||
return new ResponseFilterVendor(totalBytes);
|
||||
}
|
||||
#if DEBUG
|
||||
else{
|
||||
System.Diagnostics.Debug.WriteLine($"Missing uncompressed size header in {request.Url}");
|
||||
|
||||
foreach(string key in headers){
|
||||
System.Diagnostics.Debug.WriteLine($" {key}: {headers[key]}");
|
||||
}
|
||||
|
||||
System.Diagnostics.Debugger.Break();
|
||||
}
|
||||
#endif
|
||||
if (request.ResourceType == ResourceType.Script && request.Url.Contains(UrlVendorResource) && int.TryParse(response.ResponseHeaders["Content-Length"], out int totalBytes)){
|
||||
return new ResponseFilterVendor(totalBytes);
|
||||
}
|
||||
|
||||
return base.GetResourceResponseFilter(browserControl, browser, frame, request, response);
|
||||
|
43
Core/Handling/ResourceHandlerFactory.cs
Normal file
43
Core/Handling/ResourceHandlerFactory.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using CefSharp;
|
||||
using TweetDuck.Data;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
sealed class ResourceHandlerFactory : IResourceHandlerFactory{
|
||||
public bool HasHandlers => !handlers.IsEmpty;
|
||||
|
||||
private readonly ConcurrentDictionary<string, IResourceHandler> handlers = new ConcurrentDictionary<string, IResourceHandler>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public IResourceHandler GetResourceHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request){
|
||||
try{
|
||||
return handlers.TryGetValue(request.Url, out IResourceHandler handler) ? handler : null;
|
||||
}finally{
|
||||
request.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// registration
|
||||
|
||||
public bool RegisterHandler(string url, IResourceHandler handler){
|
||||
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
|
||||
handlers.AddOrUpdate(uri.AbsoluteUri, handler, (key, prev) => handler);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool RegisterHandler(ResourceLink link){
|
||||
return RegisterHandler(link.Url, link.Handler);
|
||||
}
|
||||
|
||||
public bool UnregisterHandler(string url){
|
||||
return handlers.TryRemove(url, out IResourceHandler _);
|
||||
}
|
||||
|
||||
public bool UnregisterHandler(ResourceLink link){
|
||||
return UnregisterHandler(link.Url);
|
||||
}
|
||||
}
|
||||
}
|
@@ -54,6 +54,14 @@ namespace TweetDuck.Core.Management{
|
||||
RefreshTimer();
|
||||
}
|
||||
|
||||
public static void TryClearNow(){
|
||||
try{
|
||||
Directory.Delete(CacheFolder, true);
|
||||
}catch{
|
||||
// welp, too bad
|
||||
}
|
||||
}
|
||||
|
||||
public static void Exit(){
|
||||
if (AutoClearTimer != null){
|
||||
AutoClearTimer.Dispose();
|
||||
@@ -61,11 +69,7 @@ namespace TweetDuck.Core.Management{
|
||||
}
|
||||
|
||||
if (ClearOnExit){
|
||||
try{
|
||||
Directory.Delete(CacheFolder, true);
|
||||
}catch{
|
||||
// welp, too bad
|
||||
}
|
||||
TryClearNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Management{
|
||||
sealed class ContextInfo{
|
||||
@@ -107,7 +107,7 @@ namespace TweetDuck.Core.Management{
|
||||
private string unsafeLinkUrl = string.Empty;
|
||||
private string mediaUrl = string.Empty;
|
||||
|
||||
private ChirpInfo chirp = default(ChirpInfo);
|
||||
private ChirpInfo chirp = default;
|
||||
|
||||
public void AddContext(IContextMenuParams parameters){
|
||||
ContextMenuType flags = parameters.TypeFlags;
|
||||
|
@@ -3,9 +3,10 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetLib.Core.Data;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.Plugins.Enums;
|
||||
|
||||
namespace TweetDuck.Core.Management{
|
||||
sealed class ProfileManager{
|
||||
|
@@ -40,7 +40,7 @@ namespace TweetDuck.Core.Management{
|
||||
|
||||
if ((process = Process.Start(new ProcessStartInfo{
|
||||
FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"),
|
||||
Arguments = $"{owner.Handle} {Config.VideoPlayerVolume} \"{url}\" \"{pipe.GenerateToken()}\"",
|
||||
Arguments = $"{owner.Handle} {(int)Math.Floor(100F*owner.GetDPIScale())} {Config.VideoPlayerVolume} \"{url}\" \"{pipe.GenerateToken()}\"",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true
|
||||
})) != null){
|
||||
@@ -135,7 +135,7 @@ namespace TweetDuck.Core.Management{
|
||||
|
||||
private void process_OutputDataReceived(object sender, DataReceivedEventArgs e){
|
||||
if (!string.IsNullOrEmpty(e.Data)){
|
||||
Program.Reporter.Log("[VideoPlayer] "+e.Data);
|
||||
Program.Reporter.LogVerbose("[VideoPlayer] "+e.Data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -14,15 +14,13 @@ namespace TweetDuck.Core.Notification{
|
||||
protected static UserConfig Config => Program.Config.User;
|
||||
|
||||
protected static int FontSizeLevel{
|
||||
get{
|
||||
switch(TweetDeckBridge.FontSize){
|
||||
case "largest": return 4;
|
||||
case "large": return 3;
|
||||
case "small": return 1;
|
||||
case "smallest": return 0;
|
||||
default: return 2;
|
||||
}
|
||||
}
|
||||
get => TweetDeckBridge.FontSize switch{
|
||||
"largest" => 4,
|
||||
"large" => 3,
|
||||
"small" => 1,
|
||||
"smallest" => 0,
|
||||
_ => 2
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual Point PrimaryLocation{
|
||||
@@ -121,18 +119,20 @@ namespace TweetDuck.Core.Notification{
|
||||
this.owner = owner;
|
||||
this.owner.FormClosed += owner_FormClosed;
|
||||
|
||||
ResourceHandlerFactory resourceHandlerFactory = new ResourceHandlerFactory();
|
||||
resourceHandlerFactory.RegisterHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
|
||||
resourceHandlerFactory.RegisterHandler(TweetNotification.AppLogo);
|
||||
|
||||
this.browser = new ChromiumWebBrowser("about:blank"){
|
||||
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBase(false)
|
||||
RequestHandler = new RequestHandlerBase(false),
|
||||
ResourceHandlerFactory = resourceHandlerFactory
|
||||
};
|
||||
|
||||
this.browser.Dock = DockStyle.None;
|
||||
this.browser.ClientSize = ClientSize;
|
||||
|
||||
this.browser.SetupResourceHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
|
||||
this.browser.SetupResourceHandler(TweetNotification.AppLogo);
|
||||
this.browser.SetupZoomEvents();
|
||||
|
||||
Controls.Add(browser);
|
||||
|
@@ -6,10 +6,10 @@ using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetDuck.Resources;
|
||||
using TweetLib.Core.Data;
|
||||
using TweetLib.Core.Features.Plugins.Enums;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
abstract partial class FormNotificationMain : FormNotificationBase{
|
||||
@@ -44,27 +44,17 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
|
||||
private int BaseClientWidth{
|
||||
get{
|
||||
switch(Config.NotificationSize){
|
||||
default:
|
||||
return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*FontSizeLevel));
|
||||
|
||||
case TweetNotification.Size.Custom:
|
||||
return Config.CustomNotificationSize.Width;
|
||||
}
|
||||
}
|
||||
get => Config.NotificationSize switch{
|
||||
TweetNotification.Size.Custom => Config.CustomNotificationSize.Width,
|
||||
_ => BrowserUtils.Scale(284, SizeScale * (1.0 + 0.05 * FontSizeLevel))
|
||||
};
|
||||
}
|
||||
|
||||
private int BaseClientHeight{
|
||||
get{
|
||||
switch(Config.NotificationSize){
|
||||
default:
|
||||
return BrowserUtils.Scale(122, SizeScale*(1.0+0.08*FontSizeLevel));
|
||||
|
||||
case TweetNotification.Size.Custom:
|
||||
return Config.CustomNotificationSize.Height;
|
||||
}
|
||||
}
|
||||
get => Config.NotificationSize switch{
|
||||
TweetNotification.Size.Custom => Config.CustomNotificationSize.Height,
|
||||
_ => BrowserUtils.Scale(122, SizeScale * (1.0 + 0.08 * FontSizeLevel))
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual string BodyClasses => IsCursorOverBrowser ? "td-notification td-hover" : "td-notification";
|
||||
@@ -83,7 +73,7 @@ namespace TweetDuck.Core.Notification{
|
||||
browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
||||
browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
||||
|
||||
plugins.Register(browser, PluginEnvironment.Notification, this);
|
||||
plugins.Register(browser, PluginEnvironment.Notification);
|
||||
|
||||
mouseHookDelegate = MouseHookProc;
|
||||
Disposed += (sender, args) => StopMouseHook(true);
|
||||
|
@@ -6,9 +6,9 @@ using CefSharp;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Resources;
|
||||
using TweetLib.Core.Data;
|
||||
|
||||
namespace TweetDuck.Core.Notification.Screenshot{
|
||||
sealed class FormNotificationScreenshotable : FormNotificationBase{
|
||||
|
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
|
||||
namespace TweetDuck.Core.Notification.Screenshot{
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
sealed class ScreenshotBridge{
|
||||
private readonly Control owner;
|
||||
|
||||
|
@@ -11,18 +11,16 @@ namespace TweetDuck.Core.Notification{
|
||||
public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
|
||||
|
||||
public static IResourceHandler CreateFileHandler(string path){
|
||||
string mimeType;
|
||||
|
||||
switch(Path.GetExtension(path)){
|
||||
case ".weba":
|
||||
case ".webm": mimeType = "audio/webm"; break;
|
||||
case ".wav": mimeType = "audio/wav"; break;
|
||||
case ".ogg": mimeType = "audio/ogg"; break;
|
||||
case ".mp3": mimeType = "audio/mp3"; break;
|
||||
case ".flac": mimeType = "audio/flac"; break;
|
||||
case ".opus": mimeType = "audio/ogg; codecs=opus"; break;
|
||||
default: mimeType = null; break;
|
||||
}
|
||||
string mimeType = Path.GetExtension(path) switch{
|
||||
".weba" => "audio/webm",
|
||||
".webm" => "audio/webm",
|
||||
".wav" => "audio/wav",
|
||||
".ogg" => "audio/ogg",
|
||||
".mp3" => "audio/mp3",
|
||||
".flac" => "audio/flac",
|
||||
".opus" => "audio/ogg; codecs=opus",
|
||||
_ => null
|
||||
};
|
||||
|
||||
try{
|
||||
return ResourceHandler.FromFilePath(path, mimeType);
|
||||
|
@@ -59,7 +59,7 @@ namespace TweetDuck.Core.Notification{
|
||||
build.Append("<style type='text/css'>").Append(customCSS).Append("</style>");
|
||||
}
|
||||
|
||||
build.Append("</head><body class='scroll-styled-v system-font-stack");
|
||||
build.Append("</head><body class='scroll-styled-v");
|
||||
|
||||
if (!string.IsNullOrEmpty(bodyClasses)){
|
||||
build.Append(' ').Append(bodyClasses);
|
||||
|
@@ -2,7 +2,8 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using TweetDuck.Data.Serialization;
|
||||
using TweetLib.Core.Serialization;
|
||||
using TweetLib.Core.Serialization.Converters;
|
||||
|
||||
namespace TweetDuck.Core.Other.Analytics{
|
||||
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]
|
||||
|
@@ -9,6 +9,8 @@ using System.Timers;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other.Analytics{
|
||||
sealed class AnalyticsManager : IDisposable{
|
||||
@@ -20,7 +22,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
||||
#else
|
||||
"https://tweetduck.chylex.com/breadcrumb/report"
|
||||
#endif
|
||||
);
|
||||
);
|
||||
|
||||
public AnalyticsFile File { get; }
|
||||
|
||||
@@ -80,7 +82,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
||||
private void SetLastDataCollectionTime(DateTime dt, string message = null){
|
||||
File.LastDataCollection = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind);
|
||||
File.LastCollectionVersion = Program.VersionTag;
|
||||
File.LastCollectionMessage = message ?? dt.ToString("g", Program.Culture);
|
||||
File.LastCollectionMessage = message ?? dt.ToString("g", Lib.Culture);
|
||||
|
||||
File.Save();
|
||||
RestartTimer();
|
||||
@@ -117,7 +119,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
||||
System.Diagnostics.Debugger.Break();
|
||||
#endif
|
||||
|
||||
BrowserUtils.CreateWebClient().UploadValues(CollectionUrl, "POST", report.ToNameValueCollection());
|
||||
WebUtils.NewClient(BrowserUtils.UserAgentVanilla).UploadValues(CollectionUrl, "POST", report.ToNameValueCollection());
|
||||
}).ContinueWith(task => browser.InvokeAsyncSafe(() => {
|
||||
if (task.Status == TaskStatus.RanToCompletion){
|
||||
SetLastDataCollectionTime(DateTime.Now);
|
||||
|
@@ -11,7 +11,10 @@ using System.Text.RegularExpressions;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.Plugins.Enums;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other.Analytics{
|
||||
static class AnalyticsReportGenerator{
|
||||
@@ -27,7 +30,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
||||
{ "System Edition" , SystemEdition },
|
||||
{ "System Environment" , Environment.Is64BitOperatingSystem ? "64-bit" : "32-bit" },
|
||||
{ "System Build" , SystemBuild },
|
||||
{ "System Locale" , Program.Culture.Name.ToLower() },
|
||||
{ "System Locale" , Lib.Culture.Name.ToLower() },
|
||||
0,
|
||||
{ "RAM" , Exact(RamSize) },
|
||||
{ "GPU" , GpuVendor },
|
||||
@@ -79,7 +82,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
||||
{ "Custom Notification CSS" , RoundUp((UserConfig.CustomNotificationCSS ?? string.Empty).Length, 50) },
|
||||
0,
|
||||
{ "Plugins All" , List(plugins.Plugins.Select(Plugin)) },
|
||||
{ "Plugins Enabled" , List(plugins.Plugins.Where(plugin => plugins.Config.IsEnabled(plugin)).Select(Plugin)) },
|
||||
{ "Plugins Enabled" , List(plugins.Plugins.Where(plugins.Config.IsEnabled).Select(Plugin)) },
|
||||
0,
|
||||
{ "Theme" , Dict(editLayoutDesign, "_theme", "light/def") },
|
||||
{ "Column Width" , Dict(editLayoutDesign, "columnWidth", "310px/def") },
|
||||
@@ -204,36 +207,30 @@ namespace TweetDuck.Core.Other.Analytics{
|
||||
}
|
||||
|
||||
private static string TrayMode{
|
||||
get{
|
||||
switch(UserConfig.TrayBehavior){
|
||||
case TrayIcon.Behavior.DisplayOnly: return "icon";
|
||||
case TrayIcon.Behavior.MinimizeToTray: return "minimize";
|
||||
case TrayIcon.Behavior.CloseToTray: return "close";
|
||||
case TrayIcon.Behavior.Combined: return "combined";
|
||||
default: return "off";
|
||||
}
|
||||
}
|
||||
get => UserConfig.TrayBehavior switch{
|
||||
TrayIcon.Behavior.DisplayOnly => "icon",
|
||||
TrayIcon.Behavior.MinimizeToTray => "minimize",
|
||||
TrayIcon.Behavior.CloseToTray => "close",
|
||||
TrayIcon.Behavior.Combined => "combined",
|
||||
_ => "off"
|
||||
};
|
||||
}
|
||||
|
||||
private static string NotificationPosition{
|
||||
get{
|
||||
switch(UserConfig.NotificationPosition){
|
||||
case TweetNotification.Position.TopLeft: return "top left";
|
||||
case TweetNotification.Position.TopRight: return "top right";
|
||||
case TweetNotification.Position.BottomLeft: return "bottom left";
|
||||
case TweetNotification.Position.BottomRight: return "bottom right";
|
||||
default: return "custom";
|
||||
}
|
||||
}
|
||||
get => UserConfig.NotificationPosition switch{
|
||||
TweetNotification.Position.TopLeft => "top left",
|
||||
TweetNotification.Position.TopRight => "top right",
|
||||
TweetNotification.Position.BottomLeft => "bottom left",
|
||||
TweetNotification.Position.BottomRight => "bottom right",
|
||||
_ => "custom"
|
||||
};
|
||||
}
|
||||
|
||||
private static string NotificationSize{
|
||||
get{
|
||||
switch(UserConfig.NotificationSize){
|
||||
case TweetNotification.Size.Auto: return "auto";
|
||||
default: return RoundUp(UserConfig.CustomNotificationSize.Width, 20)+"x"+RoundUp(UserConfig.CustomNotificationSize.Height, 20);
|
||||
}
|
||||
}
|
||||
get => UserConfig.NotificationSize switch{
|
||||
TweetNotification.Size.Auto => "auto",
|
||||
_ => RoundUp(UserConfig.CustomNotificationSize.Width, 20) + "x" + RoundUp(UserConfig.CustomNotificationSize.Height, 20)
|
||||
};
|
||||
}
|
||||
|
||||
private static string NotificationTimer{
|
||||
|
@@ -64,11 +64,16 @@ namespace TweetDuck.Core.Other{
|
||||
Size = new Size(owner.Size.Width*3/4, owner.Size.Height*3/4);
|
||||
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
|
||||
|
||||
ResourceHandlerFactory resourceHandlerFactory = new ResourceHandlerFactory();
|
||||
resourceHandlerFactory.RegisterHandler(DummyPage);
|
||||
|
||||
this.browser = new ChromiumWebBrowser(url){
|
||||
MenuHandler = new ContextMenuGuide(owner),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
KeyboardHandler = new KeyboardHandlerBase(),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBase(true)
|
||||
RequestHandler = new RequestHandlerBase(true),
|
||||
ResourceHandlerFactory = resourceHandlerFactory
|
||||
};
|
||||
|
||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
@@ -77,8 +82,6 @@ namespace TweetDuck.Core.Other{
|
||||
browser.BrowserSettings.BackgroundColor = (uint)BackColor.ToArgb();
|
||||
browser.Dock = DockStyle.None;
|
||||
browser.Location = ControlExtensions.InvisibleLocation;
|
||||
|
||||
browser.SetupResourceHandler(DummyPage);
|
||||
browser.SetupZoomEvents();
|
||||
|
||||
Controls.Add(browser);
|
||||
|
8
Core/Other/FormPlugins.Designer.cs
generated
8
Core/Other/FormPlugins.Designer.cs
generated
@@ -1,4 +1,6 @@
|
||||
namespace TweetDuck.Core.Other {
|
||||
using TweetDuck.Plugins;
|
||||
|
||||
namespace TweetDuck.Core.Other {
|
||||
partial class FormPlugins {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
@@ -27,7 +29,7 @@
|
||||
this.btnClose = new System.Windows.Forms.Button();
|
||||
this.btnReload = new System.Windows.Forms.Button();
|
||||
this.btnOpenFolder = new System.Windows.Forms.Button();
|
||||
this.flowLayoutPlugins = new TweetDuck.Plugins.Controls.PluginListFlowLayout();
|
||||
this.flowLayoutPlugins = new PluginListFlowLayout();
|
||||
this.timerLayout = new System.Windows.Forms.Timer(this.components);
|
||||
this.SuspendLayout();
|
||||
//
|
||||
@@ -117,7 +119,7 @@
|
||||
private System.Windows.Forms.Button btnClose;
|
||||
private System.Windows.Forms.Button btnReload;
|
||||
private System.Windows.Forms.Button btnOpenFolder;
|
||||
private Plugins.Controls.PluginListFlowLayout flowLayoutPlugins;
|
||||
private PluginListFlowLayout flowLayoutPlugins;
|
||||
private System.Windows.Forms.Timer timerLayout;
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@ using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Controls;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
|
||||
namespace TweetDuck.Core.Other{
|
||||
sealed partial class FormPlugins : Form, FormManager.IAppDialog{
|
||||
|
@@ -10,7 +10,7 @@ using TweetDuck.Core.Other.Settings;
|
||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Updates;
|
||||
using TweetLib.Core.Features.Updates;
|
||||
|
||||
namespace TweetDuck.Core.Other{
|
||||
sealed partial class FormSettings : Form, FormManager.IAppDialog{
|
||||
@@ -195,7 +195,7 @@ namespace TweetDuck.Core.Other{
|
||||
private sealed class SettingsTab{
|
||||
public Button Button { get; }
|
||||
|
||||
public BaseTabSettings Control => control ?? (control = constructor());
|
||||
public BaseTabSettings Control => control ??= constructor();
|
||||
public bool IsInitialized => control != null;
|
||||
|
||||
private readonly Func<BaseTabSettings> constructor;
|
||||
|
@@ -5,8 +5,6 @@ using TweetDuck.Core.Other.Analytics;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
sealed partial class DialogSettingsAnalytics : Form{
|
||||
public string CefArgs => textBoxReport.Text;
|
||||
|
||||
public DialogSettingsAnalytics(AnalyticsReport report){
|
||||
InitializeComponent();
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetLib.Core.Collections;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
sealed partial class DialogSettingsCefArgs : Form{
|
||||
|
@@ -4,8 +4,8 @@ using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core.Management;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
sealed partial class DialogSettingsManage : Form{
|
||||
@@ -33,6 +33,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
|
||||
private readonly PluginManager plugins;
|
||||
private readonly Dictionary<CheckBox, ProfileManager.Items> checkBoxMap = new Dictionary<CheckBox, ProfileManager.Items>(4);
|
||||
private readonly bool openImportImmediately;
|
||||
|
||||
private State currentState;
|
||||
private ProfileManager importManager;
|
||||
@@ -51,6 +52,8 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
this.checkBoxMap[cbSession] = ProfileManager.Items.Session;
|
||||
this.checkBoxMap[cbPluginData] = ProfileManager.Items.PluginData;
|
||||
|
||||
this.openImportImmediately = openImportImmediately;
|
||||
|
||||
if (openImportImmediately){
|
||||
radioImport.Checked = true;
|
||||
btnContinue_Click(null, EventArgs.Empty);
|
||||
@@ -88,6 +91,10 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
|
||||
}){
|
||||
if (dialog.ShowDialog() != DialogResult.OK){
|
||||
if (openImportImmediately){
|
||||
Close();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Data;
|
||||
using TweetLib.Core.Collections;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
sealed partial class DialogSettingsRestart : Form{
|
||||
@@ -18,7 +18,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
tbDataFolder.Enabled = false;
|
||||
}
|
||||
else{
|
||||
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder, string.Empty);
|
||||
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder) ?? string.Empty;
|
||||
tbDataFolder.TextChanged += control_Change;
|
||||
}
|
||||
|
||||
|
71
Core/Other/Settings/TabSettingsGeneral.Designer.cs
generated
71
Core/Other/Settings/TabSettingsGeneral.Designer.cs
generated
@@ -39,6 +39,7 @@
|
||||
this.checkAnimatedAvatars = new System.Windows.Forms.CheckBox();
|
||||
this.labelUpdates = new System.Windows.Forms.Label();
|
||||
this.flowPanelLeft = new System.Windows.Forms.FlowLayoutPanel();
|
||||
this.checkFocusDmInput = new System.Windows.Forms.CheckBox();
|
||||
this.checkKeepLikeFollowDialogsOpen = new System.Windows.Forms.CheckBox();
|
||||
this.labelTray = new System.Windows.Forms.Label();
|
||||
this.comboBoxTrayType = new System.Windows.Forms.ComboBox();
|
||||
@@ -82,11 +83,11 @@
|
||||
//
|
||||
this.checkUpdateNotifications.AutoSize = true;
|
||||
this.checkUpdateNotifications.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 393);
|
||||
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 409);
|
||||
this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
|
||||
this.checkUpdateNotifications.Name = "checkUpdateNotifications";
|
||||
this.checkUpdateNotifications.Size = new System.Drawing.Size(182, 19);
|
||||
this.checkUpdateNotifications.TabIndex = 13;
|
||||
this.checkUpdateNotifications.TabIndex = 14;
|
||||
this.checkUpdateNotifications.Text = "Check Updates Automatically";
|
||||
this.checkUpdateNotifications.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -94,12 +95,12 @@
|
||||
//
|
||||
this.btnCheckUpdates.AutoSize = true;
|
||||
this.btnCheckUpdates.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 417);
|
||||
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 433);
|
||||
this.btnCheckUpdates.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||
this.btnCheckUpdates.Name = "btnCheckUpdates";
|
||||
this.btnCheckUpdates.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||
this.btnCheckUpdates.Size = new System.Drawing.Size(128, 25);
|
||||
this.btnCheckUpdates.TabIndex = 14;
|
||||
this.btnCheckUpdates.TabIndex = 15;
|
||||
this.btnCheckUpdates.Text = "Check Updates Now";
|
||||
this.btnCheckUpdates.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -119,11 +120,11 @@
|
||||
//
|
||||
this.checkBestImageQuality.AutoSize = true;
|
||||
this.checkBestImageQuality.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 98);
|
||||
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 122);
|
||||
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
|
||||
this.checkBestImageQuality.Name = "checkBestImageQuality";
|
||||
this.checkBestImageQuality.Size = new System.Drawing.Size(125, 19);
|
||||
this.checkBestImageQuality.TabIndex = 4;
|
||||
this.checkBestImageQuality.TabIndex = 5;
|
||||
this.checkBestImageQuality.Text = "Best Image Quality";
|
||||
this.checkBestImageQuality.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -131,11 +132,11 @@
|
||||
//
|
||||
this.checkOpenSearchInFirstColumn.AutoSize = true;
|
||||
this.checkOpenSearchInFirstColumn.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 50);
|
||||
this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 74);
|
||||
this.checkOpenSearchInFirstColumn.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
|
||||
this.checkOpenSearchInFirstColumn.Name = "checkOpenSearchInFirstColumn";
|
||||
this.checkOpenSearchInFirstColumn.Size = new System.Drawing.Size(245, 19);
|
||||
this.checkOpenSearchInFirstColumn.TabIndex = 2;
|
||||
this.checkOpenSearchInFirstColumn.TabIndex = 3;
|
||||
this.checkOpenSearchInFirstColumn.Text = "Add Search Columns Before First Column";
|
||||
this.checkOpenSearchInFirstColumn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -158,11 +159,11 @@
|
||||
//
|
||||
this.labelZoom.AutoSize = true;
|
||||
this.labelZoom.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
|
||||
this.labelZoom.Location = new System.Drawing.Point(3, 155);
|
||||
this.labelZoom.Location = new System.Drawing.Point(3, 179);
|
||||
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||
this.labelZoom.Name = "labelZoom";
|
||||
this.labelZoom.Size = new System.Drawing.Size(39, 15);
|
||||
this.labelZoom.TabIndex = 6;
|
||||
this.labelZoom.TabIndex = 7;
|
||||
this.labelZoom.Text = "Zoom";
|
||||
//
|
||||
// zoomUpdateTimer
|
||||
@@ -185,21 +186,21 @@
|
||||
//
|
||||
this.panelZoom.Controls.Add(this.trackBarZoom);
|
||||
this.panelZoom.Controls.Add(this.labelZoomValue);
|
||||
this.panelZoom.Location = new System.Drawing.Point(0, 171);
|
||||
this.panelZoom.Location = new System.Drawing.Point(0, 195);
|
||||
this.panelZoom.Margin = new System.Windows.Forms.Padding(0, 1, 0, 0);
|
||||
this.panelZoom.Name = "panelZoom";
|
||||
this.panelZoom.Size = new System.Drawing.Size(300, 35);
|
||||
this.panelZoom.TabIndex = 7;
|
||||
this.panelZoom.TabIndex = 8;
|
||||
//
|
||||
// checkAnimatedAvatars
|
||||
//
|
||||
this.checkAnimatedAvatars.AutoSize = true;
|
||||
this.checkAnimatedAvatars.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 122);
|
||||
this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 146);
|
||||
this.checkAnimatedAvatars.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
|
||||
this.checkAnimatedAvatars.Name = "checkAnimatedAvatars";
|
||||
this.checkAnimatedAvatars.Size = new System.Drawing.Size(158, 19);
|
||||
this.checkAnimatedAvatars.TabIndex = 5;
|
||||
this.checkAnimatedAvatars.TabIndex = 6;
|
||||
this.checkAnimatedAvatars.Text = "Enable Animated Avatars";
|
||||
this.checkAnimatedAvatars.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -207,11 +208,11 @@
|
||||
//
|
||||
this.labelUpdates.AutoSize = true;
|
||||
this.labelUpdates.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
|
||||
this.labelUpdates.Location = new System.Drawing.Point(0, 367);
|
||||
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
|
||||
this.labelUpdates.Location = new System.Drawing.Point(0, 383);
|
||||
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 27, 0, 1);
|
||||
this.labelUpdates.Name = "labelUpdates";
|
||||
this.labelUpdates.Size = new System.Drawing.Size(69, 19);
|
||||
this.labelUpdates.TabIndex = 12;
|
||||
this.labelUpdates.TabIndex = 13;
|
||||
this.labelUpdates.Text = "UPDATES";
|
||||
//
|
||||
// flowPanelLeft
|
||||
@@ -220,6 +221,7 @@
|
||||
| System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.flowPanelLeft.Controls.Add(this.labelUI);
|
||||
this.flowPanelLeft.Controls.Add(this.checkExpandLinks);
|
||||
this.flowPanelLeft.Controls.Add(this.checkFocusDmInput);
|
||||
this.flowPanelLeft.Controls.Add(this.checkOpenSearchInFirstColumn);
|
||||
this.flowPanelLeft.Controls.Add(this.checkKeepLikeFollowDialogsOpen);
|
||||
this.flowPanelLeft.Controls.Add(this.checkBestImageQuality);
|
||||
@@ -240,15 +242,27 @@
|
||||
this.flowPanelLeft.TabIndex = 0;
|
||||
this.flowPanelLeft.WrapContents = false;
|
||||
//
|
||||
// checkFocusDmInput
|
||||
//
|
||||
this.checkFocusDmInput.AutoSize = true;
|
||||
this.checkFocusDmInput.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.checkFocusDmInput.Location = new System.Drawing.Point(6, 50);
|
||||
this.checkFocusDmInput.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
|
||||
this.checkFocusDmInput.Name = "checkFocusDmInput";
|
||||
this.checkFocusDmInput.Size = new System.Drawing.Size(282, 19);
|
||||
this.checkFocusDmInput.TabIndex = 2;
|
||||
this.checkFocusDmInput.Text = "Focus Input Field When Opening Direct Message";
|
||||
this.checkFocusDmInput.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// checkKeepLikeFollowDialogsOpen
|
||||
//
|
||||
this.checkKeepLikeFollowDialogsOpen.AutoSize = true;
|
||||
this.checkKeepLikeFollowDialogsOpen.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.checkKeepLikeFollowDialogsOpen.Location = new System.Drawing.Point(6, 74);
|
||||
this.checkKeepLikeFollowDialogsOpen.Location = new System.Drawing.Point(6, 98);
|
||||
this.checkKeepLikeFollowDialogsOpen.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
|
||||
this.checkKeepLikeFollowDialogsOpen.Name = "checkKeepLikeFollowDialogsOpen";
|
||||
this.checkKeepLikeFollowDialogsOpen.Size = new System.Drawing.Size(190, 19);
|
||||
this.checkKeepLikeFollowDialogsOpen.TabIndex = 3;
|
||||
this.checkKeepLikeFollowDialogsOpen.TabIndex = 4;
|
||||
this.checkKeepLikeFollowDialogsOpen.Text = "Keep Like/Follow Dialogs Open";
|
||||
this.checkKeepLikeFollowDialogsOpen.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -256,11 +270,11 @@
|
||||
//
|
||||
this.labelTray.AutoSize = true;
|
||||
this.labelTray.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
|
||||
this.labelTray.Location = new System.Drawing.Point(0, 236);
|
||||
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
|
||||
this.labelTray.Location = new System.Drawing.Point(0, 255);
|
||||
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 25, 0, 1);
|
||||
this.labelTray.Name = "labelTray";
|
||||
this.labelTray.Size = new System.Drawing.Size(99, 19);
|
||||
this.labelTray.TabIndex = 8;
|
||||
this.labelTray.TabIndex = 9;
|
||||
this.labelTray.Text = "SYSTEM TRAY";
|
||||
//
|
||||
// comboBoxTrayType
|
||||
@@ -268,32 +282,32 @@
|
||||
this.comboBoxTrayType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.comboBoxTrayType.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.comboBoxTrayType.FormattingEnabled = true;
|
||||
this.comboBoxTrayType.Location = new System.Drawing.Point(5, 260);
|
||||
this.comboBoxTrayType.Location = new System.Drawing.Point(5, 279);
|
||||
this.comboBoxTrayType.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
|
||||
this.comboBoxTrayType.Name = "comboBoxTrayType";
|
||||
this.comboBoxTrayType.Size = new System.Drawing.Size(144, 23);
|
||||
this.comboBoxTrayType.TabIndex = 9;
|
||||
this.comboBoxTrayType.TabIndex = 10;
|
||||
//
|
||||
// labelTrayIcon
|
||||
//
|
||||
this.labelTrayIcon.AutoSize = true;
|
||||
this.labelTrayIcon.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
|
||||
this.labelTrayIcon.Location = new System.Drawing.Point(3, 295);
|
||||
this.labelTrayIcon.Location = new System.Drawing.Point(3, 314);
|
||||
this.labelTrayIcon.Margin = new System.Windows.Forms.Padding(3, 9, 3, 0);
|
||||
this.labelTrayIcon.Name = "labelTrayIcon";
|
||||
this.labelTrayIcon.Size = new System.Drawing.Size(56, 15);
|
||||
this.labelTrayIcon.TabIndex = 10;
|
||||
this.labelTrayIcon.TabIndex = 11;
|
||||
this.labelTrayIcon.Text = "Tray Icon";
|
||||
//
|
||||
// checkTrayHighlight
|
||||
//
|
||||
this.checkTrayHighlight.AutoSize = true;
|
||||
this.checkTrayHighlight.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.checkTrayHighlight.Location = new System.Drawing.Point(6, 316);
|
||||
this.checkTrayHighlight.Location = new System.Drawing.Point(6, 335);
|
||||
this.checkTrayHighlight.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
|
||||
this.checkTrayHighlight.Name = "checkTrayHighlight";
|
||||
this.checkTrayHighlight.Size = new System.Drawing.Size(114, 19);
|
||||
this.checkTrayHighlight.TabIndex = 11;
|
||||
this.checkTrayHighlight.TabIndex = 12;
|
||||
this.checkTrayHighlight.Text = "Enable Highlight";
|
||||
this.checkTrayHighlight.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -548,5 +562,6 @@
|
||||
private System.Windows.Forms.Label labelTranslationTarget;
|
||||
private System.Windows.Forms.ComboBox comboBoxTranslationTarget;
|
||||
private System.Windows.Forms.CheckBox checkHardwareAcceleration;
|
||||
private System.Windows.Forms.CheckBox checkFocusDmInput;
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,8 @@ using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling.General;
|
||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Updates;
|
||||
using TweetLib.Core.Features.Updates;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings{
|
||||
sealed partial class TabSettingsGeneral : BaseTabSettings{
|
||||
@@ -34,6 +35,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
// user interface
|
||||
|
||||
toolTip.SetToolTip(checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead.");
|
||||
toolTip.SetToolTip(checkFocusDmInput, "Places cursor into Direct Message input\r\nfield when opening a conversation.");
|
||||
toolTip.SetToolTip(checkOpenSearchInFirstColumn, "By default, TweetDeck adds Search columns at the end.\r\nThis option makes them appear before the first column instead.");
|
||||
toolTip.SetToolTip(checkKeepLikeFollowDialogsOpen, "Allows liking and following from multiple accounts at once,\r\ninstead of automatically closing the dialog after taking an action.");
|
||||
toolTip.SetToolTip(checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
|
||||
@@ -42,6 +44,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue));
|
||||
|
||||
checkExpandLinks.Checked = Config.ExpandLinksOnHover;
|
||||
checkFocusDmInput.Checked = Config.FocusDmInput;
|
||||
checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn;
|
||||
checkKeepLikeFollowDialogsOpen.Checked = Config.KeepLikeFollowDialogsOpen;
|
||||
checkBestImageQuality.Checked = Config.BestImageQuality;
|
||||
@@ -127,6 +130,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
|
||||
public override void OnReady(){
|
||||
checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
|
||||
checkFocusDmInput.CheckedChanged += checkFocusDmInput_CheckedChanged;
|
||||
checkOpenSearchInFirstColumn.CheckedChanged += checkOpenSearchInFirstColumn_CheckedChanged;
|
||||
checkKeepLikeFollowDialogsOpen.CheckedChanged += checkKeepLikeFollowDialogsOpen_CheckedChanged;
|
||||
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
|
||||
@@ -160,6 +164,10 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
Config.ExpandLinksOnHover = checkExpandLinks.Checked;
|
||||
}
|
||||
|
||||
private void checkFocusDmInput_CheckedChanged(object sender, EventArgs e){
|
||||
Config.FocusDmInput = checkFocusDmInput.Checked;
|
||||
}
|
||||
|
||||
private void checkOpenSearchInFirstColumn_CheckedChanged(object sender, EventArgs e){
|
||||
Config.OpenSearchInFirstColumn = checkOpenSearchInFirstColumn.Checked;
|
||||
}
|
||||
|
@@ -12,13 +12,16 @@ using TweetDuck.Core.Handling.General;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetDuck.Resources;
|
||||
using TweetLib.Core.Features.Plugins.Enums;
|
||||
|
||||
namespace TweetDuck.Core{
|
||||
sealed class TweetDeckBrowser : IDisposable{
|
||||
private static UserConfig Config => Program.Config.User;
|
||||
|
||||
private const string ErrorUrl = "http://td/error";
|
||||
private const string TwitterStyleUrl = "https://abs.twimg.com/tduck/css";
|
||||
|
||||
public bool Ready { get; private set; }
|
||||
|
||||
public bool Enabled{
|
||||
@@ -39,10 +42,14 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
|
||||
private readonly ChromiumWebBrowser browser;
|
||||
private readonly ResourceHandlerFactory resourceHandlerFactory = new ResourceHandlerFactory();
|
||||
|
||||
private string prevSoundNotificationPath = null;
|
||||
|
||||
public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridge tdBridge, UpdateBridge updateBridge){
|
||||
resourceHandlerFactory.RegisterHandler(TweetNotification.AppLogo);
|
||||
resourceHandlerFactory.RegisterHandler(TwitterUtils.LoadingSpinner);
|
||||
|
||||
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
|
||||
|
||||
this.browser = new ChromiumWebBrowser(TwitterUtils.TweetDeckURL){
|
||||
@@ -52,7 +59,8 @@ namespace TweetDuck.Core{
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
KeyboardHandler = new KeyboardHandlerBrowser(owner),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
RequestHandler = requestHandler
|
||||
RequestHandler = requestHandler,
|
||||
ResourceHandlerFactory = resourceHandlerFactory
|
||||
};
|
||||
|
||||
this.browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
@@ -66,13 +74,10 @@ namespace TweetDuck.Core{
|
||||
this.browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb();
|
||||
this.browser.Dock = DockStyle.None;
|
||||
this.browser.Location = ControlExtensions.InvisibleLocation;
|
||||
|
||||
this.browser.SetupResourceHandler(TweetNotification.AppLogo);
|
||||
this.browser.SetupResourceHandler(TwitterUtils.LoadingSpinner);
|
||||
this.browser.SetupZoomEvents();
|
||||
|
||||
owner.Controls.Add(browser);
|
||||
plugins.Register(browser, PluginEnvironment.Browser, owner, true);
|
||||
plugins.Register(browser, PluginEnvironment.Browser, true);
|
||||
|
||||
Config.MuteToggled += Config_MuteToggled;
|
||||
Config.SoundNotificationChanged += Config_SoundNotificationInfoChanged;
|
||||
@@ -117,10 +122,15 @@ namespace TweetDuck.Core{
|
||||
|
||||
if (frame.IsMain){
|
||||
if (TwitterUtils.IsTwitterWebsite(frame)){
|
||||
string css = ScriptLoader.LoadResource("styles/twitter.css", browser);
|
||||
resourceHandlerFactory.RegisterHandler(TwitterStyleUrl, ResourceHandler.FromString(css, mimeType: "text/css"));
|
||||
|
||||
ScriptLoader.ExecuteFile(frame, "twitter.js", browser);
|
||||
}
|
||||
|
||||
frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride);
|
||||
if (!TwitterUtils.IsTwitterLogin2FactorWebsite(frame)){
|
||||
frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +159,10 @@ namespace TweetDuck.Core{
|
||||
|
||||
ScriptLoader.ExecuteFile(frame, "update.js", browser);
|
||||
}
|
||||
|
||||
if (frame.Url == ErrorUrl){
|
||||
resourceHandlerFactory.UnregisterHandler(ErrorUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_LoadError(object sender, LoadErrorEventArgs e){
|
||||
@@ -160,7 +174,8 @@ namespace TweetDuck.Core{
|
||||
string errorPage = ScriptLoader.LoadResourceSilent("pages/error.html");
|
||||
|
||||
if (errorPage != null){
|
||||
browser.LoadHtml(errorPage.Replace("{err}", BrowserUtils.GetErrorName(e.ErrorCode)), "http://td/error");
|
||||
resourceHandlerFactory.RegisterHandler(ErrorUrl, ResourceHandler.FromString(errorPage.Replace("{err}", BrowserUtils.GetErrorName(e.ErrorCode))));
|
||||
browser.Load(ErrorUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,8 +191,14 @@ namespace TweetDuck.Core{
|
||||
string newNotificationPath = Config.NotificationSoundPath;
|
||||
|
||||
if (prevSoundNotificationPath != newNotificationPath){
|
||||
browser.SetupResourceHandler(soundUrl, hasCustomSound ? SoundNotification.CreateFileHandler(newNotificationPath) : null);
|
||||
prevSoundNotificationPath = newNotificationPath;
|
||||
|
||||
if (hasCustomSound){
|
||||
resourceHandlerFactory.RegisterHandler(soundUrl, SoundNotification.CreateFileHandler(newNotificationPath));
|
||||
}
|
||||
else{
|
||||
resourceHandlerFactory.UnregisterHandler(soundUrl);
|
||||
}
|
||||
}
|
||||
|
||||
browser.ExecuteScriptAsync("TDGF_setSoundNotificationData", hasCustomSound, Config.NotificationSoundVolume);
|
||||
|
@@ -3,17 +3,16 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Data;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Utils{
|
||||
static class BrowserUtils{
|
||||
public static string UserAgentVanilla => Program.BrandName+" "+Application.ProductVersion;
|
||||
public static string UserAgentChrome => "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"+Cef.ChromiumVersion+" Safari/537.36";
|
||||
public static string UserAgentVanilla => Program.BrandName + " " + Application.ProductVersion;
|
||||
public static string UserAgentChrome => "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + Cef.ChromiumVersion + " Safari/537.36";
|
||||
|
||||
public static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
|
||||
|
||||
@@ -60,21 +59,6 @@ namespace TweetDuck.Core.Utils{
|
||||
return (ChromiumWebBrowser)browserControl;
|
||||
}
|
||||
|
||||
public static void SetupResourceHandler(this ChromiumWebBrowser browser, string url, IResourceHandler handler){
|
||||
DefaultResourceHandlerFactory factory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory;
|
||||
|
||||
if (handler == null){
|
||||
factory.UnregisterHandler(url);
|
||||
}
|
||||
else{
|
||||
factory.RegisterHandler(url, handler);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetupResourceHandler(this ChromiumWebBrowser browser, ResourceLink resource){
|
||||
browser.SetupResourceHandler(resource.Url, resource.Handler);
|
||||
}
|
||||
|
||||
public static void SetupZoomEvents(this ChromiumWebBrowser browser){
|
||||
void UpdateZoomLevel(object sender, EventArgs args){
|
||||
SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
|
||||
@@ -90,29 +74,11 @@ namespace TweetDuck.Core.Utils{
|
||||
};
|
||||
}
|
||||
|
||||
private const string TwitterTrackingUrl = "t.co";
|
||||
|
||||
public enum UrlCheckResult{
|
||||
Invalid, Tracking, Fine
|
||||
}
|
||||
|
||||
public static UrlCheckResult CheckUrl(string url){
|
||||
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
|
||||
string scheme = uri.Scheme;
|
||||
|
||||
if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto){
|
||||
return uri.Host == TwitterTrackingUrl ? UrlCheckResult.Tracking : UrlCheckResult.Fine;
|
||||
}
|
||||
}
|
||||
|
||||
return UrlCheckResult.Invalid;
|
||||
}
|
||||
|
||||
public static void OpenExternalBrowser(string url){
|
||||
if (string.IsNullOrWhiteSpace(url))return;
|
||||
|
||||
switch(CheckUrl(url)){
|
||||
case UrlCheckResult.Fine:
|
||||
switch(UrlUtils.Check(url)){
|
||||
case UrlUtils.CheckResult.Fine:
|
||||
if (FormGuide.CheckGuideUrl(url, out string hash)){
|
||||
FormGuide.Show(hash);
|
||||
}
|
||||
@@ -133,9 +99,9 @@ namespace TweetDuck.Core.Utils{
|
||||
|
||||
break;
|
||||
|
||||
case UrlCheckResult.Tracking:
|
||||
case UrlUtils.CheckResult.Tracking:
|
||||
if (Config.IgnoreTrackingUrlWarning){
|
||||
goto case UrlCheckResult.Fine;
|
||||
goto case UrlUtils.CheckResult.Fine;
|
||||
}
|
||||
|
||||
using(FormMessage form = new FormMessage("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n"+url, MessageBoxIcon.Warning)){
|
||||
@@ -151,13 +117,13 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
|
||||
if (result == DialogResult.Ignore || result == DialogResult.Yes){
|
||||
goto case UrlCheckResult.Fine;
|
||||
goto case UrlUtils.CheckResult.Fine;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case UrlCheckResult.Invalid:
|
||||
case UrlUtils.CheckResult.Invalid:
|
||||
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
|
||||
break;
|
||||
}
|
||||
@@ -190,50 +156,10 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetFileNameFromUrl(string url){
|
||||
string file = Path.GetFileName(new Uri(url).AbsolutePath);
|
||||
return string.IsNullOrEmpty(file) ? null : file;
|
||||
}
|
||||
|
||||
public static string GetErrorName(CefErrorCode code){
|
||||
return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty);
|
||||
}
|
||||
|
||||
public static WebClient CreateWebClient(){
|
||||
WindowsUtils.EnsureTLS12();
|
||||
|
||||
WebClient client = new WebClient{ Proxy = null };
|
||||
client.Headers[HttpRequestHeader.UserAgent] = UserAgentVanilla;
|
||||
return client;
|
||||
}
|
||||
|
||||
public static WebClient DownloadFileAsync(string url, string target, string cookie, Action onSuccess, Action<Exception> onFailure){
|
||||
WebClient client = CreateWebClient();
|
||||
|
||||
if (cookie != null){
|
||||
client.Headers[HttpRequestHeader.Cookie] = cookie;
|
||||
}
|
||||
|
||||
client.DownloadFileCompleted += (sender, args) => {
|
||||
if (args.Cancelled){
|
||||
try{
|
||||
File.Delete(target);
|
||||
}catch{
|
||||
// didn't want it deleted anyways
|
||||
}
|
||||
}
|
||||
else if (args.Error != null){
|
||||
onFailure?.Invoke(args.Error);
|
||||
}
|
||||
else{
|
||||
onSuccess?.Invoke();
|
||||
}
|
||||
};
|
||||
|
||||
client.DownloadFileAsync(new Uri(url), target);
|
||||
return client;
|
||||
}
|
||||
|
||||
public static int Scale(int baseValue, double scaleFactor){
|
||||
return (int)Math.Round(baseValue*scaleFactor);
|
||||
}
|
||||
|
@@ -8,7 +8,10 @@ using TweetDuck.Core.Management;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Data;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
using TweetLib.Core.Utils;
|
||||
using Cookie = CefSharp.Cookie;
|
||||
|
||||
namespace TweetDuck.Core.Utils{
|
||||
@@ -16,7 +19,7 @@ namespace TweetDuck.Core.Utils{
|
||||
public const string TweetDeckURL = "https://tweetdeck.twitter.com";
|
||||
|
||||
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
|
||||
public const string BackgroundColorOverride = "setTimeout(function f(){let h=document.head;if(!h){setTimeout(f,5);return;}let e=document.createElement('style');e.innerHTML='body,body::before{background:#1c6399!important}';h.appendChild(e);},1)";
|
||||
public const string BackgroundColorOverride = "setTimeout(function f(){let h=document.head;if(!h){setTimeout(f,5);return;}let e=document.createElement('style');e.innerHTML='body,body::before{background:#1c6399!important;margin:0}';h.appendChild(e);},1)";
|
||||
|
||||
public static readonly ResourceLink LoadingSpinner = new ResourceLink("https://ton.twimg.com/tduck/spinner", ResourceHandler.FromByteArray(Properties.Resources.spinner, "image/apng"));
|
||||
|
||||
@@ -27,14 +30,6 @@ namespace TweetDuck.Core.Utils{
|
||||
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
|
||||
};
|
||||
|
||||
public static readonly string[] ValidImageExtensions = {
|
||||
".jpg", ".jpeg", ".png", ".gif"
|
||||
};
|
||||
|
||||
public enum ImageQuality{
|
||||
Default, Orig
|
||||
}
|
||||
|
||||
public static bool IsTweetDeckWebsite(IFrame frame){
|
||||
return frame.Url.Contains("//tweetdeck.twitter.com/");
|
||||
}
|
||||
@@ -43,38 +38,23 @@ namespace TweetDuck.Core.Utils{
|
||||
return frame.Url.Contains("//twitter.com/");
|
||||
}
|
||||
|
||||
private static string ExtractMediaBaseLink(string url){
|
||||
int slash = url.LastIndexOf('/');
|
||||
return slash == -1 ? url : StringUtils.ExtractBefore(url, ':', slash);
|
||||
public static bool IsTwitterLogin2FactorWebsite(IFrame frame){
|
||||
return frame.Url.Contains("//twitter.com/account/login_verification");
|
||||
}
|
||||
|
||||
public static string GetMediaLink(string url, ImageQuality quality){
|
||||
if (quality == ImageQuality.Orig){
|
||||
string result = ExtractMediaBaseLink(url);
|
||||
|
||||
if (url.Contains("//ton.twitter.com/") && url.Contains("/ton/data/dm/")){
|
||||
result += ":large";
|
||||
}
|
||||
else if (result != url || url.Contains("//pbs.twimg.com/media/")){
|
||||
result += ":orig";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
else{
|
||||
return url;
|
||||
}
|
||||
return ImageUrl.TryParse(url, out var obj) ? obj.WithQuality(quality) : url;
|
||||
}
|
||||
|
||||
public static string GetImageFileName(string url){
|
||||
return BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(url));
|
||||
return UrlUtils.GetFileNameFromUrl(ImageUrl.TryParse(url, out var obj) ? obj.WithNoQuality : url);
|
||||
}
|
||||
|
||||
public static void ViewImage(string url, ImageQuality quality){
|
||||
void ViewImageInternal(string path){
|
||||
string ext = Path.GetExtension(path);
|
||||
|
||||
if (ValidImageExtensions.Contains(ext)){
|
||||
if (ImageUrl.ValidExtensions.Contains(ext)){
|
||||
WindowsUtils.OpenAssociatedProgram(path);
|
||||
}
|
||||
else{
|
||||
@@ -84,7 +64,7 @@ namespace TweetDuck.Core.Utils{
|
||||
|
||||
string file = Path.Combine(BrowserCache.CacheFolder, GetImageFileName(url) ?? Path.GetRandomFileName());
|
||||
|
||||
if (WindowsUtils.FileExistsAndNotEmpty(file)){
|
||||
if (FileUtils.FileExistsAndNotEmpty(file)){
|
||||
ViewImageInternal(file);
|
||||
}
|
||||
else{
|
||||
@@ -139,7 +119,7 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
|
||||
public static void DownloadVideo(string url, string username){
|
||||
string filename = BrowserUtils.GetFileNameFromUrl(url);
|
||||
string filename = UrlUtils.GetFileNameFromUrl(url);
|
||||
string ext = Path.GetExtension(filename);
|
||||
|
||||
using(SaveFileDialog dialog = new SaveFileDialog{
|
||||
@@ -174,7 +154,10 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
}
|
||||
|
||||
BrowserUtils.DownloadFileAsync(url, target, cookieStr, onSuccess, onFailure);
|
||||
WebClient client = WebUtils.NewClient(BrowserUtils.UserAgentChrome);
|
||||
client.Headers[HttpRequestHeader.Cookie] = cookieStr;
|
||||
client.DownloadFileCompleted += WebUtils.FileDownloadCallback(target, onSuccess, onFailure);
|
||||
client.DownloadFileAsync(new Uri(url), target);
|
||||
}, scheduler);
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
@@ -16,7 +15,6 @@ namespace TweetDuck.Core.Utils{
|
||||
private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false);
|
||||
|
||||
private static readonly bool IsWindows8OrNewer;
|
||||
private static bool HasMicrosoftBeenBroughtTo2008Yet;
|
||||
|
||||
public static int CurrentProcessID { get; }
|
||||
public static bool ShouldAvoidToolWindow { get; }
|
||||
@@ -33,47 +31,6 @@ namespace TweetDuck.Core.Utils{
|
||||
ShouldAvoidToolWindow = IsWindows8OrNewer;
|
||||
}
|
||||
|
||||
public static void EnsureTLS12(){
|
||||
if (!HasMicrosoftBeenBroughtTo2008Yet){
|
||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
||||
ServicePointManager.SecurityProtocol &= ~(SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11);
|
||||
HasMicrosoftBeenBroughtTo2008Yet = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateDirectoryForFile(string file){
|
||||
string dir = Path.GetDirectoryName(file);
|
||||
|
||||
if (dir == null){
|
||||
throw new ArgumentException("Invalid file path: "+file);
|
||||
}
|
||||
else if (dir.Length > 0){
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CheckFolderWritePermission(string path){
|
||||
string testFile = Path.Combine(path, ".test");
|
||||
|
||||
try{
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
using(File.Create(testFile)){}
|
||||
File.Delete(testFile);
|
||||
return true;
|
||||
}catch{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool FileExistsAndNotEmpty(string path){
|
||||
try{
|
||||
return new FileInfo(path).Length > 0;
|
||||
}catch{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool OpenAssociatedProgram(string file, string arguments = "", bool runElevated = false){
|
||||
try{
|
||||
using(Process.Start(new ProcessStartInfo{
|
||||
|
@@ -1,8 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Data.Serialization{
|
||||
interface ITypeConverter{
|
||||
bool TryWriteType(Type type, object value, out string converted);
|
||||
bool TryReadType(Type type, string value, out object converted);
|
||||
}
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data.Serialization;
|
||||
using TweetLib.Core.Serialization.Converters;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Data{
|
||||
sealed class WindowState{
|
||||
|
@@ -1,88 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace TweetDuck.Plugins.Enums{
|
||||
[Flags]
|
||||
enum PluginEnvironment{
|
||||
None = 0,
|
||||
Browser = 1,
|
||||
Notification = 2
|
||||
}
|
||||
|
||||
static class PluginEnvironmentExtensions{
|
||||
public static IEnumerable<PluginEnvironment> Values{
|
||||
get{
|
||||
yield return PluginEnvironment.Browser;
|
||||
yield return PluginEnvironment.Notification;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IncludesDisabledPlugins(this PluginEnvironment environment){
|
||||
return environment == PluginEnvironment.Browser;
|
||||
}
|
||||
|
||||
public static string GetPluginScriptFile(this PluginEnvironment environment){
|
||||
switch(environment){
|
||||
case PluginEnvironment.Browser: return "browser.js";
|
||||
case PluginEnvironment.Notification: return "notification.js";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetPluginScriptVariables(this PluginEnvironment environment){
|
||||
switch(environment){
|
||||
case PluginEnvironment.Browser: return "$,$TD,$TDP,TD";
|
||||
case PluginEnvironment.Notification: return "$TD,$TDP";
|
||||
default: return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<PluginEnvironment, T> Map<T>(T forNone, T forBrowser, T forNotification){
|
||||
return new PluginEnvironmentDictionary<T>(forNone, forBrowser, forNotification);
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberHidesStaticFromOuterClass")]
|
||||
private sealed class PluginEnvironmentDictionary<T> : IReadOnlyDictionary<PluginEnvironment, T>{
|
||||
private const int TotalKeys = 3;
|
||||
|
||||
public IEnumerable<PluginEnvironment> Keys => Enum.GetValues(typeof(PluginEnvironment)).Cast<PluginEnvironment>();
|
||||
public IEnumerable<T> Values => data;
|
||||
public int Count => TotalKeys;
|
||||
|
||||
public T this[PluginEnvironment key] => data[(int)key];
|
||||
|
||||
private readonly T[] data;
|
||||
|
||||
public PluginEnvironmentDictionary(T forNone, T forBrowser, T forNotification){
|
||||
this.data = new T[TotalKeys];
|
||||
this.data[(int)PluginEnvironment.None] = forNone;
|
||||
this.data[(int)PluginEnvironment.Browser] = forBrowser;
|
||||
this.data[(int)PluginEnvironment.Notification] = forNotification;
|
||||
}
|
||||
|
||||
public bool ContainsKey(PluginEnvironment key){
|
||||
return key >= 0 && (int)key < TotalKeys;
|
||||
}
|
||||
|
||||
public bool TryGetValue(PluginEnvironment key, out T value){
|
||||
if (ContainsKey(key)){
|
||||
value = this[key];
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<PluginEnvironment, T>> GetEnumerator(){
|
||||
return Keys.Select(key => new KeyValuePair<PluginEnvironment, T>(key, this[key])).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +0,0 @@
|
||||
namespace TweetDuck.Plugins.Enums{
|
||||
enum PluginFolder{
|
||||
Root, Data
|
||||
}
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
namespace TweetDuck.Plugins.Enums{
|
||||
enum PluginGroup{
|
||||
Official, Custom
|
||||
}
|
||||
|
||||
static class PluginGroupExtensions{
|
||||
public static string GetIdentifierPrefix(this PluginGroup group){
|
||||
switch(group){
|
||||
case PluginGroup.Official: return "official/";
|
||||
case PluginGroup.Custom: return "custom/";
|
||||
default: return "unknown/";
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetIdentifierPrefixShort(this PluginGroup group){
|
||||
switch(group){
|
||||
case PluginGroup.Official: return "o/";
|
||||
case PluginGroup.Custom: return "c/";
|
||||
default: return "?/";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
namespace TweetDuck.Plugins.Controls {
|
||||
namespace TweetDuck.Plugins {
|
||||
partial class PluginControl {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
@@ -3,9 +3,10 @@ using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.Plugins.Enums;
|
||||
|
||||
namespace TweetDuck.Plugins.Controls{
|
||||
namespace TweetDuck.Plugins{
|
||||
sealed partial class PluginControl : UserControl{
|
||||
private readonly PluginManager pluginManager;
|
||||
private readonly Plugin plugin;
|
||||
@@ -55,19 +56,19 @@ namespace TweetDuck.Plugins.Controls{
|
||||
private void panelDescription_Resize(object sender, EventArgs e){
|
||||
SuspendLayout();
|
||||
|
||||
int maxWidth = panelDescription.Width-(panelDescription.VerticalScroll.Visible ? SystemInformation.VerticalScrollBarWidth : 0);
|
||||
int maxWidth = panelDescription.Width - (panelDescription.VerticalScroll.Visible ? SystemInformation.VerticalScrollBarWidth : 0);
|
||||
labelDescription.MaximumSize = new Size(maxWidth, int.MaxValue);
|
||||
|
||||
Font font = labelDescription.Font;
|
||||
int descriptionLines = TextRenderer.MeasureText(labelDescription.Text, font, new Size(maxWidth, int.MaxValue), TextFormatFlags.WordBreak).Height/(font.Height-1);
|
||||
int descriptionLines = TextRenderer.MeasureText(labelDescription.Text, font, new Size(maxWidth, int.MaxValue), TextFormatFlags.WordBreak).Height / (font.Height - 1);
|
||||
|
||||
int requiredLines = Math.Max(descriptionLines, 1+(string.IsNullOrEmpty(labelVersion.Text) ? 0 : 1)+(isConfigurable ? 1 : 0));
|
||||
int requiredLines = Math.Max(descriptionLines, 1 + (string.IsNullOrEmpty(labelVersion.Text) ? 0 : 1) + (isConfigurable ? 1 : 0));
|
||||
|
||||
switch(requiredLines){
|
||||
case 1: nextHeight = MaximumSize.Height-2*(font.Height-1); break;
|
||||
case 2: nextHeight = MaximumSize.Height-(font.Height-1); break;
|
||||
default: nextHeight = MaximumSize.Height; break;
|
||||
}
|
||||
nextHeight = requiredLines switch{
|
||||
1 => MaximumSize.Height - 2 * (font.Height - 1),
|
||||
2 => MaximumSize.Height - 1 * (font.Height - 1),
|
||||
_ => MaximumSize.Height
|
||||
};
|
||||
|
||||
if (nextHeight != Height){
|
||||
timerLayout.Start();
|
@@ -1,7 +1,7 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Plugins.Controls{
|
||||
namespace TweetDuck.Plugins{
|
||||
sealed class PluginListFlowLayout : FlowLayoutPanel{
|
||||
public PluginListFlowLayout(){
|
||||
FlowDirection = FlowDirection.TopDown;
|
@@ -1,70 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
static class PluginLoader{
|
||||
private static readonly string[] EndTag = { "[END]" };
|
||||
|
||||
public static Plugin FromFolder(string path, PluginGroup group){
|
||||
string name = Path.GetFileName(path);
|
||||
|
||||
if (string.IsNullOrEmpty(name)){
|
||||
throw new ArgumentException("Could not extract directory name from path: "+path);
|
||||
}
|
||||
|
||||
Plugin.Builder builder = new Plugin.Builder(group, name, path, Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name));
|
||||
|
||||
foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){
|
||||
builder.AddEnvironment(PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal)));
|
||||
}
|
||||
|
||||
string metaFile = Path.Combine(path, ".meta");
|
||||
|
||||
if (!File.Exists(metaFile)){
|
||||
throw new ArgumentException("Plugin is missing a .meta file");
|
||||
}
|
||||
|
||||
string currentTag = null, currentContents = string.Empty;
|
||||
|
||||
foreach(string line in File.ReadAllLines(metaFile, Encoding.UTF8).Concat(EndTag).Select(line => line.TrimEnd()).Where(line => line.Length > 0)){
|
||||
if (line[0] == '[' && line[line.Length-1] == ']'){
|
||||
if (currentTag != null){
|
||||
SetProperty(builder, currentTag, currentContents);
|
||||
}
|
||||
|
||||
currentTag = line.Substring(1, line.Length-2).ToUpper();
|
||||
currentContents = string.Empty;
|
||||
|
||||
if (line.Equals(EndTag[0])){
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (currentTag != null){
|
||||
currentContents = currentContents.Length == 0 ? line : currentContents+Environment.NewLine+line;
|
||||
}
|
||||
else{
|
||||
throw new FormatException("Missing metadata tag before value: "+line);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.BuildAndSetup();
|
||||
}
|
||||
|
||||
private static void SetProperty(Plugin.Builder builder, string tag, string value){
|
||||
switch(tag){
|
||||
case "NAME": builder.Name = value; break;
|
||||
case "DESCRIPTION": builder.Description = value; break;
|
||||
case "AUTHOR": builder.Author = value; break;
|
||||
case "VERSION": builder.Version = value; break;
|
||||
case "WEBSITE": builder.Website = value; break;
|
||||
case "CONFIGFILE": builder.ConfigFile = value; break;
|
||||
case "CONFIGDEFAULT": builder.ConfigDefault = value; break;
|
||||
case "REQUIRES": builder.RequiredVersion = Version.TryParse(value, out Version version) ? version : throw new FormatException("Invalid required minimum version: "+value); break;
|
||||
default: throw new FormatException("Invalid metadata tag: "+tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,18 +5,20 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetDuck.Plugins.Events;
|
||||
using TweetDuck.Resources;
|
||||
using TweetLib.Core.Data;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.Plugins.Config;
|
||||
using TweetLib.Core.Features.Plugins.Enums;
|
||||
using TweetLib.Core.Features.Plugins.Events;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
sealed class PluginManager{
|
||||
private static readonly IReadOnlyDictionary<PluginEnvironment, string> PluginSetupScriptNames = PluginEnvironmentExtensions.Map(null, "plugins.browser.js", "plugins.notification.js");
|
||||
sealed class PluginManager : IPluginManager{
|
||||
private const string SetupScriptPrefix = "plugins.";
|
||||
|
||||
public string PathOfficialPlugins => Path.Combine(rootPath, "official");
|
||||
public string PathCustomPlugins => Path.Combine(rootPath, "user");
|
||||
public string PathCustomPlugins => Path.Combine(pluginFolder, PluginGroup.Custom.GetSubFolder());
|
||||
|
||||
public IEnumerable<Plugin> Plugins => plugins;
|
||||
public IEnumerable<InjectedHTML> NotificationInjections => bridge.NotificationInjections;
|
||||
@@ -26,7 +28,10 @@ namespace TweetDuck.Plugins{
|
||||
public event EventHandler<PluginErrorEventArgs> Reloaded;
|
||||
public event EventHandler<PluginErrorEventArgs> Executed;
|
||||
|
||||
private readonly string rootPath;
|
||||
private readonly string pluginFolder;
|
||||
private readonly string pluginDataFolder;
|
||||
|
||||
private readonly Control sync;
|
||||
private readonly PluginBridge bridge;
|
||||
|
||||
private readonly HashSet<Plugin> plugins = new HashSet<Plugin>();
|
||||
@@ -35,20 +40,23 @@ namespace TweetDuck.Plugins{
|
||||
|
||||
private IWebBrowser mainBrowser;
|
||||
|
||||
public PluginManager(IPluginConfig config, string rootPath){
|
||||
public PluginManager(Control sync, IPluginConfig config, string pluginFolder, string pluginDataFolder){
|
||||
this.Config = config;
|
||||
this.Config.PluginChangedState += Config_PluginChangedState;
|
||||
|
||||
this.rootPath = rootPath;
|
||||
this.pluginFolder = pluginFolder;
|
||||
this.pluginDataFolder = pluginDataFolder;
|
||||
|
||||
this.sync = sync;
|
||||
this.bridge = new PluginBridge(this);
|
||||
}
|
||||
|
||||
public void Register(IWebBrowser browser, PluginEnvironment environment, Control sync, bool asMainBrowser = false){
|
||||
public void Register(IWebBrowser browser, PluginEnvironment environment, bool asMainBrowser = false){
|
||||
browser.FrameLoadEnd += (sender, args) => {
|
||||
IFrame frame = args.Frame;
|
||||
|
||||
if (frame.IsMain && TwitterUtils.IsTweetDeckWebsite(frame)){
|
||||
ExecutePlugins(frame, environment, sync);
|
||||
ExecutePlugins(frame, environment);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -81,10 +89,10 @@ namespace TweetDuck.Plugins{
|
||||
}
|
||||
else if (plugin.HasConfig){
|
||||
if (File.Exists(plugin.ConfigPath)){
|
||||
using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath.Replace('/', '\\')+"\"")){}
|
||||
using(Process.Start("explorer.exe", "/select,\"" + plugin.ConfigPath.Replace('/', '\\') + "\"")){}
|
||||
}
|
||||
else{
|
||||
using(Process.Start("explorer.exe", '"'+plugin.GetPluginFolder(PluginFolder.Data).Replace('/', '\\')+'"')){}
|
||||
using(Process.Start("explorer.exe", '"' + plugin.GetPluginFolder(PluginFolder.Data).Replace('/', '\\') + '"')){}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,7 +111,7 @@ namespace TweetDuck.Plugins{
|
||||
}while(tokens.ContainsKey(token) && --attempts >= 0);
|
||||
|
||||
if (attempts < 0){
|
||||
token = -tokens.Count-1;
|
||||
token = -tokens.Count - 1;
|
||||
}
|
||||
|
||||
tokens[token] = plugin;
|
||||
@@ -118,35 +126,22 @@ namespace TweetDuck.Plugins{
|
||||
plugins.Clear();
|
||||
tokens.Clear();
|
||||
|
||||
List<string> loadErrors = new List<string>(2);
|
||||
List<string> loadErrors = new List<string>(1);
|
||||
|
||||
IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
|
||||
if (!Directory.Exists(path)){
|
||||
yield break;
|
||||
foreach(var result in PluginGroupExtensions.Values.SelectMany(group => PluginLoader.AllInFolder(pluginFolder, pluginDataFolder, group))){
|
||||
if (result.HasValue){
|
||||
plugins.Add(result.Value);
|
||||
}
|
||||
|
||||
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
|
||||
Plugin plugin;
|
||||
|
||||
try{
|
||||
plugin = PluginLoader.FromFolder(fullDir, group);
|
||||
}catch(Exception e){
|
||||
loadErrors.Add(group.GetIdentifierPrefix()+Path.GetFileName(fullDir)+": "+e.Message);
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return plugin;
|
||||
else{
|
||||
loadErrors.Add(result.Exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
plugins.UnionWith(LoadPluginsFrom(PathOfficialPlugins, PluginGroup.Official));
|
||||
plugins.UnionWith(LoadPluginsFrom(PathCustomPlugins, PluginGroup.Custom));
|
||||
|
||||
Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors));
|
||||
}
|
||||
|
||||
private void ExecutePlugins(IFrame frame, PluginEnvironment environment, Control sync){
|
||||
if (!HasAnyPlugin(environment) || !ScriptLoader.ExecuteFile(frame, PluginSetupScriptNames[environment], sync)){
|
||||
private void ExecutePlugins(IFrame frame, PluginEnvironment environment){
|
||||
if (!HasAnyPlugin(environment) || !ScriptLoader.ExecuteFile(frame, SetupScriptPrefix + environment.GetPluginScriptFile(), sync)){
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -170,14 +165,16 @@ namespace TweetDuck.Plugins{
|
||||
try{
|
||||
script = File.ReadAllText(path);
|
||||
}catch(Exception e){
|
||||
failedPlugins.Add(plugin.Identifier+" ("+Path.GetFileName(path)+"): "+e.Message);
|
||||
failedPlugins.Add($"{plugin.Identifier} ({Path.GetFileName(path)}): {e.Message}");
|
||||
continue;
|
||||
}
|
||||
|
||||
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, GetTokenFromPlugin(plugin), environment), "plugin:"+plugin);
|
||||
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, GetTokenFromPlugin(plugin), environment), $"plugin:{plugin}");
|
||||
}
|
||||
|
||||
Executed?.Invoke(this, new PluginErrorEventArgs(failedPlugins));
|
||||
sync.InvokeAsyncSafe(() => {
|
||||
Executed?.Invoke(this, new PluginErrorEventArgs(failedPlugins));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
40
Program.cs
40
Program.cs
@@ -2,25 +2,26 @@ using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
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.Management;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Core.Collections;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck{
|
||||
static class Program{
|
||||
public const string BrandName = "TweetDuck";
|
||||
public const string Website = "https://tweetduck.chylex.com";
|
||||
public const string BrandName = Lib.BrandName;
|
||||
public const string VersionTag = Lib.VersionTag;
|
||||
|
||||
public const string VersionTag = "1.16.2";
|
||||
public const string Website = "https://tweetduck.chylex.com";
|
||||
|
||||
public static readonly string ProgramPath = AppDomain.CurrentDomain.BaseDirectory;
|
||||
public static readonly bool IsPortable = File.Exists(Path.Combine(ProgramPath, "makeportable"));
|
||||
@@ -47,23 +48,18 @@ namespace TweetDuck{
|
||||
private static readonly LockManager LockManager = new LockManager(Path.Combine(StoragePath, ".lock"));
|
||||
private static bool HasCleanedUp;
|
||||
|
||||
public static CultureInfo Culture { get; }
|
||||
public static Reporter Reporter { get; }
|
||||
public static ConfigManager Config { get; }
|
||||
|
||||
static Program(){
|
||||
Culture = CultureInfo.CurrentCulture;
|
||||
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
|
||||
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
||||
|
||||
#if DEBUG
|
||||
CultureInfo.DefaultThreadCurrentUICulture = Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-us"); // force english exceptions
|
||||
#endif
|
||||
|
||||
Reporter = new Reporter(ErrorLogFilePath);
|
||||
Reporter.SetupUnhandledExceptionHandler("TweetDuck Has Failed :(");
|
||||
|
||||
Config = new ConfigManager();
|
||||
|
||||
Lib.Initialize(new App.Builder{
|
||||
ErrorHandler = Reporter
|
||||
});
|
||||
}
|
||||
|
||||
[STAThread]
|
||||
@@ -74,7 +70,7 @@ namespace TweetDuck{
|
||||
|
||||
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
|
||||
|
||||
if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){
|
||||
if (!FileUtils.CheckFolderWritePermission(StoragePath)){
|
||||
FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: "+StoragePath, FormMessage.OK);
|
||||
return;
|
||||
}
|
||||
@@ -126,6 +122,14 @@ namespace TweetDuck{
|
||||
|
||||
if (Arguments.HasFlag(Arguments.ArgUpdated)){
|
||||
WindowsUtils.TryDeleteFolderWhenAble(InstallerPath, 8000);
|
||||
BrowserCache.TryClearNow();
|
||||
}
|
||||
|
||||
try{
|
||||
RequestHandlerBase.LoadResourceRewriteRules(Arguments.GetValue(Arguments.ArgFreeze));
|
||||
}catch(Exception e){
|
||||
FormMessage.Error("Resource Freeze", "Error parsing resource rewrite rules: "+e.Message, FormMessage.OK);
|
||||
return;
|
||||
}
|
||||
|
||||
BrowserCache.RefreshTimer();
|
||||
@@ -159,7 +163,7 @@ namespace TweetDuck{
|
||||
|
||||
// ProgramPath has a trailing backslash
|
||||
string updaterArgs = "/SP- /SILENT /FORCECLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+Arguments.GetCurrentForInstallerCmd()+"\""+(IsPortable ? " /PORTABLE=1" : "");
|
||||
bool runElevated = !IsPortable || !WindowsUtils.CheckFolderWritePermission(ProgramPath);
|
||||
bool runElevated = !IsPortable || !FileUtils.CheckFolderWritePermission(ProgramPath);
|
||||
|
||||
if (WindowsUtils.OpenAssociatedProgram(mainForm.UpdateInstallerPath, updaterArgs, runElevated)){
|
||||
Application.Exit();
|
||||
@@ -171,7 +175,7 @@ namespace TweetDuck{
|
||||
}
|
||||
|
||||
private static string GetDataStoragePath(){
|
||||
string custom = Arguments.GetValue(Arguments.ArgDataFolder, null);
|
||||
string custom = Arguments.GetValue(Arguments.ArgDataFolder);
|
||||
|
||||
if (custom != null && (custom.Contains(Path.DirectorySeparatorChar) || custom.Contains(Path.AltDirectorySeparatorChar))){
|
||||
if (Path.GetInvalidPathChars().Any(custom.Contains)){
|
||||
|
2
Properties/Resources.Designer.cs
generated
2
Properties/Resources.Designer.cs
generated
@@ -19,7 +19,7 @@ namespace TweetDuck.Properties {
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
25
README.md
25
README.md
@@ -1,30 +1,28 @@
|
||||
# Support
|
||||
|
||||
[Follow TweetDuck on Twitter](https://twitter.com/TryMyAwesomeApp) | [Support via PayPal](https://paypal.me/chylex) | [Support via Patreon](https://www.patreon.com/chylex)
|
||||
[Follow TweetDuck on Twitter](https://twitter.com/TryMyAwesomeApp) | [Support via Ko-fi](https://ko-fi.com/chylex) | [Support via Patreon](https://www.patreon.com/chylex)
|
||||
|
||||
# Build Instructions
|
||||
|
||||
### Setup
|
||||
|
||||
The program was built using Visual Studio 2017. Before opening the solution, please make sure you have the following workloads and components installed (optional components that are not listed can be deselected to save space):
|
||||
The program can be built using Visual Studio 2019. Before opening the solution, please make sure you have the following workloads and components installed (optional components that are not listed can be deselected to save space):
|
||||
* **.NET desktop development**
|
||||
* .NET Framework 4 – 4.6 development tools
|
||||
* .NET Framework 4.7.2 SDK
|
||||
* F# desktop language support
|
||||
* **Desktop development with C++**
|
||||
* VC++ 2017 latest v141 tools
|
||||
* MSVC v142 - VS 2019 C++ x64/x86 build tools (v14.20)
|
||||
|
||||
After opening the solution, right-click the solution and select **Restore NuGet Packages**, or manually run this command in the **Package Manager Console**:
|
||||
```
|
||||
PM> Install-Package CefSharp.WinForms -Version 67.0.0-pre01
|
||||
PM> Install-Package CefSharp.WinForms -Version 67.0.0
|
||||
```
|
||||
|
||||
Note that some pre-release builds of CefSharp are not available on NuGet. To correctly restore packages in that case, open **Package Manager Settings**, and add `https://www.myget.org/F/cefsharp/api/v3/index.json` to the list of package sources.
|
||||
|
||||
### Debug
|
||||
|
||||
The `Debug` configuration uses a separate data folder by default (`%LOCALAPPDATA%\TweetDuckDebug`) to avoid affecting an existing installation of TweetDuck. You can modify this by opening **TweetDuck Properties** in Visual Studio, clicking the **Debug** tab, and changing the **Command line arguments** field.
|
||||
|
||||
While debugging, opening the main menu and clicking **Reload browser** automatically rebuilds all resources in `Resources/Scripts` and `Resources/Plugins`. This allows editing HTML/CSS/JS files without restarting the program, but it will cause a short delay between browser reloads. An F# compiler must be present when building the project to enable this feature: `C:\Program Files (x86)\Microsoft SDKs\F#\10.1\Framework\v4.0\fsc.exe`
|
||||
While debugging, opening the main menu and clicking **Reload browser** automatically rebuilds all resources in `Resources/Scripts` and `Resources/Plugins`. This allows editing HTML/CSS/JS files without restarting the program, but it will cause a short delay between browser reloads.
|
||||
|
||||
### Release
|
||||
|
||||
@@ -44,18 +42,13 @@ If you decide to publicly release a custom version, please make it clear that it
|
||||
- Some files are checked for invalid characters:
|
||||
- `Resources/Plugins/emoji-keyboard/emoji-ordering.txt` line endings must be LF (line feed); any CR (carriage return) in the file will cause a failed build, and you will need to ensure correct line endings in your text editor
|
||||
|
||||
#### Error: The "EmbedAllSources" parameter is not supported by the "Csc" task
|
||||
1. Open `C:\Program Files (x86)\Visual Studio\2017\<edition>\MSBuild\15.0\Bin\Microsoft.CSharp.CurrentVersion.targets` in a text editor
|
||||
2. Remove line that says `EmbedAllSources="$(EmbedAllSources)"`
|
||||
3. Hope the next Visual Studio update fixes it...
|
||||
|
||||
### Installers
|
||||
|
||||
TweetDuck uses **Inno Setup** for installers and updates. First, download and install [InnoSetup QuickStart Pack](http://www.jrsoftware.org/isdl.php) (non-unicode; editor and encryption support not required) and the [Inno Download Plugin](https://code.google.com/archive/p/inno-download-plugin).
|
||||
TweetDuck uses **Inno Setup** for installers and updates. First, download and install [InnoSetup 5.6.1](http://files.jrsoftware.org/is/5/innosetup-5.6.1.exe) (with Preprocessor support) and the [Inno Download Plugin 1.5.0](https://drive.google.com/folderview?id=0Bzw1xBVt0mokSXZrUEFIanV4azA&usp=sharing#list).
|
||||
|
||||
Next, add the Inno Setup installation folder (usually `C:\Program Files (x86)\Inno Setup 5`) into your **PATH** environment variable. You may need to restart File Explorer for the change to take place.
|
||||
Next, add the Inno Setup installation folder (usually `C:\Program Files (x86)\Inno Setup 5`) into your **PATH** environment variable. You may need to restart File Explorer and Visual Studio for the change to take place.
|
||||
|
||||
Now you can generate installers by running `bld/GEN INSTALLERS.bat`. Note that this will only package the files, you still need to run the [release build](#release) in Visual Studio!
|
||||
Now you can generate installers by running `bld/GEN INSTALLERS.bat`. Note that this will only package the files, you still need to run the [release build](#release) in Visual Studio first!
|
||||
|
||||
After the window closes, three installers will be generated inside the `bld/Output` folder:
|
||||
* **TweetDuck.exe**
|
||||
|
23
Reporter.cs
23
Reporter.cs
@@ -4,10 +4,13 @@ using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Core.Application;
|
||||
|
||||
namespace TweetDuck{
|
||||
sealed class Reporter{
|
||||
sealed class Reporter : IAppErrorHandler{
|
||||
private readonly string logFile;
|
||||
|
||||
public Reporter(string logFile){
|
||||
@@ -22,9 +25,17 @@ namespace TweetDuck{
|
||||
};
|
||||
}
|
||||
|
||||
public bool Log(string data){
|
||||
public bool LogVerbose(string data){
|
||||
return Arguments.HasFlag(Arguments.ArgLogging) && LogImportant(data);
|
||||
}
|
||||
|
||||
public bool LogImportant(string data){
|
||||
return ((IAppErrorHandler)this).Log(data);
|
||||
}
|
||||
|
||||
bool IAppErrorHandler.Log(string text){
|
||||
#if DEBUG
|
||||
Debug.WriteLine(data);
|
||||
Debug.WriteLine(text);
|
||||
#endif
|
||||
|
||||
StringBuilder build = new StringBuilder();
|
||||
@@ -33,8 +44,8 @@ namespace TweetDuck{
|
||||
build.Append("Please, report all issues to: https://github.com/chylex/TweetDuck/issues\r\n\r\n");
|
||||
}
|
||||
|
||||
build.Append("[").Append(DateTime.Now.ToString("G", Program.Culture)).Append("]\r\n");
|
||||
build.Append(data).Append("\r\n\r\n");
|
||||
build.Append("[").Append(DateTime.Now.ToString("G", Lib.Culture)).Append("]\r\n");
|
||||
build.Append(text).Append("\r\n\r\n");
|
||||
|
||||
try{
|
||||
File.AppendAllText(logFile, build.ToString(), Encoding.UTF8);
|
||||
@@ -45,7 +56,7 @@ namespace TweetDuck{
|
||||
}
|
||||
|
||||
public void HandleException(string caption, string message, bool canIgnore, Exception e){
|
||||
bool loggedSuccessfully = Log(e.ToString());
|
||||
bool loggedSuccessfully = LogImportant(e.ToString());
|
||||
|
||||
string exceptionText = e is ExpandedLogException ? e.Message+"\n\nDetails with potentially sensitive information are in the Error Log." : e.Message;
|
||||
FormMessage form = new FormMessage(caption, message+"\nError: "+exceptionText, canIgnore ? MessageBoxIcon.Warning : MessageBoxIcon.Error);
|
||||
|
@@ -87,14 +87,14 @@ enabled(){
|
||||
// update UI
|
||||
|
||||
this.btnClearAllHTML = `
|
||||
<a class="clear-columns-btn-all-parent js-header-action link-clean cf app-nav-link padding-h--10" data-title="Clear columns (hold Shift to restore)" data-action="td-clearcolumns-doall">
|
||||
<a class="clear-columns-btn-all-parent js-header-action link-clean cf app-nav-link padding-h--16 padding-v--2" data-title="Clear columns (hold Shift to restore)" data-action="td-clearcolumns-doall">
|
||||
<div class="obj-left margin-l--2"><i class="icon icon-medium icon-clear-timeline"></i></div>
|
||||
<div class="clear-columns-btn-all nbfc padding-ts hide-condensed txt-size--16 app-nav-link-text">Clear columns</div>
|
||||
<div class="clear-columns-btn-all nbfc padding-ts hide-condensed txt-size--14 app-nav-link-text">Clear columns</div>
|
||||
</a>`;
|
||||
|
||||
this.btnClearOneHTML = `
|
||||
<a class="js-action-header-button column-header-link" href="#" data-action="td-clearcolumns-dosingle">
|
||||
<i class="icon icon-clear-timeline js-show-tip" data-placement="bottom" data-original-title="Clear column (hold Shift to restore)" data-action="td-clearcolumns-dosingle"></i>
|
||||
<i class="icon icon-clear-timeline js-show-tip" data-placement="bottom" data-original-title="Clear column (hold Shift to restore)" data-action="td-clearcolumns-dosingle"></i>
|
||||
</a>`;
|
||||
|
||||
this.prevNavMenuMustache = TD.mustaches["menus/column_nav_menu.mustache"];
|
||||
@@ -110,18 +110,28 @@ enabled(){
|
||||
|
||||
// styles
|
||||
|
||||
if (!document.getElementById("td-clearcolumns-workaround")){
|
||||
// TD started caching mustaches so disabling the plugin doesn't update the column headers properly...
|
||||
let workaround = document.createElement("style");
|
||||
workaround.id = "td-clearcolumns-workaround";
|
||||
workaround.innerText = "#tduck a[data-action='td-clearcolumns-dosingle'] { display: none }";
|
||||
document.head.appendChild(workaround);
|
||||
}
|
||||
|
||||
this.css = window.TDPF_createCustomStyle(this);
|
||||
|
||||
this.css.insert(".js-app-add-column.is-hidden + .clear-columns-btn-all-parent { display: none; }");
|
||||
this.css.insert(".column-navigator-overflow .clear-columns-btn-all-parent { display: none !important; }");
|
||||
this.css.insert(".column-navigator-overflow { bottom: 224px !important; }");
|
||||
this.css.insert(".app-navigator .clear-columns-btn-all-parent { font-weight: 700; }");
|
||||
|
||||
this.css.insert(".column-header-links { min-width: 51px !important; }");
|
||||
this.css.insert(".column[data-td-icon='icon-message'] .column-header-links { min-width: 110px !important; }");
|
||||
this.css.insert(".btn-options-tray[data-action='clear'] { display: none !important; }");
|
||||
|
||||
this.css.insert(".column[data-td-icon='icon-schedule'] a[data-action='td-clearcolumns-dosingle'] { display: none; }");
|
||||
this.css.insert(".column[data-td-icon='icon-custom-timeline'] a[data-action='td-clearcolumns-dosingle'] { display: none; }");
|
||||
this.css.insert("#tduck a[data-action='td-clearcolumns-dosingle'] { display: inline-block; }");
|
||||
this.css.insert("#tduck .column[data-td-icon='icon-schedule'] a[data-action='td-clearcolumns-dosingle'] { display: none; }");
|
||||
this.css.insert("#tduck .column[data-td-icon='icon-custom-timeline'] a[data-action='td-clearcolumns-dosingle'] { display: none; }");
|
||||
}
|
||||
|
||||
ready(){
|
||||
|
@@ -8,6 +8,7 @@ enabled(){
|
||||
_theme: "light",
|
||||
themeOverride: false,
|
||||
columnWidth: "310px",
|
||||
composerWidth: "default",
|
||||
fontSize: "12px",
|
||||
hideTweetActions: true,
|
||||
moveTweetActionsToRight: true,
|
||||
@@ -380,8 +381,15 @@ enabled(){
|
||||
this.css.insert("#general_settings .cf { display: none !important }");
|
||||
this.css.insert("#settings-modal .js-setting-list li:nth-child(3) { border-bottom: 1px solid #ccd6dd }");
|
||||
|
||||
this.css.insert("html[data-td-font] { font-size: "+this.config.fontSize+" !important }");
|
||||
this.css.insert(".avatar { border-radius: "+this.config.avatarRadius+"% !important }");
|
||||
this.css.insert(`html[data-td-font] { font-size: ${this.config.fontSize} !important }`);
|
||||
this.css.insert(`.avatar { border-radius: ${this.config.avatarRadius}% !important }`);
|
||||
|
||||
if (this.config.composerWidth !== "default"){
|
||||
const width = this.config.composerWidth;
|
||||
this.css.insert(`.js-app-content.is-open { margin-right: ${width} !important; transform: translateX(${width}) !important }`);
|
||||
this.css.insert(`#tduck .js-app-content.tduck-is-opening { margin-right: 0 !important }`);
|
||||
this.css.insert(`.js-drawer { width: ${width} !important; left: -${width} !important }`);
|
||||
}
|
||||
|
||||
let currentTheme = TD.settings.getTheme();
|
||||
|
||||
@@ -432,7 +440,8 @@ enabled(){
|
||||
}
|
||||
|
||||
if (this.config.forceArialFont){
|
||||
this.css.insert("#tduck .system-font-stack { font-family: Arial, sans-serif; font-weight: 400 }");
|
||||
this.css.insert("#tduck { font-family: Arial, sans-serif; font-weight: 400 }");
|
||||
this.css.insert("#tduck input, #tduck label, #tduck select, #tduck textarea { font-family: Arial }")
|
||||
}
|
||||
|
||||
if (this.config.increaseQuoteTextSize){
|
||||
@@ -569,7 +578,7 @@ ${iconData.map(entry => `#tduck .icon-${entry[0]}:before{content:\"\\f0${entry[1
|
||||
let cols = this.config.columnWidth.slice(1);
|
||||
|
||||
this.css.insert(".column { width: calc((100vw - 205px) / "+cols+" - 6px) !important; min-width: 160px }");
|
||||
this.css.insert(".is-condensed .column { width: calc((100vw - 55px) / "+cols+" - 6px) !important }");
|
||||
this.css.insert(".is-condensed .column { width: calc((100vw - 65px) / "+cols+" - 6px) !important }");
|
||||
}
|
||||
else{
|
||||
this.css.insert(".column { width: "+this.config.columnWidth+" !important }");
|
||||
@@ -603,7 +612,7 @@ html[data-td-font] { font-size: ${this.config.fontSize} !important }
|
||||
.avatar { border-radius: ${this.config.avatarRadius}% !important }
|
||||
|
||||
${this.config.forceArialFont ? `
|
||||
#tduck .system-font-stack { font-family: Arial, sans-serif; font-weight: 400 }
|
||||
#tduck { font-family: Arial, sans-serif; font-weight: 400 }
|
||||
` : ``}
|
||||
|
||||
${this.config.increaseQuoteTextSize ? `
|
||||
@@ -637,6 +646,13 @@ ${notificationScrollbarColor ? `
|
||||
$(".js-dropdown.pos-r").toggleClass("pos-r pos-l");
|
||||
}
|
||||
};
|
||||
|
||||
this.uiDrawerActiveEvent = (e, data) => {
|
||||
return if data.activeDrawer === null || this.config.composerWidth === "default";
|
||||
|
||||
const ele = $(".js-app-content").addClass("tduck-is-opening");
|
||||
setTimeout(() => ele.removeClass("tduck-is-opening"), 250);
|
||||
};
|
||||
}
|
||||
|
||||
ready(){
|
||||
@@ -646,6 +662,7 @@ ready(){
|
||||
|
||||
// layout events
|
||||
$(document).on("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
||||
$(document).on("uiDrawerActive", this.uiDrawerActiveEvent);
|
||||
|
||||
// modal
|
||||
$("[data-action='settings-menu']").on("click", this.onSettingsMenuClickedEvent);
|
||||
@@ -708,10 +725,12 @@ disabled(){
|
||||
window.clearTimeout(this.optimizationTimer);
|
||||
}
|
||||
|
||||
$(document).off("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
||||
$(window).off("focus", this.onWindowFocusEvent);
|
||||
$(window).off("blur", this.onWindowBlurEvent);
|
||||
|
||||
$(document).off("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
||||
$(document).off("uiDrawerActive", this.uiDrawerActiveEvent);
|
||||
|
||||
TD.components.GlobalSettings.prototype.getInfo = this.prevFuncSettingsGetInfo;
|
||||
TD.components.GlobalSettings.prototype.switchTab = this.prevFuncSettingsSwitchTab;
|
||||
|
||||
|
@@ -55,6 +55,20 @@
|
||||
<option disabled></option>
|
||||
</select>
|
||||
|
||||
<!-- COMPOSER SIZE -->
|
||||
|
||||
<label class="txt-uppercase touch-larger-label">
|
||||
<b>New Tweet panel</b>
|
||||
</label>
|
||||
<select data-td-key="composerWidth">
|
||||
<option value="default">Default</option>
|
||||
<option value="270px">Narrow (270px)</option>
|
||||
<option value="310px">Medium (310px)</option>
|
||||
<option value="350px">Wide (350px)</option>
|
||||
<option value="custom-px">Custom</option>
|
||||
<option value="change-custom-px">Change custom value...</option>
|
||||
</select>
|
||||
|
||||
<!-- FONT SIZE -->
|
||||
|
||||
<label class="txt-uppercase touch-larger-label">
|
||||
@@ -69,10 +83,6 @@
|
||||
<option value="custom-px">Custom</option>
|
||||
<option value="change-custom-px">Change custom value...</option>
|
||||
</select>
|
||||
<label class="checkbox">
|
||||
<input data-td-key="forceArialFont" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Use Arial as default font
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input data-td-key="increaseQuoteTextSize" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Increase quoted tweet font size
|
||||
@@ -116,6 +126,10 @@
|
||||
<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="forceArialFont" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Use Arial as default font
|
||||
</label>
|
||||
|
||||
<!-- ADVANCED -->
|
||||
|
||||
@@ -172,13 +186,24 @@
|
||||
|
||||
#edit-design-panel {
|
||||
width: 693px;
|
||||
height: 380px;
|
||||
height: 424px;
|
||||
background-color: #FFF;
|
||||
box-shadow: 0 0 10px rgba(17, 17, 17, 0.5);
|
||||
}
|
||||
|
||||
#edit-design-panel .mdl-header {
|
||||
color: #8899A6;
|
||||
}
|
||||
|
||||
#edit-design-panel .mdl-inner {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
#edit-design-panel .mdl-content {
|
||||
border: 1px solid #CCD6DD;
|
||||
background: #EAEAEA;
|
||||
}
|
||||
|
||||
#edit-design-panel-inner-cols {
|
||||
padding: 0 6px;
|
||||
}
|
||||
@@ -206,7 +231,7 @@
|
||||
|
||||
#edit-design-panel-content label.radio {
|
||||
display: inline-block;
|
||||
margin: 0 16px 5px 4px;
|
||||
margin: 0 16px 0 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
@@ -117,7 +117,7 @@ html.dark .account-settings-bt{border-top:1px solid #e1e8ed}
|
||||
html.dark .account-settings-bb{border-bottom:1px solid #e1e8ed}
|
||||
html.dark .account-stats a{color:#66757f}
|
||||
html.dark .account-stats a:hover{color:#2b7bb9}
|
||||
html.dark .column{background-color:#222426}
|
||||
html.dark .column-panel{background-color:#222426}
|
||||
html.dark .column.is-focused{box-shadow:0 0 0 6px #7aa2c0}
|
||||
html.dark .column-background-fill{background-color:#F5F8FA}
|
||||
html.dark .more-tweets-glow{background-color:#55acee;background:radial-gradient(ellipse farthest-corner at 50% 100%,#55acee 0%,#55acee 25%,rgba(255,255,255,0) 75%)}
|
||||
@@ -135,7 +135,6 @@ html.dark .column-header{background-color:#292F33}
|
||||
html.dark .is-inverted-dark .column-header{border-bottom:1px solid #ddd}
|
||||
html.dark .is-inverted-dark .column-title-edit-box{color:#111;background-color:#fff;border-color:#e1e8ed}
|
||||
html.dark .column-header{border-bottom:1px solid #222426}
|
||||
html.dark .column-header-temp{border-bottom:1px solid #ddd}
|
||||
html.dark .column-title-edit-box{color:#e1e8ed;background-color:#14171A;border-color:#111}
|
||||
html.dark .column-number{color:#66757f}
|
||||
html.dark .is-new .column-type-icon{color:#55acee}
|
||||
@@ -428,8 +427,7 @@ html.dark .list-account .username{color:#8899a6}
|
||||
html.dark .list-listmember .username{color:#8899a6}
|
||||
html.dark .list-listmember .bio{color:#657786}
|
||||
html.dark .divider-bar{background-color:#ddd}
|
||||
html.dark select{background-color:#fff}
|
||||
html.dark input,html.dark textarea,html.dark select{color:#111;border:1px solid #e1e8ed}
|
||||
html.dark input,html.dark textarea,html.dark select{color:#111;border:1px solid #e1e8ed;background-color:#fff}
|
||||
html.dark input:disabled{background-color:#eaeaea;border-color:#e1e8ed}
|
||||
html.dark select:disabled{background-color:#f5f8fa}
|
||||
html.dark input:focus,html.dark select:focus,html.dark textarea:focus,html.dark .focus{border-color:rgba(80,165,230,0.8);box-shadow:inset 0 1px 3px rgba(17,17,17,0.1),0 0 8px rgba(80,165,230,0.6)}
|
||||
@@ -508,18 +506,6 @@ html.dark .lst-group .selected a:hover{background:#55acee}
|
||||
html.dark .lst-group .selected .fullname,html.dark .lst-group .selected .inner strong,html.dark .lst-group .selected .list-link,html.dark .lst-group .selected .list-twitter-list,html.dark .lst-group .selected .list-subtitle,html.dark .lst-group .selected .list-account,html.dark .lst-group .selected .list-listmember,html.dark .lst-group .selected .txt-ellipsis{color:#F5F8FA}
|
||||
html.dark .lst-group .selected .username,html.dark .lst-group .selected .bytext,html.dark .lst-group .selected .subtitle,html.dark .lst-group .selected .icon-protected{color:#eef3f7}
|
||||
html.dark .itm-remove{border-top:1px solid #ddd}
|
||||
html.dark .caret-outer{border-bottom:7px solid rgba(17,17,17,0.1)}
|
||||
html.dark .caret-inner{border-bottom:6px solid #fff}
|
||||
html.dark .drp-h-divider{border-bottom:1px solid #ddd}
|
||||
html.dark .dropdown-menu .typeahead-item,html.dark .dropdown-menu [data-action]{color:#292F33}
|
||||
html.dark .dropdown-menu .is-selected{background:#55acee;color:#fff}
|
||||
html.dark .dropdown-menu .is-selected [data-action]{color:#fff}
|
||||
html.dark .dropdown-menu .is-selected a:not(:hover):not(:focus){color:#fff}
|
||||
html.dark .dropdown-menu a:not(:hover):not(:focus){color:#292F33}
|
||||
html.dark .dropdown-menu-old li:hover{background:#55acee}
|
||||
html.dark .dropdown-menu-old li:hover a{color:#fff}
|
||||
html.dark .dropdown-menu-old li:hover .attribution{color:#fff}
|
||||
html.dark .non-selectable-item{color:#292F33}
|
||||
html.dark .update-available-item:before{background-color:#FFAD1F}
|
||||
html.dark .is-selected .update-available-item:before{background-color:rgba(41,47,51,0.2)}
|
||||
html.dark .popover{background-color:#fff;box-shadow:0 0 10px rgba(17,17,17,0.7)}
|
||||
@@ -802,7 +788,12 @@ html.dark .NotificationList .Notification-body{color:#14171A}
|
||||
html.dark .DrawerModal{color:#14171A}
|
||||
/* fixes */
|
||||
html.dark .app-search-fake{border-color:transparent}
|
||||
html.dark .spinner-small,html.dark .spinner-large{filter:grayscale(80%)brightness(93%)}
|
||||
html.dark .spinner-small,html.dark .spinner-large{filter:grayscale(85%)brightness(117%)}
|
||||
html.dark .tweet>.color-twitter-blue{color:#8bd!important}
|
||||
html.dark .hw-card-container>div{border-color:#292F33;background:transparent}
|
||||
html.dark .hw-card-container>div>div{border-color:#292F33}
|
||||
html.dark .modal-content,html.dark .lst-group,html.dark #actions-modal{color:#292F33}
|
||||
html.dark #actions-modal .account-link{color:#38444d}
|
||||
html.dark .mdl-accent{background-color:transparent}
|
||||
html.dark .lst-launcher a span{color:#657786!important}
|
||||
html.dark .social-proof-names a{color:#3b94d9}
|
||||
|
@@ -56,11 +56,13 @@ enabled(){
|
||||
this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
|
||||
window.TDPF_injectMustache("compose/docked_compose.mustache", "append", '<div class="cf margin-t--12 margin-b--30">', buttonHTML);
|
||||
|
||||
let maybeDockedComposePanel = $(".js-docked-compose");
|
||||
this.getDrawerInput = () => {
|
||||
return $(".js-compose-text", me.composeDrawer);
|
||||
};
|
||||
|
||||
if (maybeDockedComposePanel.length){
|
||||
maybeDockedComposePanel.find(".cf.margin-t--12.margin-b--30").first().append(buttonHTML);
|
||||
}
|
||||
this.getDrawerScroller = () => {
|
||||
return $(".js-compose-scroller > .scroll-v", me.composeDrawer);
|
||||
};
|
||||
|
||||
// keyboard generation
|
||||
|
||||
@@ -79,12 +81,12 @@ enabled(){
|
||||
|
||||
this.currentKeywords = [];
|
||||
|
||||
this.composePanelScroller.trigger("scroll");
|
||||
this.getDrawerScroller().trigger("scroll");
|
||||
|
||||
$(".emoji-keyboard-popup-btn").removeClass("is-selected");
|
||||
|
||||
if (refocus){
|
||||
this.composeInput.focus();
|
||||
this.getDrawerInput().focus();
|
||||
|
||||
if (lastEmojiKeyword && lastEmojiPosition === 0){
|
||||
document.execCommand("insertText", false, lastEmojiKeyword);
|
||||
@@ -229,16 +231,16 @@ enabled(){
|
||||
this.currentSpanner.style.height = ($(this.currentKeyboard).height()-10)+"px";
|
||||
$(".emoji-keyboard-popup-btn").parent().after(this.currentSpanner);
|
||||
|
||||
this.composePanelScroller.trigger("scroll");
|
||||
this.getDrawerScroller().trigger("scroll");
|
||||
};
|
||||
|
||||
const getKeyboardTop = () => {
|
||||
let button = $(".emoji-keyboard-popup-btn");
|
||||
return button.offset().top+button.outerHeight()+me.composePanelScroller.scrollTop()+8;
|
||||
return button.offset().top + button.outerHeight() + me.getDrawerScroller().scrollTop() + 8;
|
||||
};
|
||||
|
||||
const insertEmoji = (src, alt) => {
|
||||
let input = this.composeInput;
|
||||
let input = this.getDrawerInput();
|
||||
|
||||
let val = input.val();
|
||||
let posStart = input[0].selectionStart;
|
||||
@@ -378,6 +380,11 @@ enabled(){
|
||||
hideKeyboard();
|
||||
};
|
||||
|
||||
this.composerActiveEvent = function(e){
|
||||
$(".emoji-keyboard-popup-btn", me.composeDrawer).on("click", me.emojiKeyboardButtonClickEvent);
|
||||
$(".js-docked-compose .js-compose-scroller > .scroll-v", me.composeDrawer).on("scroll", me.composerScrollEvent);
|
||||
};
|
||||
|
||||
this.documentClickEvent = function(e){
|
||||
if (me.currentKeyboard && $(e.target).closest(".compose-text-container").length === 0){
|
||||
hideKeyboard();
|
||||
@@ -396,20 +403,26 @@ enabled(){
|
||||
me.currentKeyboard.style.top = getKeyboardTop()+"px";
|
||||
}
|
||||
};
|
||||
|
||||
// re-enabling
|
||||
|
||||
let maybeDockedComposePanel = $(".js-docked-compose");
|
||||
|
||||
if (maybeDockedComposePanel.length){
|
||||
maybeDockedComposePanel.find(".cf.margin-t--12.margin-b--30").first().append(buttonHTML);
|
||||
this.composerActiveEvent();
|
||||
}
|
||||
}
|
||||
|
||||
ready(){
|
||||
this.composeDrawer = $("[data-drawer='compose']");
|
||||
this.composeInput = $(".js-compose-text", ".js-docked-compose");
|
||||
this.composeDrawer = $(".js-drawer[data-drawer='compose']");
|
||||
this.composeSelector = ".js-compose-text,.js-reply-tweetbox";
|
||||
|
||||
this.composePanelScroller = $(".js-compose-scroller", ".js-docked-compose").first().children().first();
|
||||
this.composePanelScroller.on("scroll", this.composerScrollEvent);
|
||||
|
||||
$(".emoji-keyboard-popup-btn").on("click", this.emojiKeyboardButtonClickEvent);
|
||||
$(document).on("click", this.documentClickEvent);
|
||||
$(document).on("keydown", this.documentKeyEvent);
|
||||
$(document).on("tduckOldComposerActive", this.composerActiveEvent);
|
||||
$(document).on("uiComposeImageAdded", this.uploadFilesEvent);
|
||||
|
||||
this.composeDrawer.on("uiComposeTweetSending", this.composerSendingEvent);
|
||||
|
||||
$(document).on("keydown", this.composeSelector, this.composeInputKeyDownEvent);
|
||||
@@ -529,14 +542,13 @@ disabled(){
|
||||
$(this.currentSpanner).remove();
|
||||
}
|
||||
|
||||
this.composePanelScroller.off("scroll", this.composerScrollEvent);
|
||||
|
||||
$(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonClickEvent);
|
||||
$(".emoji-keyboard-popup-btn").remove();
|
||||
|
||||
$(document).off("click", this.documentClickEvent);
|
||||
$(document).off("keydown", this.documentKeyEvent);
|
||||
$(document).off("tduckOldComposerActive", this.composerActiveEvent);
|
||||
$(document).off("uiComposeImageAdded", this.uploadFilesEvent);
|
||||
|
||||
this.composeDrawer.off("uiComposeTweetSending", this.composerSendingEvent);
|
||||
|
||||
$(document).off("keydown", this.composeSelector, this.composeInputKeyDownEvent);
|
||||
|
@@ -376,22 +376,25 @@ enabled(){
|
||||
};
|
||||
|
||||
this.drawerToggleEvent = function(e, data){
|
||||
if (data.activeDrawer === null){
|
||||
if (typeof data === "undefined" || data.activeDrawer !== "compose"){
|
||||
hideTemplateModal();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ready(){
|
||||
$(".manage-templates-btn").on("click", this.manageTemplatesButtonClickEvent);
|
||||
$(".js-drawer[data-drawer='compose']").on("click", ".manage-templates-btn", this.manageTemplatesButtonClickEvent);
|
||||
$(document).on("uiDrawerActive", this.drawerToggleEvent);
|
||||
$(document).on("click", ".js-new-composer-opt-in", this.drawerToggleEvent);
|
||||
}
|
||||
|
||||
disabled(){
|
||||
$(".manage-templates-btn").remove();
|
||||
$("#templates-modal-wrap").remove();
|
||||
|
||||
$(".js-drawer[data-drawer='compose']").off("click", ".manage-templates-btn", this.manageTemplatesButtonClickEvent);
|
||||
$(document).off("uiDrawerActive", this.drawerToggleEvent);
|
||||
$(document).off("click", ".js-new-composer-opt-in", this.drawerToggleEvent);
|
||||
|
||||
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
|
||||
}
|
||||
|
@@ -171,14 +171,14 @@
|
||||
}
|
||||
|
||||
.template-editor-form input, .template-editor-form textarea {
|
||||
color: #111;
|
||||
background-color: #fff;
|
||||
color: #111 !important;
|
||||
background-color: #fff !important;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.template-editor-form input:focus, .template-editor-form textarea:focus {
|
||||
box-shadow: inset 0 1px 3px rgba(17, 17, 17, 0.1), 0 0 8px rgba(80, 165, 230, 0.6);
|
||||
box-shadow: inset 0 1px 3px rgba(17, 17, 17, 0.1), 0 0 8px rgba(80, 165, 230, 0.6) !important;
|
||||
}
|
||||
|
||||
.template-editor-form textarea {
|
||||
|
@@ -145,7 +145,7 @@ namespace TweetDuck.Resources{
|
||||
|
||||
// ReSharper disable PossibleNullReferenceException
|
||||
object instPluginManager = typeFormBrowser.GetField("plugins", flagsInstance).GetValue(FormManager.TryFind<FormBrowser>());
|
||||
typePluginManager.GetField("rootPath", flagsInstance).SetValue(instPluginManager, newPluginRoot);
|
||||
typePluginManager.GetField("pluginFolder", flagsInstance).SetValue(instPluginManager, newPluginRoot);
|
||||
|
||||
Debug.WriteLine("Reloading hot swapped plugins...");
|
||||
((PluginManager)instPluginManager).Reload();
|
||||
|
@@ -414,7 +414,7 @@
|
||||
let fontSizeName = TD.settings.getFontSize();
|
||||
let themeName = TD.settings.getTheme();
|
||||
|
||||
let columnBackground = getClassStyleProperty("column", "background-color");
|
||||
let columnBackground = getClassStyleProperty("column-panel", "background-color");
|
||||
|
||||
let tags = [
|
||||
"<html "+Array.prototype.map.call(document.documentElement.attributes, ele => `${ele.name}="${ele.value}"`).join(" ")+"><head>"
|
||||
@@ -477,6 +477,19 @@
|
||||
});
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Hook into composer event.
|
||||
//
|
||||
execSafe(function hookComposerEvents(){
|
||||
$(document).on("uiDrawerActive uiRwebComposerOptOut", function(e, data){
|
||||
return if e.type === "uiDrawerActive" && data.activeDrawer !== "compose";
|
||||
|
||||
setTimeout(function(){
|
||||
$(document).trigger("tduckOldComposerActive");
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Add TweetDuck buttons to the settings menu.
|
||||
//
|
||||
@@ -810,7 +823,7 @@
|
||||
html.addClass($(document.documentElement).attr("class"));
|
||||
html.addClass($(document.body).attr("class"));
|
||||
|
||||
html.css("background-color", getClassStyleProperty("column", "background-color"));
|
||||
html.css("background-color", getClassStyleProperty("stream-item", "background-color"));
|
||||
html.css("border", "none");
|
||||
|
||||
for(let selector of [ ".js-quote-detail", ".js-media-preview-container", ".js-media" ]){
|
||||
@@ -1039,14 +1052,16 @@
|
||||
// Block: Refocus the textbox after switching accounts.
|
||||
//
|
||||
onAppReady.push(function setupAccountSwitchRefocus(){
|
||||
const composeInput = $$(".js-compose-text", ".js-docked-compose");
|
||||
|
||||
const refocusInput = function(){
|
||||
composeInput.focus();
|
||||
$$(".js-compose-text", ".js-docked-compose").focus();
|
||||
};
|
||||
|
||||
$$(".js-account-list", ".js-docked-compose").delegate(".js-account-item", "click", function(e){
|
||||
const accountItemClickEvent = function(e){
|
||||
setTimeout(refocusInput, 0);
|
||||
};
|
||||
|
||||
$(document).on("tduckOldComposerActive", function(e){
|
||||
$$(".js-account-list", ".js-docked-compose").delegate(".js-account-item", "click", accountItemClickEvent);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1198,6 +1213,8 @@
|
||||
//
|
||||
execSafe(function setupVideoPlayer(){
|
||||
window.TDGF_playVideo = function(url, username){
|
||||
return if !url;
|
||||
|
||||
$('<div id="td-video-player-overlay" class="ovl" style="display:block"></div>').on("click contextmenu", function(){
|
||||
$TD.playVideo(null, null);
|
||||
}).appendTo(app);
|
||||
@@ -1281,6 +1298,31 @@
|
||||
};
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Fix DM image previews and GIF thumbnails not loading due to new URLs.
|
||||
//
|
||||
if (ensurePropertyExists(TD, "services", "TwitterMedia", "prototype", "getTwitterPreviewUrl")){
|
||||
const prevFunc = TD.services.TwitterMedia.prototype.getTwitterPreviewUrl;
|
||||
|
||||
TD.services.TwitterMedia.prototype.getTwitterPreviewUrl = function(){
|
||||
const url = prevFunc.apply(this, arguments);
|
||||
|
||||
if (url.startsWith("https://ton.twitter.com/1.1/ton/data/dm/") || url.startsWith("https://pbs.twimg.com/tweet_video_thumb/")){
|
||||
const format = url.match(/\?.*format=(\w+)/);
|
||||
|
||||
if (format && format.length === 2){
|
||||
const fix = `.${format[1]}?`;
|
||||
|
||||
if (!url.includes(fix)){
|
||||
return url.replace("?", fix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Block: Fix youtu.be previews not showing up for https links.
|
||||
//
|
||||
@@ -1300,29 +1342,25 @@
|
||||
//
|
||||
// Block: Add a pin icon to make tweet compose drawer stay open.
|
||||
//
|
||||
onAppReady.push(function setupStayOpenPin(){
|
||||
let ele = $(`
|
||||
<svg id="td-compose-drawer-pin" viewBox="0 0 24 24" class="icon js-show-tip" data-original-title="Stay open" data-tooltip-position="left">
|
||||
<path d="M9.884,16.959l3.272,0.001l-0.82,4.568l-1.635,0l-0.817,-4.569Z"/>
|
||||
<rect x="8.694" y="7.208" width="5.652" height="7.445"/>
|
||||
<path d="M16.877,17.448c0,-1.908 -1.549,-3.456 -3.456,-3.456l-3.802,0c-1.907,0 -3.456,1.548 -3.456,3.456l10.714,0Z"/>
|
||||
<path d="M6.572,5.676l2.182,2.183l5.532,0l2.182,-2.183l0,-1.455l-9.896,0l0,1.455Z"/>
|
||||
</svg>`).appendTo(".js-docked-compose .js-compose-header");
|
||||
execSafe(function setupStayOpenPin(){
|
||||
$(document).on("tduckOldComposerActive", function(e){
|
||||
let ele = $(`#import "markup/pin.html"`).appendTo(".js-docked-compose .js-compose-header");
|
||||
|
||||
ele.click(function(){
|
||||
if (TD.settings.getComposeStayOpen()){
|
||||
ele.css("transform", "rotate(0deg)");
|
||||
TD.settings.setComposeStayOpen(false);
|
||||
}
|
||||
else{
|
||||
ele.css("transform", "rotate(90deg)");
|
||||
TD.settings.setComposeStayOpen(true);
|
||||
}
|
||||
});
|
||||
|
||||
ele.click(function(){
|
||||
if (TD.settings.getComposeStayOpen()){
|
||||
ele.css("transform", "rotate(0deg)");
|
||||
TD.settings.setComposeStayOpen(false);
|
||||
}
|
||||
else{
|
||||
ele.css("transform", "rotate(90deg)");
|
||||
TD.settings.setComposeStayOpen(true);
|
||||
}
|
||||
});
|
||||
|
||||
if (TD.settings.getComposeStayOpen()){
|
||||
ele.css("transform", "rotate(90deg)");
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
@@ -1503,6 +1541,8 @@
|
||||
//
|
||||
if (ensurePropertyExists(TD, "components", "ConversationDetailView", "prototype", "showChirp")){
|
||||
TD.components.ConversationDetailView.prototype.showChirp = appendToFunction(TD.components.ConversationDetailView.prototype.showChirp, function(){
|
||||
return if !$TDX.focusDmInput;
|
||||
|
||||
setTimeout(function(){
|
||||
$(".js-reply-tweetbox").first().focus();
|
||||
}, 100);
|
||||
@@ -1577,7 +1617,7 @@
|
||||
}
|
||||
|
||||
//
|
||||
// Block: Fix broken horizontal scrolling of column container when holding Shift. TODO Fix broken smooth scrolling.
|
||||
// Block: Fix broken horizontal scrolling of column container when holding Shift.
|
||||
//
|
||||
if (ensurePropertyExists(TD, "ui", "columns", "setupColumnScrollListeners")){
|
||||
TD.ui.columns.setupColumnScrollListeners = appendToFunction(TD.ui.columns.setupColumnScrollListeners, function(column){
|
||||
@@ -1585,9 +1625,7 @@
|
||||
return if !ele.length;
|
||||
|
||||
ele.off("onmousewheel").on("mousewheel", ".scroll-v", function(e){
|
||||
if (e.shiftKey){
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
e.stopImmediatePropagation();
|
||||
});
|
||||
|
||||
window.TDGF_prioritizeNewestEvent(ele[0], "mousewheel");
|
||||
|
6
Resources/Scripts/imports/markup/pin.html
Normal file
6
Resources/Scripts/imports/markup/pin.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg id="td-compose-drawer-pin" viewBox="0 0 24 24" class="icon js-show-tip" data-original-title="Stay open" data-tooltip-position="left">
|
||||
<path d="M9.884,16.959l3.272,0.001l-0.82,4.568l-1.635,0l-0.817,-4.569Z"/>
|
||||
<rect x="8.694" y="7.208" width="5.652" height="7.445"/>
|
||||
<path d="M16.877,17.448c0,-1.908 -1.549,-3.456 -3.456,-3.456l-3.802,0c-1.907,0 -3.456,1.548 -3.456,3.456l10.714,0Z"/>
|
||||
<path d="M6.572,5.676l2.182,2.183l5.532,0l2.182,-2.183l0,-1.455l-9.896,0l0,1.455Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 488 B |
@@ -7,19 +7,30 @@
|
||||
min-width: 515px;
|
||||
max-width: 835px;
|
||||
height: 328px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#td-introduction-modal .mdl-inner {
|
||||
padding-top: 0;
|
||||
#td-introduction-modal .mdl-header {
|
||||
color: #8899a6;
|
||||
}
|
||||
|
||||
#td-introduction-modal .mdl-header-title {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#td-introduction-modal .mdl-dismiss {
|
||||
color: #292f33;
|
||||
}
|
||||
|
||||
#td-introduction-modal .mdl-inner {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
#td-introduction-modal .mdl-content {
|
||||
padding: 4px 16px 0;
|
||||
overflow-y: auto;
|
||||
border-color: #ccd6dd;
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
||||
#td-introduction-modal p {
|
||||
|
@@ -1,38 +0,0 @@
|
||||
/*****************************/
|
||||
/* Fix min width and margins */
|
||||
/*****************************/
|
||||
|
||||
.page-canvas {
|
||||
width: auto !important;
|
||||
max-width: 888px;
|
||||
}
|
||||
|
||||
.signout-wrapper {
|
||||
width: auto !important;
|
||||
margin: 0 auto !important;
|
||||
}
|
||||
|
||||
.signout {
|
||||
margin: 60px 0 54px !important;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/*******************/
|
||||
/* General styling */
|
||||
/*******************/
|
||||
|
||||
.aside {
|
||||
/* hide elements around dialog */
|
||||
display: none;
|
||||
}
|
||||
|
||||
.buttons button, .buttons a {
|
||||
/* style buttons */
|
||||
display: inline-block;
|
||||
margin: 0 4px !important;
|
||||
border: 1px solid rgba(0, 0, 0, 0.3) !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
@@ -50,7 +50,7 @@ button, .btn, .mdl, .mdl-content, .modal-content, .popover, .lst-modal, .tooltip
|
||||
border-radius: 1px !important;
|
||||
}
|
||||
|
||||
.tweet-button, .app-search-fake, .app-search-input, .compose-text-container, .compose-reply-tweet, .compose-message-recipient-input-container, .compose-message-recipient, .compose-media-bar-holder, .compose-media-bar-thumb, .media-grid-container, .js-add-image-description, .js-quote-tweet-holder, .detail-view-inline-text {
|
||||
.tweet-button, .app-search-fake, .app-search-input, .compose-text-container, .compose-reply-tweet, .compose-message-recipient-input-container, .compose-message-recipient, .compose-media-bar-holder, .compose-media-bar-thumb, .media-grid-container, .js-add-image-description, .js-quote-tweet-holder, .detail-view-inline-text, .mdl-column-rhs {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
@@ -249,10 +249,14 @@ a[data-full-url] {
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
.js-docked-compose footer {
|
||||
.js-docked-compose .compose-remember-state {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.js-new-composer-opt-in {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.compose-content {
|
||||
bottom: 0 !important;
|
||||
}
|
||||
@@ -369,14 +373,25 @@ html[data-td-font='smallest'] .badge-verified:before {
|
||||
|
||||
html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
|
||||
/* fix cut off badge in detail view */
|
||||
width: 13px !important;
|
||||
width: 14px !important;
|
||||
height: 14px !important;
|
||||
background-position: -223px -97px !important;
|
||||
}
|
||||
|
||||
html[data-td-font='smallest'] .fullname-badged:before, html[data-td-font='small'] .fullname-badged:before {
|
||||
/* fix cut off badge in follow chirps */
|
||||
margin-top: -7px !important;
|
||||
/* fix cut off badge in follow chirps and detail view */
|
||||
margin-top: -1px !important;
|
||||
min-width: 14px !important;
|
||||
}
|
||||
|
||||
html[data-td-font='smallest'] .tweet-detail-wrapper .fullname-badged:before {
|
||||
/* fix misaligned badge in detail view */
|
||||
margin-top: -2px !important;
|
||||
}
|
||||
|
||||
html[data-td-font='small'] .tweet-detail-wrapper .fullname-badged:before {
|
||||
/* fix misaligned badge in detail view */
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.keyboard-shortcut-list {
|
||||
|
@@ -6,6 +6,7 @@ html, body {
|
||||
height: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body::before {
|
||||
|
@@ -25,6 +25,17 @@
|
||||
margin: 0 auto !important;
|
||||
}
|
||||
|
||||
.ResponsiveLayout {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.Section {
|
||||
padding: 36px !important;
|
||||
}
|
||||
|
||||
/*******************/
|
||||
/* General styling */
|
||||
/*******************/
|
||||
@@ -39,7 +50,7 @@ body {
|
||||
box-shadow: 0 0 150px rgba(255, 255, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
.topbar, .TopNav {
|
||||
/* hide top bar */
|
||||
display: none !important;
|
||||
}
|
||||
@@ -65,3 +76,42 @@ button[type='submit'] {
|
||||
margin-top: 15px !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
/********************************************/
|
||||
/* Fix min width and margins on logout page */
|
||||
/********************************************/
|
||||
|
||||
html[logout] .page-canvas {
|
||||
width: auto !important;
|
||||
max-width: 888px;
|
||||
}
|
||||
|
||||
html[logout] .signout-wrapper {
|
||||
width: auto !important;
|
||||
margin: 0 auto !important;
|
||||
}
|
||||
|
||||
html[logout] .signout {
|
||||
margin: 60px 0 54px !important;
|
||||
}
|
||||
|
||||
html[logout] .buttons {
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/*******************************/
|
||||
/* General logout page styling */
|
||||
/*******************************/
|
||||
|
||||
html[logout] .aside {
|
||||
/* hide elements around dialog */
|
||||
display: none;
|
||||
}
|
||||
|
||||
html[logout] .buttons button, html[logout] .buttons a {
|
||||
/* style buttons */
|
||||
display: inline-block;
|
||||
margin: 0 4px !important;
|
||||
border: 1px solid rgba(0, 0, 0, 0.3) !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
@@ -8,15 +8,15 @@
|
||||
return;
|
||||
}
|
||||
|
||||
let style = document.createElement("style");
|
||||
let link = document.createElement("link");
|
||||
link.rel = "stylesheet";
|
||||
link.href = "https://abs.twimg.com/tduck/css";
|
||||
|
||||
style.innerText = `#import "styles/twitter.base.css"`;
|
||||
document.head.appendChild(link);
|
||||
|
||||
if (location.pathname === "/logout"){
|
||||
style.innerText += `#import "styles/twitter.logout.css"`;
|
||||
document.documentElement.setAttribute("logout", "");
|
||||
}
|
||||
|
||||
document.head.appendChild(style);
|
||||
};
|
||||
|
||||
setTimeout(injectCSS, 1);
|
||||
|
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="packages\CefSharp.WinForms.67.0.0-pre01\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.67.0.0-pre01\build\CefSharp.WinForms.props')" />
|
||||
<Import Project="packages\CefSharp.Common.67.0.0-pre01\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.67.0.0-pre01\build\CefSharp.Common.props')" />
|
||||
<Import Project="packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props" Condition="Exists('packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props')" />
|
||||
<Import Project="packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.props')" />
|
||||
<Import Project="packages\CefSharp.Common.67.0.0\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.67.0.0\build\CefSharp.Common.props')" />
|
||||
<Import Project="packages\cef.redist.x86.3.3396.1786\build\cef.redist.x86.props" Condition="Exists('packages\cef.redist.x86.3.3396.1786\build\cef.redist.x86.props')" />
|
||||
<Import Project="packages\cef.redist.x64.3.3396.1786\build\cef.redist.x64.props" Condition="Exists('packages\cef.redist.x64.3.3396.1786\build\cef.redist.x64.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
@@ -13,7 +14,8 @@
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>TweetDuck</RootNamespace>
|
||||
<AssemblyName>TweetDuck</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ApplicationIcon>Resources\Images\icon.ico</ApplicationIcon>
|
||||
@@ -32,7 +34,6 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<LangVersion>7</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
@@ -42,7 +43,6 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<LangVersion>7</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
@@ -54,10 +54,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Configuration\Arguments.cs" />
|
||||
<Compile Include="Configuration\Instance\FileConfigInstance.cs" />
|
||||
<Compile Include="Configuration\ConfigManager.cs" />
|
||||
<Compile Include="Configuration\Instance\IConfigInstance.cs" />
|
||||
<Compile Include="Configuration\Instance\PluginConfigInstance.cs" />
|
||||
<Compile Include="Configuration\LockManager.cs" />
|
||||
<Compile Include="Configuration\SystemConfig.cs" />
|
||||
<Compile Include="Configuration\UserConfig.cs" />
|
||||
@@ -91,10 +88,12 @@
|
||||
<DependentUpon>FormBrowser.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Handling\General\FileDialogHandler.cs" />
|
||||
<Compile Include="Core\Handling\KeyboardHandlerBase.cs" />
|
||||
<Compile Include="Core\Handling\KeyboardHandlerBrowser.cs" />
|
||||
<Compile Include="Core\Handling\KeyboardHandlerNotification.cs" />
|
||||
<Compile Include="Core\Handling\RequestHandlerBase.cs" />
|
||||
<Compile Include="Core\Handling\RequestHandlerBrowser.cs" />
|
||||
<Compile Include="Core\Handling\ResourceHandlerFactory.cs" />
|
||||
<Compile Include="Core\Handling\ResourceHandlerNotification.cs" />
|
||||
<Compile Include="Core\Management\ContextInfo.cs" />
|
||||
<Compile Include="Core\Notification\Example\FormNotificationExample.cs">
|
||||
@@ -195,10 +194,7 @@
|
||||
<DependentUpon>TabSettingsFeedback.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\TweetDeckBrowser.cs" />
|
||||
<Compile Include="Core\Utils\LocaleUtils.cs" />
|
||||
<Compile Include="Core\Utils\StringUtils.cs" />
|
||||
<Compile Include="Core\Utils\TwitterUtils.cs" />
|
||||
<Compile Include="Data\CombinedFileStream.cs" />
|
||||
<Compile Include="Core\Management\ProfileManager.cs" />
|
||||
<Compile Include="Core\Other\Settings\TabSettingsAdvanced.cs">
|
||||
<SubType>UserControl</SubType>
|
||||
@@ -228,19 +224,11 @@
|
||||
<DependentUpon>TabSettingsNotifications.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Notification\Screenshot\ScreenshotBridge.cs" />
|
||||
<Compile Include="Data\CommandLineArgs.cs" />
|
||||
<Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
|
||||
<Compile Include="Data\ResourceLink.cs" />
|
||||
<Compile Include="Data\Result.cs" />
|
||||
<Compile Include="Data\Serialization\FileSerializer.cs" />
|
||||
<Compile Include="Data\InjectedHTML.cs" />
|
||||
<Compile Include="Data\Serialization\ITypeConverter.cs" />
|
||||
<Compile Include="Data\Serialization\SerializationSoftException.cs" />
|
||||
<Compile Include="Data\Serialization\SingleTypeConverter.cs" />
|
||||
<Compile Include="Data\TwoKeyDictionary.cs" />
|
||||
<Compile Include="Data\WindowState.cs" />
|
||||
<Compile Include="Core\Utils\WindowsUtils.cs" />
|
||||
<Compile Include="Core\Bridge\TweetDeckBridge.cs" />
|
||||
@@ -250,34 +238,23 @@
|
||||
<Compile Include="Core\Other\FormSettings.Designer.cs">
|
||||
<DependentUpon>FormSettings.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Plugins\Controls\PluginControl.cs">
|
||||
<Compile Include="Plugins\PluginControl.cs">
|
||||
<SubType>UserControl</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Plugins\Controls\PluginControl.Designer.cs">
|
||||
<Compile Include="Plugins\PluginControl.Designer.cs">
|
||||
<DependentUpon>PluginControl.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Plugins\Controls\PluginListFlowLayout.cs">
|
||||
<Compile Include="Plugins\PluginListFlowLayout.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Plugins\Enums\PluginFolder.cs" />
|
||||
<Compile Include="Plugins\IPluginConfig.cs" />
|
||||
<Compile Include="Plugins\Plugin.cs" />
|
||||
<Compile Include="Plugins\Events\PluginChangedStateEventArgs.cs" />
|
||||
<Compile Include="Plugins\PluginBridge.cs" />
|
||||
<Compile Include="Configuration\PluginConfig.cs" />
|
||||
<Compile Include="Plugins\Enums\PluginEnvironment.cs" />
|
||||
<Compile Include="Plugins\Enums\PluginGroup.cs" />
|
||||
<Compile Include="Plugins\Events\PluginErrorEventArgs.cs" />
|
||||
<Compile Include="Plugins\PluginLoader.cs" />
|
||||
<Compile Include="Plugins\PluginManager.cs" />
|
||||
<Compile Include="Plugins\PluginScriptGenerator.cs" />
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Reporter.cs" />
|
||||
<Compile Include="Updates\UpdateCheckEventArgs.cs" />
|
||||
<Compile Include="Updates\FormUpdateDownload.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -294,9 +271,6 @@
|
||||
<Compile Include="Core\Utils\BrowserUtils.cs" />
|
||||
<Compile Include="Core\Utils\NativeMethods.cs" />
|
||||
<Compile Include="Updates\UpdateCheckClient.cs" />
|
||||
<Compile Include="Updates\UpdateDownloadStatus.cs" />
|
||||
<Compile Include="Updates\UpdateHandler.cs" />
|
||||
<Compile Include="Updates\UpdateInfo.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Resources\ScriptLoader.cs" />
|
||||
@@ -377,6 +351,10 @@
|
||||
<None Include="Resources\Scripts\update.js" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="lib\TweetLib.Core\TweetLib.Core.csproj">
|
||||
<Project>{93ba3cb4-a812-4949-b07d-8d393fb38937}</Project>
|
||||
<Name>TweetLib.Core</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="subprocess\TweetDuck.Browser.csproj">
|
||||
<Project>{b10b0017-819e-4f71-870f-8256b36a26aa}</Project>
|
||||
<Name>TweetDuck.Browser</Name>
|
||||
@@ -406,7 +384,7 @@ IF EXIST "$(ProjectDir)bld\post_build.exe" (
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Target Name="BeforeBuild" Condition="(!$([System.IO.File]::Exists("$(ProjectDir)\bld\post_build.exe")) OR ($([System.IO.File]::GetLastWriteTime("$(ProjectDir)\Resources\PostBuild.fsx").Ticks) > $([System.IO.File]::GetLastWriteTime("$(ProjectDir)\bld\post_build.exe").Ticks)))">
|
||||
<Exec Command=""$(ProjectDir)bld\POST BUILD.bat"" WorkingDirectory="$(ProjectDir)bld\" IgnoreExitCode="true" />
|
||||
<Exec Command=""$(ProjectDir)bld\POST BUILD.bat" "$(DevEnvDir)CommonExtensions\Microsoft\FSharp\fsc.exe"" WorkingDirectory="$(ProjectDir)bld\" IgnoreExitCode="true" />
|
||||
</Target>
|
||||
<Target Name="AfterBuild" Condition="$(ConfigurationName) == Release">
|
||||
<Exec Command="del "$(TargetDir)*.pdb"" />
|
||||
@@ -424,11 +402,12 @@ IF EXIST "$(ProjectDir)bld\post_build.exe" (
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('packages\cef.redist.x64.3.3396.1786\build\cef.redist.x64.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x64.3.3396.1786\build\cef.redist.x64.props'))" />
|
||||
<Error Condition="!Exists('packages\cef.redist.x86.3.3396.1786\build\cef.redist.x86.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x86.3.3396.1786\build\cef.redist.x86.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.Common.67.0.0-pre01\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.67.0.0-pre01\build\CefSharp.Common.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.Common.67.0.0-pre01\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.67.0.0-pre01\build\CefSharp.Common.targets'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.WinForms.67.0.0-pre01\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.67.0.0-pre01\build\CefSharp.WinForms.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.WinForms.67.0.0-pre01\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.67.0.0-pre01\build\CefSharp.WinForms.targets'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.Common.67.0.0\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.67.0.0\build\CefSharp.Common.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.Common.67.0.0\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.67.0.0\build\CefSharp.Common.targets'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.targets'))" />
|
||||
<Error Condition="!Exists('packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props'))" />
|
||||
</Target>
|
||||
<Import Project="packages\CefSharp.Common.67.0.0-pre01\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.67.0.0-pre01\build\CefSharp.Common.targets')" />
|
||||
<Import Project="packages\CefSharp.WinForms.67.0.0-pre01\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.67.0.0-pre01\build\CefSharp.WinForms.targets')" />
|
||||
<Import Project="packages\CefSharp.Common.67.0.0\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.67.0.0\build\CefSharp.Common.targets')" />
|
||||
<Import Project="packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.targets')" />
|
||||
</Project>
|
@@ -1,6 +1,6 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27130.2027
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.28729.10
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck", "TweetDuck.csproj", "{2389A7CD-E0D3-4706-8294-092929A33A2D}"
|
||||
EndProject
|
||||
@@ -14,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetTest.System", "lib\Twe
|
||||
EndProject
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TweetTest.Unit", "lib\TweetTest.Unit\TweetTest.Unit.fsproj", "{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck.Core", "lib\TweetLib.Core\TweetLib.Core.csproj", "{93BA3CB4-A812-4949-B07D-8D393FB38937}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x86 = Debug|x86
|
||||
@@ -42,6 +44,10 @@ Global
|
||||
{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Debug|x86.Build.0 = Debug|x86
|
||||
{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Release|x86.ActiveCfg = Debug|x86
|
||||
{93BA3CB4-A812-4949-B07D-8D393FB38937}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{93BA3CB4-A812-4949-B07D-8D393FB38937}.Debug|x86.Build.0 = Debug|x86
|
||||
{93BA3CB4-A812-4949-B07D-8D393FB38937}.Release|x86.ActiveCfg = Release|x86
|
||||
{93BA3CB4-A812-4949-B07D-8D393FB38937}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetLib.Core.Features.Updates;
|
||||
|
||||
namespace TweetDuck.Updates{
|
||||
sealed partial class FormUpdateDownload : Form{
|
||||
|
@@ -6,10 +6,12 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Script.Serialization;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetLib.Core.Features.Updates;
|
||||
using TweetLib.Core.Utils;
|
||||
using JsonObject = System.Collections.Generic.IDictionary<string, object>;
|
||||
|
||||
namespace TweetDuck.Updates{
|
||||
sealed class UpdateCheckClient{
|
||||
sealed class UpdateCheckClient : IUpdateCheckClient{
|
||||
private const string ApiLatestRelease = "https://api.github.com/repos/chylex/TweetDuck/releases/latest";
|
||||
private const string UpdaterAssetName = "TweetDuck.Update.exe";
|
||||
|
||||
@@ -19,10 +21,12 @@ namespace TweetDuck.Updates{
|
||||
this.installerFolder = installerFolder;
|
||||
}
|
||||
|
||||
public Task<UpdateInfo> Check(){
|
||||
bool IUpdateCheckClient.CanCheck => Program.Config.User.EnableUpdateCheck;
|
||||
|
||||
Task<UpdateInfo> IUpdateCheckClient.Check(){
|
||||
TaskCompletionSource<UpdateInfo> result = new TaskCompletionSource<UpdateInfo>();
|
||||
|
||||
WebClient client = BrowserUtils.CreateWebClient();
|
||||
WebClient client = WebUtils.NewClient(BrowserUtils.UserAgentVanilla);
|
||||
client.Headers[HttpRequestHeader.Accept] = "application/vnd.github.v3+json";
|
||||
|
||||
client.DownloadStringTaskAsync(ApiLatestRelease).ContinueWith(task => {
|
||||
@@ -65,10 +69,9 @@ namespace TweetDuck.Updates{
|
||||
private static Exception ExpandWebException(Exception e){
|
||||
if (e is WebException we && we.Response is HttpWebResponse response){
|
||||
try{
|
||||
using(Stream stream = response.GetResponseStream())
|
||||
using(StreamReader reader = new StreamReader(stream, Encoding.GetEncoding(response.CharacterSet ?? "utf-8"))){
|
||||
return new Reporter.ExpandedLogException(e, reader.ReadToEnd());
|
||||
}
|
||||
using var stream = response.GetResponseStream();
|
||||
using var reader = new StreamReader(stream, Encoding.GetEncoding(response.CharacterSet ?? "utf-8"));
|
||||
return new Reporter.ExpandedLogException(e, reader.ReadToEnd());
|
||||
}catch{
|
||||
// whatever
|
||||
}
|
||||
|
@@ -1,16 +1,12 @@
|
||||
@ECHO OFF
|
||||
|
||||
DEL "post_build.exe"
|
||||
|
||||
SET fsc="%PROGRAMFILES(x86)%\Microsoft SDKs\F#\10.1\Framework\v4.0\fsc.exe"
|
||||
|
||||
IF NOT EXIST %fsc% (
|
||||
SET fsc="%PROGRAMFILES%\Microsoft SDKs\F#\10.1\Framework\v4.0\fsc.exe"
|
||||
IF EXIST "post_build.exe" (
|
||||
DEL "post_build.exe"
|
||||
)
|
||||
|
||||
IF NOT EXIST %fsc% (
|
||||
IF NOT EXIST %1 (
|
||||
ECHO fsc.exe not found
|
||||
EXIT 1
|
||||
)
|
||||
|
||||
%fsc% --standalone --deterministic --preferreduilang:en-US --platform:x86 --target:exe --out:post_build.exe "%~dp0..\Resources\PostBuild.fsx"
|
||||
%1 --standalone --deterministic --preferreduilang:en-US --platform:x86 --target:exe --out:post_build.exe "%~dp0..\Resources\PostBuild.fsx"
|
||||
|
@@ -76,14 +76,14 @@ function TDGetNetFrameworkVersion: Cardinal; forward;
|
||||
function TDIsVCMissing: Boolean; forward;
|
||||
procedure TDInstallVCRedist; forward;
|
||||
|
||||
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. }
|
||||
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.7.2. }
|
||||
function InitializeSetup: Boolean;
|
||||
begin
|
||||
UpdatePath := ExpandConstant('{param:UPDATEPATH}')
|
||||
ForceRedistPrompt := ExpandConstant('{param:PROMPTREDIST}')
|
||||
VisitedTasksPage := False
|
||||
|
||||
if (TDGetNetFrameworkVersion() < 379893) and (MsgBox('{#MyAppName} requires .NET Framework 4.5.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
|
||||
if (TDGetNetFrameworkVersion() < 461808) and (MsgBox('{#MyAppName} requires .NET Framework 4.7.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
|
||||
begin
|
||||
Result := False
|
||||
Exit
|
||||
|
@@ -64,14 +64,14 @@ function TDGetNetFrameworkVersion: Cardinal; forward;
|
||||
function TDIsVCMissing: Boolean; forward;
|
||||
procedure TDInstallVCRedist; forward;
|
||||
|
||||
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. }
|
||||
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.7.2. }
|
||||
function InitializeSetup: Boolean;
|
||||
begin
|
||||
UpdatePath := ExpandConstant('{param:UPDATEPATH}')
|
||||
ForceRedistPrompt := ExpandConstant('{param:PROMPTREDIST}')
|
||||
VisitedTasksPage := False
|
||||
|
||||
if (TDGetNetFrameworkVersion() < 379893) and (MsgBox('{#MyAppName} requires .NET Framework 4.5.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
|
||||
if (TDGetNetFrameworkVersion() < 461808) and (MsgBox('{#MyAppName} requires .NET Framework 4.7.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
|
||||
begin
|
||||
Result := False
|
||||
Exit
|
||||
|
@@ -81,7 +81,7 @@ procedure TDExecuteFullDownload; forward;
|
||||
var IsPortable: Boolean;
|
||||
var UpdatePath: String;
|
||||
|
||||
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. Prepare full download package if required. }
|
||||
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.7.2. Prepare full download package if required. }
|
||||
function InitializeSetup: Boolean;
|
||||
begin
|
||||
IsPortable := ExpandConstant('{param:PORTABLE}') = '1'
|
||||
@@ -99,7 +99,7 @@ begin
|
||||
idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/releases/download/'+TDGetAppVersionClean()+'/'+TDGetFullDownloadFileName(), ExpandConstant('{tmp}\{#MyAppName}.Full.exe'))
|
||||
end;
|
||||
|
||||
if (TDGetNetFrameworkVersion() < 379893) and (MsgBox('{#MyAppName} requires .NET Framework 4.5.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
|
||||
if (TDGetNetFrameworkVersion() < 461808) and (MsgBox('{#MyAppName} requires .NET Framework 4.7.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
|
||||
begin
|
||||
Result := False
|
||||
Exit
|
||||
@@ -220,14 +220,14 @@ begin
|
||||
end;
|
||||
|
||||
{ Return whether the version of the installed libcef.dll library matches internal one. }
|
||||
{ TODO: Remove workaround that forces full installation for 1.15 and older eventually. }
|
||||
{ TODO: Remove workaround that forces full installation for 1.16 and older eventually. }
|
||||
function TDIsMatchingCEFVersion: Boolean;
|
||||
var CEFVersion: String;
|
||||
var TDVersionMS: Cardinal;
|
||||
var TDVersionLS: Cardinal;
|
||||
|
||||
begin
|
||||
if (GetVersionNumbers(UpdatePath+'TweetDuck.exe', TDVersionMS, TDVersionLS)) and ((TDVersionMS and $FFFF) < 16) then
|
||||
if (GetVersionNumbers(UpdatePath+'TweetDuck.exe', TDVersionMS, TDVersionLS)) and ((TDVersionMS and $FFFF) < 17) then
|
||||
begin
|
||||
Result := False
|
||||
Exit
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props" Condition="Exists('..\..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
@@ -9,8 +10,10 @@
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>TweetLib.Communication</RootNamespace>
|
||||
<AssemblyName>TweetLib.Communication</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@@ -20,7 +23,7 @@
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<LangVersion>7</LangVersion>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
@@ -40,5 +43,14 @@
|
||||
<Compile Include="DuplexPipe.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.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>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props'))" />
|
||||
</Target>
|
||||
</Project>
|
4
lib/TweetLib.Communication/packages.config
Normal file
4
lib/TweetLib.Communication/packages.config
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Net.Compilers" version="3.0.0" targetFramework="net472" developmentDependency="true" />
|
||||
</packages>
|
28
lib/TweetLib.Core/App.cs
Normal file
28
lib/TweetLib.Core/App.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using TweetLib.Core.Application;
|
||||
|
||||
namespace TweetLib.Core{
|
||||
public sealed class App{
|
||||
public static IAppErrorHandler ErrorHandler { get; private set; }
|
||||
|
||||
// Builder
|
||||
|
||||
public sealed class Builder{
|
||||
public IAppErrorHandler? ErrorHandler { get; set; }
|
||||
|
||||
// Validation
|
||||
|
||||
internal void Initialize(){
|
||||
App.ErrorHandler = Validate(ErrorHandler, nameof(ErrorHandler))!;
|
||||
}
|
||||
|
||||
private T Validate<T>(T obj, string name){
|
||||
if (obj == null){
|
||||
throw new InvalidOperationException("Missing property " + name + " on the provided App.");
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
lib/TweetLib.Core/Application/IAppErrorHandler.cs
Normal file
8
lib/TweetLib.Core/Application/IAppErrorHandler.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace TweetLib.Core.Application{
|
||||
public interface IAppErrorHandler{
|
||||
bool Log(string text);
|
||||
void HandleException(string caption, string message, bool canIgnore, Exception e);
|
||||
}
|
||||
}
|
@@ -2,8 +2,8 @@
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace TweetDuck.Data{
|
||||
sealed class CommandLineArgs{
|
||||
namespace TweetLib.Core.Collections{
|
||||
public sealed class CommandLineArgs{
|
||||
public static CommandLineArgs FromStringArray(char entryChar, string[] array){
|
||||
CommandLineArgs args = new CommandLineArgs();
|
||||
ReadStringArray(entryChar, array, args);
|
||||
@@ -15,7 +15,7 @@ namespace TweetDuck.Data{
|
||||
string entry = array[index];
|
||||
|
||||
if (entry.Length > 0 && entry[0] == entryChar){
|
||||
if (index < array.Length-1){
|
||||
if (index < array.Length - 1){
|
||||
string potentialValue = array[index+1];
|
||||
|
||||
if (potentialValue.Length > 0 && potentialValue[0] == entryChar){
|
||||
@@ -52,7 +52,7 @@ namespace TweetDuck.Data{
|
||||
}
|
||||
else{
|
||||
key = matchValue.Substring(0, indexEquals).TrimStart('-');
|
||||
value = matchValue.Substring(indexEquals+1).Trim('"');
|
||||
value = matchValue.Substring(indexEquals + 1).Trim('"');
|
||||
}
|
||||
|
||||
if (key.Length != 0){
|
||||
@@ -66,7 +66,7 @@ namespace TweetDuck.Data{
|
||||
private readonly HashSet<string> flags = new HashSet<string>();
|
||||
private readonly Dictionary<string, string> values = new Dictionary<string, string>();
|
||||
|
||||
public int Count => flags.Count+values.Count;
|
||||
public int Count => flags.Count + values.Count;
|
||||
|
||||
public void AddFlag(string flag){
|
||||
flags.Add(flag.ToLower());
|
||||
@@ -84,12 +84,8 @@ namespace TweetDuck.Data{
|
||||
values[key.ToLower()] = value;
|
||||
}
|
||||
|
||||
public bool HasValue(string key){
|
||||
return values.ContainsKey(key.ToLower());
|
||||
}
|
||||
|
||||
public string GetValue(string key, string defaultValue){
|
||||
return values.TryGetValue(key.ToLower(), out string val) ? val : defaultValue;
|
||||
public string? GetValue(string key){
|
||||
return values.TryGetValue(key.ToLower(), out string val) ? val : null;
|
||||
}
|
||||
|
||||
public void RemoveValue(string key){
|
||||
@@ -103,7 +99,7 @@ namespace TweetDuck.Data{
|
||||
copy.AddFlag(flag);
|
||||
}
|
||||
|
||||
foreach(KeyValuePair<string, string> kvp in values){
|
||||
foreach(var kvp in values){
|
||||
copy.SetValue(kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
@@ -115,7 +111,7 @@ namespace TweetDuck.Data{
|
||||
target[flag] = "1";
|
||||
}
|
||||
|
||||
foreach(KeyValuePair<string, string> kvp in values){
|
||||
foreach(var kvp in values){
|
||||
target[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
@@ -127,11 +123,11 @@ namespace TweetDuck.Data{
|
||||
build.Append(flag).Append(' ');
|
||||
}
|
||||
|
||||
foreach(KeyValuePair<string, string> kvp in values){
|
||||
foreach(var kvp in values){
|
||||
build.Append(kvp.Key).Append(" \"").Append(kvp.Value).Append("\" ");
|
||||
}
|
||||
|
||||
return build.Length == 0 ? string.Empty : build.Remove(build.Length-1, 1).ToString();
|
||||
return build.Length == 0 ? string.Empty : build.Remove(build.Length - 1, 1).ToString();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace TweetDuck.Data{
|
||||
sealed class TwoKeyDictionary<K1, K2, V>{
|
||||
namespace TweetLib.Core.Collections{
|
||||
public sealed class TwoKeyDictionary<K1, K2, V>{
|
||||
private readonly Dictionary<K1, Dictionary<K2, V>> dict;
|
||||
private readonly int innerCapacity;
|
||||
|
||||
@@ -85,7 +85,8 @@ namespace TweetDuck.Data{
|
||||
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetValue(K1 outerKey, K2 innerKey, out V value){
|
||||
@@ -93,7 +94,7 @@ namespace TweetDuck.Data{
|
||||
return innerDict.TryGetValue(innerKey, out value);
|
||||
}
|
||||
else{
|
||||
value = default(V);
|
||||
value = default!;
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Data{
|
||||
sealed class CombinedFileStream : IDisposable{
|
||||
public const char KeySeparator = '|';
|
||||
namespace TweetLib.Core.Data{
|
||||
public sealed class CombinedFileStream : IDisposable{
|
||||
private const char KeySeparator = '|';
|
||||
|
||||
private readonly Stream stream;
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace TweetDuck.Data{
|
||||
stream.Write(contents, 0, contents.Length);
|
||||
}
|
||||
|
||||
public Entry ReadFile(){
|
||||
public Entry? ReadFile(){
|
||||
int nameLength = stream.ReadByte();
|
||||
|
||||
if (nameLength == -1){
|
||||
@@ -64,7 +64,7 @@ namespace TweetDuck.Data{
|
||||
return new Entry(Encoding.UTF8.GetString(name), contents);
|
||||
}
|
||||
|
||||
public string SkipFile(){
|
||||
public string? SkipFile(){
|
||||
int nameLength = stream.ReadByte();
|
||||
|
||||
if (nameLength == -1){
|
||||
@@ -120,7 +120,7 @@ namespace TweetDuck.Data{
|
||||
|
||||
public void WriteToFile(string path, bool createDirectory){
|
||||
if (createDirectory){
|
||||
WindowsUtils.CreateDirectoryForFile(path);
|
||||
FileUtils.CreateDirectoryForFile(path);
|
||||
}
|
||||
|
||||
File.WriteAllBytes(path, contents);
|
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Data{
|
||||
sealed class InjectedHTML{
|
||||
namespace TweetLib.Core.Data{
|
||||
public sealed class InjectedHTML{
|
||||
public enum Position{
|
||||
Before, After
|
||||
}
|
||||
@@ -27,7 +27,7 @@ namespace TweetDuck.Data{
|
||||
|
||||
switch(position){
|
||||
case Position.Before: cutIndex = index; break;
|
||||
case Position.After: cutIndex = index+search.Length; break;
|
||||
case Position.After: cutIndex = index + search.Length; break;
|
||||
default: return targetHTML;
|
||||
}
|
||||
|
@@ -1,14 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Data{
|
||||
sealed class Result<T>{
|
||||
namespace TweetLib.Core.Data{
|
||||
public sealed class Result<T>{
|
||||
public bool HasValue => exception == null;
|
||||
|
||||
public T Value => HasValue ? value : throw new InvalidOperationException("Requested value from a failed result.");
|
||||
public Exception Exception => exception ?? throw new InvalidOperationException("Requested exception from a successful result.");
|
||||
|
||||
private readonly T value;
|
||||
private readonly Exception exception;
|
||||
private readonly Exception? exception;
|
||||
|
||||
public Result(T value){
|
||||
this.value = value;
|
||||
@@ -16,7 +16,7 @@ namespace TweetDuck.Data{
|
||||
}
|
||||
|
||||
public Result(Exception exception){
|
||||
this.value = default(T);
|
||||
this.value = default!;
|
||||
this.exception = exception ?? throw new ArgumentNullException(nameof(exception));
|
||||
}
|
||||
|
||||
@@ -25,12 +25,12 @@ namespace TweetDuck.Data{
|
||||
onSuccess(value);
|
||||
}
|
||||
else{
|
||||
onException(exception);
|
||||
onException(exception!);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<R> Select<R>(Func<T, R> map){
|
||||
return HasValue ? new Result<R>(map(value)) : new Result<R>(exception);
|
||||
return HasValue ? new Result<R>(map(value)) : new Result<R>(exception!);
|
||||
}
|
||||
}
|
||||
}
|
50
lib/TweetLib.Core/Features/Configuration/BaseConfig.cs
Normal file
50
lib/TweetLib.Core/Features/Configuration/BaseConfig.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TweetLib.Core.Features.Configuration{
|
||||
public abstract class BaseConfig{
|
||||
private readonly IConfigManager configManager;
|
||||
|
||||
protected BaseConfig(IConfigManager configManager){
|
||||
this.configManager = configManager;
|
||||
}
|
||||
|
||||
// Management
|
||||
|
||||
public void Save(){
|
||||
configManager.GetInstanceInfo(this).Save();
|
||||
}
|
||||
|
||||
public void Reload(){
|
||||
configManager.GetInstanceInfo(this).Reload();
|
||||
}
|
||||
|
||||
public void Reset(){
|
||||
configManager.GetInstanceInfo(this).Reset();
|
||||
}
|
||||
|
||||
// Construction methods
|
||||
|
||||
public T ConstructWithDefaults<T>() where T : BaseConfig{
|
||||
return (T)ConstructWithDefaults(configManager);
|
||||
}
|
||||
|
||||
protected abstract BaseConfig ConstructWithDefaults(IConfigManager configManager);
|
||||
|
||||
// Utility methods
|
||||
|
||||
protected void UpdatePropertyWithEvent<T>(ref T field, T value, EventHandler eventHandler){
|
||||
if (!EqualityComparer<T>.Default.Equals(field, value)){
|
||||
field = value;
|
||||
eventHandler?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
protected void UpdatePropertyWithRestartRequest<T>(ref T field, T value){
|
||||
if (!EqualityComparer<T>.Default.Equals(field, value)){
|
||||
field = value;
|
||||
configManager.TriggerProgramRestartRequested();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,22 +1,20 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using TweetDuck.Data.Serialization;
|
||||
|
||||
namespace TweetDuck.Configuration.Instance{
|
||||
sealed class FileConfigInstance<T> : IConfigInstance<T> where T : ConfigManager.BaseConfig{
|
||||
private const string ErrorTitle = "Configuration Error";
|
||||
using TweetLib.Core.Serialization;
|
||||
|
||||
namespace TweetLib.Core.Features.Configuration{
|
||||
public sealed class FileConfigInstance<T> : IConfigInstance<T> where T : BaseConfig{
|
||||
public T Instance { get; }
|
||||
public FileSerializer<T> Serializer { get; }
|
||||
|
||||
private readonly string filenameMain;
|
||||
private readonly string filenameBackup;
|
||||
private readonly string errorIdentifier;
|
||||
private readonly string identifier;
|
||||
|
||||
public FileConfigInstance(string filename, T instance, string errorIdentifier){
|
||||
public FileConfigInstance(string filename, T instance, string identifier){
|
||||
this.filenameMain = filename;
|
||||
this.filenameBackup = filename+".bak";
|
||||
this.errorIdentifier = errorIdentifier;
|
||||
this.filenameBackup = filename + ".bak";
|
||||
this.identifier = identifier;
|
||||
|
||||
this.Instance = instance;
|
||||
this.Serializer = new FileSerializer<T>();
|
||||
@@ -27,14 +25,14 @@ namespace TweetDuck.Configuration.Instance{
|
||||
}
|
||||
|
||||
public void Load(){
|
||||
Exception firstException = null;
|
||||
Exception? firstException = null;
|
||||
|
||||
for(int attempt = 0; attempt < 2; attempt++){
|
||||
try{
|
||||
LoadInternal(attempt > 0);
|
||||
|
||||
if (firstException != null){ // silently log exception that caused a backup restore
|
||||
Program.Reporter.Log(firstException.ToString());
|
||||
App.ErrorHandler.Log(firstException.ToString());
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -49,13 +47,13 @@ namespace TweetDuck.Configuration.Instance{
|
||||
}
|
||||
|
||||
if (firstException is FormatException){
|
||||
Program.Reporter.HandleException(ErrorTitle, "The configuration file for "+errorIdentifier+" is outdated or corrupted. If you continue, your "+errorIdentifier+" will be reset.", true, firstException);
|
||||
OnException($"The configuration file for {identifier} is outdated or corrupted. If you continue, your {identifier} will be reset.", firstException);
|
||||
}
|
||||
else if (firstException is SerializationSoftException sse){
|
||||
Program.Reporter.HandleException(ErrorTitle, $"{sse.Errors.Count} error{(sse.Errors.Count == 1 ? " was" : "s were")} encountered while loading the configuration file for "+errorIdentifier+". If you continue, some of your "+errorIdentifier+" will be reset.", true, firstException);
|
||||
OnException($"{sse.Errors.Count} error{(sse.Errors.Count == 1 ? " was" : "s were")} encountered while loading the configuration file for {identifier}. If you continue, some of your {identifier} will be reset.", firstException);
|
||||
}
|
||||
else if (firstException != null){
|
||||
Program.Reporter.HandleException(ErrorTitle, "Could not open the configuration file for "+errorIdentifier+". If you continue, your "+errorIdentifier+" will be reset.", true, firstException);
|
||||
OnException($"Could not open the configuration file for {identifier}. If you continue, your {identifier} will be reset.", firstException);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,9 +66,9 @@ namespace TweetDuck.Configuration.Instance{
|
||||
|
||||
Serializer.Write(filenameMain, Instance);
|
||||
}catch(SerializationSoftException e){
|
||||
Program.Reporter.HandleException(ErrorTitle, $"{e.Errors.Count} error{(e.Errors.Count == 1 ? " was" : "s were")} encountered while saving the configuration file for "+errorIdentifier+".", true, e);
|
||||
OnException($"{e.Errors.Count} error{(e.Errors.Count == 1 ? " was" : "s were")} encountered while saving the configuration file for {identifier}.", e);
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException(ErrorTitle, "Could not save the configuration file for "+errorIdentifier+".", true, e);
|
||||
OnException($"Could not save the configuration file for {identifier}.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,10 +80,10 @@ namespace TweetDuck.Configuration.Instance{
|
||||
Serializer.Write(filenameMain, Instance.ConstructWithDefaults<T>());
|
||||
LoadInternal(false);
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException(ErrorTitle, "Could not regenerate the configuration file for "+errorIdentifier+".", true, e);
|
||||
OnException($"Could not regenerate the configuration file for {identifier}.", e);
|
||||
}
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException(ErrorTitle, "Could not reload the configuration file for "+errorIdentifier+".", true, e);
|
||||
OnException($"Could not reload the configuration file for {identifier}.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,11 +92,15 @@ namespace TweetDuck.Configuration.Instance{
|
||||
File.Delete(filenameMain);
|
||||
File.Delete(filenameBackup);
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException(ErrorTitle, "Could not delete configuration files to reset "+errorIdentifier+".", true, e);
|
||||
OnException($"Could not delete configuration files to reset {identifier}.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
Reload();
|
||||
}
|
||||
|
||||
private static void OnException(string message, Exception e){
|
||||
App.ErrorHandler.HandleException("Configuration Error", message, true, e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
namespace TweetDuck.Configuration.Instance{
|
||||
interface IConfigInstance<out T>{
|
||||
namespace TweetLib.Core.Features.Configuration{
|
||||
public interface IConfigInstance<out T>{
|
||||
T Instance { get; }
|
||||
|
||||
void Save();
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user