mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 19:32:10 +02:00
Compare commits
51 Commits
Author | SHA1 | Date | |
---|---|---|---|
3a28556c7f | |||
9ecc92b9a5 | |||
ca023be98a | |||
11a1423f76 | |||
79f6df121b | |||
71eade7e86 | |||
5f81d29036 | |||
ec1cb5dc5f | |||
fd969e2d55 | |||
37e33b77ff | |||
f7ed7703b4 | |||
4bb35295ca | |||
1e4f673f9e | |||
7cadb1c403 | |||
37148f5093 | |||
f6bc26789f | |||
b3f5a88525 | |||
1e538d2b28 | |||
7d7bfb7b01 | |||
41d86ba440 | |||
3df474a8a5 | |||
a50d6e8f47 | |||
6081e5b9c1 | |||
66ccea920c | |||
470d63093f | |||
eae0507831 | |||
92af85d3bb | |||
7635af5730 | |||
a838e89695 | |||
b22289a8b9 | |||
45b3ff52c6 | |||
4464991f4c | |||
b0d2f77583 | |||
b211a4405d | |||
8823016d2c | |||
859fdc7ec1 | |||
028d5ed01f | |||
5fd5a2a436 | |||
79a7e7470c | |||
9ecef78aed | |||
65a837a6e1 | |||
6e4db4acea | |||
26fb977d05 | |||
b42cd1c048 | |||
467f7cd12f | |||
66699ce9df | |||
cf7d903932 | |||
a7ab67925c | |||
a474ba4260 | |||
09e5636e86 | |||
2295a875be |
@@ -67,7 +67,7 @@ namespace TweetDck.Configuration{
|
|||||||
|
|
||||||
public string NotificationSoundPath{
|
public string NotificationSoundPath{
|
||||||
get{
|
get{
|
||||||
return !string.IsNullOrEmpty(notificationSoundPath) && File.Exists(notificationSoundPath) ? notificationSoundPath : string.Empty;
|
return string.IsNullOrEmpty(notificationSoundPath) ? string.Empty : notificationSoundPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
set{
|
set{
|
||||||
|
34
Core/Bridge/PropertyBridge.cs
Normal file
34
Core/Bridge/PropertyBridge.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace TweetDck.Core.Bridge{
|
||||||
|
static class PropertyBridge{
|
||||||
|
[Flags]
|
||||||
|
public enum Properties{
|
||||||
|
ExpandLinksOnHover = 1,
|
||||||
|
MuteNotifications = 2,
|
||||||
|
HasCustomNotificationSound = 4,
|
||||||
|
All = ExpandLinksOnHover | MuteNotifications | HasCustomNotificationSound
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GenerateScript(Properties properties = Properties.All){
|
||||||
|
StringBuilder build = new StringBuilder();
|
||||||
|
build.Append("(function(c){");
|
||||||
|
|
||||||
|
if (properties.HasFlag(Properties.ExpandLinksOnHover)){
|
||||||
|
build.Append("c.expandLinksOnHover=").Append(Program.UserConfig.ExpandLinksOnHover ? "true;" : "false;");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.HasFlag(Properties.MuteNotifications)){
|
||||||
|
build.Append("c.muteNotifications=").Append(Program.UserConfig.MuteNotifications ? "true;" : "false;");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.HasFlag(Properties.HasCustomNotificationSound)){
|
||||||
|
build.Append("c.hasCustomNotificationSound=").Append(Program.UserConfig.NotificationSoundPath.Length > 0 ? "true;" : "false;");
|
||||||
|
}
|
||||||
|
|
||||||
|
build.Append("})(window.$TDX=window.$TDX||{})");
|
||||||
|
return build.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -21,48 +21,6 @@ namespace TweetDck.Core.Bridge{
|
|||||||
private readonly FormBrowser form;
|
private readonly FormBrowser form;
|
||||||
private readonly FormNotification notification;
|
private readonly FormNotification notification;
|
||||||
|
|
||||||
public string BrandName{
|
|
||||||
get{
|
|
||||||
return Program.BrandName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string VersionTag{
|
|
||||||
get{
|
|
||||||
return Program.VersionTag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool MuteNotifications{
|
|
||||||
get{
|
|
||||||
return Program.UserConfig.MuteNotifications;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasCustomNotificationSound{
|
|
||||||
get{
|
|
||||||
return !string.IsNullOrEmpty(Program.UserConfig.NotificationSoundPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ExpandLinksOnHover{
|
|
||||||
get{
|
|
||||||
return Program.UserConfig.ExpandLinksOnHover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasCustomBrowserCSS{
|
|
||||||
get{
|
|
||||||
return !string.IsNullOrEmpty(Program.UserConfig.CustomBrowserCSS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string CustomBrowserCSS{
|
|
||||||
get{
|
|
||||||
return Program.UserConfig.CustomBrowserCSS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public TweetDeckBridge(FormBrowser form, FormNotification notification){
|
public TweetDeckBridge(FormBrowser form, FormNotification notification){
|
||||||
this.form = form;
|
this.form = form;
|
||||||
this.notification = notification;
|
this.notification = notification;
|
||||||
@@ -81,26 +39,26 @@ namespace TweetDck.Core.Bridge{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void SetLastRightClickedLink(string link){
|
public void SetLastRightClickedLink(string link){
|
||||||
form.InvokeSafe(() => LastRightClickedLink = link);
|
form.InvokeAsyncSafe(() => LastRightClickedLink = link);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetLastHighlightedTweet(string link, string quotedLink){
|
public void SetLastHighlightedTweet(string link, string quotedLink){
|
||||||
form.InvokeSafe(() => {
|
form.InvokeAsyncSafe(() => {
|
||||||
LastHighlightedTweet = link;
|
LastHighlightedTweet = link;
|
||||||
LastHighlightedQuotedTweet = quotedLink;
|
LastHighlightedQuotedTweet = quotedLink;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetNotificationQuotedTweet(string link){
|
public void SetNotificationQuotedTweet(string link){
|
||||||
notification.InvokeSafe(() => notification.CurrentQuotedTweetUrl = link);
|
notification.InvokeAsyncSafe(() => notification.CurrentQuotedTweetUrl = link);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenSettingsMenu(){
|
public void OpenSettingsMenu(){
|
||||||
form.InvokeSafe(form.OpenSettings);
|
form.InvokeAsyncSafe(form.OpenSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenPluginsMenu(){
|
public void OpenPluginsMenu(){
|
||||||
form.InvokeSafe(form.OpenPlugins);
|
form.InvokeAsyncSafe(form.OpenPlugins);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnTweetPopup(string tweetHtml, string tweetUrl, int tweetCharacters){
|
public void OnTweetPopup(string tweetHtml, string tweetUrl, int tweetCharacters){
|
||||||
@@ -111,7 +69,7 @@ namespace TweetDck.Core.Bridge{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void OnTweetSound(){
|
public void OnTweetSound(){
|
||||||
form.InvokeSafe(() => {
|
form.InvokeAsyncSafe(() => {
|
||||||
form.OnTweetNotification();
|
form.OnTweetNotification();
|
||||||
form.PlayNotificationSound();
|
form.PlayNotificationSound();
|
||||||
});
|
});
|
||||||
@@ -119,15 +77,15 @@ namespace TweetDck.Core.Bridge{
|
|||||||
|
|
||||||
public void DisplayTooltip(string text, bool showInNotification){
|
public void DisplayTooltip(string text, bool showInNotification){
|
||||||
if (showInNotification){
|
if (showInNotification){
|
||||||
notification.InvokeSafe(() => notification.DisplayTooltip(text));
|
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
form.InvokeSafe(() => form.DisplayTooltip(text));
|
form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadNextNotification(){
|
public void LoadNextNotification(){
|
||||||
notification.InvokeSafe(notification.FinishCurrentTweet);
|
notification.InvokeAsyncSafe(notification.FinishCurrentTweet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TryPasteImage(){
|
public void TryPasteImage(){
|
||||||
@@ -163,11 +121,11 @@ namespace TweetDck.Core.Bridge{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void ScreenshotTweet(string html, int width, int height){
|
public void ScreenshotTweet(string html, int width, int height){
|
||||||
form.InvokeSafe(() => form.OnTweetScreenshotReady(html, width, height));
|
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FixClipboard(){
|
public void FixClipboard(){
|
||||||
form.InvokeSafe(WindowsUtils.ClipboardStripHtmlStyles);
|
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenBrowser(string url){
|
public void OpenBrowser(string url){
|
||||||
|
@@ -16,6 +16,10 @@ namespace TweetDck.Core.Controls{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void InvokeAsyncSafe(this Control control, Action func){
|
||||||
|
control.BeginInvoke(func);
|
||||||
|
}
|
||||||
|
|
||||||
public static void MoveToCenter(this Form targetForm, Form parentForm){
|
public static void MoveToCenter(this Form targetForm, Form parentForm){
|
||||||
targetForm.Location = new Point(parentForm.Location.X+parentForm.Width/2-targetForm.Width/2, parentForm.Location.Y+parentForm.Height/2-targetForm.Height/2);
|
targetForm.Location = new Point(parentForm.Location.X+parentForm.Width/2-targetForm.Width/2, parentForm.Location.Y+parentForm.Height/2-targetForm.Height/2);
|
||||||
}
|
}
|
||||||
|
@@ -13,10 +13,10 @@ using TweetDck.Updates;
|
|||||||
using TweetDck.Plugins;
|
using TweetDck.Plugins;
|
||||||
using TweetDck.Plugins.Enums;
|
using TweetDck.Plugins.Enums;
|
||||||
using TweetDck.Plugins.Events;
|
using TweetDck.Plugins.Events;
|
||||||
using System.Media;
|
|
||||||
using TweetDck.Core.Bridge;
|
using TweetDck.Core.Bridge;
|
||||||
using TweetDck.Core.Notification;
|
using TweetDck.Core.Notification;
|
||||||
using TweetDck.Core.Notification.Screenshot;
|
using TweetDck.Core.Notification.Screenshot;
|
||||||
|
using TweetDck.Updates.Events;
|
||||||
|
|
||||||
namespace TweetDck.Core{
|
namespace TweetDck.Core{
|
||||||
sealed partial class FormBrowser : Form{
|
sealed partial class FormBrowser : Form{
|
||||||
@@ -41,7 +41,7 @@ namespace TweetDck.Core{
|
|||||||
private FormWindowState prevState;
|
private FormWindowState prevState;
|
||||||
|
|
||||||
private TweetScreenshotManager notificationScreenshotManager;
|
private TweetScreenshotManager notificationScreenshotManager;
|
||||||
private SoundPlayer notificationSound;
|
private SoundNotification soundNotification;
|
||||||
|
|
||||||
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){
|
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -53,7 +53,11 @@ namespace TweetDck.Core{
|
|||||||
this.plugins.PluginChangedState += plugins_PluginChangedState;
|
this.plugins.PluginChangedState += plugins_PluginChangedState;
|
||||||
|
|
||||||
this.notification = CreateNotificationForm(NotificationFlags.AutoHide | NotificationFlags.TopMost);
|
this.notification = CreateNotificationForm(NotificationFlags.AutoHide | NotificationFlags.TopMost);
|
||||||
|
#if DEBUG
|
||||||
|
this.notification.CanMoveWindow = () => (ModifierKeys & Keys.Alt) == Keys.Alt;
|
||||||
|
#else
|
||||||
this.notification.CanMoveWindow = () => false;
|
this.notification.CanMoveWindow = () => false;
|
||||||
|
#endif
|
||||||
this.notification.Show();
|
this.notification.Show();
|
||||||
|
|
||||||
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
|
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
|
||||||
@@ -69,11 +73,13 @@ namespace TweetDck.Core{
|
|||||||
|
|
||||||
this.browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
this.browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
||||||
this.browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
this.browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
||||||
this.browser.RegisterJsObject("$TD", new TweetDeckBridge(this, notification));
|
this.browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(this, notification));
|
||||||
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
|
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
|
||||||
|
|
||||||
Controls.Add(browser);
|
Controls.Add(browser);
|
||||||
|
|
||||||
|
Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
|
||||||
|
|
||||||
Disposed += (sender, args) => {
|
Disposed += (sender, args) => {
|
||||||
browser.Dispose();
|
browser.Dispose();
|
||||||
|
|
||||||
@@ -81,8 +87,8 @@ namespace TweetDck.Core{
|
|||||||
notificationScreenshotManager.Dispose();
|
notificationScreenshotManager.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notificationSound != null){
|
if (soundNotification != null){
|
||||||
notificationSound.Dispose();
|
soundNotification.Dispose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -92,13 +98,16 @@ namespace TweetDck.Core{
|
|||||||
|
|
||||||
UpdateTrayIcon();
|
UpdateTrayIcon();
|
||||||
|
|
||||||
|
Config.MuteToggled += Config_MuteToggled;
|
||||||
|
|
||||||
this.updates = new UpdateHandler(browser, this, updaterSettings);
|
this.updates = new UpdateHandler(browser, this, updaterSettings);
|
||||||
this.updates.UpdateAccepted += updates_UpdateAccepted;
|
this.updates.UpdateAccepted += updates_UpdateAccepted;
|
||||||
|
this.updates.UpdateDismissed += updates_UpdateDismissed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowChildForm(Form form){
|
private void ShowChildForm(Form form){
|
||||||
|
form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
|
||||||
form.Show(this);
|
form.Show(this);
|
||||||
form.Shown += (sender, args) => form.MoveToCenter(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ForceClose(){
|
public void ForceClose(){
|
||||||
@@ -135,7 +144,9 @@ namespace TweetDck.Core{
|
|||||||
|
|
||||||
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
|
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
|
||||||
|
UpdateProperties();
|
||||||
ScriptLoader.ExecuteFile(e.Frame, "code.js");
|
ScriptLoader.ExecuteFile(e.Frame, "code.js");
|
||||||
|
ReinjectCustomCSS(Config.CustomBrowserCSS);
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
ScriptLoader.ExecuteFile(e.Frame, "debug.js");
|
ScriptLoader.ExecuteFile(e.Frame, "debug.js");
|
||||||
@@ -192,6 +203,10 @@ namespace TweetDck.Core{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Config_MuteToggled(object sender, EventArgs e){
|
||||||
|
UpdateProperties(PropertyBridge.Properties.MuteNotifications);
|
||||||
|
}
|
||||||
|
|
||||||
private void Config_TrayBehaviorChanged(object sender, EventArgs e){
|
private void Config_TrayBehaviorChanged(object sender, EventArgs e){
|
||||||
if (!isLoaded)return;
|
if (!isLoaded)return;
|
||||||
|
|
||||||
@@ -241,6 +256,11 @@ namespace TweetDck.Core{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updates_UpdateDismissed(object sender, UpdateDismissedEventArgs e){
|
||||||
|
Config.DismissedUpdate = e.VersionTag;
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void WndProc(ref Message m){
|
protected override void WndProc(ref Message m){
|
||||||
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
|
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
|
||||||
trayIcon_ClickRestore(trayIcon, new EventArgs());
|
trayIcon_ClickRestore(trayIcon, new EventArgs());
|
||||||
@@ -269,29 +289,47 @@ namespace TweetDck.Core{
|
|||||||
notification.ResumeNotification();
|
notification.ResumeNotification();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// javascript calls
|
||||||
|
|
||||||
|
public void ReinjectCustomCSS(string css){
|
||||||
|
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css == null ? string.Empty : css.Replace(Environment.NewLine, " "));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateProperties(PropertyBridge.Properties properties = PropertyBridge.Properties.All){
|
||||||
|
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(properties));
|
||||||
|
}
|
||||||
|
|
||||||
// callback handlers
|
// callback handlers
|
||||||
|
|
||||||
public void OpenSettings(){
|
public void OpenSettings(){
|
||||||
|
OpenSettings(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenSettings(int tabIndex){
|
||||||
if (currentFormSettings != null){
|
if (currentFormSettings != null){
|
||||||
currentFormSettings.BringToFront();
|
currentFormSettings.BringToFront();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
||||||
|
|
||||||
currentFormSettings = new FormSettings(this, plugins, updates);
|
currentFormSettings = new FormSettings(this, plugins, updates, tabIndex);
|
||||||
|
|
||||||
currentFormSettings.FormClosed += (sender, args) => {
|
currentFormSettings.FormClosed += (sender, args) => {
|
||||||
currentFormSettings = null;
|
currentFormSettings = null;
|
||||||
|
|
||||||
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
|
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
|
||||||
|
updates.Settings.DismissedUpdate = string.Empty;
|
||||||
Config.DismissedUpdate = string.Empty;
|
Config.DismissedUpdate = string.Empty;
|
||||||
Config.Save();
|
Config.Save();
|
||||||
|
|
||||||
updates.Check(false);
|
updates.Check(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Config.EnableTrayHighlight){
|
if (!Config.EnableTrayHighlight){
|
||||||
trayIcon.HasNotifications = false;
|
trayIcon.HasNotifications = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateProperties(PropertyBridge.Properties.ExpandLinksOnHover | PropertyBridge.Properties.HasCustomNotificationSound);
|
||||||
};
|
};
|
||||||
|
|
||||||
ShowChildForm(currentFormSettings);
|
ShowChildForm(currentFormSettings);
|
||||||
@@ -327,19 +365,15 @@ namespace TweetDck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void PlayNotificationSound(){
|
public void PlayNotificationSound(){
|
||||||
if (string.IsNullOrEmpty(Config.NotificationSoundPath)){
|
if (Config.NotificationSoundPath.Length == 0){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notificationSound == null){
|
if (soundNotification == null){
|
||||||
notificationSound = new SoundPlayer();
|
soundNotification = new SoundNotification(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notificationSound.SoundLocation != Config.NotificationSoundPath){
|
soundNotification.Play(Config.NotificationSoundPath);
|
||||||
notificationSound.SoundLocation = Config.NotificationSoundPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
notificationSound.Play();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnTweetScreenshotReady(string html, int width, int height){
|
public void OnTweetScreenshotReady(string html, int width, int height){
|
||||||
|
@@ -133,7 +133,7 @@ namespace TweetDck.Core{
|
|||||||
|
|
||||||
if (!flags.HasFlag(NotificationFlags.DisableScripts)){
|
if (!flags.HasFlag(NotificationFlags.DisableScripts)){
|
||||||
notificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
|
notificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
|
||||||
browser.RegisterJsObject("$TD", new TweetDeckBridge(owner, this));
|
browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(owner, this));
|
||||||
|
|
||||||
if (plugins != null){
|
if (plugins != null){
|
||||||
pluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
|
pluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
|
||||||
@@ -238,6 +238,7 @@ namespace TweetDck.Core{
|
|||||||
|
|
||||||
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
if (e.Frame.IsMain && notificationJS != null && browser.Address != "about:blank" && !flags.HasFlag(NotificationFlags.DisableScripts)){
|
if (e.Frame.IsMain && notificationJS != null && browser.Address != "about:blank" && !flags.HasFlag(NotificationFlags.DisableScripts)){
|
||||||
|
e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Properties.ExpandLinksOnHover));
|
||||||
ScriptLoader.ExecuteScript(e.Frame, notificationJS, NotificationScriptIdentifier);
|
ScriptLoader.ExecuteScript(e.Frame, notificationJS, NotificationScriptIdentifier);
|
||||||
|
|
||||||
if (plugins != null && plugins.HasAnyPlugin(PluginEnvironment.Notification)){
|
if (plugins != null && plugins.HasAnyPlugin(PluginEnvironment.Notification)){
|
||||||
@@ -435,7 +436,7 @@ namespace TweetDck.Core{
|
|||||||
else{
|
else{
|
||||||
Point position = PointToClient(Cursor.Position);
|
Point position = PointToClient(Cursor.Position);
|
||||||
position.Offset(20, 5);
|
position.Offset(20, 5);
|
||||||
toolTip.Show(text, this, position);
|
toolTip.Show(text, this, position); // TODO figure out flickering when moving the mouse
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -101,7 +101,7 @@ namespace TweetDck.Core.Handling{
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void SetClipboardText(string text){
|
protected void SetClipboardText(string text){
|
||||||
form.InvokeSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
|
form.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void RemoveSeparatorIfLast(IMenuModel model){
|
protected static void RemoveSeparatorIfLast(IMenuModel model){
|
||||||
|
@@ -33,6 +33,10 @@ namespace TweetDck.Core.Handling{
|
|||||||
model.Remove(CefMenuCommand.ViewSource);
|
model.Remove(CefMenuCommand.ViewSource);
|
||||||
RemoveSeparatorIfLast(model);
|
RemoveSeparatorIfLast(model);
|
||||||
|
|
||||||
|
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
|
||||||
|
model.AddSeparator();
|
||||||
|
}
|
||||||
|
|
||||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
|
|
||||||
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
|
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
|
||||||
@@ -76,6 +80,8 @@ namespace TweetDck.Core.Handling{
|
|||||||
AddDebugMenuItems(globalMenu);
|
AddDebugMenuItems(globalMenu);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RemoveSeparatorIfLast(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
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){
|
||||||
@@ -89,19 +95,19 @@ namespace TweetDck.Core.Handling{
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuSettings:
|
case MenuSettings:
|
||||||
form.InvokeSafe(form.OpenSettings);
|
form.InvokeAsyncSafe(form.OpenSettings);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuAbout:
|
case MenuAbout:
|
||||||
form.InvokeSafe(form.OpenAbout);
|
form.InvokeAsyncSafe(form.OpenAbout);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuPlugins:
|
case MenuPlugins:
|
||||||
form.InvokeSafe(form.OpenPlugins);
|
form.InvokeAsyncSafe(form.OpenPlugins);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuMute:
|
case MenuMute:
|
||||||
form.InvokeSafe(() => {
|
form.InvokeAsyncSafe(() => {
|
||||||
Program.UserConfig.MuteNotifications = !Program.UserConfig.MuteNotifications;
|
Program.UserConfig.MuteNotifications = !Program.UserConfig.MuteNotifications;
|
||||||
Program.UserConfig.Save();
|
Program.UserConfig.Save();
|
||||||
});
|
});
|
||||||
@@ -117,7 +123,7 @@ namespace TweetDck.Core.Handling{
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuScreenshotTweet:
|
case MenuScreenshotTweet:
|
||||||
form.InvokeSafe(form.TriggerTweetScreenshot);
|
form.InvokeAsyncSafe(form.TriggerTweetScreenshot);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuOpenQuotedTweetUrl:
|
case MenuOpenQuotedTweetUrl:
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDck.Core.Controls;
|
using TweetDck.Core.Controls;
|
||||||
using TweetDck.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDck.Core.Handling{
|
namespace TweetDck.Core.Handling{
|
||||||
class ContextMenuNotification : ContextMenuBase{
|
class ContextMenuNotification : ContextMenuBase{
|
||||||
@@ -19,6 +18,12 @@ namespace TweetDck.Core.Handling{
|
|||||||
|
|
||||||
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();
|
||||||
|
|
||||||
|
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
|
||||||
|
model.AddItem(CefMenuCommand.Copy, "Copy");
|
||||||
|
model.AddSeparator();
|
||||||
|
}
|
||||||
|
|
||||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
|
|
||||||
if (enableCustomMenu){
|
if (enableCustomMenu){
|
||||||
@@ -44,7 +49,7 @@ namespace TweetDck.Core.Handling{
|
|||||||
|
|
||||||
RemoveSeparatorIfLast(model);
|
RemoveSeparatorIfLast(model);
|
||||||
|
|
||||||
form.InvokeSafe(() => form.ContextMenuOpen = true);
|
form.InvokeAsyncSafe(() => form.ContextMenuOpen = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
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){
|
||||||
@@ -54,11 +59,11 @@ namespace TweetDck.Core.Handling{
|
|||||||
|
|
||||||
switch((int)commandId){
|
switch((int)commandId){
|
||||||
case MenuSkipTweet:
|
case MenuSkipTweet:
|
||||||
form.InvokeSafe(form.FinishCurrentTweet);
|
form.InvokeAsyncSafe(form.FinishCurrentTweet);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuFreeze:
|
case MenuFreeze:
|
||||||
form.InvokeSafe(() => form.FreezeTimer = !form.FreezeTimer);
|
form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuCopyTweetUrl:
|
case MenuCopyTweetUrl:
|
||||||
@@ -75,7 +80,7 @@ namespace TweetDck.Core.Handling{
|
|||||||
|
|
||||||
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
||||||
base.OnContextMenuDismissed(browserControl, browser, frame);
|
base.OnContextMenuDismissed(browserControl, browser, frame);
|
||||||
form.InvokeSafe(() => form.ContextMenuOpen = false);
|
form.InvokeAsyncSafe(() => form.ContextMenuOpen = false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ namespace TweetDck.Core.Notification.Screenshot{
|
|||||||
|
|
||||||
browser.FrameLoadEnd += (sender, args) => {
|
browser.FrameLoadEnd += (sender, args) => {
|
||||||
if (args.Frame.IsMain && browser.Address != "about:blank"){
|
if (args.Frame.IsMain && browser.Address != "about:blank"){
|
||||||
ScriptLoader.ExecuteScript(args.Frame, "window.setTimeout(() => $TD_NotificationScreenshot.trigger(), 25)", "gen:screenshot");
|
ScriptLoader.ExecuteScript(args.Frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 25)", "gen:screenshot");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
66
Core/Notification/SoundNotification.cs
Normal file
66
Core/Notification/SoundNotification.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Media;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDck.Core.Other;
|
||||||
|
|
||||||
|
namespace TweetDck.Core.Notification{
|
||||||
|
class SoundNotification : IDisposable{
|
||||||
|
private readonly FormBrowser browserForm;
|
||||||
|
|
||||||
|
private SoundPlayer notificationSound;
|
||||||
|
private bool ignoreNotificationSoundError;
|
||||||
|
|
||||||
|
public SoundNotification(FormBrowser browserForm){
|
||||||
|
this.browserForm = browserForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Play(string file){
|
||||||
|
if (notificationSound == null){
|
||||||
|
notificationSound = new SoundPlayer{
|
||||||
|
LoadTimeout = 5000
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notificationSound.SoundLocation != file){
|
||||||
|
notificationSound.SoundLocation = file;
|
||||||
|
ignoreNotificationSoundError = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
notificationSound.Play();
|
||||||
|
}catch(FileNotFoundException e){
|
||||||
|
OnNotificationSoundError("File not found: "+e.FileName);
|
||||||
|
}catch(InvalidOperationException){
|
||||||
|
OnNotificationSoundError("File is not a valid sound file.");
|
||||||
|
}catch(TimeoutException){
|
||||||
|
OnNotificationSoundError("File took too long to load.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnNotificationSoundError(string message){
|
||||||
|
if (!ignoreNotificationSoundError){
|
||||||
|
ignoreNotificationSoundError = true;
|
||||||
|
|
||||||
|
using(FormMessage form = new FormMessage("Notification Sound Error", "Could not play custom notification sound."+Environment.NewLine+message, MessageBoxIcon.Error)){
|
||||||
|
form.AddButton("Ignore");
|
||||||
|
|
||||||
|
Button btnOpenSettings = form.AddButton("Open Settings");
|
||||||
|
btnOpenSettings.Width += 16;
|
||||||
|
btnOpenSettings.Location = new Point(btnOpenSettings.Location.X-16, btnOpenSettings.Location.Y);
|
||||||
|
|
||||||
|
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnOpenSettings){
|
||||||
|
browserForm.OpenSettings(FormSettings.TabIndexNotification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose(){
|
||||||
|
if (notificationSound != null){
|
||||||
|
notificationSound.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -9,10 +9,13 @@ using TweetDck.Updates;
|
|||||||
|
|
||||||
namespace TweetDck.Core.Other{
|
namespace TweetDck.Core.Other{
|
||||||
sealed partial class FormSettings : Form{
|
sealed partial class FormSettings : Form{
|
||||||
|
public const int TabIndexNotification = 1;
|
||||||
|
|
||||||
private readonly FormBrowser browser;
|
private readonly FormBrowser browser;
|
||||||
private readonly Dictionary<Type, BaseTabSettings> tabs = new Dictionary<Type, BaseTabSettings>(4);
|
private readonly Dictionary<Type, BaseTabSettings> tabs = new Dictionary<Type, BaseTabSettings>(4);
|
||||||
|
private readonly bool hasFinishedLoading;
|
||||||
|
|
||||||
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates){
|
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, int startTabIndex = 0){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName+" Settings";
|
Text = Program.BrandName+" Settings";
|
||||||
@@ -22,10 +25,12 @@ namespace TweetDck.Core.Other{
|
|||||||
|
|
||||||
this.tabPanel.SetupTabPanel(100);
|
this.tabPanel.SetupTabPanel(100);
|
||||||
this.tabPanel.AddButton("General", SelectTab<TabSettingsGeneral>);
|
this.tabPanel.AddButton("General", SelectTab<TabSettingsGeneral>);
|
||||||
this.tabPanel.AddButton("Notifications", () => SelectTab(() => new TabSettingsNotifications(browser.CreateNotificationForm(NotificationFlags.DisableContextMenu))));
|
this.tabPanel.AddButton("Notifications", () => SelectTab(() => new TabSettingsNotifications(browser.CreateNotificationForm(NotificationFlags.DisableContextMenu), !hasFinishedLoading)));
|
||||||
this.tabPanel.AddButton("Updates", () => SelectTab(() => new TabSettingsUpdates(updates)));
|
this.tabPanel.AddButton("Updates", () => SelectTab(() => new TabSettingsUpdates(updates)));
|
||||||
this.tabPanel.AddButton("Advanced", () => SelectTab(() => new TabSettingsAdvanced(browser.ReloadBrowser, plugins)));
|
this.tabPanel.AddButton("Advanced", () => SelectTab(() => new TabSettingsAdvanced(browser.ReinjectCustomCSS, plugins)));
|
||||||
this.tabPanel.SelectTab(tabPanel.Buttons.First());
|
|
||||||
|
this.tabPanel.SelectTab(tabPanel.Buttons.ElementAt(startTabIndex));
|
||||||
|
hasFinishedLoading = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SelectTab<T>() where T : BaseTabSettings, new(){
|
private void SelectTab<T>() where T : BaseTabSettings, new(){
|
||||||
|
@@ -23,6 +23,7 @@
|
|||||||
/// the contents of this method with the code editor.
|
/// the contents of this method with the code editor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void InitializeComponent() {
|
private void InitializeComponent() {
|
||||||
|
this.components = new System.ComponentModel.Container();
|
||||||
this.textBoxBrowserCSS = new System.Windows.Forms.TextBox();
|
this.textBoxBrowserCSS = new System.Windows.Forms.TextBox();
|
||||||
this.btnCancel = new System.Windows.Forms.Button();
|
this.btnCancel = new System.Windows.Forms.Button();
|
||||||
this.btnApply = new System.Windows.Forms.Button();
|
this.btnApply = new System.Windows.Forms.Button();
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
this.textBoxNotificationCSS = new System.Windows.Forms.TextBox();
|
this.textBoxNotificationCSS = new System.Windows.Forms.TextBox();
|
||||||
this.labelWarning = new System.Windows.Forms.Label();
|
this.labelWarning = new System.Windows.Forms.Label();
|
||||||
this.btnOpenWiki = new System.Windows.Forms.Button();
|
this.btnOpenWiki = new System.Windows.Forms.Button();
|
||||||
|
this.timerTestBrowser = new System.Windows.Forms.Timer(this.components);
|
||||||
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
|
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
|
||||||
this.splitContainer.Panel1.SuspendLayout();
|
this.splitContainer.Panel1.SuspendLayout();
|
||||||
this.splitContainer.Panel2.SuspendLayout();
|
this.splitContainer.Panel2.SuspendLayout();
|
||||||
@@ -52,6 +54,7 @@
|
|||||||
this.textBoxBrowserCSS.Size = new System.Drawing.Size(373, 253);
|
this.textBoxBrowserCSS.Size = new System.Drawing.Size(373, 253);
|
||||||
this.textBoxBrowserCSS.TabIndex = 0;
|
this.textBoxBrowserCSS.TabIndex = 0;
|
||||||
this.textBoxBrowserCSS.WordWrap = false;
|
this.textBoxBrowserCSS.WordWrap = false;
|
||||||
|
this.textBoxBrowserCSS.KeyUp += new System.Windows.Forms.KeyEventHandler(this.textBoxBrowserCSS_KeyUp);
|
||||||
//
|
//
|
||||||
// btnCancel
|
// btnCancel
|
||||||
//
|
//
|
||||||
@@ -132,7 +135,7 @@
|
|||||||
this.textBoxNotificationCSS.Multiline = true;
|
this.textBoxNotificationCSS.Multiline = true;
|
||||||
this.textBoxNotificationCSS.Name = "textBoxNotificationCSS";
|
this.textBoxNotificationCSS.Name = "textBoxNotificationCSS";
|
||||||
this.textBoxNotificationCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
this.textBoxNotificationCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||||
this.textBoxNotificationCSS.Size = new System.Drawing.Size(376, 253);
|
this.textBoxNotificationCSS.Size = new System.Drawing.Size(373, 253);
|
||||||
this.textBoxNotificationCSS.TabIndex = 1;
|
this.textBoxNotificationCSS.TabIndex = 1;
|
||||||
this.textBoxNotificationCSS.WordWrap = false;
|
this.textBoxNotificationCSS.WordWrap = false;
|
||||||
//
|
//
|
||||||
@@ -159,6 +162,11 @@
|
|||||||
this.btnOpenWiki.UseVisualStyleBackColor = true;
|
this.btnOpenWiki.UseVisualStyleBackColor = true;
|
||||||
this.btnOpenWiki.Click += new System.EventHandler(this.btnOpenWiki_Click);
|
this.btnOpenWiki.Click += new System.EventHandler(this.btnOpenWiki_Click);
|
||||||
//
|
//
|
||||||
|
// timerTestBrowser
|
||||||
|
//
|
||||||
|
this.timerTestBrowser.Interval = 500;
|
||||||
|
this.timerTestBrowser.Tick += new System.EventHandler(this.timerTestBrowser_Tick);
|
||||||
|
//
|
||||||
// DialogSettingsCSS
|
// DialogSettingsCSS
|
||||||
//
|
//
|
||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
@@ -195,5 +203,6 @@
|
|||||||
private System.Windows.Forms.Label labelNotification;
|
private System.Windows.Forms.Label labelNotification;
|
||||||
private System.Windows.Forms.Label labelWarning;
|
private System.Windows.Forms.Label labelWarning;
|
||||||
private System.Windows.Forms.Button btnOpenWiki;
|
private System.Windows.Forms.Button btnOpenWiki;
|
||||||
|
private System.Windows.Forms.Timer timerTestBrowser;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -17,11 +17,15 @@ namespace TweetDck.Core.Other.Settings.Dialogs{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DialogSettingsCSS(){
|
private readonly Action<string> reinjectBrowserCSS;
|
||||||
|
|
||||||
|
public DialogSettingsCSS(Action<string> reinjectBrowserCSS){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName+" Settings - CSS";
|
Text = Program.BrandName+" Settings - CSS";
|
||||||
|
|
||||||
|
this.reinjectBrowserCSS = reinjectBrowserCSS;
|
||||||
|
|
||||||
textBoxBrowserCSS.EnableMultilineShortcuts();
|
textBoxBrowserCSS.EnableMultilineShortcuts();
|
||||||
textBoxBrowserCSS.Text = Program.UserConfig.CustomBrowserCSS ?? "";
|
textBoxBrowserCSS.Text = Program.UserConfig.CustomBrowserCSS ?? "";
|
||||||
|
|
||||||
@@ -29,6 +33,16 @@ namespace TweetDck.Core.Other.Settings.Dialogs{
|
|||||||
textBoxNotificationCSS.Text = Program.UserConfig.CustomNotificationCSS ?? "";
|
textBoxNotificationCSS.Text = Program.UserConfig.CustomNotificationCSS ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void textBoxBrowserCSS_KeyUp(object sender, KeyEventArgs e){
|
||||||
|
timerTestBrowser.Stop();
|
||||||
|
timerTestBrowser.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void timerTestBrowser_Tick(object sender, EventArgs e){
|
||||||
|
reinjectBrowserCSS(textBoxBrowserCSS.Text);
|
||||||
|
timerTestBrowser.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
private void btnOpenWiki_Click(object sender, EventArgs e){
|
private void btnOpenWiki_Click(object sender, EventArgs e){
|
||||||
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/wiki");
|
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/wiki");
|
||||||
}
|
}
|
||||||
|
@@ -104,6 +104,7 @@
|
|||||||
this.Controls.Add(this.cbConfig);
|
this.Controls.Add(this.cbConfig);
|
||||||
this.Controls.Add(this.btnApply);
|
this.Controls.Add(this.btnApply);
|
||||||
this.Controls.Add(this.btnCancel);
|
this.Controls.Add(this.btnCancel);
|
||||||
|
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
|
||||||
this.MinimumSize = new System.Drawing.Size(200, 170);
|
this.MinimumSize = new System.Drawing.Size(200, 170);
|
||||||
this.Name = "DialogSettingsExport";
|
this.Name = "DialogSettingsExport";
|
||||||
this.ShowIcon = false;
|
this.ShowIcon = false;
|
||||||
|
@@ -9,13 +9,13 @@ using TweetDck.Plugins;
|
|||||||
|
|
||||||
namespace TweetDck.Core.Other.Settings{
|
namespace TweetDck.Core.Other.Settings{
|
||||||
partial class TabSettingsAdvanced : BaseTabSettings{
|
partial class TabSettingsAdvanced : BaseTabSettings{
|
||||||
private readonly Action browserReloadAction;
|
private readonly Action<string> reinjectBrowserCSS;
|
||||||
private readonly PluginManager plugins;
|
private readonly PluginManager plugins;
|
||||||
|
|
||||||
public TabSettingsAdvanced(Action browserReloadAction, PluginManager plugins){
|
public TabSettingsAdvanced(Action<string> reinjectBrowserCSS, PluginManager plugins){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
this.browserReloadAction = browserReloadAction;
|
this.reinjectBrowserCSS = reinjectBrowserCSS;
|
||||||
this.plugins = plugins;
|
this.plugins = plugins;
|
||||||
|
|
||||||
checkHardwareAcceleration.Checked = HardwareAcceleration.IsEnabled;
|
checkHardwareAcceleration.Checked = HardwareAcceleration.IsEnabled;
|
||||||
@@ -80,17 +80,13 @@ namespace TweetDck.Core.Other.Settings{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void btnEditCSS_Click(object sender, EventArgs e){
|
private void btnEditCSS_Click(object sender, EventArgs e){
|
||||||
using(DialogSettingsCSS form = new DialogSettingsCSS()){
|
using(DialogSettingsCSS form = new DialogSettingsCSS(reinjectBrowserCSS)){
|
||||||
if (form.ShowDialog(ParentForm) == DialogResult.OK){
|
if (form.ShowDialog(ParentForm) == DialogResult.OK){
|
||||||
bool hasChangedBrowser = form.BrowserCSS != Config.CustomBrowserCSS;
|
|
||||||
|
|
||||||
Config.CustomBrowserCSS = form.BrowserCSS;
|
Config.CustomBrowserCSS = form.BrowserCSS;
|
||||||
Config.CustomNotificationCSS = form.NotificationCSS;
|
Config.CustomNotificationCSS = form.NotificationCSS;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasChangedBrowser && MessageBox.Show("The browser CSS has changed, do you want to reload it?", "Browser CSS Changed", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
reinjectBrowserCSS(Config.CustomBrowserCSS); // reinject on cancel too, because the CSS is updated while typing
|
||||||
browserReloadAction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -381,6 +381,7 @@
|
|||||||
this.tbCustomSound.Name = "tbCustomSound";
|
this.tbCustomSound.Name = "tbCustomSound";
|
||||||
this.tbCustomSound.Size = new System.Drawing.Size(170, 20);
|
this.tbCustomSound.Size = new System.Drawing.Size(170, 20);
|
||||||
this.tbCustomSound.TabIndex = 0;
|
this.tbCustomSound.TabIndex = 0;
|
||||||
|
this.tbCustomSound.TextChanged += new System.EventHandler(this.tbCustomSound_TextChanged);
|
||||||
//
|
//
|
||||||
// TabSettingsNotifications
|
// TabSettingsNotifications
|
||||||
//
|
//
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDck.Core.Controls;
|
using TweetDck.Core.Controls;
|
||||||
using TweetDck.Core.Notification;
|
using TweetDck.Core.Notification;
|
||||||
@@ -11,7 +12,7 @@ namespace TweetDck.Core.Other.Settings{
|
|||||||
private readonly FormNotification notification;
|
private readonly FormNotification notification;
|
||||||
private readonly Point initCursorPosition;
|
private readonly Point initCursorPosition;
|
||||||
|
|
||||||
public TabSettingsNotifications(FormNotification notification){
|
public TabSettingsNotifications(FormNotification notification, bool ignoreAutoClick){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
this.notification = notification;
|
this.notification = notification;
|
||||||
@@ -30,7 +31,7 @@ namespace TweetDck.Core.Other.Settings{
|
|||||||
this.notification.Activated += notification_Activated;
|
this.notification.Activated += notification_Activated;
|
||||||
this.notification.Show(this);
|
this.notification.Show(this);
|
||||||
|
|
||||||
initCursorPosition = Cursor.Position;
|
initCursorPosition = ignoreAutoClick ? ControlExtensions.InvisibleLocation : Cursor.Position;
|
||||||
|
|
||||||
switch(Config.NotificationPosition){
|
switch(Config.NotificationPosition){
|
||||||
case TweetNotification.Position.TopLeft: radioLocTL.Checked = true; break;
|
case TweetNotification.Position.TopLeft: radioLocTL.Checked = true; break;
|
||||||
@@ -58,7 +59,7 @@ namespace TweetDck.Core.Other.Settings{
|
|||||||
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
|
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
|
||||||
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value.ToString(CultureInfo.InvariantCulture)+" px";
|
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value.ToString(CultureInfo.InvariantCulture)+" px";
|
||||||
|
|
||||||
tbCustomSound.Text = Config.NotificationSoundPath ?? string.Empty;
|
tbCustomSound.Text = Config.NotificationSoundPath;
|
||||||
|
|
||||||
Disposed += (sender, args) => this.notification.Dispose();
|
Disposed += (sender, args) => this.notification.Dispose();
|
||||||
}
|
}
|
||||||
@@ -77,7 +78,7 @@ namespace TweetDck.Core.Other.Settings{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void notification_Activated(object sender, EventArgs e){
|
private void notification_Activated(object sender, EventArgs e){
|
||||||
if (Cursor.Position == initCursorPosition){
|
if (Cursor.Position == initCursorPosition && initCursorPosition != ControlExtensions.InvisibleLocation){
|
||||||
Timer delay = WindowsUtils.CreateSingleTickTimer(1);
|
Timer delay = WindowsUtils.CreateSingleTickTimer(1);
|
||||||
|
|
||||||
delay.Tick += (sender2, args2) => { // here you can see a disgusting hack to force the freshly opened notification window out of focus
|
delay.Tick += (sender2, args2) => { // here you can see a disgusting hack to force the freshly opened notification window out of focus
|
||||||
@@ -167,6 +168,13 @@ namespace TweetDck.Core.Other.Settings{
|
|||||||
notification.ShowNotificationForSettings(false);
|
notification.ShowNotificationForSettings(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void tbCustomSound_TextChanged(object sender, EventArgs e){
|
||||||
|
// also runs when the control is created, i.e. when Ready is false
|
||||||
|
|
||||||
|
bool fileExists = string.IsNullOrEmpty(tbCustomSound.Text) || File.Exists(tbCustomSound.Text);
|
||||||
|
tbCustomSound.ForeColor = fileExists ? SystemColors.WindowText : Color.Maroon;
|
||||||
|
}
|
||||||
|
|
||||||
private void btnBrowseSound_Click(object sender, EventArgs e){
|
private void btnBrowseSound_Click(object sender, EventArgs e){
|
||||||
using(OpenFileDialog dialog = new OpenFileDialog{
|
using(OpenFileDialog dialog = new OpenFileDialog{
|
||||||
AutoUpgradeEnabled = true,
|
AutoUpgradeEnabled = true,
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDck.Updates;
|
using TweetDck.Updates;
|
||||||
|
using TweetDck.Updates.Events;
|
||||||
|
|
||||||
namespace TweetDck.Core.Other.Settings{
|
namespace TweetDck.Core.Other.Settings{
|
||||||
partial class TabSettingsUpdates : BaseTabSettings{
|
partial class TabSettingsUpdates : BaseTabSettings{
|
||||||
@@ -27,12 +28,18 @@ namespace TweetDck.Core.Other.Settings{
|
|||||||
private void btnCheckUpdates_Click(object sender, EventArgs e){
|
private void btnCheckUpdates_Click(object sender, EventArgs e){
|
||||||
if (!Ready)return;
|
if (!Ready)return;
|
||||||
|
|
||||||
Config.DismissedUpdate = string.Empty;
|
|
||||||
Config.Save();
|
|
||||||
|
|
||||||
updateCheckEventId = updates.Check(true);
|
updateCheckEventId = updates.Check(true);
|
||||||
|
|
||||||
|
if (updateCheckEventId == -1){
|
||||||
|
MessageBox.Show("Sorry, your system is no longer supported.", "Unsupported System", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
}
|
||||||
|
else{
|
||||||
btnCheckUpdates.Enabled = false;
|
btnCheckUpdates.Enabled = false;
|
||||||
|
|
||||||
|
updates.Settings.DismissedUpdate = string.Empty;
|
||||||
|
Config.DismissedUpdate = string.Empty;
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
|
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
|
||||||
|
100
Core/Utils/TwoKeyDictionary.cs
Normal file
100
Core/Utils/TwoKeyDictionary.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace TweetDck.Core.Utils{
|
||||||
|
class TwoKeyDictionary<K1, K2, V>{
|
||||||
|
private readonly Dictionary<K1, Dictionary<K2, V>> dict;
|
||||||
|
private readonly int innerCapacity;
|
||||||
|
|
||||||
|
public TwoKeyDictionary() : this(16, 16){}
|
||||||
|
|
||||||
|
public TwoKeyDictionary(int outerCapacity, int innerCapacity){
|
||||||
|
this.dict = new Dictionary<K1, Dictionary<K2, V>>(outerCapacity);
|
||||||
|
this.innerCapacity = innerCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
|
||||||
|
public V this[K1 outerKey, K2 innerKey]{
|
||||||
|
get{ // throws on missing key
|
||||||
|
return dict[outerKey][innerKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
Dictionary<K2, V> innerDict;
|
||||||
|
|
||||||
|
if (!dict.TryGetValue(outerKey, out innerDict)){
|
||||||
|
dict.Add(outerKey, innerDict = new Dictionary<K2, V>(innerCapacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
innerDict[innerKey] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Members
|
||||||
|
|
||||||
|
public void Add(K1 outerKey, K2 innerKey, V value){ // throws on duplicate
|
||||||
|
Dictionary<K2, V> innerDict;
|
||||||
|
|
||||||
|
if (!dict.TryGetValue(outerKey, out innerDict)){
|
||||||
|
dict.Add(outerKey, innerDict = new Dictionary<K2, V>(innerCapacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
innerDict.Add(innerKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear(){
|
||||||
|
this.dict.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear(K1 outerKey){ // throws on missing key, but keeps the key unlike Remove(K1)
|
||||||
|
dict[outerKey].Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(K1 outerKey){
|
||||||
|
return dict.ContainsKey(outerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(K1 outerKey, K2 innerKey){
|
||||||
|
Dictionary<K2, V> innerDict;
|
||||||
|
return dict.TryGetValue(outerKey, out innerDict) && innerDict.ContainsKey(innerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count(){
|
||||||
|
return dict.Values.Sum(d => d.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count(K1 outerKey){ // throws on missing key
|
||||||
|
return dict[outerKey].Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(K1 outerKey){
|
||||||
|
return dict.Remove(outerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(K1 outerKey, K2 innerKey){
|
||||||
|
Dictionary<K2, V> innerDict;
|
||||||
|
|
||||||
|
if (dict.TryGetValue(outerKey, out innerDict) && innerDict.Remove(innerKey)){
|
||||||
|
if (innerDict.Count == 0){
|
||||||
|
dict.Remove(outerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(K1 outerKey, K2 innerKey, out V value){
|
||||||
|
Dictionary<K2, V> innerDict;
|
||||||
|
|
||||||
|
if (dict.TryGetValue(outerKey, out innerDict)){
|
||||||
|
return innerDict.TryGetValue(innerKey, out value);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
value = default(V);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,24 +1,35 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using TweetDck.Core.Utils;
|
||||||
using TweetDck.Plugins.Enums;
|
using TweetDck.Plugins.Enums;
|
||||||
using TweetDck.Plugins.Events;
|
using TweetDck.Plugins.Events;
|
||||||
|
|
||||||
namespace TweetDck.Plugins{
|
namespace TweetDck.Plugins{
|
||||||
class PluginBridge{
|
class PluginBridge{
|
||||||
|
private static string SanitizeCacheKey(string key){
|
||||||
|
return key.Replace('\\', '/').Trim();
|
||||||
|
}
|
||||||
|
|
||||||
private readonly PluginManager manager;
|
private readonly PluginManager manager;
|
||||||
private readonly Dictionary<string, string> fileCache = new Dictionary<string, string>(2);
|
private readonly TwoKeyDictionary<int, string, string> fileCache = new TwoKeyDictionary<int, string, string>(4, 2);
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void manager_Reloaded(object sender, PluginLoadEventArgs e){
|
private void manager_Reloaded(object sender, PluginLoadEventArgs e){
|
||||||
fileCache.Clear();
|
fileCache.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void manager_PluginChangedState(object sender, PluginChangedStateEventArgs e){
|
||||||
|
if (!e.IsEnabled){
|
||||||
|
fileCache.Remove(manager.GetTokenFromPlugin(e.Plugin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
|
private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
|
||||||
Plugin plugin = manager.GetPluginFromToken(token);
|
Plugin plugin = manager.GetPluginFromToken(token);
|
||||||
string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(folder, path);
|
string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(folder, path);
|
||||||
@@ -35,15 +46,17 @@ namespace TweetDck.Plugins{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ReadFileUnsafe(string fullPath, bool readCached){
|
private string ReadFileUnsafe(int token, string cacheKey, string fullPath, bool readCached){
|
||||||
|
cacheKey = SanitizeCacheKey(cacheKey);
|
||||||
|
|
||||||
string cachedContents;
|
string cachedContents;
|
||||||
|
|
||||||
if (readCached && fileCache.TryGetValue(fullPath, out cachedContents)){
|
if (readCached && fileCache.TryGetValue(token, cacheKey, out cachedContents)){
|
||||||
return cachedContents;
|
return cachedContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
return fileCache[fullPath] = File.ReadAllText(fullPath, Encoding.UTF8);
|
return fileCache[token, cacheKey] = File.ReadAllText(fullPath, Encoding.UTF8);
|
||||||
}catch(FileNotFoundException){
|
}catch(FileNotFoundException){
|
||||||
throw new Exception("File not found.");
|
throw new Exception("File not found.");
|
||||||
}catch(DirectoryNotFoundException){
|
}catch(DirectoryNotFoundException){
|
||||||
@@ -60,17 +73,17 @@ namespace TweetDck.Plugins{
|
|||||||
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
||||||
|
|
||||||
File.WriteAllText(fullPath, contents, Encoding.UTF8);
|
File.WriteAllText(fullPath, contents, Encoding.UTF8);
|
||||||
fileCache[fullPath] = contents;
|
fileCache[token, SanitizeCacheKey(path)] = contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ReadFile(int token, string path, bool cache){
|
public string ReadFile(int token, string path, bool cache){
|
||||||
return ReadFileUnsafe(GetFullPathOrThrow(token, PluginFolder.Data, path), cache);
|
return ReadFileUnsafe(token, path, GetFullPathOrThrow(token, PluginFolder.Data, path), cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteFile(int token, string path){
|
public void DeleteFile(int token, string path){
|
||||||
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
|
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
|
||||||
|
|
||||||
fileCache.Remove(fullPath);
|
fileCache.Remove(token, SanitizeCacheKey(path));
|
||||||
File.Delete(fullPath);
|
File.Delete(fullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +92,7 @@ namespace TweetDck.Plugins{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string ReadFileRoot(int token, string path){
|
public string ReadFileRoot(int token, string path){
|
||||||
return ReadFileUnsafe(GetFullPathOrThrow(token, PluginFolder.Root, path), true);
|
return ReadFileUnsafe(token, "root*"+path, GetFullPathOrThrow(token, PluginFolder.Root, path), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheckFileExistsRoot(int token, string path){
|
public bool CheckFileExistsRoot(int token, string path){
|
||||||
|
@@ -13,6 +13,8 @@ namespace TweetDck.Plugins{
|
|||||||
public const string PluginNotificationScriptFile = "plugins.notification.js";
|
public const string PluginNotificationScriptFile = "plugins.notification.js";
|
||||||
public const string PluginGlobalScriptFile = "plugins.js";
|
public const string PluginGlobalScriptFile = "plugins.js";
|
||||||
|
|
||||||
|
private const int InvalidToken = 0;
|
||||||
|
|
||||||
public string PathOfficialPlugins { get { return Path.Combine(rootPath, "official"); } }
|
public string PathOfficialPlugins { get { return Path.Combine(rootPath, "official"); } }
|
||||||
public string PathCustomPlugins { get { return Path.Combine(rootPath, "user"); } }
|
public string PathCustomPlugins { get { return Path.Combine(rootPath, "user"); } }
|
||||||
|
|
||||||
@@ -63,6 +65,16 @@ namespace TweetDck.Plugins{
|
|||||||
return plugins.Any(plugin => plugin.Environments.HasFlag(environment));
|
return plugins.Any(plugin => plugin.Environments.HasFlag(environment));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int GetTokenFromPlugin(Plugin plugin){
|
||||||
|
foreach(KeyValuePair<int, Plugin> kvp in tokens){
|
||||||
|
if (kvp.Value.Equals(plugin)){
|
||||||
|
return kvp.Key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return InvalidToken;
|
||||||
|
}
|
||||||
|
|
||||||
public Plugin GetPluginFromToken(int token){
|
public Plugin GetPluginFromToken(int token){
|
||||||
Plugin plugin;
|
Plugin plugin;
|
||||||
return tokens.TryGetValue(token, out plugin) ? plugin : null;
|
return tokens.TryGetValue(token, out plugin) ? plugin : null;
|
||||||
@@ -108,7 +120,7 @@ namespace TweetDck.Plugins{
|
|||||||
int token;
|
int token;
|
||||||
|
|
||||||
if (tokens.ContainsValue(plugin)){
|
if (tokens.ContainsValue(plugin)){
|
||||||
token = tokens.First(kvp => kvp.Value.Equals(plugin)).Key;
|
token = GetTokenFromPlugin(plugin);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
token = GenerateToken();
|
token = GenerateToken();
|
||||||
@@ -141,7 +153,7 @@ namespace TweetDck.Plugins{
|
|||||||
for(int attempt = 0; attempt < 1000; attempt++){
|
for(int attempt = 0; attempt < 1000; attempt++){
|
||||||
int token = rand.Next();
|
int token = rand.Next();
|
||||||
|
|
||||||
if (!tokens.ContainsKey(token)){
|
if (!tokens.ContainsKey(token) && token != InvalidToken){
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,8 +21,8 @@ namespace TweetDck{
|
|||||||
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.6.2";
|
public const string VersionTag = "1.6.4";
|
||||||
public const string VersionFull = "1.6.2.0";
|
public const string VersionFull = "1.6.4.0";
|
||||||
|
|
||||||
public static readonly Version Version = new Version(VersionTag);
|
public static readonly Version Version = new Version(VersionTag);
|
||||||
|
|
||||||
@@ -126,6 +126,8 @@ namespace TweetDck{
|
|||||||
|
|
||||||
BrowserCache.ClearOldCacheFiles();
|
BrowserCache.ClearOldCacheFiles();
|
||||||
|
|
||||||
|
CefSharpSettings.WcfEnabled = false;
|
||||||
|
|
||||||
CefSettings settings = new CefSettings{
|
CefSettings settings = new CefSettings{
|
||||||
AcceptLanguageList = BrowserUtils.HeaderAcceptLanguage,
|
AcceptLanguageList = BrowserUtils.HeaderAcceptLanguage,
|
||||||
UserAgent = BrowserUtils.HeaderUserAgent,
|
UserAgent = BrowserUtils.HeaderUserAgent,
|
||||||
@@ -155,7 +157,8 @@ namespace TweetDck{
|
|||||||
plugins.Reload();
|
plugins.Reload();
|
||||||
|
|
||||||
FormBrowser mainForm = new FormBrowser(plugins, new UpdaterSettings{
|
FormBrowser mainForm = new FormBrowser(plugins, new UpdaterSettings{
|
||||||
AllowPreReleases = Args.HasFlag("-debugupdates")
|
AllowPreReleases = Args.HasFlag("-debugupdates"),
|
||||||
|
DismissedUpdate = UserConfig.DismissedUpdate
|
||||||
});
|
});
|
||||||
|
|
||||||
Application.Run(mainForm);
|
Application.Run(mainForm);
|
||||||
|
14
Resources/Plugins/.debug/.meta
Normal file
14
Resources/Plugins/.debug/.meta
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[name]
|
||||||
|
Debug plugin
|
||||||
|
|
||||||
|
[description]
|
||||||
|
- Runs several tests on startup, only included in debug configuration
|
||||||
|
|
||||||
|
[author]
|
||||||
|
chylex
|
||||||
|
|
||||||
|
[version]
|
||||||
|
1.0
|
||||||
|
|
||||||
|
[website]
|
||||||
|
https://tweetduck.chylex.com
|
3
Resources/Plugins/.debug/browser.js
Normal file
3
Resources/Plugins/.debug/browser.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
enabled(){
|
||||||
|
|
||||||
|
}
|
@@ -2,6 +2,7 @@
|
|||||||
Revert TweetDeck design changes
|
Revert TweetDeck design changes
|
||||||
|
|
||||||
[description]
|
[description]
|
||||||
|
- Individually configurable options to revert and tweak TweetDeck design changes
|
||||||
- Moves action menu to the right and hides it by default
|
- Moves action menu to the right and hides it by default
|
||||||
- Reverts interactive texts around tweets (such as 'Details' or 'Conversation')
|
- Reverts interactive texts around tweets (such as 'Details' or 'Conversation')
|
||||||
|
|
||||||
@@ -9,10 +10,16 @@ Revert TweetDeck design changes
|
|||||||
chylex
|
chylex
|
||||||
|
|
||||||
[version]
|
[version]
|
||||||
1.1
|
1.2
|
||||||
|
|
||||||
[website]
|
[website]
|
||||||
https://tweetduck.chylex.com
|
https://tweetduck.chylex.com
|
||||||
|
|
||||||
|
[configfile]
|
||||||
|
configuration.js
|
||||||
|
|
||||||
|
[configdefault]
|
||||||
|
configuration.default.js
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
1.4.1
|
1.4.1
|
@@ -1,20 +1,36 @@
|
|||||||
enabled(){
|
enabled(){
|
||||||
// add a stylesheet to change tweet actions
|
|
||||||
this.css = window.TDPF_createCustomStyle(this);
|
this.css = window.TDPF_createCustomStyle(this);
|
||||||
this.css.insert(".tweet-actions { float: right !important; width: auto !important; }");
|
this.prevFooterMustache = TD.mustaches["status/tweet_single_footer.mustache"];
|
||||||
|
|
||||||
|
// load configuration
|
||||||
|
window.TDPF_loadConfigurationFile(this, "configuration.js", "configuration.default.js", config => {
|
||||||
|
if (config.hideTweetActions){
|
||||||
this.css.insert(".tweet-action { opacity: 0; }");
|
this.css.insert(".tweet-action { opacity: 0; }");
|
||||||
this.css.insert(".is-favorite .tweet-action, .is-retweet .tweet-action { opacity: 0.5; visibility: visible !important; }");
|
this.css.insert(".is-favorite .tweet-action, .is-retweet .tweet-action { opacity: 0.5; visibility: visible !important; }");
|
||||||
this.css.insert(".tweet:hover .tweet-action, .is-favorite .tweet-action[rel='favorite'], .is-retweet .tweet-action[rel='retweet'] { opacity: 1; visibility: visible !important; }");
|
this.css.insert(".tweet:hover .tweet-action, .is-favorite .tweet-action[rel='favorite'], .is-retweet .tweet-action[rel='retweet'] { opacity: 1; visibility: visible !important; }");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.moveTweetActionsToRight){
|
||||||
|
this.css.insert(".tweet-actions { float: right !important; width: auto !important; }");
|
||||||
this.css.insert(".tweet-actions > li:nth-child(4) { margin-right: 2px !important; }");
|
this.css.insert(".tweet-actions > li:nth-child(4) { margin-right: 2px !important; }");
|
||||||
|
}
|
||||||
|
|
||||||
// revert small links around the tweet
|
if (config.smallComposeTextSize){
|
||||||
this.prevFooterMustache = TD.mustaches["status/tweet_single_footer.mustache"];
|
this.css.insert(".compose-text { font-size: 12px !important; height: 120px !important; }");
|
||||||
|
}
|
||||||
|
|
||||||
var footerLayout = TD.mustaches["status/tweet_single_footer.mustache"];
|
if (config.revertConversationLinks){
|
||||||
footerLayout = footerLayout.replace('txt-mute txt-size--12', 'txt-mute txt-small');
|
var footer = TD.mustaches["status/tweet_single_footer.mustache"];
|
||||||
footerLayout = footerLayout.replace('{{#inReplyToID}}', '{{^inReplyToID}} <a class="pull-left margin-txs txt-mute txt-small is-vishidden-narrow" href="#" rel="viewDetails">{{_i}}Details{{/i}}</a> <a class="pull-left margin-txs txt-mute txt-small is-vishidden is-visshown-narrow" href="#" rel="viewDetails">{{_i}}Open{{/i}}</a> {{/inReplyToID}} {{#inReplyToID}}');
|
footer = footer.replace('txt-mute txt-size--12', 'txt-mute txt-small');
|
||||||
footerLayout = footerLayout.replace('<span class="link-complex-target"> {{_i}}View Conversation{{/i}}', '<i class="icon icon-conversation icon-small-context"></i> <span class="link-complex-target"> <span class="is-vishidden-wide is-vishidden-narrow">{{_i}}View{{/i}}</span> <span class="is-vishidden is-visshown-wide">{{_i}}Conversation{{/i}}</span>');
|
footer = footer.replace('{{#inReplyToID}}', '{{^inReplyToID}} <a class="pull-left margin-txs txt-mute txt-small is-vishidden-narrow" href="#" rel="viewDetails">{{_i}}Details{{/i}}</a> <a class="pull-left margin-txs txt-mute txt-small is-vishidden is-visshown-narrow" href="#" rel="viewDetails">{{_i}}Open{{/i}}</a> {{/inReplyToID}} {{#inReplyToID}}');
|
||||||
TD.mustaches["status/tweet_single_footer.mustache"] = footerLayout;
|
footer = footer.replace('<span class="link-complex-target"> {{_i}}View Conversation{{/i}}', '<i class="icon icon-conversation icon-small-context"></i> <span class="link-complex-target"> <span class="is-vishidden-wide is-vishidden-narrow">{{_i}}View{{/i}}</span> <span class="is-vishidden is-visshown-wide">{{_i}}Conversation{{/i}}</span>');
|
||||||
|
TD.mustaches["status/tweet_single_footer.mustache"] = footer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.moveTweetActionsToRight){
|
||||||
|
$(document).on("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// fix layout for right-aligned actions menu
|
// fix layout for right-aligned actions menu
|
||||||
this.uiShowActionsMenuEvent = function(){
|
this.uiShowActionsMenuEvent = function(){
|
||||||
@@ -22,13 +38,9 @@ enabled(){
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ready(){
|
|
||||||
$(document).on("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
disabled(){
|
disabled(){
|
||||||
this.css.remove();
|
this.css.remove();
|
||||||
|
TD.mustaches["status/tweet_single_footer.mustache"] = this.prevFooterMustache;
|
||||||
|
|
||||||
$(document).off("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
$(document).off("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
||||||
TD.mustaches["status/tweet_single_footer.mustache"] = this.prevFooterMustache;
|
|
||||||
}
|
}
|
22
Resources/Plugins/design-revert/configuration.default.js
Normal file
22
Resources/Plugins/design-revert/configuration.default.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
/*
|
||||||
|
* Hides the action bar below each tweet.
|
||||||
|
* The action bar fully appears when the mouse is over the tweet, or at half the opacity when the tweet is liked/retweeted.
|
||||||
|
*/
|
||||||
|
hideTweetActions: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Moves the action bar to the right side of the tweet.
|
||||||
|
*/
|
||||||
|
moveTweetActionsToRight: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reverts New Tweet font size to a smaller one.
|
||||||
|
*/
|
||||||
|
smallComposeTextSize: false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reverts design changes to the 'View Conversation' and 'Details' links.
|
||||||
|
*/
|
||||||
|
revertConversationLinks: true
|
||||||
|
}
|
18
Resources/Plugins/emoji-keyboard/.meta
Normal file
18
Resources/Plugins/emoji-keyboard/.meta
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[name]
|
||||||
|
Emoji keyboard
|
||||||
|
|
||||||
|
[description]
|
||||||
|
- Adds an emoji keyboard when writing tweets
|
||||||
|
- Emoji list provided by http://unicode.org/emoji/charts/emoji-ordering.html
|
||||||
|
|
||||||
|
[author]
|
||||||
|
chylex
|
||||||
|
|
||||||
|
[version]
|
||||||
|
1.0
|
||||||
|
|
||||||
|
[website]
|
||||||
|
https://tweetduck.chylex.com
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
1.5.3
|
178
Resources/Plugins/emoji-keyboard/browser.js
Normal file
178
Resources/Plugins/emoji-keyboard/browser.js
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
enabled(){
|
||||||
|
this.emojiHTML = "";
|
||||||
|
|
||||||
|
var me = this;
|
||||||
|
|
||||||
|
// styles
|
||||||
|
|
||||||
|
this.css = window.TDPF_createCustomStyle(this);
|
||||||
|
this.css.insert(".emoji-keyboard { position: absolute; width: 15.35em; height: 11.2em; background-color: white; overflow-y: auto; padding: 0.1em; box-sizing: border-box; border-radius: 2px; font-size: 24px; z-index: 9999 }");
|
||||||
|
this.css.insert(".emoji-keyboard .separator { height: 26px; }");
|
||||||
|
this.css.insert(".emoji-keyboard .emoji { padding: 0.1em !important; cursor: pointer }");
|
||||||
|
|
||||||
|
// layout
|
||||||
|
|
||||||
|
var buttonHTML = '<button class="needsclick btn btn-on-blue txt-left padding-v--9 emoji-keyboard-popup-btn"><i class="icon icon-heart"></i></button>';
|
||||||
|
|
||||||
|
this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
|
||||||
|
TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="cf margin-t--12 margin-b--30">', '<div class="cf margin-t--12 margin-b--30">'+buttonHTML);
|
||||||
|
|
||||||
|
var dockedComposePanel = $(".js-docked-compose");
|
||||||
|
|
||||||
|
if (dockedComposePanel.length){
|
||||||
|
dockedComposePanel.find(".cf.margin-t--12.margin-b--30").first().append(buttonHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyboard generation
|
||||||
|
|
||||||
|
this.currentKeyboard = null;
|
||||||
|
|
||||||
|
var hideKeyboard = () => {
|
||||||
|
$(this.currentKeyboard).remove();
|
||||||
|
this.currentKeyboard = null;
|
||||||
|
|
||||||
|
$(".emoji-keyboard-popup-btn").removeClass("is-selected");
|
||||||
|
};
|
||||||
|
|
||||||
|
this.generateKeyboardFor = (input, left, top) => {
|
||||||
|
var created = document.createElement("div");
|
||||||
|
document.body.appendChild(created);
|
||||||
|
|
||||||
|
created.classList.add("emoji-keyboard");
|
||||||
|
created.style.left = left+"px";
|
||||||
|
created.style.top = top+"px";
|
||||||
|
created.innerHTML = this.emojiHTML;
|
||||||
|
|
||||||
|
created.addEventListener("click", function(e){
|
||||||
|
if (e.target.tagName === "IMG"){
|
||||||
|
input.val(input.val()+e.target.getAttribute("alt"));
|
||||||
|
input.trigger("change");
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
return created;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.prevTryPasteImage = window.TDGF_tryPasteImage;
|
||||||
|
var prevTryPasteImageF = this.prevTryPasteImage;
|
||||||
|
|
||||||
|
window.TDGF_tryPasteImage = function(){
|
||||||
|
if (me.currentKeyboard){
|
||||||
|
hideKeyboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
return prevTryPasteImageF.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
// event handlers
|
||||||
|
|
||||||
|
this.emojiKeyboardButtonClickEvent = function(e){
|
||||||
|
if (me.currentKeyboard){
|
||||||
|
hideKeyboard();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
var pos = $(this).offset();
|
||||||
|
me.currentKeyboard = me.generateKeyboardFor($(".js-compose-text").first(), pos.left, pos.top+$(this).outerHeight()+8);
|
||||||
|
|
||||||
|
$(this).addClass("is-selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
$(this).blur();
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.documentClickEvent = function(e){
|
||||||
|
if (me.currentKeyboard && !e.target.classList.contains("js-compose-text")){
|
||||||
|
hideKeyboard();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.documentKeyEvent = function(e){
|
||||||
|
if (me.currentKeyboard && e.keyCode === 27){ // escape
|
||||||
|
hideKeyboard();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO
|
||||||
|
* ----
|
||||||
|
* add emoji search if I can be bothered
|
||||||
|
* lazy emoji loading
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
ready(){
|
||||||
|
$(".emoji-keyboard-popup-btn").on("click", this.emojiKeyboardButtonClickEvent);
|
||||||
|
$(document).on("click", this.documentClickEvent);
|
||||||
|
$(document).on("keydown", this.documentKeyEvent);
|
||||||
|
|
||||||
|
// HTML generation
|
||||||
|
|
||||||
|
var convUnicode = function(codePt){
|
||||||
|
if (codePt > 0xFFFF){
|
||||||
|
codePt -= 0x10000;
|
||||||
|
return String.fromCharCode(0xD800+(codePt>>10), 0xDC00+(codePt&0x3FF));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return String.fromCharCode(codePt);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$TDP.readFileRoot(this.$token, "emoji-ordering.txt").then(contents => {
|
||||||
|
let generated = [];
|
||||||
|
|
||||||
|
let addDeclaration = decl => {
|
||||||
|
generated.push(decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join(""));
|
||||||
|
};
|
||||||
|
|
||||||
|
let skinTones = [
|
||||||
|
"1F3FB", "1F3FC", "1F3FD", "1F3FE", "1F3FF"
|
||||||
|
];
|
||||||
|
|
||||||
|
for(let line of contents.split("\n")){
|
||||||
|
if (line[0] === '@'){
|
||||||
|
generated.push("___");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
let decl = line.slice(0, line.indexOf(";"));
|
||||||
|
let skinIndex = decl.indexOf('$');
|
||||||
|
|
||||||
|
if (skinIndex !== -1){
|
||||||
|
let declPre = decl.slice(0, skinIndex);
|
||||||
|
let declPost = decl.slice(skinIndex+1);
|
||||||
|
|
||||||
|
skinTones.map(skinTone => declPre+skinTone+declPost).forEach(addDeclaration);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
addDeclaration(decl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = "<p style='font-size:13px;color:#444;margin:4px;text-align:center'>Please, note that most emoji will not show up properly in the text box above, but they will display in the tweet.</p>";
|
||||||
|
this.emojiHTML = start+TD.util.cleanWithEmoji(generated.join("")).replace(/___/g, "<div class='separator'></div>");
|
||||||
|
}).catch(err => {
|
||||||
|
$TD.alert("error", "Problem loading emoji keyboard: "+err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
disabled(){
|
||||||
|
this.css.remove();
|
||||||
|
|
||||||
|
if (this.currentKeyboard){
|
||||||
|
$(this.currentKeyboard).remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.TDGF_tryPasteImage = this.prevTryPasteImage;
|
||||||
|
|
||||||
|
$(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonClickEvent);
|
||||||
|
$(".emoji-keyboard-popup-btn").remove();
|
||||||
|
|
||||||
|
$(document).off("click", this.documentClickEvent);
|
||||||
|
$(document).off("keydown", this.documentKeyEvent);
|
||||||
|
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
|
||||||
|
}
|
1671
Resources/Plugins/emoji-keyboard/emoji-ordering.txt
Normal file
1671
Resources/Plugins/emoji-keyboard/emoji-ordering.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
(function($, $TD, TD){
|
(function($, $TD, $TDX, TD){
|
||||||
//
|
//
|
||||||
// Variable: Current highlighted column jQuery object.
|
// Variable: Current highlighted column jQuery object.
|
||||||
//
|
//
|
||||||
@@ -10,71 +10,14 @@
|
|||||||
var highlightedTweetEle;
|
var highlightedTweetEle;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Function: Initializes TweetD*ck events. Called after the website app is loaded.
|
// Variable: Array of functions called after the website app is loaded.
|
||||||
//
|
//
|
||||||
var initializeTweetDck = function(){
|
var onAppReady = [];
|
||||||
// Settings button hook
|
|
||||||
$("[data-action='settings-menu']").click(function(){
|
|
||||||
setTimeout(function(){
|
|
||||||
var menu = $(".js-dropdown-content").children("ul").first();
|
|
||||||
if (menu.length === 0)return;
|
|
||||||
|
|
||||||
menu.children(".drp-h-divider").last().after([
|
//
|
||||||
'<li class="is-selectable" data-std><a href="#" data-action="td-settings">'+$TD.brandName+' settings</a></li>',
|
// Variable: DOM object containing the main app element.
|
||||||
'<li class="is-selectable" data-std><a href="#" data-action="td-plugins">'+$TD.brandName+' plugins</a></li>',
|
//
|
||||||
'<li class="drp-h-divider"></li>'
|
var app = $(document.body).children(".js-app");
|
||||||
].join(""));
|
|
||||||
|
|
||||||
var buttons = menu.children("[data-std]");
|
|
||||||
|
|
||||||
buttons.on("click", "a", function(){
|
|
||||||
var action = $(this).attr("data-action");
|
|
||||||
|
|
||||||
if (action === "td-settings"){
|
|
||||||
$TD.openSettingsMenu();
|
|
||||||
}
|
|
||||||
else if (action === "td-plugins"){
|
|
||||||
$TD.openPluginsMenu();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
buttons.hover(function(){
|
|
||||||
$(this).addClass("is-selected");
|
|
||||||
}, function(){
|
|
||||||
$(this).removeClass("is-selected");
|
|
||||||
});
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Notification handling
|
|
||||||
$.subscribe("/notifications/new", function(obj){
|
|
||||||
for(let index = obj.items.length-1; index >= 0; index--){
|
|
||||||
onNewTweet(obj.column, obj.items[index]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup video element replacement and fix missing target in user links
|
|
||||||
new MutationObserver(function(){
|
|
||||||
$("video").each(function(){
|
|
||||||
$(this).parent().replaceWith("<a href='"+$(this).attr("src")+"' rel='url' target='_blank' style='display:block; border:1px solid #555; padding:3px 6px'>► Open video in browser</a>");
|
|
||||||
});
|
|
||||||
|
|
||||||
$("a[rel='user']").attr("target", "_blank");
|
|
||||||
}).observe($(".js-app-columns")[0], {
|
|
||||||
childList: true,
|
|
||||||
subtree: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Finish init and load plugins
|
|
||||||
$TD.loadFontSizeClass(TD.settings.getFontSize());
|
|
||||||
$TD.loadNotificationHeadContents(getNotificationHeadContents());
|
|
||||||
|
|
||||||
window.TD_APP_READY = true;
|
|
||||||
|
|
||||||
if (window.TD_PLUGINS){
|
|
||||||
window.TD_PLUGINS.onReady();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Function: Prepends code at the beginning of a function. If the prepended function returns true, execution of the original function is cancelled.
|
// Function: Prepends code at the beginning of a function. If the prepended function returns true, execution of the original function is cancelled.
|
||||||
@@ -112,6 +55,7 @@
|
|||||||
|
|
||||||
html.css("border", "0");
|
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");
|
||||||
|
|
||||||
var url = html.find("time").first().children("a").first().attr("href") || "";
|
var url = html.find("time").first().children("a").first().attr("href") || "";
|
||||||
|
|
||||||
@@ -136,23 +80,6 @@
|
|||||||
return tags.join("");
|
return tags.join("");
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
|
||||||
// Block: Observe the app <div> element and initialize TweetD*ck whenever possible.
|
|
||||||
//
|
|
||||||
var app = $("body").children(".js-app");
|
|
||||||
|
|
||||||
new MutationObserver(function(){
|
|
||||||
if (window.TD_APP_READY && app.hasClass("is-hidden")){
|
|
||||||
window.TD_APP_READY = false;
|
|
||||||
}
|
|
||||||
else if (!window.TD_APP_READY && !app.hasClass("is-hidden")){
|
|
||||||
initializeTweetDck();
|
|
||||||
}
|
|
||||||
}).observe(app[0], {
|
|
||||||
attributes: true,
|
|
||||||
attributeFilter: [ "class" ]
|
|
||||||
});
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Hook into settings object to detect when the settings change.
|
// Block: Hook into settings object to detect when the settings change.
|
||||||
//
|
//
|
||||||
@@ -167,7 +94,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Force popup notification settings.
|
// Block: Enable popup notifications.
|
||||||
//
|
//
|
||||||
TD.controller.notifications.hasNotifications = function(){
|
TD.controller.notifications.hasNotifications = function(){
|
||||||
return true;
|
return true;
|
||||||
@@ -177,6 +104,49 @@
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$.subscribe("/notifications/new", function(obj){
|
||||||
|
for(let index = obj.items.length-1; index >= 0; index--){
|
||||||
|
onNewTweet(obj.column, obj.items[index]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Block: Add TweetDuck buttons to the settings menu.
|
||||||
|
//
|
||||||
|
onAppReady.push(function(){
|
||||||
|
$("[data-action='settings-menu']").click(function(){
|
||||||
|
setTimeout(function(){
|
||||||
|
var menu = $(".js-dropdown-content").children("ul").first();
|
||||||
|
if (menu.length === 0)return;
|
||||||
|
|
||||||
|
menu.children(".drp-h-divider").last().after([
|
||||||
|
'<li class="is-selectable" data-std><a href="#" data-action="td-settings">TweetDuck settings</a></li>',
|
||||||
|
'<li class="is-selectable" data-std><a href="#" data-action="td-plugins">TweetDuck plugins</a></li>',
|
||||||
|
'<li class="drp-h-divider"></li>'
|
||||||
|
].join(""));
|
||||||
|
|
||||||
|
var buttons = menu.children("[data-std]");
|
||||||
|
|
||||||
|
buttons.on("click", "a", function(){
|
||||||
|
var action = $(this).attr("data-action");
|
||||||
|
|
||||||
|
if (action === "td-settings"){
|
||||||
|
$TD.openSettingsMenu();
|
||||||
|
}
|
||||||
|
else if (action === "td-plugins"){
|
||||||
|
$TD.openPluginsMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
buttons.hover(function(){
|
||||||
|
$(this).addClass("is-selected");
|
||||||
|
}, function(){
|
||||||
|
$(this).removeClass("is-selected");
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Expand shortened links on hover or display tooltip.
|
// Block: Expand shortened links on hover or display tooltip.
|
||||||
//
|
//
|
||||||
@@ -198,7 +168,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($TD.expandLinksOnHover){
|
if ($TDX.expandLinksOnHover){
|
||||||
tooltipTimer = window.setTimeout(function(){
|
tooltipTimer = window.setTimeout(function(){
|
||||||
var expanded = me.attr("data-full-url");
|
var expanded = me.attr("data-full-url");
|
||||||
expanded = cutStart(expanded, "https://");
|
expanded = cutStart(expanded, "https://");
|
||||||
@@ -217,7 +187,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (e.type === "mouseleave"){
|
else if (e.type === "mouseleave"){
|
||||||
if ($TD.expandLinksOnHover){
|
if ($TDX.expandLinksOnHover){
|
||||||
var prevText = me.attr("td-prev-text");
|
var prevText = me.attr("td-prev-text");
|
||||||
|
|
||||||
if (prevText){
|
if (prevText){
|
||||||
@@ -256,7 +226,7 @@
|
|||||||
var soundEle = document.getElementById("update-sound");
|
var soundEle = document.getElementById("update-sound");
|
||||||
|
|
||||||
soundEle.play = prependToFunction(soundEle.play, function(){
|
soundEle.play = prependToFunction(soundEle.play, function(){
|
||||||
return $TD.muteNotifications || $TD.hasCustomNotificationSound;
|
return $TDX.muteNotifications || $TDX.hasCustomNotificationSound;
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -386,7 +356,7 @@
|
|||||||
$TD.clickUploadImage(Math.floor(buttonPos.left), Math.floor(buttonPos.top));
|
$TD.clickUploadImage(Math.floor(buttonPos.left), Math.floor(buttonPos.top));
|
||||||
};
|
};
|
||||||
|
|
||||||
$(".js-app").delegate(".js-compose-text,.js-reply-tweetbox", "paste", function(){
|
app.delegate(".js-compose-text,.js-reply-tweetbox", "paste", function(){
|
||||||
lastPasteElement = $(this);
|
lastPasteElement = $(this);
|
||||||
$TD.tryPasteImage();
|
$TD.tryPasteImage();
|
||||||
});
|
});
|
||||||
@@ -524,6 +494,25 @@
|
|||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Block: Swap shift key functionality for selecting accounts.
|
||||||
|
//
|
||||||
|
onAppReady.push(function(){
|
||||||
|
$(".js-drawer[data-drawer='compose']").delegate(".js-account-list > .js-account-item", "click", function(e){
|
||||||
|
e.shiftKey = !e.shiftKey;
|
||||||
|
});
|
||||||
|
|
||||||
|
TD.components.AccountSelector.prototype.refreshPostingAccounts = appendToFunction(TD.components.AccountSelector.prototype.refreshPostingAccounts, function(){
|
||||||
|
if (!this.$node.attr("td-account-selector-hook")){
|
||||||
|
this.$node.attr("td-account-selector-hook", "1");
|
||||||
|
|
||||||
|
this.$node.delegate(".js-account-item", "click", function(e){
|
||||||
|
e.shiftKey = !e.shiftKey;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Work around clipboard HTML formatting.
|
// Block: Work around clipboard HTML formatting.
|
||||||
//
|
//
|
||||||
@@ -543,12 +532,79 @@
|
|||||||
styleOfficial.sheet.insertRule(".txt-base-smallest .badge-verified:before { height: 13px !important; }", 0); // fix cut off badge icon
|
styleOfficial.sheet.insertRule(".txt-base-smallest .badge-verified:before { height: 13px !important; }", 0); // fix cut off badge icon
|
||||||
styleOfficial.sheet.insertRule(".keyboard-shortcut-list { vertical-align: top; }", 0); // fix keyboard navigation alignment
|
styleOfficial.sheet.insertRule(".keyboard-shortcut-list { vertical-align: top; }", 0); // fix keyboard navigation alignment
|
||||||
|
|
||||||
if ($TD.hasCustomBrowserCSS){
|
styleOfficial.sheet.insertRule(".is-video a:not([href*='youtu']), .is-gif .js-media-gif-container { cursor: alias; }", 0); // change cursor on unsupported videos
|
||||||
var styleCustom = document.createElement("style");
|
styleOfficial.sheet.insertRule(".is-video a:not([href*='youtu']) .icon-bg-dot, .is-gif .icon-bg-dot { color: #bd3d37; }", 0); // change play icon color on unsupported videos
|
||||||
styleCustom.innerHTML = $TD.customBrowserCSS;
|
|
||||||
document.head.appendChild(styleCustom);
|
|
||||||
}
|
|
||||||
|
|
||||||
TD.services.TwitterActionRetweetedRetweet.prototype.iconClass = "icon-retweet icon-retweet-color txt-base-medium"; // fix retweet icon mismatch
|
TD.services.TwitterActionRetweetedRetweet.prototype.iconClass = "icon-retweet icon-retweet-color txt-base-medium"; // fix retweet icon mismatch
|
||||||
|
|
||||||
|
window.TDGF_reinjectCustomCSS = function(styles){
|
||||||
|
$("#tweetduck-custom-css").remove();
|
||||||
|
|
||||||
|
if (styles && styles.length){
|
||||||
|
$(document.head).append("<style type='text/css' id='tweetduck-custom-css'>"+styles+"</style>");
|
||||||
|
}
|
||||||
|
};
|
||||||
})();
|
})();
|
||||||
})($, $TD, TD);
|
|
||||||
|
//
|
||||||
|
// Block: Setup unsupported video element hook.
|
||||||
|
//
|
||||||
|
(function(){
|
||||||
|
var cancelModal = false;
|
||||||
|
|
||||||
|
TD.components.MediaGallery.prototype._loadTweet = appendToFunction(TD.components.MediaGallery.prototype._loadTweet, function(){
|
||||||
|
var media = this.chirp.getMedia().find(media => media.mediaId === this.clickedMediaEntityId);
|
||||||
|
|
||||||
|
if (media && media.isVideo && media.service !== "youtube"){
|
||||||
|
$TD.openBrowser(this.clickedLink);
|
||||||
|
cancelModal = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
TD.components.BaseModal.prototype.setAndShowContainer = prependToFunction(TD.components.BaseModal.prototype.setAndShowContainer, function(){
|
||||||
|
if (cancelModal){
|
||||||
|
cancelModal = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
TD.ui.Column.prototype.playGifIfNotManuallyPaused = function(){};
|
||||||
|
TD.mustaches["status/media_thumb.mustache"] = TD.mustaches["status/media_thumb.mustache"].replace("is-gif", "is-gif is-paused");
|
||||||
|
|
||||||
|
app.delegate(".js-gif-play", "click", function(e){
|
||||||
|
var parent = $(e.target).closest(".js-tweet").first();
|
||||||
|
var link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
|
||||||
|
|
||||||
|
$TD.openBrowser(link.attr("href"));
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Block: Finish initialization and load plugins.
|
||||||
|
//
|
||||||
|
onAppReady.push(function(){
|
||||||
|
$TD.loadFontSizeClass(TD.settings.getFontSize());
|
||||||
|
$TD.loadNotificationHeadContents(getNotificationHeadContents());
|
||||||
|
|
||||||
|
if (window.TD_PLUGINS){
|
||||||
|
window.TD_PLUGINS.onReady();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Block: Observe the main app element and call the ready event whenever the contents are loaded.
|
||||||
|
//
|
||||||
|
new MutationObserver(function(){
|
||||||
|
if (window.TD_APP_READY && app.hasClass("is-hidden")){
|
||||||
|
window.TD_APP_READY = false;
|
||||||
|
}
|
||||||
|
else if (!window.TD_APP_READY && !app.hasClass("is-hidden")){
|
||||||
|
onAppReady.forEach(func => func());
|
||||||
|
window.TD_APP_READY = true;
|
||||||
|
}
|
||||||
|
}).observe(app[0], {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: [ "class" ]
|
||||||
|
});
|
||||||
|
})($, $TD, $TDX, TD);
|
||||||
|
@@ -1,11 +1,27 @@
|
|||||||
(function($, $TD, TD){
|
(function($, $TD, $TDX, TD){
|
||||||
|
var isDebugging = false;
|
||||||
|
|
||||||
$(document).keydown(function(e){
|
$(document).keydown(function(e){
|
||||||
|
|
||||||
// ==============================
|
// ==========================
|
||||||
// F4 key - simulate notification
|
// F4 key - toggle debug mode
|
||||||
// ==============================
|
// ==========================
|
||||||
|
|
||||||
if (e.keyCode === 115){
|
if (e.keyCode === 115){
|
||||||
|
isDebugging = !isDebugging;
|
||||||
|
$(".app-title").first().css("background-color", isDebugging ? "#5A6B75" : "#292F33");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug mode handling
|
||||||
|
|
||||||
|
else if (isDebugging){
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// N key - simulate popup notification
|
||||||
|
// ===================================
|
||||||
|
|
||||||
|
if (e.keyCode === 78){
|
||||||
var col = TD.controller.columnManager.getAllOrdered()[0];
|
var col = TD.controller.columnManager.getAllOrdered()[0];
|
||||||
|
|
||||||
$.publish("/notifications/new",[{
|
$.publish("/notifications/new",[{
|
||||||
@@ -15,5 +31,19 @@
|
|||||||
]
|
]
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// S key - simulate sound notification
|
||||||
|
// ===================================
|
||||||
|
|
||||||
|
else if (e.keyCode === 83){
|
||||||
|
if ($TDX.hasCustomNotificationSound){
|
||||||
|
$TD.onTweetSound();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
document.getElementById("update-sound").play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})($, $TD, TD);
|
})($, $TD, $TDX, TD);
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
(function($TD){
|
(function($TD, $TDX){
|
||||||
//
|
//
|
||||||
// Variable: Collection of all <a> tags.
|
// Variable: Collection of all <a> tags.
|
||||||
//
|
//
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($TD.expandLinksOnHover){
|
if ($TDX.expandLinksOnHover){
|
||||||
tooltipTimer = window.setTimeout(function(){
|
tooltipTimer = window.setTimeout(function(){
|
||||||
var expanded = url;
|
var expanded = url;
|
||||||
expanded = cutStart(expanded, "https://");
|
expanded = cutStart(expanded, "https://");
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
addEventListener(links, "mouseleave", function(e){
|
addEventListener(links, "mouseleave", function(e){
|
||||||
if (!e.currentTarget.hasAttribute("data-full-url"))return;
|
if (!e.currentTarget.hasAttribute("data-full-url"))return;
|
||||||
|
|
||||||
if ($TD.expandLinksOnHover){
|
if ($TDX.expandLinksOnHover){
|
||||||
var prevText = e.currentTarget.getAttribute("td-prev-text");
|
var prevText = e.currentTarget.getAttribute("td-prev-text");
|
||||||
|
|
||||||
if (prevText){
|
if (prevText){
|
||||||
@@ -146,4 +146,4 @@
|
|||||||
document.body.addEventListener("mouseleave", function(){
|
document.body.addEventListener("mouseleave", function(){
|
||||||
document.body.classList.remove("td-hover");
|
document.body.classList.remove("td-hover");
|
||||||
});
|
});
|
||||||
})($TD);
|
})($TD, $TDX);
|
||||||
|
@@ -7,25 +7,25 @@
|
|||||||
//
|
//
|
||||||
// Constant: Update exe file name.
|
// Constant: Update exe file name.
|
||||||
//
|
//
|
||||||
const updateFileName = $TDU.brandName+".Update.exe";
|
const updateFileName = "TweetDuck.Update.exe";
|
||||||
|
|
||||||
//
|
//
|
||||||
// Constant: Url that returns JSON data about latest version.
|
// Constant: Url that returns JSON data about latest version.
|
||||||
//
|
//
|
||||||
const updateCheckUrlLatest = "https://api.github.com/repos/chylex/"+$TDU.brandName+"/releases/latest";
|
const updateCheckUrlLatest = "https://api.github.com/repos/chylex/TweetDuck/releases/latest";
|
||||||
|
|
||||||
//
|
//
|
||||||
// Constant: Url that returns JSON data about all versions, including prereleases.
|
// Constant: Url that returns JSON data about all versions, including prereleases.
|
||||||
//
|
//
|
||||||
const updateCheckUrlAll = "https://api.github.com/repos/chylex/"+$TDU.brandName+"/releases";
|
const updateCheckUrlAll = "https://api.github.com/repos/chylex/TweetDuck/releases";
|
||||||
|
|
||||||
//
|
//
|
||||||
// Function: Creates the update notification element. Removes the old one if already exists.
|
// Function: Creates the update notification element. Removes the old one if already exists.
|
||||||
//
|
//
|
||||||
var createUpdateNotificationElement = function(version, download){
|
var displayNotification = function(version, download){
|
||||||
var outdated = version === "unsupported";
|
var outdated = version === "unsupported";
|
||||||
|
|
||||||
var ele = $("#tweetdck-update");
|
var ele = $("#tweetduck-update");
|
||||||
var existed = ele.length > 0;
|
var existed = ele.length > 0;
|
||||||
|
|
||||||
if (existed > 0){
|
if (existed > 0){
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var html = outdated ? [
|
var html = outdated ? [
|
||||||
"<div id='tweetdck-update'>",
|
"<div id='tweetduck-update'>",
|
||||||
"<p class='tdu-title'>Unsupported System</p>",
|
"<p class='tdu-title'>Unsupported System</p>",
|
||||||
"<p class='tdu-info'>You will not receive updates.</p>",
|
"<p class='tdu-info'>You will not receive updates.</p>",
|
||||||
"<div class='tdu-buttons'>",
|
"<div class='tdu-buttons'>",
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
"</div>",
|
"</div>",
|
||||||
"</div>"
|
"</div>"
|
||||||
] : [
|
] : [
|
||||||
"<div id='tweetdck-update'>",
|
"<div id='tweetduck-update'>",
|
||||||
"<p class='tdu-title'>"+$TDU.brandName+" Update</p>",
|
"<p class='tdu-title'>TweetDuck Update</p>",
|
||||||
"<p class='tdu-info'>Version "+version+" is now available.</p>",
|
"<p class='tdu-info'>Version "+version+" is now available.</p>",
|
||||||
"<div class='tdu-buttons'>",
|
"<div class='tdu-buttons'>",
|
||||||
"<button class='btn btn-positive tdu-btn-download'><span class='label'>Download</span></button>",
|
"<button class='btn btn-positive tdu-btn-download'><span class='label'>Download</span></button>",
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
|
|
||||||
$(document.body).append(html.join(""));
|
$(document.body).append(html.join(""));
|
||||||
|
|
||||||
ele = $("#tweetdck-update");
|
ele = $("#tweetduck-update");
|
||||||
|
|
||||||
var buttonDiv = ele.children("div.tdu-buttons").first();
|
var buttonDiv = ele.children("div.tdu-buttons").first();
|
||||||
|
|
||||||
@@ -128,33 +128,19 @@
|
|||||||
//
|
//
|
||||||
// Function: Runs an update check and updates all DOM elements appropriately.
|
// Function: Runs an update check and updates all DOM elements appropriately.
|
||||||
//
|
//
|
||||||
var runUpdateCheck = function(force, eventID){
|
var runUpdateCheck = function(eventID, versionTag, dismissedVersionTag, allowPre){
|
||||||
if (!$TDU.isSystemSupported){
|
|
||||||
if ($TDU.dismissedVersionTag !== "unsupported"){
|
|
||||||
createUpdateNotificationElement("unsupported");
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(updateCheckTimeoutID);
|
clearTimeout(updateCheckTimeoutID);
|
||||||
updateCheckTimeoutID = setTimeout(runUpdateCheck, 1000*60*60); // 1 hour
|
updateCheckTimeoutID = setTimeout($TDU.triggerUpdateCheck, 1000*60*60); // 1 hour
|
||||||
|
|
||||||
if (!$TDU.updateCheckEnabled && !force){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var allowPre = $TDU.allowPreReleases;
|
|
||||||
|
|
||||||
$.getJSON(allowPre ? updateCheckUrlAll : updateCheckUrlLatest, function(response){
|
$.getJSON(allowPre ? updateCheckUrlAll : updateCheckUrlLatest, function(response){
|
||||||
var release = allowPre ? response[0] : response;
|
var release = allowPre ? response[0] : response;
|
||||||
|
|
||||||
var tagName = release.tag_name;
|
var tagName = release.tag_name;
|
||||||
var hasUpdate = tagName !== $TDU.versionTag && tagName !== $TDU.dismissedVersionTag && release.assets.length > 0;
|
var hasUpdate = tagName !== versionTag && tagName !== dismissedVersionTag && release.assets.length > 0;
|
||||||
|
|
||||||
if (hasUpdate){
|
if (hasUpdate){
|
||||||
var obj = release.assets.find(asset => asset.name === updateFileName) || release.assets[0];
|
var obj = release.assets.find(asset => asset.name === updateFileName) || release.assets[0];
|
||||||
createUpdateNotificationElement(tagName, obj.browser_download_url);
|
displayNotification(tagName, obj.browser_download_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventID){ // ignore undefined and 0
|
if (eventID){ // ignore undefined and 0
|
||||||
@@ -166,6 +152,6 @@
|
|||||||
//
|
//
|
||||||
// Block: Setup global functions.
|
// Block: Setup global functions.
|
||||||
//
|
//
|
||||||
|
window.TDUF_displayNotification = displayNotification;
|
||||||
window.TDUF_runUpdateCheck = runUpdateCheck;
|
window.TDUF_runUpdateCheck = runUpdateCheck;
|
||||||
runUpdateCheck();
|
|
||||||
})($, $TDU);
|
})($, $TDU);
|
||||||
|
@@ -71,6 +71,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Configuration\LockManager.cs" />
|
<Compile Include="Configuration\LockManager.cs" />
|
||||||
<Compile Include="Configuration\UserConfig.cs" />
|
<Compile Include="Configuration\UserConfig.cs" />
|
||||||
|
<Compile Include="Core\Bridge\PropertyBridge.cs" />
|
||||||
<Compile Include="Core\Controls\ControlExtensions.cs" />
|
<Compile Include="Core\Controls\ControlExtensions.cs" />
|
||||||
<Compile Include="Core\Controls\FlatButton.cs">
|
<Compile Include="Core\Controls\FlatButton.cs">
|
||||||
<SubType>Component</SubType>
|
<SubType>Component</SubType>
|
||||||
@@ -112,6 +113,7 @@
|
|||||||
<Compile Include="Core\Handling\FileDialogHandler.cs" />
|
<Compile Include="Core\Handling\FileDialogHandler.cs" />
|
||||||
<Compile Include="Core\Handling\JavaScriptDialogHandler.cs" />
|
<Compile Include="Core\Handling\JavaScriptDialogHandler.cs" />
|
||||||
<Compile Include="Core\Handling\LifeSpanHandler.cs" />
|
<Compile Include="Core\Handling\LifeSpanHandler.cs" />
|
||||||
|
<Compile Include="Core\Notification\SoundNotification.cs" />
|
||||||
<Compile Include="Core\Notification\TweetNotification.cs" />
|
<Compile Include="Core\Notification\TweetNotification.cs" />
|
||||||
<Compile Include="Core\Other\FormAbout.cs">
|
<Compile Include="Core\Other\FormAbout.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
@@ -190,6 +192,7 @@
|
|||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Core\Notification\NotificationFlags.cs" />
|
<Compile Include="Core\Notification\NotificationFlags.cs" />
|
||||||
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
|
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
|
||||||
|
<Compile Include="Core\Utils\TwoKeyDictionary.cs" />
|
||||||
<Compile Include="Core\Utils\WindowState.cs" />
|
<Compile Include="Core\Utils\WindowState.cs" />
|
||||||
<Compile Include="Core\Utils\WindowsUtils.cs" />
|
<Compile Include="Core\Utils\WindowsUtils.cs" />
|
||||||
<Compile Include="Core\Bridge\TweetDeckBridge.cs" />
|
<Compile Include="Core\Bridge\TweetDeckBridge.cs" />
|
||||||
@@ -222,6 +225,7 @@
|
|||||||
<Compile Include="Plugins\PluginManager.cs" />
|
<Compile Include="Plugins\PluginManager.cs" />
|
||||||
<Compile Include="Plugins\PluginScriptGenerator.cs" />
|
<Compile Include="Plugins\PluginScriptGenerator.cs" />
|
||||||
<Compile Include="Reporter.cs" />
|
<Compile Include="Reporter.cs" />
|
||||||
|
<Compile Include="Updates\Events\UpdateDismissedEventArgs.cs" />
|
||||||
<Compile Include="Updates\FormUpdateDownload.cs">
|
<Compile Include="Updates\FormUpdateDownload.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -238,8 +242,8 @@
|
|||||||
<Compile Include="Core\Utils\BrowserUtils.cs" />
|
<Compile Include="Core\Utils\BrowserUtils.cs" />
|
||||||
<Compile Include="Core\Utils\HardwareAcceleration.cs" />
|
<Compile Include="Core\Utils\HardwareAcceleration.cs" />
|
||||||
<Compile Include="Core\Utils\NativeMethods.cs" />
|
<Compile Include="Core\Utils\NativeMethods.cs" />
|
||||||
<Compile Include="Updates\UpdateAcceptedEventArgs.cs" />
|
<Compile Include="Updates\Events\UpdateAcceptedEventArgs.cs" />
|
||||||
<Compile Include="Updates\UpdateCheckEventArgs.cs" />
|
<Compile Include="Updates\Events\UpdateCheckEventArgs.cs" />
|
||||||
<Compile Include="Updates\UpdateHandler.cs" />
|
<Compile Include="Updates\UpdateHandler.cs" />
|
||||||
<Compile Include="Updates\UpdateInfo.cs" />
|
<Compile Include="Updates\UpdateInfo.cs" />
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
@@ -349,7 +353,15 @@ mkdir "$(TargetDir)plugins\official"
|
|||||||
mkdir "$(TargetDir)plugins\user"
|
mkdir "$(TargetDir)plugins\user"
|
||||||
xcopy "$(ProjectDir)Resources\Plugins\*" "$(TargetDir)plugins\official\" /E /Y
|
xcopy "$(ProjectDir)Resources\Plugins\*" "$(TargetDir)plugins\official\" /E /Y
|
||||||
rmdir "$(ProjectDir)\bin\Debug"
|
rmdir "$(ProjectDir)\bin\Debug"
|
||||||
rmdir "$(ProjectDir)\bin\Release"</PostBuildEvent>
|
rmdir "$(ProjectDir)\bin\Release"
|
||||||
|
|
||||||
|
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
|
||||||
|
|
||||||
|
if $(ConfigurationName) == Debug (
|
||||||
|
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
|
||||||
|
mkdir "$(TargetDir)plugins\user\.debug"
|
||||||
|
xcopy "$(ProjectDir)Resources\Plugins\.debug\*" "$(TargetDir)plugins\user\.debug\" /E /Y
|
||||||
|
)</PostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="packages\cef.redist.x86.3.2785.1486\build\cef.redist.x86.targets" Condition="Exists('packages\cef.redist.x86.3.2785.1486\build\cef.redist.x86.targets')" />
|
<Import Project="packages\cef.redist.x86.3.2785.1486\build\cef.redist.x86.targets" Condition="Exists('packages\cef.redist.x86.3.2785.1486\build\cef.redist.x86.targets')" />
|
||||||
<Import Project="packages\cef.redist.x64.3.2785.1486\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.2785.1486\build\cef.redist.x64.targets')" />
|
<Import Project="packages\cef.redist.x64.3.2785.1486\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.2785.1486\build\cef.redist.x64.targets')" />
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace TweetDck.Updates{
|
namespace TweetDck.Updates.Events{
|
||||||
class UpdateAcceptedEventArgs : EventArgs{
|
class UpdateAcceptedEventArgs : EventArgs{
|
||||||
public readonly UpdateInfo UpdateInfo;
|
public readonly UpdateInfo UpdateInfo;
|
||||||
|
|
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace TweetDck.Updates{
|
namespace TweetDck.Updates.Events{
|
||||||
class UpdateCheckEventArgs : EventArgs{
|
class UpdateCheckEventArgs : EventArgs{
|
||||||
public int EventId { get; private set; }
|
public int EventId { get; private set; }
|
||||||
public bool UpdateAvailable { get; private set; }
|
public bool UpdateAvailable { get; private set; }
|
11
Updates/Events/UpdateDismissedEventArgs.cs
Normal file
11
Updates/Events/UpdateDismissedEventArgs.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace TweetDck.Updates.Events{
|
||||||
|
class UpdateDismissedEventArgs : EventArgs{
|
||||||
|
public readonly string VersionTag;
|
||||||
|
|
||||||
|
public UpdateDismissedEventArgs(string versionTag){
|
||||||
|
this.VersionTag = versionTag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -9,7 +9,7 @@ using TweetDck.Core.Utils;
|
|||||||
|
|
||||||
namespace TweetDck.Updates{
|
namespace TweetDck.Updates{
|
||||||
sealed partial class FormUpdateDownload : Form{
|
sealed partial class FormUpdateDownload : Form{
|
||||||
private const double BytesToMB = 1024.0*1024.0;
|
private const double BytesToKB = 1024.0;
|
||||||
|
|
||||||
public string InstallerPath{
|
public string InstallerPath{
|
||||||
get{
|
get{
|
||||||
@@ -69,7 +69,7 @@ namespace TweetDck.Updates{
|
|||||||
progressDownload.SetValueInstant(1000);
|
progressDownload.SetValueInstant(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
labelStatus.Text = (e.BytesReceived/BytesToMB).ToString("0.0", CultureInfo.CurrentCulture)+" MB";
|
labelStatus.Text = (e.BytesReceived/BytesToKB).ToString("0.0", CultureInfo.CurrentCulture)+" kB";
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if (progressDownload.Style != ProgressBarStyle.Continuous){
|
if (progressDownload.Style != ProgressBarStyle.Continuous){
|
||||||
@@ -77,7 +77,7 @@ namespace TweetDck.Updates{
|
|||||||
}
|
}
|
||||||
|
|
||||||
progressDownload.SetValueInstant(e.ProgressPercentage*10);
|
progressDownload.SetValueInstant(e.ProgressPercentage*10);
|
||||||
labelStatus.Text = (e.BytesReceived/BytesToMB).ToString("0.0", CultureInfo.CurrentCulture)+" / "+(e.TotalBytesToReceive/BytesToMB).ToString("0.0", CultureInfo.CurrentCulture)+" MB";
|
labelStatus.Text = (e.BytesReceived/BytesToKB).ToString("0.0", CultureInfo.CurrentCulture)+" / "+(e.TotalBytesToReceive/BytesToKB).ToString("0.0", CultureInfo.CurrentCulture)+" kB";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -5,14 +5,28 @@ using TweetDck.Core;
|
|||||||
using TweetDck.Core.Controls;
|
using TweetDck.Core.Controls;
|
||||||
using TweetDck.Core.Utils;
|
using TweetDck.Core.Utils;
|
||||||
using TweetDck.Resources;
|
using TweetDck.Resources;
|
||||||
|
using TweetDck.Updates.Events;
|
||||||
|
|
||||||
namespace TweetDck.Updates{
|
namespace TweetDck.Updates{
|
||||||
class UpdateHandler{
|
class UpdateHandler{
|
||||||
|
private static bool IsSystemSupported{
|
||||||
|
get{
|
||||||
|
return true; // Environment.OSVersion.Version >= new Version("6.1"); // 6.1 NT version = Windows 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdaterSettings Settings{
|
||||||
|
get{
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly ChromiumWebBrowser browser;
|
private readonly ChromiumWebBrowser browser;
|
||||||
private readonly FormBrowser form;
|
private readonly FormBrowser form;
|
||||||
private readonly UpdaterSettings settings;
|
private readonly UpdaterSettings settings;
|
||||||
|
|
||||||
public event EventHandler<UpdateAcceptedEventArgs> UpdateAccepted;
|
public event EventHandler<UpdateAcceptedEventArgs> UpdateAccepted;
|
||||||
|
public event EventHandler<UpdateDismissedEventArgs> UpdateDismissed;
|
||||||
public event EventHandler<UpdateCheckEventArgs> CheckFinished;
|
public event EventHandler<UpdateCheckEventArgs> CheckFinished;
|
||||||
|
|
||||||
private int lastEventId;
|
private int lastEventId;
|
||||||
@@ -23,26 +37,50 @@ namespace TweetDck.Updates{
|
|||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
|
||||||
browser.FrameLoadEnd += browser_FrameLoadEnd;
|
browser.FrameLoadEnd += browser_FrameLoadEnd;
|
||||||
browser.RegisterJsObject("$TDU", new Bridge(this));
|
browser.RegisterAsyncJsObject("$TDU", new Bridge(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
|
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
|
||||||
ScriptLoader.ExecuteFile(e.Frame, "update.js");
|
ScriptLoader.ExecuteFile(e.Frame, "update.js");
|
||||||
|
Check(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Check(bool force){
|
public int Check(bool force){
|
||||||
browser.ExecuteScriptAsync("TDUF_runUpdateCheck", force, ++lastEventId);
|
if (IsSystemSupported){
|
||||||
|
if (Program.UserConfig.EnableUpdateCheck || force){
|
||||||
|
string dismissedUpdate = force || settings.DismissedUpdate == null ? string.Empty : settings.DismissedUpdate;
|
||||||
|
|
||||||
|
browser.ExecuteScriptAsync("TDUF_runUpdateCheck", ++lastEventId, Program.VersionTag, dismissedUpdate, settings.AllowPreReleases);
|
||||||
return lastEventId;
|
return lastEventId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if (settings.DismissedUpdate != "unsupported"){
|
||||||
|
browser.ExecuteScriptAsync("TDUF_displayNotification", "unsupported");
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
private void TriggerUpdateAcceptedEvent(UpdateAcceptedEventArgs args){
|
private void TriggerUpdateAcceptedEvent(UpdateAcceptedEventArgs args){
|
||||||
if (UpdateAccepted != null){
|
if (UpdateAccepted != null){
|
||||||
form.InvokeSafe(() => UpdateAccepted(this, args));
|
form.InvokeSafe(() => UpdateAccepted(this, args));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TriggerUpdateDismissedEvent(UpdateDismissedEventArgs args){
|
||||||
|
form.InvokeSafe(() => {
|
||||||
|
settings.DismissedUpdate = args.VersionTag;
|
||||||
|
|
||||||
|
if (UpdateDismissed != null){
|
||||||
|
UpdateDismissed(this, args);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void TriggerCheckFinishedEvent(UpdateCheckEventArgs args){
|
private void TriggerCheckFinishedEvent(UpdateCheckEventArgs args){
|
||||||
if (CheckFinished != null){
|
if (CheckFinished != null){
|
||||||
form.InvokeSafe(() => CheckFinished(this, args));
|
form.InvokeSafe(() => CheckFinished(this, args));
|
||||||
@@ -50,48 +88,16 @@ namespace TweetDck.Updates{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class Bridge{
|
public class Bridge{
|
||||||
public string BrandName{
|
|
||||||
get{
|
|
||||||
return Program.BrandName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string VersionTag{
|
|
||||||
get{
|
|
||||||
return Program.VersionTag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool UpdateCheckEnabled{
|
|
||||||
get{
|
|
||||||
return Program.UserConfig.EnableUpdateCheck;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string DismissedVersionTag{
|
|
||||||
get{
|
|
||||||
return Program.UserConfig.DismissedUpdate ?? string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AllowPreReleases{
|
|
||||||
get{
|
|
||||||
return owner.settings.AllowPreReleases;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsSystemSupported{
|
|
||||||
get{
|
|
||||||
return true; // Environment.OSVersion.Version >= new Version("6.1"); // 6.1 NT version = Windows 7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly UpdateHandler owner;
|
private readonly UpdateHandler owner;
|
||||||
|
|
||||||
public Bridge(UpdateHandler owner){
|
public Bridge(UpdateHandler owner){
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void TriggerUpdateCheck(){
|
||||||
|
owner.Check(false);
|
||||||
|
}
|
||||||
|
|
||||||
public void OnUpdateCheckFinished(int eventId, bool isUpdateAvailable, string latestVersion){
|
public void OnUpdateCheckFinished(int eventId, bool isUpdateAvailable, string latestVersion){
|
||||||
owner.TriggerCheckFinishedEvent(new UpdateCheckEventArgs(eventId, isUpdateAvailable, latestVersion));
|
owner.TriggerCheckFinishedEvent(new UpdateCheckEventArgs(eventId, isUpdateAvailable, latestVersion));
|
||||||
}
|
}
|
||||||
@@ -101,10 +107,7 @@ namespace TweetDck.Updates{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void OnUpdateDismissed(string versionTag){
|
public void OnUpdateDismissed(string versionTag){
|
||||||
owner.form.InvokeSafe(() => {
|
owner.TriggerUpdateDismissedEvent(new UpdateDismissedEventArgs(versionTag));
|
||||||
Program.UserConfig.DismissedUpdate = versionTag;
|
|
||||||
Program.UserConfig.Save();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenBrowser(string url){
|
public void OpenBrowser(string url){
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
namespace TweetDck.Updates{
|
namespace TweetDck.Updates{
|
||||||
class UpdaterSettings{
|
class UpdaterSettings{
|
||||||
public bool AllowPreReleases { get; set; }
|
public bool AllowPreReleases { get; set; }
|
||||||
|
public string DismissedUpdate { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,14 +10,14 @@
|
|||||||
|
|
||||||
[Setup]
|
[Setup]
|
||||||
AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06}
|
AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06}
|
||||||
AppName={#MyAppName}
|
AppName={#MyAppName} Portable
|
||||||
AppVersion={#MyAppVersion}
|
AppVersion={#MyAppVersion}
|
||||||
AppVerName={#MyAppName} {#MyAppVersion}
|
AppVerName={#MyAppName} {#MyAppVersion}
|
||||||
AppPublisher={#MyAppPublisher}
|
AppPublisher={#MyAppPublisher}
|
||||||
AppPublisherURL={#MyAppURL}
|
AppPublisherURL={#MyAppURL}
|
||||||
AppSupportURL={#MyAppURL}
|
AppSupportURL={#MyAppURL}
|
||||||
AppUpdatesURL={#MyAppURL}
|
AppUpdatesURL={#MyAppURL}
|
||||||
DefaultDirName={pf}\{#MyAppName}
|
DefaultDirName={sd}\{#MyAppName}
|
||||||
DefaultGroupName={#MyAppName}
|
DefaultGroupName={#MyAppName}
|
||||||
OutputBaseFilename={#MyAppName}.Portable
|
OutputBaseFilename={#MyAppName}.Portable
|
||||||
VersionInfoVersion={#MyAppVersion}
|
VersionInfoVersion={#MyAppVersion}
|
||||||
|
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
[Setup]
|
[Setup]
|
||||||
AppId={{{#MyAppID}}
|
AppId={{{#MyAppID}}
|
||||||
AppName={#MyAppName}
|
AppName={#MyAppName} Update
|
||||||
AppVersion={#MyAppVersion}
|
AppVersion={#MyAppVersion}
|
||||||
AppVerName={#MyAppName} {#MyAppVersion}
|
AppVerName={#MyAppName} {#MyAppVersion}
|
||||||
AppPublisher={#MyAppPublisher}
|
AppPublisher={#MyAppPublisher}
|
||||||
|
201
tests/Core/Utils/TestTwoKeyDictionary.cs
Normal file
201
tests/Core/Utils/TestTwoKeyDictionary.cs
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using TweetDck.Core.Utils;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace UnitTests.Core.Utils{
|
||||||
|
[TestClass]
|
||||||
|
public class TestTwoKeyDictionary{
|
||||||
|
private static TwoKeyDictionary<string, int, string> CreateDict(){
|
||||||
|
TwoKeyDictionary<string, int, string> dict = new TwoKeyDictionary<string, int, string>();
|
||||||
|
|
||||||
|
dict.Add("aaa", 0, "x");
|
||||||
|
dict.Add("aaa", 1, "y");
|
||||||
|
dict.Add("aaa", 2, "z");
|
||||||
|
|
||||||
|
dict.Add("bbb", 0, "test 1");
|
||||||
|
dict.Add("bbb", 10, "test 2");
|
||||||
|
dict.Add("bbb", 20, "test 3");
|
||||||
|
dict.Add("bbb", 30, "test 4");
|
||||||
|
|
||||||
|
dict.Add("ccc", -5, "");
|
||||||
|
dict.Add("", 0, "");
|
||||||
|
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestAdd(){
|
||||||
|
CreateDict();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(ArgumentException))]
|
||||||
|
public void TestAddDuplicate(){
|
||||||
|
var dict = new TwoKeyDictionary<string, int, string>();
|
||||||
|
|
||||||
|
dict.Add("aaa", 0, "test");
|
||||||
|
dict.Add("aaa", 0, "oops");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestAccessor(){
|
||||||
|
var dict = CreateDict();
|
||||||
|
|
||||||
|
// get
|
||||||
|
|
||||||
|
Assert.AreEqual("x", dict["aaa", 0]);
|
||||||
|
Assert.AreEqual("y", dict["aaa", 1]);
|
||||||
|
Assert.AreEqual("z", dict["aaa", 2]);
|
||||||
|
|
||||||
|
Assert.AreEqual("test 3", dict["bbb", 20]);
|
||||||
|
|
||||||
|
Assert.AreEqual("", dict["ccc", -5]);
|
||||||
|
Assert.AreEqual("", dict["", 0]);
|
||||||
|
|
||||||
|
// set
|
||||||
|
|
||||||
|
dict["aaa", 0] = "replaced entry";
|
||||||
|
Assert.AreEqual("replaced entry", dict["aaa", 0]);
|
||||||
|
|
||||||
|
dict["aaa", 3] = "new entry";
|
||||||
|
Assert.AreEqual("new entry", dict["aaa", 3]);
|
||||||
|
|
||||||
|
dict["xxxxx", 150] = "new key and entry";
|
||||||
|
Assert.AreEqual("new key and entry", dict["xxxxx", 150]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(KeyNotFoundException))]
|
||||||
|
public void TestAccessorMissingKey1(){
|
||||||
|
var _ = CreateDict()["missing", 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(KeyNotFoundException))]
|
||||||
|
public void TestAccessorMissingKey2(){
|
||||||
|
var _ = CreateDict()["aaa", 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestClear(){
|
||||||
|
var dict = CreateDict();
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains("bbb"));
|
||||||
|
dict.Clear("bbb");
|
||||||
|
Assert.IsTrue(dict.Contains("bbb"));
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains(""));
|
||||||
|
dict.Clear("");
|
||||||
|
Assert.IsTrue(dict.Contains(""));
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains("aaa"));
|
||||||
|
Assert.IsTrue(dict.Contains("ccc"));
|
||||||
|
dict.Clear();
|
||||||
|
Assert.IsFalse(dict.Contains("aaa"));
|
||||||
|
Assert.IsFalse(dict.Contains("ccc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(KeyNotFoundException))]
|
||||||
|
public void TestClearMissingKey(){
|
||||||
|
CreateDict().Clear("missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestContains(){
|
||||||
|
var dict = CreateDict();
|
||||||
|
|
||||||
|
// positive
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains("aaa"));
|
||||||
|
Assert.IsTrue(dict.Contains("aaa", 0));
|
||||||
|
Assert.IsTrue(dict.Contains("aaa", 1));
|
||||||
|
Assert.IsTrue(dict.Contains("aaa", 2));
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains("ccc"));
|
||||||
|
Assert.IsTrue(dict.Contains("ccc", -5));
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains(""));
|
||||||
|
Assert.IsTrue(dict.Contains("", 0));
|
||||||
|
|
||||||
|
// negative
|
||||||
|
|
||||||
|
Assert.IsFalse(dict.Contains("missing"));
|
||||||
|
Assert.IsFalse(dict.Contains("missing", 999));
|
||||||
|
|
||||||
|
Assert.IsFalse(dict.Contains("aaa", 3));
|
||||||
|
Assert.IsFalse(dict.Contains("", -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestCount(){
|
||||||
|
var dict = CreateDict();
|
||||||
|
|
||||||
|
Assert.AreEqual(9, dict.Count());
|
||||||
|
Assert.AreEqual(3, dict.Count("aaa"));
|
||||||
|
Assert.AreEqual(4, dict.Count("bbb"));
|
||||||
|
Assert.AreEqual(1, dict.Count("ccc"));
|
||||||
|
Assert.AreEqual(1, dict.Count(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(KeyNotFoundException))]
|
||||||
|
public void TestCountMissingKey(){
|
||||||
|
CreateDict().Count("missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestRemove(){
|
||||||
|
var dict = CreateDict();
|
||||||
|
|
||||||
|
// negative
|
||||||
|
Assert.IsFalse(dict.Remove("missing"));
|
||||||
|
Assert.IsFalse(dict.Remove("aaa", 3));
|
||||||
|
|
||||||
|
// positive
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains("aaa"));
|
||||||
|
Assert.IsTrue(dict.Remove("aaa"));
|
||||||
|
Assert.IsFalse(dict.Contains("aaa"));
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains("bbb", 10));
|
||||||
|
Assert.IsTrue(dict.Remove("bbb", 10));
|
||||||
|
Assert.IsFalse(dict.Contains("bbb", 10));
|
||||||
|
Assert.IsTrue(dict.Contains("bbb"));
|
||||||
|
Assert.IsTrue(dict.Contains("bbb", 20));
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Remove("bbb", 0));
|
||||||
|
Assert.IsTrue(dict.Remove("bbb", 20));
|
||||||
|
Assert.IsTrue(dict.Remove("bbb", 30));
|
||||||
|
Assert.IsFalse(dict.Contains("bbb"));
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains(""));
|
||||||
|
Assert.IsTrue(dict.Remove("", 0));
|
||||||
|
Assert.IsFalse(dict.Contains(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestTryGetValue(){
|
||||||
|
var dict = CreateDict();
|
||||||
|
string val;
|
||||||
|
|
||||||
|
// positive
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.TryGetValue("bbb", 10, out val));
|
||||||
|
Assert.AreEqual("test 2", val);
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.TryGetValue("ccc", -5, out val));
|
||||||
|
Assert.AreEqual("", val);
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.TryGetValue("", 0, out val));
|
||||||
|
Assert.AreEqual("", val);
|
||||||
|
|
||||||
|
// negative
|
||||||
|
|
||||||
|
Assert.IsFalse(dict.TryGetValue("ccc", -50, out val));
|
||||||
|
Assert.IsFalse(dict.TryGetValue("", 1, out val));
|
||||||
|
Assert.IsFalse(dict.TryGetValue("missing", 0, out val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -51,6 +51,7 @@
|
|||||||
<Compile Include="Core\Utils\TestBrowserUtils.cs" />
|
<Compile Include="Core\Utils\TestBrowserUtils.cs" />
|
||||||
<Compile Include="Core\Utils\TestCommandLineArgs.cs" />
|
<Compile Include="Core\Utils\TestCommandLineArgs.cs" />
|
||||||
<Compile Include="Core\Utils\TestCommandLineArgsParser.cs" />
|
<Compile Include="Core\Utils\TestCommandLineArgsParser.cs" />
|
||||||
|
<Compile Include="Core\Utils\TestTwoKeyDictionary.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="TestUtils.cs" />
|
<Compile Include="TestUtils.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
Reference in New Issue
Block a user