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

Compare commits

..

79 Commits

Author SHA1 Message Date
d91b4bd1f3 Release 1.13.1 2018-02-14 18:43:02 +01:00
c0c64f6d62 Remove old TweetDeck installation check from the installer & tweak formatting 2018-02-14 17:45:19 +01:00
1a5d2af779 Decrease post-update analytics report to 8h and increase startup delay to 2m 2018-02-14 17:10:24 +01:00
f40a33192b Revert and fix various changes from recent TweetDeck update 2018-02-14 15:39:27 +01:00
ca4900aff0 Fix 'Manage templates' button after a recent TweetDeck update 2018-02-14 15:17:58 +01:00
56fc9e2d40 Fix black theme issues (mismatched detection & rare bug with wrong notification background color) 2018-02-13 16:51:40 +01:00
d2174c0b69 Fix misaligned avatars in activity columns 2018-02-13 15:59:21 +01:00
9f76754ad3 Force full install from 1.13 to 1.13.0.1 2018-02-13 13:37:30 +01:00
118ceaec35 Release 1.13.0.1 2018-02-13 13:27:52 +01:00
5a57d28a7d Fix crash by checking and downloading for VC++ 2015 in the installer
Closes #205
2018-02-13 13:17:40 +01:00
07af99f862 Fix wrong background color in tweet screenshots when using black theme 2018-02-13 12:42:25 +01:00
59fba7fba0 Fix a hidden crash that prevented desktop notifications from showing 2018-02-13 12:37:11 +01:00
dd4edc4249 Update CefSharp to latest 64 and remove VC++ 2012 2018-02-13 11:28:48 +01:00
856226473a Update README.md 2018-02-13 05:52:03 +01:00
8d1c07d6b2 Release 1.13 2018-02-12 18:48:42 +01:00
c32462cc9e Update TweetDeck color selectors in CSS for black theme 2018-02-12 18:23:16 +01:00
ec94ea3273 Refactor PluginManager to use ITweetDeckBrowser & do some cleanup 2018-02-12 15:26:21 +01:00
41acd8c15b Refactor UpdateHandler to reference ITweetDeckBrowser 2018-02-12 11:35:39 +01:00
155a79f2ec Add ITweetDeckBrowser for refactoring 2018-02-12 11:34:23 +01:00
9197cb9be6 Add support for 'Configure' button to edit-design plugin 2018-02-12 11:26:50 +01:00
03d50c847b Add 'Configure' button to plugins with a configure() method & close dialog afterwards 2018-02-12 10:40:00 +01:00
bf45c40365 Make analytics debugging easier & tweak Counter serialization 2018-02-12 06:13:08 +01:00
679e126194 Reset all analytics counters 2018-02-12 05:41:03 +01:00
50e39164bd Update and add analytics data points & increase report interval to 14 days 2018-02-11 20:01:57 +01:00
cb9f75e968 Refactor AnalyticsFile events and usage 2018-02-11 16:59:02 +01:00
aa7f6cc3b1 Fix loading spinner sometimes being visible before getting replaced 2018-02-10 23:20:34 +01:00
fe601aed41 Redesign favorite/retweet notifications to be more compact and show full text 2018-02-10 13:09:09 +01:00
2282a9df28 Move 'Show this thread' in desktop notifications above media/quotes & fix hover color w/ black theme 2018-02-10 08:29:59 +01:00
2b54627750 Tweak media size and margins in desktop notifications 2018-02-10 07:54:50 +01:00
16051a0d25 Forgot this 2018-02-10 07:13:56 +01:00
66d5f0d790 Refactor IResourceHandler usage 2018-02-10 07:07:11 +01:00
07d29207f0 Restore loading background color and spinner from before the TweetDeck update 2018-02-10 06:50:52 +01:00
a60be2afcc More Visual Studio shit 2018-02-07 21:58:21 +01:00
027f3ee253 Remove recently added follow notification 2018-02-07 03:22:50 +01:00
04774815e4 Fix bad padding in introduction modal 2018-02-07 03:20:58 +01:00
61a73c055b Fix weird alignment of stuff in notification columns 2018-02-07 00:39:05 +01:00
7731534ffc Save some space in edit-design plugin 2018-02-07 00:21:57 +01:00
ed7bf99610 Prevent dev tools from leaking info in all request headers 2018-02-06 21:10:29 +01:00
cbe4272556 Hide unused items in TweetDeck Settings modal (startup notifications, gif autoplay) 2018-02-06 20:43:18 +01:00
8f5e3dfdcc Merge pull request #203 from chylex/cefsharp64
Update CefSharp to 64 & re-enable mp3s in sound notifications
2018-02-06 18:40:23 +01:00
35500c51f1 Allow export/import/restoring system options & refactor Manage Options dialog 2018-02-06 18:35:36 +01:00
629f873bb2 Add a debugger trigger shortcut to debug plugin 2018-02-06 18:25:11 +01:00
a44cb884c4 Fix a crash when restarting after importing/resetting profile & refactor 2018-02-06 17:04:32 +01:00
d5ad1d0daa Fix loading spinners, and links in notifications when using black theme
Closes #202
2018-02-06 04:38:57 +01:00
61ae7e3b6a Fix 'Show this thread' being too close to media thumbnails in notifications 2018-02-06 04:37:45 +01:00
01583e424f Re-add mp3 support in sound notifications 2018-02-06 04:10:03 +01:00
5c0aa1b3da Update CefSharp to 64 (early build) 2018-02-06 04:06:18 +01:00
07391efa70 Fix more visual issues (remove DM reply button background w/ black theme) 2018-02-02 23:13:43 +01:00
b80f1bfc7c Fix more visual issues (remove disabled button border w/ black theme) 2018-02-02 22:31:01 +01:00
ad310db86c Fix more visual issues (profile modal w/ black theme, timeline input shadow) 2018-02-02 21:57:28 +01:00
4ce0122a29 Fix hover/click effects on buttons under reply input box 2018-02-02 19:15:44 +01:00
a8894f7054 Fix visual issues with search input and buttons 2018-02-02 18:01:11 +01:00
1d1515351b Release 1.12.5.1 2018-02-02 16:57:28 +01:00
2a9ddd4468 Fix edit-design modal, black theme quote border, and dark theme scrollbar color 2018-02-02 16:56:06 +01:00
0f9a944775 Square-ify border of reply box & fix notification background 2018-02-02 15:54:22 +01:00
34ee9ebd66 Release 1.12.5 2018-02-02 15:24:19 +01:00
43f632b555 Allow detecting custom edit-design themes in analytics 2018-02-02 15:19:25 +01:00
7cf3f1d32c Add option for the old dark theme in edit-design plugin 2018-02-02 14:59:33 +01:00
e51e87647e Remove unknown property error in FileSerializer & refactor reading 2018-02-02 13:49:10 +01:00
b8aae88b11 Fix broken Shift swap when selecting accounts after a recent TweetDeck update 2018-01-31 00:28:19 +01:00
d06e29db15 Get rid of string.Split in FileSerializer
string.Split is not suitable for potentially very large strings, so this
decently improves memory usage
2018-01-30 15:45:19 +01:00
62449450f3 Release 1.12.4 2018-01-29 08:40:38 +01:00
b290c94635 Fix a video player crash caused by not handling remote commands on UI thread 2018-01-29 08:20:33 +01:00
f909b887d9 Fix screenshot issues (wrong media size and margin, hide "Show this thread") 2018-01-28 21:48:57 +01:00
5cf4843212 Fix broken screenshots of tweets with just one line of text 2018-01-28 21:40:18 +01:00
b3d1e1bfac Fix example notification timer breaking on skip (forward mouse button or Enter) 2018-01-28 20:54:49 +01:00
df47499a28 Update example notification (username, avatar, improve text) 2018-01-28 20:50:18 +01:00
421475ec87 Add a notification and way to follow the new TweetDuck account 2018-01-28 20:22:13 +01:00
29d999b8eb Remove old data collection notification that was shown after updating 2018-01-28 20:05:50 +01:00
acacd9a5e5 Fix reversed button tab order in FormMessage 2018-01-28 19:39:12 +01:00
b81c26f93f Add an option to ignore tracking URL warnings (t.co) 2018-01-28 19:38:40 +01:00
00b212944c Implement top tier account bamboozle scheme 2018-01-28 19:12:32 +01:00
70ba006e4d Reorganize logo file resources and remove about.png 2018-01-26 17:44:41 +01:00
118e0cae62 Compress the logo in the About form 2018-01-26 17:36:15 +01:00
c003bb4e71 Add a way to display the TweetDuck logo in the browser 2018-01-26 17:32:54 +01:00
e9b2fa7603 Release 1.12.3.1 2018-01-26 15:58:17 +01:00
35afaa105d Fix text alignment in the Feedback tab in Options 2018-01-26 15:49:44 +01:00
2e300a7b8f Fix broken stylesheets in notifications after a recent TweetDeck update
Closes #199
2018-01-26 15:43:52 +01:00
f3f5b88550 Refactor resource handler related extension methods 2018-01-22 14:53:38 +01:00
76 changed files with 2196 additions and 918 deletions

View File

@@ -4,9 +4,7 @@ using TweetDuck.Data.Serialization;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
sealed class SystemConfig{ sealed class SystemConfig{
private static readonly FileSerializer<SystemConfig> Serializer = new FileSerializer<SystemConfig>{ private static readonly FileSerializer<SystemConfig> Serializer = new FileSerializer<SystemConfig>();
HandleUnknownProperties = FileSerializer<SystemConfig>.IgnoreProperties("EnableBrowserGCReload", "BrowserMemoryThreshold")
};
public static readonly bool IsHardwareAccelerationSupported = File.Exists(Path.Combine(Program.ProgramPath, "libEGL.dll")) && public static readonly bool IsHardwareAccelerationSupported = File.Exists(Path.Combine(Program.ProgramPath, "libEGL.dll")) &&
File.Exists(Path.Combine(Program.ProgramPath, "libGLESv2.dll")); File.Exists(Path.Combine(Program.ProgramPath, "libGLESv2.dll"));

View File

@@ -11,9 +11,7 @@ using TweetDuck.Data.Serialization;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
sealed class UserConfig{ sealed class UserConfig{
private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>{ private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>();
HandleUnknownProperties = FileSerializer<UserConfig>.IgnoreProperties("AppLocale")
};
static UserConfig(){ static UserConfig(){
Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter); Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
@@ -39,7 +37,6 @@ namespace TweetDuck.Configuration{
public bool FirstRun { get; set; } = true; public bool FirstRun { get; set; } = true;
public bool AllowDataCollection { get; set; } = false; public bool AllowDataCollection { get; set; } = false;
public bool ShowDataCollectionNotification { get; set; } = true;
public WindowState BrowserWindow { get; set; } = new WindowState(); public WindowState BrowserWindow { get; set; } = new WindowState();
public WindowState PluginsWindow { get; set; } = new WindowState(); public WindowState PluginsWindow { get; set; } = new WindowState();
@@ -51,6 +48,7 @@ namespace TweetDuck.Configuration{
public bool BestImageQuality { get; set; } = true; public bool BestImageQuality { get; set; } = true;
public bool EnableAnimatedImages { get; set; } = true; public bool EnableAnimatedImages { get; set; } = true;
public bool IgnoreTrackingUrlWarning { get; set; } = false;
public bool EnableSmoothScrolling { get; set; } = true; public bool EnableSmoothScrolling { get; set; } = true;
public string BrowserPath { get; set; } = null; public string BrowserPath { get; set; } = null;
private int _zoomLevel = 100; private int _zoomLevel = 100;

View File

@@ -10,14 +10,14 @@ using TweetDuck.Core.Notification;
using TweetDuck.Core.Notification.Screenshot; using TweetDuck.Core.Notification.Screenshot;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Core.Other.Analytics; using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Other.Settings;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events; using TweetDuck.Plugins.Events;
using TweetDuck.Updates; using TweetDuck.Updates;
namespace TweetDuck.Core{ namespace TweetDuck.Core{
sealed partial class FormBrowser : Form{ sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{
private static UserConfig Config => Program.UserConfig; private static UserConfig Config => Program.UserConfig;
public bool IsWaiting{ public bool IsWaiting{
@@ -39,6 +39,9 @@ namespace TweetDuck.Core{
public string UpdateInstallerPath { get; private set; } public string UpdateInstallerPath { get; private set; }
public PluginManager PluginManager => plugins;
public AnalyticsFile AnalyticsFile => analytics?.File ?? AnalyticsFile.Dummy;
private readonly TweetDeckBrowser browser; private readonly TweetDeckBrowser browser;
private readonly PluginManager plugins; private readonly PluginManager plugins;
private readonly UpdateHandler updates; private readonly UpdateHandler updates;
@@ -65,11 +68,11 @@ namespace TweetDuck.Core{
this.notification = new FormNotificationTweet(this, plugins); this.notification = new FormNotificationTweet(this, plugins);
this.notification.Show(); this.notification.Show();
this.browser = new TweetDeckBrowser(this, plugins, new TweetDeckBridge.Browser(this, notification)); this.browser = new TweetDeckBrowser(this, new TweetDeckBridge.Browser(this, notification));
this.browser.PageLoaded += browser_PageLoaded;
this.contextMenu = ContextMenuBrowser.CreateMenu(this); this.contextMenu = ContextMenuBrowser.CreateMenu(this);
this.plugins.Register(browser, PluginEnvironment.Browser, true);
Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
Disposed += (sender, args) => { Disposed += (sender, args) => {
@@ -92,7 +95,7 @@ namespace TweetDuck.Core{
UpdateTrayIcon(); UpdateTrayIcon();
this.updates = browser.CreateUpdateHandler(updaterSettings); this.updates = new UpdateHandler(browser, updaterSettings);
this.updates.UpdateAccepted += updates_UpdateAccepted; this.updates.UpdateAccepted += updates_UpdateAccepted;
this.updates.UpdateDismissed += updates_UpdateDismissed; this.updates.UpdateDismissed += updates_UpdateDismissed;
@@ -196,7 +199,7 @@ namespace TweetDuck.Core{
} }
private void Config_MuteToggled(object sender, EventArgs e){ private void Config_MuteToggled(object sender, EventArgs e){
TriggerAnalyticsEvent(AnalyticsFile.Event.MuteNotification); AnalyticsFile.NotificationMutes.Trigger();
} }
private void Config_TrayBehaviorChanged(object sender, EventArgs e){ private void Config_TrayBehaviorChanged(object sender, EventArgs e){
@@ -214,45 +217,13 @@ namespace TweetDuck.Core{
ForceClose(); ForceClose();
} }
private void browser_PageLoaded(object sender, EventArgs e){
if (Config.ShowDataCollectionNotification){
this.InvokeAsyncSafe(() => {
if (!Config.FirstRun && Config.AllowDataCollection){
FormMessage form = new FormMessage("Anonymous Data Update", "Hi! You can now review your anonymous data report, and opt-out if you've changed your mind. Collected data will be used to focus development on most commonly used features. If you want to opt-out but still support the project, any feedback and donations are appreciated.", MessageBoxIcon.Information);
form.AddButton("OK", ControlType.Accept | ControlType.Focused);
Button btnReviewSettings = new Button{
Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
Font = SystemFonts.MessageBoxFont,
Location = new Point(9, 12),
Margin = new Padding(0, 0, 48, 0),
Size = new Size(160, 26),
Text = "Review Feedback Options",
UseVisualStyleBackColor = true
};
btnReviewSettings.Click += (sender2, args2) => {
form.Close();
OpenSettings(typeof(TabSettingsFeedback));
};
form.AddActionControl(btnReviewSettings);
ShowChildForm(form);
}
Config.ShowDataCollectionNotification = false;
Config.Save();
});
}
}
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){ private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
if (e.HasErrors){ if (e.HasErrors){
FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK); FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
} }
if (isLoaded){ if (isLoaded){
ReloadToTweetDeck(); browser.ReloadToTweetDeck();
} }
} }
@@ -303,7 +274,7 @@ namespace TweetDuck.Core{
} }
else{ else{
browser.OnMouseClickExtra(m.WParam); browser.OnMouseClickExtra(m.WParam);
TriggerAnalyticsEvent(AnalyticsFile.Event.BrowserExtraMouseButton); AnalyticsFile.BrowserExtraMouseButtons.Trigger();
} }
return; return;
@@ -328,6 +299,7 @@ namespace TweetDuck.Core{
public void ReloadToTweetDeck(){ public void ReloadToTweetDeck(){
browser.ReloadToTweetDeck(); browser.ReloadToTweetDeck();
AnalyticsFile.BrowserReloads.Trigger();
} }
public void TriggerTweetScreenshot(){ public void TriggerTweetScreenshot(){
@@ -344,10 +316,7 @@ namespace TweetDuck.Core{
public void ApplyROT13(){ public void ApplyROT13(){
browser.ApplyROT13(); browser.ApplyROT13();
} AnalyticsFile.UsedROT13.Trigger();
public void TriggerAnalyticsEvent(AnalyticsFile.Event e){
analytics?.TriggerEvent(e);
} }
// callback handlers // callback handlers
@@ -356,7 +325,6 @@ namespace TweetDuck.Core{
if (Config.FirstRun){ if (Config.FirstRun){
Config.FirstRun = false; Config.FirstRun = false;
Config.AllowDataCollection = allowDataCollection; Config.AllowDataCollection = allowDataCollection;
Config.ShowDataCollectionNotification = false;
Config.Save(); Config.Save();
if (allowDataCollection && analytics == null){ if (allowDataCollection && analytics == null){
@@ -419,21 +387,21 @@ namespace TweetDuck.Core{
form.Dispose(); form.Dispose();
}; };
TriggerAnalyticsEvent(AnalyticsFile.Event.OpenOptions); AnalyticsFile.OpenOptions.Trigger();
ShowChildForm(form); ShowChildForm(form);
} }
} }
public void OpenAbout(){ public void OpenAbout(){
if (!FormManager.TryBringToFront<FormAbout>()){ if (!FormManager.TryBringToFront<FormAbout>()){
TriggerAnalyticsEvent(AnalyticsFile.Event.OpenAbout); AnalyticsFile.OpenAbout.Trigger();
ShowChildForm(new FormAbout()); ShowChildForm(new FormAbout());
} }
} }
public void OpenPlugins(){ public void OpenPlugins(){
if (!FormManager.TryBringToFront<FormPlugins>()){ if (!FormManager.TryBringToFront<FormPlugins>()){
TriggerAnalyticsEvent(AnalyticsFile.Event.OpenPlugins); AnalyticsFile.OpenPlugins.Trigger();
ShowChildForm(new FormPlugins(plugins)); ShowChildForm(new FormPlugins(plugins));
} }
} }
@@ -445,7 +413,7 @@ namespace TweetDuck.Core{
} }
public void OnTweetSound(){ public void OnTweetSound(){
TriggerAnalyticsEvent(AnalyticsFile.Event.SoundNotification); AnalyticsFile.SoundNotifications.Trigger();
} }
public void PlayVideo(string url, string username){ public void PlayVideo(string url, string username){
@@ -463,7 +431,7 @@ namespace TweetDuck.Core{
} }
videoPlayer.Launch(url, username); videoPlayer.Launch(url, username);
TriggerAnalyticsEvent(AnalyticsFile.Event.VideoPlay); AnalyticsFile.VideoPlays.Trigger();
} }
public bool ProcessBrowserKey(Keys key){ public bool ProcessBrowserKey(Keys key){
@@ -485,7 +453,7 @@ namespace TweetDuck.Core{
notification.FinishCurrentNotification(); notification.FinishCurrentNotification();
browser.ShowTweetDetail(columnId, chirpId, fallbackUrl); browser.ShowTweetDetail(columnId, chirpId, fallbackUrl);
TriggerAnalyticsEvent(AnalyticsFile.Event.TweetDetail); AnalyticsFile.TweetDetails.Trigger();
} }
public void OnTweetScreenshotReady(string html, int width, int height){ public void OnTweetScreenshotReady(string html, int width, int height){
@@ -494,7 +462,7 @@ namespace TweetDuck.Core{
} }
notificationScreenshotManager.Trigger(html, width, height); notificationScreenshotManager.Trigger(html, width, height);
TriggerAnalyticsEvent(AnalyticsFile.Event.TweetScreenshot); AnalyticsFile.TweetScreenshots.Trigger();
} }
public void DisplayTooltip(string text){ public void DisplayTooltip(string text){

View File

@@ -9,7 +9,9 @@ using TweetDuck.Core.Utils;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using TweetDuck.Core.Management; using TweetDuck.Core.Management;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Core.Other.Analytics;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling{
abstract class ContextMenuBase : IContextMenuHandler{ abstract class ContextMenuBase : IContextMenuHandler{
@@ -43,6 +45,12 @@ namespace TweetDuck.Core.Handling{
private string[] lastHighlightedTweetAuthors; private string[] lastHighlightedTweetAuthors;
private string[] lastHighlightedTweetImageList; private string[] lastHighlightedTweetImageList;
private readonly AnalyticsFile.IProvider analytics;
protected ContextMenuBase(AnalyticsFile.IProvider analytics){
this.analytics = analytics;
}
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){ if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
lastHighlightedTweetAuthors = StringUtils.EmptyArray; lastHighlightedTweetAuthors = StringUtils.EmptyArray;
@@ -81,7 +89,7 @@ namespace TweetDuck.Core.Handling{
model.AddItem(MenuSaveMedia, TextSave("video")); model.AddItem(MenuSaveMedia, TextSave("video"));
model.AddSeparator(); model.AddSeparator();
} }
else if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){ else if (((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage) && parameters.SourceUrl != TweetNotification.AppLogo.Url){
model.AddItem(MenuViewImage, "View image in photo viewer"); model.AddItem(MenuViewImage, "View image in photo viewer");
model.AddItem(MenuOpenMediaUrl, TextOpen("image")); model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
model.AddItem(MenuCopyMediaUrl, TextCopy("image")); model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
@@ -96,26 +104,29 @@ namespace TweetDuck.Core.Handling{
} }
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){ public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
Control control = browserControl.AsControl();
switch(commandId){ switch(commandId){
case MenuOpenLinkUrl: case MenuOpenLinkUrl:
OpenBrowser(browserControl.AsControl(), IsLink ? ContextInfo.Value : parameters.LinkUrl); OpenBrowser(control, IsLink ? ContextInfo.Value : parameters.LinkUrl);
break; break;
case MenuCopyLinkUrl: case MenuCopyLinkUrl:
SetClipboardText(browserControl.AsControl(), IsLink ? ContextInfo.Value : parameters.UnfilteredLinkUrl); SetClipboardText(control, IsLink ? ContextInfo.Value : parameters.UnfilteredLinkUrl);
break; break;
case MenuCopyUsername: case MenuCopyUsername:
Match match = TwitterUtils.RegexAccount.Match(parameters.UnfilteredLinkUrl); Match match = TwitterUtils.RegexAccount.Match(parameters.UnfilteredLinkUrl);
SetClipboardText(browserControl.AsControl(), match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl); SetClipboardText(control, match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
control.InvokeAsyncSafe(analytics.AnalyticsFile.CopiedUsernames.Trigger);
break; break;
case MenuOpenMediaUrl: case MenuOpenMediaUrl:
OpenBrowser(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality)); OpenBrowser(control, TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
break; break;
case MenuCopyMediaUrl: case MenuCopyMediaUrl:
SetClipboardText(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality)); SetClipboardText(control, TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
break; break;
case MenuViewImage: case MenuViewImage:
@@ -137,6 +148,8 @@ namespace TweetDuck.Core.Handling{
ViewFile(); ViewFile();
} }
else{ else{
control.InvokeAsyncSafe(analytics.AnalyticsFile.ViewedImages.Trigger);
BrowserUtils.DownloadFileAsync(TwitterUtils.GetMediaLink(url, ImageQuality), file, ViewFile, ex => { BrowserUtils.DownloadFileAsync(TwitterUtils.GetMediaLink(url, ImageQuality), file, ViewFile, ex => {
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK); FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
}); });
@@ -146,15 +159,18 @@ namespace TweetDuck.Core.Handling{
case MenuSaveMedia: case MenuSaveMedia:
if (IsVideo){ if (IsVideo){
control.InvokeAsyncSafe(analytics.AnalyticsFile.DownloadedVideos.Trigger);
TwitterUtils.DownloadVideo(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault()); TwitterUtils.DownloadVideo(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault());
} }
else{ else{
control.InvokeAsyncSafe(analytics.AnalyticsFile.DownloadedImages.Trigger);
TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality); TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
} }
break; break;
case MenuSaveTweetImages: case MenuSaveTweetImages:
control.InvokeAsyncSafe(analytics.AnalyticsFile.DownloadedImages.Trigger);
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality); TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
break; break;

View File

@@ -2,7 +2,6 @@
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Bridge; using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling{
@@ -31,7 +30,7 @@ namespace TweetDuck.Core.Handling{
private string lastHighlightedTweetUrl; private string lastHighlightedTweetUrl;
private string lastHighlightedQuoteUrl; private string lastHighlightedQuoteUrl;
public ContextMenuBrowser(FormBrowser form){ public ContextMenuBrowser(FormBrowser form) : base(form){
this.form = form; this.form = form;
} }
@@ -97,7 +96,7 @@ namespace TweetDuck.Core.Handling{
RemoveSeparatorIfLast(model); RemoveSeparatorIfLast(model);
form.InvokeAsyncSafe(() => form.TriggerAnalyticsEvent(AnalyticsFile.Event.BrowserContextMenu)); form.InvokeAsyncSafe(form.AnalyticsFile.BrowserContextMenus.Trigger);
} }
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){ public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
@@ -166,7 +165,7 @@ namespace TweetDuck.Core.Handling{
menu.Popup += (sender, args) => { menu.Popup += (sender, args) => {
menu.MenuItems[1].Checked = Program.UserConfig.MuteNotifications; menu.MenuItems[1].Checked = Program.UserConfig.MuteNotifications;
form.TriggerAnalyticsEvent(AnalyticsFile.Event.BrowserContextMenu); form.AnalyticsFile.BrowserContextMenus.Trigger();
}; };
return menu; return menu;

View File

@@ -1,7 +1,10 @@
using CefSharp; using CefSharp;
using TweetDuck.Core.Other.Analytics;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling{
sealed class ContextMenuGuide : ContextMenuBase{ sealed class ContextMenuGuide : ContextMenuBase{
public ContextMenuGuide(AnalyticsFile.IProvider analytics) : base(analytics){}
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
model.Clear(); model.Clear();
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model); base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);

View File

@@ -1,7 +1,6 @@
using CefSharp; using CefSharp;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Other.Analytics;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling{
sealed class ContextMenuNotification : ContextMenuBase{ sealed class ContextMenuNotification : ContextMenuBase{
@@ -14,7 +13,7 @@ namespace TweetDuck.Core.Handling{
private readonly FormNotificationBase form; private readonly FormNotificationBase form;
private readonly bool enableCustomMenu; private readonly bool enableCustomMenu;
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu){ public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu) : base(form){
this.form = form; this.form = form;
this.enableCustomMenu = enableCustomMenu; this.enableCustomMenu = enableCustomMenu;
} }
@@ -57,7 +56,7 @@ namespace TweetDuck.Core.Handling{
form.InvokeAsyncSafe(() => { form.InvokeAsyncSafe(() => {
form.ContextMenuOpen = true; form.ContextMenuOpen = true;
form.TriggerAnalyticsEvent(AnalyticsFile.Event.NotificationContextMenu); form.AnalyticsFile.NotificationContextMenus.Trigger();
}); });
} }

View File

@@ -2,7 +2,6 @@
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Other.Analytics;
namespace TweetDuck.Core.Handling { namespace TweetDuck.Core.Handling {
sealed class KeyboardHandlerNotification : IKeyboardHandler{ sealed class KeyboardHandlerNotification : IKeyboardHandler{
@@ -13,7 +12,7 @@ namespace TweetDuck.Core.Handling {
} }
private void TriggerKeyboardShortcutAnalytics(){ private void TriggerKeyboardShortcutAnalytics(){
notification.InvokeAsyncSafe(() => notification.TriggerAnalyticsEvent(AnalyticsFile.Event.NotificationKeyboardShortcut)); 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){ bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){

View File

@@ -1,4 +1,5 @@
using CefSharp; using System.Collections.Specialized;
using CefSharp;
using CefSharp.Handler; using CefSharp.Handler;
using TweetDuck.Core.Handling.General; using TweetDuck.Core.Handling.General;
@@ -7,5 +8,15 @@ namespace TweetDuck.Core.Handling{
public override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture){ public override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture){
return LifeSpanHandler.HandleLinkClick(browserControl, targetDisposition, targetUrl); return LifeSpanHandler.HandleLinkClick(browserControl, targetDisposition, targetUrl);
} }
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
if (ContextMenuBase.HasDevTools){
NameValueCollection headers = request.Headers;
headers.Remove("x-devtools-emulate-network-conditions-client-id");
request.Headers = headers;
}
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
}
} }
} }

View File

@@ -1,4 +1,5 @@
using CefSharp; using CefSharp;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling{
sealed class RequestHandlerBrowser : RequestHandlerBase{ sealed class RequestHandlerBrowser : RequestHandlerBase{
@@ -8,10 +9,20 @@ namespace TweetDuck.Core.Handling{
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){ public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
if (request.ResourceType == ResourceType.Script && request.Url.Contains("analytics.")){ if (request.ResourceType == ResourceType.Script && request.Url.Contains("analytics.")){
callback.Dispose();
return CefReturnValue.Cancel; return CefReturnValue.Cancel;
} }
return CefReturnValue.Continue; 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.Image && request.Url.Contains("backgrounds/spinner_blue")){
request.Url = TwitterUtils.LoadingSpinner.Url;
return true;
}
return base.OnResourceResponse(browserControl, browser, frame, request, response);
} }
} }
} }

10
Core/ITweetDeckBrowser.cs Normal file
View File

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

View File

@@ -16,7 +16,7 @@ namespace TweetDuck.Core.Management{
public enum Items{ public enum Items{
None = 0, None = 0,
UserConfig = 1, UserConfig = 1,
SystemConfig = 2, // TODO implement later SystemConfig = 2,
Session = 4, Session = 4,
PluginData = 8, PluginData = 8,
All = UserConfig|SystemConfig|Session|PluginData All = UserConfig|SystemConfig|Session|PluginData
@@ -210,4 +210,10 @@ namespace TweetDuck.Core.Management{
public string Relative { get; set; } public string Relative { get; set; }
} }
} }
static class ProfileManagerExtensions{
public static bool NeedsRestart(this ProfileManager.Items items){
return items.HasFlag(ProfileManager.Items.SystemConfig) || items.HasFlag(ProfileManager.Items.Session);
}
}
} }

View File

@@ -22,7 +22,7 @@ namespace TweetDuck.Core.Management{
public event EventHandler ProcessExited; public event EventHandler ProcessExited;
private readonly Form owner; private readonly FormBrowser owner;
private string lastUrl; private string lastUrl;
private string lastUsername; private string lastUsername;
@@ -30,7 +30,7 @@ namespace TweetDuck.Core.Management{
private DuplexPipe.Server currentPipe; private DuplexPipe.Server currentPipe;
private bool isClosing; private bool isClosing;
public VideoPlayer(Form owner){ public VideoPlayer(FormBrowser owner){
this.owner = owner; this.owner = owner;
this.owner.FormClosing += owner_FormClosing; this.owner.FormClosing += owner_FormClosing;
} }
@@ -83,6 +83,7 @@ namespace TweetDuck.Core.Management{
break; break;
case "download": case "download":
owner.AnalyticsFile.DownloadedVideos.Trigger();
TwitterUtils.DownloadVideo(lastUrl, lastUsername); TwitterUtils.DownloadVideo(lastUrl, lastUsername);
break; break;

View File

@@ -24,19 +24,21 @@ namespace TweetDuck.Core.Notification.Example{
private readonly TweetNotification exampleNotification; private readonly TweetNotification exampleNotification;
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){ public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){
string exampleTweetHTML = ScriptLoader.LoadResource("pages/example.html", true); string exampleTweetHTML = ScriptLoader.LoadResource("pages/example.html", true).Replace("{avatar}", TweetNotification.AppLogo.Url);
#if DEBUG #if DEBUG
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>"); exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
#endif #endif
exampleNotification = TweetNotification.Example(exampleTweetHTML, 95); exampleNotification = TweetNotification.Example(exampleTweetHTML, 176);
} }
public override void HideNotification(){ public override void HideNotification(){
Location = ControlExtensions.InvisibleLocation; Location = ControlExtensions.InvisibleLocation;
} }
public override void FinishCurrentNotification(){}
public void ShowExampleNotification(bool reset){ public void ShowExampleNotification(bool reset){
if (reset){ if (reset){
LoadTweet(exampleNotification); LoadTweet(exampleNotification);

View File

@@ -12,7 +12,7 @@ using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
partial class FormNotificationBase : Form{ partial class FormNotificationBase : Form, AnalyticsFile.IProvider{
protected static int FontSizeLevel{ protected static int FontSizeLevel{
get{ get{
switch(TweetDeckBridge.FontSize){ switch(TweetDeckBridge.FontSize){
@@ -90,6 +90,8 @@ namespace TweetDuck.Core.Notification{
} }
} }
public AnalyticsFile AnalyticsFile => owner.AnalyticsFile;
protected override bool ShowWithoutActivation => true; protected override bool ShowWithoutActivation => true;
protected float DpiScale { get; } protected float DpiScale { get; }
@@ -139,7 +141,8 @@ namespace TweetDuck.Core.Notification{
DpiScale = this.GetDPIScale(); DpiScale = this.GetDPIScale();
browser.GetHandlerFactory().RegisterHandler(TwitterUtils.TweetDeckURL, this.resourceHandler); browser.SetupResourceHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
browser.SetupResourceHandler(TweetNotification.AppLogo);
Controls.Add(browser); Controls.Add(browser);
@@ -160,10 +163,6 @@ namespace TweetDuck.Core.Notification{
base.WndProc(ref m); base.WndProc(ref m);
} }
public void TriggerAnalyticsEvent(AnalyticsFile.Event e){
owner.TriggerAnalyticsEvent(e);
}
// event handlers // event handlers
private void owner_FormClosed(object sender, FormClosedEventArgs e){ private void owner_FormClosed(object sender, FormClosedEventArgs e){

View File

@@ -5,7 +5,6 @@ using System.Windows.Forms;
using TweetDuck.Core.Bridge; using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling; using TweetDuck.Core.Handling;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data; using TweetDuck.Data;
using TweetDuck.Plugins; using TweetDuck.Plugins;
@@ -13,7 +12,7 @@ using TweetDuck.Plugins.Enums;
using TweetDuck.Resources; using TweetDuck.Resources;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
abstract partial class FormNotificationMain : FormNotificationBase{ abstract partial class FormNotificationMain : FormNotificationBase, ITweetDeckBrowser{
private const string NotificationScriptFile = "notification.js"; private const string NotificationScriptFile = "notification.js";
private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile); private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile);
@@ -82,17 +81,35 @@ namespace TweetDuck.Core.Notification{
this.timerBarHeight = BrowserUtils.Scale(4, DpiScale); this.timerBarHeight = BrowserUtils.Scale(4, DpiScale);
browser.KeyboardHandler = new KeyboardHandlerNotification(this); browser.KeyboardHandler = new KeyboardHandlerNotification(this);
browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge.Notification(owner, this)); browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge.Notification(owner, this));
browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
browser.LoadingStateChanged += Browser_LoadingStateChanged; browser.LoadingStateChanged += Browser_LoadingStateChanged;
browser.FrameLoadEnd += Browser_FrameLoadEnd; browser.FrameLoadEnd += Browser_FrameLoadEnd;
plugins.Register(this, PluginEnvironment.Notification);
mouseHookDelegate = MouseHookProc; mouseHookDelegate = MouseHookProc;
Disposed += (sender, args) => StopMouseHook(true); Disposed += (sender, args) => StopMouseHook(true);
} }
void ITweetDeckBrowser.RegisterBridge(string name, object obj){
browser.RegisterAsyncJsObject(name, obj);
}
void ITweetDeckBrowser.OnFrameLoaded(Action<IFrame> callback){
browser.FrameLoadEnd += (sender, args) => {
IFrame frame = args.Frame;
if (frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){
callback(frame);
}
};
}
void ITweetDeckBrowser.ExecuteFunction(string name, params object[] args){
browser.ExecuteScriptAsync(name, args);
}
// mouse wheel hook // mouse wheel hook
private void StartMouseHook(){ private void StartMouseHook(){
@@ -128,7 +145,7 @@ namespace TweetDuck.Core.Notification{
} }
blockXButtonUp = true; blockXButtonUp = true;
this.InvokeAsyncSafe(() => TriggerAnalyticsEvent(AnalyticsFile.Event.NotificationExtraMouseButton)); this.InvokeAsyncSafe(AnalyticsFile.NotificationExtraMouseButtons.Trigger);
return NativeMethods.HOOK_HANDLED; return NativeMethods.HOOK_HANDLED;
} }
else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp){ else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp){
@@ -167,7 +184,6 @@ namespace TweetDuck.Core.Notification{
if (e.Frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){ if (e.Frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){
e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification)); e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
ScriptLoader.ExecuteScript(e.Frame, NotificationJS, NotificationScriptIdentifier); ScriptLoader.ExecuteScript(e.Frame, NotificationJS, NotificationScriptIdentifier);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification);
} }
} }
@@ -233,7 +249,7 @@ namespace TweetDuck.Core.Notification{
protected override string GetTweetHTML(TweetNotification tweet){ protected override string GetTweetHTML(TweetNotification tweet){
string html = base.GetTweetHTML(tweet); string html = base.GetTweetHTML(tweet);
foreach(InjectedHTML injection in plugins.Bridge.NotificationInjections){ foreach(InjectedHTML injection in plugins.NotificationInjections){
html = injection.Inject(html); html = injection.Inject(html);
} }

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
@@ -94,7 +93,7 @@ namespace TweetDuck.Core.Notification{
} }
needsTrim |= tweetQueue.Count >= TrimMinimum; needsTrim |= tweetQueue.Count >= TrimMinimum;
TriggerAnalyticsEvent(AnalyticsFile.Event.DesktopNotification); AnalyticsFile.DesktopNotifications.Trigger();
} }
public override void HideNotification(){ public override void HideNotification(){

View File

@@ -33,7 +33,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
protected override string GetTweetHTML(TweetNotification tweet){ protected override string GetTweetHTML(TweetNotification tweet){
string html = tweet.GenerateHtml("td-screenshot", false); string html = tweet.GenerateHtml("td-screenshot", false);
foreach(InjectedHTML injection in plugins.Bridge.NotificationInjections){ foreach(InjectedHTML injection in plugins.NotificationInjections){
html = injection.Inject(html); html = injection.Inject(html);
} }

View File

@@ -8,7 +8,7 @@ using TweetDuck.Core.Other.Settings;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
static class SoundNotification{ static class SoundNotification{
public const string SupportedFormats = "*.wav;*.ogg;*.flac;*.opus;*.weba;*.webm"; // TODO add mp3 when supported public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
public static IResourceHandler CreateFileHandler(string path){ public static IResourceHandler CreateFileHandler(string path){
string mimeType; string mimeType;
@@ -18,25 +18,19 @@ namespace TweetDuck.Core.Notification{
case ".webm": mimeType = "audio/webm"; break; case ".webm": mimeType = "audio/webm"; break;
case ".wav": mimeType = "audio/wav"; break; case ".wav": mimeType = "audio/wav"; break;
case ".ogg": mimeType = "audio/ogg"; break; case ".ogg": mimeType = "audio/ogg"; break;
case ".mp3": mimeType = "audio/mp3"; break;
case ".flac": mimeType = "audio/flac"; break; case ".flac": mimeType = "audio/flac"; break;
case ".opus": mimeType = "audio/ogg; codecs=opus"; break; case ".opus": mimeType = "audio/ogg; codecs=opus"; break;
case ".mp3": TryShowError("MP3 sound notifications are temporarily unsupported, please convert the file to another format, such as .ogg, .wav, or .flac."); return null;
default: mimeType = null; break; default: mimeType = null; break;
} }
try{ try{
return ResourceHandler.FromFilePath(path, mimeType); return ResourceHandler.FromFilePath(path, mimeType);
}catch{ }catch{
TryShowError("Could not find custom notification sound file:\n"+path);
return null;
}
}
private static void TryShowError(string message){
FormBrowser browser = FormManager.TryFind<FormBrowser>(); FormBrowser browser = FormManager.TryFind<FormBrowser>();
browser?.InvokeAsyncSafe(() => { browser?.InvokeAsyncSafe(() => {
using(FormMessage form = new FormMessage("Sound Notification Error", message, MessageBoxIcon.Error)){ using(FormMessage form = new FormMessage("Sound Notification Error", "Could not find custom notification sound file:\n"+path, MessageBoxIcon.Error)){
form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused); form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused);
Button btnViewOptions = form.AddButton("View Options"); Button btnViewOptions = form.AddButton("View Options");
@@ -48,6 +42,9 @@ namespace TweetDuck.Core.Notification{
} }
} }
}); });
return null;
}
} }
} }
} }

View File

@@ -1,12 +1,15 @@
using System; using System;
using System.Text; using System.Text;
using CefSharp;
using TweetDuck.Core.Bridge; using TweetDuck.Core.Bridge;
using TweetDuck.Data;
using TweetDuck.Resources; using TweetDuck.Resources;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
sealed class TweetNotification{ sealed class TweetNotification{
private const string DefaultHeadLayout = @"<html id='tduck' class='os-windows txt-size--14' data-td-font='medium' data-td-theme='dark'><head><meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>"; private const string DefaultHeadLayout = @"<html id='tduck' class='os-windows txt-size--14' data-td-font='medium' data-td-theme='dark'><head><meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>";
private static readonly string CustomCSS = ScriptLoader.LoadResource("styles/notification.css"); private static readonly string CustomCSS = ScriptLoader.LoadResource("styles/notification.css");
public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandler.FromByteArray(Properties.Resources.avatar, "image/png"));
public static TweetNotification Example(string html, int characters){ public static TweetNotification Example(string html, int characters){
return new TweetNotification(string.Empty, string.Empty, "Home", html, characters, string.Empty, string.Empty, true); return new TweetNotification(string.Empty, string.Empty, "Home", html, characters, string.Empty, string.Empty, true);

View File

@@ -1,26 +1,27 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using TweetDuck.Data.Serialization; using TweetDuck.Data.Serialization;
namespace TweetDuck.Core.Other.Analytics{ namespace TweetDuck.Core.Other.Analytics{
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]
sealed class AnalyticsFile{ sealed class AnalyticsFile{
private static readonly FileSerializer<AnalyticsFile> Serializer = new FileSerializer<AnalyticsFile>{ private static readonly FileSerializer<AnalyticsFile> Serializer = new FileSerializer<AnalyticsFile>();
HandleUnknownProperties = FileSerializer<AnalyticsFile>.IgnoreProperties("CountGCReloads")
};
static AnalyticsFile(){ static AnalyticsFile(){
Serializer.RegisterTypeConverter(typeof(DateTime), new SingleTypeConverter<DateTime>{ Serializer.RegisterTypeConverter(typeof(DateTime), new SingleTypeConverter<DateTime>{
ConvertToString = value => value.ToBinary().ToString(), ConvertToString = value => value.ToBinary().ToString(),
ConvertToObject = value => DateTime.FromBinary(long.Parse(value)) ConvertToObject = value => DateTime.FromBinary(long.Parse(value))
}); });
Serializer.RegisterTypeConverter(typeof(Counter), new SingleTypeConverter<Counter>{
ConvertToString = value => value.Value.ToString(),
ConvertToObject = value => int.Parse(value)
});
} }
public enum Event{ public static readonly AnalyticsFile Dummy = new AnalyticsFile(null);
OpenOptions, OpenPlugins, OpenAbout, OpenGuide,
DesktopNotification, SoundNotification, MuteNotification,
BrowserContextMenu, BrowserExtraMouseButton,
NotificationContextMenu, NotificationExtraMouseButton, NotificationKeyboardShortcut,
TweetScreenshot, TweetDetail, VideoPlay
}
// STATE PROPERTIES // STATE PROPERTIES
@@ -30,57 +31,57 @@ namespace TweetDuck.Core.Other.Analytics{
// USAGE DATA // USAGE DATA
public int CountOpenOptions { get; private set; } = 0; public Counter OpenOptions { get; private set; } = 0;
public int CountOpenPlugins { get; private set; } = 0; public Counter OpenPlugins { get; private set; } = 0;
public int CountOpenAbout { get; private set; } = 0; public Counter OpenAbout { get; private set; } = 0;
public int CountOpenGuide { get; private set; } = 0; public Counter OpenGuide { get; private set; } = 0;
public int CountDesktopNotifications { get; private set; } = 0; public Counter DesktopNotifications { get; private set; } = 0;
public int CountSoundNotifications { get; private set; } = 0; public Counter SoundNotifications { get; private set; } = 0;
public int CountMuteNotifications { get; private set; } = 0; public Counter NotificationMutes { get; private set; } = 0;
public int CountBrowserContextMenus { get; private set; } = 0; public Counter BrowserContextMenus { get; private set; } = 0;
public int CountBrowserExtraMouseButtons { get; private set; } = 0; public Counter BrowserExtraMouseButtons { get; private set; } = 0;
public int CountNotificationContextMenus { get; private set; } = 0; public Counter NotificationContextMenus { get; private set; } = 0;
public int CountNotificationExtraMouseButtons { get; private set; } = 0; public Counter NotificationExtraMouseButtons { get; private set; } = 0;
public int CountNotificationKeyboardShortcuts { get; private set; } = 0; public Counter NotificationKeyboardShortcuts { get; private set; } = 0;
public int CountTweetScreenshots { get; private set; } = 0; public Counter BrowserReloads { get; private set; } = 0;
public int CountTweetDetails { get; private set; } = 0; public Counter CopiedUsernames { get; private set; } = 0;
public int CountVideoPlays { get; private set; } = 0; public Counter ViewedImages { get; private set; } = 0;
public Counter DownloadedImages { get; private set; } = 0;
public Counter DownloadedVideos { get; private set; } = 0;
public Counter UsedROT13 { get; private set; } = 0;
public Counter TweetScreenshots { get; private set; } = 0;
public Counter TweetDetails { get; private set; } = 0;
public Counter VideoPlays { get; private set; } = 0;
// END OF DATA // END OF DATA
public event EventHandler PropertyChanged;
private readonly string file; private readonly string file;
private AnalyticsFile(string file){ private AnalyticsFile(string file){
this.file = file; this.file = file;
} }
public void TriggerEvent(Event e){ private void SetupProperties(){
switch(e){ foreach(Counter counter in GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.PropertyType == typeof(Counter)).Select(prop => (Counter)prop.GetValue(this))){
case Event.OpenOptions: ++CountOpenOptions; break; counter.SetOwner(this);
case Event.OpenPlugins: ++CountOpenPlugins; break;
case Event.OpenAbout: ++CountOpenAbout; break;
case Event.OpenGuide: ++CountOpenGuide; break;
case Event.DesktopNotification: ++CountDesktopNotifications; break;
case Event.SoundNotification: ++CountSoundNotifications; break;
case Event.MuteNotification: ++CountMuteNotifications; break;
case Event.BrowserContextMenu: ++CountBrowserContextMenus; break;
case Event.BrowserExtraMouseButton: ++CountBrowserExtraMouseButtons; break;
case Event.NotificationContextMenu: ++CountNotificationContextMenus; break;
case Event.NotificationExtraMouseButton: ++CountNotificationExtraMouseButtons; break;
case Event.NotificationKeyboardShortcut: ++CountNotificationKeyboardShortcuts; break;
case Event.TweetScreenshot: ++CountTweetScreenshots; break;
case Event.TweetDetail: ++CountTweetDetails; break;
case Event.VideoPlay: ++CountVideoPlays; break;
} }
} }
public void OnPropertyChanged(){
PropertyChanged?.Invoke(this, EventArgs.Empty);
}
public void Save(){ public void Save(){
if (file == null){
return;
}
try{ try{
Serializer.Write(file, this); Serializer.Write(file, this);
}catch(Exception e){ }catch(Exception e){
@@ -97,7 +98,34 @@ namespace TweetDuck.Core.Other.Analytics{
Program.Reporter.HandleException("Analytics File Error", "Could not open the analytics file.", true, e); Program.Reporter.HandleException("Analytics File Error", "Could not open the analytics file.", true, e);
} }
config.SetupProperties();
return config; return config;
} }
public interface IProvider{
AnalyticsFile AnalyticsFile { get; }
}
public sealed class Counter{
public int Value { get; private set; }
private AnalyticsFile owner;
public Counter(int value){
this.Value = value;
}
public void SetOwner(AnalyticsFile owner){
this.owner = owner;
}
public void Trigger(){
++Value;
owner?.OnPropertyChanged();
}
public static implicit operator int(Counter counter) => counter.Value;
public static implicit operator Counter(int value) => new Counter(value);
}
} }
} }

View File

@@ -1,4 +1,8 @@
using System; // Uncomment to debug reports locally
// #define ANALYTICS_LOCALHOST
// #define ANALYTICS_INSTANT
using System;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Timers; using System.Timers;
@@ -8,8 +12,15 @@ using TweetDuck.Plugins;
namespace TweetDuck.Core.Other.Analytics{ namespace TweetDuck.Core.Other.Analytics{
sealed class AnalyticsManager : IDisposable{ sealed class AnalyticsManager : IDisposable{
private static readonly TimeSpan CollectionInterval = TimeSpan.FromDays(7); private static readonly TimeSpan CollectionInterval = TimeSpan.FromDays(14);
private static readonly Uri CollectionUrl = new Uri("https://tweetduck.chylex.com/breadcrumb/report");
private static readonly Uri CollectionUrl = new Uri(
#if (DEBUG && ANALYTICS_LOCALHOST)
"http://localhost/newhome/tweetduck/~breadcrumb/request.php?type=report"
#else
"https://tweetduck.chylex.com/breadcrumb/report"
#endif
);
public AnalyticsFile File { get; } public AnalyticsFile File { get; }
@@ -20,7 +31,9 @@ namespace TweetDuck.Core.Other.Analytics{
public AnalyticsManager(FormBrowser browser, PluginManager plugins, string file){ public AnalyticsManager(FormBrowser browser, PluginManager plugins, string file){
this.browser = browser; this.browser = browser;
this.plugins = plugins; this.plugins = plugins;
this.File = AnalyticsFile.Load(file); this.File = AnalyticsFile.Load(file);
this.File.PropertyChanged += File_PropertyChanged;
this.currentTimer = new Timer{ SynchronizingObject = browser }; this.currentTimer = new Timer{ SynchronizingObject = browser };
this.currentTimer.Elapsed += currentTimer_Elapsed; this.currentTimer.Elapsed += currentTimer_Elapsed;
@@ -29,14 +42,20 @@ namespace TweetDuck.Core.Other.Analytics{
this.saveTimer.Elapsed += saveTimer_Elapsed; this.saveTimer.Elapsed += saveTimer_Elapsed;
if (this.File.LastCollectionVersion != Program.VersionTag){ if (this.File.LastCollectionVersion != Program.VersionTag){
ScheduleReportIn(TimeSpan.FromHours(12), string.Empty); ScheduleReportIn(TimeSpan.FromHours(8), string.Empty);
} }
else{ else{
RestartTimer(); RestartTimer();
} }
#if (DEBUG && ANALYTICS_INSTANT)
SendReport();
#endif
} }
public void Dispose(){ public void Dispose(){
File.PropertyChanged -= File_PropertyChanged;
if (saveTimer.Enabled){ if (saveTimer.Enabled){
File.Save(); File.Save();
} }
@@ -45,8 +64,7 @@ namespace TweetDuck.Core.Other.Analytics{
saveTimer.Dispose(); saveTimer.Dispose();
} }
public void TriggerEvent(AnalyticsFile.Event e){ private void File_PropertyChanged(object sender, EventArgs e){
File.TriggerEvent(e);
saveTimer.Enabled = true; saveTimer.Enabled = true;
} }
@@ -72,7 +90,7 @@ namespace TweetDuck.Core.Other.Analytics{
TimeSpan diff = DateTime.Now.Subtract(File.LastDataCollection); TimeSpan diff = DateTime.Now.Subtract(File.LastDataCollection);
int minutesTillNext = (int)(CollectionInterval.TotalMinutes-Math.Floor(diff.TotalMinutes)); int minutesTillNext = (int)(CollectionInterval.TotalMinutes-Math.Floor(diff.TotalMinutes));
currentTimer.Interval = Math.Max(minutesTillNext, 1)*60000; currentTimer.Interval = Math.Max(minutesTillNext, 2)*60000;
currentTimer.Start(); currentTimer.Start();
} }
@@ -95,7 +113,7 @@ namespace TweetDuck.Core.Other.Analytics{
Task.Factory.StartNew(() => { Task.Factory.StartNew(() => {
AnalyticsReport report = AnalyticsReportGenerator.Create(File, info, plugins); AnalyticsReport report = AnalyticsReportGenerator.Create(File, info, plugins);
#if DEBUG #if (DEBUG && !ANALYTICS_INSTANT)
System.Diagnostics.Debugger.Break(); System.Diagnostics.Debugger.Break();
#endif #endif
@@ -122,6 +140,14 @@ namespace TweetDuck.Core.Other.Analytics{
message = "HTTP Error "+(response != null ? $"{(int)response.StatusCode} ({response.StatusDescription})" : "(unknown code)"); message = "HTTP Error "+(response != null ? $"{(int)response.StatusCode} ({response.StatusDescription})" : "(unknown code)");
break; break;
} }
#if DEBUG
System.IO.Stream responseStream = e.Response.GetResponseStream();
if (responseStream != null){
System.Diagnostics.Debug.WriteLine(new System.IO.StreamReader(responseStream).ReadToEnd());
}
#endif
} }
ScheduleReportIn(TimeSpan.FromHours(4), message ?? "Error: "+(task.Exception.InnerException?.Message ?? task.Exception.Message)); ScheduleReportIn(TimeSpan.FromHours(4), message ?? "Error: "+(task.Exception.InnerException?.Message ?? task.Exception.Message));

View File

@@ -12,6 +12,7 @@ using TweetDuck.Core.Handling;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;
namespace TweetDuck.Core.Other.Analytics{ namespace TweetDuck.Core.Other.Analytics{
static class AnalyticsReportGenerator{ static class AnalyticsReportGenerator{
@@ -37,15 +38,24 @@ namespace TweetDuck.Core.Other.Analytics{
{ "Screen DPI" , info.DPI != null ? Exact(info.DPI.Value) : "(unknown)" }, { "Screen DPI" , info.DPI != null ? Exact(info.DPI.Value) : "(unknown)" },
0, 0,
{ "Hardware Acceleration" , Bool(SysConfig.HardwareAcceleration) }, { "Hardware Acceleration" , Bool(SysConfig.HardwareAcceleration) },
{ "Clear Cache Automatically" , Bool(SysConfig.ClearCacheAutomatically) },
{ "Clear Cache Threshold" , Exact(SysConfig.ClearCacheThreshold) },
0, 0,
{ "Expand Links" , Bool(UserConfig.ExpandLinksOnHover) }, { "Expand Links" , Bool(UserConfig.ExpandLinksOnHover) },
{ "Switch Account Selectors" , Bool(UserConfig.SwitchAccountSelectors) }, { "Switch Account Selectors" , Bool(UserConfig.SwitchAccountSelectors) },
{ "Search In First Column" , Bool(UserConfig.OpenSearchInFirstColumn) }, { "Search In First Column" , Bool(UserConfig.OpenSearchInFirstColumn) },
{ "Keep Like Follow Dialogs Open" , Bool(UserConfig.KeepLikeFollowDialogsOpen) }, { "Keep Like Follow Dialogs Open" , Bool(UserConfig.KeepLikeFollowDialogsOpen) },
{ "Best Image Quality" , Bool(UserConfig.BestImageQuality) }, { "Best Image Quality" , Bool(UserConfig.BestImageQuality) },
{ "Spell Check" , Bool(UserConfig.EnableSpellCheck) }, { "Animated Images" , Bool(UserConfig.EnableAnimatedImages) },
0,
{ "Smooth Scrolling" , Bool(UserConfig.EnableSmoothScrolling) },
{ "Custom Browser" , CustomBrowser },
{ "Zoom" , Exact(UserConfig.ZoomLevel) }, { "Zoom" , Exact(UserConfig.ZoomLevel) },
0, 0,
{ "Spell Check" , Bool(UserConfig.EnableSpellCheck) },
{ "Spell Check Language" , UserConfig.SpellCheckLanguage.ToLower() },
{ "Translation Target Language" , UserConfig.TranslationTarget },
0,
{ "Updates" , Bool(UserConfig.EnableUpdateCheck) }, { "Updates" , Bool(UserConfig.EnableUpdateCheck) },
{ "Update Dismissed" , Bool(!string.IsNullOrEmpty(UserConfig.DismissedUpdate)) }, { "Update Dismissed" , Bool(!string.IsNullOrEmpty(UserConfig.DismissedUpdate)) },
0, 0,
@@ -70,8 +80,8 @@ namespace TweetDuck.Core.Other.Analytics{
{ "Custom Browser CSS" , RoundUp((UserConfig.CustomBrowserCSS ?? string.Empty).Length, 50) }, { "Custom Browser CSS" , RoundUp((UserConfig.CustomBrowserCSS ?? string.Empty).Length, 50) },
{ "Custom Notification CSS" , RoundUp((UserConfig.CustomNotificationCSS ?? string.Empty).Length, 50) }, { "Custom Notification CSS" , RoundUp((UserConfig.CustomNotificationCSS ?? string.Empty).Length, 50) },
0, 0,
{ "Plugins All" , List(plugins.Plugins.Select(plugin => plugin.Identifier)) }, { "Plugins All" , List(plugins.Plugins.Select(Plugin)) },
{ "Plugins Enabled" , List(plugins.Plugins.Where(plugin => plugins.Config.IsEnabled(plugin)).Select(plugin => plugin.Identifier)) }, { "Plugins Enabled" , List(plugins.Plugins.Where(plugin => plugins.Config.IsEnabled(plugin)).Select(Plugin)) },
0, 0,
{ "Theme" , Dict(editLayoutDesign, "_theme", "light/def") }, { "Theme" , Dict(editLayoutDesign, "_theme", "light/def") },
{ "Column Width" , Dict(editLayoutDesign, "columnWidth", "310px/def") }, { "Column Width" , Dict(editLayoutDesign, "columnWidth", "310px/def") },
@@ -87,21 +97,27 @@ namespace TweetDuck.Core.Other.Analytics{
{ "Reply Account Mode" , ReplyAccountConfigFromPlugin }, { "Reply Account Mode" , ReplyAccountConfigFromPlugin },
{ "Template Count" , Exact(TemplateCountFromPlugin) }, { "Template Count" , Exact(TemplateCountFromPlugin) },
0, 0,
{ "Opened Options" , LogRound(file.CountOpenOptions, 4) }, { "Opened Options" , LogRound(file.OpenOptions, 4) },
{ "Opened Plugins" , LogRound(file.CountOpenPlugins, 4) }, { "Opened Plugins" , LogRound(file.OpenPlugins, 4) },
{ "Opened About" , LogRound(file.CountOpenAbout, 4) }, { "Opened About" , LogRound(file.OpenAbout, 4) },
{ "Opened Guide" , LogRound(file.CountOpenGuide, 4) }, { "Opened Guide" , LogRound(file.OpenGuide, 4) },
{ "Desktop Notifications" , LogRound(file.CountDesktopNotifications, 5) }, { "Desktop Notifications" , LogRound(file.DesktopNotifications, 5) },
{ "Sound Notifications" , LogRound(file.CountSoundNotifications, 5) }, { "Sound Notifications" , LogRound(file.SoundNotifications, 5) },
{ "Mute Notifications" , LogRound(file.CountMuteNotifications, 2) }, { "Notification Mutes" , LogRound(file.NotificationMutes, 2) },
{ "Browser Context Menus" , LogRound(file.CountBrowserContextMenus, 2) }, { "Browser Context Menus" , LogRound(file.BrowserContextMenus, 2) },
{ "Browser Extra Mouse Buttons" , LogRound(file.CountBrowserExtraMouseButtons, 2) }, { "Browser Extra Mouse Buttons" , LogRound(file.BrowserExtraMouseButtons, 2) },
{ "Notification Context Menus" , LogRound(file.CountNotificationContextMenus, 2) }, { "Notification Context Menus" , LogRound(file.NotificationContextMenus, 2) },
{ "Notification Extra Mouse Buttons" , LogRound(file.CountNotificationExtraMouseButtons, 2) }, { "Notification Extra Mouse Buttons" , LogRound(file.NotificationExtraMouseButtons, 2) },
{ "Notification Keyboard Shortcuts" , LogRound(file.CountNotificationKeyboardShortcuts, 2) }, { "Notification Keyboard Shortcuts" , LogRound(file.NotificationKeyboardShortcuts, 2) },
{ "Tweet Screenshots" , LogRound(file.CountTweetScreenshots, 2) }, { "Browser Reloads" , LogRound(file.BrowserReloads, 2) },
{ "Tweet Details" , LogRound(file.CountTweetDetails, 2) }, { "Copied Usernames" , LogRound(file.CopiedUsernames, 2) },
{ "Video Plays" , LogRound(file.CountVideoPlays, 4) } { "Viewed Images" , LogRound(file.ViewedImages, 2) },
{ "Downloaded Images" , LogRound(file.DownloadedImages, 2) },
{ "Downloaded Videos" , LogRound(file.DownloadedVideos, 2) },
{ "Used ROT13" , LogRound(file.UsedROT13, 2) },
{ "Tweet Screenshots" , LogRound(file.TweetScreenshots, 2) },
{ "Tweet Details" , LogRound(file.TweetDetails, 2) },
{ "Video Plays" , LogRound(file.VideoPlays, 4) }
}.FinalizeReport(); }.FinalizeReport();
} }
@@ -112,6 +128,7 @@ namespace TweetDuck.Core.Other.Analytics{
private static string Exact(int value) => value.ToString(); private static string Exact(int value) => value.ToString();
private static string RoundUp(int value, int multiple) => (multiple*(int)Math.Ceiling((double)value/multiple)).ToString(); private static string RoundUp(int value, int multiple) => (multiple*(int)Math.Ceiling((double)value/multiple)).ToString();
private static string LogRound(int value, int logBase) => (value <= 0 ? 0 : (int)Math.Pow(logBase, Math.Floor(Math.Log(value, logBase)))).ToString(); private static string LogRound(int value, int logBase) => (value <= 0 ? 0 : (int)Math.Pow(logBase, Math.Floor(Math.Log(value, logBase)))).ToString();
private static string Plugin(Plugin plugin) => plugin.Group.GetIdentifierPrefixShort()+plugin.Identifier.Substring(plugin.Group.GetIdentifierPrefix().Length);
private static string Dict(Dictionary<string, string> dict, string key, string def = "(unknown)") => dict.TryGetValue(key, out string value) ? value : def; private static string Dict(Dictionary<string, string> dict, string key, string def = "(unknown)") => dict.TryGetValue(key, out string value) ? value : def;
private static string List(IEnumerable<string> list) => string.Join("|", list.DefaultIfEmpty("(none)")); private static string List(IEnumerable<string> list) => string.Join("|", list.DefaultIfEmpty("(none)"));
@@ -182,6 +199,12 @@ namespace TweetDuck.Core.Other.Analytics{
ProgramArguments = args.Keys.Select(key => key.TrimStart('-')).ToArray(); ProgramArguments = args.Keys.Select(key => key.TrimStart('-')).ToArray();
} }
private static string CustomBrowser{
get{
return Path.GetFileName(UserConfig.BrowserPath) ?? string.Empty;
}
}
private static string TrayMode{ private static string TrayMode{
get{ get{
switch(UserConfig.TrayBehavior){ switch(UserConfig.TrayBehavior){

View File

@@ -38,7 +38,6 @@ namespace TweetDuck.Core.Other {
// //
this.pictureLogo.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None; this.pictureLogo.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None;
this.pictureLogo.ErrorImage = null; this.pictureLogo.ErrorImage = null;
this.pictureLogo.Image = ((System.Drawing.Image)(resources.GetObject("pictureLogo.Image")));
this.pictureLogo.InitialImage = null; this.pictureLogo.InitialImage = null;
this.pictureLogo.Location = new System.Drawing.Point(12, 12); this.pictureLogo.Location = new System.Drawing.Point(12, 12);
this.pictureLogo.Name = "pictureLogo"; this.pictureLogo.Name = "pictureLogo";

View File

@@ -1,4 +1,6 @@
using System.ComponentModel; using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
@@ -17,6 +19,10 @@ namespace TweetDuck.Core.Other{
labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website)); labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website));
labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink)); labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink)); labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink));
MemoryStream logoStream = new MemoryStream(Properties.Resources.avatar);
pictureLogo.Image = Image.FromStream(logoStream);
Disposed += (sender, args) => logoStream.Dispose();
} }
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){ private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){

View File

@@ -1,246 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="pictureLogo.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAABGdBTUEAALGOfPtRkwAAACBjSFJNAACH
DwAAjA8AAP1SAACBQAAAfXkAAOmLAAA85QAAGcxzPIV3AAAKOWlDQ1BQaG90b3Nob3AgSUNDIHByb2Zp
bGUAAEjHnZZ3VFTXFofPvXd6oc0wAlKG3rvAANJ7k15FYZgZYCgDDjM0sSGiAhFFRJoiSFDEgNFQJFZE
sRAUVLAHJAgoMRhFVCxvRtaLrqy89/Ly++Osb+2z97n77L3PWhcAkqcvl5cGSwGQyhPwgzyc6RGRUXTs
AIABHmCAKQBMVka6X7B7CBDJy82FniFyAl8EAfB6WLwCcNPQM4BOB/+fpFnpfIHomAARm7M5GSwRF4g4
JUuQLrbPipgalyxmGCVmvihBEcuJOWGRDT77LLKjmNmpPLaIxTmns1PZYu4V8bZMIUfEiK+ICzO5nCwR
3xKxRoowlSviN+LYVA4zAwAUSWwXcFiJIjYRMYkfEuQi4uUA4EgJX3HcVyzgZAvEl3JJS8/hcxMSBXQd
li7d1NqaQffkZKVwBALDACYrmcln013SUtOZvBwAFu/8WTLi2tJFRbY0tba0NDQzMv2qUP91829K3NtF
ehn4uWcQrf+L7a/80hoAYMyJarPziy2uCoDOLQDI3fti0zgAgKSobx3Xv7oPTTwviQJBuo2xcVZWlhGX
wzISF/QP/U+Hv6GvvmckPu6P8tBdOfFMYYqALq4bKy0lTcinZ6QzWRy64Z+H+B8H/nUeBkGceA6fwxNF
hImmjMtLELWbx+YKuGk8Opf3n5r4D8P+pMW5FonS+BFQY4yA1HUqQH7tBygKESDR+8Vd/6NvvvgwIH55
4SqTi3P/7zf9Z8Gl4iWDm/A5ziUohM4S8jMX98TPEqABAUgCKpAHykAd6ABDYAasgC1wBG7AG/iDEBAJ
VgMWSASpgA+yQB7YBApBMdgJ9oBqUAcaQTNoBcdBJzgFzoNL4Bq4AW6D+2AUTIBnYBa8BgsQBGEhMkSB
5CEVSBPSh8wgBmQPuUG+UBAUCcVCCRAPEkJ50GaoGCqDqqF6qBn6HjoJnYeuQIPQXWgMmoZ+h97BCEyC
qbASrAUbwwzYCfaBQ+BVcAK8Bs6FC+AdcCXcAB+FO+Dz8DX4NjwKP4PnEIAQERqiihgiDMQF8UeikHiE
j6xHipAKpAFpRbqRPuQmMorMIG9RGBQFRUcZomxRnqhQFAu1BrUeVYKqRh1GdaB6UTdRY6hZ1Ec0Ga2I
1kfboL3QEegEdBa6EF2BbkK3oy+ib6Mn0K8xGAwNo42xwnhiIjFJmLWYEsw+TBvmHGYQM46Zw2Kx8lh9
rB3WH8vECrCF2CrsUexZ7BB2AvsGR8Sp4Mxw7rgoHA+Xj6vAHcGdwQ3hJnELeCm8Jt4G749n43PwpfhG
fDf+On4Cv0CQJmgT7AghhCTCJkIloZVwkfCA8JJIJKoRrYmBRC5xI7GSeIx4mThGfEuSIemRXEjRJCFp
B+kQ6RzpLuklmUzWIjuSo8gC8g5yM/kC+RH5jQRFwkjCS4ItsUGiRqJDYkjiuSReUlPSSXK1ZK5kheQJ
yeuSM1J4KS0pFymm1HqpGqmTUiNSc9IUaVNpf+lU6RLpI9JXpKdksDJaMm4ybJkCmYMyF2TGKQhFneJC
YVE2UxopFykTVAxVm+pFTaIWU7+jDlBnZWVkl8mGyWbL1sielh2lITQtmhcthVZKO04bpr1borTEaQln
yfYlrUuGlszLLZVzlOPIFcm1yd2WeydPl3eTT5bfJd8p/1ABpaCnEKiQpbBf4aLCzFLqUtulrKVFS48v
vacIK+opBimuVTyo2K84p6Ss5KGUrlSldEFpRpmm7KicpFyufEZ5WoWiYq/CVSlXOavylC5Ld6Kn0Cvp
vfRZVUVVT1Whar3qgOqCmrZaqFq+WpvaQ3WCOkM9Xr1cvUd9VkNFw08jT6NF454mXpOhmai5V7NPc15L
Wytca6tWp9aUtpy2l3audov2Ax2yjoPOGp0GnVu6GF2GbrLuPt0berCehV6iXo3edX1Y31Kfq79Pf9AA
bWBtwDNoMBgxJBk6GWYathiOGdGMfI3yjTqNnhtrGEcZ7zLuM/5oYmGSYtJoct9UxtTbNN+02/R3Mz0z
llmN2S1zsrm7+QbzLvMXy/SXcZbtX3bHgmLhZ7HVosfig6WVJd+y1XLaSsMq1qrWaoRBZQQwShiXrdHW
ztYbrE9Zv7WxtBHYHLf5zdbQNtn2iO3Ucu3lnOWNy8ft1OyYdvV2o/Z0+1j7A/ajDqoOTIcGh8eO6o5s
xybHSSddpySno07PnU2c+c7tzvMuNi7rXM65Iq4erkWuA24ybqFu1W6P3NXcE9xb3Gc9LDzWepzzRHv6
eO7yHPFS8mJ5NXvNelt5r/Pu9SH5BPtU+zz21fPl+3b7wX7efrv9HqzQXMFb0ekP/L38d/s/DNAOWBPw
YyAmMCCwJvBJkGlQXlBfMCU4JvhI8OsQ55DSkPuhOqHC0J4wybDosOaw+XDX8LLw0QjjiHUR1yIVIrmR
XVHYqLCopqi5lW4r96yciLaILoweXqW9KnvVldUKq1NWn46RjGHGnIhFx4bHHol9z/RnNjDn4rziauNm
WS6svaxnbEd2OXuaY8cp40zG28WXxU8l2CXsTphOdEisSJzhunCruS+SPJPqkuaT/ZMPJX9KCU9pS8Wl
xqae5Mnwknm9acpp2WmD6frphemja2zW7Fkzy/fhN2VAGasyugRU0c9Uv1BHuEU4lmmfWZP5Jiss60S2
dDYvuz9HL2d7zmSue+63a1FrWWt78lTzNuWNrXNaV78eWh+3vmeD+oaCDRMbPTYe3kTYlLzpp3yT/LL8
V5vDN3cXKBVsLBjf4rGlpVCikF84stV2a9021DbutoHt5turtn8sYhddLTYprih+X8IqufqN6TeV33za
Eb9joNSydP9OzE7ezuFdDrsOl0mX5ZaN7/bb3VFOLy8qf7UnZs+VimUVdXsJe4V7Ryt9K7uqNKp2Vr2v
Tqy+XeNc01arWLu9dn4fe9/Qfsf9rXVKdcV17w5wD9yp96jvaNBqqDiIOZh58EljWGPft4xvm5sUmoqb
PhziHRo9HHS4t9mqufmI4pHSFrhF2DJ9NProje9cv+tqNWytb6O1FR8Dx4THnn4f+/3wcZ/jPScYJ1p/
0Pyhtp3SXtQBdeR0zHYmdo52RXYNnvQ+2dNt293+o9GPh06pnqo5LXu69AzhTMGZT2dzz86dSz83cz7h
/HhPTM/9CxEXbvUG9g5c9Ll4+ZL7pQt9Tn1nL9tdPnXF5srJq4yrndcsr3X0W/S3/2TxU/uA5UDHdavr
XTesb3QPLh88M+QwdP6m681Lt7xuXbu94vbgcOjwnZHokdE77DtTd1PuvriXeW/h/sYH6AdFD6UeVjxS
fNTws+7PbaOWo6fHXMf6Hwc/vj/OGn/2S8Yv7ycKnpCfVEyqTDZPmU2dmnafvvF05dOJZ+nPFmYKf5X+
tfa5zvMffnP8rX82YnbiBf/Fp99LXsq/PPRq2aueuYC5R69TXy/MF72Rf3P4LeNt37vwd5MLWe+x7ys/
6H7o/ujz8cGn1E+f/gUDmPP8usTo0wAAAAlwSFlzAAAuIgAALiIBquLdkgAAEVpJREFUeF7tXQl0VNd5
Thq3Tk7tc+qmiZMm7YmdJj1OT3Gd0kTMG3mJnZQEJzaSjXETx0lqNwt2jWscQGixBAJLSMhgA8YYbIyQ
hHYJI4lFGMwioQWxyewgMdoXtKFd4u/337lPjDRv9pk3g+A75zsMWv577/fd+9/l3Rl9johu0Y/U/OIt
6sfrLwIYT2de+SJ4F/j34LfB+8Ap4L9J8uvvgfy9b4DfBO8EPy9DBBwC3gCI93fgDHAuuA7cDVaDzWAX
OAAOgcNgP9gN8vc+A4vB9eBr4M/Be8GAMiPgDIBAXwVDwbXgEbAPJC+RDToGvgfOBr8mi/UbAsIACMFp
ZRaYC7aBWuL5gh3gdvBZ8C5ZHV3hVwPQaM7hS0ETqCWQnqwDE8Apsnq6wC8GoJEPgFtAzt9aYviTPJ9k
gkGyuj6FrgagUd8CeVIcBLUaH0gcATeD35XV9wl0MQCN4OXjn8EroFZjA5m8qooE/1o2x6vwuQGouAEs
A7UadyPxOPgj2SyvwacGoMILwRsh3ThLTksx4BdkEz2GTwxABb8C8pJSqxGTgTvAb8jmegSvG4CK/QvI
O1Wtik8mXgSnyma7Da8agAo9Auq5kfI3+Shkhmy+W/CaAajI42CvrNjNRJ7jZksZXIZXDEAFWPxA3FTp
xVHQLRM8NgAFPwTejD1/InkH/biUxWl4ZAAK5LP3mynnO+JV0KWJ2W0DUNCXwVOy4Fu8zhrQ6SWqJwbk
WxQa2Mxop6fSWyk0rYlCUhspZEs9zdxSJ8iv+WuhW5vpqYw27d93nXvB26RUduGWAQg+36KwwCULv7VF
iDx3Wx1tPNxCBy92UU17P7VdHaLWniE619JLu8900Nv7m+jFLBgCM9gs/l3NmCqlqU+nt+D/msYtk3LZ
hcsGIPB/gPaPF7jyKrW+rwNZnJCUBoraUU9H63po9No1NM8++odGac/ZDno5r45C2QitESGF59gv5dVT
clUHzc5gEyb8XOaVa6DDsyMU67wBCHg7WCULsEkezi9/jEptbfKLCdzrn0s30b5znWiS6xgeuUaby1so
NKVubDSwGZzCOG3NzTdRQXW7+NnM4+b2atUDPAPeIeXTBEK4ZMA8i+Ca5Ir+LrOOOvtHKGkfcit6kp4m
sBh/zDGRqaMfzfEMh5CuZiXXUOgWE72Ua6L1Jc10qqmXRkbNo4nT2PMw2sHcESvl0wTCOGcAAv0DyFtv
rULGyAKsO9SMUERD6EnRO+t1M4F76++zTNTUNSjK9wYutvVhzujTTGGrMG/wqNCqiwX5IsA/SxmtgDBO
G7DBIqhN8oR3suEqQpnRh7watcOcU31pAvfCWUgZ1Y3Xy/YlCk+Z28rlqrTTvkwpoxUQyrEBCPCvoONz
fVTg1xkm6hkYQajr4Mktdpd5uecrE9jg5IpWWaJvwRN1SHKtaA/PCfMLmyiq2DxPaNUN5KMKg5RzHBDO
KQNSLYLZJFfgVSz3tMBDeM0BrMNlr9H6fXfJ8V7AErK7f1iW5hsMjYxSSnkjPbnxLP1vbq2YE04399KV
vmGak9/kqF0FUs5xQFj7BuAXvwM6ddDGOfiNnQ0IYxvbTrYjVWDiwkrFW6OB83DaEd/3/obOfiq71Ekt
3YOkzgg8NUTxPOdgLpiV1TE6K7vzASnrGBDCoQF8b0cz6ESyqDG77BvAOI2VxJycy7bX2q4QJoYgDVxo
7ZPR9QVv4OzNb7OyOll8mpnWTNPXnlwnZR0DQtg2AAHuAPnCkmbwieQREFZQjzCOwfPChlJs/3mt7cFo
YAPn5NTRsFwa6on3kII05zUIjt4uXv9icx099u5pCn7rKBkSynoMC1K/LuUVQBi7BswcF9gBWYzfZ5vE
RsZZnG/powjsVsXc4IYR/DtLix2POm+C9wGrPjWfK1nWl3v6MxA+FB1xxgeX6OF3TpKSdASsIiMMMK48
TkpM4RwprwDC2TUgTw3uFFEZXgo2dA4glPNgu8pruyms0HxAJnaWThrBP7v2oHnfoQd48xWBelqmHe7t
Is2kNNJ/rj9HD648RsoKC+FVwgBj/MGDUl4BhNQ2AIH/FuTLq1aNtkfuFYWfXUEo18FGHKvrofg9DfRs
mknEcjQq2ABejeiFyEKTWXyUbU4z7fSLj0z06NpTELnKLLyl6BOZVDVgDM+8R8ps14AQy4Y6S1fmAXvg
npZ/oo0iiuro2VSYkdIgVhocX0zc0hQ2YDWWt3phIdrGp5+hW1vpZxsv0SOcZrR6uy1iFAQvLvqDlNmu
Ae9aCus0IQzn8/LL3QjnHbAZpZe6RE9fsL2O/jvTJFIdlzMz2URRhZflT/oer+fX0E/ePUXBttKMI3Ia
enNfhpRZ2wAI+XnQ4amnLXLamPdxHQ1i4+IL8AqqHvMMp6vdZ65Q8en2sXW5r/HbjZVYzVRoi+sMYZwx
saxWWbj1Swhn04B/BPkQSVNgu+TUAHKefB/LzMkE3s3PXgfxsbLRFNd5XlMWZT6AkDYN+JmVsE5QLEPz
WujXGY0iZ3N60GOHqheuDgzT4++U80SqJarz5DQUXfAcQlobACE5/fAb4jRFtkdOPatLWqmrf4RSKlvo
1fzL9MSGM7TuQJ04R7nR0dDRSw8mlGqL6gpXniDjkp2xyoKULyCslQH81MutCZgNWLz7+qaI83Jz9yAm
0A5q6/HeGb2/cPxyBxnivWEAT8T7c4Mjc+5EWCsD/gbkt4JqimyP6rGA+sRosqHgWD0Zlh/WFtUVsgHL
S44aI3O/hrBWBvCboN273cwTMJaHvEKZjEgsOkNKogcrIJViJVTeaIzefi/CWhnA7zJvGiesC+TVT85x
8wPryYYXNpl3upqiukQYsKKyR1my63sIa2UAv3WU3xelKbAj8k71lXz/nE76Em09A/Tjt5B+PF0BqUw6
MogN2RSEtjKAHz+6twdg8k6Yz4NOuXceFKgorm7yTv5XmXRkVIk78H2EtjLgfpBv+moL7AR5Mv5NRp1X
byf4GzH5n3kn/6vkkbS8dCpCWxnAnzrikQFMPiRbWFAnbkXc6OAN2BNreAPmjfwvyQbEH9I0gD/yxfM3
W/BxRFqTuBrYOzj+lsSNht0nG72bfphJVddgwL8jvJUBHk3C4yhNeCXfRJfaPL+p5i+8ln4Cq59KbSHd
ZVLloLJs7/0Ib2UAL0NbxgnpCWEC75Bnp5rog7IW6ujz7dURb+NsYxdvmswpQ0tItyiWod1KTJHmMpQ3
YvxhR9qCukiekGenN4tHjU9+VENPbzpLcbtq6GS9954X+BLLtp/y7uTLNG/EmozRBd9GEVYG8Gf37LEU
0RNy7/+o8gqdae6jQxc76eCFTqqo7aTGzsBPSRdbeuihRG/3fpANiC85qkTkaB5F8Adr8CeaaArqKsUj
yiJ9by14C2/keXnpqXIV5pTYPblB8zZ9BcVYGcDH0f9nKaJH5I1ZSgMduNCF8DcOjtS0k+L13C/JBize
EWeYn3wbihpvgHwgw+/71RbUDfIo+FNuvdWl3UAFn+a+KM59vLzyUWl+IPMbFHVd97EXZgPuAb3yKSfq
ZSUeBbFFNSgi8PHRwRrzut8Xvd/Ma8FR+XYfSf4FyJ+PoymqM1TvRIakNtP098/TQ5h4psWXUnzBaRQT
uDhZ1yknXi/uei0JHZTlpbVBr6z7IoqzNkAlRHR9Ira4E/lEcv31O5Hq9Q2Qe9bCrJPU3T+EYgILPajT
r96v8F3qYSL/G5bszuKOjiLHeP2FBETkj5G0FlmDapp5amsrzfiwhh5ZPeFOpCXxNSWxnGauKaed2OI7
8eZF3RCejXqjbj5MPWYDInJfZo1RpF0D+EOX7L4nbOzqdWqTSDN8J3Kst2sVbkF+sMGj4fmNlZR2uFas
uYflg3t/mLJy11lf530Q6/+3qoaw+vkOa4xibRvAgMj8oaYTRLe+eu3UnUgt8mhgIxLKKBhLvsdXltDc
zRV0qUXfXfKHBy7pID4ongUfKpfyOmXAf00U3ubVa0+IOCzAM+vKqOxCG6qhHzbziodvOvhq0rUk0k9Q
WPpLUl4BVMG2AbOyu+4CW1j8Jx1dvXaHLDx6/09XHabU0loaHNb32cG7n1zQT3w+fkgs75r6Pwl3S3kF
UA3bBjB+vvH8CnH1WqYLzeCukEcNVhnc459YXSaGf0evvk/O2OjovGqZdnQQn/l2NQVF5G2Qso4B1bFv
gCEs/X6kmhHNoCp5NHBDBPm1ShYbTKwQPZ1723T09tfTT4hnrL2D+h9N17Zdpd9uNI88UUet9viCGAFB
r29WpKxjQJXsG8AwLtv3sbhOpxUYjZi+5hg9s+E4zVpfRT9ZeZh+nFRKP11ZRqFry+mFD6soKreakg/V
iPzuz/V/TqVJ1E2s83UVX9yE2294baPV3y5AtRwboIRnT0OgUavAko++fZR++cFxKj7VQgPDI9Q3OCKE
HhgKjLOfc03d9ErqcX1TjiVhADLJdCnnOKB6jg1gGOP2pwsntQpAbxLzw/JSCsuqprNocCCgoaOPEgrP
0MOJJeZjZT17vUo++Vz6yU4poxVQTScNWJT5XfSefvNmQqMgJhuBhirxJTQ/4wRVXGqnUT9c0Drf3E3L
IfyPON2Ina0fer0gVoxJVcNBr31g9QZtFaiucwYwjEt2xdqcCywpRgRWOph0f7m+QuT/y+1XfbrD5Ztr
24/V05wtR8Wmzr/CS7JWscWrpHyaQNWdN0AJS7vDmFh2zmYqmkiZmnjF8WBCibhbuWbPeTqMyZiXnp4Y
wgdnfF2cl7Gc3x9Db+ccr/sEa4u87l9R3hAcnvllKZ8m0BTnDWAo4VmPGldyIXZSkRalGdwzeWTwaonP
gRZlV9O6vRcoq8JEu7E0LTnXSidMHSKNHK29Qofwf76Xw99/Dz8XiRXV7z48QjPeLkOqg+gwN2BEtyRv
WCNyQqRsNuGyAQxlya44p1KRPaqGQDxhCnrvGFlYlRZf558TcwxP+IEmuCXFI8eda6RcduGWAcaFaX9p
jD/0qdOp6GYia7K8pCp4UfqXpFx24ZYBDGVB6jeNKypMt0ywoPm8p9UYkW3zI8omwm0DGIbXN//AuKKy
VxSsVaGbiWYNhrBpdenPnHhkAAMTzUwUPnxTm6C2PWrbc1IWp+GxAQxDePavxlXkpiLazO2OKfqjlMMl
eMUABruPCt1kI4HbiuVm9PY/SRlchtcMYBjCMkJQMcwJN8HEzB0tqWrIGF3wvGy+W/CqAQwlLH2aknDY
xGthzYpPBnIHW1HRYozMeUw222143QDGtD8n3yP2CcKESZaSeAOaUFpuDM+8TzbXI/jEAIZxYepfGaIL
48yVngQpiVMO2qHEFq81Lkr32p819JkBKgyLMmeYD/Bu4NHAdU8sr1Ui85+SzfIafG4Aw7go4y5jbPEy
8TzhRpobxMitGjYu3fOOMWzrV2VzvApdDFDxw3mbpyhL92aL09RATkuibhitb35agIn2h7L6PoGuBqgw
hOc8Zow7UCQayyMiIPYOnONlmow7sEeJzPPoL+Q5C78YoAI76KBpMTtXK4nlLcZV6HVqz9MUyEfkMpkr
KnuMSz/ZpETkPCyrpwv8aoCKH8zdcPe0yG0vYum6X8wTLIivRoZczQgmVQ2izBIluvBVZeHWb8nq6IqA
MMASWDX9k+GN7X8wxO5JU5aXnjeLxoKxIVI4YYzKCQKrX7cUWpiJf/n7CWW1yrJ9WcaYwpeU8Cynj419
hYAzwBJB8zbdHhyVd78xpuA5ZfHOWEzgWeixFVgSNiBldGG0DICj4uE7PyEzvx4Q6YQ/ECm+5Cgm0jxl
8Y5lyhsfPw9zpyrzU3zyp8ndRUAbwFDrZViQdlvQ/NQ7kKPvNkYX3GuIKbpPWfrJFP7IFyXu4FQYM1V5
c//3lWV7p/A70JWobfcq4dlfN4Zn3WmYn3y7GseSgYCxulhW7Bb1p+YXb1Ev0uf+H9A3E1Z4VJUaAAAA
AElFTkSuQmCC
</value>
</data>
</root>

View File

@@ -6,7 +6,6 @@ using CefSharp.WinForms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling; using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General; using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using TweetDuck.Resources; using TweetDuck.Resources;
@@ -43,7 +42,7 @@ namespace TweetDuck.Core.Other{
FormBrowser owner = FormManager.TryFind<FormBrowser>(); FormBrowser owner = FormManager.TryFind<FormBrowser>();
if (owner != null){ if (owner != null){
owner.TriggerAnalyticsEvent(AnalyticsFile.Event.OpenGuide); owner.AnalyticsFile.OpenGuide.Trigger();
new FormGuide(url, owner).Show(owner); new FormGuide(url, owner).Show(owner);
} }
} }
@@ -55,7 +54,7 @@ namespace TweetDuck.Core.Other{
private readonly ChromiumWebBrowser browser; private readonly ChromiumWebBrowser browser;
private FormGuide(string url, Form owner){ private FormGuide(string url, FormBrowser owner){
InitializeComponent(); InitializeComponent();
Text = Program.BrandName+" Guide"; Text = Program.BrandName+" Guide";
@@ -66,7 +65,7 @@ namespace TweetDuck.Core.Other{
} }
this.browser = new ChromiumWebBrowser(url){ this.browser = new ChromiumWebBrowser(url){
MenuHandler = new ContextMenuGuide(), MenuHandler = new ContextMenuGuide(owner),
JsDialogHandler = new JavaScriptDialogHandler(), JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler(), LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBrowser() RequestHandler = new RequestHandlerBrowser()

View File

@@ -133,7 +133,7 @@ namespace TweetDuck.Core.Other{
Font = SystemFonts.MessageBoxFont, Font = SystemFonts.MessageBoxFont,
Location = new Point(0, 12), Location = new Point(0, 12),
Size = new Size(BrowserUtils.Scale(88, dpiScale), BrowserUtils.Scale(26, dpiScale)), Size = new Size(BrowserUtils.Scale(88, dpiScale), BrowserUtils.Scale(26, dpiScale)),
TabIndex = buttonCount, TabIndex = 256-buttonCount,
Text = title, Text = title,
UseVisualStyleBackColor = true UseVisualStyleBackColor = true
}; };

View File

@@ -76,7 +76,7 @@ namespace TweetDuck.Core.Other{
} }
private void btnOpenFolder_Click(object sender, EventArgs e){ private void btnOpenFolder_Click(object sender, EventArgs e){
using(Process.Start("explorer.exe", "\""+pluginManager.PathCustomPlugins+"\"")){} using(Process.Start("explorer.exe", '"'+pluginManager.PathCustomPlugins+'"')){}
} }
private void btnReload_Click(object sender, EventArgs e){ private void btnReload_Click(object sender, EventArgs e){

View File

@@ -67,10 +67,13 @@ namespace TweetDuck.Core.Other{
FormClosing -= FormSettings_FormClosing; FormClosing -= FormSettings_FormClosing;
if (dialog.ShowDialog() == DialogResult.OK){ if (dialog.ShowDialog() == DialogResult.OK){
if (!dialog.IsRestarting){
browser.ResumeNotification(); browser.ResumeNotification();
BrowserProcessHandler.UpdatePrefs(); BrowserProcessHandler.UpdatePrefs();
ShouldReloadBrowser = dialog.ShouldReloadBrowser; ShouldReloadBrowser = dialog.ShouldReloadBrowser;
}
Close(); Close();
} }
else{ else{

View File

@@ -26,16 +26,17 @@
this.components = new System.ComponentModel.Container(); this.components = new System.ComponentModel.Container();
this.btnCancel = new System.Windows.Forms.Button(); this.btnCancel = new System.Windows.Forms.Button();
this.btnContinue = new System.Windows.Forms.Button(); this.btnContinue = new System.Windows.Forms.Button();
this.cbConfig = new System.Windows.Forms.CheckBox(); this.cbProgramConfig = new System.Windows.Forms.CheckBox();
this.cbSession = new System.Windows.Forms.CheckBox(); this.cbSession = new System.Windows.Forms.CheckBox();
this.cbPluginData = new System.Windows.Forms.CheckBox(); this.cbPluginData = new System.Windows.Forms.CheckBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.panelExport = new System.Windows.Forms.Panel(); this.cbSystemConfig = new System.Windows.Forms.CheckBox();
this.panelDecision = new System.Windows.Forms.Panel(); this.panelSelection = new System.Windows.Forms.FlowLayoutPanel();
this.radioReset = new System.Windows.Forms.RadioButton(); this.panelDecision = new System.Windows.Forms.FlowLayoutPanel();
this.radioExport = new System.Windows.Forms.RadioButton();
this.radioImport = new System.Windows.Forms.RadioButton(); this.radioImport = new System.Windows.Forms.RadioButton();
this.panelExport.SuspendLayout(); this.radioExport = new System.Windows.Forms.RadioButton();
this.radioReset = new System.Windows.Forms.RadioButton();
this.panelSelection.SuspendLayout();
this.panelDecision.SuspendLayout(); this.panelDecision.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
@@ -56,95 +57,92 @@
this.btnContinue.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnContinue.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnContinue.AutoSize = true; this.btnContinue.AutoSize = true;
this.btnContinue.Enabled = false; this.btnContinue.Enabled = false;
this.btnContinue.Location = new System.Drawing.Point(125, 97); this.btnContinue.Location = new System.Drawing.Point(119, 97);
this.btnContinue.Name = "btnContinue"; this.btnContinue.Name = "btnContinue";
this.btnContinue.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.btnContinue.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnContinue.Size = new System.Drawing.Size(45, 23); this.btnContinue.Size = new System.Drawing.Size(51, 23);
this.btnContinue.TabIndex = 3; this.btnContinue.TabIndex = 3;
this.btnContinue.Text = "Next"; this.btnContinue.Text = "Next";
this.btnContinue.UseVisualStyleBackColor = true; this.btnContinue.UseVisualStyleBackColor = true;
this.btnContinue.Click += new System.EventHandler(this.btnContinue_Click); this.btnContinue.Click += new System.EventHandler(this.btnContinue_Click);
// //
// cbConfig // cbProgramConfig
// //
this.cbConfig.AutoSize = true; this.cbProgramConfig.AutoSize = true;
this.cbConfig.Location = new System.Drawing.Point(0, 3); this.cbProgramConfig.Location = new System.Drawing.Point(3, 3);
this.cbConfig.Name = "cbConfig"; this.cbProgramConfig.Name = "cbProgramConfig";
this.cbConfig.Size = new System.Drawing.Size(104, 17); this.cbProgramConfig.Size = new System.Drawing.Size(104, 17);
this.cbConfig.TabIndex = 0; this.cbProgramConfig.TabIndex = 0;
this.cbConfig.Text = "Program Options"; this.cbProgramConfig.Text = "Program Options";
this.toolTip.SetToolTip(this.cbConfig, "Interface, notification, and update options."); this.toolTip.SetToolTip(this.cbProgramConfig, "Interface, notification, and update options.");
this.cbConfig.UseVisualStyleBackColor = true; this.cbProgramConfig.UseVisualStyleBackColor = true;
this.cbConfig.CheckedChanged += new System.EventHandler(this.cbConfig_CheckedChanged); this.cbProgramConfig.CheckedChanged += new System.EventHandler(this.checkBoxSelection_CheckedChanged);
// //
// cbSession // cbSession
// //
this.cbSession.AutoSize = true; this.cbSession.AutoSize = true;
this.cbSession.Location = new System.Drawing.Point(0, 27); this.cbSession.Location = new System.Drawing.Point(3, 49);
this.cbSession.Name = "cbSession"; this.cbSession.Name = "cbSession";
this.cbSession.Size = new System.Drawing.Size(92, 17); this.cbSession.Size = new System.Drawing.Size(92, 17);
this.cbSession.TabIndex = 1; this.cbSession.TabIndex = 2;
this.cbSession.Text = "Login Session"; this.cbSession.Text = "Login Session";
this.toolTip.SetToolTip(this.cbSession, "A token that allows logging into the\r\ncurrent TweetDeck account."); this.toolTip.SetToolTip(this.cbSession, "A token that allows logging into the\r\ncurrent TweetDeck account.");
this.cbSession.UseVisualStyleBackColor = true; this.cbSession.UseVisualStyleBackColor = true;
this.cbSession.CheckedChanged += new System.EventHandler(this.cbSession_CheckedChanged); this.cbSession.CheckedChanged += new System.EventHandler(this.checkBoxSelection_CheckedChanged);
// //
// cbPluginData // cbPluginData
// //
this.cbPluginData.AutoSize = true; this.cbPluginData.AutoSize = true;
this.cbPluginData.Location = new System.Drawing.Point(0, 51); this.cbPluginData.Location = new System.Drawing.Point(3, 72);
this.cbPluginData.Name = "cbPluginData"; this.cbPluginData.Name = "cbPluginData";
this.cbPluginData.Size = new System.Drawing.Size(81, 17); this.cbPluginData.Size = new System.Drawing.Size(81, 17);
this.cbPluginData.TabIndex = 2; this.cbPluginData.TabIndex = 3;
this.cbPluginData.Text = "Plugin Data"; this.cbPluginData.Text = "Plugin Data";
this.toolTip.SetToolTip(this.cbPluginData, "Data files generated by plugins.\r\nDoes not include the plugins themselves."); this.toolTip.SetToolTip(this.cbPluginData, "Data files generated by plugins.\r\nDoes not include the plugins themselves.");
this.cbPluginData.UseVisualStyleBackColor = true; this.cbPluginData.UseVisualStyleBackColor = true;
this.cbPluginData.CheckedChanged += new System.EventHandler(this.cbPluginData_CheckedChanged); this.cbPluginData.CheckedChanged += new System.EventHandler(this.checkBoxSelection_CheckedChanged);
// //
// panelExport // cbSystemConfig
// //
this.panelExport.Controls.Add(this.cbConfig); this.cbSystemConfig.AutoSize = true;
this.panelExport.Controls.Add(this.cbPluginData); this.cbSystemConfig.Location = new System.Drawing.Point(3, 26);
this.panelExport.Controls.Add(this.cbSession); this.cbSystemConfig.Name = "cbSystemConfig";
this.panelExport.Location = new System.Drawing.Point(12, 12); this.cbSystemConfig.Size = new System.Drawing.Size(99, 17);
this.panelExport.Name = "panelExport"; this.cbSystemConfig.TabIndex = 1;
this.panelExport.Size = new System.Drawing.Size(220, 79); this.cbSystemConfig.Text = "System Options";
this.panelExport.TabIndex = 5; this.toolTip.SetToolTip(this.cbSystemConfig, "Hardware acceleration and cache options.");
this.panelExport.Visible = false; this.cbSystemConfig.UseVisualStyleBackColor = true;
this.cbSystemConfig.CheckedChanged += new System.EventHandler(this.checkBoxSelection_CheckedChanged);
//
// panelSelection
//
this.panelSelection.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelSelection.Controls.Add(this.cbProgramConfig);
this.panelSelection.Controls.Add(this.cbSystemConfig);
this.panelSelection.Controls.Add(this.cbSession);
this.panelSelection.Controls.Add(this.cbPluginData);
this.panelSelection.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.panelSelection.Location = new System.Drawing.Point(12, 12);
this.panelSelection.Name = "panelSelection";
this.panelSelection.Size = new System.Drawing.Size(220, 89);
this.panelSelection.TabIndex = 2;
this.panelSelection.Visible = false;
this.panelSelection.WrapContents = false;
// //
// panelDecision // panelDecision
// //
this.panelDecision.Controls.Add(this.radioReset); this.panelDecision.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
this.panelDecision.Controls.Add(this.radioExport); | System.Windows.Forms.AnchorStyles.Right)));
this.panelDecision.Controls.Add(this.radioImport); this.panelDecision.Controls.Add(this.radioImport);
this.panelDecision.Controls.Add(this.radioExport);
this.panelDecision.Controls.Add(this.radioReset);
this.panelDecision.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.panelDecision.Location = new System.Drawing.Point(12, 12); this.panelDecision.Location = new System.Drawing.Point(12, 12);
this.panelDecision.Name = "panelDecision"; this.panelDecision.Name = "panelDecision";
this.panelDecision.Size = new System.Drawing.Size(220, 79); this.panelDecision.Size = new System.Drawing.Size(220, 71);
this.panelDecision.TabIndex = 6; this.panelDecision.TabIndex = 0;
// this.panelDecision.WrapContents = false;
// radioReset
//
this.radioReset.AutoSize = true;
this.radioReset.Location = new System.Drawing.Point(3, 49);
this.radioReset.Name = "radioReset";
this.radioReset.Size = new System.Drawing.Size(104, 17);
this.radioReset.TabIndex = 2;
this.radioReset.TabStop = true;
this.radioReset.Text = "Restore Defaults";
this.radioReset.UseVisualStyleBackColor = true;
this.radioReset.CheckedChanged += new System.EventHandler(this.radioDecision_CheckedChanged);
//
// radioExport
//
this.radioExport.AutoSize = true;
this.radioExport.Location = new System.Drawing.Point(3, 26);
this.radioExport.Name = "radioExport";
this.radioExport.Size = new System.Drawing.Size(87, 17);
this.radioExport.TabIndex = 1;
this.radioExport.TabStop = true;
this.radioExport.Text = "Export Profile";
this.radioExport.UseVisualStyleBackColor = true;
this.radioExport.CheckedChanged += new System.EventHandler(this.radioDecision_CheckedChanged);
// //
// radioImport // radioImport
// //
@@ -158,25 +156,49 @@
this.radioImport.UseVisualStyleBackColor = true; this.radioImport.UseVisualStyleBackColor = true;
this.radioImport.CheckedChanged += new System.EventHandler(this.radioDecision_CheckedChanged); this.radioImport.CheckedChanged += new System.EventHandler(this.radioDecision_CheckedChanged);
// //
// radioExport
//
this.radioExport.AutoSize = true;
this.radioExport.Location = new System.Drawing.Point(3, 26);
this.radioExport.Name = "radioExport";
this.radioExport.Size = new System.Drawing.Size(87, 17);
this.radioExport.TabIndex = 1;
this.radioExport.TabStop = true;
this.radioExport.Text = "Export Profile";
this.radioExport.UseVisualStyleBackColor = true;
this.radioExport.CheckedChanged += new System.EventHandler(this.radioDecision_CheckedChanged);
//
// radioReset
//
this.radioReset.AutoSize = true;
this.radioReset.Location = new System.Drawing.Point(3, 49);
this.radioReset.Name = "radioReset";
this.radioReset.Size = new System.Drawing.Size(104, 17);
this.radioReset.TabIndex = 2;
this.radioReset.TabStop = true;
this.radioReset.Text = "Restore Defaults";
this.radioReset.UseVisualStyleBackColor = true;
this.radioReset.CheckedChanged += new System.EventHandler(this.radioDecision_CheckedChanged);
//
// DialogSettingsManage // DialogSettingsManage
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(244, 132); this.ClientSize = new System.Drawing.Size(244, 132);
this.Controls.Add(this.panelDecision);
this.Controls.Add(this.panelExport);
this.Controls.Add(this.btnContinue); this.Controls.Add(this.btnContinue);
this.Controls.Add(this.btnCancel); this.Controls.Add(this.btnCancel);
this.Controls.Add(this.panelDecision);
this.Controls.Add(this.panelSelection);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.MaximizeBox = false; this.MaximizeBox = false;
this.MinimizeBox = false; this.MinimizeBox = false;
this.MinimumSize = new System.Drawing.Size(200, 170); this.MinimumSize = new System.Drawing.Size(260, 170);
this.Name = "DialogSettingsManage"; this.Name = "DialogSettingsManage";
this.ShowIcon = false; this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Manage Options"; this.Text = "Manage Options";
this.panelExport.ResumeLayout(false); this.panelSelection.ResumeLayout(false);
this.panelExport.PerformLayout(); this.panelSelection.PerformLayout();
this.panelDecision.ResumeLayout(false); this.panelDecision.ResumeLayout(false);
this.panelDecision.PerformLayout(); this.panelDecision.PerformLayout();
this.ResumeLayout(false); this.ResumeLayout(false);
@@ -188,12 +210,13 @@
private System.Windows.Forms.Button btnCancel; private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.Button btnContinue; private System.Windows.Forms.Button btnContinue;
private System.Windows.Forms.CheckBox cbConfig; private System.Windows.Forms.CheckBox cbProgramConfig;
private System.Windows.Forms.CheckBox cbSystemConfig;
private System.Windows.Forms.CheckBox cbSession; private System.Windows.Forms.CheckBox cbSession;
private System.Windows.Forms.CheckBox cbPluginData; private System.Windows.Forms.CheckBox cbPluginData;
private System.Windows.Forms.ToolTip toolTip; private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.Panel panelExport; private System.Windows.Forms.FlowLayoutPanel panelSelection;
private System.Windows.Forms.Panel panelDecision; private System.Windows.Forms.FlowLayoutPanel panelDecision;
private System.Windows.Forms.RadioButton radioReset; private System.Windows.Forms.RadioButton radioReset;
private System.Windows.Forms.RadioButton radioExport; private System.Windows.Forms.RadioButton radioExport;
private System.Windows.Forms.RadioButton radioImport; private System.Windows.Forms.RadioButton radioImport;

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
@@ -16,15 +17,17 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
set{ set{
// this will call events and SetFlag, which also updates the UI // this will call events and SetFlag, which also updates the UI
cbConfig.Checked = value.HasFlag(ProfileManager.Items.UserConfig); foreach(KeyValuePair<CheckBox, ProfileManager.Items> kvp in checkBoxMap){
cbSession.Checked = value.HasFlag(ProfileManager.Items.Session); kvp.Key.Checked = value.HasFlag(kvp.Value);
cbPluginData.Checked = value.HasFlag(ProfileManager.Items.PluginData); }
} }
} }
public bool IsRestarting { get; private set; }
public bool ShouldReloadBrowser { get; private set; } public bool ShouldReloadBrowser { get; private set; }
private readonly PluginManager plugins; private readonly PluginManager plugins;
private readonly Dictionary<CheckBox, ProfileManager.Items> checkBoxMap = new Dictionary<CheckBox, ProfileManager.Items>(4);
private State currentState; private State currentState;
private ProfileManager importManager; private ProfileManager importManager;
@@ -36,22 +39,20 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
this.plugins = plugins; this.plugins = plugins;
this.currentState = State.Deciding; this.currentState = State.Deciding;
this.checkBoxMap[cbProgramConfig] = ProfileManager.Items.UserConfig;
this.checkBoxMap[cbSystemConfig] = ProfileManager.Items.SystemConfig;
this.checkBoxMap[cbSession] = ProfileManager.Items.Session;
this.checkBoxMap[cbPluginData] = ProfileManager.Items.PluginData;
} }
private void radioDecision_CheckedChanged(object sender, EventArgs e){ private void radioDecision_CheckedChanged(object sender, EventArgs e){
btnContinue.Enabled = true; btnContinue.Enabled = true;
} }
private void cbConfig_CheckedChanged(object sender, EventArgs e){ private void checkBoxSelection_CheckedChanged(object sender, EventArgs e){
SetFlag(ProfileManager.Items.UserConfig, cbConfig.Checked); CheckBox cb = (CheckBox)sender;
} SetFlag(checkBoxMap[cb], cb.Checked);
private void cbSession_CheckedChanged(object sender, EventArgs e){
SetFlag(ProfileManager.Items.Session, cbSession.Checked);
}
private void cbPluginData_CheckedChanged(object sender, EventArgs e){
SetFlag(ProfileManager.Items.PluginData, cbPluginData.Checked);
} }
private void btnContinue_Click(object sender, EventArgs e){ private void btnContinue_Click(object sender, EventArgs e){
@@ -88,9 +89,9 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
Text = "Import Profile"; Text = "Import Profile";
SelectedItems = importManager.FindImportItems(); SelectedItems = importManager.FindImportItems();
cbConfig.Enabled = cbConfig.Checked; foreach(CheckBox cb in checkBoxMap.Keys){
cbSession.Enabled = cbSession.Checked; cb.Enabled = cb.Checked;
cbPluginData.Enabled = cbPluginData.Checked; }
} }
// Export // Export
@@ -99,12 +100,13 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
Text = "Export Profile"; Text = "Export Profile";
btnContinue.Text = "Export Profile"; btnContinue.Text = "Export Profile";
SelectedItems = ProfileManager.Items.All & ~ProfileManager.Items.Session; SelectedItems = ProfileManager.Items.UserConfig | ProfileManager.Items.PluginData;
} }
// Continue... // Continue...
panelDecision.Visible = false; panelDecision.Visible = false;
panelExport.Visible = true; panelSelection.Visible = true;
Height += panelSelection.Height-panelDecision.Height;
break; break;
case State.Reset: case State.Reset:
@@ -131,10 +133,10 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
} }
if (SelectedItems.HasFlag(ProfileManager.Items.Session)){ if (SelectedItems.HasFlag(ProfileManager.Items.Session)){
Program.Restart(Arguments.ArgDeleteCookies); RestartProgram(Arguments.ArgDeleteCookies);
} }
else if (SelectedItems.HasFlag(ProfileManager.Items.SystemConfig)){ else if (SelectedItems.HasFlag(ProfileManager.Items.SystemConfig)){
Program.Restart(); RestartProgram();
} }
else{ else{
ShouldReloadBrowser = true; ShouldReloadBrowser = true;
@@ -152,10 +154,10 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
if (importManager.IsRestarting){ if (importManager.IsRestarting){
if (SelectedItems.HasFlag(ProfileManager.Items.Session)){ if (SelectedItems.HasFlag(ProfileManager.Items.Session)){
Program.Restart(Arguments.ArgImportCookies); RestartProgram(Arguments.ArgImportCookies);
} }
else if (SelectedItems.HasFlag(ProfileManager.Items.SystemConfig)){ else if (SelectedItems.HasFlag(ProfileManager.Items.SystemConfig)){
Program.Restart(); RestartProgram();
} }
} }
else{ else{
@@ -212,11 +214,16 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
btnContinue.Enabled = _selectedItems != ProfileManager.Items.None; btnContinue.Enabled = _selectedItems != ProfileManager.Items.None;
if (currentState == State.Import){ if (currentState == State.Import){
btnContinue.Text = _selectedItems.HasFlag(ProfileManager.Items.Session) ? "Import && Restart" : "Import Profile"; btnContinue.Text = _selectedItems.NeedsRestart() ? "Import && Restart" : "Import Profile";
} }
else if (currentState == State.Reset){ else if (currentState == State.Reset){
btnContinue.Text = _selectedItems.HasFlag(ProfileManager.Items.Session) ? "Restore && Restart" : "Restore Defaults"; btnContinue.Text = _selectedItems.NeedsRestart() ? "Restore && Restart" : "Restore Defaults";
} }
} }
private void RestartProgram(params string[] extraArgs){
IsRestarting = true;
Program.Restart(extraArgs);
}
} }
} }

View File

@@ -61,6 +61,7 @@
this.labelDataCollectionLink.TabIndex = 1; this.labelDataCollectionLink.TabIndex = 1;
this.labelDataCollectionLink.TabStop = true; this.labelDataCollectionLink.TabStop = true;
this.labelDataCollectionLink.Text = "(learn more)"; this.labelDataCollectionLink.Text = "(learn more)";
this.labelDataCollectionLink.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.labelDataCollectionLink.UseCompatibleTextRendering = true; this.labelDataCollectionLink.UseCompatibleTextRendering = true;
// //
// checkDataCollection // checkDataCollection

View File

@@ -9,14 +9,10 @@ using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General; using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events;
using TweetDuck.Resources; using TweetDuck.Resources;
using TweetDuck.Updates;
namespace TweetDuck.Core{ namespace TweetDuck.Core{
sealed class TweetDeckBrowser : IDisposable{ sealed class TweetDeckBrowser : ITweetDeckBrowser, IDisposable{
public bool Ready { get; private set; } public bool Ready { get; private set; }
public bool Enabled{ public bool Enabled{
@@ -32,14 +28,11 @@ namespace TweetDuck.Core{
} }
} }
public event EventHandler PageLoaded;
private readonly ChromiumWebBrowser browser; private readonly ChromiumWebBrowser browser;
private readonly PluginManager plugins;
private string prevSoundNotificationPath = null; private string prevSoundNotificationPath = null;
public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridge bridge){ public TweetDeckBrowser(FormBrowser owner, TweetDeckBridge bridge){
this.browser = new ChromiumWebBrowser(TwitterUtils.TweetDeckURL){ this.browser = new ChromiumWebBrowser(TwitterUtils.TweetDeckURL){
DialogHandler = new FileDialogHandler(), DialogHandler = new FileDialogHandler(),
DragHandler = new DragHandlerBrowser(), DragHandler = new DragHandlerBrowser(),
@@ -60,16 +53,15 @@ namespace TweetDuck.Core{
this.browser.LoadError += browser_LoadError; this.browser.LoadError += browser_LoadError;
this.browser.RegisterAsyncJsObject("$TD", bridge); this.browser.RegisterAsyncJsObject("$TD", bridge);
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
this.browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb(); this.browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb();
this.browser.Dock = DockStyle.None; this.browser.Dock = DockStyle.None;
this.browser.Location = ControlExtensions.InvisibleLocation; this.browser.Location = ControlExtensions.InvisibleLocation;
owner.Controls.Add(browser); this.browser.SetupResourceHandler(TweetNotification.AppLogo);
this.browser.SetupResourceHandler(TwitterUtils.LoadingSpinner);
this.plugins = plugins; owner.Controls.Add(browser);
this.plugins.PluginChangedState += plugins_PluginChangedState;
Program.UserConfig.MuteToggled += UserConfig_MuteToggled; Program.UserConfig.MuteToggled += UserConfig_MuteToggled;
Program.UserConfig.ZoomLevelChanged += UserConfig_ZoomLevelChanged; Program.UserConfig.ZoomLevelChanged += UserConfig_ZoomLevelChanged;
@@ -91,8 +83,6 @@ namespace TweetDuck.Core{
} }
public void Dispose(){ public void Dispose(){
plugins.PluginChangedState -= plugins_PluginChangedState;
Program.UserConfig.MuteToggled -= UserConfig_MuteToggled; Program.UserConfig.MuteToggled -= UserConfig_MuteToggled;
Program.UserConfig.ZoomLevelChanged -= UserConfig_ZoomLevelChanged; Program.UserConfig.ZoomLevelChanged -= UserConfig_ZoomLevelChanged;
Program.UserConfig.SoundNotificationChanged -= UserConfig_SoundNotificationInfoChanged; Program.UserConfig.SoundNotificationChanged -= UserConfig_SoundNotificationInfoChanged;
@@ -100,6 +90,24 @@ namespace TweetDuck.Core{
browser.Dispose(); browser.Dispose();
} }
void ITweetDeckBrowser.RegisterBridge(string name, object obj){
browser.RegisterAsyncJsObject(name, obj);
}
void ITweetDeckBrowser.OnFrameLoaded(Action<IFrame> callback){
browser.FrameLoadEnd += (sender, args) => {
IFrame frame = args.Frame;
if (frame.IsMain && TwitterUtils.IsTweetDeckWebsite(frame)){
callback(frame);
}
};
}
void ITweetDeckBrowser.ExecuteFunction(string name, params object[] args){
browser.ExecuteScriptAsync(name, args);
}
// event handlers // event handlers
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){ private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
@@ -126,24 +134,23 @@ namespace TweetDuck.Core{
} }
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain && TwitterUtils.IsTweetDeckWebsite(e.Frame)){ IFrame frame = e.Frame;
e.Frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorFix);
if (frame.IsMain && TwitterUtils.IsTweetDeckWebsite(frame)){
frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride);
UpdateProperties(); UpdateProperties();
TweetDeckBridge.RestoreSessionData(e.Frame); TweetDeckBridge.RestoreSessionData(frame);
ScriptLoader.ExecuteFile(e.Frame, "code.js"); ScriptLoader.ExecuteFile(frame, "code.js");
InjectBrowserCSS(); InjectBrowserCSS();
ReinjectCustomCSS(Program.UserConfig.CustomBrowserCSS); ReinjectCustomCSS(Program.UserConfig.CustomBrowserCSS);
UserConfig_SoundNotificationInfoChanged(null, EventArgs.Empty); UserConfig_SoundNotificationInfoChanged(null, EventArgs.Empty);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser);
TweetDeckBridge.ResetStaticProperties(); TweetDeckBridge.ResetStaticProperties();
if (Program.UserConfig.FirstRun){ if (Program.UserConfig.FirstRun){
ScriptLoader.ExecuteFile(e.Frame, "introduction.js"); ScriptLoader.ExecuteFile(frame, "introduction.js");
} }
PageLoaded?.Invoke(this, EventArgs.Empty);
} }
} }
@@ -161,10 +168,6 @@ namespace TweetDuck.Core{
} }
} }
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
browser.ExecuteScriptAsync("TDPF_setPluginState", e.Plugin, e.IsEnabled);
}
private void UserConfig_MuteToggled(object sender, EventArgs e){ private void UserConfig_MuteToggled(object sender, EventArgs e){
UpdateProperties(); UpdateProperties();
} }
@@ -178,16 +181,7 @@ namespace TweetDuck.Core{
bool hasCustomSound = Program.UserConfig.IsCustomSoundNotificationSet; bool hasCustomSound = Program.UserConfig.IsCustomSoundNotificationSet;
if (prevSoundNotificationPath != Program.UserConfig.NotificationSoundPath){ if (prevSoundNotificationPath != Program.UserConfig.NotificationSoundPath){
DefaultResourceHandlerFactory handlerFactory = browser.GetHandlerFactory(); browser.SetupResourceHandler(soundUrl, hasCustomSound ? SoundNotification.CreateFileHandler(Program.UserConfig.NotificationSoundPath) : null);
IResourceHandler resourceHandler = hasCustomSound ? SoundNotification.CreateFileHandler(Program.UserConfig.NotificationSoundPath) : null;
if (resourceHandler != null){
handlerFactory.RegisterHandler(soundUrl, resourceHandler);
}
else{
handlerFactory.UnregisterHandler(soundUrl);
}
prevSoundNotificationPath = Program.UserConfig.NotificationSoundPath; prevSoundNotificationPath = Program.UserConfig.NotificationSoundPath;
} }
@@ -196,10 +190,6 @@ namespace TweetDuck.Core{
// external handling // external handling
public UpdateHandler CreateUpdateHandler(UpdaterSettings settings){
return new UpdateHandler(browser, settings);
}
public void HideVideoOverlay(bool focus){ public void HideVideoOverlay(bool focus){
if (focus){ if (focus){
browser.GetBrowser().GetHost().SendFocusEvent(true); browser.GetBrowser().GetHost().SendFocusEvent(true);

View File

@@ -7,6 +7,7 @@ using System.Net;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp.WinForms; using CefSharp.WinForms;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Data;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Core.Utils{
static class BrowserUtils{ static class BrowserUtils{
@@ -38,8 +39,19 @@ namespace TweetDuck.Core.Utils{
return (ChromiumWebBrowser)browserControl; return (ChromiumWebBrowser)browserControl;
} }
public static DefaultResourceHandlerFactory GetHandlerFactory(this ChromiumWebBrowser browser){ public static void SetupResourceHandler(this ChromiumWebBrowser browser, string url, IResourceHandler handler){
return (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory; 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);
} }
private const string TwitterTrackingUrl = "t.co"; private const string TwitterTrackingUrl = "t.co";
@@ -86,10 +98,27 @@ namespace TweetDuck.Core.Utils{
break; break;
case UrlCheckResult.Tracking: case UrlCheckResult.Tracking:
if (FormMessage.Warning("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n"+url, FormMessage.Yes, FormMessage.No)){ if (Program.UserConfig.IgnoreTrackingUrlWarning){
goto case UrlCheckResult.Fine; goto case UrlCheckResult.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)){
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel | ControlType.Focused);
form.AddButton(FormMessage.Yes, DialogResult.Yes, ControlType.Accept);
form.AddButton("Always Visit", DialogResult.Ignore);
DialogResult result = form.ShowDialog();
if (result == DialogResult.Ignore){
Program.UserConfig.IgnoreTrackingUrlWarning = true;
Program.UserConfig.Save();
}
if (result == DialogResult.Ignore || result == DialogResult.Yes){
goto case UrlCheckResult.Fine;
}
}
break; break;
case UrlCheckResult.Invalid: case UrlCheckResult.Invalid:

View File

@@ -6,13 +6,16 @@ using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Data;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Core.Utils{
static class TwitterUtils{ static class TwitterUtils{
public const string TweetDeckURL = "https://tweetdeck.twitter.com"; public const string TweetDeckURL = "https://tweetdeck.twitter.com";
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153); public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
public const string BackgroundColorFix = "let e=document.createElement('style');document.head.appendChild(e);e.innerHTML='body::before{background:#1c6399!important}'"; public const string BackgroundColorOverride = "let e=document.createElement('style');document.head.appendChild(e);e.innerHTML='body,body::before{background:#1c6399!important}'";
public static readonly ResourceLink LoadingSpinner = new ResourceLink("https://ton.twimg.com/tduck/spinner", ResourceHandler.FromByteArray(Properties.Resources.spinner, "image/apng"));
private static readonly Lazy<Regex> RegexAccountLazy = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/(?!signup$|tos$|privacy$)([^/]+)/?$", RegexOptions.Compiled), false); private static readonly Lazy<Regex> RegexAccountLazy = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/(?!signup$|tos$|privacy$)([^/]+)/?$", RegexOptions.Compiled), false);
public static Regex RegexAccount => RegexAccountLazy.Value; public static Regex RegexAccount => RegexAccountLazy.Value;

13
Data/ResourceLink.cs Normal file
View File

@@ -0,0 +1,13 @@
using CefSharp;
namespace TweetDuck.Data{
sealed class ResourceLink{
public string Url { get; }
public IResourceHandler Handler { get; }
public ResourceLink(string url, IResourceHandler handler){
this.Url = url;
this.Handler = handler;
}
}
}

View File

@@ -52,9 +52,6 @@ namespace TweetDuck.Data.Serialization{
private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter(); private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter();
public delegate void HandleUnknownPropertiesHandler(T obj, Dictionary<string, string> data);
public HandleUnknownPropertiesHandler HandleUnknownProperties { get; set; }
private readonly Dictionary<string, PropertyInfo> props; private readonly Dictionary<string, PropertyInfo> props;
private readonly Dictionary<Type, ITypeConverter> converters; private readonly Dictionary<Type, ITypeConverter> converters;
@@ -93,18 +90,38 @@ namespace TweetDuck.Data.Serialization{
} }
public void Read(string file, T obj){ public void Read(string file, T obj){
Dictionary<string, string> unknownProperties = new Dictionary<string, string>(4); string contents;
using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))){ using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))){
switch(reader.Peek()){ contents = UnescapeStream(reader);
case -1: }
if (string.IsNullOrWhiteSpace(contents)){
throw new FormatException("File is empty."); throw new FormatException("File is empty.");
case 0: }
case 1: else if (contents[0] <= (char)1){
throw new FormatException("Input appears to be a binary file."); throw new FormatException("Input appears to be a binary file.");
} }
foreach(string line in UnescapeStream(reader).Split(new string[]{ NewLineReal }, StringSplitOptions.RemoveEmptyEntries)){ int currentPos = 0;
do{
string line;
int nextPos = contents.IndexOf(NewLineReal, currentPos);
if (nextPos == -1){
line = contents.Substring(currentPos);
currentPos = -1;
if (string.IsNullOrEmpty(line)){
break;
}
}
else{
line = contents.Substring(currentPos, nextPos-currentPos);
currentPos = nextPos+NewLineReal.Length;
}
int space = line.IndexOf(' '); int space = line.IndexOf(' ');
if (space == -1){ if (space == -1){
@@ -115,7 +132,7 @@ namespace TweetDuck.Data.Serialization{
string value = UnescapeLine(line.Substring(space+1)); string value = UnescapeLine(line.Substring(space+1));
if (props.TryGetValue(property, out PropertyInfo info)){ if (props.TryGetValue(property, out PropertyInfo info)){
if (!converters.TryGetValue(info.PropertyType, out ITypeConverter serializer)) { if (!converters.TryGetValue(info.PropertyType, out ITypeConverter serializer)){
serializer = BasicSerializerObj; serializer = BasicSerializerObj;
} }
@@ -126,19 +143,7 @@ namespace TweetDuck.Data.Serialization{
throw new SerializationException($"Invalid file format, cannot convert value: {value} (property: {property})"); throw new SerializationException($"Invalid file format, cannot convert value: {value} (property: {property})");
} }
} }
else{ }while(currentPos != -1);
unknownProperties[property] = value;
}
}
}
if (unknownProperties.Count > 0){
HandleUnknownProperties?.Invoke(obj, unknownProperties);
if (unknownProperties.Count > 0){
throw new SerializationException($"Invalid file format, unknown properties: {string.Join(", ", unknownProperties.Keys)}");
}
}
} }
public void ReadIfExists(string file, T obj){ public void ReadIfExists(string file, T obj){
@@ -148,14 +153,6 @@ namespace TweetDuck.Data.Serialization{
}catch(DirectoryNotFoundException){} }catch(DirectoryNotFoundException){}
} }
public static HandleUnknownPropertiesHandler IgnoreProperties(params string[] properties){
return (obj, data) => {
foreach(string property in properties){
data.Remove(property);
}
};
}
private sealed class BasicTypeConverter : ITypeConverter{ private sealed class BasicTypeConverter : ITypeConverter{
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){ bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
switch(Type.GetTypeCode(type)){ switch(Type.GetTypeCode(type)){

View File

@@ -31,7 +31,7 @@
this.flowLayoutInfo = new System.Windows.Forms.FlowLayoutPanel(); this.flowLayoutInfo = new System.Windows.Forms.FlowLayoutPanel();
this.labelWebsite = new System.Windows.Forms.Label(); this.labelWebsite = new System.Windows.Forms.Label();
this.labelVersion = new System.Windows.Forms.Label(); this.labelVersion = new System.Windows.Forms.Label();
this.btnOpenConfig = new System.Windows.Forms.Button(); this.btnConfigure = new System.Windows.Forms.Button();
this.labelType = new TweetDuck.Core.Controls.LabelVertical(); this.labelType = new TweetDuck.Core.Controls.LabelVertical();
this.panelDescription.SuspendLayout(); this.panelDescription.SuspendLayout();
this.flowLayoutInfo.SuspendLayout(); this.flowLayoutInfo.SuspendLayout();
@@ -135,16 +135,16 @@
this.labelVersion.TextAlign = System.Drawing.ContentAlignment.TopRight; this.labelVersion.TextAlign = System.Drawing.ContentAlignment.TopRight;
this.labelVersion.UseMnemonic = false; this.labelVersion.UseMnemonic = false;
// //
// btnOpenConfig // btnConfigure
// //
this.btnOpenConfig.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnConfigure.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnOpenConfig.Location = new System.Drawing.Point(382, 80); this.btnConfigure.Location = new System.Drawing.Point(382, 80);
this.btnOpenConfig.Name = "btnOpenConfig"; this.btnConfigure.Name = "btnConfigure";
this.btnOpenConfig.Size = new System.Drawing.Size(68, 23); this.btnConfigure.Size = new System.Drawing.Size(68, 23);
this.btnOpenConfig.TabIndex = 4; this.btnConfigure.TabIndex = 4;
this.btnOpenConfig.Text = "Configure"; this.btnConfigure.Text = "Configure";
this.btnOpenConfig.UseVisualStyleBackColor = true; this.btnConfigure.UseVisualStyleBackColor = true;
this.btnOpenConfig.Click += new System.EventHandler(this.btnOpenConfig_Click); this.btnConfigure.Click += new System.EventHandler(this.btnConfigure_Click);
// //
// labelType // labelType
// //
@@ -163,7 +163,7 @@
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.labelType); this.Controls.Add(this.labelType);
this.Controls.Add(this.btnOpenConfig); this.Controls.Add(this.btnConfigure);
this.Controls.Add(this.flowLayoutInfo); this.Controls.Add(this.flowLayoutInfo);
this.Controls.Add(this.panelDescription); this.Controls.Add(this.panelDescription);
this.Controls.Add(this.labelName); this.Controls.Add(this.labelName);
@@ -194,7 +194,7 @@
private System.Windows.Forms.FlowLayoutPanel flowLayoutInfo; private System.Windows.Forms.FlowLayoutPanel flowLayoutInfo;
private System.Windows.Forms.Label labelWebsite; private System.Windows.Forms.Label labelWebsite;
private System.Windows.Forms.Label labelVersion; private System.Windows.Forms.Label labelVersion;
private System.Windows.Forms.Button btnOpenConfig; private System.Windows.Forms.Button btnConfigure;
private Core.Controls.LabelVertical labelType; private Core.Controls.LabelVertical labelType;
} }
} }

View File

@@ -1,7 +1,5 @@
using System; using System;
using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
@@ -57,8 +55,9 @@ namespace TweetDuck.Plugins.Controls{
} }
} }
private void btnOpenConfig_Click(object sender, EventArgs e){ private void btnConfigure_Click(object sender, EventArgs e){
using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath.Replace('/', '\\')+"\"")){} pluginManager.ConfigurePlugin(plugin);
ParentForm?.Close();
} }
private void btnToggleState_Click(object sender, EventArgs e){ private void btnToggleState_Click(object sender, EventArgs e){
@@ -87,14 +86,13 @@ namespace TweetDuck.Plugins.Controls{
labelName.ForeColor = textColor; labelName.ForeColor = textColor;
labelDescription.ForeColor = textColor; labelDescription.ForeColor = textColor;
btnToggleState.Text = isEnabled ? "Disable" : "Enable"; btnToggleState.Text = isEnabled ? "Disable" : "Enable";
btnOpenConfig.Visible = plugin.HasConfig; btnConfigure.Visible = isEnabled && pluginManager.IsPluginConfigurable(plugin);
btnOpenConfig.Enabled = btnOpenConfig.Visible && File.Exists(plugin.ConfigPath);
} }
else{ else{
labelName.ForeColor = Color.DarkRed; labelName.ForeColor = Color.DarkRed;
labelDescription.ForeColor = Color.DarkRed; labelDescription.ForeColor = Color.DarkRed;
btnToggleState.Visible = false; btnToggleState.Visible = false;
btnOpenConfig.Visible = false; btnConfigure.Visible = false;
} }
} }
} }

View File

@@ -11,5 +11,13 @@
default: return "unknown/"; default: return "unknown/";
} }
} }
public static string GetIdentifierPrefixShort(this PluginGroup group){
switch(group){
case PluginGroup.Official: return "o/";
case PluginGroup.Custom: return "c/";
default: return "?/";
}
}
} }
} }

View File

@@ -15,14 +15,15 @@ namespace TweetDuck.Plugins{
private readonly PluginManager manager; private readonly PluginManager manager;
private readonly TwoKeyDictionary<int, string, string> fileCache = new TwoKeyDictionary<int, string, string>(4, 2); private readonly TwoKeyDictionary<int, string, string> fileCache = new TwoKeyDictionary<int, string, string>(4, 2);
private readonly TwoKeyDictionary<int, string, InjectedHTML> notificationInjections = new TwoKeyDictionary<int,string,InjectedHTML>(4, 1); private readonly TwoKeyDictionary<int, string, InjectedHTML> notificationInjections = new TwoKeyDictionary<int, string, InjectedHTML>(4, 1);
public IEnumerable<InjectedHTML> NotificationInjections => notificationInjections.InnerValues; public IEnumerable<InjectedHTML> NotificationInjections => notificationInjections.InnerValues;
public HashSet<Plugin> WithConfigureFunction { get; } = new HashSet<Plugin>();
public PluginBridge(PluginManager manager){ public PluginBridge(PluginManager manager){
this.manager = manager; this.manager = manager;
this.manager.Reloaded += manager_Reloaded; this.manager.Reloaded += manager_Reloaded;
this.manager.PluginChangedState += manager_PluginChangedState; this.manager.Config.PluginChangedState += Config_PluginChangedState;
} }
// Event handlers // Event handlers
@@ -31,7 +32,7 @@ namespace TweetDuck.Plugins{
fileCache.Clear(); fileCache.Clear();
} }
private void manager_PluginChangedState(object sender, PluginChangedStateEventArgs e){ private void Config_PluginChangedState(object sender, PluginChangedStateEventArgs e){
if (!e.IsEnabled){ if (!e.IsEnabled){
int token = manager.GetTokenFromPlugin(e.Plugin); int token = manager.GetTokenFromPlugin(e.Plugin);
@@ -114,5 +115,13 @@ namespace TweetDuck.Plugins{
public void InjectIntoNotificationsAfter(int token, string key, string search, string html){ public void InjectIntoNotificationsAfter(int token, string key, string search, string html){
notificationInjections[token, key] = new InjectedHTML(InjectedHTML.Position.After, search, html); notificationInjections[token, key] = new InjectedHTML(InjectedHTML.Position.After, search, html);
} }
public void SetConfigurable(int token){
Plugin plugin = manager.GetPluginFromToken(token);
if (plugin != null){
WithConfigureFunction.Add(plugin);
}
}
} }
} }

View File

@@ -6,7 +6,7 @@ using TweetDuck.Plugins.Events;
namespace TweetDuck.Plugins{ namespace TweetDuck.Plugins{
sealed class PluginConfig{ sealed class PluginConfig{
public event EventHandler<PluginChangedStateEventArgs> InternalPluginChangedState; // should only be accessed from PluginManager public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
public IEnumerable<string> DisabledPlugins => disabled; public IEnumerable<string> DisabledPlugins => disabled;
public bool AnyDisabled => disabled.Count > 0; public bool AnyDisabled => disabled.Count > 0;
@@ -20,7 +20,7 @@ namespace TweetDuck.Plugins{
public void SetEnabled(Plugin plugin, bool enabled){ public void SetEnabled(Plugin plugin, bool enabled){
if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))){ if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))){
InternalPluginChangedState?.Invoke(this, new PluginChangedStateEventArgs(plugin, enabled)); PluginChangedState?.Invoke(this, new PluginChangedStateEventArgs(plugin, enabled));
} }
} }

View File

@@ -1,8 +1,11 @@
using CefSharp; using CefSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using TweetDuck.Core;
using TweetDuck.Data;
using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events; using TweetDuck.Plugins.Events;
using TweetDuck.Resources; using TweetDuck.Resources;
@@ -19,33 +22,45 @@ namespace TweetDuck.Plugins{
public string PathCustomPlugins => Path.Combine(rootPath, "user"); public string PathCustomPlugins => Path.Combine(rootPath, "user");
public IEnumerable<Plugin> Plugins => plugins; public IEnumerable<Plugin> Plugins => plugins;
public IEnumerable<InjectedHTML> NotificationInjections => bridge.NotificationInjections;
public PluginConfig Config { get; } public PluginConfig Config { get; }
public PluginBridge Bridge { get; }
public event EventHandler<PluginErrorEventArgs> Reloaded; public event EventHandler<PluginErrorEventArgs> Reloaded;
public event EventHandler<PluginErrorEventArgs> Executed; public event EventHandler<PluginErrorEventArgs> Executed;
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
private readonly string rootPath; private readonly string rootPath;
private readonly string configPath; private readonly string configPath;
private readonly PluginBridge bridge;
private readonly HashSet<Plugin> plugins = new HashSet<Plugin>(); private readonly HashSet<Plugin> plugins = new HashSet<Plugin>();
private readonly Dictionary<int, Plugin> tokens = new Dictionary<int, Plugin>(); private readonly Dictionary<int, Plugin> tokens = new Dictionary<int, Plugin>();
private readonly Random rand = new Random(); private readonly Random rand = new Random();
private ITweetDeckBrowser mainBrowser;
public PluginManager(string rootPath, string configPath){ public PluginManager(string rootPath, string configPath){
this.rootPath = rootPath; this.rootPath = rootPath;
this.configPath = configPath; this.configPath = configPath;
this.Config = new PluginConfig(); this.Config = new PluginConfig();
this.Bridge = new PluginBridge(this); this.bridge = new PluginBridge(this);
Config.Load(configPath); Config.Load(configPath);
Config.InternalPluginChangedState += Config_InternalPluginChangedState; Config.PluginChangedState += Config_PluginChangedState;
} }
private void Config_InternalPluginChangedState(object sender, PluginChangedStateEventArgs e){ public void Register(ITweetDeckBrowser browser, PluginEnvironment environment, bool asMainBrowser = false){
PluginChangedState?.Invoke(this, e); browser.OnFrameLoaded(frame => ExecutePlugins(frame, environment));
browser.RegisterBridge("$TDP", bridge);
if (asMainBrowser){
mainBrowser = browser;
}
}
private void Config_PluginChangedState(object sender, PluginChangedStateEventArgs e){
mainBrowser?.ExecuteFunction("TDPF_setPluginState", e.Plugin, e.IsEnabled);
Config.Save(configPath); Config.Save(configPath);
} }
@@ -57,6 +72,24 @@ namespace TweetDuck.Plugins{
return plugins.Any(plugin => plugin.Environments.HasFlag(environment)); return plugins.Any(plugin => plugin.Environments.HasFlag(environment));
} }
public bool IsPluginConfigurable(Plugin plugin){
return plugin.HasConfig || bridge.WithConfigureFunction.Contains(plugin);
}
public void ConfigurePlugin(Plugin plugin){
if (bridge.WithConfigureFunction.Contains(plugin)){
mainBrowser?.ExecuteFunction("TDPF_configurePlugin", plugin);
}
else if (plugin.HasConfig){
if (File.Exists(plugin.ConfigPath)){
using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath.Replace('/', '\\')+"\"")){}
}
else{
using(Process.Start("explorer.exe", '"'+plugin.GetPluginFolder(PluginFolder.Data).Replace('/', '\\')+'"')){}
}
}
}
public int GetTokenFromPlugin(Plugin plugin){ public int GetTokenFromPlugin(Plugin plugin){
foreach(KeyValuePair<int, Plugin> kvp in tokens){ foreach(KeyValuePair<int, Plugin> kvp in tokens){
if (kvp.Value.Equals(plugin)){ if (kvp.Value.Equals(plugin)){
@@ -115,7 +148,7 @@ namespace TweetDuck.Plugins{
Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors)); Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors));
} }
public void ExecutePlugins(IFrame frame, PluginEnvironment environment){ private void ExecutePlugins(IFrame frame, PluginEnvironment environment){
if (!HasAnyPlugin(environment)){ if (!HasAnyPlugin(environment)){
return; return;
} }

View File

@@ -20,7 +20,7 @@ namespace TweetDuck{
public const string BrandName = "TweetDuck"; public const string BrandName = "TweetDuck";
public const string Website = "https://tweetduck.chylex.com"; public const string Website = "https://tweetduck.chylex.com";
public const string VersionTag = "1.12.3"; public const string VersionTag = "1.13.1";
public static readonly bool IsPortable = File.Exists("makeportable"); public static readonly bool IsPortable = File.Exists("makeportable");
@@ -129,7 +129,9 @@ namespace TweetDuck{
} }
BrowserCache.RefreshTimer(); BrowserCache.RefreshTimer();
CefSharpSettings.WcfEnabled = false; CefSharpSettings.WcfEnabled = false;
CefSharpSettings.LegacyJavascriptBindingEnabled = true;
CefSettings settings = new CefSettings{ CefSettings settings = new CefSettings{
UserAgent = BrowserUtils.HeaderUserAgent, UserAgent = BrowserUtils.HeaderUserAgent,

View File

@@ -19,7 +19,7 @@ namespace TweetDuck.Properties {
// class via a tool like ResGen or Visual Studio. // class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen // To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project. // with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources { internal class Resources {
@@ -60,6 +60,16 @@ namespace TweetDuck.Properties {
} }
} }
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] avatar {
get {
object obj = ResourceManager.GetObject("avatar", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary> /// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary> /// </summary>
@@ -89,5 +99,15 @@ namespace TweetDuck.Properties {
return ((System.Drawing.Icon)(obj)); return ((System.Drawing.Icon)(obj));
} }
} }
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] spinner {
get {
object obj = ResourceManager.GetObject("spinner", resourceCulture);
return ((byte[])(obj));
}
}
} }
} }

View File

@@ -118,6 +118,9 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="avatar" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\avatar.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="icon" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="icon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> <value>..\Resources\icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data> </data>
@@ -127,4 +130,7 @@
<data name="icon_tray_new" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="icon_tray_new" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\icon-tray-new.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> <value>..\Resources\icon-tray-new.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data> </data>
<data name="spinner" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\spinner.apng;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root> </root>

View File

@@ -1,6 +1,6 @@
# Support # Support
[Follow the developer](https://twitter.com/chylexmc) &nbsp;|&nbsp; [Support via PayPal](https://paypal.me/chylex) &nbsp;|&nbsp; [Support via Patreon](https://www.patreon.com/chylex) [Follow TweetDuck on Twitter](https://twitter.com/TryMyAwesomeApp) &nbsp;|&nbsp; [Support via PayPal](https://paypal.me/chylex) &nbsp;|&nbsp; [Support via Patreon](https://www.patreon.com/chylex)
# Build Instructions # Build Instructions
@@ -12,10 +12,9 @@ The program was built using Visual Studio 2017. Before opening the solution, ple
* **Desktop development with C++** * **Desktop development with C++**
* VC++ 2017 v141 toolset * VC++ 2017 v141 toolset
After opening the solution, download the following NuGet packages by right-clicking on the solution and selecting **Restore NuGet Packages**, or manually running these commands in the **Package Manager Console**: After opening the solution, download the following NuGet packages by right-clicking on the solution and selecting **Restore NuGet Packages**, or manually running this command in the **Package Manager Console**:
``` ```
PM> Install-Package CefSharp.WinForms -Version 63.0.0-pre01 -Source https://www.myget.org/F/cefsharp/api/v3/index.json PM> Install-Package CefSharp.WinForms -Version 64.0.0-CI2508 -Source https://www.myget.org/F/cefsharp/api/v3/index.json
PM> Install-Package Microsoft.VC120.CRT.JetBrains
``` ```
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. 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.

View File

@@ -42,6 +42,14 @@ enabled(){
col.model.setHasSound(prevSound); col.model.setHasSound(prevSound);
}, 1); }, 1);
} }
// ========================
// D key - trigger debugger
// ========================
else if (e.keyCode === 68){
debugger;
}
} }
}; };
} }

View File

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

View File

@@ -7,6 +7,7 @@ enabled(){
this.defaultConfig = { this.defaultConfig = {
_theme: "light", _theme: "light",
themeOverride: false,
columnWidth: "310px", columnWidth: "310px",
fontSize: "12px", fontSize: "12px",
hideTweetActions: true, hideTweetActions: true,
@@ -22,6 +23,8 @@ enabled(){
this.firstTimeLoad = null; this.firstTimeLoad = null;
var me = this;
// modal dialog loading // modal dialog loading
$TDP.readFileRoot(this.$token, "modal.html").then(contents => { $TDP.readFileRoot(this.$token, "modal.html").then(contents => {
this.htmlModal = contents; this.htmlModal = contents;
@@ -125,7 +128,7 @@ enabled(){
let itemEditDesign = $('<li class="is-selectable"><a href="#" data-action>Edit layout &amp; design</a></li>'); let itemEditDesign = $('<li class="is-selectable"><a href="#" data-action>Edit layout &amp; design</a></li>');
itemEditDesign.insertAfter(itemTD); itemEditDesign.insertAfter(itemTD);
itemEditDesign.on("click", "a", this.openEditDesignDialog); itemEditDesign.on("click", "a", this.configure.bind(this));
itemEditDesign.hover(function(){ itemEditDesign.hover(function(){
$(this).addClass("is-selected"); $(this).addClass("is-selected");
@@ -136,8 +139,6 @@ enabled(){
}; };
// modal dialog setup // modal dialog setup
var me = this;
var updateKey = function(key, value){ var updateKey = function(key, value){
me.config[key] = value; me.config[key] = value;
@@ -147,7 +148,7 @@ enabled(){
}, 1); // delays the slight lag caused by saving and reinjection }, 1); // delays the slight lag caused by saving and reinjection
}; };
var customDesignModal = TD.components.BaseModal.extend(function(){ this.customDesignModal = TD.components.BaseModal.extend(function(){
let modal = $("#td-design-plugin-modal"); let modal = $("#td-design-plugin-modal");
this.setAndShowContainer(modal, false); this.setAndShowContainer(modal, false);
@@ -237,13 +238,31 @@ enabled(){
}); });
// THEMES // THEMES
modal.find("[data-td-theme='"+TD.settings.getTheme()+"']").prop("checked", true); let selectedTheme = TD.settings.getTheme();
if (selectedTheme === "dark" && me.config.themeOverride === "black"){
selectedTheme = me.config.themeOverride;
}
modal.find("[data-td-theme='"+selectedTheme+"']").prop("checked", true);
modal.find("[data-td-theme]").change(function(){ modal.find("[data-td-theme]").change(function(){
me.config._theme = $(this).attr("data-td-theme"); let theme = $(this).attr("data-td-theme");
me.config._theme = theme;
if (theme === "black"){
me.config.themeOverride = theme;
theme = "dark";
}
else{
me.config.themeOverride = false;
}
setTimeout(function(){ setTimeout(function(){
if (theme != TD.settings.getTheme()){
$(document).trigger("uiToggleTheme"); $(document).trigger("uiToggleTheme");
}
me.saveConfig(); me.saveConfig();
me.reinjectAll(); me.reinjectAll();
}, 1); }, 1);
@@ -261,8 +280,6 @@ enabled(){
} }
}); });
this.openEditDesignDialog = () => new customDesignModal();
// animation optimization // animation optimization
this.optimizations = null; this.optimizations = null;
this.optimizationTimer = null; this.optimizationTimer = null;
@@ -335,6 +352,14 @@ enabled(){
this.css = window.TDPF_createCustomStyle(this); this.css = window.TDPF_createCustomStyle(this);
if (this.theme){
this.theme.remove();
}
if (this.config.themeOverride){
this.theme = window.TDPF_createCustomStyle(this);
}
if (this.icons){ if (this.icons){
document.head.removeChild(this.icons); document.head.removeChild(this.icons);
this.icons = null; this.icons = null;
@@ -359,11 +384,17 @@ enabled(){
this.css.insert("html[data-td-font] { font-size: "+this.config.fontSize+" !important }"); 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(".avatar { border-radius: "+this.config.avatarRadius+"% !important }");
let currentTheme = TD.settings.getTheme();
if (currentTheme === "dark" && this.config.themeOverride){
currentTheme = this.config.themeOverride;
}
let notificationScrollbarColor = null; let notificationScrollbarColor = null;
if (this.config.themeColorTweaks){ if (this.config.themeColorTweaks){
switch(TD.settings.getTheme()){ switch(currentTheme){
case "dark": case "black":
this.css.insert(".app-content, .app-columns-container { background-color: #444448 !important }"); this.css.insert(".app-content, .app-columns-container { background-color: #444448 !important }");
this.css.insert(".column-drag-handle { opacity: 0.5 !important }"); this.css.insert(".column-drag-handle { opacity: 0.5 !important }");
this.css.insert(".column-drag-handle:hover { opacity: 1 !important }"); this.css.insert(".column-drag-handle:hover { opacity: 1 !important }");
@@ -371,6 +402,10 @@ enabled(){
notificationScrollbarColor = "666"; notificationScrollbarColor = "666";
break; break;
case "dark":
this.css.insert(".scroll-styled-v:not(.scroll-alt)::-webkit-scrollbar-track, .scroll-styled-h:not(.scroll-alt)::-webkit-scrollbar-track { border-left-color: #14171A !important }");
break;
case "light": case "light":
this.css.insert(".scroll-styled-v:not(.scroll-alt)::-webkit-scrollbar-thumb:not(:hover), .scroll-styled-h:not(.scroll-alt)::-webkit-scrollbar-thumb:not(:hover) { background-color: #d2d6da !important }"); this.css.insert(".scroll-styled-v:not(.scroll-alt)::-webkit-scrollbar-thumb:not(:hover), .scroll-styled-h:not(.scroll-alt)::-webkit-scrollbar-thumb:not(:hover) { background-color: #d2d6da !important }");
this.css.insert(".app-columns-container.scroll-styled-h::-webkit-scrollbar-thumb:not(:hover) { background-color: #a5aeb5 !important }"); this.css.insert(".app-columns-container.scroll-styled-h::-webkit-scrollbar-thumb:not(:hover) { background-color: #a5aeb5 !important }");
@@ -405,87 +440,87 @@ enabled(){
if (this.config.revertIcons){ if (this.config.revertIcons){
let iconData = [ let iconData = [
[ ".icon-twitter-bird", "00" ], [ "twitter-bird", "00" ],
[ ".icon-mention", "01" ], [ "mention", "01" ],
[ ".icon-following", "02" ], [ "following", "02" ],
[ ".icon-message", "03" ], [ "message", "03" ],
[ ".icon-home", "04" ], [ "home", "04" ],
[ ".icon-hashtag", "05" ], [ "hashtag", "05" ],
[ ".icon-reply", "06" ], [ "reply", "06" ],
[ ".icon-favorite", "55" ], [ "favorite", "55" ],
[ ".icon-retweet", "08" ], [ "retweet", "08" ],
[ ".icon-drafts", "09" ], [ "drafts", "09" ],
[ ".icon-search", "0a" ], [ "search", "0a" ],
[ ".icon-trash", "0c" ], [ "trash", "0c" ],
[ ".icon-close", "0d" ], [ "close", "0d" ],
[ ".icon-arrow-r:before,.Icon--caretRight", "0e" ], [ "arrow-r:before,.Icon--caretRight", "0e" ],
[ ".icon-arrow-l:before,.Icon--caretLeft", "0f" ], [ "arrow-l:before,.Icon--caretLeft", "0f" ],
[ ".icon-protected", "13" ], [ "protected", "13" ],
[ ".icon-list", "14" ], [ "list", "14" ],
[ ".icon-camera", "15" ], [ "camera", "15" ],
[ ".icon-more", "16" ], [ "more", "16" ],
[ ".icon-settings", "18" ], [ "settings", "18" ],
[ ".icon-notifications", "19" ], [ "notifications", "19" ],
[ ".icon-user-dd", "1a" ], [ "user-dd", "1a" ],
[ ".icon-activity", "1c" ], [ "activity", "1c" ],
[ ".icon-trending", "1d" ], [ "trending", "1d" ],
[ ".icon-minus", "1e" ], [ "minus", "1e" ],
[ ".icon-plus", "1f" ], [ "plus", "1f" ],
[ ".icon-geo", "20" ], [ "geo", "20" ],
[ ".icon-check", "21" ], [ "check", "21" ],
[ ".icon-schedule", "22" ], [ "schedule", "22" ],
[ ".icon-dot", "23" ], [ "dot", "23" ],
[ ".icon-user", "24" ], [ "user", "24" ],
[ ".icon-content", "25" ], [ "content", "25" ],
[ ".icon-arrow-d:before,.Icon--caretDown", "26" ], [ "arrow-d:before,.Icon--caretDown", "26" ],
[ ".icon-arrow-u", "27" ], [ "arrow-u", "27" ],
[ ".icon-share", "28" ], [ "share", "28" ],
[ ".icon-info", "29" ], [ "info", "29" ],
[ ".icon-verified", "2a" ], [ "verified", "2a" ],
[ ".icon-translator", "2b" ], [ "translator", "2b" ],
[ ".icon-blocked", "2c" ], [ "blocked", "2c" ],
[ ".icon-constrain", "2d" ], [ "constrain", "2d" ],
[ ".icon-play-video", "2e" ], [ "play-video", "2e" ],
[ ".icon-empty", "2f" ], [ "empty", "2f" ],
[ ".icon-clear-input", "30" ], [ "clear-input", "30" ],
[ ".icon-compose", "31" ], [ "compose", "31" ],
[ ".icon-mark-read", "32" ], [ "mark-read", "32" ],
[ ".icon-arrow-r-double", "33" ], [ "arrow-r-double", "33" ],
[ ".icon-arrow-l-double", "34" ], [ "arrow-l-double", "34" ],
[ ".icon-follow", "35" ], [ "follow", "35" ],
[ ".icon-image", "36" ], [ "image", "36" ],
[ ".icon-popout", "37" ], [ "popout", "37" ],
[ ".icon-move", "39" ], [ "move", "39" ],
[ ".icon-compose-grid", "3a" ], [ "compose-grid", "3a" ],
[ ".icon-compose-minigrid", "3b" ], [ "compose-minigrid", "3b" ],
[ ".icon-compose-list", "3c" ], [ "compose-list", "3c" ],
[ ".icon-edit", "40" ], [ "edit", "40" ],
[ ".icon-clear-timeline", "41" ], [ "clear-timeline", "41" ],
[ ".icon-sliders", "42" ], [ "sliders", "42" ],
[ ".icon-custom-timeline", "43" ], [ "custom-timeline", "43" ],
[ ".icon-compose-dm", "44" ], [ "compose-dm", "44" ],
[ ".icon-bg-dot", "45" ], [ "bg-dot", "45" ],
[ ".icon-user-team-mgr", "46" ], [ "user-team-mgr", "46" ],
[ ".icon-user-switch", "47" ], [ "user-switch", "47" ],
[ ".icon-conversation", "48" ], [ "conversation", "48" ],
[ ".icon-dataminr", "49" ], [ "dataminr", "49" ],
[ ".icon-link", "4a", ], [ "link", "4a", ],
[ ".icon-flash", "50" ], [ "flash", "50" ],
[ ".icon-pointer-u", "51" ], [ "pointer-u", "51" ],
[ ".icon-analytics", "54" ], [ "analytics", "54" ],
[ ".icon-heart", "55" ], [ "heart", "55" ],
[ ".icon-calendar", "56" ], [ "calendar", "56" ],
[ ".icon-attachment", "57" ], [ "attachment", "57" ],
[ ".icon-play", "58" ], [ "play", "58" ],
[ ".icon-bookmark", "59" ], [ "bookmark", "59" ],
[ ".icon-play-badge", "60" ], [ "play-badge", "60" ],
[ ".icon-gif-badge", "61" ], [ "gif-badge", "61" ],
[ ".icon-poll", "62" ], [ "poll", "62" ],
[ ".icon-heart-filled", "55" ], [ "heart-filled", "55" ],
[ ".icon-retweet-filled", "08" ], [ "retweet-filled", "08" ],
[ ".icon-list-filled", "14" ], [ "list-filled", "14" ],
[ ".icon-user-filled", "35" ], [ "user-filled", "35" ],
]; ];
this.icons = document.createElement("style"); this.icons = document.createElement("style");
@@ -497,9 +532,12 @@ enabled(){
font-style: normal; font-style: normal;
} }
${iconData.map(entry => `#tduck ${entry[0]}:before{content:\"\\f0${entry[1]}\";font-family:_of!important}`).join("")} ${iconData.map(entry => `#tduck .icon-${entry[0]}:before{content:\"\\f0${entry[1]}\";font-family:_of!important}`).join("")}
.drawer .btn .icon, .app-header .btn .icon { line-height: 1em !important } .drawer .btn .icon, .app-header .btn .icon { line-height: 1em !important }
.app-search-fake .icon { margin-top: -3px !important }
#tduck .search-input-control .icon { font-size: 20px !important; top: -4px !important }
.column-header .column-type-icon { bottom: 26px !important } .column-header .column-type-icon { bottom: 26px !important }
.is-options-open .column-type-icon { bottom: 25px !important } .is-options-open .column-type-icon { bottom: 25px !important }
@@ -510,6 +548,15 @@ ${iconData.map(entry => `#tduck ${entry[0]}:before{content:\"\\f0${entry[1]}\";f
document.head.appendChild(this.icons); document.head.appendChild(this.icons);
} }
if (currentTheme === "black"){
$TDP.readFileRoot(this.$token, "theme.black.css").then(contents => {
if (this.theme){
this.theme.element.innerHTML = contents;
TD.settings.setTheme("dark"); // forces refresh of notification head tag
}
});
}
if (this.config.columnWidth[0] === '/'){ if (this.config.columnWidth[0] === '/'){
let cols = this.config.columnWidth.slice(1); let cols = this.config.columnWidth.slice(1);
@@ -561,6 +608,12 @@ ${this.config.revertIcons ? `
#tduck .icon-user-dd:before{content:"\\f01a";font-family:_of!important} #tduck .icon-user-dd:before{content:"\\f01a";font-family:_of!important}
` : ``} ` : ``}
${currentTheme === "black" ? `
html.dark a, html.dark a:hover, html.dark a:focus, html.dark a:active { color: #8bd }
#tduck-show-thread { color: #8bd !important }
.quoted-tweet { border-color: #292f33 !important }
` : ``}
${notificationScrollbarColor ? ` ${notificationScrollbarColor ? `
.scroll-styled-v::-webkit-scrollbar-thumb:not(:hover), .scroll-styled-h::-webkit-scrollbar-thumb:not(:hover) { background-color: #${notificationScrollbarColor} !important } .scroll-styled-v::-webkit-scrollbar-thumb:not(:hover), .scroll-styled-h::-webkit-scrollbar-thumb:not(:hover) { background-color: #${notificationScrollbarColor} !important }
` : ``} ` : ``}
@@ -605,7 +658,7 @@ ready(){
TD.components.GlobalSettings.prototype.switchTab = function(tab){ TD.components.GlobalSettings.prototype.switchTab = function(tab){
if (tab === "tdp-edit-design"){ if (tab === "tdp-edit-design"){
me.openEditDesignDialog(); me.configure();
} }
else{ else{
return me.prevFuncSettingsSwitchTab.apply(this, arguments); return me.prevFuncSettingsSwitchTab.apply(this, arguments);
@@ -613,11 +666,19 @@ ready(){
}; };
} }
configure(){
new this.customDesignModal();
}
disabled(){ disabled(){
if (this.css){ if (this.css){
this.css.remove(); this.css.remove();
} }
if (this.theme){
this.theme.remove();
}
if (this.icons){ if (this.icons){
document.head.removeChild(this.icons); document.head.removeChild(this.icons);
} }

View File

@@ -15,6 +15,10 @@
<label class="txt-uppercase touch-larger-label"> <label class="txt-uppercase touch-larger-label">
<b>Theme</b> <b>Theme</b>
</label> </label>
<label class="radio">
<input data-td-theme="black" class="js-theme-radio touch-larger-label" name="theme" type="radio">
Black
</label>
<label class="radio"> <label class="radio">
<input data-td-theme="dark" class="js-theme-radio touch-larger-label" name="theme" type="radio"> <input data-td-theme="dark" class="js-theme-radio touch-larger-label" name="theme" type="radio">
Dark Dark
@@ -164,6 +168,10 @@
height: 380px; height: 380px;
} }
#edit-design-panel .mdl-inner {
padding-top: 0;
}
#edit-design-panel-inner-cols { #edit-design-panel-inner-cols {
padding: 0 6px; padding: 0 6px;
} }

View File

@@ -0,0 +1,852 @@
html.dark{color:#e1e8ed}
html.dark .is-inverted-dark .stream-item{background-color:#fff}
html.dark::selection{background:#e1e8ed;color:#111}
html.dark a{color:#8bd}
html.dark a:hover,html.dark a:focus,html.dark a:active{color:#8bd}
html.dark .txt-mute{color:#8899a6}
html.dark .txt-mute a:not(:hover):not(:focus){color:#8899a6}
html.dark .txt-mute-text-only{color:#8899a6}
html.dark .color-twitter-darker-gray{color:#657786}
html.dark .color-twitter-white{color:#fff}
html.dark .color-twitter-gray{color:#AAB8C2}
html.dark .color-twitter-blue{color:#1DA1F2}
html.dark .color-twitter-red{color:#E0245E}
html.dark .color-twitter-deep-red{color:#A01744}
html.dark .color-twitter-green{color:#17BF63}
html.dark .color-twitter-deep-green{color:#008951}
html.dark .color-twitter-deep-black{color:#14171A}
html.dark .color-twitter-dark-black{color:#292F33}
html.dark .color-twitter-dark-gray{color:#8899A6}
html.dark .color-twitter-black{color:#000}
html.dark .color-twitter-yellow{color:#FFAD1F}
html.dark .bg-color-twitter-white{background-color:#fff}
html.dark .bg-color-twitter-blue{background-color:#1DA1F2}
html.dark .bg-color-twitter-medium-blue{background-color:#1DA1F2}
html.dark .bg-color-twitter-faded-yellow{background-color:#FFE8B6}
html.dark .bg-color-twitter-deep-black{background-color:#292F33}
html.dark .bg-color-twitter-lightest-gray{background-color:#F5F8FA}
html.dark .link-hover-override:hover .link-hover-target{color:#8bd}
html.dark .scroll-styled-v::-webkit-scrollbar-track,html.dark .scroll-styled-h::-webkit-scrollbar-track{border-left:1px solid #292F33}
html.dark .scroll-styled-v::-webkit-scrollbar-thumb,html.dark .scroll-styled-h::-webkit-scrollbar-thumb{background-color:#657786}
html.dark .scroll-styled-v::-webkit-scrollbar-thumb:hover,html.dark .scroll-styled-h::-webkit-scrollbar-thumb:hover{background-color:#8899a6}
html.dark .scroll-alt::-webkit-scrollbar-track{border-color:#eaeaea}
html.dark .scroll-alt::-webkit-scrollbar-thumb{background-color:#e1e8ed}
html.dark .scroll-alt::-webkit-scrollbar-thumb:hover{background-color:#657786}
html.dark .scroll-conversation{background:#292F33}
html.dark .is-inverted-dark .column-scroller::-webkit-scrollbar-track{border-left-color:#e1e8ed}
html.dark .is-inverted-dark .column-scroller::-webkit-scrollbar-thumb{background-color:#ddd}
html.dark .is-inverted-dark .column-scroller::-webkit-scrollbar-thumb:hover{background-color:#8899a6}
html.dark .antiscroll-scrollbar{background:rgba(255,255,255,0.2)}
html.dark .is-loading{background-color:#fff}
html.dark .with-drop-shadow:after{box-shadow:inset 0 2px 1px rgba(17,17,17,0.25);border-top:1px solid rgba(17,17,17,0.25)}
html.dark .border-separated li{border-bottom:1px solid #CCD6DD}
html.dark .dark-border{border:1px solid #292f33}
html.dark .dark-border-top{border-top:1px solid #292f33}
html.dark .bs-1{box-shadow:0 1px 4px rgba(0,0,0,0.25)}
html.dark .is-inverted-dark{color:#292F33}
html.dark .is-inverted-dark a,html.dark .is-inverted-dark a:hover,html.dark .is-inverted-dark a:focus,html.dark .is-inverted-dark a:active{color:#3b94d9}
html.dark .is-inverted-dark .link-normal-dark,html.dark .is-inverted-dark .link-normal-dark:hover,html.dark .is-inverted-dark .link-normal-dark:focus,html.dark .is-inverted-dark .link-normal-dark:active{color:#111}
html.dark .is-inverted-dark .list-link,html.dark .is-inverted-dark .list-twitter-list,html.dark .is-inverted-dark .list-subtitle,html.dark .is-inverted-dark .list-account,html.dark .is-inverted-dark .list-listmember{color:#292F33}
html.dark .is-inverted-dark .txt-mute{color:#8899a6}
html.dark .is-inverted-dark .txt-mute a:not(:hover):not(:focus){color:#8899a6}
html.dark .is-inverted-dark .stream-item:not(.conversation-event),html.dark .is-inverted-dark .conversation-event+.stream-item:not(.conversation-event){border-color:#eaeaea}
html.dark .is-inverted-dark .account-link{color:#292F33}
html.dark .is-inverted-dark .account-bio{color:#8899a6}
html.dark .is-inverted-dark .with-drop-shadow:after{box-shadow:inset 0 2px 4px #ccd6dd;border-top:1px solid rgba(17,17,17,0.25)}
html.dark .is-inverted-dark .column-close-link{color:#66757f}
html.dark .is-inverted-dark .accordion{color:#111}
html.dark .is-inverted-dark .accordion-header{color:#111}
html.dark .is-inverted-dark .accordion-divider-t{border-top:1px solid #eaeaea}
html.dark .is-inverted-dark .accordion-header:hover{color:#111}
html.dark .is-inverted-dark .facet-type-content.is-active{background-color:rgba(210,155,154,0.2)}
html.dark .is-inverted-dark .facet-type-user.is-active{background-color:rgba(255,217,131,0.2)}
html.dark .is-inverted-dark .facet-type-location.is-active{background-color:rgba(118,194,158,0.2)}
html.dark .is-inverted-dark .facet-type-preferences.is-active{background-color:rgba(136,153,166,0.2)}
html.dark .is-inverted-dark .facet-type-engagement.is-active{background-color:#e1e8ed}
html.dark .is-inverted-dark .facet-type{border-bottom:1px solid #eaeaea}
html.dark .is-inverted-dark .accordion .is-active{color:#111}
html.dark .is-inverted-dark .accordion .is-active .accordion-header,html.dark .is-inverted-dark .accordion .is-active .accordion-header:hover{color:#111}
html.dark .is-inverted-dark .tweet-detail-wrapper{background:#F5F8FA}
html.dark .is-inverted-dark .scroll-conversation{background:#eaeaea}
html.dark .is-inverted-dark .tweet-detail-actions,html.dark .is-inverted-dark .tweet-stats,html.dark .is-inverted-dark .card-holder{border-top-color:#eaeaea}
html.dark .is-inverted-dark .tweet-detail-action{color:#8899a6}
html.dark .is-inverted-dark .tweet-detail-action:hover,html.dark .is-inverted-dark .tweet-detail-action:focus,html.dark .is-inverted-dark .tweet-detail-action:active,html.dark .is-inverted-dark .tweet-detail-action.is-selected{color:#292F33}
html.dark .is-inverted-dark .social-proof-for-tweet-title{background-color:#eaeaea;color:#777;border-bottom:#ddd}
html.dark .is-inverted-dark .rpl textarea{border:1px solid #e1e8ed;background:#fff;box-shadow:#8899a6 0 1px 0 inset}
html.dark .is-inverted-dark .media-badge{border-color:#e1e8ed;color:#8899a6}
html.dark .is-inverted-dark .media-badge:hover{background-color:#f5f8fa}
html.dark .icon-favorite-color{color:#E0245E}
html.dark .icon-follow-color{color:#50a5e6}
html.dark .icon-list-color{color:#66757f}
html.dark .icon-image-color{color:#66757f}
html.dark .icon-mention-color{color:#66757f}
html.dark .icon-unread-color{color:#50a5e6}
html.dark .icon-remove-color{color:#dd2e44}
html.dark .icon-submit-color{color:#77b255}
html.dark .icon-retweet-color{color:#17BF63}
html.dark .icon-twitter-blue-color{color:#2b7bb9}
html.dark .btn:hover{color:#1DA1F2;background-color:#292F33}
html.dark .btn:focus{color:#1DA1F2;background-color:#292F33;box-shadow:0 0 0 1px #fff,0 0 0 3px #71C9F8}
html.dark .btn:active,html.dark .btn.is-selected{color:#1DA1F2;background-color:#292F33}
html.dark .btn[disabled],html.dark .btn[disabled]:hover,html.dark .btn[disabled]:active,html.dark .btn.is-disabled,html.dark .btn.is-disabled:hover,html.dark .btn.is-disabled:focus,html.dark .btn.is-disabled:active{color:#1DA1F2}
html.dark .btn-on-dark:focus{box-shadow:0 0 0 1px #292F33,0 0 0 3px #71C9F8}
html.dark .mdl-content .btn-on-dark:focus{box-shadow:0 0 0 1px #fff,0 0 0 3px #71C9F8}
html.dark .is-inverted-dark .btn:hover,html.dark .is-inverted-dark .btn:focus,html.dark .is-inverted-dark .btn:active,html.dark .is-inverted-dark .btn.is-selected{background-color:#F2F9FF}
html.dark .is-inverted-dark .btn-positive:hover,html.dark .is-inverted-dark .btn-positive-alt:hover,html.dark .is-inverted-dark .btn-fav.s-favorited:hover,html.dark .is-inverted-dark .s-following .follow-btn:hover,html.dark .s-following .is-inverted-dark .follow-btn:hover{background-color:#005FD1}
html.dark .is-inverted-dark .btn-positive:focus,html.dark .is-inverted-dark .btn-positive-alt:focus,html.dark .is-inverted-dark .btn-fav.s-favorited:focus,html.dark .is-inverted-dark .s-following .follow-btn:focus,html.dark .s-following .is-inverted-dark .follow-btn:focus{background-color:#005FD1}
html.dark .is-inverted-dark .btn-positive:active,html.dark .is-inverted-dark .btn-positive-alt:active,html.dark .is-inverted-dark .btn-fav.s-favorited:active,html.dark .is-inverted-dark .s-following .follow-btn:active,html.dark .s-following .is-inverted-dark .follow-btn:active,html.dark .is-inverted-dark .btn-positive.is-selected,html.dark .is-inverted-dark .is-selected.btn-positive-alt,html.dark .is-inverted-dark .is-selected.btn-fav.s-favorited,html.dark .is-inverted-dark .s-following .is-selected.follow-btn,html.dark .s-following .is-inverted-dark .is-selected.follow-btn{background-color:#005FD1}
html.dark .is-inverted-dark .btn-positive-alt:hover,html.dark .is-inverted-dark .btn-fav.s-favorited:hover,html.dark .is-inverted-dark .s-following .follow-btn:hover,html.dark .s-following .is-inverted-dark .follow-btn:hover{background-color:#A01744}
html.dark .is-inverted-dark .btn-positive-alt:focus,html.dark .is-inverted-dark .btn-fav.s-favorited:focus,html.dark .is-inverted-dark .s-following .follow-btn:focus,html.dark .s-following .is-inverted-dark .follow-btn:focus{background-color:#A01744}
html.dark .is-inverted-dark .btn-positive-alt:active,html.dark .is-inverted-dark .btn-fav.s-favorited:active,html.dark .is-inverted-dark .s-following .follow-btn:active,html.dark .s-following .is-inverted-dark .follow-btn:active,html.dark .is-inverted-dark .btn-positive-alt.is-selected,html.dark .is-inverted-dark .is-selected.btn-fav.s-favorited,html.dark .is-inverted-dark .s-following .is-selected.follow-btn,html.dark .s-following .is-inverted-dark .is-selected.follow-btn{background-color:#A01744}
html.dark .is-inverted-dark .btn-negative:hover{background-color:#A01744}
html.dark .is-inverted-dark .btn-negative:focus{background-color:#A01744}
html.dark .is-inverted-dark .btn-negative:active,html.dark .is-inverted-dark .btn-negative.is-selected{background-color:#A01744}
html.dark .is-inverted-dark .btn-tertiary:hover{background-color:#F5F8FA}
html.dark .is-inverted-dark .btn-tertiary:focus{background-color:#F5F8FA}
html.dark .is-inverted-dark .btn-tertiary:active,html.dark .is-inverted-dark .btn-tertiary.is-selected{background-color:#F5F8FA}
html.dark .btn-positive,html.dark .btn-positive-alt,html.dark .btn-fav.s-favorited,html.dark .s-following .follow-btn{color:#fff;background-color:#1DA1F2;border:1px solid #1DA1F2}
html.dark .btn-positive:hover,html.dark .btn-positive-alt:hover,html.dark .btn-fav.s-favorited:hover,html.dark .s-following .follow-btn:hover{color:#fff;background-color:#005FD1;border:1px solid #005FD1}
html.dark .btn-positive:focus,html.dark .btn-positive-alt:focus,html.dark .btn-fav.s-favorited:focus,html.dark .s-following .follow-btn:focus{color:#fff;background-color:#005FD1;border:1px solid #005FD1;box-shadow:0 0 0 1px #fff,0 0 0 3px #71C9F8}
html.dark .btn-positive:active,html.dark .btn-positive-alt:active,html.dark .btn-fav.s-favorited:active,html.dark .s-following .follow-btn:active,html.dark .btn-positive.is-selected,html.dark .is-selected.btn-positive-alt,html.dark .is-selected.btn-fav.s-favorited,html.dark .s-following .is-selected.follow-btn{color:#fff;background-color:#005FD1;border:1px solid #005FD1}
html.dark .btn-positive[disabled],html.dark [disabled].btn-positive-alt,html.dark [disabled].btn-fav.s-favorited,html.dark .s-following [disabled].follow-btn,html.dark .btn-positive[disabled]:hover,html.dark [disabled].btn-positive-alt:hover,html.dark [disabled].btn-fav.s-favorited:hover,html.dark .s-following [disabled].follow-btn:hover,html.dark .btn-positive[disabled]:active,html.dark [disabled].btn-positive-alt:active,html.dark [disabled].btn-fav.s-favorited:active,html.dark .s-following [disabled].follow-btn:active,html.dark .btn-positive.is-disabled,html.dark .is-disabled.btn-positive-alt,html.dark .is-disabled.btn-fav.s-favorited,html.dark .s-following .is-disabled.follow-btn,html.dark .btn-positive.is-disabled:hover,html.dark .is-disabled.btn-positive-alt:hover,html.dark .is-disabled.btn-fav.s-favorited:hover,html.dark .s-following .is-disabled.follow-btn:hover,html.dark .btn-positive.is-disabled:focus,html.dark .is-disabled.btn-positive-alt:focus,html.dark .is-disabled.btn-fav.s-favorited:focus,html.dark .s-following .is-disabled.follow-btn:focus,html.dark .btn-positive.is-disabled:active,html.dark .is-disabled.btn-positive-alt:active,html.dark .is-disabled.btn-fav.s-favorited:active,html.dark .s-following .is-disabled.follow-btn:active{color:#fff;background-color:#1DA1F2;border:1px solid #1DA1F2}
html.dark .btn-compose{color:#fff;background-color:#2b7bb9}
html.dark .btn-compose:hover{color:#fff;background-color:#2b7bb9}
html.dark .btn-compose:focus{color:#fff;background-color:#2b7bb9}
html.dark .btn-compose:active,html.dark .btn-compose.is-selected{color:#fff;background-color:#2b7bb9}
html.dark .btn-positive-alt:hover,html.dark .btn-fav.s-favorited:hover,html.dark .s-following .follow-btn:hover{color:#fff;background-color:#A01744;border:#A01744}
html.dark .btn-positive-alt:active,html.dark .btn-fav.s-favorited:active,html.dark .s-following .follow-btn:active,html.dark .btn-positive-alt.is-selected,html.dark .is-selected.btn-fav.s-favorited,html.dark .s-following .is-selected.follow-btn{color:#fff;background-color:#A01744;border:#A01744}
html.dark .btn-negative{border-color:#E0245E;color:#fff;background-color:#E0245E}
html.dark .btn-negative:hover{color:#fff;background-color:#A01744;border-color:#A01744}
html.dark .btn-negative:focus{color:#fff;background-color:#A01744;border-color:#A01744;box-shadow:0 0 0 1px #fff,0 0 0 3px #F6809A}
html.dark .btn-negative:active,html.dark .btn-negative.is-selected{color:#fff;background-color:#A01744;border-color:#A01744}
html.dark .btn-negative[disabled],html.dark .btn-negative[disabled]:hover,html.dark .btn-negative[disabled]:active,html.dark .btn-negative.is-disabled,html.dark .btn-negative.is-disabled:hover,html.dark .btn-negative.is-disabled:focus,html.dark .btn-negative.is-disabled:active{border-color:#E0245E;color:#fff;background-color:#E0245E}
html.dark .btn-tertiary{border-color:#657786;color:#657786}
html.dark .btn-tertiary:hover{color:#657786;background-color:#F5F8FA;border-color:#657786}
html.dark .btn-tertiary:focus{color:#657786;background-color:#F5F8FA;border-color:#657786;box-shadow:0 0 0 1px #fff,0 0 0 3px #CCD6DD}
html.dark .btn-tertiary:active,html.dark .btn-tertiary.is-selected{color:#657786;background-color:#F5F8FA;border-color:#657786}
html.dark .btn-tertiary[disabled],html.dark .btn-tertiary[disabled]:hover,html.dark .btn-tertiary[disabled]:active,html.dark .btn-tertiary.is-disabled,html.dark .btn-tertiary.is-disabled:hover,html.dark .btn-tertiary.is-disabled:focus,html.dark .btn-tertiary.is-disabled:active{color:#AAB8C2;border-color:#e1e8ed;background-color:#eaeaea}
html.dark .btn-on-blue{color:#fff;background-color:#66757f}
html.dark .btn-on-blue:hover{color:#fff;background-color:#66757f}
html.dark .btn-on-blue:focus{color:#fff;background-color:#66757f;box-shadow:0 0 2px 3px #50a5e6}
html.dark .btn-on-blue:active,html.dark .btn-on-blue.is-selected{color:#fff;background-color:#434c51}
html.dark .btn-on-blue[disabled],html.dark .btn-on-blue[disabled]:hover,html.dark .btn-on-blue[disabled]:active,html.dark .btn-on-blue.is-disabled,html.dark .btn-on-blue.is-disabled:hover,html.dark .btn-on-blue.is-disabled:focus,html.dark .btn-on-blue.is-disabled:active{color:#fff;background-color:#66757f}
html.dark .btn-neutral-negative{color:#d29b9a}
html.dark .btn-neutral-negative:hover,html.dark .btn-neutral-negative:focus{color:#d29b9a}
html.dark .btn-neutral-negative[disabled],html.dark .btn-neutral-negative[disabled]:hover,html.dark .btn-neutral-negative[disabled]:active,html.dark .btn-neutral-negative.is-disabled,html.dark .btn-neutral-negative.is-disabled:hover,html.dark .btn-neutral-negative.is-disabled:focus,html.dark .btn-neutral-negative.is-disabled:active{color:#d29b9a}
html.dark .btn-neutral-positive{color:#8bd}
html.dark .btn-neutral-positive:hover,html.dark .btn-neutral-positive:focus{color:#8bd}
html.dark .btn-neutral-positive[disabled],html.dark .btn-neutral-positive[disabled]:hover,html.dark .btn-neutral-positive[disabled]:active,html.dark .btn-neutral-positive.is-disabled,html.dark .btn-neutral-positive.is-disabled:hover,html.dark .btn-neutral-positive.is-disabled:focus,html.dark .btn-neutral-positive.is-disabled:active{color:#8bd}
html.dark .btn-options-tray{color:#e1e8ed}
html.dark .btn-options-tray:hover,html.dark .btn-options-tray:focus{color:#8bd}
html.dark .btn-options-tray[disabled],html.dark .btn-options-tray[disabled]:hover,html.dark .btn-options-tray[disabled]:active,html.dark .btn-options-tray.is-disabled,html.dark .btn-options-tray.is-disabled:hover,html.dark .btn-options-tray.is-disabled:focus,html.dark .btn-options-tray.is-disabled:active{color:#8bd}
html.dark .btn-bg-positive{background-color:rgba(102,117,127,0.5)}
html.dark .btn-bg-positive:hover,html.dark .btn-bg-positive:focus{background-color:rgba(102,117,127,0.5)}
html.dark .btn-bg-positive[disabled],html.dark .btn-bg-positive[disabled]:hover,html.dark .btn-bg-positive[disabled]:active,html.dark .btn-bg-positive.is-disabled,html.dark .btn-bg-positive.is-disabled:hover,html.dark .btn-bg-positive.is-disabled:focus,html.dark .btn-bg-positive.is-disabled:active{background-color:rgba(102,117,127,0.5)}
html.dark .btn-bg-negative{background-color:#5d5457}
html.dark .btn-bg-negative:hover,html.dark .btn-bg-negative:focus{background-color:#5d5457}
html.dark .btn-bg-negative[disabled],html.dark .btn-bg-negative[disabled]:hover,html.dark .btn-bg-negative[disabled]:active,html.dark .btn-bg-negative.is-disabled,html.dark .btn-bg-negative.is-disabled:hover,html.dark .btn-bg-negative.is-disabled:focus,html.dark .btn-bg-negative.is-disabled:active{background-color:#5d5457}
html.dark .btn-bg-white{background-color:#fff;color:#55acee}
html.dark .btn-bg-white:hover,html.dark .btn-bg-white:focus{background-color:#fff;color:#55acee}
html.dark .follow-btn .icon,html.dark .follow-btn .Icon{color:#1DA1F2}
html.dark .input-group-button{border:1px solid #e1e8ed}
html.dark .account-profile-header{background-color:#1DA1F2}
html.dark .account-settings-bt{border-top:1px solid #e1e8ed}
html.dark .account-settings-bb{border-bottom:1px solid #e1e8ed}
html.dark .account-stats a{color:#66757f}
html.dark .account-stats a:hover{color:#2b7bb9}
html.dark .column{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%)}
html.dark .more-tweets-btn{box-shadow:0 2px 0 rgba(0,0,0,0.2)}
html.dark .more-tweets-btn:active,html.dark .more-tweets-btn:focus{box-shadow:0 1px 0 rgba(0,0,0,0.3)}
html.dark .more-tweets-btn-container--mouse-release .more-tweets-btn,html.dark .more-tweets-btn-container--loading .more-tweets-btn{background-color:#66757f}
html.dark .drag-drop-indicator{background-color:#3b94d9;color:#fff}
html.dark .location-form .icon-close,html.dark .location-form .Icon--close{background-color:rgba(255,255,255,0.35)}
html.dark .search-filter-callout-triangle{border-color:transparent transparent #55acee}
html.dark .live-video-timelines{background-color:#292F33;border-bottom:1px solid #292F33}
html.dark .live-video-timelines button{color:#8bd;background-color:#292F33}
html.dark .live-video-timelines button:active,html.dark .live-video-timelines button:focus,html.dark .live-video-timelines button:hover{background-color:#292F33;color:#8bd}
html.dark .column-type-scheduled{background-color:#292F33}
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}
html.dark .column-header-link{color:#66757f}
html.dark .column-header-link:hover,html.dark .column-header-link:focus,html.dark .column-header-link:active{color:#fff}
html.dark .is-options-open .column-settings-link{background-color:#292f33;color:#8bd;border-color:#222426}
html.dark .is-options-open .column-settings-link:hover{color:#8bd}
html.dark .column-message{background-color:#292f33}
html.dark .filter-error{color:#fff;background-color:#be1931}
html.dark .facet-content{color:#d29b9a}
html.dark .facet-user{color:#ffd983}
html.dark .facet-action{color:#9cd1d4}
html.dark .facet-engagement{color:#8899a6}
html.dark .edit-conversation-name{border-bottom:1px solid #222426}
html.dark .column-options{background-color:#292f33}
html.dark .with-column-divider-bottom{border-bottom:1px solid #292F33}
html.dark .column-options .button-tray{background-color:#292F33}
html.dark .btn-options-unique{color:#e1e8ed}
html.dark .is-inverted-dark .column-options{background-color:#fff}
html.dark .is-inverted-dark .with-column-divider-bottom{border-bottom:1px solid #e1e8ed}
html.dark .column-nav-updates{color:#55acee}
html.dark .contributor-manager .link-complex{border:1px solid #e1e8ed}
html.dark .contributor-settings-role{border-bottom:1px solid #e1e8ed}
html.dark .contributor-row[data-state='settings']{background-color:#fff}
html.dark .contributor-row[data-state='confirmAdd']{background-color:#fff}
html.dark .contributor-row[data-state='confirmAdd-added']{background-color:#fff}
html.dark .contributor-row[data-state='confirmDeadmin']{background-color:#fff}
html.dark .contributor-row[data-state='confirmRemove'],html.dark .contributor-row[data-state='confirmRemove-removing']{background-color:#fff}
html.dark .contributor-row[data-state='confirmRemove-removing']{background-color:#fff}
html.dark .stream-item{border-bottom:1px solid #292F33;background-color:#222426}
html.dark .is-streamed{background-color:#4F0299}
html.dark .gap-chirp{background-color:#14171A;color:#8899a6}
html.dark .gap-chirp:hover .gap-chirp-text--with-size,html.dark .gap-chirp:active .gap-chirp-text--with-size{background:#1f2428}
html.dark .gap-chirp-text--with-size{border-color:#4b5964}
html.dark .is-inverted-dark .thread{background-color:#E1E8ED}
html.dark .thread{background-color:#3a3d42}
html.dark .list-item{color:#66757f}
html.dark .list-item:hover,html.dark .list-item:active,html.dark .list-item.is-selected{background-color:#55acee;color:#fff}
html.dark .list-item:hover:not(.is-selected){color:#66757f}
html.dark .list-item:hover .txt-mute,html.dark .list-item:active .txt-mute,html.dark .list-item.is-selected .txt-mute{color:#e1e8ed}
html.dark .list-item:hover .list-icon,html.dark .list-item:active .list-icon,html.dark .list-item.is-selected .list-icon{color:#fff}
html.dark .list-item:hover:not(.is-selected) .list-icon{color:#8899a6}
html.dark .list-icon{color:#8899a6}
html.dark .list-divider{border-top:1px solid rgba(17,17,17,0.25)}
html.dark .list-item-button{color:#aab8c2;background-color:#F5F8FA}
html.dark .is-touch-search .list-item:hover,html.dark .is-touch-search .list-item:active,html.dark .is-touch-search .list-item.is-selected{background-color:#55acee;color:#fff}
html.dark .is-touch-search .list-item:hover .list-icon,html.dark .is-touch-search .list-item:active .list-icon,html.dark .is-touch-search .list-item.is-selected .list-icon{color:#fff}
html.dark .avatar-border--2{border:2px solid #fff;background-color:#fff}
html.dark .account-link{color:#e1e8ed}
.on-blue html.dark .account-link{color:#fff}
.compose .quoted-tweet html.dark .account-link{color:#66757f}
html.dark .media-badge{border:1px solid #292F33;color:#999}
html.dark .media-badge:hover{background-color:#14171A}
html.dark .media-size-large-height::after,html.dark .media-item.media-size-large::after{background-image:linear-gradient(rgba(17,17,17,0.25),rgba(17,17,17,0))}
html.dark .media-sensitive{background:#292f33;color:#8899a6}
html.dark .media-sensitive-title{color:#e1e8ed}
html.dark .media-caret{border-color:#292F33 transparent transparent}
html.dark .video-overlay{color:#fff}
html.dark .is-inverted-dark .media-sensitive{background:#e1e8ed;color:#8899a6}
html.dark .is-inverted-dark .media-sensitive-title{color:#292f33}
html.dark .is-inverted-dark .triangle{border-color:#fff transparent transparent}
html.dark .is-inverted-dark .media-badge{border:1px solid #e1e8ed;color:#8899a6}
html.dark .is-inverted-dark .media-badge:hover{background-color:#f5f8fa}
html.dark .tweet-action,html.dark .tweet-detail-action,html.dark .dm-action{color:#8899a6}
html.dark .tweet-action:hover .icon-reply,html.dark .tweet-detail-action:hover .icon-reply,html.dark .dm-action:hover .icon-reply,html.dark .tweet-action:focus .icon-reply,html.dark .tweet-detail-action:focus .icon-reply,html.dark .dm-action:focus .icon-reply,html.dark .tweet-action:active .icon-reply,html.dark .tweet-detail-action:active .icon-reply,html.dark .dm-action:active .icon-reply,html.dark .tweet-action.is-selected .icon-reply,html.dark .is-selected.tweet-detail-action .icon-reply,html.dark .is-selected.dm-action .icon-reply{color:#1DA1F2}
html.dark .tweet-action:hover .icon-retweet,html.dark .tweet-detail-action:hover .icon-retweet,html.dark .dm-action:hover .icon-retweet,html.dark .tweet-action:focus .icon-retweet,html.dark .tweet-detail-action:focus .icon-retweet,html.dark .dm-action:focus .icon-retweet,html.dark .tweet-action:active .icon-retweet,html.dark .tweet-detail-action:active .icon-retweet,html.dark .dm-action:active .icon-retweet,html.dark .tweet-action.is-selected .icon-retweet,html.dark .is-selected.tweet-detail-action .icon-retweet,html.dark .is-selected.dm-action .icon-retweet{color:#17BF63}
html.dark .tweet-action:hover .icon-favorite,html.dark .tweet-detail-action:hover .icon-favorite,html.dark .dm-action:hover .icon-favorite,html.dark .tweet-action:focus .icon-favorite,html.dark .tweet-detail-action:focus .icon-favorite,html.dark .dm-action:focus .icon-favorite,html.dark .tweet-action:active .icon-favorite,html.dark .tweet-detail-action:active .icon-favorite,html.dark .dm-action:active .icon-favorite,html.dark .tweet-action.is-selected .icon-favorite,html.dark .is-selected.tweet-detail-action .icon-favorite,html.dark .is-selected.dm-action .icon-favorite{color:#E0245E}
html.dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-action,html.dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-detail-action,html.dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .dm-action{color:#657786}
html.dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-action:hover,html.dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-detail-action:hover,html.dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .dm-action:hover,html.dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-action:focus,html.dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-detail-action:focus,html.dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .dm-action:focus,html.dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-action:active,html.dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-detail-action:active,html.dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .dm-action:active,html.dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-action.is-selected,html.dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .is-selected.tweet-detail-action,html.dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .is-selected.dm-action{color:#8899a6}
html.dark .is-inverted-dark .tweet-action,html.dark .is-inverted-dark .tweet-detail-action,html.dark .is-inverted-dark .dm-action,html.dark .is-inverted-dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-action,html.dark .is-inverted-dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-detail-action,html.dark .is-inverted-dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .dm-action{color:#8899a6}
html.dark .is-inverted-dark .tweet-action:hover,html.dark .is-inverted-dark .tweet-detail-action:hover,html.dark .is-inverted-dark .dm-action:hover,html.dark .is-inverted-dark .tweet-action:focus,html.dark .is-inverted-dark .tweet-detail-action:focus,html.dark .is-inverted-dark .dm-action:focus,html.dark .is-inverted-dark .tweet-action:active,html.dark .is-inverted-dark .tweet-detail-action:active,html.dark .is-inverted-dark .dm-action:active,html.dark .is-inverted-dark .tweet-action.is-selected,html.dark .is-inverted-dark .is-selected.tweet-detail-action,html.dark .is-inverted-dark .is-selected.dm-action,html.dark .is-inverted-dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-action:hover,html.dark .is-inverted-dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-detail-action:hover,html.dark .is-inverted-dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .dm-action:hover,html.dark .is-inverted-dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-action:focus,html.dark .is-inverted-dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-detail-action:focus,html.dark .is-inverted-dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .dm-action:focus,html.dark .is-inverted-dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-action:active,html.dark .is-inverted-dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-detail-action:active,html.dark .is-inverted-dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .dm-action:active,html.dark .is-inverted-dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .tweet-action.is-selected,html.dark .is-inverted-dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .is-selected.tweet-detail-action,html.dark .is-inverted-dark .chirp-container .stream-item:not(:hover):not(.is-selected-tweet) .is-selected.dm-action{color:#111}
html.dark .is-inverted-dark .quoted-tweet{border-color:#E1E8ED;color:#66757f}
html.dark .is-retweet .icon-retweet-toggle{color:#17BF63}
html.dark .is-favorite .icon-favorite-toggle{color:#E0245E}
html.dark .is-minimalist .tweet-img{background:#292F33}
html.dark .is-selected-tweet{background:#292F33}
html.dark .in-tweet-divider:before{background:#292F33}
html.dark .tweet-translation-original-text{color:#8899a6}
html.dark .quoted-tweet{border:1px solid #E1E8ED;color:#66757f;border-color:#292F33;color:#999}
html.dark .scheduled-tweet{border:1px solid #292F33;color:#8899A6}
html.dark .stream-item .icon-edit,html.dark .stream-item .icon-trash{color:#657786}
html.dark .stream-item .icon-edit:hover,html.dark .stream-item .icon-trash:hover{color:#8899A6}
html.dark .tweet-detail-wrapper{background:#222426}
html.dark .tweet-stats{border-top:1px solid #292F33}
html.dark .tweet-stat{color:#8899a6}
html.dark .stat-word{color:#66757f}
.is-actionable:hover html.dark .stat-word{color:#8bd}
html.dark .tweet-detail-actions{border-top:1px solid #292F33}
html.dark .conversation-more{color:#8bd}
html.dark .column-detail .is-selected-tweet{background:#292F33}
html.dark .social-proof-for-tweet-title{background-color:#292F33;color:#8899a6;border-bottom:1px solid #292F33}
html.dark .is-inverted-dark .tweet-detail-reply .is-selected{color:#111}
html.dark .is-unread{background:#485865}
html.dark .is-unread .txt-mute,html.dark .is-unread .conversation-indicator{color:#e1e8ed}
html.dark .is-unread.is-selected-tweet{background:#66757f}
html.dark .is-inverted-dark .is-unread{background:#bbddf5}
html.dark .is-inverted-dark .is-unread .txt-mute,html.dark .is-inverted-dark .is-unread .conversation-indicator{color:#292F33}
html.dark .conversation-indicator{color:#8899a6}
html.dark .conversation-event{background-color:#292F33}
html.dark .conversation-event+.stream-item:not(.conversation-event),html.dark .conversation-event:first-child{border-top:1px solid #292F33}
html.dark .add-participant{background-color:#292F33}
html.dark .rpl{border-bottom:#292F33}
html.dark .rpl textarea{border:1px solid #292F33;background:#fff;box-shadow:inset 0 1px 1px rgba(17,17,17,0.5)}
html.dark .rpl input.over-char-count{color:#ea596e}
html.dark .spinner-button-with-progress{color:#fff}
html.dark .app-header{background-color:#292f33}
html.dark .app-title{background-color:#292f33}
html.dark .app-content{background-color:#14171A}
html.dark .app-columns-container{background-color:#14171A}
html.dark .app-navigator{background-color:#292f33}
html.dark .app-nav-link{color:#8899A6}
html.dark .app-nav-link:focus,html.dark .app-nav-link:active{color:#8899A6}
html.dark .app-nav-link.is-selected,html.dark .app-nav-link:hover{color:#fff}
html.dark .app-nav-tab{color:#8899A6}
html.dark .app-nav-tab:hover{color:#fff}
html.dark .app-nav-tab.is-selected{color:#292f33}
html.dark .app-nav-tab.is-selected:hover{color:#292f33}
html.dark .attach-compose-buttons .tweet-button{background-color:#485865!important}
html.dark .with-nav-border-t:before{border-top:1px solid #777}
html.dark .app-search-input,html.dark .app-search-fake{background-color:#14171A;color:#aab8c2;box-shadow:inset 0 1px 1px rgba(17,17,17,0.5)}
html.dark .app-search-input::-webkit-input-placeholder{color:#aab8c2}
html.dark .app-search-input::placeholder{color:#aab8c2}
html.dark .app-search-input:focus,html.dark .app-search-input.is-focused{border:1px solid #292F33;color:#292F33;background-color:#fff}
html.dark .app-search-fake{color:#777}
html.dark .app-search-button{color:#aab8c2}
html.dark .app-search-button:hover{color:#aab8c2}
html.dark .message-banner .dismiss{color:#292F33}
html.dark .typeahead{background-color:#fff;color:#292f33}
html.dark .typeahead .fullname{color:#292f33}
html.dark .typeahead .username{color:#8899a6}
html.dark .accordion,html.dark .accordion-popover{color:#e1e8ed}
html.dark .accordion-divider-t{border-top:1px solid #292F33}
html.dark .accordion-header{color:#e1e8ed}
html.dark .accordion-header:hover{color:#e1e8ed}
html.dark .facet-type{border-bottom:1px solid #292F33}
html.dark .facet-type-thumb-size{border-top:1px solid #292F33}
html.dark .facet-type.is-active{background-color:rgba(136,153,166,0.2)}
html.dark .facet-subtitle{color:#8bd}
html.dark .accordion .is-active{color:#e1e8ed}
html.dark .accordion .is-active .accordion-header,html.dark .accordion .is-active .accordion-header:hover{color:#e1e8ed}
html.dark .account-settings-row.is-highlighted{background-color:#F5F8FA;border-top:1px solid #CCD6DD}
html.dark .account-settings-row.is-highlighted:last-child{border-bottom:1px solid #CCD6DD}
html.dark .join-team{border-top:1px solid #E1E8ED;border-bottom:1px solid #E1E8ED}
html.dark .account-row-separator-b:after{background-color:#e1e8ed}
html.dark .separator-a:before{background-color:#e1e8ed}
html.dark .tooltip-inner{background-color:#111;color:#ddd}
html.dark .tooltip-arrow{border:5px dashed #111}
html.dark .bottom{border-bottom-color:#111}
html.dark .top{border-top-color:#111}
html.dark .left{border-left-color:#111}
html.dark .right{border-right-color:#111}
html.dark .stroke-twitter-light-gray{stroke:#CCD6DD}
html.dark .stroke-twitter-blue{stroke:#1DA1F2}
html.dark .stroke-twitter-yellow{stroke:#FFAD1F}
html.dark .stroke-twitter-red{stroke:#E0245E}
html.dark .numbered-badge{background-color:#55acee;color:#fff}
html.dark .numbered-badge-onheader{border:2px solid #292F33}
html.dark .numbered-badge-onnav{border:2px solid #292f33}
html.dark .is-open .drawer:after{box-shadow:2px 0 1px rgba(0,0,0,0.2)}
html.dark .drawer-header{border-bottom:1px solid #e1e8ed}
html.dark .dataminr{background-color:#f5f8fa}
html.dark .txt-dataminr{color:#8899a6}
html.dark .dataminr-title{background-color:#ccd6dd;color:#66757f}
html.dark .dataminr-search-terms-detail{color:#66757f}
html.dark .dataminr-separator{border-bottom:4px solid #e1e8ed}
html.dark .is-dataminr-tweet{background-color:#fff}
html.dark .dataminr-header,html.dark .dataminr-meta-link{color:#8899a6}
html.dark .dataminr-category-pill{color:#fff;background-color:#5585ad}
html.dark .dataminr-category-mn{background-color:#1F90BF}
html.dark .dataminr-category-mbg{background-color:#1F90BF}
html.dark .dataminr-category-ln{background-color:#1F90BF}
html.dark .dataminr-category-bg{background-color:#1F90BF}
html.dark .dataminr-category-rpr{background-color:#1F90BF}
html.dark .dataminr-category-er{background-color:#CC412E}
html.dark .dataminr-category-gov{background-color:#CC412E}
html.dark .dataminr-category-ngo{background-color:#CC412E}
html.dark .dataminr-category-spo{background-color:#8A64AD}
html.dark .dataminr-category-ent{background-color:#8A64AD}
html.dark .dataminr-category-uni{background-color:#CC412E}
html.dark .dataminr-category-bsn{background-color:#CC412E}
html.dark .dataminr-category-alt{background-color:#b26333}
html.dark .dataminr-category-ctr{background-color:#CC7332}
html.dark .dataminr-label{color:#e28409}
html.dark .dataminr-label-momentum{color:#5caee1}
html.dark .dataminr-map-img{border:1px solid #ccd6dd}
html.dark .dataminr-bio-count{color:#66757f}
html.dark .dataminr-user-profile{background-color:#fff}
html.dark .dataminr{background-color:#292f33}
html.dark .txt-dataminr{color:#aab8c2}
html.dark .dataminr-title{background-color:#657786;color:#e1e8ed}
html.dark .dataminr-search-terms-detail{color:#e1e8ed}
html.dark .is-dataminr-tweet{background-color:#444448}
html.dark .dataminr-separator{border-bottom:2px solid #444448}
html.dark .dataminr-header,html.dark .dataminr-meta-link{color:#ccd6dd}
html.dark .dataminr-map-img{border:1px solid #292F33}
html.dark .dataminr-label{color:#FFAD1F}
html.dark .dataminr-label-momentum{color:#55acee}
html.dark .dataminr-bio-count{color:#aab8c2}
html.dark .dataminr-user-profile{background-color:#111}
html.dark .dataminr-external-link{background-color:#222426}
html.dark .is-inverted-dark .dataminr{background-color:#f5f8fa}
html.dark .is-inverted-dark .txt-dataminr{color:#8899a6}
html.dark .is-inverted-dark .dataminr-title{background-color:#ccd6dd;color:#66757f}
html.dark .is-inverted-dark .dataminr-search-terms-detail{color:#66757f}
html.dark .is-inverted-dark .dataminr-separator{border-bottom:4px solid #e1e8ed}
html.dark .is-inverted-dark .is-dataminr-tweet{background-color:#fff}
html.dark .is-inverted-dark .dataminr-header,html.dark .is-inverted-dark .dataminr-meta-link{color:#8899a6}
html.dark .is-inverted-dark .dataminr-category-pill{color:#fff;background-color:#5585ad}
html.dark .is-inverted-dark .dataminr-category-mn{background-color:#1F90BF}
html.dark .is-inverted-dark .dataminr-category-mbg{background-color:#1F90BF}
html.dark .is-inverted-dark .dataminr-category-ln{background-color:#1F90BF}
html.dark .is-inverted-dark .dataminr-category-bg{background-color:#1F90BF}
html.dark .is-inverted-dark .dataminr-category-rpr{background-color:#1F90BF}
html.dark .is-inverted-dark .dataminr-category-er{background-color:#CC412E}
html.dark .is-inverted-dark .dataminr-category-gov{background-color:#CC412E}
html.dark .is-inverted-dark .dataminr-category-ngo{background-color:#CC412E}
html.dark .is-inverted-dark .dataminr-category-spo{background-color:#8A64AD}
html.dark .is-inverted-dark .dataminr-category-ent{background-color:#8A64AD}
html.dark .is-inverted-dark .dataminr-category-uni{background-color:#CC412E}
html.dark .is-inverted-dark .dataminr-category-bsn{background-color:#CC412E}
html.dark .is-inverted-dark .dataminr-category-alt{background-color:#b26333}
html.dark .is-inverted-dark .dataminr-category-ctr{background-color:#CC7332}
html.dark .is-inverted-dark .dataminr-label{color:#e28409}
html.dark .is-inverted-dark .dataminr-label-momentum{color:#5caee1}
html.dark .is-inverted-dark .dataminr-map-img{border:1px solid #ccd6dd}
html.dark .is-inverted-dark .dataminr-bio-count{color:#66757f}
html.dark .is-inverted-dark .dataminr-user-profile{background-color:#fff}
html.dark .info-caret{border-color:transparent #55acee transparent transparent}
html.dark .info-popover-close{color:#fff}
html.dark .info-popover-close:hover,html.dark .info-popover-close:active{color:#fff}
html.dark .info-popover-list-item:before{color:#88c9f9}
html.dark .info-popover-content{border:1px solid #fff}
html.dark .poll-bar{background-color:#8899A6}
html.dark .poll-bar--winner{background-color:#3b94d9}
html.dark .other-replies{color:#8899a6}
html.dark .other-replies-link,html.dark .other-replies-link:hover{color:#8bd}
html.dark .compose .other-replies,html.dark .inline-reply .other-replies{color:#8899A6}
html.dark .compose .other-replies-link,html.dark .compose .other-replies-link:hover,html.dark .inline-reply .other-replies-link,html.dark .inline-reply .other-replies-link:hover{color:#2b7bb9}
html.dark .ovl,html.dark .overlay{background:rgba(41,47,51,0.9)}
html.dark .overlay-opaque{background-color:#1c6399;background-image:radial-gradient(center center,ellipse cover,#1c6399,#274256)}
html.dark .seamful .modal-content{background:#fff}
html.dark .modal-content-with-border{border:1px solid #ccd6dd}
html.dark .mdl{background-color:#fff;box-shadow:0 0 10px rgba(17,17,17,0.5)}
html.dark .mdl-header{color:#8899a6}
html.dark .mdl-header-divider{border-bottom:1px solid #e1e8ed}
html.dark .mdl-content{border:1px solid #ccd6dd;background:#eaeaea}
html.dark .mdl-placeholder{color:#aab8c2;text-shadow:0 1px 0 rgba(255,255,255,0.8)}
html.dark .mdl-dismiss{color:#292F33}
html.dark .mdl-dismiss:hover{color:#292F33}
html.dark .mdl-btn-media,html.dark .is-inverted-light .mdl-btn-media{color:#fff}
html.dark .mdl-btn-media:hover,html.dark .mdl-btn-media:active,html.dark .mdl-btn-media:focus,html.dark .is-inverted-light .mdl-btn-media:hover,html.dark .is-inverted-light .mdl-btn-media:active{color:#fff}
html.dark .mdl-media-prev,html.dark .mdl-media-next{background:rgba(17,17,17,0.3)}
html.dark .mdl-column-med{background:#F5F8FA}
html.dark .mdl-column-rhs{border-left:1px solid #ccd6dd;background:#fff}
html.dark .s-minimal .mdl-header{border-bottom:1px solid #e1e8ed}
html.dark .lst-launcher .top-row{border-bottom:1px solid #e1e8ed}
html.dark .lst-launcher .is-disabled a,html.dark .lst-launcher .is-disabled a:hover,html.dark .lst-launcher .is-disabled a:focus,html.dark .lst-launcher .is-disabled a:active{background:#fff}
html.dark .lst-launcher a:hover,html.dark .lst-launcher a:focus,html.dark .lst-launcher a:active{background:#f2f9ff;border:1px solid #bbddf5}
html.dark .lst-profile a,html.dark .lst-profile a:hover,html.dark .lst-profile a:focus,html.dark .lst-profile a:active{color:#657786}
html.dark .mdl-col-settings{background-color:#fff;border-left:1px solid #E1E8ED}
html.dark .mdl-links{color:#8899a6}
html.dark .mdl-links a{color:#8899a6}
html.dark .mdl-account-shared-warning .mdl-content{background:#fff}
html.dark .char-count:disabled{color:#777}
html.dark .over-char-count:disabled{color:#be1931}
html.dark .cmp-replyto{background-color:#eaeaea;border-top:1px solid #ddd}
html.dark .s-link-added.s-photo-added p:last-child{border-top:1px solid #ddd}
html.dark .accs li{background:#eaeaea;border:1px solid #e1e8ed}
html.dark .accs li:hover{background:#e1e8ed}
html.dark .accs .icon,html.dark .accs .Icon{color:#999}
html.dark .accs .acc-selected{background-color:#55acee;border:1px solid #e1e8ed}
html.dark .accs .acc-selected i{color:#fff}
html.dark .accs .acc-selected:hover{border-color:#e1e8ed;background-color:#50a5e6}
html.dark .inline-reply{background-color:#485865;color:#fff}
html.dark .inline-reply .btn-neutral,html.dark .inline-reply .character-count{color:#fff}
html.dark .reply-triangle{border-color:transparent transparent #485865}
html.dark .detail-view-inline-text{border:1px solid #ccd6dd;background-color:#fff;color:#8899a6}
html.dark .is-inverted-dark .detail-view-inline{border-color:#ccd6dd}
html.dark .med-fullpanel{background-color:#111}
html.dark .med-link{color:#8bd}
html.dark .med-origlink,html.dark .med-flaglink{color:#8bd}
html.dark .med-origlink:hover,html.dark .med-flaglink:hover{color:#8bd}
html.dark .embed-modal .mdl-content{background:#fff}
html.dark .embed-loading-container{border:1px solid #ccd6dd}
html.dark .keyboard-shortcut-list-modal .mdl-content{background:#fff}
html.dark .text-like-keyboard-key{background-color:#eaeaea;border:1px solid #e1e8ed;box-shadow:0 1px 2px #e1e8ed,0 1px 2px #fff inset}
html.dark .s-checked .checked{color:#5c913b}
html.dark .list-link,html.dark .list-twitter-list,html.dark .list-subtitle,html.dark .list-account,html.dark .list-listmember,html.dark .list-account,html.dark .list-listaccount,html.dark .list-subtitle,html.dark .list-filter{color:#292F33}
html.dark .list-link:hover,html.dark .list-twitter-list:hover,html.dark .list-subtitle:hover,html.dark .list-account:hover,html.dark .list-listmember:hover,html.dark .list-account:hover,html.dark .list-listaccount:hover,html.dark .list-subtitle:hover{color:#111;background:#fff}
html.dark .list-link:hover:hover,html.dark .list-twitter-list:hover:hover,html.dark .list-subtitle:hover:hover,html.dark .list-account:hover:hover,html.dark .list-listmember:hover:hover,html.dark .list-link:hover:focus,html.dark .list-twitter-list:hover:focus,html.dark .list-subtitle:hover:focus,html.dark .list-account:hover:focus,html.dark .list-listmember:hover:focus,html.dark .list-link:hover:active,html.dark .list-twitter-list:hover:active,html.dark .list-subtitle:hover:active,html.dark .list-account:hover:active,html.dark .list-listmember:hover:active,html.dark .list-account:hover:hover,html.dark .list-account:hover:focus,html.dark .list-account:hover:active,html.dark .list-listaccount:hover:hover,html.dark .list-listaccount:hover:focus,html.dark .list-listaccount:hover:active,html.dark .list-subtitle:hover:hover,html.dark .list-subtitle:hover:focus,html.dark .list-subtitle:hover:active{color:#111;background:#fff}
html.dark .list-twitter-list .inner strong{color:#292F33}
html.dark .list-twitter-list .bytext,html.dark .list-twitter-list .txt-ellipsis{color:#8899a6}
html.dark .list-twitter-list .subtitle{color:#8899a6}
html.dark .list-subtitle span{color:#8899a6}
html.dark .list-account{text-shadow:0 1px 0 #fff}
html.dark .list-account .fullname{color:#292F33}
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-image:url("data:image/svg+xml;utf8,<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='18' height='8' viewBox='0 0 18 8'><path fill='#aaa' d='M9.82875,0.840168025 C9.59875,0.608328018 9.22625,0.608328018 8.99625,0.840168025 L5.00125,4.86964815 L1.00375,0.840168025 C0.77375,0.608328018 0.40125,0.608328018 0.17125,0.840168025 C-0.05875,1.07200803 -0.05625,1.44748804 0.17125,1.67932805 L4.58375,6.12712819 C4.69875,6.24304819 4.84875,6.30100819 5.00125,6.30100819 C5.15125,6.30100819 5.30125,6.24304819 5.41625,6.12712819 L9.82875,1.67932805 C10.05875,1.44748804 10.05875,1.07200803 9.82875,0.840168025'></path></svg>");background-color:#fff}
html.dark input,html.dark textarea,html.dark select{color:#111;border:1px solid #e1e8ed}
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)}
html.dark input.on-blue:focus{box-shadow:0 0 2px 3px #50a5e6}
html.dark .frm{color:#111}
html.dark .with-emphasis{border:1px solid #8899a6}
html.dark .with-emphasis:disabled{border-color:#999}
html.dark::-webkit-input-placeholder input::-webkit-input-placeholder,html.dark textarea::-webkit-input-placeholder{color:#aab8c2}
html.dark::placeholder input::placeholder,html.dark textarea::placeholder{color:#aab8c2}
html.dark::-webkit-validation-bubble-message{border:1px solid #ea596e;background-color:#ffe8eb}
html.dark::-webkit-validation-bubble-arrow{border:1px solid #ea596e;background-color:#ffe8eb}
html.dark .s-error input{border-color:rgba(200,120,114,0.8)}
html.dark .s-error label{color:#be1931}
html.dark .s-error input:focus{border-color:#c87872;box-shadow:0 0 6px rgba(200,120,114,0.5)}
html.dark input,html.dark textarea,html.dark select{color:#111}
html.dark .search-input-perform-search,html.dark .search-input-clear-search,html.dark .search-input-spinner{color:#aab8c2}
html.dark .search-input-perform-search:hover,html.dark .search-input-clear-search:hover,html.dark .search-input-spinner:hover{color:#aab8c2}
html.dark .input-clear-control{color:#aab8c2}
html.dark .input-clear-control:hover{color:#aab8c2}
html.dark .toggle-item.is-selected{color:#e1e8ed}
html.dark .toggle-item{color:#8bd}
html.dark .add-on{color:#999;border:1px solid #e1e8ed}
html.dark .add-on.with-emphasis{border:1px solid #8899a6}
html.dark .input-prepend input{border-left-color:#ccd6dd}
html.dark #calbody{background:#fff}
html.dark #caldays{border-bottom:1px solid #eaeaea}
html.dark #caldays span{color:#292f33}
html.dark #calweeks{background-color:#fff}
html.dark .calweek a{color:#444}
html.dark .calweek a:hover,html.dark .calfocus{background-color:#ccd6dd}
html.dark a.caloff{color:#ccd6dd}
html.dark a.caloff:hover{color:#fff;background-color:#e1e8ed}
html.dark a.caldisabled{background-color:#F5F8FA!important;color:#e1e8ed!important}
html.dark #calcurrent{background-color:#50a5e6;color:#292f33}
html.dark #caltoday{background-color:#ddd;color:#fff}
html.dark .cal{color:#e1e8ed}
html.dark .cal header{border-bottom:1px soild #eaeaea}
html.dark .prf-header{background:#292f33;text-shadow:0 1px 1px rgba(17,17,17,0.8);color:#fff}
html.dark .prf-header .prf-siteurl,html.dark .prf-header .prf-bio a,html.dark .prf-header .pretty-link{color:#fff}
html.dark .prf-header .prf-siteurl:hover,html.dark .prf-header .prf-bio a:hover,html.dark .prf-header .pretty-link:hover{color:#fff}
html.dark .prf-header-inner-overlay{background-image:linear-gradient(transparent 0,rgba(17,17,17,0.55) 100%)}
html.dark .prf .fullname{color:#fff}
html.dark .prf .username{color:#fff}
html.dark .prf-meta{border-top:1px solid #eaeaea;background:#fff}
html.dark .prf-stats li+li a{border-left:1px solid #eaeaea}
html.dark .prf-stats a{color:#8899a6}
html.dark .prf-stats a strong{color:#292F33}
html.dark .prf-stats a:hover,html.dark .prf-stats a:hover strong{color:#1c6399}
html.dark .prf .lst-profile span{color:#aab8c2}
html.dark .prf .lst-profile i{color:#aab8c2}
html.dark .prf .lst-profile a{border-right:1px solid #fff}
html.dark .prf .lst-profile a:hover span{color:#505060}
html.dark .prf .lst-profile a:hover i{color:#505060}
html.dark .detail-group{border-bottom:#292F33}
html.dark .prf-follow-status{background-color:rgba(17,17,17,0.25);color:#fff}
html.dark .profile-full-follow-status{background-color:#eaeaea}
html.dark .social-proof-container{background-color:#e1e8ed}
html.dark .profile-full{background-color:#fff}
html.dark .profile-icon{color:#8899a6}
html.dark .profile-full-avatar{background-color:#fff}
html.dark .profile-full-bio-count{color:#292f33}
html.dark .profile-full{background-color:#111}
html.dark .profile-icon{color:#ccd6dd}
html.dark .profile-full-avatar{background-color:#111}
html.dark .profile-full-bio-count{color:#ccd6dd}
html.dark .is-inverted-dark .profile-full{background-color:#fff}
html.dark .is-inverted-dark .profile-icon{color:#8899a6}
html.dark .is-inverted-dark .profile-full-avatar{background-color:#fff}
html.dark .is-inverted-dark .profile-full-bio-count{color:#292f33}
html.dark .lst li{border-bottom:1px solid #ddd}
html.dark .lst-modal{background:#fff;border:1px solid #eaeaea}
html.dark .lst .s-selected{background-color:#50a5e6;color:#fff}
html.dark .lst .s-selected .fullname,html.dark .lst .s-selected .username{color:#fff}
html.dark .lst-group .selected{background:#55acee;color:#F5F8FA}
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)}
html.dark .release-notes{background-color:#F5F8FA}
html.dark .release-notes-header-subtitle{color:#8899a6}
html.dark .release-notes-image-bullet{border:1px solid #ddd}
html.dark .startflow-background:before{background-color:#292f33;background-image:linear-gradient(36deg,#3b94d9 0%,transparent 100%)}
html.dark .startflow-link{color:#2b7bb9}
html.dark .startflow-link:hover,html.dark .startflow-link:focus,html.dark .startflow-link:active{color:#2b7bb9}
html.dark .startflow-link-on-background{color:#55acee}
html.dark .app-info-title{color:#fff}
html.dark .app-info-text p{color:#ccd6dd}
html.dark .form-legend{border-bottom:1px solid #ccd6dd;color:#111}
html.dark .startflow-panel,html.dark .startflow-panel-rounded{background-color:#fff;color:#292f33;border:1px solid #292f33}
html.dark .form-login-pwd::-webkit-input-placeholder,html.dark .form-login-email::-webkit-input-placeholder,html.dark .form-login-username::-webkit-input-placeholder{color:#999}
html.dark .form-login-pwd::placeholder,html.dark .form-login-email::placeholder,html.dark .form-login-username::placeholder{color:#999}
html.dark .privacy-info{color:#aab8c2}
html.dark .privacy-info a,html.dark .privacy-info a:visited,html.dark .privacy-info a:hover,html.dark .privacy-info a:active{color:#aab8c2}
html.dark .form-message{color:#fff}
html.dark .form-error-message{background-color:#a0041e}
html.dark .form-success-message{background-color:#5c913b}
html.dark .form-warning-message{background-color:#5c913b}
html.dark .startflow-msg-header{background-color:#ccd6dd}
html.dark .startflow-msg-warning{background-color:#ffcc4d}
html.dark .compose{background-color:#485865;color:#fff}
html.dark .compose-header{border-bottom:1px solid #66757f}
html.dark .compose-text-container{background-color:#fff}
html.dark .compose-text{color:#111}
html.dark .compose-text::-webkit-input-placeholder{color:#AAB8C2}
html.dark .compose-text::placeholder{color:#AAB8C2}
html.dark .compose-text-title{color:#88c9f9;color:#fff}
html.dark .compose-send-button-success{color:#fff}
html.dark .compose-reply-tweet{background-color:#e1e8ed;color:#292f33}
html.dark .compose-reply-tweet-remove{color:#292f33}
html.dark .compose-reply-tweet .tweet-body a{color:#2b7bb9}
html.dark .compose-reply-tweet .fullname{color:#292f33}
html.dark .compose-reply-tweet .username{color:#8899a6}
html.dark .replyto-caret{border-color:transparent transparent #fff}
html.dark .compose-message-account{color:#111}
html.dark .compose-message-recipient{border:1px solid #eaeaea}
html.dark .compose-message-recipient-input-container.is-focused{box-shadow:0 0 2px 3px #50a5e6}
html.dark .compose-media-bar-holder{background-color:#fff}
html.dark .compose-media-info-bar-holder{background-color:#fff;color:#8899a6}
html.dark .compose-media-info-bar{background:#e1e8ed}
html.dark .compose-account{color:#fff}
html.dark .compose-account-img{background-color:#66757f}
html.dark .compose-account:hover{color:#fff}
html.dark .compose-account:focus{color:#fff}
html.dark .compose-account:focus .compose-account-img{box-shadow:0 0 2px 3px #50a5e6}
html.dark .is-selected.compose-account:focus .compose-account-img{box-shadow:0 0 2px 3px #50a5e6}
html.dark .compose-account-selected{background-color:#17BF63}
html.dark .compose-remember-state{color:#fff}
html.dark .video-container .video-controls{background:rgba(0,0,0,0.5);background:linear-gradient(transparent,rgba(0,0,0,0.65))}
html.dark .column-nav-link:focus,html.dark .column-nav-link:active{color:#F5F8FA}
html.dark .column-nav-link.is-selected,html.dark .column-nav-link:hover{color:#fff}
html.dark .column-nav-item{color:#e1e8ed;background-color:#292f33}
html.dark .column-nav-link:after{color:#8899A6}
html.dark .column-nav-link .attribution{color:#8899A6}
html.dark .draggable-dragging{box-shadow:0 4px 10px rgba(17,17,17,0.8)}
html.dark .nav-user-info .username{color:#8899A6}
html.dark .nav-user-info .fullname{color:#F5F8FA}
html.dark .account-bio{color:#8899a6}
html.dark .DatePickerDropdown-menuItem--footer{border-top:1px solid #ccd6dd;background-color:#f5f8fa}
html.dark .DatePicker-monthButton{color:#1da1f2}
html.dark .DatePicker-monthButton:hover,html.dark .DatePicker-monthButton:focus{color:#005fd1}
html.dark .DatePicker-monthButton[disabled]{color:#ccd6dd}
html.dark .DatePicker-calendarDayHeader{color:#657786}
html.dark .DatePicker-calendarDay{color:#ccd6dd}
html.dark .DatePicker-calendarDay.is-selectable{color:#14171a}
html.dark .DatePicker-calendarDay.is-selectable.is-adjacentMonth{color:#657786}
html.dark .DatePicker-calendarDay.is-selectable:hover{background-color:#005fd1}
html.dark .DatePicker-calendarDay.is-withinRange{background-color:#1da1f2}
html.dark .DatePicker-calendarDay.is-withinRange.is-adjacentMonth{color:#ccd6dd}
html.dark .DatePicker-calendarDay.is-rangeStart,html.dark .DatePicker-calendarDay.is-rangeEnd{background-color:#005fd1}
html.dark .DatePicker-calendarDay.is-rangeStart.is-adjacentMonth,html.dark .DatePicker-calendarDay.is-rangeEnd.is-adjacentMonth{color:#ccd6dd}
html.dark .DatePicker--withPendingRange .DatePicker-calendarDay.is-withinRange,html.dark .DatePicker--withPendingRange .DatePicker-calendarDay.is-rangeStart,html.dark .DatePicker--withPendingRange .DatePicker-calendarDay.is-rangeEnd{border:1px solid #1da1f2;color:#14171a}
html.dark .DatePicker--withPendingRange .DatePicker-calendarDay.is-withinRange.is-adjacentMonth,html.dark .DatePicker--withPendingRange .DatePicker-calendarDay.is-rangeStart.is-adjacentMonth,html.dark .DatePicker--withPendingRange .DatePicker-calendarDay.is-rangeEnd.is-adjacentMonth{color:#657786}
html.dark .DatePicker--withPendingRange .DatePicker-calendarDay.is-rangeStart,html.dark .DatePicker--withPendingRange .DatePicker-calendarDay.is-rangeEnd{background-color:#eaf5fd}
html.dark .DatePicker-time{border-top:1px solid #ccd6dd}
html.dark .DatePicker-timeZone{color:#657786}
html.dark .Dropdown{background-color:rgba(255,255,255,0.98);box-shadow:0 1px 4px rgba(0,0,0,0.25)}
html.dark .Dropdown-detailPanel{border:1px solid #ccd6dd;background-color:#f5f8fa}
html.dark .Dropdown-divider{background-color:#ccd6dd}
html.dark .Dropdown-menuItem .Dropdown-menuItemContent,html.dark .Dropdown-menuGroupLabel{color:#14171a}
html.dark .Dropdown-menuItem .Dropdown-menuItemContent .Icon--check{color:#1da1f2}
html.dark .Dropdown-menuItem.is-focus{background-color:#1da1f2}
html.dark .Dropdown-menuItem.is-focus .User .Icon--verified::before{color:#1da1f2}
html.dark .Dropdown-menuGroupLabel{color:#657786}
html.dark .ButtonGroup>.Button.is-selected,html.dark .ButtonGroup>.Button.is-selected:visited{background-color:#1da1f2;border:1px solid #1da1f2}
html.dark .ButtonGroup>.Button.is-selected:focus,html.dark .ButtonGroup>.Button.is-selected.is-focus{box-shadow:0 0 0 2px white,0 0 0 4px #71c9f8;background:#1da1f2;border-color:#1da1f2}
html.dark .ButtonGroup>.Button.is-selected:hover,html.dark .ButtonGroup>.Button.is-selected.is-hover{background-color:#1da1f2;border-color:#1da1f2}
html.dark .ButtonGroup>.Button.is-selected:active,html.dark .ButtonGroup>.Button.is-selected.is-active{box-shadow:0 0 0 2px white,0 0 0 4px #1da1f2;background-color:#1da1f2;border-color:#1da1f2}
html.dark .ButtonGroup>.Button.is-selected[disabled],html.dark .ButtonGroup>.Button.is-selected.is-disabled,html.dark fieldset[disabled] .ButtonGroup>.Button.is-selected{background-color:#1da1f2;border-color:#1da1f2}
html.dark .ButtonGroup--tertiary>.Button.is-selected,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button.is-selected,html.dark .ButtonGroup--tertiary>.Button.is-selected:visited,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button.is-selected:visited{background-color:#657786;border:1px solid #657786}
html.dark .ButtonGroup--tertiary>.Button.is-selected:focus,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button.is-selected:focus,html.dark .ButtonGroup--tertiary>.Button.is-selected.is-focus,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button.is-selected.is-focus{box-shadow:0 0 0 2px white,0 0 0 4px #ccd6dd;background:#657786;border-color:#657786}
html.dark .ButtonGroup--tertiary>.Button.is-selected:hover,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button.is-selected:hover,html.dark .ButtonGroup--tertiary>.Button.is-selected.is-hover,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button.is-selected.is-hover{background-color:#657786;border-color:#657786}
html.dark .ButtonGroup--tertiary>.Button.is-selected:active,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button.is-selected:active,html.dark .ButtonGroup--tertiary>.Button.is-selected.is-active,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button.is-selected.is-active{box-shadow:0 0 0 2px white,0 0 0 4px #aab8c2;background-color:#657786;border-color:#657786}
html.dark .ButtonGroup--tertiary>.Button.is-selected[disabled],html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button.is-selected[disabled],html.dark .ButtonGroup--tertiary>.Button.is-selected.is-disabled,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button.is-selected.is-disabled,html.dark fieldset[disabled] .ButtonGroup--tertiary>.Button.is-selected,html.dark fieldset[disabled] .ButtonGroup--tertiary>.ButtonGroup>.Button.is-selected{background-color:#657786;border-color:#657786}
html.dark .Button,html.dark .Button:visited,html.dark .Button.is-visited{border:1px solid #1da1f2;color:#1da1f2}
html.dark .Button:focus,html.dark .Button.is-focus{box-shadow:0 0 0 2px white,0 0 0 4px #71c9f8;border-color:#1da1f2;color:#1da1f2}
html.dark .Button:hover,html.dark .Button.is-hover{background-color:#eaf5fd;color:#1da1f2}
html.dark .Button:active,html.dark .Button.is-active{box-shadow:0 0 0 2px white,0 0 0 4px #1da1f2;background:#eaf5fd;border-color:#1da1f2;color:#1da1f2}
html.dark .Button.Button--primary,html.dark .Button.Button--primary:visited,html.dark .ButtonGroup--primary>.Button,html.dark .ButtonGroup--primary>.Button:visited,html.dark .ButtonGroup--primary>.ButtonGroup>.Button,html.dark .ButtonGroup--primary>.ButtonGroup>.Button:visited{background-color:#1da1f2;border:1px solid #1da1f2}
html.dark .Button.Button--primary:focus,html.dark .Button.Button--primary.is-focus,html.dark .ButtonGroup--primary>.Button:focus,html.dark .ButtonGroup--primary>.Button.is-focus,html.dark .ButtonGroup--primary>.ButtonGroup>.Button:focus,html.dark .ButtonGroup--primary>.ButtonGroup>.Button.is-focus{box-shadow:0 0 0 2px white,0 0 0 4px #71c9f8;background:#1da1f2;border-color:#1da1f2}
html.dark .Button.Button--primary:hover,html.dark .Button.Button--primary.is-hover,html.dark .ButtonGroup--primary>.Button:hover,html.dark .ButtonGroup--primary>.Button.is-hover,html.dark .ButtonGroup--primary>.ButtonGroup>.Button:hover,html.dark .ButtonGroup--primary>.ButtonGroup>.Button.is-hover{background-color:#005fd1;border-color:#005fd1}
html.dark .Button.Button--primary:active,html.dark .Button.Button--primary.is-active,html.dark .ButtonGroup--primary>.Button:active,html.dark .ButtonGroup--primary>.Button.is-active,html.dark .ButtonGroup--primary>.ButtonGroup>.Button:active,html.dark .ButtonGroup--primary>.ButtonGroup>.Button.is-active{box-shadow:0 0 0 2px white,0 0 0 4px #1da1f2;background-color:#005fd1;border-color:#005fd1}
html.dark .Button.Button--primary[disabled],html.dark .Button.Button--primary.is-disabled,html.dark fieldset[disabled] .Button.Button--primary,html.dark .ButtonGroup--primary>.Button[disabled],html.dark .ButtonGroup--primary>.Button.is-disabled,html.dark fieldset[disabled] .ButtonGroup--primary>.Button,html.dark .ButtonGroup--primary>.ButtonGroup>.Button[disabled],html.dark .ButtonGroup--primary>.ButtonGroup>.Button.is-disabled,html.dark fieldset[disabled] .ButtonGroup--primary>.ButtonGroup>.Button{background-color:#1da1f2;border-color:#1da1f2}
html.dark .Button.Button--tertiary,html.dark .Button.Button--tertiary:visited,html.dark .ButtonGroup--tertiary>.Button,html.dark .ButtonGroup--tertiary>.Button:visited,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button:visited{border:1px solid #657786;color:#657786}
html.dark .Button.Button--tertiary:focus,html.dark .Button.Button--tertiary.is-focus,html.dark .ButtonGroup--tertiary>.Button:focus,html.dark .ButtonGroup--tertiary>.Button.is-focus,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button:focus,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button.is-focus{box-shadow:0 0 0 2px white,0 0 0 4px #ccd6dd;border-color:#657786;color:#657786}
html.dark .Button.Button--tertiary:hover,html.dark .Button.Button--tertiary.is-hover,html.dark .ButtonGroup--tertiary>.Button:hover,html.dark .ButtonGroup--tertiary>.Button.is-hover,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button:hover,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button.is-hover{background-color:#f5f8fa;border-color:#657786;color:#657786}
html.dark .Button.Button--tertiary:active,html.dark .Button.Button--tertiary.is-active,html.dark .ButtonGroup--tertiary>.Button:active,html.dark .ButtonGroup--tertiary>.Button.is-active,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button:active,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button.is-active{box-shadow:0 0 0 2px white,0 0 0 4px #657786;background-color:#f5f8fa;border-color:#657786;color:#657786}
html.dark .Button.Button--tertiary[disabled],html.dark .Button.Button--tertiary.is-disabled,html.dark fieldset[disabled] .Button.Button--tertiary,html.dark .ButtonGroup--tertiary>.Button[disabled],html.dark .ButtonGroup--tertiary>.Button.is-disabled,html.dark fieldset[disabled] .ButtonGroup--tertiary>.Button,html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button[disabled],html.dark .ButtonGroup--tertiary>.ButtonGroup>.Button.is-disabled,html.dark fieldset[disabled] .ButtonGroup--tertiary>.ButtonGroup>.Button{border-color:#657786}
html.dark .Button.Button--success,html.dark .Button.Button--success:visited{background-color:#17bf63;border:1px solid #17bf63}
html.dark .Button.Button--success:focus,html.dark .Button.Button--success.is-focus{box-shadow:0 0 0 2px white,0 0 0 4px #68e090;background:#17bf63;border-color:#17bf63}
html.dark .Button.Button--success:hover,html.dark .Button.Button--success.is-hover{background-color:#008951;border-color:#008951}
html.dark .Button.Button--success:active,html.dark .Button.Button--success.is-active{box-shadow:0 0 0 2px white,0 0 0 4px #17bf63;background-color:#008951;border-color:#008951}
html.dark .Button.Button--success[disabled],html.dark .Button.Button--success.is-disabled,html.dark fieldset[disabled] .Button.Button--success{background-color:#17bf63;border-color:#17bf63}
html.dark .Button.Button--warning,html.dark .Button.Button--warning:visited{background-color:#ffad1f;border:1px solid #ffad1f}
html.dark .Button.Button--warning:focus,html.dark .Button.Button--warning.is-focus{box-shadow:0 0 0 2px white,0 0 0 4px #ffd03f;background:#ffad1f;border-color:#ffad1f}
html.dark .Button.Button--warning:hover,html.dark .Button.Button--warning.is-hover{background-color:#f98e00;border-color:#f98e00}
html.dark .Button.Button--warning:active,html.dark .Button.Button--warning.is-active{box-shadow:0 0 0 2px white,0 0 0 4px #ffad1f;background-color:#f98e00;border-color:#f98e00}
html.dark .Button.Button--warning[disabled],html.dark .Button.Button--warning.is-disabled,html.dark fieldset[disabled] .Button.Button--warning{background-color:#ffad1f;border-color:#ffad1f}
html.dark .Button.Button--danger,html.dark .Button.Button--danger:visited{background-color:#e0245e;border:1px solid #e0245e}
html.dark .Button.Button--danger:focus,html.dark .Button.Button--danger.is-focus{box-shadow:0 0 0 2px white,0 0 0 4px #f6809a;background:#e0245e;border-color:#e0245e}
html.dark .Button.Button--danger:hover,html.dark .Button.Button--danger.is-hover{background-color:#a01744;border-color:#a01744}
html.dark .Button.Button--danger:active,html.dark .Button.Button--danger.is-active{box-shadow:0 0 0 2px white,0 0 0 4px #e0245e;background-color:#a01744;border-color:#a01744}
html.dark .Button.Button--danger[disabled],html.dark .Button.Button--danger.is-disabled,html.dark fieldset[disabled] .Button.Button--danger{background-color:#e0245e;border-color:#e0245e}
html.dark .Button.Button--link{color:#1b95e0}
html.dark .Button.Button--dangerLink{color:#e0245e}
html.dark .ProgressBar{background-color:#ccd6dd;color:#1da1f2}
html.dark .ProgressBar::-webkit-progress-bar{background-color:#ccd6dd}
html.dark .ProgressBar.ProgressBar:indeterminate{border-top:1.5px solid #ccd6dd;border-bottom:1.5px solid #ccd6dd}
html.dark .ProgressBar::-webkit-progress-value{background-color:#1da1f2}
html.dark .ProgressBar--red{color:#e0245e}
html.dark .ProgressBar--red::-webkit-progress-value{background-color:#e0245e}
html.dark .ProgressBar--yellow{color:#ffad1f}
html.dark .ProgressBar--yellow::-webkit-progress-value{background-color:#ffad1f}
html.dark .ProgressBar--green{color:#17bf63}
html.dark .ProgressBar--green::-webkit-progress-value{background-color:#17bf63}
html.dark .ProgressBar--blue{color:#1da1f2}
html.dark .ProgressBar--blue::-webkit-progress-value{background-color:#1da1f2}
html.dark .ProgressBar--white{background-color:#657786}
html.dark .ProgressBar--white::-webkit-progress-bar{background-color:#657786}
html.dark .ProgressBar--large.ProgressBar:indeterminate{border-top:4.5px solid #ccd6dd;border-bottom:4.5px solid #ccd6dd}
html.dark .Notification-inner{box-shadow:0 2px 4px rgba(0,0,0,0.1)}
html.dark .Notification-icon{background-color:#1da1f2}
html.dark .Notification-content{border:1px solid #ccd6dd}
html.dark .Notification-title+.Notification-body{color:#657786}
html.dark .Notification-closeButton{color:#aab8c2}
html.dark .Notification-closeButton:hover,html.dark .Notification-closeButton:focus{color:#657786}
html.dark .Notification--green .Notification-icon{background-color:#17bf63}
html.dark .Notification--red .Notification-icon{background-color:#e0245e}
html.dark .ModalOverlay{background-color:rgba(20,23,26,0.8)}
html.dark .Drawer{background-color:#fff}
html.dark .Drawer:not([dir="rtl"]){border-left:1px solid #ccd6dd}
html.dark .Drawer[dir="rtl"]{border-right:1px solid #ccd6dd}
html.dark .Drawer--modal{background-color:#fff}
html.dark .Drawer-close{color:#aab8c2}
html.dark .Drawer-close:hover{color:#657786}
html.dark .DialogContent-title{border-bottom:2px solid #ccd6dd}
html.dark .DialogContent-footer{background-color:#f5f8fa;border-top:1px solid #ccd6dd}
html.dark .Tooltip{border-color:#ccd6dd;box-shadow:0 2px 4px rgba(0,0,0,0.1)}
html.dark .Tooltip .Tooltip-content{color:#14171a}
html.dark .Tooltip .Tooltip-close{color:#aab8c2}
html.dark .Tooltip .Tooltip-triangleOuter{border-color:transparent #ccd6dd transparent transparent}
html.dark .Tooltip.Tooltip--left .Tooltip-triangleOuter,html.dark .Tooltip.Tooltip--topLeft .Tooltip-triangleOuter,html.dark .Tooltip.Tooltip--bottomLeft .Tooltip-triangleOuter{border-color:transparent transparent transparent #ccd6dd}
html.dark .Tooltip.Tooltip--top .Tooltip-triangleOuter,html.dark .Tooltip.Tooltip--topLeft .Tooltip-triangleOuter{border-color:#ccd6dd transparent transparent}
html.dark .Tooltip.Tooltip--bottom .Tooltip-triangleOuter,html.dark .Tooltip.Tooltip--bottomLeft .Tooltip-triangleOuter{border-color:transparent transparent #ccd6dd}
html.dark .Tooltip--dark{background:#14171a;border-color:#14171a}
html.dark .Tooltip--dark .Tooltip-triangleOuter{border-color:transparent #14171a transparent transparent}
html.dark .Tooltip--dark .Tooltip-triangleInner{border-color:transparent #14171a transparent transparent}
html.dark .Tooltip--dark.Tooltip--left .Tooltip-triangleOuter,html.dark .Tooltip--dark.Tooltip--topLeft .Tooltip-triangleOuter,html.dark .Tooltip--dark.Tooltip--bottomLeft .Tooltip-triangleOuter{border-color:transparent transparent transparent #14171a}
html.dark .Tooltip--dark.Tooltip--left .Tooltip-triangleInner,html.dark .Tooltip--dark.Tooltip--topLeft .Tooltip-triangleInner,html.dark .Tooltip--dark.Tooltip--bottomLeft .Tooltip-triangleInner{border-color:transparent transparent transparent #14171a}
html.dark .Tooltip--dark.Tooltip--top .Tooltip-triangleOuter,html.dark .Tooltip--dark.Tooltip--topLeft .Tooltip-triangleOuter{border-color:#14171a transparent transparent}
html.dark .Tooltip--dark.Tooltip--top .Tooltip-triangleInner,html.dark .Tooltip--dark.Tooltip--topLeft .Tooltip-triangleInner{border-color:#14171a transparent transparent}
html.dark .Tooltip--dark.Tooltip--bottom .Tooltip-triangleOuter,html.dark .Tooltip--dark.Tooltip--bottomLeft .Tooltip-triangleOuter{border-color:transparent transparent #14171a}
html.dark .Tooltip--dark.Tooltip--bottom .Tooltip-triangleInner,html.dark .Tooltip--dark.Tooltip--bottomLeft .Tooltip-triangleInner{border-color:transparent transparent #14171a}
html.dark .Tooltip--intro{background:#1da1f2;border-color:#1da1f2}
html.dark .Tooltip--intro .Tooltip-triangleOuter{border-color:transparent #1da1f2 transparent transparent}
html.dark .Tooltip--intro .Tooltip-triangleInner{border-color:transparent #1da1f2 transparent transparent}
html.dark .Tooltip--intro.Tooltip--left .Tooltip-triangleOuter,html.dark .Tooltip--intro.Tooltip--topLeft .Tooltip-triangleOuter,html.dark .Tooltip--intro.Tooltip--bottomLeft .Tooltip-triangleOuter{border-color:transparent transparent transparent #1da1f2}
html.dark .Tooltip--intro.Tooltip--left .Tooltip-triangleInner,html.dark .Tooltip--intro.Tooltip--topLeft .Tooltip-triangleInner,html.dark .Tooltip--intro.Tooltip--bottomLeft .Tooltip-triangleInner{border-color:transparent transparent transparent #1da1f2}
html.dark .Tooltip--intro.Tooltip--top .Tooltip-triangleOuter,html.dark .Tooltip--intro.Tooltip--topLeft .Tooltip-triangleOuter{border-color:#1da1f2 transparent transparent}
html.dark .Tooltip--intro.Tooltip--top .Tooltip-triangleInner,html.dark .Tooltip--intro.Tooltip--topLeft .Tooltip-triangleInner{border-color:#1da1f2 transparent transparent}
html.dark .Tooltip--intro.Tooltip--bottom .Tooltip-triangleOuter,html.dark .Tooltip--intro.Tooltip--bottomLeft .Tooltip-triangleOuter{border-color:transparent transparent #1da1f2}
html.dark .Tooltip--intro.Tooltip--bottom .Tooltip-triangleInner,html.dark .Tooltip--intro.Tooltip--bottomLeft .Tooltip-triangleInner{border-color:transparent transparent #1da1f2}
html.dark .Tooltip-close:hover{color:#657786}
html.dark .TooltipHoverTarget{background-image:linear-gradient(to right,#1da1f2 50%,transparent 0%)}
html.dark .LegendItem-color{background-color:#aab8c2}
html.dark .LegendItem--gray .LegendItem-color{background-color:#aab8c2}
html.dark .LegendItem--blue .LegendItem-color{background-color:#1da1f2}
html.dark .LegendItem--green .LegendItem-color{background-color:#17bf63}
html.dark .LegendItem--yellow .LegendItem-color{background-color:#ffad1f}
html.dark .LegendItem--red .LegendItem-color{background-color:#e0245e}
html.dark .LegendItem--purple .LegendItem-color{background-color:#794bc4}
html.dark .DateRangeDropdown-menuItem--footer{border-top:1px solid #ccd6dd;background-color:#f5f8fa}
html.dark .DateRange:not([dir='rtl']) .DateRange-presets{border-right:1px solid #ccd6dd}
html.dark .DateRange:not([dir='rtl']) .DateRange-pickersRow:first-child .DateRange-pickerWrapper:last-child{border-left:1px solid #ccd6dd}
html.dark .DateRange[dir='rtl'] .DateRange-presets{border-left:1px solid #ccd6dd}
html.dark .DateRange[dir='rtl'] .DateRange-pickersRow:first-child .DateRange-pickerWrapper:last-child{border-right:1px solid #ccd6dd}
html.dark .PillGroup .Pill.is-selected{background:#005fd1}
html.dark .PillGroup .Pill>a,html.dark .PillGroup .Pill>button{color:#1b95e0}
html.dark .PillGroup .Pill>a:hover,html.dark .PillGroup .Pill>button:hover{background:#eaf5fd}
html.dark .PillGroup .Pill>a:focus,html.dark .PillGroup .Pill>button:focus{box-shadow:0 0 0 2px white,0 0 0 4px #71c9f8}
html.dark .PillGroup .Pill>a:active,html.dark .PillGroup .Pill>button:active{box-shadow:0 0 0 2px white,0 0 0 4px #1da1f2}
html.dark .PillGroup .Pill.is-selected>a,html.dark .PillGroup .Pill.is-selected>button{color:#FFF}
html.dark .FormInput,html.dark .FormTextarea{border:1px solid #ccd6dd;color:#14171a}
html.dark .FormInput-characterCount{color:#ccd6dd}
html.dark .FormInput-characterCount.is-negative{color:#e0245e}
html.dark .FormInput::-webkit-input-placeholder,html.dark .FormTextarea::-webkit-input-placeholder{color:#aab8c2}
html.dark .FormInput[disabled],html.dark .FormTextarea[disabled],html.dark .FormInput.is-disabled,html.dark .FormTextarea.is-disabled,html.dark fieldset[disabled] .FormInput,html.dark fieldset[disabled] .FormTextarea,html.dark .FormInputWrapper.is-disabled .FormInput{background:#f5f8fa;color:#657786}
html.dark .FormInput.is-error,html.dark .FormTextarea.is-error,html.dark .FormInput.is-invalid,html.dark .FormTextarea.is-invalid,html.dark .FormInputWrapper.is-invalid .FormInput{border-color:#e0245e}
html.dark .FormInput.is-error:focus,html.dark .FormTextarea.is-error:focus,html.dark .FormInput.is-invalid:focus,html.dark .FormTextarea.is-invalid:focus,html.dark .FormInput.is-error.is-focus,html.dark .FormTextarea.is-error.is-focus,html.dark .FormInput.is-invalid.is-focus,html.dark .FormTextarea.is-invalid.is-focus,html.dark .FormInputWrapper.is-invalid .FormInput:focus,html.dark .FormInputWrapper.is-invalid .FormInput.is-focus{border-color:#e0245e;box-shadow:inset 0 0 0 1px #e0245e}
html.dark .FormInput.is-valid,html.dark .FormTextarea.is-valid{border-color:#17bf63}
html.dark .FormInput.is-valid:focus,html.dark .FormTextarea.is-valid:focus,html.dark .FormInput.is-valid.is-focus,html.dark .FormTextarea.is-valid.is-focus{border-color:#17bf63;box-shadow:inset 0 0 0 1px #17bf63}
html.dark .FormInput:focus,html.dark .FormTextarea:focus,html.dark .FormInput.is-focus,html.dark .FormTextarea.is-focus,html.dark .FormInputWrapper.is-focus .FormInput{border-color:#1da1f2;box-shadow:inset 0 0 0 1px #1da1f2}
html.dark .FormOption.is-disabled{color:#aab8c2}
html.dark .FormInputWrapper-absoluteStartAdornment .Icon,html.dark .FormInputWrapper-absoluteEndAdornment .Icon{color:#aab8c2}
html.dark .FormInputWrapper-absoluteStartAdornment .Icon--caretDown,html.dark .FormInputWrapper-absoluteEndAdornment .Icon--caretDown{color:#14171a}
html.dark .FormInputWrapper.is-disabled .FormInputWrapper-absoluteStartAdornment .Icon--caretDown,html.dark .FormInputWrapper.is-disabled .FormInputWrapper-absoluteEndAdornment .Icon--caretDown,html.dark fieldset[disabled] .FormInputWrapper-absoluteStartAdornment .Icon--caretDown,html.dark fieldset[disabled] .FormInputWrapper-absoluteEndAdornment .Icon--caretDown{color:#657786}
html.dark .FormInputWrapper-startAdornment,html.dark .FormInputWrapper-endAdornment{border:1px solid #ccd6dd}
html.dark .FormField.is-invalid .FormField-validationMessage{color:#e0245e}
html.dark .FormField.is-valid .FormField-validationMessage{color:#008951}
html.dark .FormField-description{color:#657786}
html.dark .Token-checkbox input[type="checkbox"]:focus+.Icon{box-shadow:0 0 0 5px white,0 0 0 7px #71c9f8}
html.dark .Token--small .Token-trigger input[type="checkbox"]:focus+.Icon,html.dark .TokenGroup--small>.Token .Token-trigger input[type="checkbox"]:focus+.Icon,html.dark .TokenGroup--small>.TokenGroup>.Token .Token-trigger input[type="checkbox"]:focus+.Icon{box-shadow:0 0 0 3px white,0 0 0 5px #71c9f8}
html.dark .Token--xsmall .Token-trigger input[type="checkbox"]:focus+.Icon,html.dark .TokenGroup--xsmall>.Token .Token-trigger input[type="checkbox"]:focus+.Icon,html.dark .TokenGroup--xsmall>.TokenGroup>.Token .Token-trigger input[type="checkbox"]:focus+.Icon{box-shadow:0 0 0 0 white,0 0 0 2px #71c9f8}
html.dark .Token,html.dark .Token--blue{border-color:#1da1f2;color:#1da1f2}
html.dark .Token .Token-adornment,html.dark .Token--blue .Token-adornment{background-color:#1da1f2}
html.dark .Token:hover,html.dark .Token--blue:hover{background-color:#97e3ff;color:#005fd1}
html.dark .Token.is-selected,html.dark .Token--blue.is-selected{background-color:#1da1f2}
html.dark .Token.is-selected .Token-adornment,html.dark .Token--blue.is-selected .Token-adornment{color:#1da1f2}
html.dark .Token.is-selected:hover,html.dark .Token--blue.is-selected:hover{background-color:#005fd1;border-color:#005fd1}
html.dark .Token:focus,html.dark .Token--blue:focus,html.dark .Token.is-focused,html.dark .Token--blue.is-focused{box-shadow:0 0 0 1px white,0 0 0 3px #71c9f8}
html.dark .Token--green{border-color:#17bf63;color:#17bf63}
html.dark .Token--green .Token-adornment{background-color:#17bf63}
html.dark .Token--green:hover{background-color:#a5f2aa;color:#008951}
html.dark .Token--green.is-selected{background-color:#17bf63}
html.dark .Token--green.is-selected .Token-adornment{color:#17bf63}
html.dark .Token--green.is-selected:hover{background-color:#008951;border-color:#008951}
html.dark .Token--green:focus,html.dark .Token--green.is-focused{box-shadow:0 0 0 1px white,0 0 0 3px #68e090}
html.dark .Token--red{border-color:#e0245e;color:#e0245e}
html.dark .Token--red .Token-adornment{background-color:#e0245e}
html.dark .Token--red:hover{background-color:#ffb8c2;color:#a01744}
html.dark .Token--red.is-selected{background-color:#e0245e}
html.dark .Token--red.is-selected .Token-adornment{color:#e0245e}
html.dark .Token--red.is-selected:hover{background-color:#a01744;border-color:#a01744}
html.dark .Token--red:focus,html.dark .Token--red.is-focused{box-shadow:0 0 0 1px white,0 0 0 3px #f6809a}
html.dark .Token--purple{border-color:#794bc4;color:#794bc4}
html.dark .Token--purple .Token-adornment{background-color:#794bc4}
html.dark .Token--purple:hover{background-color:#c7b4fa;color:#4f0299}
html.dark .Token--purple.is-selected{background-color:#794bc4}
html.dark .Token--purple.is-selected .Token-adornment{color:#794bc4}
html.dark .Token--purple.is-selected:hover{background-color:#4f0299;border-color:#4f0299}
html.dark .Token--purple:focus,html.dark .Token--purple.is-focused{box-shadow:0 0 0 1px white,0 0 0 3px #a37ced}
html.dark .Token--yellow{border-color:#ffad1f;color:#ffad1f}
html.dark .Token--yellow .Token-adornment{background-color:#ffad1f}
html.dark .Token--yellow:hover{background-color:#ffe76e;color:#f98e00}
html.dark .Token--yellow.is-selected{background-color:#ffad1f}
html.dark .Token--yellow.is-selected .Token-adornment{color:#ffad1f}
html.dark .Token--yellow.is-selected:hover{background-color:#f98e00;border-color:#f98e00}
html.dark .Token--yellow:focus,html.dark .Token--yellow.is-focused{box-shadow:0 0 0 1px white,0 0 0 3px #ffd03f}
html.dark .Token--gray{border-color:#657786;color:#657786}
html.dark .Token--gray .Token-adornment{background-color:#657786}
html.dark .Token--gray:hover{background-color:#e6ecf0;color:#657786}
html.dark .Token--gray.is-selected{background-color:#657786}
html.dark .Token--gray.is-selected .Token-adornment{color:#657786}
html.dark .Token--gray.is-selected:hover{background-color:#aab8c2;border-color:#aab8c2}
html.dark .Token--gray:focus,html.dark .Token--gray.is-focused{box-shadow:0 0 0 1px white,0 0 0 3px #aab8c2}
html.dark .FormTokenInput-input::-webkit-input-placeholder{color:#aab8c2}
html.dark .DataPoint .DataPoint-label{color:#657786}
html.dark .DataPoint .DataPoint-info{color:#14171a}
html.dark .DataPoint .DataPoint-trend--negative{color:#e0245e}
html.dark .DataPoint .DataPoint-trend--positive{color:#17bf63}
html.dark .DataPoint--withBottomBorder{border-bottom:1px solid #ccd6dd}
html.dark .FormTokenInput.FormTextarea::-webkit-input-placeholder{color:#8899A6}
html.dark .FormTokenInput.FormTextarea::placeholder{color:#8899A6}
html.dark .DatePicker.date-unselected .is-rangeStart,html.dark .DatePicker.date-unselected .is-rangeEnd{color:#14171a}
html.dark .DatePicker.date-unselected .is-rangeStart:hover,html.dark .DatePicker.date-unselected .is-rangeEnd:hover{background-color:#005091;color:#ffffff}
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 .tweet>.color-twitter-blue{color:#8bd!important}

View File

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

View File

@@ -34,7 +34,7 @@ enabled(){
// button // button
var buttonHTML = '<button class="manage-templates-btn needsclick btn btn-on-blue full-width txt-left margin-b--12 padding-v--9"><i class="icon icon-bookmark"></i><span class="label padding-ls">Manage templates</span></button>'; var buttonHTML = '<button class="manage-templates-btn needsclick btn btn-on-blue full-width txt-left margin-b--12 padding-v--6 padding-h--12"><i class="icon icon-bookmark"></i><span class="label padding-ls">Manage templates</span></button>';
this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"]; this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="js-tweet-type-button">', buttonHTML+'<div class="js-tweet-type-button">'); TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="js-tweet-type-button">', buttonHTML+'<div class="js-tweet-type-button">');

View File

@@ -157,7 +157,6 @@
thumbSizeClass: "media-size-medium" thumbSizeClass: "media-size-medium"
})); }));
html.css("border", "0");
html.find("footer").last().remove(); // apparently withTweetActions breaks for certain tweets, nice html.find("footer").last().remove(); // apparently withTweetActions breaks for certain tweets, nice
html.find(".js-quote-detail").removeClass("is-actionable margin-b--8"); // prevent quoted tweets from changing the cursor and reduce bottom margin html.find(".js-quote-detail").removeClass("is-actionable margin-b--8"); // prevent quoted tweets from changing the cursor and reduce bottom margin
@@ -186,6 +185,12 @@
return $(this).text() === "Show this thread"; return $(this).text() === "Show this thread";
}).first().each(function(){ }).first().each(function(){
this.id = "tduck-show-thread"; this.id = "tduck-show-thread";
let moveBefore = html.find(".tweet-body > .js-media, .tweet-body > .js-media-preview-container, .quoted-tweet");
if (moveBefore){
$(this).css("margin-top", "5px").removeClass("margin-b--5").parent("span").detach().insertBefore(moveBefore);
}
}); });
let type = tweet.getChirpType(); let type = tweet.getChirpType();
@@ -194,6 +199,9 @@
html.find(".js-user-actions-menu").parent().remove(); html.find(".js-user-actions-menu").parent().remove();
html.find(".account-bio").removeClass("padding-t--5").css("padding-top", "2px"); html.find(".account-bio").removeClass("padding-t--5").css("padding-top", "2px");
} }
else if ((type.startsWith("favorite") || type.startsWith("retweet")) && tweet.isAboutYou()){
html.children().first().addClass("td-notification-padded");
}
else if (type.includes("list_member")){ else if (type.includes("list_member")){
html.find(".activity-header").css("margin-top", "2px"); html.find(".activity-header").css("margin-top", "2px");
html.find(".avatar").first().css("margin-bottom", "0"); html.find(".avatar").first().css("margin-bottom", "0");
@@ -280,18 +288,20 @@
"<html "+Array.prototype.map.call(document.documentElement.attributes, ele => `${ele.name}="${ele.value}"`).join(" ")+"><head>" "<html "+Array.prototype.map.call(document.documentElement.attributes, ele => `${ele.name}="${ele.value}"`).join(" ")+"><head>"
]; ];
$(document.head).children("link[href*='css/font.']:first,link[href*='css/app-"+themeName+".']:first,meta[charset],meta[http-equiv]").each(function(){ $(document.head).children("link[rel='stylesheet'],meta[charset]").each(function(){
tags.push($(this)[0].outerHTML); tags.push($(this)[0].outerHTML);
}); });
tags.push("<style type='text/css'>"); tags.push("<style type='text/css'>");
tags.push("body { background: "+getClassStyleProperty("column", "background-color")+" !important }"); // set background color tags.push("body { background: "+getClassStyleProperty("column", "background-color")+" !important }"); // set background color
tags.push("body::before { content: none !important }"); // remove background gradient
tags.push(".column { background: transparent !important }"); // remove background color from columns
tags.push("a[data-full-url] { word-break: break-all !important }"); // break long urls tags.push("a[data-full-url] { word-break: break-all !important }"); // break long urls
tags.push(".media-item, .media-preview { border-radius: 1px !important }"); // square-ify media tags.push(".media-item, .media-preview { border-radius: 1px !important }"); // square-ify media
tags.push(".quoted-tweet { border-radius: 0 !important }"); // square-ify quoted tweets tags.push(".quoted-tweet { border-radius: 0 !important }"); // square-ify quoted tweets
tags.push(".tweet-context .nbfc { text-overflow: ellipsis !important; white-space: nowrap !important }"); // force ellipsis on long usernames tags.push(".activity-header.has-source-avatar { margin-bottom: 4px !important }"); // tweak distance between avatar and text
tags.push(".activity-header { align-items: center !important; margin-bottom: 4px !important }"); // tweak alignment of avatar and text in notifications tags.push(".activity-header .tweet-timestamp { line-height: unset !important }"); // fix timestamp position
tags.push(".activity-header .tweet-timestamp { line-height: unset !important }"); // fix timestamp position in notifications tags.push(".activity-header .icon-user-filled { vertical-align: sub !important; }"); // fix follow icon position
tags.push("#tduck-show-thread { display: inline-block !important; cursor: pointer }"); tags.push("#tduck-show-thread { display: inline-block !important; cursor: pointer }");
if (fontSizeName === "smallest"){ if (fontSizeName === "smallest"){
@@ -446,21 +456,6 @@
data.setData("text/html", `<a href="${url}">${url}</a>`); data.setData("text/html", `<a href="${url}">${url}</a>`);
}); });
if (ensurePropertyExists(TD, "services", "TwitterUser", "prototype", "fromJSONObject")){
const prevFunc = TD.services.TwitterUser.prototype.fromJSONObject;
TD.services.TwitterUser.prototype.fromJSONObject = function(){
let obj = prevFunc.apply(this, arguments);
let e = arguments[0].entities;
if (e && e.url && e.url.urls && e.url.urls.length && e.url.urls[0].expanded_url){
obj.url = e.url.urls[0].expanded_url;
}
return obj;
};
}
if (ensurePropertyExists(TD, "services", "TwitterMedia", "prototype", "fromMediaEntity")){ if (ensurePropertyExists(TD, "services", "TwitterMedia", "prototype", "fromMediaEntity")){
const prevFunc = TD.services.TwitterMedia.prototype.fromMediaEntity; const prevFunc = TD.services.TwitterMedia.prototype.fromMediaEntity;
@@ -480,6 +475,66 @@
}; };
} }
//
// Block: Bypass t.co in user profiles and setup a top tier account bamboozle scheme.
//
(function(){
const realDisplayName = "TweetDuck";
const realAvatar = "https://ton.twimg.com/tduck/avatar";
const accountId = "957608948189880320";
if (ensurePropertyExists(TD, "services", "TwitterUser", "prototype", "fromJSONObject")){
const prevFunc = TD.services.TwitterUser.prototype.fromJSONObject;
TD.services.TwitterUser.prototype.fromJSONObject = function(){
let obj = prevFunc.apply(this, arguments);
let e = arguments[0].entities;
if (obj.id === accountId){
obj.name = realDisplayName;
obj.emojifiedName = realDisplayName;
obj.profileImageURL = realAvatar;
obj.url = "https://tweetduck.chylex.com";
obj.entities.url.urls = [{
url: obj.url,
expanded_url: obj.url,
display_url: "tweetduck.chylex.com",
indices: [ 0, 23 ]
}];
}
else if (e && e.url && e.url.urls && e.url.urls.length && e.url.urls[0].expanded_url){
obj.url = e.url.urls[0].expanded_url;
}
return obj;
};
}
if (ensurePropertyExists(TD, "services", "TwitterClient", "prototype", "typeaheadSearch")){
const prevFunc = TD.services.TwitterClient.prototype.typeaheadSearch;
TD.services.TwitterClient.prototype.typeaheadSearch = function(data, onSuccess, onError){
if (data.query && data.query.toLowerCase().endsWith("tweetduck")){
data.query = "TryMyAwesomeApp";
}
return prevFunc.call(this, data, function(result){
for(let user of result.users){
if (user.id_str === accountId){
user.name = realDisplayName;
user.profile_image_url = realAvatar;
user.profile_image_url_https = realAvatar;
break;
}
}
onSuccess.apply(this, arguments);
}, onError);
};
}
})();
// //
// Block: Include additional information in context menus. // Block: Include additional information in context menus.
// //
@@ -507,7 +562,6 @@
// //
// Block: Hook into the notification sound effect. // Block: Hook into the notification sound effect.
// //
HTMLAudioElement.prototype.play = prependToFunction(HTMLAudioElement.prototype.play, function(){ HTMLAudioElement.prototype.play = prependToFunction(HTMLAudioElement.prototype.play, function(){
return $TDX.muteNotifications; return $TDX.muteNotifications;
}); });
@@ -626,7 +680,11 @@
let isReply = !isDetail && (parent.hasClass("js-replies-to") || parent.hasClass("js-replies-before")); let isReply = !isDetail && (parent.hasClass("js-replies-to") || parent.hasClass("js-replies-before"));
selectedTweet = selectedTweet.clone(); selectedTweet = selectedTweet.clone();
selectedTweet.children().first().addClass($(document.documentElement).attr("class")).css("padding-bottom", "0");
let container = selectedTweet.children().first();
container.addClass($(document.documentElement).attr("class"));
container.addClass($(document.body).attr("class"));
container.css("padding-bottom", "0");
setImportantProperty(selectedTweet.find(".js-tweet-text"), "margin-bottom", "8px"); setImportantProperty(selectedTweet.find(".js-tweet-text"), "margin-bottom", "8px");
setImportantProperty(selectedTweet.find(".js-quote-detail"), "margin-bottom", "10px"); setImportantProperty(selectedTweet.find(".js-quote-detail"), "margin-bottom", "10px");
@@ -646,7 +704,10 @@
selectedTweet.find("footer").last().prevUntil(":not(.txt-mute)").addBack().remove(); // footer, date, location selectedTweet.find("footer").last().prevUntil(":not(.txt-mute)").addBack().remove(); // footer, date, location
} }
else{ else{
setImportantProperty(selectedTweet.find(".js-media-preview-container"), "margin-bottom", "10px"); setImportantProperty(selectedTweet.find(".js-media-preview-container").filter(function(){
return $(this).closest(".js-quote-detail").length === 0;
}), "margin-bottom", "10px");
selectedTweet.find("footer").last().remove(); selectedTweet.find("footer").last().remove();
} }
@@ -655,6 +716,10 @@
selectedTweet.find(".thread").remove(); selectedTweet.find(".thread").remove();
} }
selectedTweet.find("p.link-complex-target").filter(function(){
return $(this).text() === "Show this thread";
}).first().remove();
selectedTweet.find(".js-poll-link").remove(); selectedTweet.find(".js-poll-link").remove();
selectedTweet.find(".td-screenshot-remove").remove(); selectedTweet.find(".td-screenshot-remove").remove();
@@ -664,9 +729,13 @@
width: tweetWidth+"px" width: tweetWidth+"px"
}).appendTo(document.body); }).appendTo(document.body);
let realHeight = Math.floor(testTweet.height()); let testTweetAvatar = testTweet.find(".tweet-avatar").first();
let avatarBottom = testTweetAvatar.offset().top+testTweetAvatar.height();
let realHeight = Math.floor(Math.max(testTweet.height(), avatarBottom+10));
testTweet.remove(); testTweet.remove();
selectedTweet.find(".js-stream-item-content").first().css("height", "100vh");
$TD.screenshotTweet(selectedTweet.html(), tweetWidth, realHeight); $TD.screenshotTweet(selectedTweet.html(), tweetWidth, realHeight);
} }
}; };
@@ -870,7 +939,7 @@
$(".js-compose-text", ".js-docked-compose").focus(); $(".js-compose-text", ".js-docked-compose").focus();
}; };
$(".js-drawer[data-drawer='compose']").delegate(".js-account-list > .js-account-item", "click", onAccountClick); $(".js-account-list", ".js-docked-compose").delegate(".js-account-item", "click", onAccountClick);
return if !ensurePropertyExists(TD, "components", "AccountSelector", "prototype", "refreshPostingAccounts"); return if !ensurePropertyExists(TD, "components", "AccountSelector", "prototype", "refreshPostingAccounts");

View File

@@ -12,6 +12,10 @@
height: 328px; height: 328px;
} }
#td-introduction-modal .mdl-inner {
padding-top: 0;
}
#td-introduction-modal .mdl-header-title { #td-introduction-modal .mdl-header-title {
cursor: default; cursor: default;
} }
@@ -44,10 +48,6 @@
margin-top: 15px; margin-top: 15px;
} }
#td-introduction-modal p:last-child {
margin-top: 18px;
}
#td-introduction-modal footer { #td-introduction-modal footer {
padding: 10px 0; padding: 10px 0;
} }
@@ -82,10 +82,10 @@
<div class="mdl-inner"> <div class="mdl-inner">
<div class="mdl-content"> <div class="mdl-content">
<p>Thank you for downloading TweetDuck!</p> <p>Thank you for downloading TweetDuck!</p>
<p><a id="td-introduction-follow" href="#">Follow @TryMyAwesomeApp</a> for latest news and updates about the app.</p>
<div class="main-menu"></div> <div class="main-menu"></div>
<p><strong>Right-click anywhere</strong> or click <strong>Settings&nbsp;&nbsp;TweetDuck</strong> in the left panel to open the main menu. You can also right-click links, tweets, images and videos, and desktop notifications to access their respective context menus.</p> <p><strong>Right-click anywhere</strong> or click <strong>Settings&nbsp;&nbsp;TweetDuck</strong> in the left panel to open the main menu. You can also right-click links, tweets, images and videos, and desktop notifications to access their respective context menus.</p>
<p>Click <strong>Show Guide</strong> to see awesome features TweetDuck offers, or view the guide later by going to <strong>About TweetDuck</strong> and clicking the help button on top.</p> <p>Click <strong>Show Guide</strong> to see awesome features TweetDuck offers, or view the guide later by going to <strong>About TweetDuck</strong> and clicking the help button on top.</p>
<p>Follow the developer <a id="td-introduction-follow" href="#">@chylexmc</a> for latest news &amp; updates about the app, and some occasional rants.</p>
</div> </div>
<footer class="txt-right"> <footer class="txt-right">
<div class="anondata"> <div class="anondata">
@@ -106,7 +106,7 @@
onSuccess(tdUser); onSuccess(tdUser);
} }
else{ else{
TD.controller.clients.getPreferredClient().getUsersByIds([ "572571847" ], users => onSuccess(users[0]), onError); TD.controller.clients.getPreferredClient().getUsersByIds([ "957608948189880320" ], users => onSuccess(users[0]), onError);
} }
}; };
@@ -117,7 +117,7 @@
$(document).trigger("uiShowFollowFromOptions", { userToFollow: user }); $(document).trigger("uiShowFollowFromOptions", { userToFollow: user });
$(".js-modals-container").find("header a[rel='user']").each(function(){ $(".js-modals-container").find("header a[rel='user']").each(function(){
this.outerHTML = this.innerText; this.outerHTML = "TweetDuck";
}); });
}, () => { }, () => {
alert("An error occurred when retrieving the account information."); alert("An error occurred when retrieving the account information.");

View File

@@ -3,22 +3,22 @@
<div class="js-tweet tweet"> <div class="js-tweet tweet">
<header class="tweet-header"> <header class="tweet-header">
<time class="tweet-timestamp js-timestamp pull-right txt-mute"> <time class="tweet-timestamp js-timestamp pull-right txt-mute">
<a target="_blank" rel="url" href="https://twitter.com/chylexmc" class="txt-size-variable--12">0s</a> <a target="_blank" rel="url" href="https://twitter.com/TryMyAwesomeApp" class="txt-size-variable--12">now</a>
</time> </time>
<a target="_blank" rel="user" href="https://twitter.com/chylexmc" class="account-link link-complex block"> <a target="_blank" rel="user" href="https://twitter.com/TryMyAwesomeApp" class="account-link link-complex block">
<div class="obj-left item-img tweet-img"> <div class="obj-left item-img tweet-img">
<img width="48" height="48" alt="chylexmc's avatar" src="https://pbs.twimg.com/profile_images/765161905312980992/AhDP9iY-_normal.jpg" class="tweet-avatar avatar pull-right"> <img width="48" height="48" alt="TryMyAwesomeApp's avatar" src="{avatar}" class="tweet-avatar avatar pull-right">
</div> </div>
<div class="nbfc"> <div class="nbfc">
<span class="account-inline txt-ellipsis"> <span class="account-inline txt-ellipsis">
<b class="fullname link-complex-target">chylex</b> <b class="fullname link-complex-target">TweetDuck</b>
<span class="username txt-mute">@chylexmc</span> <span class="username txt-mute">@TryMyAwesomeApp</span>
</span> </span>
</div> </div>
</a> </a>
</header> </header>
<div class="tweet-body"> <div class="tweet-body">
<p class="js-tweet-text tweet-text with-linebreaks">This is an example tweet, which lets you test the location and duration of popup notifications.</p> <p class="js-tweet-text tweet-text with-linebreaks">Here you can see the position and appearance of desktop notifications.<br><br>For location and size, you can pick a preset, or select <strong>Custom</strong> and then freely move or resize the window.</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -35,6 +35,10 @@
install(plugin){ install(plugin){
this.installed.push(plugin); this.installed.push(plugin);
if (typeof plugin.obj.configure === "function"){
$TDP.setConfigurable(plugin.obj.$token);
}
if (!this.isDisabled(plugin)){ if (!this.isDisabled(plugin)){
plugin.obj.enabled(); plugin.obj.enabled();
this.runWhenReady(plugin); this.runWhenReady(plugin);
@@ -93,6 +97,13 @@
window.TD_PLUGINS.setState(window.TD_PLUGINS.findObject(identifier), enable); window.TD_PLUGINS.setState(window.TD_PLUGINS.findObject(identifier), enable);
}; };
//
// Block: Setup a function to trigger plugin configuration.
//
window.TDPF_configurePlugin = function(identifier){
window.TD_PLUGINS.findObject(identifier).obj.configure();
};
// //
// Block: Setup a function to reload the page. // Block: Setup a function to reload the page.
// //

View File

@@ -38,7 +38,7 @@
/* Square-ify stuff */ /* Square-ify stuff */
/********************/ /********************/
.btn, .mdl, .mdl-content, .app-search-fake, .app-search-input, .popover, .lst-modal, .tooltip-inner { button, .btn, .mdl, .mdl-content, .popover, .lst-modal, .tooltip-inner {
border-radius: 1px !important; border-radius: 1px !important;
} }
@@ -46,7 +46,7 @@
border-radius: 1px !important; border-radius: 1px !important;
} }
.compose-text-container, .compose-reply-tweet, .compose-message-recipient-input-container, .compose-message-recipient, .compose-media-bar-holder, .media-grid-container, .js-quote-tweet-holder { .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, .media-grid-container, .js-quote-tweet-holder, .detail-view-inline-text {
border-radius: 0 !important; border-radius: 0 !important;
} }
@@ -54,6 +54,40 @@
border-radius: 0 !important; border-radius: 0 !important;
} }
/***********************************/
/* Revert various design decisions */
/***********************************/
button {
font-weight: normal !important;
}
.tweet-button:not(:hover) {
background-color: #2B7BB9 !important;
border-color: #2B7BB9 !important;
}
#tduck .js-compose-scroller button > i {
font-size: 20px !important;
vertical-align: -4px !important;
}
.js-show-drawer {
text-align: left;
}
.js-show-drawer .icon-compose {
display: inline-block !important;
}
.js-show-drawer span::before {
content: "New ";
}
.js-account-safeguard-checkbox, .js-account-safeguard-checkbox label {
margin-bottom: 0 !important;
}
/***********************/ /***********************/
/* Hide TweetDeck logo */ /* Hide TweetDeck logo */
/***********************/ /***********************/
@@ -94,23 +128,16 @@
/* Tweak notification layout and design */ /* Tweak notification layout and design */
/****************************************/ /****************************************/
.tweet-context .nbfc { .activity-header.has-source-avatar {
text-overflow: ellipsis !important; margin-bottom: 4px !important
white-space: nowrap !important;
}
.activity-header {
align-items: center !important;
margin-bottom: 4px !important;
} }
.activity-header .tweet-timestamp { .activity-header .tweet-timestamp {
line-height: unset !important; line-height: unset !important;
} }
.account-bio.padding-t--5 { .activity-header .icon-user-filled {
/* follow notification padding */ vertical-align: sub !important;
padding-top: 2px !important;
} }
html[data-td-theme='light'] .stream-item:not(:hover) .js-user-actions-menu { html[data-td-theme='light'] .stream-item:not(:hover) .js-user-actions-menu {
@@ -125,6 +152,23 @@ html[data-td-theme='dark'] .stream-item:not(:hover) .js-user-actions-menu {
opacity: 0.25; opacity: 0.25;
} }
.stream-item[data-key^="favorite"] .has-source-avatar .item-img, .stream-item[data-key^="retweet"] .has-source-avatar .item-img {
position: absolute;
left: 21px;
top: 48px;
width: 0 !important;
}
.stream-item[data-key^="favorite"] .has-source-avatar > .nbfc, .stream-item[data-key^="retweet"] .has-source-avatar > .nbfc {
margin-left: 46px;
line-height: unset !important;
}
.stream-item[data-key^="favorite"] .has-source-avatar > .nbfc > .avatar, .stream-item[data-key^="retweet"] .has-source-avatar > .nbfc > .avatar {
position: absolute;
margin-left: -34px;
}
/***********************/ /***********************/
/* Tweaks for features */ /* Tweaks for features */
/***********************/ /***********************/
@@ -173,6 +217,12 @@ a[data-full-url] {
vertical-align: -10% !important; vertical-align: -10% !important;
} }
#tduck .nav-user-info .hide-condensed {
/* move login account info */
margin: 0px !important;
padding-left: 2px !important;
}
html[data-td-font='smallest'] .sprite-verified-mini { html[data-td-font='smallest'] .sprite-verified-mini {
/* fix cut off badge when zoomed in */ /* fix cut off badge when zoomed in */
width: 13px !important; width: 13px !important;
@@ -209,6 +259,27 @@ html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
cursor: pointer; cursor: pointer;
} }
.inline-reply .btn-square, .rpl-actions .btn-square {
/* remove effects from buttons under reply input... this keeps happening for some stupid reason */
background: transparent !important;
box-shadow: none !important;
}
.js-add-to-customtimeline-input {
/* the custom timeline input shadow is behaving super weird when focused */
box-shadow: none !important;
}
#tduck .js-follow-button .icon-follow {
/* follow icon is too small which makes it aliased and misaligned */
font-size: 17px !important;
}
.js-follow-button .following-text, .js-follow-button .unfollow-text {
/* unfollow button has a border for some reason */
border-width: 0 !important;
}
/***************************************************************/ /***************************************************************/
/* Fix glaring visual issues that twitter hasn't fixed yet smh */ /* Fix glaring visual issues that twitter hasn't fixed yet smh */
/***************************************************************/ /***************************************************************/
@@ -287,3 +358,15 @@ html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
.column-type-message.is-shifted-1 .username { .column-type-message.is-shifted-1 .username {
vertical-align: bottom; vertical-align: bottom;
} }
/******************************/
/* Hide unused Settings items */
/******************************/
#show-startup-notifications, #show-startup-notifications + span {
display: none;
}
#auto-play-gifs, #auto-play-gifs + span {
display: none;
}

View File

@@ -2,10 +2,6 @@
/* General */ /* General */
/***********/ /***********/
body:before {
content: none !important;
}
body { body {
overflow-y: auto !important; overflow-y: auto !important;
} }
@@ -34,13 +30,34 @@ body {
vertical-align: -10% !important; vertical-align: -10% !important;
} }
/************************************/
/* Favorite & retweet notifications */
/************************************/
.td-notification-padded .item-img {
position: absolute;
left: 21px;
top: 48px;
width: 0 !important;
}
.td-notification-padded .activity-header > .nbfc {
margin-left: 46px;
line-height: unset !important;
}
.td-notification-padded .activity-header > .nbfc > .avatar {
position: absolute;
margin-left: -34px;
}
/*********/ /*********/
/* Media */ /* Media */
/*********/ /*********/
.media-size-medium { .media-size-medium {
max-height: 240px; max-height: 240px;
height: calc(100vh - 16px) !important; height: calc(100vh - 20px) !important;
border-radius: 1px !important; border-radius: 1px !important;
} }
@@ -49,6 +66,11 @@ body {
} }
#tduck .js-media, #tduck .js-media-preview-container { #tduck .js-media, #tduck .js-media-preview-container {
padding-top: 1px;
margin-bottom: 2px !important;
}
#tduck .js-quote-detail .js-media, #tduck .js-quote-detail .js-media-preview-container {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

BIN
Resources/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
Resources/spinner.apng Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="packages\CefSharp.WinForms.63.0.0-pre01\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.63.0.0-pre01\build\CefSharp.WinForms.props')" /> <Import Project="packages\CefSharp.WinForms.64.0.0-CI2508\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.64.0.0-CI2508\build\CefSharp.WinForms.props')" />
<Import Project="packages\CefSharp.Common.63.0.0-pre01\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.63.0.0-pre01\build\CefSharp.Common.props')" /> <Import Project="packages\CefSharp.Common.64.0.0-CI2508\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.64.0.0-CI2508\build\CefSharp.Common.props')" />
<Import Project="packages\cef.redist.x86.3.3282.1731\build\cef.redist.x86.props" Condition="Exists('packages\cef.redist.x86.3.3282.1731\build\cef.redist.x86.props')" />
<Import Project="packages\cef.redist.x64.3.3282.1731\build\cef.redist.x64.props" Condition="Exists('packages\cef.redist.x64.3.3282.1731\build\cef.redist.x64.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -105,6 +107,7 @@
<Compile Include="Core\Handling\RequestHandlerBase.cs" /> <Compile Include="Core\Handling\RequestHandlerBase.cs" />
<Compile Include="Core\Handling\RequestHandlerBrowser.cs" /> <Compile Include="Core\Handling\RequestHandlerBrowser.cs" />
<Compile Include="Core\Handling\ResourceHandlerNotification.cs" /> <Compile Include="Core\Handling\ResourceHandlerNotification.cs" />
<Compile Include="Core\ITweetDeckBrowser.cs" />
<Compile Include="Core\Notification\Example\FormNotificationExample.cs"> <Compile Include="Core\Notification\Example\FormNotificationExample.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@@ -247,6 +250,7 @@
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" /> <Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
<Compile Include="Data\ResourceLink.cs" />
<Compile Include="Data\Serialization\FileSerializer.cs" /> <Compile Include="Data\Serialization\FileSerializer.cs" />
<Compile Include="Data\InjectedHTML.cs" /> <Compile Include="Data\InjectedHTML.cs" />
<Compile Include="Data\Serialization\ITypeConverter.cs" /> <Compile Include="Data\Serialization\ITypeConverter.cs" />
@@ -280,6 +284,11 @@
<Compile Include="Plugins\Events\PluginErrorEventArgs.cs" /> <Compile Include="Plugins\Events\PluginErrorEventArgs.cs" />
<Compile Include="Plugins\PluginManager.cs" /> <Compile Include="Plugins\PluginManager.cs" />
<Compile Include="Plugins\PluginScriptGenerator.cs" /> <Compile Include="Plugins\PluginScriptGenerator.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Reporter.cs" /> <Compile Include="Reporter.cs" />
<Compile Include="Updates\FormUpdateDownload.cs"> <Compile Include="Updates\FormUpdateDownload.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
@@ -301,11 +310,6 @@
<Compile Include="Updates\UpdateInfo.cs" /> <Compile Include="Updates\UpdateInfo.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Resources\ScriptLoader.cs" /> <Compile Include="Resources\ScriptLoader.cs" />
<Compile Include="Updates\UpdateEventArgs.cs" /> <Compile Include="Updates\UpdateEventArgs.cs" />
<Compile Include="Updates\UpdaterSettings.cs" /> <Compile Include="Updates\UpdaterSettings.cs" />
@@ -337,14 +341,10 @@
<DependentUpon>FormBrowser.cs</DependentUpon> <DependentUpon>FormBrowser.cs</DependentUpon>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="Core\Other\FormAbout.resx">
<DependentUpon>FormAbout.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx"> <EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator> <Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType> <SubType>Designer</SubType>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -358,11 +358,13 @@
<None Include="Resources\icon-tray-new.ico" /> <None Include="Resources\icon-tray-new.ico" />
<None Include="Resources\icon-tray.ico" /> <None Include="Resources\icon-tray.ico" />
<None Include="Resources\PostBuild.ps1" /> <None Include="Resources\PostBuild.ps1" />
<None Include="Resources\spinner.apng" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Resources\Plugins\" /> <Folder Include="Resources\Plugins\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Resources\avatar.png" />
<Content Include="Resources\Scripts\code.js" /> <Content Include="Resources\Scripts\code.js" />
<Content Include="Resources\Scripts\introduction.js" /> <Content Include="Resources\Scripts\introduction.js" />
<Content Include="Resources\Scripts\notification.js" /> <Content Include="Resources\Scripts\notification.js" />
@@ -396,8 +398,6 @@
del "$(TargetDir)LICENSE.txt" del "$(TargetDir)LICENSE.txt"
ren "$(TargetDir)LICENSE.md" "LICENSE.txt" ren "$(TargetDir)LICENSE.md" "LICENSE.txt"
xcopy "$(ProjectDir)bld\Resources\CEFSHARP-LICENSE.txt" "$(TargetDir)" /Y xcopy "$(ProjectDir)bld\Resources\CEFSHARP-LICENSE.txt" "$(TargetDir)" /Y
xcopy "$(ProjectDir)packages\Microsoft.VC120.CRT.JetBrains.12.0.21005.2\DotFiles\msvcp120.dll" "$(TargetDir)" /Y
xcopy "$(ProjectDir)packages\Microsoft.VC120.CRT.JetBrains.12.0.21005.2\DotFiles\msvcr120.dll" "$(TargetDir)" /Y
rmdir "$(TargetDir)scripts" /S /Q rmdir "$(TargetDir)scripts" /S /Q
mkdir "$(TargetDir)scripts" mkdir "$(TargetDir)scripts"
@@ -436,19 +436,17 @@ powershell -ExecutionPolicy Unrestricted -File "$(ProjectDir)Resources\PostBuild
<PropertyGroup> <PropertyGroup>
<PreBuildEvent>powershell Get-Process TweetDuck.Browser -ErrorAction SilentlyContinue ^| Where-Object {$_.Path -eq '$(TargetDir)TweetDuck.Browser.exe'} ^| Stop-Process; Exit 0</PreBuildEvent> <PreBuildEvent>powershell Get-Process TweetDuck.Browser -ErrorAction SilentlyContinue ^| Where-Object {$_.Path -eq '$(TargetDir)TweetDuck.Browser.exe'} ^| Stop-Process; Exit 0</PreBuildEvent>
</PropertyGroup> </PropertyGroup>
<Import Project="packages\cef.redist.x64.3.3239.1716\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.3239.1716\build\cef.redist.x64.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <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> <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> </PropertyGroup>
<Error Condition="!Exists('packages\cef.redist.x64.3.3239.1716\build\cef.redist.x64.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x64.3.3239.1716\build\cef.redist.x64.targets'))" /> <Error Condition="!Exists('packages\cef.redist.x64.3.3282.1731\build\cef.redist.x64.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x64.3.3282.1731\build\cef.redist.x64.props'))" />
<Error Condition="!Exists('packages\cef.redist.x86.3.3239.1716\build\cef.redist.x86.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x86.3.3239.1716\build\cef.redist.x86.targets'))" /> <Error Condition="!Exists('packages\cef.redist.x86.3.3282.1731\build\cef.redist.x86.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x86.3.3282.1731\build\cef.redist.x86.props'))" />
<Error Condition="!Exists('packages\CefSharp.Common.63.0.0-pre01\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.63.0.0-pre01\build\CefSharp.Common.props'))" /> <Error Condition="!Exists('packages\CefSharp.Common.64.0.0-CI2508\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.64.0.0-CI2508\build\CefSharp.Common.props'))" />
<Error Condition="!Exists('packages\CefSharp.Common.63.0.0-pre01\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.63.0.0-pre01\build\CefSharp.Common.targets'))" /> <Error Condition="!Exists('packages\CefSharp.Common.64.0.0-CI2508\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.64.0.0-CI2508\build\CefSharp.Common.targets'))" />
<Error Condition="!Exists('packages\CefSharp.WinForms.63.0.0-pre01\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.63.0.0-pre01\build\CefSharp.WinForms.props'))" /> <Error Condition="!Exists('packages\CefSharp.WinForms.64.0.0-CI2508\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.64.0.0-CI2508\build\CefSharp.WinForms.props'))" />
<Error Condition="!Exists('packages\CefSharp.WinForms.63.0.0-pre01\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.63.0.0-pre01\build\CefSharp.WinForms.targets'))" /> <Error Condition="!Exists('packages\CefSharp.WinForms.64.0.0-CI2508\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.64.0.0-CI2508\build\CefSharp.WinForms.targets'))" />
</Target> </Target>
<Import Project="packages\cef.redist.x86.3.3239.1716\build\cef.redist.x86.targets" Condition="Exists('packages\cef.redist.x86.3.3239.1716\build\cef.redist.x86.targets')" /> <Import Project="packages\CefSharp.Common.64.0.0-CI2508\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.64.0.0-CI2508\build\CefSharp.Common.targets')" />
<Import Project="packages\CefSharp.Common.63.0.0-pre01\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.63.0.0-pre01\build\CefSharp.Common.targets')" /> <Import Project="packages\CefSharp.WinForms.64.0.0-CI2508\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.64.0.0-CI2508\build\CefSharp.WinForms.targets')" />
<Import Project="packages\CefSharp.WinForms.63.0.0-pre01\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.63.0.0-pre01\build\CefSharp.WinForms.targets')" />
</Project> </Project>

View File

@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26430.16 VisualStudioVersion = 15.0.27130.2027
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck", "TweetDuck.csproj", "{2389A7CD-E0D3-4706-8294-092929A33A2D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck", "TweetDuck.csproj", "{2389A7CD-E0D3-4706-8294-092929A33A2D}"
EndProject EndProject
@@ -20,7 +20,6 @@ Global
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.ActiveCfg = Debug|x86 {2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.ActiveCfg = Debug|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.Build.0 = Debug|x86 {2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.Build.0 = Debug|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.Deploy.0 = Debug|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Release|x86.ActiveCfg = Release|x86 {2389A7CD-E0D3-4706-8294-092929A33A2D}.Release|x86.ActiveCfg = Release|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Release|x86.Build.0 = Release|x86 {2389A7CD-E0D3-4706-8294-092929A33A2D}.Release|x86.Build.0 = Release|x86
{B10B0017-819E-4F71-870F-8256B36A26AA}.Debug|x86.ActiveCfg = Debug|x86 {B10B0017-819E-4F71-870F-8256B36A26AA}.Debug|x86.ActiveCfg = Debug|x86
@@ -42,4 +41,7 @@ Global
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {43936FDB-CBF7-493D-A99A-24B516646584}
EndGlobalSection
EndGlobal EndGlobal

View File

@@ -1,14 +1,14 @@
using CefSharp; using CefSharp;
using CefSharp.WinForms;
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Resources; using TweetDuck.Resources;
namespace TweetDuck.Updates{ namespace TweetDuck.Updates{
sealed class UpdateHandler{ sealed class UpdateHandler{
private readonly ChromiumWebBrowser browser; private readonly ITweetDeckBrowser browser;
private readonly UpdaterSettings settings; private readonly UpdaterSettings settings;
public event EventHandler<UpdateEventArgs> UpdateAccepted; public event EventHandler<UpdateEventArgs> UpdateAccepted;
@@ -18,20 +18,18 @@ namespace TweetDuck.Updates{
private int lastEventId; private int lastEventId;
private UpdateInfo lastUpdateInfo; private UpdateInfo lastUpdateInfo;
public UpdateHandler(ChromiumWebBrowser browser, UpdaterSettings settings){ public UpdateHandler(ITweetDeckBrowser browser, UpdaterSettings settings){
this.browser = browser; this.browser = browser;
this.settings = settings; this.settings = settings;
browser.FrameLoadEnd += browser_FrameLoadEnd; browser.OnFrameLoaded(OnFrameLoaded);
browser.RegisterAsyncJsObject("$TDU", new Bridge(this)); browser.RegisterBridge("$TDU", new Bridge(this));
} }
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ private void OnFrameLoaded(IFrame frame){
if (e.Frame.IsMain && TwitterUtils.IsTweetDeckWebsite(e.Frame)){ ScriptLoader.ExecuteFile(frame, "update.js");
ScriptLoader.ExecuteFile(e.Frame, "update.js");
Check(false); Check(false);
} }
}
public int Check(bool force){ public int Check(bool force){
if (Program.UserConfig.EnableUpdateCheck || force){ if (Program.UserConfig.EnableUpdateCheck || force){
@@ -39,7 +37,7 @@ namespace TweetDuck.Updates{
settings.DismissedUpdate = null; settings.DismissedUpdate = null;
} }
browser.ExecuteScriptAsync("TDUF_runUpdateCheck", ++lastEventId, Program.VersionTag, settings.DismissedUpdate ?? string.Empty, settings.AllowPreReleases); browser.ExecuteFunction("TDUF_runUpdateCheck", ++lastEventId, Program.VersionTag, settings.DismissedUpdate ?? string.Empty, settings.AllowPreReleases);
return lastEventId; return lastEventId;
} }

Binary file not shown.

View File

@@ -4,9 +4,11 @@
#define MyAppName "TweetDuck" #define MyAppName "TweetDuck"
#define MyAppPublisher "chylex" #define MyAppPublisher "chylex"
#define MyAppURL "https://tweetduck.chylex.com" #define MyAppURL "https://tweetduck.chylex.com"
#define MyAppShortURL "https://td.chylex.com"
#define MyAppExeName "TweetDuck.exe" #define MyAppExeName "TweetDuck.exe"
#define MyAppVersion GetFileVersion("..\bin\x86\Release\TweetDuck.exe") #define MyAppVersion GetFileVersion("..\bin\x86\Release\TweetDuck.exe")
#define VCRedistLink "releases/download/1.13/vc_redist.x86.exe"
[Setup] [Setup]
AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06} AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06}
@@ -30,6 +32,8 @@ SolidCompression=yes
InternalCompressLevel=max InternalCompressLevel=max
MinVersion=0,6.1 MinVersion=0,6.1
#include <idp.iss>
[Languages] [Languages]
Name: "english"; MessagesFile: "compiler:Default.isl" Name: "english"; MessagesFile: "compiler:Default.isl"
@@ -56,36 +60,44 @@ Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache"
[Code] [Code]
var UpdatePath: String; var UpdatePath: String;
var ForceRedistPrompt: String;
function TDGetNetFrameworkVersion: Cardinal; forward; 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.5.2. }
function InitializeSetup: Boolean; function InitializeSetup: Boolean;
begin begin
UpdatePath := ExpandConstant('{param:UPDATEPATH}') UpdatePath := ExpandConstant('{param:UPDATEPATH}')
ForceRedistPrompt := ExpandConstant('{param:PROMPTREDIST}')
if TDGetNetFrameworkVersion() >= 379893 then 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
begin
Result := True;
Exit;
end;
if (MsgBox('{#MyAppName} requires .NET Framework 4.5.2 or newer,'+#13+#10+'please download it from {#MyAppURL}'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
begin begin
Result := False; Result := False;
Exit; Exit;
end; end;
if (TDIsVCMissing() or (ForceRedistPrompt = '1')) and (MsgBox('Microsoft Visual C++ 2015 appears to be missing, would you like to automatically install it?', mbConfirmation, MB_YESNO) = IDYES) then
begin
idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/{#VCRedistLink}', ExpandConstant('{tmp}\{#MyAppName}.VC.exe'));
end;
Result := True; Result := True;
end; end;
{ Set the installation path if updating. } { Set the installation path if updating, and prepare download plugin if there are any files to download. }
procedure InitializeWizard(); procedure InitializeWizard();
begin begin
if (UpdatePath <> '') then if (UpdatePath <> '') then
begin begin
WizardForm.DirEdit.Text := UpdatePath; WizardForm.DirEdit.Text := UpdatePath;
end; end;
if (idpFilesCount <> 0) then
begin
idpDownloadAfter(wpReady);
end;
end; end;
{ Skip the install path selection page if running from an update installer. } { Skip the install path selection page if running from an update installer. }
@@ -94,15 +106,12 @@ begin
Result := (PageID = wpSelectDir) and (UpdatePath <> '') Result := (PageID = wpSelectDir) and (UpdatePath <> '')
end; end;
{ Check for an old TweetDeck profile and show a warning before installation. } { Install VC++ if downloaded. }
procedure CurStepChanged(CurStep: TSetupStep); procedure CurStepChanged(CurStep: TSetupStep);
begin begin
if CurStep = ssInstall then if CurStep = ssInstall then
begin begin
if DirExists(ExpandConstant('{localappdata}\twitter\TweetDeck')) then TDInstallVCRedist();
begin
MsgBox('Detected a profile from an old TweetDeck installation, you may uninstall the old client to free up some space.', mbInformation, MB_OK)
end;
end; end;
end; end;
@@ -145,3 +154,65 @@ begin
Result := 0; Result := 0;
end; end;
{ Check if Visual C++ 2015 or 2017 is installed. }
function TDIsVCMissing: Boolean;
var Keys: TArrayOfString;
var Index: Integer;
var Key: String;
var DisplayName: String;
begin
if RegGetSubkeyNames(HKEY_LOCAL_MACHINE, 'Software\Classes\Installer\Dependencies', Keys) then
begin
for Index := 0 to GetArrayLength(Keys)-1 do
begin
Key := Keys[Index];
if RegQueryStringValue(HKEY_LOCAL_MACHINE, 'Software\Classes\Installer\Dependencies\'+Key, 'DisplayName', DisplayName) then
begin
if (Pos('Microsoft Visual C++', DisplayName) = 1) and (Pos('(x86)', DisplayName) > 1) and ((Pos(' 2015 ', DisplayName) > 1) or (Pos(' 2017 ', DisplayName) > 1)) then
begin
Result := False;
Exit;
end;
end;
end;
end;
Result := True;
end;
{ Run the Visual C++ installer if downloaded. }
procedure TDInstallVCRedist;
var InstallFile: String;
var ResultCode: Integer;
begin
InstallFile := ExpandConstant('{tmp}\{#MyAppName}.VC.exe')
if FileExists(InstallFile) then
begin
WizardForm.ProgressGauge.Style := npbstMarquee;
try
if Exec(InstallFile, '/passive /norestart', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then
begin
if ResultCode <> 0 then
begin
DeleteFile(InstallFile);
Exit;
end;
end else
begin
MsgBox('Could not run the Visual C++ installer, please visit https://github.com/{#MyAppPublisher}/{#MyAppName}/{#VCRedistLink} and download the latest version manually. Error: '+SysErrorMessage(ResultCode), mbCriticalError, MB_OK);
DeleteFile(InstallFile);
Exit;
end;
finally
WizardForm.ProgressGauge.Style := npbstNormal;
DeleteFile(InstallFile);
end;
end;
end;

View File

@@ -4,9 +4,11 @@
#define MyAppName "TweetDuck" #define MyAppName "TweetDuck"
#define MyAppPublisher "chylex" #define MyAppPublisher "chylex"
#define MyAppURL "https://tweetduck.chylex.com" #define MyAppURL "https://tweetduck.chylex.com"
#define MyAppShortURL "https://td.chylex.com"
#define MyAppExeName "TweetDuck.exe" #define MyAppExeName "TweetDuck.exe"
#define MyAppVersion GetFileVersion("..\bin\x86\Release\TweetDuck.exe") #define MyAppVersion GetFileVersion("..\bin\x86\Release\TweetDuck.exe")
#define VCRedistLink "releases/download/1.13/vc_redist.x86.exe"
[Setup] [Setup]
AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06} AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06}
@@ -30,6 +32,8 @@ SolidCompression=yes
InternalCompressLevel=max InternalCompressLevel=max
MinVersion=0,6.1 MinVersion=0,6.1
#include <idp.iss>
[Languages] [Languages]
Name: "english"; MessagesFile: "compiler:Default.isl" Name: "english"; MessagesFile: "compiler:Default.isl"
@@ -42,36 +46,44 @@ Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChang
[Code] [Code]
var UpdatePath: String; var UpdatePath: String;
var ForceRedistPrompt: String;
function TDGetNetFrameworkVersion: Cardinal; forward; 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.5.2. }
function InitializeSetup: Boolean; function InitializeSetup: Boolean;
begin begin
UpdatePath := ExpandConstant('{param:UPDATEPATH}') UpdatePath := ExpandConstant('{param:UPDATEPATH}')
ForceRedistPrompt := ExpandConstant('{param:PROMPTREDIST}')
if TDGetNetFrameworkVersion() >= 379893 then 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
begin
Result := True;
Exit;
end;
if (MsgBox('{#MyAppName} requires .NET Framework 4.5.2 or newer,'+#13+#10+'please download it from {#MyAppURL}'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
begin begin
Result := False; Result := False;
Exit; Exit;
end; end;
if (TDIsVCMissing() or (ForceRedistPrompt = '1')) and (MsgBox('Microsoft Visual C++ 2015 appears to be missing, would you like to automatically install it?', mbConfirmation, MB_YESNO) = IDYES) then
begin
idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/{#VCRedistLink}', ExpandConstant('{tmp}\{#MyAppName}.VC.exe'));
end;
Result := True; Result := True;
end; end;
{ Set the installation path if updating. } { Set the installation path if updating, and prepare download plugin if there are any files to download. }
procedure InitializeWizard(); procedure InitializeWizard();
begin begin
if (UpdatePath <> '') then if (UpdatePath <> '') then
begin begin
WizardForm.DirEdit.Text := UpdatePath; WizardForm.DirEdit.Text := UpdatePath;
end; end;
if (idpFilesCount <> 0) then
begin
idpDownloadAfter(wpReady);
end;
end; end;
{ Skip the install path selection page if running from an update installer. } { Skip the install path selection page if running from an update installer. }
@@ -80,6 +92,24 @@ begin
Result := (PageID = wpSelectDir) and (UpdatePath <> '') Result := (PageID = wpSelectDir) and (UpdatePath <> '')
end; end;
{ Install VC++ if downloaded, and create a 'makeportable' file for portable installs. }
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssInstall then
begin
TDInstallVCRedist();
end else if CurStep = ssPostInstall then
begin
while not SaveStringToFile(ExpandConstant('{app}\makeportable'), '', False) do
begin
if MsgBox('Could not create a ''makeportable'' file in the installation folder. If the file is not present, the installation will not be fully portable.', mbCriticalError, MB_RETRYCANCEL) <> IDRETRY then
begin
break;
end;
end;
end;
end;
{ Return DWORD value containing the build version of .NET Framework. } { Return DWORD value containing the build version of .NET Framework. }
function TDGetNetFrameworkVersion: Cardinal; function TDGetNetFrameworkVersion: Cardinal;
var FrameworkVersion: Cardinal; var FrameworkVersion: Cardinal;
@@ -94,17 +124,64 @@ begin
Result := 0; Result := 0;
end; end;
{ Create a 'makeportable' file if running in portable mode. } { Check if Visual C++ 2015 or 2017 is installed. }
procedure CurStepChanged(CurStep: TSetupStep); function TDIsVCMissing: Boolean;
var Keys: TArrayOfString;
var Index: Integer;
var Key: String;
var DisplayName: String;
begin begin
if CurStep = ssPostInstall then if RegGetSubkeyNames(HKEY_LOCAL_MACHINE, 'Software\Classes\Installer\Dependencies', Keys) then
begin begin
while not SaveStringToFile(ExpandConstant('{app}\makeportable'), '', False) do for Index := 0 to GetArrayLength(Keys)-1 do
begin begin
if MsgBox('Could not create a ''makeportable'' file in the installation folder. If the file is not present, the installation will not be fully portable.', mbCriticalError, MB_RETRYCANCEL) <> IDRETRY then Key := Keys[Index];
if RegQueryStringValue(HKEY_LOCAL_MACHINE, 'Software\Classes\Installer\Dependencies\'+Key, 'DisplayName', DisplayName) then
begin begin
break; if (Pos('Microsoft Visual C++', DisplayName) = 1) and (Pos('(x86)', DisplayName) > 1) and ((Pos(' 2015 ', DisplayName) > 1) or (Pos(' 2017 ', DisplayName) > 1)) then
begin
Result := False;
Exit;
end; end;
end; end;
end; end;
end;
Result := True;
end;
{ Run the Visual C++ installer if downloaded. }
procedure TDInstallVCRedist;
var InstallFile: String;
var ResultCode: Integer;
begin
InstallFile := ExpandConstant('{tmp}\{#MyAppName}.VC.exe')
if FileExists(InstallFile) then
begin
WizardForm.ProgressGauge.Style := npbstMarquee;
try
if Exec(InstallFile, '/passive /norestart', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then
begin
if ResultCode <> 0 then
begin
DeleteFile(InstallFile);
Exit;
end;
end else
begin
MsgBox('Could not run the Visual C++ installer, please visit https://github.com/{#MyAppPublisher}/{#MyAppName}/{#VCRedistLink} and download the latest version manually. Error: '+SysErrorMessage(ResultCode), mbCriticalError, MB_OK);
DeleteFile(InstallFile);
Exit;
end;
finally
WizardForm.ProgressGauge.Style := npbstNormal;
DeleteFile(InstallFile);
end;
end;
end; end;

View File

@@ -4,6 +4,7 @@
#define MyAppName "TweetDuck" #define MyAppName "TweetDuck"
#define MyAppPublisher "chylex" #define MyAppPublisher "chylex"
#define MyAppURL "https://tweetduck.chylex.com" #define MyAppURL "https://tweetduck.chylex.com"
#define MyAppShortURL "https://td.chylex.com"
#define MyAppExeName "TweetDuck.exe" #define MyAppExeName "TweetDuck.exe"
#define MyAppID "8C25A716-7E11-4AAD-9992-8B5D0C78AE06" #define MyAppID "8C25A716-7E11-4AAD-9992-8B5D0C78AE06"
@@ -59,7 +60,10 @@ Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\Cache"
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache" Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache"
[InstallDelete] [InstallDelete]
Type: files; Name: "{app}\msvcp120.dll"
Type: files; Name: "{app}\msvcr120.dll"
Type: files; Name: "{app}\TweetLib.Audio.dll" Type: files; Name: "{app}\TweetLib.Audio.dll"
Type: filesandordirs; Name: "{app}\scripts"
Type: filesandordirs; Name: "{app}\plugins\official" Type: filesandordirs; Name: "{app}\plugins\official"
Type: files; Name: "{app}\locales\am.pak" Type: files; Name: "{app}\locales\am.pak"
Type: files; Name: "{app}\locales\ar.pak" Type: files; Name: "{app}\locales\ar.pak"
@@ -145,13 +149,7 @@ begin
idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/releases/download/'+TDGetAppVersionClean()+'/'+TDGetFullDownloadFileName(), ExpandConstant('{tmp}\{#MyAppName}.Full.exe')); idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/releases/download/'+TDGetAppVersionClean()+'/'+TDGetFullDownloadFileName(), ExpandConstant('{tmp}\{#MyAppName}.Full.exe'));
end; end;
if TDGetNetFrameworkVersion() >= 379893 then 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
begin
Result := True;
Exit;
end;
if (MsgBox('{#MyAppName} requires .NET Framework 4.5.2 or newer,'+#13+#10+'please download it from {#MyAppURL}'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
begin begin
Result := False; Result := False;
Exit; Exit;
@@ -272,8 +270,15 @@ end;
{ Return whether the version of the installed libcef.dll library matches internal one. } { Return whether the version of the installed libcef.dll library matches internal one. }
function TDIsMatchingCEFVersion: Boolean; function TDIsMatchingCEFVersion: Boolean;
var CEFVersion: String; var CEFVersion: String;
var TmpTDVersion: String;
begin begin
if GetVersionNumbersString(UpdatePath+'TweetDuck.exe', TmpTDVersion) and (CompareStr(TmpTDVersion, '1.13.0.0') = 0) then
begin
Result := False;
Exit;
end;
Result := (GetVersionNumbersString(UpdatePath+'libcef.dll', CEFVersion) and (CompareStr(CEFVersion, '{#CefVersion}') = 0)) Result := (GetVersionNumbersString(UpdatePath+'libcef.dll', CEFVersion) and (CompareStr(CEFVersion, '{#CefVersion}') = 0))
end; end;

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="cef.redist.x64" version="3.3239.1716" targetFramework="net452" xmlns="" /> <package id="cef.redist.x64" version="3.3282.1731" targetFramework="net452" xmlns="" />
<package id="cef.redist.x86" version="3.3239.1716" targetFramework="net452" xmlns="" /> <package id="cef.redist.x86" version="3.3282.1731" targetFramework="net452" xmlns="" />
<package id="CefSharp.Common" version="63.0.0-pre01" targetFramework="net452" xmlns="" /> <package id="CefSharp.Common" version="64.0.0-CI2508" targetFramework="net452" xmlns="" />
<package id="CefSharp.WinForms" version="63.0.0-pre01" targetFramework="net452" xmlns="" /> <package id="CefSharp.WinForms" version="64.0.0-CI2508" targetFramework="net452" xmlns="" />
<package id="Microsoft.VC120.CRT.JetBrains" version="12.0.21005.2" targetFramework="net452" xmlns="" /> <package id="Microsoft.VC120.CRT.JetBrains" version="12.0.21005.2" targetFramework="net452" xmlns="" />
</packages> </packages>

View File

@@ -3,7 +3,7 @@ using CefSharp.BrowserSubprocess;
namespace TweetDuck.Browser{ namespace TweetDuck.Browser{
static class Program{ static class Program{
internal const string Version = "1.4.0.0"; internal const string Version = "1.4.1.0";
private static int Main(string[] args){ private static int Main(string[] args){
SubProcess.EnableHighDPISupport(); SubProcess.EnableHighDPISupport();

View File

@@ -24,9 +24,9 @@
<StartupObject /> <StartupObject />
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="CefSharp.BrowserSubprocess.Core, Version=63.0.0.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=x86"> <Reference Include="CefSharp.BrowserSubprocess.Core, Version=64.0.0.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\CefSharp.Common.63.0.0-pre01\CefSharp\x86\CefSharp.BrowserSubprocess.Core.dll</HintPath> <HintPath>..\packages\CefSharp.Common.64.0.0-CI2508\CefSharp\x86\CefSharp.BrowserSubprocess.Core.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
</ItemGroup> </ItemGroup>

View File

@@ -75,6 +75,7 @@ namespace TweetDuck.Video{
} }
private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){ private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
Invoke(new Action(() => {
switch(e.Key){ switch(e.Key){
case "key": case "key":
HandleKey((Keys)int.Parse(e.Data, NumberStyles.Integer)); HandleKey((Keys)int.Parse(e.Data, NumberStyles.Integer));
@@ -84,6 +85,7 @@ namespace TweetDuck.Video{
StopVideo(); StopVideo();
break; break;
} }
}));
} }
private void player_PlayStateChange(int newState){ private void player_PlayStateChange(int newState){

View File

@@ -5,7 +5,7 @@ using System.Windows.Forms;
namespace TweetDuck.Video{ namespace TweetDuck.Video{
static class Program{ static class Program{
internal const string Version = "1.2.2.0"; internal const string Version = "1.2.2.1";
// referenced in VideoPlayer // referenced in VideoPlayer
// set by task manager -- public const int CODE_PROCESS_KILLED = 1; // set by task manager -- public const int CODE_PROCESS_KILLED = 1;