mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 10:32:10 +02:00
Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
1410974292 | |||
44413fa96c | |||
342a4b4067 | |||
4356dde92d | |||
21e64a18d8 | |||
5a305a6740 | |||
44595bad40 | |||
7fc9edc9cb | |||
93e191f522 | |||
8d8355e792 | |||
a5379d290c | |||
caea8d4315 | |||
24224ab4c6 | |||
4dbc02360c | |||
aa7a29af0c | |||
296d0c6199 | |||
812a034e8d | |||
e9de789b79 | |||
cfbc1b9575 | |||
e39e85e4dd | |||
3f0b161cd0 | |||
ebe3868720 | |||
ffd0f5e986 | |||
217535a3ba | |||
7abfbea2da | |||
86ffeaac9a | |||
ab915b7115 | |||
705b5d38cf | |||
fc2acb00b3 | |||
5add8a1d0e | |||
063d3a2637 | |||
f1f90a2ee3 | |||
ed317a4e46 | |||
cca16f3bb1 | |||
aba156cb3b | |||
cd4e4d7095 | |||
8fbb639430 | |||
d5bf8ec558 | |||
b6cff40f1e | |||
833e42f455 | |||
8134843dad | |||
1f92d5e633 | |||
dc51c0ae85 | |||
45c79643d6 | |||
9041bfc627 | |||
0b3b3dd0be | |||
89e92dab59 | |||
8c168c9ad7 | |||
9f63357a92 |
@@ -42,7 +42,6 @@ namespace TweetDuck.Configuration{
|
||||
public WindowState PluginsWindow { get; set; } = new WindowState();
|
||||
|
||||
public bool ExpandLinksOnHover { get; set; } = true;
|
||||
public bool SwitchAccountSelectors { get; set; } = true;
|
||||
public bool OpenSearchInFirstColumn { get; set; } = true;
|
||||
public bool KeepLikeFollowDialogsOpen { get; set; } = true;
|
||||
public bool BestImageQuality { get; set; } = true;
|
||||
@@ -51,6 +50,7 @@ namespace TweetDuck.Configuration{
|
||||
public bool IgnoreTrackingUrlWarning { get; set; } = false;
|
||||
public bool EnableSmoothScrolling { get; set; } = true;
|
||||
public string BrowserPath { get; set; } = null;
|
||||
public string SearchEngineUrl { get; set; } = null;
|
||||
private int _zoomLevel = 100;
|
||||
private bool _muteNotifications;
|
||||
|
||||
|
@@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
|
||||
namespace TweetDuck.Core.Bridge{
|
||||
sealed class CallbackBridge{
|
||||
private readonly Control owner;
|
||||
private readonly Action safeCallback;
|
||||
|
||||
public CallbackBridge(Control owner, Action safeCallback){
|
||||
this.owner = owner;
|
||||
this.safeCallback = safeCallback;
|
||||
}
|
||||
|
||||
public void Trigger(){
|
||||
owner.InvokeSafe(safeCallback);
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,7 +15,6 @@ namespace TweetDuck.Core.Bridge{
|
||||
build.Append("x.expandLinksOnHover=").Append(Bool(Program.UserConfig.ExpandLinksOnHover));
|
||||
|
||||
if (environment == Environment.Browser){
|
||||
build.Append("x.switchAccountSelectors=").Append(Bool(Program.UserConfig.SwitchAccountSelectors));
|
||||
build.Append("x.openSearchInFirstColumn=").Append(Bool(Program.UserConfig.OpenSearchInFirstColumn));
|
||||
build.Append("x.keepLikeFollowDialogsOpen=").Append(Bool(Program.UserConfig.KeepLikeFollowDialogsOpen));
|
||||
build.Append("x.muteNotifications=").Append(Bool(Program.UserConfig.MuteNotifications));
|
||||
|
@@ -3,7 +3,7 @@ using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Management;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Utils;
|
||||
@@ -13,20 +13,12 @@ namespace TweetDuck.Core.Bridge{
|
||||
class TweetDeckBridge{
|
||||
public static string FontSize { get; private set; }
|
||||
public static string NotificationHeadLayout { get; private set; }
|
||||
|
||||
public static string LastHighlightedTweetUrl = string.Empty;
|
||||
public static string LastHighlightedQuoteUrl = string.Empty;
|
||||
private static string LastHighlightedTweetAuthors = string.Empty;
|
||||
private static string LastHighlightedTweetImages = string.Empty;
|
||||
|
||||
public static string[] LastHighlightedTweetAuthorsArray => LastHighlightedTweetAuthors.Split(';');
|
||||
public static string[] LastHighlightedTweetImagesArray => LastHighlightedTweetImages.Split(';');
|
||||
public static readonly ContextInfo ContextInfo = new ContextInfo();
|
||||
|
||||
private static readonly Dictionary<string, string> SessionData = new Dictionary<string, string>(2);
|
||||
|
||||
public static void ResetStaticProperties(){
|
||||
FontSize = NotificationHeadLayout = null;
|
||||
LastHighlightedTweetUrl = LastHighlightedQuoteUrl = LastHighlightedTweetAuthors = LastHighlightedTweetImages = string.Empty;
|
||||
}
|
||||
|
||||
public static void RestoreSessionData(IFrame frame){
|
||||
@@ -72,13 +64,8 @@ namespace TweetDuck.Core.Bridge{
|
||||
});
|
||||
}
|
||||
|
||||
public void SetLastHighlightedTweet(string tweetUrl, string quoteUrl, string authors, string imageList){
|
||||
form.InvokeAsyncSafe(() => {
|
||||
LastHighlightedTweetUrl = tweetUrl;
|
||||
LastHighlightedQuoteUrl = quoteUrl;
|
||||
LastHighlightedTweetAuthors = authors;
|
||||
LastHighlightedTweetImages = imageList;
|
||||
});
|
||||
public void SetRightClickedChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
||||
ContextInfo.SetChirp(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
||||
}
|
||||
|
||||
public void DisplayTooltip(string text){
|
||||
@@ -112,8 +99,8 @@ namespace TweetDuck.Core.Bridge{
|
||||
|
||||
// Global
|
||||
|
||||
public void SetLastRightClickInfo(string type, string link){
|
||||
form.InvokeAsyncSafe(() => ContextMenuBase.SetContextInfo(type, link));
|
||||
public void SetLastRightClickInfo(string type, string url){
|
||||
ContextInfo.SetLink(type, url);
|
||||
}
|
||||
|
||||
public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
|
||||
@@ -130,8 +117,8 @@ namespace TweetDuck.Core.Bridge{
|
||||
});
|
||||
}
|
||||
|
||||
public void ScreenshotTweet(string html, int width, int height){
|
||||
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
|
||||
public void ScreenshotTweet(string html, int width){
|
||||
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width));
|
||||
}
|
||||
|
||||
public void PlayVideo(string url, string username){
|
||||
|
@@ -15,6 +15,7 @@ using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetDuck.Plugins.Events;
|
||||
using TweetDuck.Updates;
|
||||
using TweetDuck.Updates.Events;
|
||||
|
||||
namespace TweetDuck.Core{
|
||||
sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{
|
||||
@@ -38,8 +39,7 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
|
||||
public string UpdateInstallerPath { get; private set; }
|
||||
|
||||
public PluginManager PluginManager => plugins;
|
||||
|
||||
public AnalyticsFile AnalyticsFile => analytics?.File ?? AnalyticsFile.Dummy;
|
||||
|
||||
private readonly TweetDeckBrowser browser;
|
||||
@@ -298,10 +298,18 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
|
||||
public void ReloadToTweetDeck(){
|
||||
#if DEBUG
|
||||
Resources.ScriptLoader.HotSwap();
|
||||
#endif
|
||||
|
||||
browser.ReloadToTweetDeck();
|
||||
AnalyticsFile.BrowserReloads.Trigger();
|
||||
}
|
||||
|
||||
public void AddSearchColumn(string query){
|
||||
browser.AddSearchColumn(query);
|
||||
}
|
||||
|
||||
public void TriggerTweetScreenshot(){
|
||||
browser.TriggerTweetScreenshot();
|
||||
}
|
||||
@@ -456,12 +464,12 @@ namespace TweetDuck.Core{
|
||||
AnalyticsFile.TweetDetails.Trigger();
|
||||
}
|
||||
|
||||
public void OnTweetScreenshotReady(string html, int width, int height){
|
||||
public void OnTweetScreenshotReady(string html, int width){
|
||||
if (notificationScreenshotManager == null){
|
||||
notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
|
||||
}
|
||||
|
||||
notificationScreenshotManager.Trigger(html, width, height);
|
||||
notificationScreenshotManager.Trigger(html, width);
|
||||
AnalyticsFile.TweetScreenshots.Trigger();
|
||||
}
|
||||
|
||||
|
@@ -3,15 +3,15 @@ using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Management;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
abstract class ContextMenuBase : IContextMenuHandler{
|
||||
@@ -19,19 +19,6 @@ namespace TweetDuck.Core.Handling{
|
||||
|
||||
private static TwitterUtils.ImageQuality ImageQuality => Program.UserConfig.TwitterImageQuality;
|
||||
|
||||
private static KeyValuePair<string, string> ContextInfo;
|
||||
private static bool IsLink => ContextInfo.Key == "link";
|
||||
private static bool IsImage => ContextInfo.Key == "image";
|
||||
private static bool IsVideo => ContextInfo.Key == "video";
|
||||
|
||||
public static void SetContextInfo(string type, string link){
|
||||
ContextInfo = new KeyValuePair<string, string>(string.IsNullOrEmpty(link) ? null : type, link);
|
||||
}
|
||||
|
||||
private static string GetMediaLink(IContextMenuParams parameters){
|
||||
return IsImage || IsVideo ? ContextInfo.Value : parameters.SourceUrl;
|
||||
}
|
||||
|
||||
private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand)26500;
|
||||
private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand)26501;
|
||||
private const CefMenuCommand MenuCopyUsername = (CefMenuCommand)26502;
|
||||
@@ -40,10 +27,11 @@ namespace TweetDuck.Core.Handling{
|
||||
private const CefMenuCommand MenuCopyMediaUrl = (CefMenuCommand)26505;
|
||||
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26506;
|
||||
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26507;
|
||||
private const CefMenuCommand MenuSearchInBrowser = (CefMenuCommand)26508;
|
||||
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
|
||||
|
||||
private string[] lastHighlightedTweetAuthors;
|
||||
private string[] lastHighlightedTweetImageList;
|
||||
protected ContextInfo.LinkInfo LastLink { get; private set; }
|
||||
protected ContextInfo.ChirpInfo LastChirp { get; private set; }
|
||||
|
||||
private readonly AnalyticsFile.IProvider analytics;
|
||||
|
||||
@@ -51,19 +39,28 @@ namespace TweetDuck.Core.Handling{
|
||||
this.analytics = analytics;
|
||||
}
|
||||
|
||||
private void ResetContextInfo(){
|
||||
LastLink = default(ContextInfo.LinkInfo);
|
||||
LastChirp = default(ContextInfo.ChirpInfo);
|
||||
TweetDeckBridge.ContextInfo.Reset();
|
||||
}
|
||||
|
||||
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
||||
lastHighlightedTweetAuthors = StringUtils.EmptyArray;
|
||||
lastHighlightedTweetImageList = StringUtils.EmptyArray;
|
||||
ContextInfo = default(KeyValuePair<string, string>);
|
||||
ResetContextInfo();
|
||||
}
|
||||
else{
|
||||
lastHighlightedTweetAuthors = TweetDeckBridge.LastHighlightedTweetAuthorsArray;
|
||||
lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImagesArray;
|
||||
LastLink = TweetDeckBridge.ContextInfo.Link;
|
||||
LastChirp = TweetDeckBridge.ContextInfo.Chirp;
|
||||
}
|
||||
|
||||
bool hasTweetImage = IsImage;
|
||||
bool hasTweetVideo = IsVideo;
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
|
||||
model.AddItem(MenuSearchInBrowser, "Search in browser");
|
||||
model.AddSeparator();
|
||||
}
|
||||
|
||||
bool hasTweetImage = LastLink.IsImage;
|
||||
bool hasTweetVideo = LastLink.IsVideo;
|
||||
|
||||
string TextOpen(string name) => "Open "+name+" in browser";
|
||||
string TextCopy(string name) => "Copy "+name+" address";
|
||||
@@ -95,7 +92,7 @@ namespace TweetDuck.Core.Handling{
|
||||
model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
|
||||
model.AddItem(MenuSaveMedia, TextSave("image"));
|
||||
|
||||
if (lastHighlightedTweetImageList.Length > 1){
|
||||
if (LastChirp.Images.Length > 1){
|
||||
model.AddItem(MenuSaveTweetImages, TextSave("all images"));
|
||||
}
|
||||
|
||||
@@ -108,11 +105,11 @@ namespace TweetDuck.Core.Handling{
|
||||
|
||||
switch(commandId){
|
||||
case MenuOpenLinkUrl:
|
||||
OpenBrowser(control, IsLink ? ContextInfo.Value : parameters.LinkUrl);
|
||||
OpenBrowser(control, LastLink.GetUrl(parameters, true));
|
||||
break;
|
||||
|
||||
case MenuCopyLinkUrl:
|
||||
SetClipboardText(control, IsLink ? ContextInfo.Value : parameters.UnfilteredLinkUrl);
|
||||
SetClipboardText(control, LastLink.GetUrl(parameters, false));
|
||||
break;
|
||||
|
||||
case MenuCopyUsername:
|
||||
@@ -122,35 +119,37 @@ namespace TweetDuck.Core.Handling{
|
||||
break;
|
||||
|
||||
case MenuOpenMediaUrl:
|
||||
OpenBrowser(control, TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
|
||||
OpenBrowser(control, TwitterUtils.GetMediaLink(LastLink.GetMediaSource(parameters), ImageQuality));
|
||||
break;
|
||||
|
||||
case MenuCopyMediaUrl:
|
||||
SetClipboardText(control, TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
|
||||
SetClipboardText(control, TwitterUtils.GetMediaLink(LastLink.GetMediaSource(parameters), ImageQuality));
|
||||
break;
|
||||
|
||||
case MenuViewImage:
|
||||
string url = GetMediaLink(parameters);
|
||||
string file = Path.Combine(BrowserCache.CacheFolder, TwitterUtils.GetImageFileName(url));
|
||||
|
||||
void ViewFile(){
|
||||
string ext = Path.GetExtension(file);
|
||||
void ViewImage(string path){
|
||||
string ext = Path.GetExtension(path);
|
||||
|
||||
if (TwitterUtils.ValidImageExtensions.Contains(ext)){
|
||||
WindowsUtils.OpenAssociatedProgram(file);
|
||||
WindowsUtils.OpenAssociatedProgram(path);
|
||||
}
|
||||
else{
|
||||
FormMessage.Error("Image Download", "Invalid file extension "+ext, FormMessage.OK);
|
||||
}
|
||||
}
|
||||
|
||||
string url = LastLink.GetMediaSource(parameters);
|
||||
string file = Path.Combine(BrowserCache.CacheFolder, TwitterUtils.GetImageFileName(url) ?? Path.GetRandomFileName());
|
||||
|
||||
if (File.Exists(file)){
|
||||
ViewFile();
|
||||
ViewImage(file);
|
||||
}
|
||||
else{
|
||||
control.InvokeAsyncSafe(analytics.AnalyticsFile.ViewedImages.Trigger);
|
||||
|
||||
BrowserUtils.DownloadFileAsync(TwitterUtils.GetMediaLink(url, ImageQuality), file, ViewFile, ex => {
|
||||
BrowserUtils.DownloadFileAsync(TwitterUtils.GetMediaLink(url, ImageQuality), file, () => {
|
||||
ViewImage(file);
|
||||
}, ex => {
|
||||
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
|
||||
});
|
||||
}
|
||||
@@ -158,20 +157,26 @@ namespace TweetDuck.Core.Handling{
|
||||
break;
|
||||
|
||||
case MenuSaveMedia:
|
||||
if (IsVideo){
|
||||
if (LastLink.IsVideo){
|
||||
control.InvokeAsyncSafe(analytics.AnalyticsFile.DownloadedVideos.Trigger);
|
||||
TwitterUtils.DownloadVideo(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault());
|
||||
TwitterUtils.DownloadVideo(LastLink.GetMediaSource(parameters), LastChirp.Authors.LastOrDefault());
|
||||
}
|
||||
else{
|
||||
control.InvokeAsyncSafe(analytics.AnalyticsFile.DownloadedImages.Trigger);
|
||||
TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
|
||||
TwitterUtils.DownloadImage(LastLink.GetMediaSource(parameters), LastChirp.Authors.LastOrDefault(), ImageQuality);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MenuSaveTweetImages:
|
||||
control.InvokeAsyncSafe(analytics.AnalyticsFile.DownloadedImages.Trigger);
|
||||
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
|
||||
TwitterUtils.DownloadImages(LastChirp.Images, LastChirp.Authors.LastOrDefault(), ImageQuality);
|
||||
break;
|
||||
|
||||
case MenuSearchInBrowser:
|
||||
string query = parameters.SelectionText;
|
||||
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalSearch(query));
|
||||
DeselectAll(frame);
|
||||
break;
|
||||
|
||||
case MenuOpenDevTools:
|
||||
@@ -183,13 +188,17 @@ namespace TweetDuck.Core.Handling{
|
||||
}
|
||||
|
||||
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
||||
ContextInfo = default(KeyValuePair<string, string>);
|
||||
ResetContextInfo();
|
||||
}
|
||||
|
||||
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void DeselectAll(IFrame frame){
|
||||
ScriptLoader.ExecuteScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect");
|
||||
}
|
||||
|
||||
protected void OpenBrowser(Control control, string url){
|
||||
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
|
||||
}
|
||||
@@ -197,6 +206,10 @@ namespace TweetDuck.Core.Handling{
|
||||
protected void SetClipboardText(Control control, string text){
|
||||
control.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
|
||||
}
|
||||
|
||||
protected static void InsertSelectionSearchItem(IMenuModel model, CefMenuCommand insertCommand, string insertLabel){
|
||||
model.InsertItemAt(model.GetIndexOf(MenuSearchInBrowser)+1, insertCommand, insertLabel);
|
||||
}
|
||||
|
||||
protected static void AddDebugMenuItems(IMenuModel model){
|
||||
model.AddItem(MenuOpenDevTools, "Open dev tools");
|
||||
|
@@ -1,6 +1,5 @@
|
||||
using CefSharp;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
@@ -18,6 +17,7 @@ namespace TweetDuck.Core.Handling{
|
||||
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26613;
|
||||
private const CefMenuCommand MenuScreenshotTweet = (CefMenuCommand)26614;
|
||||
private const CefMenuCommand MenuInputApplyROT13 = (CefMenuCommand)26615;
|
||||
private const CefMenuCommand MenuSearchInColumn = (CefMenuCommand)26616;
|
||||
|
||||
private const string TitleReloadBrowser = "Reload browser";
|
||||
private const string TitleMuteNotifications = "Mute notifications";
|
||||
@@ -26,23 +26,23 @@ namespace TweetDuck.Core.Handling{
|
||||
private const string TitleAboutProgram = "About "+Program.BrandName;
|
||||
|
||||
private readonly FormBrowser form;
|
||||
|
||||
private string lastHighlightedTweetUrl;
|
||||
private string lastHighlightedQuoteUrl;
|
||||
|
||||
|
||||
public ContextMenuBrowser(FormBrowser form) : base(form){
|
||||
this.form = form;
|
||||
}
|
||||
|
||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||
bool isSelecting = parameters.TypeFlags.HasFlag(ContextMenuType.Selection);
|
||||
bool isEditing = parameters.TypeFlags.HasFlag(ContextMenuType.Editable);
|
||||
|
||||
model.Remove(CefMenuCommand.Back);
|
||||
model.Remove(CefMenuCommand.Forward);
|
||||
model.Remove(CefMenuCommand.Print);
|
||||
model.Remove(CefMenuCommand.ViewSource);
|
||||
RemoveSeparatorIfLast(model);
|
||||
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
|
||||
if (isSelecting){
|
||||
if (isEditing){
|
||||
model.AddSeparator();
|
||||
model.AddItem(MenuInputApplyROT13, "Apply ROT13");
|
||||
}
|
||||
@@ -52,20 +52,16 @@ namespace TweetDuck.Core.Handling{
|
||||
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
|
||||
lastHighlightedTweetUrl = TweetDeckBridge.LastHighlightedTweetUrl;
|
||||
lastHighlightedQuoteUrl = TweetDeckBridge.LastHighlightedQuoteUrl;
|
||||
|
||||
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
||||
lastHighlightedTweetUrl = string.Empty;
|
||||
lastHighlightedQuoteUrl = string.Empty;
|
||||
if (isSelecting && !isEditing && TwitterUtils.IsTweetDeckWebsite(frame)){
|
||||
InsertSelectionSearchItem(model, MenuSearchInColumn, "Search in a column");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(lastHighlightedTweetUrl) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
|
||||
if (!string.IsNullOrEmpty(LastChirp.TweetUrl) && !isSelecting && !isEditing){
|
||||
model.AddItem(MenuOpenTweetUrl, "Open tweet in browser");
|
||||
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
|
||||
model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard");
|
||||
|
||||
if (!string.IsNullOrEmpty(lastHighlightedQuoteUrl)){
|
||||
if (!string.IsNullOrEmpty(LastChirp.QuoteUrl)){
|
||||
model.AddSeparator();
|
||||
model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
|
||||
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
||||
@@ -74,7 +70,7 @@ namespace TweetDuck.Core.Handling{
|
||||
model.AddSeparator();
|
||||
}
|
||||
|
||||
if ((parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
|
||||
if (!isSelecting && !isEditing){
|
||||
AddSeparator(model);
|
||||
|
||||
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName);
|
||||
@@ -126,11 +122,11 @@ namespace TweetDuck.Core.Handling{
|
||||
return true;
|
||||
|
||||
case MenuOpenTweetUrl:
|
||||
OpenBrowser(form, lastHighlightedTweetUrl);
|
||||
OpenBrowser(form, LastChirp.TweetUrl);
|
||||
return true;
|
||||
|
||||
case MenuCopyTweetUrl:
|
||||
SetClipboardText(form, lastHighlightedTweetUrl);
|
||||
SetClipboardText(form, LastChirp.TweetUrl);
|
||||
return true;
|
||||
|
||||
case MenuScreenshotTweet:
|
||||
@@ -138,16 +134,22 @@ namespace TweetDuck.Core.Handling{
|
||||
return true;
|
||||
|
||||
case MenuOpenQuotedTweetUrl:
|
||||
OpenBrowser(form, lastHighlightedQuoteUrl);
|
||||
OpenBrowser(form, LastChirp.QuoteUrl);
|
||||
return true;
|
||||
|
||||
case MenuCopyQuotedTweetUrl:
|
||||
SetClipboardText(form, lastHighlightedQuoteUrl);
|
||||
SetClipboardText(form, LastChirp.QuoteUrl);
|
||||
return true;
|
||||
|
||||
case MenuInputApplyROT13:
|
||||
form.InvokeAsyncSafe(form.ApplyROT13);
|
||||
return true;
|
||||
|
||||
case MenuSearchInColumn:
|
||||
string query = parameters.SelectionText;
|
||||
form.InvokeAsyncSafe(() => form.AddSearchColumn(query));
|
||||
DeselectAll(frame);
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@@ -1,7 +1,6 @@
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Utils;
|
||||
@@ -28,7 +27,7 @@ namespace TweetDuck.Core.Handling.General{
|
||||
}
|
||||
|
||||
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){
|
||||
((ChromiumWebBrowser)browserControl).InvokeSafe(() => {
|
||||
browserControl.AsControl().InvokeSafe(() => {
|
||||
FormMessage form;
|
||||
TextBox input = null;
|
||||
|
||||
|
68
Core/Management/ContextInfo.cs
Normal file
68
Core/Management/ContextInfo.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Management{
|
||||
sealed class ContextInfo{
|
||||
public LinkInfo Link { get; private set; }
|
||||
public ChirpInfo Chirp { get; private set; }
|
||||
|
||||
public ContextInfo(){
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void SetLink(string type, string url){
|
||||
Link = string.IsNullOrEmpty(url) ? new LinkInfo() : new LinkInfo(type, url);
|
||||
}
|
||||
|
||||
public void SetChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
||||
Chirp = new ChirpInfo(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
||||
}
|
||||
|
||||
public void Reset(){
|
||||
Link = new LinkInfo();
|
||||
Chirp = new ChirpInfo();
|
||||
}
|
||||
|
||||
// Data structures
|
||||
|
||||
public struct LinkInfo{
|
||||
public bool IsLink => type == "link";
|
||||
public bool IsImage => type == "image";
|
||||
public bool IsVideo => type == "video";
|
||||
|
||||
public string GetUrl(IContextMenuParams parameters, bool safe){
|
||||
return IsLink ? url : (safe ? parameters.LinkUrl : parameters.UnfilteredLinkUrl);
|
||||
}
|
||||
|
||||
public string GetMediaSource(IContextMenuParams parameters){
|
||||
return IsImage || IsVideo ? url : parameters.SourceUrl;
|
||||
}
|
||||
|
||||
private readonly string type;
|
||||
private readonly string url;
|
||||
|
||||
public LinkInfo(string type, string url){
|
||||
this.type = type;
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChirpInfo{
|
||||
public string TweetUrl { get; }
|
||||
public string QuoteUrl { get; }
|
||||
|
||||
public string[] Authors => chirpAuthors?.Split(';') ?? StringUtils.EmptyArray;
|
||||
public string[] Images => chirpImages?.Split(';') ?? StringUtils.EmptyArray;
|
||||
|
||||
private readonly string chirpAuthors;
|
||||
private readonly string chirpImages;
|
||||
|
||||
public ChirpInfo(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
||||
this.TweetUrl = tweetUrl;
|
||||
this.QuoteUrl = quoteUrl;
|
||||
this.chirpAuthors = chirpAuthors;
|
||||
this.chirpImages = chirpImages;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -23,7 +23,6 @@ namespace TweetDuck.Core.Management{
|
||||
}
|
||||
|
||||
public bool IsRestarting { get; private set; }
|
||||
public Exception LastException { get; private set; }
|
||||
|
||||
private readonly string file;
|
||||
private readonly PluginManager plugins;
|
||||
@@ -67,7 +66,7 @@ namespace TweetDuck.Core.Management{
|
||||
|
||||
return true;
|
||||
}catch(Exception e){
|
||||
LastException = e;
|
||||
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -100,8 +99,7 @@ namespace TweetDuck.Core.Management{
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch(Exception e){
|
||||
LastException = e;
|
||||
}catch(Exception){
|
||||
items = Items.None;
|
||||
}
|
||||
|
||||
@@ -164,12 +162,12 @@ namespace TweetDuck.Core.Management{
|
||||
}
|
||||
|
||||
if (missingPlugins.Count > 0){
|
||||
FormMessage.Information("Importing TweetDuck Profile", "Detected missing plugins when importing plugin data:\n"+string.Join("\n", missingPlugins), FormMessage.OK);
|
||||
FormMessage.Information("Profile Import", "Detected missing plugins when importing plugin data:\n"+string.Join("\n", missingPlugins), FormMessage.OK);
|
||||
}
|
||||
|
||||
return true;
|
||||
}catch(Exception e){
|
||||
LastException = e;
|
||||
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing TweetDuck profile.", true, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -199,15 +197,23 @@ namespace TweetDuck.Core.Management{
|
||||
}
|
||||
|
||||
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
|
||||
return Directory.Exists(root) ? Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{
|
||||
Full = fullPath,
|
||||
Relative = fullPath.Substring(root.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) // strip leading separator character
|
||||
}) : Enumerable.Empty<PathInfo>();
|
||||
if (Directory.Exists(root)){
|
||||
int rootLength = root.Length;
|
||||
return Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo(fullPath, rootLength));
|
||||
}
|
||||
else{
|
||||
return Enumerable.Empty<PathInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class PathInfo{
|
||||
public string Full { get; set; }
|
||||
public string Relative { get; set; }
|
||||
public string Full { get; }
|
||||
public string Relative { get; }
|
||||
|
||||
public PathInfo(string fullPath, int rootLength){
|
||||
this.Full = fullPath;
|
||||
this.Relative = fullPath.Substring(rootLength).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); // strip leading separator character
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -9,25 +9,13 @@ using TweetLib.Communication;
|
||||
|
||||
namespace TweetDuck.Core.Management{
|
||||
sealed class VideoPlayer : IDisposable{
|
||||
public bool Running{
|
||||
get{
|
||||
if (currentProcess == null){
|
||||
return false;
|
||||
}
|
||||
|
||||
currentProcess.Refresh();
|
||||
return !currentProcess.HasExited;
|
||||
}
|
||||
}
|
||||
public bool Running => currentInstance != null && currentInstance.Running;
|
||||
|
||||
public event EventHandler ProcessExited;
|
||||
|
||||
private readonly FormBrowser owner;
|
||||
private string lastUrl;
|
||||
private string lastUsername;
|
||||
|
||||
private Process currentProcess;
|
||||
private DuplexPipe.Server currentPipe;
|
||||
private Instance currentInstance;
|
||||
private bool isClosing;
|
||||
|
||||
public VideoPlayer(FormBrowser owner){
|
||||
@@ -40,38 +28,43 @@ namespace TweetDuck.Core.Management{
|
||||
Destroy();
|
||||
isClosing = false;
|
||||
}
|
||||
|
||||
lastUrl = url;
|
||||
lastUsername = username;
|
||||
|
||||
try{
|
||||
currentPipe = DuplexPipe.CreateServer();
|
||||
currentPipe.DataIn += currentPipe_DataIn;
|
||||
DuplexPipe.Server pipe = DuplexPipe.CreateServer();
|
||||
pipe.DataIn += pipe_DataIn;
|
||||
|
||||
if ((currentProcess = Process.Start(new ProcessStartInfo{
|
||||
Process process;
|
||||
|
||||
if ((process = Process.Start(new ProcessStartInfo{
|
||||
FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"),
|
||||
Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\" \"{currentPipe.GenerateToken()}\"",
|
||||
Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\" \"{pipe.GenerateToken()}\"",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true
|
||||
})) != null){
|
||||
currentProcess.EnableRaisingEvents = true;
|
||||
currentProcess.Exited += process_Exited;
|
||||
|
||||
currentProcess.BeginOutputReadLine();
|
||||
currentProcess.OutputDataReceived += process_OutputDataReceived;
|
||||
}
|
||||
currentInstance = new Instance(process, pipe, url, username);
|
||||
|
||||
currentPipe.DisposeToken();
|
||||
process.EnableRaisingEvents = true;
|
||||
process.Exited += process_Exited;
|
||||
|
||||
process.BeginOutputReadLine();
|
||||
process.OutputDataReceived += process_OutputDataReceived;
|
||||
|
||||
pipe.DisposeToken();
|
||||
}
|
||||
else{
|
||||
pipe.DataIn -= pipe_DataIn;
|
||||
pipe.Dispose();
|
||||
}
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendKeyEvent(Keys key){
|
||||
currentPipe?.Write("key", ((int)key).ToString());
|
||||
currentInstance?.Pipe.Write("key", ((int)key).ToString());
|
||||
}
|
||||
|
||||
private void currentPipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
|
||||
private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
|
||||
owner.InvokeSafe(() => {
|
||||
switch(e.Key){
|
||||
case "vol":
|
||||
@@ -83,16 +76,16 @@ namespace TweetDuck.Core.Management{
|
||||
break;
|
||||
|
||||
case "download":
|
||||
owner.AnalyticsFile.DownloadedVideos.Trigger();
|
||||
TwitterUtils.DownloadVideo(lastUrl, lastUsername);
|
||||
if (currentInstance != null){
|
||||
owner.AnalyticsFile.DownloadedVideos.Trigger();
|
||||
TwitterUtils.DownloadVideo(currentInstance.Url, currentInstance.Username);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "rip":
|
||||
currentPipe.Dispose();
|
||||
currentPipe = null;
|
||||
|
||||
currentProcess.Dispose();
|
||||
currentProcess = null;
|
||||
currentInstance?.Dispose();
|
||||
currentInstance = null;
|
||||
|
||||
isClosing = false;
|
||||
TriggerProcessExitEventUnsafe();
|
||||
@@ -102,15 +95,15 @@ namespace TweetDuck.Core.Management{
|
||||
}
|
||||
|
||||
public void Close(){
|
||||
if (currentProcess != null){
|
||||
if (currentInstance != null){
|
||||
if (isClosing){
|
||||
Destroy();
|
||||
isClosing = false;
|
||||
}
|
||||
else{
|
||||
isClosing = true;
|
||||
currentProcess.Exited -= process_Exited;
|
||||
currentPipe.Write("die");
|
||||
currentInstance.Process.Exited -= process_Exited;
|
||||
currentInstance.Pipe.Write("die");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,26 +116,17 @@ namespace TweetDuck.Core.Management{
|
||||
}
|
||||
|
||||
private void Destroy(){
|
||||
if (currentProcess != null){
|
||||
try{
|
||||
currentProcess.Kill();
|
||||
}catch{
|
||||
// kill me instead then
|
||||
}
|
||||
|
||||
currentProcess.Dispose();
|
||||
currentProcess = null;
|
||||
|
||||
currentPipe.Dispose();
|
||||
currentPipe = null;
|
||||
if (currentInstance != null){
|
||||
currentInstance.KillAndDispose();
|
||||
currentInstance = null;
|
||||
|
||||
TriggerProcessExitEventUnsafe();
|
||||
}
|
||||
}
|
||||
|
||||
private void owner_FormClosing(object sender, FormClosingEventArgs e){
|
||||
if (currentProcess != null){
|
||||
currentProcess.Exited -= process_Exited;
|
||||
if (currentInstance != null){
|
||||
currentInstance.Process.Exited -= process_Exited;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,25 +137,27 @@ namespace TweetDuck.Core.Management{
|
||||
}
|
||||
|
||||
private void process_Exited(object sender, EventArgs e){
|
||||
int exitCode = currentProcess.ExitCode;
|
||||
if (currentInstance == null){
|
||||
return;
|
||||
}
|
||||
|
||||
currentProcess.Dispose();
|
||||
currentProcess = null;
|
||||
int exitCode = currentInstance.Process.ExitCode;
|
||||
string url = currentInstance.Url;
|
||||
|
||||
currentPipe.Dispose();
|
||||
currentPipe = null;
|
||||
currentInstance.Dispose();
|
||||
currentInstance = null;
|
||||
|
||||
switch(exitCode){
|
||||
case 3: // CODE_LAUNCH_FAIL
|
||||
if (FormMessage.Error("Video Playback Error", "Error launching video player, this may be caused by missing Windows Media Player. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
|
||||
BrowserUtils.OpenExternalBrowser(lastUrl);
|
||||
BrowserUtils.OpenExternalBrowser(url);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 4: // CODE_MEDIA_ERROR
|
||||
if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
|
||||
BrowserUtils.OpenExternalBrowser(lastUrl);
|
||||
BrowserUtils.OpenExternalBrowser(url);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -183,5 +169,42 @@ namespace TweetDuck.Core.Management{
|
||||
private void TriggerProcessExitEventUnsafe(){
|
||||
ProcessExited?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private sealed class Instance : IDisposable{
|
||||
public bool Running{
|
||||
get{
|
||||
Process.Refresh();
|
||||
return !Process.HasExited;
|
||||
}
|
||||
}
|
||||
|
||||
public Process Process { get; }
|
||||
public DuplexPipe.Server Pipe { get; }
|
||||
|
||||
public string Url { get; }
|
||||
public string Username { get; }
|
||||
|
||||
public Instance(Process process, DuplexPipe.Server pipe, string url, string username){
|
||||
this.Process = process;
|
||||
this.Pipe = pipe;
|
||||
this.Url = url;
|
||||
this.Username = username;
|
||||
}
|
||||
|
||||
public void KillAndDispose(){
|
||||
try{
|
||||
Process.Kill();
|
||||
}catch{
|
||||
// kill me instead then
|
||||
}
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
Process.Dispose();
|
||||
Pipe.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ namespace TweetDuck.Core.Notification.Example{
|
||||
private readonly TweetNotification exampleNotification;
|
||||
|
||||
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){
|
||||
string exampleTweetHTML = ScriptLoader.LoadResource("pages/example.html", true).Replace("{avatar}", TweetNotification.AppLogo.Url);
|
||||
string exampleTweetHTML = ScriptLoader.LoadResource("pages/example.html", true)?.Replace("{avatar}", TweetNotification.AppLogo.Url) ?? string.Empty;
|
||||
|
||||
#if DEBUG
|
||||
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
|
||||
|
@@ -134,13 +134,7 @@ namespace TweetDuck.Core.Notification{
|
||||
this.browser.Dock = DockStyle.None;
|
||||
this.browser.ClientSize = ClientSize;
|
||||
this.browser.IsBrowserInitializedChanged += browser_IsBrowserInitializedChanged;
|
||||
|
||||
#if DEBUG
|
||||
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
|
||||
#endif
|
||||
|
||||
DpiScale = this.GetDPIScale();
|
||||
|
||||
|
||||
browser.SetupResourceHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
|
||||
browser.SetupResourceHandler(TweetNotification.AppLogo);
|
||||
|
||||
@@ -151,6 +145,8 @@ namespace TweetDuck.Core.Notification{
|
||||
this.owner.FormClosed -= owner_FormClosed;
|
||||
};
|
||||
|
||||
DpiScale = this.GetDPIScale();
|
||||
|
||||
// ReSharper disable once VirtualMemberCallInContructor
|
||||
UpdateTitle();
|
||||
}
|
||||
@@ -200,7 +196,7 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
|
||||
protected virtual string GetTweetHTML(TweetNotification tweet){
|
||||
return tweet.GenerateHtml(IsCursorOverBrowser ? "td-hover" : string.Empty);
|
||||
return tweet.GenerateHtml(IsCursorOverBrowser ? "td-notification td-hover" : "td-notification");
|
||||
}
|
||||
|
||||
protected virtual void LoadTweet(TweetNotification tweet){
|
||||
@@ -221,7 +217,9 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
|
||||
public void ShowTweetDetail(){
|
||||
owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl);
|
||||
if (currentNotification != null){
|
||||
owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl);
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveToVisibleLocation(){
|
||||
|
@@ -5,6 +5,7 @@ using System.Windows.Forms;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Other.Interfaces;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Plugins;
|
||||
@@ -92,6 +93,10 @@ namespace TweetDuck.Core.Notification{
|
||||
Disposed += (sender, args) => StopMouseHook(true);
|
||||
}
|
||||
|
||||
// implementation of ITweetDeckBrowser
|
||||
|
||||
bool ITweetDeckBrowser.IsTweetDeckWebsite => IsNotificationVisible;
|
||||
|
||||
void ITweetDeckBrowser.RegisterBridge(string name, object obj){
|
||||
browser.RegisterAsyncJsObject(name, obj);
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
@@ -15,23 +15,30 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
||||
protected override bool CanDragWindow => false;
|
||||
|
||||
private readonly PluginManager plugins;
|
||||
private readonly int width;
|
||||
|
||||
public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager) : base(owner, false){
|
||||
public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager, string html, int width) : base(owner, false){
|
||||
this.plugins = pluginManager;
|
||||
this.width = width;
|
||||
|
||||
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback));
|
||||
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new ScreenshotBridge(this, SetScreenshotHeight, callback));
|
||||
|
||||
browser.LoadingStateChanged += (sender, args) => {
|
||||
if (!args.IsLoading){
|
||||
using(IFrame frame = args.Browser.MainFrame){
|
||||
ScriptLoader.ExecuteScript(frame, "window.setTimeout($TD_NotificationScreenshot.trigger, document.getElementsByTagName('iframe').length ? 267 : 67)", "gen:screenshot");
|
||||
if (!ScriptLoader.ExecuteFile(frame, "screenshot.js")){
|
||||
this.InvokeAsyncSafe(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LoadTweet(new TweetNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty));
|
||||
SetScreenshotHeight(1);
|
||||
}
|
||||
|
||||
protected override string GetTweetHTML(TweetNotification tweet){
|
||||
string html = tweet.GenerateHtml("td-screenshot", false);
|
||||
string html = tweet.GenerateHtml("td-screenshot");
|
||||
|
||||
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
||||
html = injection.Inject(html);
|
||||
@@ -40,12 +47,16 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
||||
return html;
|
||||
}
|
||||
|
||||
public void LoadNotificationForScreenshot(TweetNotification tweet, int width, int height){
|
||||
LoadTweet(tweet);
|
||||
private void SetScreenshotHeight(int height){
|
||||
SetNotificationSize(width, height);
|
||||
}
|
||||
|
||||
public void TakeScreenshot(){
|
||||
if (ClientSize.Height == 0){
|
||||
FormMessage.Error("Screenshot Failed", "Could not detect screenshot size.", FormMessage.OK);
|
||||
return;
|
||||
}
|
||||
|
||||
IntPtr context = NativeMethods.GetDC(this.Handle);
|
||||
|
||||
if (context == IntPtr.Zero){
|
||||
|
26
Core/Notification/Screenshot/ScreenshotBridge.cs
Normal file
26
Core/Notification/Screenshot/ScreenshotBridge.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
|
||||
namespace TweetDuck.Core.Notification.Screenshot{
|
||||
sealed class ScreenshotBridge{
|
||||
private readonly Control owner;
|
||||
|
||||
private readonly Action<int> safeSetHeight;
|
||||
private readonly Action safeTriggerScreenshot;
|
||||
|
||||
public ScreenshotBridge(Control owner, Action<int> safeSetHeight, Action safeTriggerScreenshot){
|
||||
this.owner = owner;
|
||||
this.safeSetHeight = safeSetHeight;
|
||||
this.safeTriggerScreenshot = safeTriggerScreenshot;
|
||||
}
|
||||
|
||||
public void SetHeight(int tweetHeight){
|
||||
owner.InvokeSafe(() => safeSetHeight(tweetHeight));
|
||||
}
|
||||
|
||||
public void TriggerScreenshot(){
|
||||
owner.InvokeSafe(safeTriggerScreenshot);
|
||||
}
|
||||
}
|
||||
}
|
@@ -37,13 +37,12 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
||||
screenshot = null;
|
||||
}
|
||||
|
||||
public void Trigger(string html, int width, int height){
|
||||
public void Trigger(string html, int width){
|
||||
if (screenshot != null){
|
||||
return;
|
||||
}
|
||||
|
||||
screenshot = new FormNotificationScreenshotable(Callback, owner, plugins);
|
||||
screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty), width, height);
|
||||
screenshot = new FormNotificationScreenshotable(Callback, owner, plugins, html, width);
|
||||
screenshot.Show();
|
||||
timeout.Start();
|
||||
|
||||
|
@@ -8,7 +8,7 @@ using TweetDuck.Resources;
|
||||
namespace TweetDuck.Core.Notification{
|
||||
sealed class TweetNotification{
|
||||
private const string DefaultHeadLayout = @"<html id='tduck' class='os-windows txt-size--14' data-td-font='medium' data-td-theme='dark'><head><meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>";
|
||||
private static readonly string CustomCSS = ScriptLoader.LoadResource("styles/notification.css");
|
||||
private static readonly string CustomCSS = ScriptLoader.LoadResource("styles/notification.css") ?? string.Empty;
|
||||
public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandler.FromByteArray(Properties.Resources.avatar, "image/png"));
|
||||
|
||||
public static TweetNotification Example(string html, int characters){
|
||||
@@ -53,17 +53,14 @@ namespace TweetDuck.Core.Notification{
|
||||
return 2000+Math.Max(1000, value*characters);
|
||||
}
|
||||
|
||||
public string GenerateHtml(string bodyClasses = null, bool enableCustomCSS = true){
|
||||
public string GenerateHtml(string bodyClasses){
|
||||
StringBuilder build = new StringBuilder();
|
||||
build.Append("<!DOCTYPE html>");
|
||||
build.Append(TweetDeckBridge.NotificationHeadLayout ?? DefaultHeadLayout);
|
||||
|
||||
if (enableCustomCSS){
|
||||
build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>");
|
||||
build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>");
|
||||
|
||||
if (!string.IsNullOrEmpty(Program.UserConfig.CustomNotificationCSS)){
|
||||
build.Append("<style type='text/css'>").Append(Program.UserConfig.CustomNotificationCSS).Append("</style>");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(Program.UserConfig.CustomNotificationCSS)){
|
||||
build.Append("<style type='text/css'>").Append(Program.UserConfig.CustomNotificationCSS).Append("</style>");
|
||||
}
|
||||
|
||||
build.Append("</head>");
|
||||
|
@@ -21,7 +21,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
||||
});
|
||||
}
|
||||
|
||||
public static readonly AnalyticsFile Dummy = new AnalyticsFile(null);
|
||||
public static readonly AnalyticsFile Dummy = new AnalyticsFile();
|
||||
|
||||
// STATE PROPERTIES
|
||||
|
||||
@@ -67,6 +67,10 @@ namespace TweetDuck.Core.Other.Analytics{
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
private AnalyticsFile(){
|
||||
this.file = null;
|
||||
}
|
||||
|
||||
private void SetupProperties(){
|
||||
foreach(Counter counter in GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.PropertyType == typeof(Counter)).Select(prop => (Counter)prop.GetValue(this))){
|
||||
counter.SetOwner(this);
|
||||
|
@@ -42,7 +42,7 @@ namespace TweetDuck.Core.Other.Analytics{
|
||||
{ "Clear Cache Threshold" , Exact(SysConfig.ClearCacheThreshold) },
|
||||
0,
|
||||
{ "Expand Links" , Bool(UserConfig.ExpandLinksOnHover) },
|
||||
{ "Switch Account Selectors" , Bool(UserConfig.SwitchAccountSelectors) },
|
||||
{ "Switch Account Selectors" , Bool(false) }, // TODO remove in next major update
|
||||
{ "Search In First Column" , Bool(UserConfig.OpenSearchInFirstColumn) },
|
||||
{ "Keep Like Follow Dialogs Open" , Bool(UserConfig.KeepLikeFollowDialogsOpen) },
|
||||
{ "Best Image Quality" , Bool(UserConfig.BestImageQuality) },
|
||||
|
@@ -58,11 +58,8 @@ namespace TweetDuck.Core.Other{
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName+" Guide";
|
||||
|
||||
if (owner != null){
|
||||
Size = new Size(owner.Size.Width*3/4, owner.Size.Height*3/4);
|
||||
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
|
||||
}
|
||||
Size = new Size(owner.Size.Width*3/4, owner.Size.Height*3/4);
|
||||
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
|
||||
|
||||
this.browser = new ChromiumWebBrowser(url){
|
||||
MenuHandler = new ContextMenuGuide(owner),
|
||||
|
10
Core/Other/FormSettings.Designer.cs
generated
10
Core/Other/FormSettings.Designer.cs
generated
@@ -33,7 +33,7 @@
|
||||
//
|
||||
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnClose.AutoSize = true;
|
||||
this.btnClose.Location = new System.Drawing.Point(449, 483);
|
||||
this.btnClose.Location = new System.Drawing.Point(449, 504);
|
||||
this.btnClose.Name = "btnClose";
|
||||
this.btnClose.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
this.btnClose.Size = new System.Drawing.Size(49, 23);
|
||||
@@ -52,7 +52,7 @@
|
||||
this.panelContents.Location = new System.Drawing.Point(135, 12);
|
||||
this.panelContents.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3);
|
||||
this.panelContents.Name = "panelContents";
|
||||
this.panelContents.Size = new System.Drawing.Size(363, 465);
|
||||
this.panelContents.Size = new System.Drawing.Size(363, 486);
|
||||
this.panelContents.TabIndex = 1;
|
||||
//
|
||||
// panelButtons
|
||||
@@ -63,14 +63,14 @@
|
||||
this.panelButtons.Location = new System.Drawing.Point(12, 12);
|
||||
this.panelButtons.Margin = new System.Windows.Forms.Padding(3, 3, 0, 3);
|
||||
this.panelButtons.Name = "panelButtons";
|
||||
this.panelButtons.Size = new System.Drawing.Size(124, 465);
|
||||
this.panelButtons.Size = new System.Drawing.Size(124, 486);
|
||||
this.panelButtons.TabIndex = 0;
|
||||
//
|
||||
// btnManageOptions
|
||||
//
|
||||
this.btnManageOptions.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.btnManageOptions.AutoSize = true;
|
||||
this.btnManageOptions.Location = new System.Drawing.Point(12, 483);
|
||||
this.btnManageOptions.Location = new System.Drawing.Point(12, 504);
|
||||
this.btnManageOptions.Name = "btnManageOptions";
|
||||
this.btnManageOptions.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
this.btnManageOptions.Size = new System.Drawing.Size(101, 23);
|
||||
@@ -83,7 +83,7 @@
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(510, 518);
|
||||
this.ClientSize = new System.Drawing.Size(510, 539);
|
||||
this.Controls.Add(this.btnManageOptions);
|
||||
this.Controls.Add(this.panelContents);
|
||||
this.Controls.Add(this.panelButtons);
|
||||
|
@@ -36,7 +36,7 @@ namespace TweetDuck.Core.Other{
|
||||
|
||||
this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1;
|
||||
|
||||
AddButton("General", () => new TabSettingsGeneral(this.browser, updates));
|
||||
AddButton("General", () => new TabSettingsGeneral(this.browser.ReloadColumns, updates));
|
||||
AddButton("Locales", () => new TabSettingsLocales());
|
||||
AddButton("System Tray", () => new TabSettingsTray());
|
||||
AddButton("Notifications", () => new TabSettingsNotifications(new FormNotificationExample(this.browser, this.plugins)));
|
||||
@@ -156,6 +156,10 @@ namespace TweetDuck.Core.Other{
|
||||
}
|
||||
|
||||
private void control_MouseLeave(object sender, EventArgs e){
|
||||
if (sender is ComboBox cb && cb.DroppedDown){
|
||||
return; // prevents comboboxes from closing when MouseLeave event triggers during opening animation
|
||||
}
|
||||
|
||||
panelContents.Focus();
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Core{
|
||||
namespace TweetDuck.Core.Other.Interfaces{
|
||||
interface ITweetDeckBrowser{
|
||||
bool IsTweetDeckWebsite { get; }
|
||||
|
||||
void RegisterBridge(string name, object obj);
|
||||
void OnFrameLoaded(Action<IFrame> callback);
|
||||
void ExecuteFunction(string name, params object[] args);
|
@@ -164,9 +164,6 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
ShouldReloadBrowser = true;
|
||||
}
|
||||
}
|
||||
else{
|
||||
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing TweetDuck profile.", true, importManager.LastException);
|
||||
}
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
@@ -191,12 +188,8 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
|
||||
Program.UserConfig.Save();
|
||||
Program.SystemConfig.Save();
|
||||
|
||||
ProfileManager manager = new ProfileManager(file, plugins);
|
||||
|
||||
if (!manager.Export(SelectedItems)){
|
||||
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, manager.LastException);
|
||||
}
|
||||
|
||||
new ProfileManager(file, plugins).Export(SelectedItems);
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
|
103
Core/Other/Settings/Dialogs/DialogSettingsSearchEngine.Designer.cs
generated
Normal file
103
Core/Other/Settings/Dialogs/DialogSettingsSearchEngine.Designer.cs
generated
Normal file
@@ -0,0 +1,103 @@
|
||||
namespace TweetDuck.Core.Other.Settings.Dialogs {
|
||||
partial class DialogSettingsSearchEngine {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.textBoxUrl = new System.Windows.Forms.TextBox();
|
||||
this.btnCancel = new System.Windows.Forms.Button();
|
||||
this.btnApply = new System.Windows.Forms.Button();
|
||||
this.labelInfo = new System.Windows.Forms.Label();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// textBoxUrl
|
||||
//
|
||||
this.textBoxUrl.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.textBoxUrl.Location = new System.Drawing.Point(12, 28);
|
||||
this.textBoxUrl.Name = "textBoxUrl";
|
||||
this.textBoxUrl.Size = new System.Drawing.Size(310, 20);
|
||||
this.textBoxUrl.TabIndex = 1;
|
||||
//
|
||||
// btnCancel
|
||||
//
|
||||
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnCancel.Location = new System.Drawing.Point(204, 56);
|
||||
this.btnCancel.Name = "btnCancel";
|
||||
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
this.btnCancel.Size = new System.Drawing.Size(56, 23);
|
||||
this.btnCancel.TabIndex = 3;
|
||||
this.btnCancel.Text = "Cancel";
|
||||
this.btnCancel.UseVisualStyleBackColor = true;
|
||||
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
|
||||
//
|
||||
// btnApply
|
||||
//
|
||||
this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnApply.Location = new System.Drawing.Point(266, 56);
|
||||
this.btnApply.Name = "btnApply";
|
||||
this.btnApply.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
this.btnApply.Size = new System.Drawing.Size(56, 23);
|
||||
this.btnApply.TabIndex = 2;
|
||||
this.btnApply.Text = "Apply";
|
||||
this.btnApply.UseVisualStyleBackColor = true;
|
||||
this.btnApply.Click += new System.EventHandler(this.btnApply_Click);
|
||||
//
|
||||
// labelInfo
|
||||
//
|
||||
this.labelInfo.AutoSize = true;
|
||||
this.labelInfo.Location = new System.Drawing.Point(12, 9);
|
||||
this.labelInfo.Margin = new System.Windows.Forms.Padding(3, 0, 3, 3);
|
||||
this.labelInfo.Name = "labelInfo";
|
||||
this.labelInfo.Size = new System.Drawing.Size(264, 13);
|
||||
this.labelInfo.TabIndex = 0;
|
||||
this.labelInfo.Text = "The search query will be added at the end of the URL.";
|
||||
//
|
||||
// DialogSettingsSearchEngine
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(334, 91);
|
||||
this.Controls.Add(this.labelInfo);
|
||||
this.Controls.Add(this.btnApply);
|
||||
this.Controls.Add(this.btnCancel);
|
||||
this.Controls.Add(this.textBoxUrl);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "DialogSettingsSearchEngine";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.TextBox textBoxUrl;
|
||||
private System.Windows.Forms.Button btnCancel;
|
||||
private System.Windows.Forms.Button btnApply;
|
||||
private System.Windows.Forms.Label labelInfo;
|
||||
}
|
||||
}
|
27
Core/Other/Settings/Dialogs/DialogSettingsSearchEngine.cs
Normal file
27
Core/Other/Settings/Dialogs/DialogSettingsSearchEngine.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
sealed partial class DialogSettingsSearchEngine : Form{
|
||||
public string Url => textBoxUrl.Text;
|
||||
|
||||
public DialogSettingsSearchEngine(){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName+" Options - Custom Search Engine";
|
||||
|
||||
textBoxUrl.Text = Program.UserConfig.SearchEngineUrl ?? "";
|
||||
textBoxUrl.Select(textBoxUrl.Text.Length, 0);
|
||||
}
|
||||
|
||||
private void btnApply_Click(object sender, EventArgs e){
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void btnCancel_Click(object sender, EventArgs e){
|
||||
DialogResult = DialogResult.Cancel;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
96
Core/Other/Settings/TabSettingsGeneral.Designer.cs
generated
96
Core/Other/Settings/TabSettingsGeneral.Designer.cs
generated
@@ -29,7 +29,6 @@
|
||||
this.checkUpdateNotifications = new System.Windows.Forms.CheckBox();
|
||||
this.btnCheckUpdates = new System.Windows.Forms.Button();
|
||||
this.labelZoomValue = new System.Windows.Forms.Label();
|
||||
this.checkSwitchAccountSelectors = new System.Windows.Forms.CheckBox();
|
||||
this.checkBestImageQuality = new System.Windows.Forms.CheckBox();
|
||||
this.checkOpenSearchInFirstColumn = new System.Windows.Forms.CheckBox();
|
||||
this.trackBarZoom = new System.Windows.Forms.TrackBar();
|
||||
@@ -45,6 +44,8 @@
|
||||
this.checkSmoothScrolling = new System.Windows.Forms.CheckBox();
|
||||
this.labelBrowserPath = new System.Windows.Forms.Label();
|
||||
this.comboBoxBrowserPath = new System.Windows.Forms.ComboBox();
|
||||
this.labelSearchEngine = new System.Windows.Forms.Label();
|
||||
this.comboBoxSearchEngine = new System.Windows.Forms.ComboBox();
|
||||
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit();
|
||||
this.panelZoom.SuspendLayout();
|
||||
this.flowPanel.SuspendLayout();
|
||||
@@ -64,21 +65,21 @@
|
||||
// checkUpdateNotifications
|
||||
//
|
||||
this.checkUpdateNotifications.AutoSize = true;
|
||||
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 386);
|
||||
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 415);
|
||||
this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
|
||||
this.checkUpdateNotifications.Name = "checkUpdateNotifications";
|
||||
this.checkUpdateNotifications.Size = new System.Drawing.Size(165, 17);
|
||||
this.checkUpdateNotifications.TabIndex = 14;
|
||||
this.checkUpdateNotifications.TabIndex = 15;
|
||||
this.checkUpdateNotifications.Text = "Check Updates Automatically";
|
||||
this.checkUpdateNotifications.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// btnCheckUpdates
|
||||
//
|
||||
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 409);
|
||||
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 438);
|
||||
this.btnCheckUpdates.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||
this.btnCheckUpdates.Name = "btnCheckUpdates";
|
||||
this.btnCheckUpdates.Size = new System.Drawing.Size(144, 23);
|
||||
this.btnCheckUpdates.TabIndex = 15;
|
||||
this.btnCheckUpdates.TabIndex = 16;
|
||||
this.btnCheckUpdates.Text = "Check Updates Now";
|
||||
this.btnCheckUpdates.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -93,36 +94,25 @@
|
||||
this.labelZoomValue.Text = "100%";
|
||||
this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
//
|
||||
// checkSwitchAccountSelectors
|
||||
//
|
||||
this.checkSwitchAccountSelectors.AutoSize = true;
|
||||
this.checkSwitchAccountSelectors.Location = new System.Drawing.Point(6, 49);
|
||||
this.checkSwitchAccountSelectors.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||
this.checkSwitchAccountSelectors.Name = "checkSwitchAccountSelectors";
|
||||
this.checkSwitchAccountSelectors.Size = new System.Drawing.Size(172, 17);
|
||||
this.checkSwitchAccountSelectors.TabIndex = 2;
|
||||
this.checkSwitchAccountSelectors.Text = "Shift Selects Multiple Accounts";
|
||||
this.checkSwitchAccountSelectors.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// checkBestImageQuality
|
||||
//
|
||||
this.checkBestImageQuality.AutoSize = true;
|
||||
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 118);
|
||||
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 95);
|
||||
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||
this.checkBestImageQuality.Name = "checkBestImageQuality";
|
||||
this.checkBestImageQuality.Size = new System.Drawing.Size(114, 17);
|
||||
this.checkBestImageQuality.TabIndex = 5;
|
||||
this.checkBestImageQuality.TabIndex = 4;
|
||||
this.checkBestImageQuality.Text = "Best Image Quality";
|
||||
this.checkBestImageQuality.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// checkOpenSearchInFirstColumn
|
||||
//
|
||||
this.checkOpenSearchInFirstColumn.AutoSize = true;
|
||||
this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 72);
|
||||
this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 49);
|
||||
this.checkOpenSearchInFirstColumn.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||
this.checkOpenSearchInFirstColumn.Name = "checkOpenSearchInFirstColumn";
|
||||
this.checkOpenSearchInFirstColumn.Size = new System.Drawing.Size(219, 17);
|
||||
this.checkOpenSearchInFirstColumn.TabIndex = 3;
|
||||
this.checkOpenSearchInFirstColumn.TabIndex = 2;
|
||||
this.checkOpenSearchInFirstColumn.Text = "Add Search Columns Before First Column";
|
||||
this.checkOpenSearchInFirstColumn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -144,11 +134,11 @@
|
||||
// labelZoom
|
||||
//
|
||||
this.labelZoom.AutoSize = true;
|
||||
this.labelZoom.Location = new System.Drawing.Point(3, 291);
|
||||
this.labelZoom.Location = new System.Drawing.Point(3, 320);
|
||||
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||
this.labelZoom.Name = "labelZoom";
|
||||
this.labelZoom.Size = new System.Drawing.Size(34, 13);
|
||||
this.labelZoom.TabIndex = 11;
|
||||
this.labelZoom.TabIndex = 12;
|
||||
this.labelZoom.Text = "Zoom";
|
||||
//
|
||||
// zoomUpdateTimer
|
||||
@@ -172,20 +162,20 @@
|
||||
this.panelZoom.Anchor = System.Windows.Forms.AnchorStyles.Top;
|
||||
this.panelZoom.Controls.Add(this.trackBarZoom);
|
||||
this.panelZoom.Controls.Add(this.labelZoomValue);
|
||||
this.panelZoom.Location = new System.Drawing.Point(0, 304);
|
||||
this.panelZoom.Location = new System.Drawing.Point(0, 333);
|
||||
this.panelZoom.Margin = new System.Windows.Forms.Padding(0);
|
||||
this.panelZoom.Name = "panelZoom";
|
||||
this.panelZoom.Size = new System.Drawing.Size(322, 36);
|
||||
this.panelZoom.TabIndex = 12;
|
||||
this.panelZoom.TabIndex = 13;
|
||||
//
|
||||
// checkAnimatedAvatars
|
||||
//
|
||||
this.checkAnimatedAvatars.AutoSize = true;
|
||||
this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 141);
|
||||
this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 118);
|
||||
this.checkAnimatedAvatars.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||
this.checkAnimatedAvatars.Name = "checkAnimatedAvatars";
|
||||
this.checkAnimatedAvatars.Size = new System.Drawing.Size(145, 17);
|
||||
this.checkAnimatedAvatars.TabIndex = 6;
|
||||
this.checkAnimatedAvatars.TabIndex = 5;
|
||||
this.checkAnimatedAvatars.Text = "Enable Animated Avatars";
|
||||
this.checkAnimatedAvatars.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -193,11 +183,11 @@
|
||||
//
|
||||
this.labelUpdates.AutoSize = true;
|
||||
this.labelUpdates.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.labelUpdates.Location = new System.Drawing.Point(0, 360);
|
||||
this.labelUpdates.Location = new System.Drawing.Point(0, 389);
|
||||
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
|
||||
this.labelUpdates.Name = "labelUpdates";
|
||||
this.labelUpdates.Size = new System.Drawing.Size(70, 20);
|
||||
this.labelUpdates.TabIndex = 13;
|
||||
this.labelUpdates.TabIndex = 14;
|
||||
this.labelUpdates.Text = "Updates";
|
||||
//
|
||||
// flowPanel
|
||||
@@ -207,7 +197,6 @@
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.flowPanel.Controls.Add(this.labelUI);
|
||||
this.flowPanel.Controls.Add(this.checkExpandLinks);
|
||||
this.flowPanel.Controls.Add(this.checkSwitchAccountSelectors);
|
||||
this.flowPanel.Controls.Add(this.checkOpenSearchInFirstColumn);
|
||||
this.flowPanel.Controls.Add(this.checkKeepLikeFollowDialogsOpen);
|
||||
this.flowPanel.Controls.Add(this.checkBestImageQuality);
|
||||
@@ -216,6 +205,8 @@
|
||||
this.flowPanel.Controls.Add(this.checkSmoothScrolling);
|
||||
this.flowPanel.Controls.Add(this.labelBrowserPath);
|
||||
this.flowPanel.Controls.Add(this.comboBoxBrowserPath);
|
||||
this.flowPanel.Controls.Add(this.labelSearchEngine);
|
||||
this.flowPanel.Controls.Add(this.comboBoxSearchEngine);
|
||||
this.flowPanel.Controls.Add(this.labelZoom);
|
||||
this.flowPanel.Controls.Add(this.panelZoom);
|
||||
this.flowPanel.Controls.Add(this.labelUpdates);
|
||||
@@ -224,18 +215,18 @@
|
||||
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
|
||||
this.flowPanel.Location = new System.Drawing.Point(9, 9);
|
||||
this.flowPanel.Name = "flowPanel";
|
||||
this.flowPanel.Size = new System.Drawing.Size(322, 438);
|
||||
this.flowPanel.Size = new System.Drawing.Size(322, 462);
|
||||
this.flowPanel.TabIndex = 0;
|
||||
this.flowPanel.WrapContents = false;
|
||||
//
|
||||
// checkKeepLikeFollowDialogsOpen
|
||||
//
|
||||
this.checkKeepLikeFollowDialogsOpen.AutoSize = true;
|
||||
this.checkKeepLikeFollowDialogsOpen.Location = new System.Drawing.Point(6, 95);
|
||||
this.checkKeepLikeFollowDialogsOpen.Location = new System.Drawing.Point(6, 72);
|
||||
this.checkKeepLikeFollowDialogsOpen.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
|
||||
this.checkKeepLikeFollowDialogsOpen.Name = "checkKeepLikeFollowDialogsOpen";
|
||||
this.checkKeepLikeFollowDialogsOpen.Size = new System.Drawing.Size(176, 17);
|
||||
this.checkKeepLikeFollowDialogsOpen.TabIndex = 4;
|
||||
this.checkKeepLikeFollowDialogsOpen.TabIndex = 3;
|
||||
this.checkKeepLikeFollowDialogsOpen.Text = "Keep Like/Follow Dialogs Open";
|
||||
this.checkKeepLikeFollowDialogsOpen.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@@ -243,43 +234,63 @@
|
||||
//
|
||||
this.labelBrowserSettings.AutoSize = true;
|
||||
this.labelBrowserSettings.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.labelBrowserSettings.Location = new System.Drawing.Point(0, 181);
|
||||
this.labelBrowserSettings.Location = new System.Drawing.Point(0, 158);
|
||||
this.labelBrowserSettings.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
|
||||
this.labelBrowserSettings.Name = "labelBrowserSettings";
|
||||
this.labelBrowserSettings.Size = new System.Drawing.Size(130, 20);
|
||||
this.labelBrowserSettings.TabIndex = 7;
|
||||
this.labelBrowserSettings.TabIndex = 6;
|
||||
this.labelBrowserSettings.Text = "Browser Settings";
|
||||
//
|
||||
// checkSmoothScrolling
|
||||
//
|
||||
this.checkSmoothScrolling.AutoSize = true;
|
||||
this.checkSmoothScrolling.Location = new System.Drawing.Point(6, 207);
|
||||
this.checkSmoothScrolling.Location = new System.Drawing.Point(6, 184);
|
||||
this.checkSmoothScrolling.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
|
||||
this.checkSmoothScrolling.Name = "checkSmoothScrolling";
|
||||
this.checkSmoothScrolling.Size = new System.Drawing.Size(105, 17);
|
||||
this.checkSmoothScrolling.TabIndex = 8;
|
||||
this.checkSmoothScrolling.TabIndex = 7;
|
||||
this.checkSmoothScrolling.Text = "Smooth Scrolling";
|
||||
this.checkSmoothScrolling.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// labelBrowserPath
|
||||
//
|
||||
this.labelBrowserPath.AutoSize = true;
|
||||
this.labelBrowserPath.Location = new System.Drawing.Point(3, 239);
|
||||
this.labelBrowserPath.Location = new System.Drawing.Point(3, 216);
|
||||
this.labelBrowserPath.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||
this.labelBrowserPath.Name = "labelBrowserPath";
|
||||
this.labelBrowserPath.Size = new System.Drawing.Size(95, 13);
|
||||
this.labelBrowserPath.TabIndex = 9;
|
||||
this.labelBrowserPath.TabIndex = 8;
|
||||
this.labelBrowserPath.Text = "Open Links With...";
|
||||
//
|
||||
// comboBoxBrowserPath
|
||||
//
|
||||
this.comboBoxBrowserPath.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.comboBoxBrowserPath.FormattingEnabled = true;
|
||||
this.comboBoxBrowserPath.Location = new System.Drawing.Point(5, 255);
|
||||
this.comboBoxBrowserPath.Location = new System.Drawing.Point(5, 232);
|
||||
this.comboBoxBrowserPath.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||
this.comboBoxBrowserPath.Name = "comboBoxBrowserPath";
|
||||
this.comboBoxBrowserPath.Size = new System.Drawing.Size(173, 21);
|
||||
this.comboBoxBrowserPath.TabIndex = 10;
|
||||
this.comboBoxBrowserPath.TabIndex = 9;
|
||||
//
|
||||
// labelSearchEngine
|
||||
//
|
||||
this.labelSearchEngine.AutoSize = true;
|
||||
this.labelSearchEngine.Location = new System.Drawing.Point(3, 268);
|
||||
this.labelSearchEngine.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||
this.labelSearchEngine.Name = "labelSearchEngine";
|
||||
this.labelSearchEngine.Size = new System.Drawing.Size(77, 13);
|
||||
this.labelSearchEngine.TabIndex = 10;
|
||||
this.labelSearchEngine.Text = "Search Engine";
|
||||
//
|
||||
// comboBoxSearchEngine
|
||||
//
|
||||
this.comboBoxSearchEngine.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.comboBoxSearchEngine.FormattingEnabled = true;
|
||||
this.comboBoxSearchEngine.Location = new System.Drawing.Point(5, 284);
|
||||
this.comboBoxSearchEngine.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||
this.comboBoxSearchEngine.Name = "comboBoxSearchEngine";
|
||||
this.comboBoxSearchEngine.Size = new System.Drawing.Size(173, 21);
|
||||
this.comboBoxSearchEngine.TabIndex = 11;
|
||||
//
|
||||
// TabSettingsGeneral
|
||||
//
|
||||
@@ -287,7 +298,7 @@
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.Controls.Add(this.flowPanel);
|
||||
this.Name = "TabSettingsGeneral";
|
||||
this.Size = new System.Drawing.Size(340, 456);
|
||||
this.Size = new System.Drawing.Size(340, 480);
|
||||
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit();
|
||||
this.panelZoom.ResumeLayout(false);
|
||||
this.flowPanel.ResumeLayout(false);
|
||||
@@ -306,7 +317,6 @@
|
||||
private System.Windows.Forms.Label labelZoomValue;
|
||||
private System.Windows.Forms.TrackBar trackBarZoom;
|
||||
private System.Windows.Forms.Timer zoomUpdateTimer;
|
||||
private System.Windows.Forms.CheckBox checkSwitchAccountSelectors;
|
||||
private System.Windows.Forms.Label labelUI;
|
||||
private System.Windows.Forms.Panel panelZoom;
|
||||
private System.Windows.Forms.Label labelUpdates;
|
||||
@@ -319,5 +329,7 @@
|
||||
private System.Windows.Forms.ComboBox comboBoxBrowserPath;
|
||||
private System.Windows.Forms.Label labelBrowserSettings;
|
||||
private System.Windows.Forms.CheckBox checkSmoothScrolling;
|
||||
private System.Windows.Forms.Label labelSearchEngine;
|
||||
private System.Windows.Forms.ComboBox comboBoxSearchEngine;
|
||||
}
|
||||
}
|
||||
|
@@ -4,29 +4,34 @@ using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling.General;
|
||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Updates;
|
||||
using TweetDuck.Updates.Events;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings{
|
||||
sealed partial class TabSettingsGeneral : BaseTabSettings{
|
||||
private readonly FormBrowser browser;
|
||||
private readonly Action reloadColumns;
|
||||
|
||||
private readonly UpdateHandler updates;
|
||||
private int updateCheckEventId = -1;
|
||||
|
||||
private readonly int browserListIndexDefault;
|
||||
private readonly int browserListIndexCustom;
|
||||
|
||||
public TabSettingsGeneral(FormBrowser browser, UpdateHandler updates){
|
||||
private readonly int searchEngineIndexDefault;
|
||||
private readonly int searchEngineIndexCustom;
|
||||
|
||||
public TabSettingsGeneral(Action reloadColumns, UpdateHandler updates){
|
||||
InitializeComponent();
|
||||
|
||||
this.browser = browser;
|
||||
|
||||
this.reloadColumns = reloadColumns;
|
||||
|
||||
this.updates = updates;
|
||||
this.updates.CheckFinished += updates_CheckFinished;
|
||||
Disposed += (sender, args) => this.updates.CheckFinished -= updates_CheckFinished;
|
||||
|
||||
toolTip.SetToolTip(checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead.");
|
||||
toolTip.SetToolTip(checkSwitchAccountSelectors, "When (re)tweeting, click to select a single account or hold Shift to\r\nselect multiple accounts, instead of TweetDeck\'s default behavior.");
|
||||
toolTip.SetToolTip(checkOpenSearchInFirstColumn, "By default, TweetDeck adds Search columns at the end.\r\nThis option makes them appear before the first column instead.");
|
||||
toolTip.SetToolTip(checkKeepLikeFollowDialogsOpen, "Allows liking and following from multiple accounts at once,\r\ninstead of automatically closing the dialog after taking an action.");
|
||||
toolTip.SetToolTip(checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
|
||||
@@ -41,7 +46,6 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
toolTip.SetToolTip(btnCheckUpdates, "Forces an update check, even for updates that had been dismissed.");
|
||||
|
||||
checkExpandLinks.Checked = Config.ExpandLinksOnHover;
|
||||
checkSwitchAccountSelectors.Checked = Config.SwitchAccountSelectors;
|
||||
checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn;
|
||||
checkKeepLikeFollowDialogsOpen.Checked = Config.KeepLikeFollowDialogsOpen;
|
||||
checkBestImageQuality.Checked = Config.BestImageQuality;
|
||||
@@ -57,6 +61,14 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
browserListIndexCustom = comboBoxBrowserPath.Items.Add("(custom program...)");
|
||||
UpdateBrowserPathSelection();
|
||||
|
||||
comboBoxSearchEngine.Items.Add(new SearchEngine("DuckDuckGo", "https://duckduckgo.com/?q="));
|
||||
comboBoxSearchEngine.Items.Add(new SearchEngine("Google", "https://www.google.com/search?q="));
|
||||
comboBoxSearchEngine.Items.Add(new SearchEngine("Bing", "https://www.bing.com/search?q="));
|
||||
comboBoxSearchEngine.Items.Add(new SearchEngine("Yahoo", "https://search.yahoo.com/search?p="));
|
||||
searchEngineIndexDefault = comboBoxSearchEngine.Items.Add("(no engine set)");
|
||||
searchEngineIndexCustom = comboBoxSearchEngine.Items.Add("(custom url...)");
|
||||
UpdateSearchEngineSelection();
|
||||
|
||||
trackBarZoom.SetValueSafe(Config.ZoomLevel);
|
||||
labelZoomValue.Text = trackBarZoom.Value+"%";
|
||||
|
||||
@@ -65,7 +77,6 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
|
||||
public override void OnReady(){
|
||||
checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
|
||||
checkSwitchAccountSelectors.CheckedChanged += checkSwitchAccountSelectors_CheckedChanged;
|
||||
checkOpenSearchInFirstColumn.CheckedChanged += checkOpenSearchInFirstColumn_CheckedChanged;
|
||||
checkKeepLikeFollowDialogsOpen.CheckedChanged += checkKeepLikeFollowDialogsOpen_CheckedChanged;
|
||||
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
|
||||
@@ -73,6 +84,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
|
||||
checkSmoothScrolling.CheckedChanged += checkSmoothScrolling_CheckedChanged;
|
||||
comboBoxBrowserPath.SelectedIndexChanged += comboBoxBrowserPath_SelectedIndexChanged;
|
||||
comboBoxSearchEngine.SelectedIndexChanged += comboBoxSearchEngine_SelectedIndexChanged;
|
||||
trackBarZoom.ValueChanged += trackBarZoom_ValueChanged;
|
||||
|
||||
checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged;
|
||||
@@ -87,10 +99,6 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
Config.ExpandLinksOnHover = checkExpandLinks.Checked;
|
||||
}
|
||||
|
||||
private void checkSwitchAccountSelectors_CheckedChanged(object sender, EventArgs e){
|
||||
Config.SwitchAccountSelectors = checkSwitchAccountSelectors.Checked;
|
||||
}
|
||||
|
||||
private void checkOpenSearchInFirstColumn_CheckedChanged(object sender, EventArgs e){
|
||||
Config.OpenSearchInFirstColumn = checkOpenSearchInFirstColumn.Checked;
|
||||
}
|
||||
@@ -105,7 +113,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
|
||||
private void checkAnimatedAvatars_CheckedChanged(object sender, EventArgs e){
|
||||
Config.EnableAnimatedImages = checkAnimatedAvatars.Checked;
|
||||
BrowserProcessHandler.UpdatePrefs().ContinueWith(task => browser.ReloadColumns());
|
||||
BrowserProcessHandler.UpdatePrefs().ContinueWith(task => reloadColumns());
|
||||
}
|
||||
|
||||
private void checkSmoothScrolling_CheckedChanged(object sender, EventArgs e){
|
||||
@@ -141,18 +149,50 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
Config.BrowserPath = dialog.FileName;
|
||||
}
|
||||
else{
|
||||
comboBoxBrowserPath.SelectedIndexChanged -= comboBoxBrowserPath_SelectedIndexChanged;
|
||||
UpdateBrowserPathSelection();
|
||||
comboBoxBrowserPath.SelectedIndexChanged += comboBoxBrowserPath_SelectedIndexChanged;
|
||||
}
|
||||
}
|
||||
|
||||
comboBoxBrowserPath.SelectedIndexChanged -= comboBoxBrowserPath_SelectedIndexChanged;
|
||||
UpdateBrowserPathSelection();
|
||||
comboBoxBrowserPath.SelectedIndexChanged += comboBoxBrowserPath_SelectedIndexChanged;
|
||||
}
|
||||
else{
|
||||
Config.BrowserPath = (comboBoxBrowserPath.SelectedItem as WindowsUtils.Browser)?.Path; // default browser item is a string and casts to null
|
||||
}
|
||||
}
|
||||
|
||||
private void comboBoxSearchEngine_SelectedIndexChanged(object sender, EventArgs e){
|
||||
if (comboBoxSearchEngine.SelectedIndex == searchEngineIndexCustom){
|
||||
using(DialogSettingsSearchEngine dialog = new DialogSettingsSearchEngine()){
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
Config.SearchEngineUrl = dialog.Url.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
comboBoxSearchEngine.SelectedIndexChanged -= comboBoxSearchEngine_SelectedIndexChanged;
|
||||
UpdateSearchEngineSelection();
|
||||
comboBoxSearchEngine.SelectedIndexChanged += comboBoxSearchEngine_SelectedIndexChanged;
|
||||
}
|
||||
else{
|
||||
Config.SearchEngineUrl = (comboBoxSearchEngine.SelectedItem as SearchEngine)?.Url; // default search engine item is a string and casts to null
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSearchEngineSelection(){
|
||||
if (string.IsNullOrEmpty(Config.SearchEngineUrl)){
|
||||
comboBoxSearchEngine.SelectedIndex = searchEngineIndexDefault;
|
||||
}
|
||||
else{
|
||||
SearchEngine engineInfo = comboBoxSearchEngine.Items.OfType<SearchEngine>().FirstOrDefault(engine => engine.Url == Config.SearchEngineUrl);
|
||||
|
||||
if (engineInfo == null){
|
||||
comboBoxSearchEngine.SelectedIndex = searchEngineIndexCustom;
|
||||
}
|
||||
else{
|
||||
comboBoxSearchEngine.SelectedItem = engineInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void trackBarZoom_ValueChanged(object sender, EventArgs e){
|
||||
if (trackBarZoom.AlignValueToTick()){
|
||||
zoomUpdateTimer.Stop();
|
||||
@@ -170,9 +210,14 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
|
||||
btnCheckUpdates.Enabled = false;
|
||||
updateCheckEventId = updates.Check(true);
|
||||
|
||||
if (updateCheckEventId == UpdateHandler.CheckCodeNotOnTweetDeck){
|
||||
FormMessage.Error("Update Check", "Updates can only be checked once TweetDeck is fully loaded.", FormMessage.OK);
|
||||
btnCheckUpdates.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void updates_CheckFinished(object sender, UpdateEventArgs e){
|
||||
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
|
||||
this.InvokeAsyncSafe(() => {
|
||||
if (e.EventId == updateCheckEventId){
|
||||
btnCheckUpdates.Enabled = true;
|
||||
@@ -188,5 +233,19 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
Config.ZoomLevel = trackBarZoom.Value;
|
||||
zoomUpdateTimer.Stop();
|
||||
}
|
||||
|
||||
private sealed class SearchEngine{
|
||||
private string Name { get; }
|
||||
public string Url { get; }
|
||||
|
||||
public SearchEngine(string name, string url){
|
||||
Name = name;
|
||||
Url = url;
|
||||
}
|
||||
|
||||
public override int GetHashCode() => Name.GetHashCode();
|
||||
public override bool Equals(object obj) => obj is SearchEngine other && Name == other.Name;
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -42,12 +42,12 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
|
||||
private void comboBoxSpellCheckLanguage_SelectedValueChanged(object sender, EventArgs e){
|
||||
Config.SpellCheckLanguage = (comboBoxSpellCheckLanguage.SelectedItem as LocaleUtils.Item)?.Code;
|
||||
Config.SpellCheckLanguage = (comboBoxSpellCheckLanguage.SelectedItem as LocaleUtils.Item)?.Code ?? "en-US";
|
||||
PromptRestart();
|
||||
}
|
||||
|
||||
private void comboBoxTranslationTarget_SelectedValueChanged(object sender, EventArgs e){
|
||||
Config.TranslationTarget = (comboBoxTranslationTarget.SelectedItem as LocaleUtils.Item)?.Code;
|
||||
Config.TranslationTarget = (comboBoxTranslationTarget.SelectedItem as LocaleUtils.Item)?.Code ?? "en";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Handling.General;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Other.Interfaces;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Resources;
|
||||
|
||||
@@ -22,6 +23,10 @@ namespace TweetDuck.Core{
|
||||
|
||||
public bool IsTweetDeckWebsite{
|
||||
get{
|
||||
if (!Ready){
|
||||
return false;
|
||||
}
|
||||
|
||||
using(IFrame frame = browser.GetBrowser().MainFrame){
|
||||
return TwitterUtils.IsTweetDeckWebsite(frame);
|
||||
}
|
||||
@@ -43,10 +48,6 @@ namespace TweetDuck.Core{
|
||||
RequestHandler = new RequestHandlerBrowser()
|
||||
};
|
||||
|
||||
#if DEBUG
|
||||
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
|
||||
#endif
|
||||
|
||||
this.browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
this.browser.FrameLoadStart += browser_FrameLoadStart;
|
||||
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
|
||||
@@ -122,14 +123,18 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
|
||||
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
|
||||
if (e.Frame.IsMain){
|
||||
IFrame frame = e.Frame;
|
||||
|
||||
if (frame.IsMain){
|
||||
if (Program.UserConfig.ZoomLevel != 100){
|
||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
|
||||
}
|
||||
|
||||
if (TwitterUtils.IsTwitterWebsite(e.Frame)){
|
||||
ScriptLoader.ExecuteFile(e.Frame, "twitter.js");
|
||||
if (TwitterUtils.IsTwitterWebsite(frame)){
|
||||
ScriptLoader.ExecuteFile(frame, "twitter.js", browser);
|
||||
}
|
||||
|
||||
frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,11 +142,9 @@ namespace TweetDuck.Core{
|
||||
IFrame frame = e.Frame;
|
||||
|
||||
if (frame.IsMain && TwitterUtils.IsTweetDeckWebsite(frame)){
|
||||
frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride);
|
||||
|
||||
UpdateProperties();
|
||||
TweetDeckBridge.RestoreSessionData(frame);
|
||||
ScriptLoader.ExecuteFile(frame, "code.js");
|
||||
ScriptLoader.ExecuteFile(frame, "code.js", browser);
|
||||
InjectBrowserCSS();
|
||||
ReinjectCustomCSS(Program.UserConfig.CustomBrowserCSS);
|
||||
UserConfig_SoundNotificationInfoChanged(null, EventArgs.Empty);
|
||||
@@ -149,7 +152,7 @@ namespace TweetDuck.Core{
|
||||
TweetDeckBridge.ResetStaticProperties();
|
||||
|
||||
if (Program.UserConfig.FirstRun){
|
||||
ScriptLoader.ExecuteFile(frame, "introduction.js");
|
||||
ScriptLoader.ExecuteFile(frame, "introduction.js", browser);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,7 +212,7 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
|
||||
public void InjectBrowserCSS(){
|
||||
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css").TrimEnd());
|
||||
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css", false, browser)?.TrimEnd() ?? string.Empty);
|
||||
}
|
||||
|
||||
public void ReinjectCustomCSS(string css){
|
||||
@@ -224,6 +227,10 @@ namespace TweetDuck.Core{
|
||||
browser.ExecuteScriptAsync("TDGF_showTweetDetail", columnId, chirpId, fallbackUrl);
|
||||
}
|
||||
|
||||
public void AddSearchColumn(string query){
|
||||
browser.ExecuteScriptAsync("TDGF_performSearch", query);
|
||||
}
|
||||
|
||||
public void TriggerTweetScreenshot(){
|
||||
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
|
||||
}
|
||||
|
@@ -127,6 +127,33 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
}
|
||||
|
||||
public static void OpenExternalSearch(string query){
|
||||
if (string.IsNullOrWhiteSpace(query))return;
|
||||
|
||||
string searchUrl = Program.UserConfig.SearchEngineUrl;
|
||||
|
||||
if (string.IsNullOrEmpty(searchUrl)){
|
||||
if (FormMessage.Question("Search Options", "You have not configured a default search engine yet, would you like to do it now?", FormMessage.Yes, FormMessage.No)){
|
||||
bool wereSettingsOpen = FormManager.TryFind<FormSettings>() != null;
|
||||
|
||||
FormManager.TryFind<FormBrowser>()?.OpenSettings();
|
||||
if (wereSettingsOpen)return;
|
||||
|
||||
FormSettings settings = FormManager.TryFind<FormSettings>();
|
||||
if (settings == null)return;
|
||||
|
||||
settings.FormClosed += (sender, args) => {
|
||||
if (args.CloseReason == CloseReason.UserClosing && Program.UserConfig.SearchEngineUrl != searchUrl){
|
||||
OpenExternalSearch(query);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
else{
|
||||
OpenExternalBrowser(searchUrl+Uri.EscapeUriString(query));
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetFileNameFromUrl(string url){
|
||||
string file = Path.GetFileName(new Uri(url).AbsolutePath);
|
||||
return string.IsNullOrEmpty(file) ? null : file;
|
||||
@@ -137,6 +164,8 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
|
||||
public static WebClient CreateWebClient(){
|
||||
WindowsUtils.EnsureTLS12();
|
||||
|
||||
WebClient client = new WebClient{ Proxy = null };
|
||||
client.Headers[HttpRequestHeader.UserAgent] = HeaderUserAgent;
|
||||
return client;
|
||||
@@ -172,11 +201,5 @@ namespace TweetDuck.Core.Utils{
|
||||
public static void SetZoomLevel(IBrowser browser, int percentage){
|
||||
browser.GetHost().SetZoomLevel(Math.Log(percentage/100.0, 1.2));
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public static void HandleConsoleMessage(object sender, ConsoleMessageEventArgs e){
|
||||
Debug.WriteLine("[Console] {0} ({1}:{2})", e.Message, e.Source, e.Line);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@@ -13,11 +13,11 @@ namespace TweetDuck.Core.Utils{
|
||||
public const string TweetDeckURL = "https://tweetdeck.twitter.com";
|
||||
|
||||
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
|
||||
public const string BackgroundColorOverride = "let e=document.createElement('style');document.head.appendChild(e);e.innerHTML='body,body::before{background:#1c6399!important}'";
|
||||
public const string BackgroundColorOverride = "setTimeout(function f(){let h=document.head;if(!h){setTimeout(f,5);return;}let e=document.createElement('style');e.innerHTML='body,body::before{background:#1c6399!important}';h.appendChild(e);},1)";
|
||||
|
||||
public static readonly ResourceLink LoadingSpinner = new ResourceLink("https://ton.twimg.com/tduck/spinner", ResourceHandler.FromByteArray(Properties.Resources.spinner, "image/apng"));
|
||||
|
||||
private static readonly Lazy<Regex> RegexAccountLazy = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/(?!signup$|tos$|privacy$)([^/]+)/?$", RegexOptions.Compiled), false);
|
||||
private static readonly Lazy<Regex> RegexAccountLazy = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/(?!signup$|tos$|privacy$|search$|search-)([^/?]+)/?$", RegexOptions.Compiled), false);
|
||||
public static Regex RegexAccount => RegexAccountLazy.Value;
|
||||
|
||||
public static readonly string[] DictionaryWords = {
|
||||
@@ -41,8 +41,8 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
|
||||
private static string ExtractMediaBaseLink(string url){
|
||||
int dot = url.LastIndexOf('/');
|
||||
return dot == -1 ? url : StringUtils.ExtractBefore(url, ':', dot);
|
||||
int slash = url.LastIndexOf('/');
|
||||
return slash == -1 ? url : StringUtils.ExtractBefore(url, ':', slash);
|
||||
}
|
||||
|
||||
public static string GetMediaLink(string url, ImageQuality quality){
|
||||
@@ -91,7 +91,7 @@ namespace TweetDuck.Core.Utils{
|
||||
AutoUpgradeEnabled = true,
|
||||
OverwritePrompt = urls.Length == 1,
|
||||
Title = "Save Image",
|
||||
FileName = $"{string.Join(" ", fileNameParts.Where(part => part.Length > 0))}{ext}",
|
||||
FileName = $"{string.Join(" ", fileNameParts.Where(part => !string.IsNullOrEmpty(part)))}{ext}",
|
||||
Filter = (urls.Length == 1 ? "Image" : "Images")+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
|
||||
}){
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
|
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
@@ -16,6 +17,8 @@ namespace TweetDuck.Core.Utils{
|
||||
|
||||
public static int CurrentProcessID { get; }
|
||||
public static bool ShouldAvoidToolWindow { get; }
|
||||
|
||||
private static bool HasMicrosoftBeenBroughtTo2008Yet;
|
||||
|
||||
static WindowsUtils(){
|
||||
using(Process me = Process.GetCurrentProcess()){
|
||||
@@ -25,6 +28,14 @@ namespace TweetDuck.Core.Utils{
|
||||
Version ver = Environment.OSVersion.Version;
|
||||
ShouldAvoidToolWindow = ver.Major == 6 && ver.Minor == 2; // windows 8/10
|
||||
}
|
||||
|
||||
public static void EnsureTLS12(){
|
||||
if (!HasMicrosoftBeenBroughtTo2008Yet){
|
||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
||||
ServicePointManager.SecurityProtocol &= ~(SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11);
|
||||
HasMicrosoftBeenBroughtTo2008Yet = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateDirectoryForFile(string file){
|
||||
string dir = Path.GetDirectoryName(file);
|
||||
|
@@ -62,8 +62,7 @@ namespace TweetDuck.Data{
|
||||
}
|
||||
|
||||
public bool Contains(K1 outerKey, K2 innerKey){
|
||||
Dictionary<K2, V> innerDict;
|
||||
return dict.TryGetValue(outerKey, out innerDict) && innerDict.ContainsKey(innerKey);
|
||||
return dict.TryGetValue(outerKey, out Dictionary<K2, V> innerDict) && innerDict.ContainsKey(innerKey);
|
||||
}
|
||||
|
||||
public int Count(){
|
||||
|
@@ -36,7 +36,7 @@ namespace TweetDuck.Plugins.Controls{
|
||||
labelDescription.Visible = false;
|
||||
}
|
||||
|
||||
panelDescription_Resize(panelDescription, null);
|
||||
panelDescription_Resize(panelDescription, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void panelDescription_Resize(object sender, EventArgs e){
|
||||
|
@@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace TweetDuck.Plugins.Enums{
|
||||
[Flags]
|
||||
@@ -45,5 +48,50 @@ namespace TweetDuck.Plugins.Enums{
|
||||
default: return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<PluginEnvironment, T> Map<T>(T forNone, T forBrowser, T forNotification){
|
||||
return new PluginEnvironmentDictionary<T>(forNone, forBrowser, forNotification);
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberHidesStaticFromOuterClass")]
|
||||
private sealed class PluginEnvironmentDictionary<T> : IReadOnlyDictionary<PluginEnvironment, T>{
|
||||
private const int TotalKeys = 3;
|
||||
|
||||
public IEnumerable<PluginEnvironment> Keys => Enum.GetValues(typeof(PluginEnvironment)).Cast<PluginEnvironment>();
|
||||
public IEnumerable<T> Values => data;
|
||||
public int Count => TotalKeys;
|
||||
|
||||
public T this[PluginEnvironment key] => data[(int)key];
|
||||
|
||||
private readonly T[] data;
|
||||
|
||||
public PluginEnvironmentDictionary(T forNone, T forBrowser, T forNotification){
|
||||
this.data = new T[TotalKeys];
|
||||
this.data[(int)PluginEnvironment.None] = forNone;
|
||||
this.data[(int)PluginEnvironment.Browser] = forBrowser;
|
||||
this.data[(int)PluginEnvironment.Notification] = forNotification;
|
||||
}
|
||||
|
||||
public bool ContainsKey(PluginEnvironment key){
|
||||
return key >= 0 && (int)key < TotalKeys;
|
||||
}
|
||||
|
||||
public bool TryGetValue(PluginEnvironment key, out T value){
|
||||
if (ContainsKey(key)){
|
||||
value = this[key];
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<PluginEnvironment, T>> GetEnumerator(){
|
||||
return Keys.Select(key => new KeyValuePair<PluginEnvironment, T>(key, this[key])).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,28 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
sealed class Plugin{
|
||||
private const string VersionWildcard = "*";
|
||||
private static readonly Version AppVersion = new Version(Program.VersionTag);
|
||||
|
||||
public string Identifier { get; }
|
||||
public PluginGroup Group { get; }
|
||||
public PluginEnvironment Environments { get; }
|
||||
|
||||
public string Name => metadata["NAME"];
|
||||
public string Description => metadata["DESCRIPTION"];
|
||||
public string Author => metadata["AUTHOR"];
|
||||
public string Version => metadata["VERSION"];
|
||||
public string Website => metadata["WEBSITE"];
|
||||
public string ConfigFile => metadata["CONFIGFILE"];
|
||||
public string ConfigDefault => metadata["CONFIGDEFAULT"];
|
||||
public string RequiredVersion => metadata["REQUIRES"];
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
public string Author { get; }
|
||||
public string Version { get; }
|
||||
public string Website { get; }
|
||||
public string ConfigFile { get; }
|
||||
public string ConfigDefault { get; }
|
||||
public Version RequiredVersion { get; }
|
||||
|
||||
public bool CanRun { get; private set; }
|
||||
public bool CanRun { get; }
|
||||
|
||||
public bool HasConfig{
|
||||
get => ConfigFile.Length > 0 && GetFullPathIfSafe(PluginFolder.Data, ConfigFile).Length > 0;
|
||||
@@ -42,41 +40,25 @@ namespace TweetDuck.Plugins{
|
||||
|
||||
private readonly string pathRoot;
|
||||
private readonly string pathData;
|
||||
private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(8){
|
||||
{ "NAME", "" },
|
||||
{ "DESCRIPTION", "" },
|
||||
{ "AUTHOR", "(anonymous)" },
|
||||
{ "VERSION", "(unknown)" },
|
||||
{ "WEBSITE", "" },
|
||||
{ "CONFIGFILE", "" },
|
||||
{ "CONFIGDEFAULT", "" },
|
||||
{ "REQUIRES", VersionWildcard }
|
||||
};
|
||||
|
||||
private Plugin(string path, string name, PluginGroup group, PluginEnvironment environments){
|
||||
this.pathRoot = path;
|
||||
this.pathData = Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name);
|
||||
private Plugin(PluginGroup group, string identifier, string pathRoot, string pathData, Builder builder){
|
||||
this.pathRoot = pathRoot;
|
||||
this.pathData = pathData;
|
||||
|
||||
this.Identifier = group.GetIdentifierPrefix()+name;
|
||||
this.Group = group;
|
||||
this.Environments = environments;
|
||||
}
|
||||
this.Identifier = identifier;
|
||||
this.Environments = builder.Environments;
|
||||
|
||||
private void OnMetadataLoaded(){
|
||||
CanRun = CheckRequiredVersion(RequiredVersion);
|
||||
this.Name = builder.Name;
|
||||
this.Description = builder.Description;
|
||||
this.Author = builder.Author;
|
||||
this.Version = builder.Version;
|
||||
this.Website = builder.Website;
|
||||
this.ConfigFile = builder.ConfigFile;
|
||||
this.ConfigDefault = builder.ConfigDefault;
|
||||
this.RequiredVersion = builder.RequiredVersion;
|
||||
|
||||
string configPath = ConfigPath, defaultConfigPath = DefaultConfigPath;
|
||||
|
||||
if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
|
||||
string dataFolder = GetPluginFolder(PluginFolder.Data);
|
||||
|
||||
try{
|
||||
Directory.CreateDirectory(dataFolder);
|
||||
File.Copy(defaultConfigPath, configPath, false);
|
||||
}catch(Exception e){
|
||||
throw new IOException("Could not generate a configuration file for '"+Identifier+"' plugin: "+e.Message, e);
|
||||
}
|
||||
}
|
||||
this.CanRun = AppVersion >= RequiredVersion;
|
||||
}
|
||||
|
||||
public string GetScriptPath(PluginEnvironment environment){
|
||||
@@ -132,78 +114,94 @@ namespace TweetDuck.Plugins{
|
||||
return obj is Plugin plugin && plugin.Identifier.Equals(Identifier);
|
||||
}
|
||||
|
||||
// Static
|
||||
|
||||
private static readonly Version AppVersion = new Version(Program.VersionTag);
|
||||
private static readonly string[] EndTag = { "[END]" };
|
||||
// Builder
|
||||
|
||||
public static Plugin CreateFromFolder(string path, PluginGroup group){
|
||||
Plugin plugin = new Plugin(path, Path.GetFileName(path), group, LoadEnvironments(path));
|
||||
LoadMetadata(path, plugin);
|
||||
return plugin;
|
||||
}
|
||||
public sealed class Builder{
|
||||
private static readonly Version DefaultRequiredVersion = new Version(0, 0, 0, 0);
|
||||
|
||||
private static PluginEnvironment LoadEnvironments(string path){
|
||||
PluginEnvironment environments = PluginEnvironment.None;
|
||||
public string Name { get; private set; }
|
||||
public string Description { get; private set; } = string.Empty;
|
||||
public string Author { get; private set; } = "(anonymous)";
|
||||
public string Version { get; private set; } = "(unknown)";
|
||||
public string Website { get; private set; } = string.Empty;
|
||||
public string ConfigFile { get; private set; } = string.Empty;
|
||||
public string ConfigDefault { get; private set; } = string.Empty;
|
||||
public Version RequiredVersion { get; private set; } = DefaultRequiredVersion;
|
||||
|
||||
foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){
|
||||
environments |= PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal));
|
||||
public PluginEnvironment Environments { get; private set; } = PluginEnvironment.None;
|
||||
|
||||
private readonly PluginGroup group;
|
||||
private readonly string pathRoot;
|
||||
private readonly string pathData;
|
||||
private readonly string identifier;
|
||||
|
||||
public Builder(PluginGroup group, string name, string pathRoot, string pathData){
|
||||
this.group = group;
|
||||
this.pathRoot = pathRoot;
|
||||
this.pathData = pathData;
|
||||
this.identifier = group.GetIdentifierPrefix()+name;
|
||||
}
|
||||
|
||||
if (environments == PluginEnvironment.None){
|
||||
throw new ArgumentException("Plugin has no script files");
|
||||
}
|
||||
|
||||
return environments;
|
||||
}
|
||||
|
||||
private static void LoadMetadata(string path, Plugin plugin){
|
||||
string metaFile = Path.Combine(path, ".meta");
|
||||
|
||||
if (!File.Exists(metaFile)){
|
||||
throw new ArgumentException("Missing .meta file");
|
||||
}
|
||||
|
||||
string currentTag = null, currentContents = string.Empty;
|
||||
|
||||
foreach(string line in File.ReadAllLines(metaFile, Encoding.UTF8).Concat(EndTag).Select(line => line.TrimEnd()).Where(line => line.Length > 0)){
|
||||
if (line[0] == '[' && line[line.Length-1] == ']'){
|
||||
if (currentTag != null){
|
||||
plugin.metadata[currentTag] = currentContents;
|
||||
}
|
||||
|
||||
currentTag = line.Substring(1, line.Length-2).ToUpper();
|
||||
currentContents = string.Empty;
|
||||
|
||||
if (line.Equals(EndTag[0])){
|
||||
break;
|
||||
}
|
||||
|
||||
if (!plugin.metadata.ContainsKey(currentTag)){
|
||||
throw new FormatException("Invalid metadata tag: "+currentTag);
|
||||
}
|
||||
public void SetFromTag(string tag, string value){
|
||||
switch(tag){
|
||||
case "NAME": this.Name = value; break;
|
||||
case "DESCRIPTION": this.Description = value; break;
|
||||
case "AUTHOR": this.Author = value; break;
|
||||
case "VERSION": this.Version = value; break;
|
||||
case "WEBSITE": this.Website = value; break;
|
||||
case "CONFIGFILE": this.ConfigFile = value; break;
|
||||
case "CONFIGDEFAULT": this.ConfigDefault = value; break;
|
||||
case "REQUIRES": SetRequiredVersion(value); break;
|
||||
default: throw new FormatException("Invalid metadata tag: "+tag);
|
||||
}
|
||||
else if (currentTag != null){
|
||||
currentContents = currentContents.Length == 0 ? line : currentContents+Environment.NewLine+line;
|
||||
}
|
||||
|
||||
public void AddEnvironment(PluginEnvironment environment){
|
||||
this.Environments |= environment;
|
||||
}
|
||||
|
||||
private void SetRequiredVersion(string versionStr){
|
||||
if (System.Version.TryParse(versionStr, out Version version)){
|
||||
this.RequiredVersion = version;
|
||||
}
|
||||
else if (versionStr == VersionWildcard){
|
||||
this.RequiredVersion = DefaultRequiredVersion;
|
||||
}
|
||||
else{
|
||||
throw new FormatException("Missing metadata tag before value: "+line);
|
||||
throw new FormatException("Plugin contains invalid minimum version: "+versionStr);
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.Name.Length == 0){
|
||||
throw new FormatException("Plugin is missing a name in the .meta file");
|
||||
public Plugin BuildAndSetup(){
|
||||
Plugin plugin = new Plugin(group, identifier, pathRoot, pathData, this);
|
||||
|
||||
if (plugin.Name.Length == 0){
|
||||
throw new InvalidOperationException("Plugin is missing a name in the .meta file");
|
||||
}
|
||||
|
||||
if (plugin.Environments == PluginEnvironment.None){
|
||||
throw new InvalidOperationException("Plugin has no script files");
|
||||
}
|
||||
|
||||
// setup
|
||||
|
||||
string configPath = plugin.ConfigPath, defaultConfigPath = plugin.DefaultConfigPath;
|
||||
|
||||
if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
|
||||
string dataFolder = plugin.GetPluginFolder(PluginFolder.Data);
|
||||
|
||||
try{
|
||||
Directory.CreateDirectory(dataFolder);
|
||||
File.Copy(defaultConfigPath, configPath, false);
|
||||
}catch(Exception e){
|
||||
throw new IOException($"Could not generate a configuration file for '{plugin.Identifier}' plugin: {e.Message}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// done
|
||||
|
||||
return plugin;
|
||||
}
|
||||
|
||||
if (plugin.RequiredVersion.Length == 0 || !(plugin.RequiredVersion == VersionWildcard || System.Version.TryParse(plugin.RequiredVersion, out Version _))){
|
||||
throw new FormatException("Plugin contains invalid version: "+plugin.RequiredVersion);
|
||||
}
|
||||
|
||||
plugin.OnMetadataLoaded();
|
||||
}
|
||||
|
||||
private static bool CheckRequiredVersion(string requires){
|
||||
return requires == VersionWildcard || AppVersion >= new Version(requires);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
56
Plugins/PluginLoader.cs
Normal file
56
Plugins/PluginLoader.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
static class PluginLoader{
|
||||
private static readonly string[] EndTag = { "[END]" };
|
||||
|
||||
public static Plugin FromFolder(string path, PluginGroup group){
|
||||
string name = Path.GetFileName(path);
|
||||
|
||||
if (string.IsNullOrEmpty(name)){
|
||||
throw new ArgumentException("Could not extract directory name from path: "+path);
|
||||
}
|
||||
|
||||
Plugin.Builder builder = new Plugin.Builder(group, name, path, Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name));
|
||||
|
||||
foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){
|
||||
builder.AddEnvironment(PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal)));
|
||||
}
|
||||
|
||||
string metaFile = Path.Combine(path, ".meta");
|
||||
|
||||
if (!File.Exists(metaFile)){
|
||||
throw new ArgumentException("Plugin is missing a .meta file");
|
||||
}
|
||||
|
||||
string currentTag = null, currentContents = string.Empty;
|
||||
|
||||
foreach(string line in File.ReadAllLines(metaFile, Encoding.UTF8).Concat(EndTag).Select(line => line.TrimEnd()).Where(line => line.Length > 0)){
|
||||
if (line[0] == '[' && line[line.Length-1] == ']'){
|
||||
if (currentTag != null){
|
||||
builder.SetFromTag(currentTag, currentContents);
|
||||
}
|
||||
|
||||
currentTag = line.Substring(1, line.Length-2).ToUpper();
|
||||
currentContents = string.Empty;
|
||||
|
||||
if (line.Equals(EndTag[0])){
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (currentTag != null){
|
||||
currentContents = currentContents.Length == 0 ? line : currentContents+Environment.NewLine+line;
|
||||
}
|
||||
else{
|
||||
throw new FormatException("Missing metadata tag before value: "+line);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.BuildAndSetup();
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,7 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using TweetDuck.Core;
|
||||
using TweetDuck.Core.Other.Interfaces;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetDuck.Plugins.Events;
|
||||
@@ -12,11 +12,15 @@ using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
sealed class PluginManager{
|
||||
private static readonly Dictionary<PluginEnvironment, string> PluginSetupScripts = new Dictionary<PluginEnvironment, string>(4){
|
||||
{ PluginEnvironment.None, ScriptLoader.LoadResource("plugins.js") },
|
||||
{ PluginEnvironment.Browser, ScriptLoader.LoadResource("plugins.browser.js") },
|
||||
{ PluginEnvironment.Notification, ScriptLoader.LoadResource("plugins.notification.js") }
|
||||
};
|
||||
private static IReadOnlyDictionary<PluginEnvironment, string> LoadSetupScripts(){
|
||||
return PluginEnvironmentExtensions.Map(
|
||||
ScriptLoader.LoadResource("plugins.js"),
|
||||
ScriptLoader.LoadResource("plugins.browser.js"),
|
||||
ScriptLoader.LoadResource("plugins.notification.js")
|
||||
);
|
||||
}
|
||||
|
||||
private static readonly IReadOnlyDictionary<PluginEnvironment, string> PluginSetupScripts = LoadSetupScripts();
|
||||
|
||||
public string PathOfficialPlugins => Path.Combine(rootPath, "official");
|
||||
public string PathCustomPlugins => Path.Combine(rootPath, "user");
|
||||
@@ -132,7 +136,7 @@ namespace TweetDuck.Plugins{
|
||||
Plugin plugin;
|
||||
|
||||
try{
|
||||
plugin = Plugin.CreateFromFolder(fullDir, group);
|
||||
plugin = PluginLoader.FromFolder(fullDir, group);
|
||||
}catch(Exception e){
|
||||
loadErrors.Add(group.GetIdentifierPrefix()+Path.GetFileName(fullDir)+": "+e.Message);
|
||||
continue;
|
||||
|
@@ -20,7 +20,7 @@ namespace TweetDuck{
|
||||
public const string BrandName = "TweetDuck";
|
||||
public const string Website = "https://tweetduck.chylex.com";
|
||||
|
||||
public const string VersionTag = "1.13.1";
|
||||
public const string VersionTag = "1.13.3";
|
||||
|
||||
public static readonly bool IsPortable = File.Exists("makeportable");
|
||||
|
||||
@@ -151,10 +151,9 @@ namespace TweetDuck{
|
||||
|
||||
Application.ApplicationExit += (sender, args) => ExitCleanup();
|
||||
|
||||
UpdaterSettings updaterSettings = new UpdaterSettings{
|
||||
UpdaterSettings updaterSettings = new UpdaterSettings(InstallerPath){
|
||||
AllowPreReleases = Arguments.HasFlag(Arguments.ArgDebugUpdates),
|
||||
DismissedUpdate = UserConfig.DismissedUpdate,
|
||||
InstallerDownloadFolder = InstallerPath
|
||||
DismissedUpdate = UserConfig.DismissedUpdate
|
||||
};
|
||||
|
||||
FormBrowser mainForm = new FormBrowser(updaterSettings);
|
||||
|
BIN
Resources/Design/app_logo.afdesign
Normal file
BIN
Resources/Design/app_logo.afdesign
Normal file
Binary file not shown.
BIN
Resources/Design/video_buttons.afdesign
Normal file
BIN
Resources/Design/video_buttons.afdesign
Normal file
Binary file not shown.
@@ -8,7 +8,7 @@ Edit layout & design
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.2.4
|
||||
1.2.5
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
@@ -610,7 +610,7 @@ ${this.config.revertIcons ? `
|
||||
|
||||
${currentTheme === "black" ? `
|
||||
html.dark a, html.dark a:hover, html.dark a:focus, html.dark a:active { color: #8bd }
|
||||
#tduck-show-thread { color: #8bd !important }
|
||||
#tduck-show-thread, .other-replies-link { color: #8bd !important }
|
||||
.quoted-tweet { border-color: #292f33 !important }
|
||||
` : ``}
|
||||
|
||||
|
@@ -9,7 +9,7 @@ Emoji keyboard
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.4.2
|
||||
1.4.4
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
@@ -32,6 +32,7 @@ enabled(){
|
||||
|
||||
this.css = window.TDPF_createCustomStyle(this);
|
||||
this.css.insert(".emoji-keyboard { position: absolute; width: 15.35em; background-color: white; border-radius: 1px; font-size: 24px; z-index: 9999 }");
|
||||
this.css.insert(".emoji-keyboard-popup-btn .icon { vertical-align: -4px !important }");
|
||||
|
||||
this.css.insert(".emoji-keyboard-list { height: 10.14em; padding: 0.1em; box-sizing: border-box; overflow-y: auto }");
|
||||
this.css.insert(".emoji-keyboard-list .separator { height: 26px }");
|
||||
@@ -49,7 +50,7 @@ enabled(){
|
||||
|
||||
// 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>';
|
||||
var buttonHTML = '<button class="needsclick btn btn-on-blue txt-left padding-v--6 padding-h--8 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);
|
||||
|
@@ -59,7 +59,19 @@ Remove unsupported emoji:
|
||||
> remove copyright
|
||||
> remove registered trademark
|
||||
> remove trademark
|
||||
0023 FE0F 20E3;keycap
|
||||
> remove keycap #
|
||||
002A FE0F 20E3;keycap *
|
||||
> remove keycap 0
|
||||
> remove keycap 1
|
||||
> remove keycap 2
|
||||
> remove keycap 3
|
||||
> remove keycap 4
|
||||
> remove keycap 5
|
||||
> remove keycap 6
|
||||
> remove keycap 7
|
||||
> remove keycap 8
|
||||
> remove keycap 9
|
||||
1F51F;keycap 10
|
||||
|
||||
1F441;eye
|
||||
> remove eye in speech bubble
|
||||
|
@@ -1354,18 +1354,7 @@
|
||||
2755;white exclamation mark
|
||||
2757;exclamation mark
|
||||
3030;wavy dash
|
||||
0023 FE0F 20E3;keycap #
|
||||
002A FE0F 20E3;keycap *
|
||||
0030 FE0F 20E3;keycap 0
|
||||
0031 FE0F 20E3;keycap 1
|
||||
0032 FE0F 20E3;keycap 2
|
||||
0033 FE0F 20E3;keycap 3
|
||||
0034 FE0F 20E3;keycap 4
|
||||
0035 FE0F 20E3;keycap 5
|
||||
0036 FE0F 20E3;keycap 6
|
||||
0037 FE0F 20E3;keycap 7
|
||||
0038 FE0F 20E3;keycap 8
|
||||
0039 FE0F 20E3;keycap 9
|
||||
1F51F;keycap 10
|
||||
1F4AF;hundred points
|
||||
1F520;input latin uppercase
|
||||
|
@@ -1,32 +1,77 @@
|
||||
Param([Parameter(Mandatory = $True, Position = 1)][string] $dir)
|
||||
Param(
|
||||
[Parameter(Mandatory = $True, Position = 1)][string] $targetDir,
|
||||
[Parameter(Mandatory = $True, Position = 2)][string] $projectDir,
|
||||
[Parameter(Position = 3)][string] $version = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Set-Location $dir
|
||||
|
||||
function Check-Carriage-Return{
|
||||
Param([Parameter(Mandatory = $True, Position = 1)] $fname)
|
||||
try{
|
||||
Write-Host "------------------------------"
|
||||
|
||||
$file = @(Get-ChildItem -Include $fname -Recurse)[0]
|
||||
|
||||
if ((Get-Content -Path $file.FullName -Raw).Contains("`r")){
|
||||
Throw "$fname cannot contain carriage return"
|
||||
if ($version.Equals("")){
|
||||
$version = (Get-Item (Join-Path $targetDir "TweetDuck.exe")).VersionInfo.FileVersion
|
||||
}
|
||||
|
||||
Write-Host "Verified" $file.FullName.Substring($dir.Length)
|
||||
}
|
||||
|
||||
function Rewrite-File{
|
||||
[CmdletBinding()]
|
||||
Param([Parameter(Mandatory = $True, ValueFromPipeline = $True)][array] $lines, [Parameter(Mandatory = $True, Position = 1)] $file)
|
||||
Write-Host "TweetDuck version" $version
|
||||
|
||||
Write-Host "------------------------------"
|
||||
|
||||
# Cleanup
|
||||
|
||||
if (Test-Path (Join-Path $targetDir "locales")){
|
||||
Remove-Item -Path (Join-Path $targetDir "locales\*.pak") -Exclude "en-US.pak"
|
||||
}
|
||||
|
||||
# Copy resources
|
||||
|
||||
Copy-Item (Join-Path $projectDir "bld\Resources\CEFSHARP-LICENSE.txt") -Destination $targetDir -Force
|
||||
Copy-Item (Join-Path $projectDir "LICENSE.md") -Destination (Join-Path $targetDir "LICENSE.txt") -Force
|
||||
|
||||
New-Item -ItemType directory -Path $targetDir -Name "scripts" | Out-Null
|
||||
New-Item -ItemType directory -Path $targetDir -Name "plugins" | Out-Null
|
||||
New-Item -ItemType directory -Path $targetDir -Name "plugins\official" | Out-Null
|
||||
New-Item -ItemType directory -Path $targetDir -Name "plugins\user" | Out-Null
|
||||
|
||||
Copy-Item (Join-Path $projectDir "Resources\Scripts\*") -Destination (Join-Path $targetDir "scripts") -Recurse
|
||||
Copy-Item (Join-Path $projectDir "Resources\Plugins\*") -Exclude ".debug", "emoji-instructions.txt" -Destination (Join-Path $targetDir "plugins\official") -Recurse
|
||||
|
||||
# Post processing
|
||||
|
||||
function Check-Carriage-Return{
|
||||
Param(
|
||||
[Parameter(Mandatory = $True, Position = 1)] $fname
|
||||
)
|
||||
|
||||
$file = @(Get-ChildItem -Path $targetDir -Include $fname -Recurse)[0]
|
||||
|
||||
if ((Get-Content -Path $file.FullName -Raw).Contains("`r")){
|
||||
Throw "$fname cannot contain carriage return"
|
||||
}
|
||||
|
||||
Write-Host "Verified" $file.FullName.Substring($targetDir.Length)
|
||||
}
|
||||
|
||||
function Rewrite-File{
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $True, ValueFromPipeline = $True)][array] $lines,
|
||||
[Parameter(Mandatory = $True, Position = 1)] $file
|
||||
)
|
||||
|
||||
$relativePath = $file.FullName.Substring($targetDir.Length)
|
||||
|
||||
if ($relativePath.StartsWith("scripts\")){
|
||||
$lines = (,("#" + $version) + $lines)
|
||||
}
|
||||
|
||||
$lines | Where { $_ -ne '' } | Set-Content -Path $file.FullName
|
||||
Write-Host "Processed" $relativePath
|
||||
}
|
||||
|
||||
$lines | Where { $_ -ne '' } | Set-Content -Path $file.FullName
|
||||
Write-Host "Processed" $file.FullName.Substring($dir.Length)
|
||||
}
|
||||
|
||||
try{
|
||||
Check-Carriage-Return("emoji-ordering.txt")
|
||||
|
||||
ForEach($file in Get-ChildItem -Filter *.js -Exclude configuration.default.js -Recurse){
|
||||
ForEach($file in Get-ChildItem -Path $targetDir -Filter "*.js" -Exclude "configuration.default.js" -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
$lines = $lines | % { $_.TrimStart() }
|
||||
$lines = $lines -Replace '^(.*?)((?<=^|[;{}()])\s?//(?:\s.*|$))?$', '$1'
|
||||
@@ -34,7 +79,7 @@ try{
|
||||
,$lines | Rewrite-File $file
|
||||
}
|
||||
|
||||
ForEach($file in Get-ChildItem -Filter *.css -Recurse){
|
||||
ForEach($file in Get-ChildItem -Path $targetDir -Filter "*.css" -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
$lines = $lines -Replace '\s*/\*.*?\*/', ''
|
||||
$lines = $lines -Replace '^\s+(.+):\s?(.+?)(?:\s?(!important))?;$', '$1:$2$3;'
|
||||
@@ -42,10 +87,12 @@ try{
|
||||
@(($lines | Where { $_ -ne '' }) -Join ' ') | Rewrite-File $file
|
||||
}
|
||||
|
||||
ForEach($file in Get-ChildItem -Filter *.html -Recurse){
|
||||
ForEach($file in Get-ChildItem -Path $targetDir -Filter "*.html" -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
,($lines | % { $_.TrimStart() }) | Rewrite-File $file
|
||||
}
|
||||
|
||||
Write-Host "------------------------------"
|
||||
}catch{
|
||||
Write-Host "Encountered an error while running PostBuild.ps1 on line" $_.InvocationInfo.ScriptLineNumber
|
||||
Write-Host $_
|
||||
|
@@ -2,36 +2,155 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Other;
|
||||
#if DEBUG
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using TweetDuck.Core;
|
||||
using TweetDuck.Plugins;
|
||||
#endif
|
||||
|
||||
namespace TweetDuck.Resources{
|
||||
static class ScriptLoader{
|
||||
private const string UrlPrefix = "td:";
|
||||
|
||||
public static string LoadResource(string name, bool silent = false){
|
||||
public static string LoadResource(string name, bool silent = false, Control sync = null){
|
||||
try{
|
||||
return File.ReadAllText(Path.Combine(Program.ScriptPath, name), Encoding.UTF8);
|
||||
}catch(Exception ex){
|
||||
if (!silent){
|
||||
FormMessage.Error("TweetDuck Has Failed :(", "Unfortunately, TweetDuck could not load the "+name+" file. The program will continue running with limited functionality.\n\n"+ex.Message, FormMessage.OK);
|
||||
string path = Program.ScriptPath;
|
||||
|
||||
#if DEBUG
|
||||
if (Directory.Exists(HotSwapTargetDir)){
|
||||
path = Path.Combine(HotSwapTargetDir, "scripts");
|
||||
Debug.WriteLine("Hot swap active, redirecting "+name);
|
||||
}
|
||||
#endif
|
||||
|
||||
string contents = File.ReadAllText(Path.Combine(path, name), Encoding.UTF8);
|
||||
int separator;
|
||||
|
||||
// first line can be either:
|
||||
// #<version>\r\n
|
||||
// #<version>\n
|
||||
|
||||
if (contents[0] != '#'){
|
||||
ShowLoadError(silent, sync, $"File {name} appears to be corrupted, please try reinstalling the app.");
|
||||
separator = 0;
|
||||
}
|
||||
else{
|
||||
separator = contents.IndexOf('\n');
|
||||
string fileVersion = contents.Substring(1, separator-1).TrimEnd();
|
||||
|
||||
if (fileVersion != Program.VersionTag){
|
||||
ShowLoadError(silent, sync, $"File {name} is made for a different version of TweetDuck ({fileVersion}) and may not function correctly in this version, please try reinstalling the app.");
|
||||
}
|
||||
}
|
||||
|
||||
return contents.Substring(separator).TrimStart();
|
||||
}catch(Exception ex){
|
||||
ShowLoadError(silent, sync, $"Could not load {name}. The program will continue running with limited functionality.\n\n{ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExecuteFile(IFrame frame, string file){
|
||||
ExecuteScript(frame, LoadResource(file), GetRootIdentifier(file));
|
||||
public static bool ExecuteFile(IFrame frame, string file, Control sync = null){
|
||||
string script = LoadResource(file, sync == null, sync);
|
||||
ExecuteScript(frame, script, GetRootIdentifier(file));
|
||||
return script != null;
|
||||
}
|
||||
|
||||
public static void ExecuteScript(IFrame frame, string script, string identifier){
|
||||
if (script != null){
|
||||
frame.ExecuteJavaScriptAsync(script, UrlPrefix+identifier, 1);
|
||||
frame.ExecuteJavaScriptAsync(script, "td:"+identifier, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetRootIdentifier(string file){
|
||||
return "root:"+Path.GetFileNameWithoutExtension(file);
|
||||
}
|
||||
|
||||
private static void ShowLoadError(bool silent, Control sync, string message){
|
||||
if (silent){
|
||||
return;
|
||||
}
|
||||
|
||||
if (sync == null){
|
||||
FormMessage.Error("Resource Error", message, FormMessage.OK);
|
||||
}
|
||||
else{
|
||||
sync.InvokeSafe(() => FormMessage.Error("Resource Error", message, FormMessage.OK));
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private static readonly string HotSwapProjectRoot = FixPathSlash(Path.GetFullPath(Path.Combine(Program.ProgramPath, "../../../")));
|
||||
private static readonly string HotSwapTargetDir = FixPathSlash(Path.Combine(HotSwapProjectRoot, "bin", "tmp"));
|
||||
private static readonly string HotSwapRebuildScript = Path.Combine(HotSwapProjectRoot, "Resources", "PostBuild.ps1");
|
||||
|
||||
static ScriptLoader(){
|
||||
if (File.Exists(HotSwapRebuildScript)){
|
||||
Debug.WriteLine("Activating resource hot swap");
|
||||
|
||||
ResetHotSwap();
|
||||
Application.ApplicationExit += (sender, args) => ResetHotSwap();
|
||||
}
|
||||
}
|
||||
|
||||
public static void HotSwap(){
|
||||
if (!File.Exists(HotSwapRebuildScript)){
|
||||
Debug.WriteLine("Failed resource hot swap, missing rebuild script: "+HotSwapRebuildScript);
|
||||
return;
|
||||
}
|
||||
|
||||
ResetHotSwap();
|
||||
Directory.CreateDirectory(HotSwapTargetDir);
|
||||
|
||||
using(Process process = Process.Start(new ProcessStartInfo{
|
||||
FileName = "powershell",
|
||||
Arguments = $"-ExecutionPolicy Unrestricted -File \"{HotSwapRebuildScript}\" \"{HotSwapTargetDir}\\\" \"{HotSwapProjectRoot}\\\" \"{Program.VersionTag}\"",
|
||||
WindowStyle = ProcessWindowStyle.Hidden
|
||||
})){
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
if (!process.WaitForExit(8000)){
|
||||
Debug.WriteLine("Failed resource hot swap, script did not finish in time");
|
||||
return;
|
||||
}
|
||||
else if (process.ExitCode != 0){
|
||||
Debug.WriteLine("Failed resource hot swap, script exited with code "+process.ExitCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Force update plugin manager setup scripts
|
||||
|
||||
string newPluginRoot = Path.Combine(HotSwapTargetDir, "plugins");
|
||||
|
||||
const BindingFlags flagsInstance = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
const BindingFlags flagsStatic = BindingFlags.Static | BindingFlags.NonPublic;
|
||||
|
||||
Type typePluginManager = typeof(PluginManager);
|
||||
Type typeFormBrowser = typeof(FormBrowser);
|
||||
|
||||
// ReSharper disable PossibleNullReferenceException
|
||||
object pluginSetupScripts = typePluginManager.GetMethod("LoadSetupScripts", flagsStatic).Invoke(null, new object[0]);
|
||||
typePluginManager.GetField("PluginSetupScripts", flagsStatic).SetValue(null, pluginSetupScripts);
|
||||
|
||||
object instPluginManager = typeFormBrowser.GetField("plugins", flagsInstance).GetValue(FormManager.TryFind<FormBrowser>());
|
||||
typePluginManager.GetField("rootPath", flagsInstance).SetValue(instPluginManager, newPluginRoot);
|
||||
|
||||
Debug.WriteLine("Reloading hot swapped plugins...");
|
||||
((PluginManager)instPluginManager).Reload();
|
||||
// ReSharper restore PossibleNullReferenceException
|
||||
}
|
||||
|
||||
private static void ResetHotSwap(){
|
||||
try{
|
||||
Directory.Delete(HotSwapTargetDir, true);
|
||||
}catch(DirectoryNotFoundException){}
|
||||
}
|
||||
|
||||
private static string FixPathSlash(string path){
|
||||
return path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)+'\\';
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@@ -280,10 +280,12 @@
|
||||
// Block: Hook into settings object to detect when the settings change, and update html attributes and notification layout.
|
||||
//
|
||||
(function(){
|
||||
const refreshSettings = function(){
|
||||
const refreshSettings = function(){
|
||||
let fontSizeName = TD.settings.getFontSize();
|
||||
let themeName = TD.settings.getTheme();
|
||||
|
||||
let columnBackground = getClassStyleProperty("column", "background-color");
|
||||
|
||||
let tags = [
|
||||
"<html "+Array.prototype.map.call(document.documentElement.attributes, ele => `${ele.name}="${ele.value}"`).join(" ")+"><head>"
|
||||
];
|
||||
@@ -292,23 +294,7 @@
|
||||
tags.push($(this)[0].outerHTML);
|
||||
});
|
||||
|
||||
tags.push("<style type='text/css'>");
|
||||
tags.push("body { background: "+getClassStyleProperty("column", "background-color")+" !important }"); // set background color
|
||||
tags.push("body::before { content: none !important }"); // remove background gradient
|
||||
tags.push(".column { background: transparent !important }"); // remove background color from columns
|
||||
tags.push("a[data-full-url] { word-break: break-all !important }"); // break long urls
|
||||
tags.push(".media-item, .media-preview { border-radius: 1px !important }"); // square-ify media
|
||||
tags.push(".quoted-tweet { border-radius: 0 !important }"); // square-ify quoted tweets
|
||||
tags.push(".activity-header.has-source-avatar { margin-bottom: 4px !important }"); // tweak distance between avatar and text
|
||||
tags.push(".activity-header .tweet-timestamp { line-height: unset !important }"); // fix timestamp position
|
||||
tags.push(".activity-header .icon-user-filled { vertical-align: sub !important; }"); // fix follow icon position
|
||||
tags.push("#tduck-show-thread { display: inline-block !important; cursor: pointer }");
|
||||
|
||||
if (fontSizeName === "smallest"){
|
||||
tags.push(".badge-verified:before { width: 13px !important; height: 13px !important; background-position: -223px -98px !important }"); // fix cut off badge icon
|
||||
}
|
||||
|
||||
tags.push("</style>");
|
||||
tags.push("<style type='text/css'>body { background: "+columnBackground+" !important }</style>");
|
||||
|
||||
doc.setAttribute("data-td-font", fontSizeName);
|
||||
doc.setAttribute("data-td-theme", themeName);
|
||||
@@ -542,7 +528,8 @@
|
||||
let me = $(this)[0];
|
||||
|
||||
if (me.classList.contains("js-media-image-link") && highlightedTweetObj){
|
||||
let media = (highlightedTweetObj.quotedTweet || highlightedTweetObj).getMedia().find(media => media.mediaId === me.getAttribute("data-media-entity-id"));
|
||||
let tweet = highlightedTweetObj.hasMedia() ? highlightedTweetObj : highlightedTweetObj.quotedTweet;
|
||||
let media = tweet.getMedia().find(media => media.mediaId === me.getAttribute("data-media-entity-id"));
|
||||
|
||||
if ((media.isVideo && media.service === "twitter") || media.isAnimatedGif){
|
||||
$TD.setLastRightClickInfo("video", media.chooseVideoVariant().url);
|
||||
@@ -594,22 +581,15 @@
|
||||
// Block: Update highlighted column and tweet for context menu and other functionality.
|
||||
//
|
||||
(function(){
|
||||
var lastTweet = "";
|
||||
|
||||
const updateHighlightedColumn = function(ele){
|
||||
highlightedColumnEle = ele;
|
||||
highlightedColumnObj = ele ? TD.controller.columnManager.get(ele.attr("data-column")) : null;
|
||||
return !!highlightedColumnObj;
|
||||
};
|
||||
|
||||
const updateHighlightedTweet = function(ele, obj, tweetUrl, quoteUrl, authors, imageList){
|
||||
const updateHighlightedTweet = function(ele, obj){
|
||||
highlightedTweetEle = ele;
|
||||
highlightedTweetObj = obj;
|
||||
|
||||
if (lastTweet !== tweetUrl){
|
||||
$TD.setLastHighlightedTweet(tweetUrl, quoteUrl, authors, imageList);
|
||||
lastTweet = tweetUrl;
|
||||
}
|
||||
};
|
||||
|
||||
const processMedia = function(chirp){
|
||||
@@ -625,6 +605,19 @@
|
||||
|
||||
mouseleave: function(){
|
||||
updateHighlightedColumn(null);
|
||||
},
|
||||
|
||||
contextmenu: function(){
|
||||
let tweet = highlightedTweetObj;
|
||||
|
||||
if (tweet && tweet.chirpType === TD.services.ChirpBase.TWEET){
|
||||
let tweetUrl = tweet.getChirpURL();
|
||||
let quoteUrl = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
|
||||
let chirpAuthors = tweet.quotedTweet ? [ tweet.getMainUser().screenName, tweet.quotedTweet.getMainUser().screenName ].join(";") : tweet.getMainUser().screenName;
|
||||
let chirpImages = tweet.hasImage() ? processMedia(tweet) : tweet.quotedTweet && tweet.quotedTweet.hasImage() ? processMedia(tweet.quotedTweet) : "";
|
||||
|
||||
$TD.setRightClickedChirp(tweetUrl || "", quoteUrl || "", chirpAuthors, chirpImages);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -636,21 +629,11 @@
|
||||
let tweet = highlightedColumnObj.findChirp(me.attr("data-tweet-id")) || highlightedColumnObj.findChirp(me.attr("data-key"));
|
||||
return if !tweet;
|
||||
|
||||
if (tweet.chirpType === TD.services.ChirpBase.TWEET){
|
||||
let tweetUrl = tweet.getChirpURL();
|
||||
let quoteUrl = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
|
||||
let authors = tweet.quotedTweet ? [ tweet.getMainUser().screenName, tweet.quotedTweet.getMainUser().screenName ].join(";") : tweet.getMainUser().screenName;
|
||||
let imageList = tweet.quotedTweet && tweet.quotedTweet.hasImage() ? processMedia(tweet.quotedTweet) : tweet.hasImage() ? processMedia(tweet) : "";
|
||||
|
||||
updateHighlightedTweet(me, tweet, tweetUrl || "", quoteUrl || "", authors, imageList);
|
||||
}
|
||||
else{
|
||||
updateHighlightedTweet(me, tweet, "", "", "", "");
|
||||
}
|
||||
updateHighlightedTweet(me, tweet);
|
||||
},
|
||||
|
||||
mouseleave: function(){
|
||||
updateHighlightedTweet(null, null, "", "", "", "");
|
||||
updateHighlightedTweet(null, null);
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -659,85 +642,52 @@
|
||||
// Block: Screenshot tweet to clipboard.
|
||||
//
|
||||
(function(){
|
||||
var selectedTweet;
|
||||
|
||||
const setImportantProperty = function(obj, property, value){
|
||||
if (obj.length === 1){
|
||||
obj[0].style.setProperty(property, value, "important");
|
||||
}
|
||||
};
|
||||
|
||||
app.delegate("article.js-stream-item", "contextmenu", function(){
|
||||
selectedTweet = $(this);
|
||||
});
|
||||
|
||||
window.TDGF_triggerScreenshot = function(){
|
||||
if (selectedTweet){
|
||||
let tweetWidth = Math.floor(selectedTweet.width());
|
||||
let parent = selectedTweet.parent();
|
||||
return if !highlightedTweetObj || !highlightedColumnObj;
|
||||
|
||||
let chirp = highlightedColumnObj.findChirp(highlightedTweetEle.attr("data-key")) || highlightedTweetObj;
|
||||
|
||||
let columnWidth = highlightedColumnEle.width();
|
||||
|
||||
let html = $(chirp.render({
|
||||
withFooter: false,
|
||||
withTweetActions: false,
|
||||
isInConvo: false,
|
||||
isFavorite: false,
|
||||
isRetweeted: false, // keeps retweet mark above tweet
|
||||
isPossiblySensitive: false,
|
||||
mediaPreviewSize: highlightedColumnObj.getMediaPreviewSize()
|
||||
}));
|
||||
|
||||
html.find("footer").last().remove(); // apparently withTweetActions breaks for certain tweets, nice
|
||||
html.find(".td-screenshot-remove").remove();
|
||||
|
||||
html.find("p.link-complex-target").filter(function(){
|
||||
return $(this).text() === "Show this thread";
|
||||
}).first().remove();
|
||||
|
||||
html.addClass($(document.documentElement).attr("class"));
|
||||
html.addClass($(document.body).attr("class"));
|
||||
|
||||
html.css("background-color", getClassStyleProperty("column", "background-color"));
|
||||
html.css("border", "none");
|
||||
|
||||
for(let selector of [ ".js-quote-detail", ".js-media-preview-container", ".js-media" ]){
|
||||
let ele = html.find(selector);
|
||||
|
||||
let isDetail = parent.hasClass("js-tweet-detail");
|
||||
let isReply = !isDetail && (parent.hasClass("js-replies-to") || parent.hasClass("js-replies-before"));
|
||||
|
||||
selectedTweet = selectedTweet.clone();
|
||||
|
||||
let container = selectedTweet.children().first();
|
||||
container.addClass($(document.documentElement).attr("class"));
|
||||
container.addClass($(document.body).attr("class"));
|
||||
container.css("padding-bottom", "0");
|
||||
|
||||
setImportantProperty(selectedTweet.find(".js-tweet-text"), "margin-bottom", "8px");
|
||||
setImportantProperty(selectedTweet.find(".js-quote-detail"), "margin-bottom", "10px");
|
||||
setImportantProperty(selectedTweet.find(".js-poll-link").next(), "margin-bottom", "8px");
|
||||
|
||||
if (isDetail){
|
||||
if (selectedTweet.find("[class*='media-grid-']").length > 0){
|
||||
setImportantProperty(selectedTweet.find(".js-tweet-media"), "margin-bottom", "10px");
|
||||
}
|
||||
else{
|
||||
setImportantProperty(selectedTweet.find(".js-tweet-media"), "margin-bottom", "6px");
|
||||
}
|
||||
|
||||
setImportantProperty(selectedTweet.find(".js-media-preview-container"), "margin-bottom", "4px");
|
||||
selectedTweet.find(".js-translate-call-to-action").first().remove();
|
||||
selectedTweet.find(".js-tweet").first().nextAll().remove();
|
||||
selectedTweet.find("footer").last().prevUntil(":not(.txt-mute)").addBack().remove(); // footer, date, location
|
||||
if (ele.length){
|
||||
ele[0].style.setProperty("margin-bottom", "2px", "important");
|
||||
break;
|
||||
}
|
||||
else{
|
||||
setImportantProperty(selectedTweet.find(".js-media-preview-container").filter(function(){
|
||||
return $(this).closest(".js-quote-detail").length === 0;
|
||||
}), "margin-bottom", "10px");
|
||||
|
||||
selectedTweet.find("footer").last().remove();
|
||||
}
|
||||
|
||||
if (isReply){
|
||||
selectedTweet.find(".is-conversation").removeClass("is-conversation");
|
||||
selectedTweet.find(".thread").remove();
|
||||
}
|
||||
|
||||
selectedTweet.find("p.link-complex-target").filter(function(){
|
||||
return $(this).text() === "Show this thread";
|
||||
}).first().remove();
|
||||
|
||||
selectedTweet.find(".js-poll-link").remove();
|
||||
selectedTweet.find(".td-screenshot-remove").remove();
|
||||
|
||||
let testTweet = selectedTweet.clone().css({
|
||||
position: "absolute",
|
||||
left: "-999px",
|
||||
width: tweetWidth+"px"
|
||||
}).appendTo(document.body);
|
||||
|
||||
let testTweetAvatar = testTweet.find(".tweet-avatar").first();
|
||||
let avatarBottom = testTweetAvatar.offset().top+testTweetAvatar.height();
|
||||
|
||||
let realHeight = Math.floor(Math.max(testTweet.height(), avatarBottom+10));
|
||||
testTweet.remove();
|
||||
|
||||
selectedTweet.find(".js-stream-item-content").first().css("height", "100vh");
|
||||
$TD.screenshotTweet(selectedTweet.html(), tweetWidth, realHeight);
|
||||
}
|
||||
|
||||
let type = chirp.getChirpType();
|
||||
|
||||
if ((type.startsWith("favorite") || type.startsWith("retweet")) && chirp.isAboutYou()){
|
||||
html.addClass("td-notification-padded");
|
||||
}
|
||||
|
||||
$TD.screenshotTweet(html[0].outerHTML, columnWidth);
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -928,26 +878,15 @@
|
||||
})();
|
||||
|
||||
//
|
||||
// Block: Swap shift key functionality for selecting accounts, and refocus the textbox afterwards.
|
||||
// Block: Refocus the textbox after switching accounts.
|
||||
//
|
||||
onAppReady.push(function(){
|
||||
const onAccountClick = function(e){
|
||||
if ($TDX.switchAccountSelectors){
|
||||
e.shiftKey = !e.shiftKey;
|
||||
}
|
||||
|
||||
const refocusInput = function(){
|
||||
$(".js-compose-text", ".js-docked-compose").focus();
|
||||
};
|
||||
|
||||
$(".js-account-list", ".js-docked-compose").delegate(".js-account-item", "click", onAccountClick);
|
||||
|
||||
return if !ensurePropertyExists(TD, "components", "AccountSelector", "prototype", "refreshPostingAccounts");
|
||||
|
||||
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", onAccountClick);
|
||||
}
|
||||
$(".js-account-list", ".js-docked-compose").delegate(".js-account-item", "click", function(e){
|
||||
setTimeout(refocusInput, 0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1205,11 +1144,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
$(".js-app-search-input").val("");
|
||||
$(".js-perform-search").blur();
|
||||
if (!("tweetduck" in data)){
|
||||
$(".js-app-search-input").val("");
|
||||
$(".js-perform-search").blur();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Setup global function to add a search column with the specified query.
|
||||
//
|
||||
onAppReady.push(function(){
|
||||
let context = $._data(document, "events")["uiSearchInputSubmit"][0].handler.context;
|
||||
|
||||
window.TDGF_performSearch = function(query){
|
||||
context.performSearch({ query, tweetduck: true });
|
||||
};
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Reorder search results to move accounts above hashtags.
|
||||
//
|
||||
onAppReady.push(function(){
|
||||
let container = $(".js-search-in-popover");
|
||||
let hashtags = $(".js-typeahead-topic-list", container);
|
||||
|
||||
$(".js-typeahead-user-list", container).insertBefore(hashtags);
|
||||
hashtags.addClass("list-divider");
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Make submitting search queries while holding Ctrl or middle-clicking the search icon open the search externally.
|
||||
//
|
||||
@@ -1406,6 +1369,8 @@
|
||||
window.gc && window.gc();
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
window.TDGF_reload = function(){}; // redefine to prevent reloading multiple times
|
||||
};
|
||||
|
||||
if (window.TD_SESSION && window.TD_SESSION.gc){
|
||||
|
@@ -14,22 +14,29 @@
|
||||
};
|
||||
|
||||
//
|
||||
// Block: Hook into links to bypass default open function and t.co.
|
||||
// Block: Hook into links to bypass default open function and t.co, and handle skipping notification when opening links.
|
||||
//
|
||||
addEventListener(links, "click", function(e){
|
||||
let ele = e.currentTarget;
|
||||
|
||||
$TD.openBrowser(ele.hasAttribute("data-full-url") ? ele.getAttribute("data-full-url") : ele.getAttribute("href"));
|
||||
e.preventDefault();
|
||||
|
||||
if ($TDX.skipOnLinkClick){
|
||||
let parentClasses = ele.parentNode.classList;
|
||||
|
||||
if (parentClasses.contains("js-tweet-text") || parentClasses.contains("js-quoted-tweet-text") || parentClasses.contains("js-timestamp")){
|
||||
$TD.loadNextNotification();
|
||||
(function(){
|
||||
const onLinkClick = function(e){
|
||||
if (e.button === 0 || e.button === 1){
|
||||
let ele = e.currentTarget;
|
||||
|
||||
$TD.openBrowser(ele.hasAttribute("data-full-url") ? ele.getAttribute("data-full-url") : ele.getAttribute("href"));
|
||||
e.preventDefault();
|
||||
|
||||
if ($TDX.skipOnLinkClick){
|
||||
let parentClasses = ele.parentNode.classList;
|
||||
|
||||
if (parentClasses.contains("js-tweet-text") || parentClasses.contains("js-quoted-tweet-text") || parentClasses.contains("js-timestamp")){
|
||||
$TD.loadNextNotification();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
addEventListener(links, "click", onLinkClick);
|
||||
addEventListener(links, "auxclick", onLinkClick);
|
||||
})();
|
||||
|
||||
//
|
||||
// Block: Allow bypassing of t.co in context menus.
|
||||
|
14
Resources/Scripts/screenshot.js
Normal file
14
Resources/Scripts/screenshot.js
Normal file
@@ -0,0 +1,14 @@
|
||||
(function($TD){
|
||||
let ele = document.getElementsByTagName("article")[0];
|
||||
ele.style.width = window.innerWidth+"px";
|
||||
|
||||
ele.style.position = "absolute";
|
||||
let contentHeight = ele.offsetHeight;
|
||||
ele.style.position = "static";
|
||||
|
||||
let avatar = ele.querySelector(".tweet-avatar");
|
||||
let avatarBottom = avatar ? avatar.getBoundingClientRect().bottom : 0;
|
||||
|
||||
$TD.setHeight(Math.floor(Math.max(contentHeight, avatarBottom+9)));
|
||||
setTimeout($TD.triggerScreenshot, document.getElementsByTagName("iframe").length ? 267 : 67);
|
||||
})($TD_NotificationScreenshot);
|
@@ -67,9 +67,13 @@ button {
|
||||
border-color: #2B7BB9 !important;
|
||||
}
|
||||
|
||||
#tduck .js-compose-scroller button > i {
|
||||
#tduck .js-compose-scroller button.full-width {
|
||||
padding: 7px 10px !important;
|
||||
}
|
||||
|
||||
#tduck .js-compose-scroller button.full-width > i {
|
||||
font-size: 20px !important;
|
||||
vertical-align: -4px !important;
|
||||
vertical-align: -5px !important;
|
||||
}
|
||||
|
||||
.js-show-drawer {
|
||||
@@ -84,6 +88,14 @@ button {
|
||||
content: "New ";
|
||||
}
|
||||
|
||||
#tduck .app-header .search-input-control input {
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
#tduck .app-header .search-input-perform-search {
|
||||
right: 8px !important;
|
||||
}
|
||||
|
||||
.js-account-safeguard-checkbox, .js-account-safeguard-checkbox label {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
@@ -183,6 +195,11 @@ a[data-full-url] {
|
||||
color: #9f51cf !important;
|
||||
}
|
||||
|
||||
.js-typeahead-user-list.is-hidden + .js-typeahead-topic-list {
|
||||
/* hide custom search result divider when not needed */
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
/* Replace 'Stay open' with a pin icon */
|
||||
/***************************************/
|
||||
|
@@ -2,10 +2,22 @@
|
||||
/* General */
|
||||
/***********/
|
||||
|
||||
body {
|
||||
body.td-notification {
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: none !important;
|
||||
}
|
||||
|
||||
.column {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/***********************/
|
||||
/* Redesign scrollbars */
|
||||
/***********************/
|
||||
|
||||
.scroll-styled-v::-webkit-scrollbar {
|
||||
width: 7px !important;
|
||||
}
|
||||
@@ -18,9 +30,41 @@ body {
|
||||
border-left: 0 !important;
|
||||
}
|
||||
|
||||
/*************/
|
||||
/* Usernames */
|
||||
/*************/
|
||||
/********************/
|
||||
/* Square-ify stuff */
|
||||
/********************/
|
||||
|
||||
.media-item, .media-preview {
|
||||
border-radius: 1px !important;
|
||||
}
|
||||
|
||||
.quoted-tweet {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/***********************/
|
||||
/* Tweaks for features */
|
||||
/***********************/
|
||||
|
||||
a[data-full-url] {
|
||||
word-break: break-all !important;
|
||||
}
|
||||
|
||||
#tduck-show-thread {
|
||||
display: inline-block !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*******************************************/
|
||||
/* Fix general visual issues or annoyances */
|
||||
/*******************************************/
|
||||
|
||||
html[data-td-font='smallest'] .badge-verified:before {
|
||||
/* fix cut off badge icon */
|
||||
width: 13px !important;
|
||||
height: 13px !important;
|
||||
background-position: -223px -98px !important;
|
||||
}
|
||||
|
||||
.account-inline .username {
|
||||
vertical-align: 10% !important;
|
||||
@@ -30,10 +74,22 @@ body {
|
||||
vertical-align: -10% !important;
|
||||
}
|
||||
|
||||
/************************************/
|
||||
/* Favorite & retweet notifications */
|
||||
/************************************/
|
||||
/****************************************/
|
||||
/* Tweak notification layout and design */
|
||||
/****************************************/
|
||||
|
||||
.activity-header.has-source-avatar {
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
|
||||
.activity-header .tweet-timestamp {
|
||||
line-height: unset !important;
|
||||
}
|
||||
|
||||
.activity-header .icon-user-filled {
|
||||
vertical-align: sub !important;
|
||||
}
|
||||
|
||||
.td-notification-padded .item-img {
|
||||
position: absolute;
|
||||
left: 21px;
|
||||
@@ -55,13 +111,13 @@ body {
|
||||
/* Media */
|
||||
/*********/
|
||||
|
||||
.media-size-medium {
|
||||
.td-notification .media-size-medium {
|
||||
max-height: 240px;
|
||||
height: calc(100vh - 20px) !important;
|
||||
border-radius: 1px !important;
|
||||
}
|
||||
|
||||
.js-quote-detail .media-size-medium {
|
||||
.td-notification .js-quote-detail .media-size-medium {
|
||||
height: calc(100vh - 28px) !important;
|
||||
}
|
||||
|
||||
|
@@ -4,7 +4,7 @@
|
||||
//
|
||||
var injectCSS = function(){
|
||||
if (!document.head){
|
||||
setTimeout(injectCSS, 25);
|
||||
setTimeout(injectCSS, 5);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
style.sheet.insertRule(rule, 0);
|
||||
};
|
||||
|
||||
addRule("body { overflow: hidden !important; background-color: #1c6399 !important; }"); // remove scrollbar and change background
|
||||
addRule("body { overflow: hidden !important; }"); // remove scrollbar
|
||||
addRule(".page-canvas { box-shadow: 0 0 150px rgba(255, 255, 255, 0.3) !important; }"); // change page box shadow
|
||||
addRule(".topbar { display: none !important; }"); // hide top bar
|
||||
|
||||
|
@@ -300,6 +300,13 @@
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Block: Check updates on startup.
|
||||
//
|
||||
$(document).one("TD.ready", function(){
|
||||
$TDU.triggerUpdateCheck();
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Setup global functions.
|
||||
//
|
||||
|
@@ -62,6 +62,7 @@
|
||||
<DefineConstants>
|
||||
</DefineConstants>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<LangVersion>7</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
@@ -107,7 +108,8 @@
|
||||
<Compile Include="Core\Handling\RequestHandlerBase.cs" />
|
||||
<Compile Include="Core\Handling\RequestHandlerBrowser.cs" />
|
||||
<Compile Include="Core\Handling\ResourceHandlerNotification.cs" />
|
||||
<Compile Include="Core\ITweetDeckBrowser.cs" />
|
||||
<Compile Include="Core\Other\Interfaces\ITweetDeckBrowser.cs" />
|
||||
<Compile Include="Core\Management\ContextInfo.cs" />
|
||||
<Compile Include="Core\Notification\Example\FormNotificationExample.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -169,6 +171,12 @@
|
||||
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsAnalytics.Designer.cs">
|
||||
<DependentUpon>DialogSettingsAnalytics.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsSearchEngine.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsSearchEngine.Designer.cs">
|
||||
<DependentUpon>DialogSettingsSearchEngine.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsCSS.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -244,7 +252,7 @@
|
||||
<Compile Include="Core\Other\Settings\TabSettingsNotifications.Designer.cs">
|
||||
<DependentUpon>TabSettingsNotifications.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Bridge\CallbackBridge.cs" />
|
||||
<Compile Include="Core\Notification\Screenshot\ScreenshotBridge.cs" />
|
||||
<Compile Include="Data\CommandLineArgs.cs" />
|
||||
<Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs">
|
||||
<SubType>Form</SubType>
|
||||
@@ -282,6 +290,7 @@
|
||||
<Compile Include="Plugins\Enums\PluginEnvironment.cs" />
|
||||
<Compile Include="Plugins\Enums\PluginGroup.cs" />
|
||||
<Compile Include="Plugins\Events\PluginErrorEventArgs.cs" />
|
||||
<Compile Include="Plugins\PluginLoader.cs" />
|
||||
<Compile Include="Plugins\PluginManager.cs" />
|
||||
<Compile Include="Plugins\PluginScriptGenerator.cs" />
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
@@ -290,6 +299,7 @@
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Reporter.cs" />
|
||||
<Compile Include="Updates\Events\UpdateCheckEventArgs.cs" />
|
||||
<Compile Include="Updates\FormUpdateDownload.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -311,7 +321,7 @@
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Resources\ScriptLoader.cs" />
|
||||
<Compile Include="Updates\UpdateEventArgs.cs" />
|
||||
<Compile Include="Updates\Events\UpdateEventArgs.cs" />
|
||||
<Compile Include="Updates\UpdaterSettings.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -394,37 +404,20 @@
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>xcopy "$(ProjectDir)LICENSE.md" "$(TargetDir)" /Y
|
||||
del "$(TargetDir)LICENSE.txt"
|
||||
ren "$(TargetDir)LICENSE.md" "LICENSE.txt"
|
||||
xcopy "$(ProjectDir)bld\Resources\CEFSHARP-LICENSE.txt" "$(TargetDir)" /Y
|
||||
|
||||
rmdir "$(TargetDir)scripts" /S /Q
|
||||
mkdir "$(TargetDir)scripts"
|
||||
xcopy "$(ProjectDir)Resources\Scripts\*" "$(TargetDir)scripts\" /E /Y
|
||||
|
||||
rmdir "$(TargetDir)plugins" /S /Q
|
||||
mkdir "$(TargetDir)plugins"
|
||||
mkdir "$(TargetDir)plugins\official"
|
||||
mkdir "$(TargetDir)plugins\user"
|
||||
xcopy "$(ProjectDir)Resources\Plugins\*" "$(TargetDir)plugins\official\" /E /Y
|
||||
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
|
||||
del "$(TargetDir)plugins\official\emoji-keyboard\emoji-instructions.txt"
|
||||
|
||||
rmdir "$(ProjectDir)bin\Debug"
|
||||
<PostBuildEvent>rmdir "$(ProjectDir)bin\Debug"
|
||||
rmdir "$(ProjectDir)bin\Release"
|
||||
|
||||
attrib +R "$(TargetDir)locales\en-US.pak"
|
||||
del /Q /A:-R "$(TargetDir)locales\*.pak"
|
||||
attrib -R "$(TargetDir)locales\en-US.pak"
|
||||
rmdir "$(TargetDir)scripts" /S /Q
|
||||
rmdir "$(TargetDir)plugins" /S /Q
|
||||
|
||||
powershell -ExecutionPolicy Unrestricted -File "$(ProjectDir)Resources\PostBuild.ps1" "$(TargetDir)\" "$(ProjectDir)\"
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
powershell -ExecutionPolicy Unrestricted -File "$(ProjectDir)Resources\PostBuild.ps1" "$(TargetDir)\"</PostBuildEvent>
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Target Name="AfterBuild" Condition="$(ConfigurationName) == Release">
|
||||
<Exec Command="del "$(TargetDir)*.pdb"" />
|
||||
|
13
Updates/Events/UpdateCheckEventArgs.cs
Normal file
13
Updates/Events/UpdateCheckEventArgs.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Updates.Events{
|
||||
sealed class UpdateCheckEventArgs : EventArgs{
|
||||
public int EventId { get; }
|
||||
public bool IsUpdateAvailable { get; }
|
||||
|
||||
public UpdateCheckEventArgs(int eventId, bool isUpdateAvailable){
|
||||
this.EventId = eventId;
|
||||
this.IsUpdateAvailable = isUpdateAvailable;
|
||||
}
|
||||
}
|
||||
}
|
11
Updates/Events/UpdateEventArgs.cs
Normal file
11
Updates/Events/UpdateEventArgs.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Updates.Events{
|
||||
sealed class UpdateEventArgs : EventArgs{
|
||||
public UpdateInfo UpdateInfo { get; }
|
||||
|
||||
public UpdateEventArgs(UpdateInfo updateInfo){
|
||||
this.UpdateInfo = updateInfo;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Updates{
|
||||
sealed class UpdateEventArgs : EventArgs{
|
||||
public int EventId { get; }
|
||||
public UpdateInfo UpdateInfo { get; }
|
||||
|
||||
public bool IsUpdateAvailable => UpdateInfo != null;
|
||||
|
||||
public UpdateEventArgs(int eventId, UpdateInfo updateInfo){
|
||||
this.EventId = eventId;
|
||||
this.UpdateInfo = updateInfo;
|
||||
}
|
||||
|
||||
public UpdateEventArgs(UpdateInfo updateInfo){
|
||||
this.EventId = updateInfo.EventId;
|
||||
this.UpdateInfo = updateInfo;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,21 +1,25 @@
|
||||
using CefSharp;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Other.Interfaces;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Resources;
|
||||
using TweetDuck.Updates.Events;
|
||||
|
||||
namespace TweetDuck.Updates{
|
||||
sealed class UpdateHandler{
|
||||
public const int CheckCodeUpdatesDisabled = -1;
|
||||
public const int CheckCodeNotOnTweetDeck = -2;
|
||||
|
||||
private readonly ITweetDeckBrowser browser;
|
||||
private readonly UpdaterSettings settings;
|
||||
|
||||
public event EventHandler<UpdateEventArgs> UpdateAccepted;
|
||||
public event EventHandler<UpdateEventArgs> UpdateDismissed;
|
||||
public event EventHandler<UpdateEventArgs> CheckFinished;
|
||||
public event EventHandler<UpdateCheckEventArgs> CheckFinished;
|
||||
|
||||
private int lastEventId;
|
||||
private ushort lastEventId;
|
||||
private UpdateInfo lastUpdateInfo;
|
||||
|
||||
public UpdateHandler(ITweetDeckBrowser browser, UpdaterSettings settings){
|
||||
@@ -27,8 +31,7 @@ namespace TweetDuck.Updates{
|
||||
}
|
||||
|
||||
private void OnFrameLoaded(IFrame frame){
|
||||
ScriptLoader.ExecuteFile(frame, "update.js");
|
||||
Check(false);
|
||||
ScriptLoader.ExecuteFile(frame, "update.js"); // TODO can't show error on failure
|
||||
}
|
||||
|
||||
public int Check(bool force){
|
||||
@@ -36,12 +39,16 @@ namespace TweetDuck.Updates{
|
||||
if (force){
|
||||
settings.DismissedUpdate = null;
|
||||
}
|
||||
|
||||
if (!browser.IsTweetDeckWebsite){
|
||||
return CheckCodeNotOnTweetDeck;
|
||||
}
|
||||
|
||||
browser.ExecuteFunction("TDUF_runUpdateCheck", ++lastEventId, Program.VersionTag, settings.DismissedUpdate ?? string.Empty, settings.AllowPreReleases);
|
||||
browser.ExecuteFunction("TDUF_runUpdateCheck", (int)unchecked(++lastEventId), Program.VersionTag, settings.DismissedUpdate ?? string.Empty, settings.AllowPreReleases);
|
||||
return lastEventId;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return CheckCodeUpdatesDisabled;
|
||||
}
|
||||
|
||||
public void BeginUpdateDownload(Form ownerForm, UpdateInfo updateInfo, Action<UpdateInfo> onSuccess){
|
||||
@@ -87,7 +94,7 @@ namespace TweetDuck.Updates{
|
||||
UpdateDismissed?.Invoke(this, args);
|
||||
}
|
||||
|
||||
private void TriggerCheckFinishedEvent(UpdateEventArgs args){
|
||||
private void TriggerCheckFinishedEvent(UpdateCheckEventArgs args){
|
||||
CheckFinished?.Invoke(this, args);
|
||||
}
|
||||
|
||||
@@ -108,8 +115,8 @@ namespace TweetDuck.Updates{
|
||||
owner.lastUpdateInfo = new UpdateInfo(owner.settings, eventId, versionTag, downloadUrl);
|
||||
owner.lastUpdateInfo.BeginSilentDownload();
|
||||
}
|
||||
|
||||
owner.TriggerCheckFinishedEvent(new UpdateEventArgs(eventId, owner.lastUpdateInfo));
|
||||
|
||||
owner.TriggerCheckFinishedEvent(new UpdateCheckEventArgs(eventId, owner.lastUpdateInfo != null));
|
||||
}
|
||||
|
||||
public void OnUpdateAccepted(){
|
||||
|
@@ -1,7 +1,12 @@
|
||||
namespace TweetDuck.Updates{
|
||||
sealed class UpdaterSettings{
|
||||
public string InstallerDownloadFolder { get; }
|
||||
|
||||
public bool AllowPreReleases { get; set; }
|
||||
public string DismissedUpdate { get; set; }
|
||||
public string InstallerDownloadFolder { get; set; }
|
||||
|
||||
public UpdaterSettings(string installerDownloadFolder){
|
||||
this.InstallerDownloadFolder = installerDownloadFolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1
bld/UPDATE ONLY.bat
Normal file
1
bld/UPDATE ONLY.bat
Normal file
@@ -0,0 +1 @@
|
||||
start "" /B "ISCC.exe" /Q "gen_upd.iss"
|
@@ -106,6 +106,15 @@ begin
|
||||
Result := (PageID = wpSelectDir) and (UpdatePath <> '')
|
||||
end;
|
||||
|
||||
{ Check the desktop icon task if not updating. }
|
||||
procedure CurPageChanged(CurPageID: Integer);
|
||||
begin
|
||||
if CurPageID = wpSelectTasks then
|
||||
begin
|
||||
WizardForm.TasksList.Checked[WizardForm.TasksList.Items.Count-1] := (UpdatePath = '');
|
||||
end;
|
||||
end;
|
||||
|
||||
{ Install VC++ if downloaded. }
|
||||
procedure CurStepChanged(CurStep: TSetupStep);
|
||||
begin
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
@@ -20,6 +20,7 @@
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<LangVersion>7</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
@@ -29,6 +30,7 @@
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<LangVersion>7</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
|
@@ -15,10 +15,12 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<LangVersion>7</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<LangVersion>7</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject />
|
||||
|
@@ -21,7 +21,19 @@ namespace UnitTests.Core{
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc/status"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://nottwitter.com/chylexmc"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/chylexmc?"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("www.twitter.com/chylexmc"));
|
||||
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/signup"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/tos"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/privacy"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/search"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/search?query"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/search-home"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/search-advanced"));
|
||||
|
||||
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/tosser"));
|
||||
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/searching"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
@@ -22,6 +22,7 @@
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<LangVersion>7</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
@@ -22,6 +22,7 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<LangVersion>7</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
@@ -33,6 +34,7 @@
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
|
||||
<LangVersion>7</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>Resources\icon.ico</ApplicationIcon>
|
||||
|
Reference in New Issue
Block a user