mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 01:32:10 +02:00
Compare commits
80 Commits
Author | SHA1 | Date | |
---|---|---|---|
979b3548db | |||
05d6c578b3 | |||
a117559063 | |||
f87c649b09 | |||
6504dc9184 | |||
25a8ddffd4 | |||
fa0f9b89cf | |||
4d00a67891 | |||
bd2c43e1f4 | |||
c7279eaa34 | |||
fd523e552c | |||
cb877b8b23 | |||
ed1bee8b89 | |||
a8e1492056 | |||
5587216c01 | |||
86569261ad | |||
4a9049c7aa | |||
75d60a8182 | |||
14d4dc2ed9 | |||
fd0e1740a5 | |||
70ca890bef | |||
b9318dfd8e | |||
995642a719 | |||
d14de4ac9e | |||
b7f325a241 | |||
27c55718c2 | |||
421ff0654b | |||
ed947458f9 | |||
9cdb20ba84 | |||
d8774b735f | |||
adcb42695f | |||
dd77b5bcbb | |||
d2445be155 | |||
10254c8af7 | |||
d7e830badf | |||
b445a3a9b8 | |||
97f42ead66 | |||
03730fafb9 | |||
0be9465dca | |||
d7f1df4995 | |||
3cb0f90706 | |||
a3e9b15a8a | |||
00bfa68a57 | |||
c311e24f08 | |||
1cdd4e4455 | |||
8078c0081a | |||
a867e1fc40 | |||
61da36ac1c | |||
720ca2a018 | |||
b39c593552 | |||
c808952a45 | |||
b468d7a766 | |||
28578f60be | |||
92a39e2527 | |||
1bce5e4342 | |||
68f586e104 | |||
d27a66202e | |||
07de2f450c | |||
3c03726634 | |||
6fb2643063 | |||
5eef6c8196 | |||
829c332e13 | |||
47eec14bca | |||
e7ee1d6be7 | |||
e41b5e5ff7 | |||
ba1bacd08c | |||
1029ea5840 | |||
339eaf0195 | |||
aa1e1549d8 | |||
1f8ae9ef80 | |||
65165de060 | |||
485836d2ce | |||
64c07c14d9 | |||
b6313c2b72 | |||
58124b5821 | |||
b6a599f8a6 | |||
19a6bc0dbd | |||
8cb81d44ee | |||
22d0a372d8 | |||
988fae75c3 |
@@ -6,6 +6,8 @@ namespace TweetDuck.Configuration{
|
||||
// public args
|
||||
public const string ArgDataFolder = "-datafolder";
|
||||
public const string ArgLogging = "-log";
|
||||
public const string ArgIgnoreGDPR = "-nogdpr";
|
||||
public const string ArgNotificationScrollWA = "-nscrollwa";
|
||||
|
||||
// internal args
|
||||
public const string ArgRestart = "-restart";
|
||||
|
@@ -64,6 +64,10 @@ namespace TweetDuck.Core.Bridge{
|
||||
});
|
||||
}
|
||||
|
||||
public void SetRightClickedLink(string type, string url){
|
||||
ContextInfo.SetLink(type, url);
|
||||
}
|
||||
|
||||
public void SetRightClickedChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
||||
ContextInfo.SetChirp(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
||||
}
|
||||
@@ -99,10 +103,6 @@ namespace TweetDuck.Core.Bridge{
|
||||
|
||||
// Global
|
||||
|
||||
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){
|
||||
notification.InvokeAsyncSafe(() => {
|
||||
form.OnTweetNotification();
|
||||
|
6
Core/FormBrowser.Designer.cs
generated
6
Core/FormBrowser.Designer.cs
generated
@@ -39,17 +39,17 @@
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = TweetDuck.Core.Utils.TwitterUtils.BackgroundColor;
|
||||
this.ClientSize = new System.Drawing.Size(400, 386);
|
||||
this.ClientSize = new System.Drawing.Size(1008, 730);
|
||||
this.Icon = Properties.Resources.icon;
|
||||
this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;
|
||||
this.MinimumSize = new System.Drawing.Size(416, 424);
|
||||
this.MinimumSize = new System.Drawing.Size(348, 424);
|
||||
this.Name = "FormBrowser";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
|
||||
this.Activated += new System.EventHandler(this.FormBrowser_Activated);
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormBrowser_FormClosing);
|
||||
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.FormBrowser_FormClosed);
|
||||
this.LocationChanged += new System.EventHandler(this.FormBrowser_LocationChanged);
|
||||
this.ResizeEnd += new System.EventHandler(this.FormBrowser_ResizeEnd);
|
||||
this.LocationChanged += new System.EventHandler(this.FormBrowser_LocationChanged);
|
||||
this.Resize += new System.EventHandler(this.FormBrowser_Resize);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
|
@@ -28,6 +28,7 @@ namespace TweetDuck.Core.Handling{
|
||||
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26506;
|
||||
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26507;
|
||||
private const CefMenuCommand MenuSearchInBrowser = (CefMenuCommand)26508;
|
||||
private const CefMenuCommand MenuReadApplyROT13 = (CefMenuCommand)26509;
|
||||
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
|
||||
|
||||
protected ContextInfo.LinkInfo LastLink { get; private set; }
|
||||
@@ -57,6 +58,8 @@ namespace TweetDuck.Core.Handling{
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
|
||||
model.AddItem(MenuSearchInBrowser, "Search in browser");
|
||||
model.AddSeparator();
|
||||
model.AddItem(MenuReadApplyROT13, "Apply ROT13");
|
||||
model.AddSeparator();
|
||||
}
|
||||
|
||||
bool hasTweetImage = LastLink.IsImage;
|
||||
@@ -126,7 +129,7 @@ namespace TweetDuck.Core.Handling{
|
||||
SetClipboardText(control, TwitterUtils.GetMediaLink(LastLink.GetMediaSource(parameters), ImageQuality));
|
||||
break;
|
||||
|
||||
case MenuViewImage:
|
||||
case MenuViewImage: {
|
||||
void ViewImage(string path){
|
||||
string ext = Path.GetExtension(path);
|
||||
|
||||
@@ -141,37 +144,60 @@ namespace TweetDuck.Core.Handling{
|
||||
string url = LastLink.GetMediaSource(parameters);
|
||||
string file = Path.Combine(BrowserCache.CacheFolder, TwitterUtils.GetImageFileName(url) ?? Path.GetRandomFileName());
|
||||
|
||||
if (File.Exists(file)){
|
||||
ViewImage(file);
|
||||
}
|
||||
else{
|
||||
control.InvokeAsyncSafe(analytics.AnalyticsFile.ViewedImages.Trigger);
|
||||
|
||||
BrowserUtils.DownloadFileAsync(TwitterUtils.GetMediaLink(url, ImageQuality), file, () => {
|
||||
control.InvokeAsyncSafe(() => {
|
||||
if (File.Exists(file)){
|
||||
ViewImage(file);
|
||||
}, ex => {
|
||||
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
|
||||
});
|
||||
}
|
||||
}
|
||||
else{
|
||||
analytics.AnalyticsFile.ViewedImages.Trigger();
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case MenuSaveMedia: {
|
||||
bool isVideo = LastLink.IsVideo;
|
||||
string url = LastLink.GetMediaSource(parameters);
|
||||
string username = LastChirp.Authors.LastOrDefault();
|
||||
|
||||
control.InvokeAsyncSafe(() => {
|
||||
if (isVideo){
|
||||
TwitterUtils.DownloadVideo(url, username);
|
||||
analytics.AnalyticsFile.DownloadedVideos.Trigger();
|
||||
}
|
||||
else{
|
||||
TwitterUtils.DownloadImage(url, username, ImageQuality);
|
||||
analytics.AnalyticsFile.DownloadedImages.Trigger();
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case MenuSaveMedia:
|
||||
if (LastLink.IsVideo){
|
||||
control.InvokeAsyncSafe(analytics.AnalyticsFile.DownloadedVideos.Trigger);
|
||||
TwitterUtils.DownloadVideo(LastLink.GetMediaSource(parameters), LastChirp.Authors.LastOrDefault());
|
||||
}
|
||||
else{
|
||||
control.InvokeAsyncSafe(analytics.AnalyticsFile.DownloadedImages.Trigger);
|
||||
TwitterUtils.DownloadImage(LastLink.GetMediaSource(parameters), LastChirp.Authors.LastOrDefault(), ImageQuality);
|
||||
}
|
||||
case MenuSaveTweetImages: {
|
||||
string[] urls = LastChirp.Images;
|
||||
string username = LastChirp.Authors.LastOrDefault();
|
||||
|
||||
control.InvokeAsyncSafe(() => {
|
||||
TwitterUtils.DownloadImages(urls, username, ImageQuality);
|
||||
analytics.AnalyticsFile.DownloadedImages.Trigger();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case MenuSaveTweetImages:
|
||||
control.InvokeAsyncSafe(analytics.AnalyticsFile.DownloadedImages.Trigger);
|
||||
TwitterUtils.DownloadImages(LastChirp.Images, LastChirp.Authors.LastOrDefault(), ImageQuality);
|
||||
break;
|
||||
case MenuReadApplyROT13:
|
||||
string selection = parameters.SelectionText;
|
||||
control.InvokeAsyncSafe(() => FormMessage.Information("ROT13", StringUtils.ConvertRot13(selection), FormMessage.OK));
|
||||
control.InvokeAsyncSafe(analytics.AnalyticsFile.UsedROT13.Trigger);
|
||||
return true;
|
||||
|
||||
case MenuSearchInBrowser:
|
||||
string query = parameters.SelectionText;
|
||||
@@ -195,15 +221,15 @@ namespace TweetDuck.Core.Handling{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void DeselectAll(IFrame frame){
|
||||
protected static void DeselectAll(IFrame frame){
|
||||
ScriptLoader.ExecuteScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect");
|
||||
}
|
||||
|
||||
protected void OpenBrowser(Control control, string url){
|
||||
protected static void OpenBrowser(Control control, string url){
|
||||
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
|
||||
}
|
||||
|
||||
protected void SetClipboardText(Control control, string text){
|
||||
protected static void SetClipboardText(Control control, string text){
|
||||
control.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
|
||||
}
|
||||
|
||||
|
@@ -16,7 +16,7 @@ namespace TweetDuck.Core.Handling{
|
||||
private const CefMenuCommand MenuOpenQuotedTweetUrl = (CefMenuCommand)26612;
|
||||
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26613;
|
||||
private const CefMenuCommand MenuScreenshotTweet = (CefMenuCommand)26614;
|
||||
private const CefMenuCommand MenuInputApplyROT13 = (CefMenuCommand)26615;
|
||||
private const CefMenuCommand MenuWriteApplyROT13 = (CefMenuCommand)26615;
|
||||
private const CefMenuCommand MenuSearchInColumn = (CefMenuCommand)26616;
|
||||
|
||||
private const string TitleReloadBrowser = "Reload browser";
|
||||
@@ -44,7 +44,7 @@ namespace TweetDuck.Core.Handling{
|
||||
if (isSelecting){
|
||||
if (isEditing){
|
||||
model.AddSeparator();
|
||||
model.AddItem(MenuInputApplyROT13, "Apply ROT13");
|
||||
model.AddItem(MenuWriteApplyROT13, "Apply ROT13");
|
||||
}
|
||||
|
||||
model.AddSeparator();
|
||||
@@ -141,7 +141,7 @@ namespace TweetDuck.Core.Handling{
|
||||
SetClipboardText(form, LastChirp.QuoteUrl);
|
||||
return true;
|
||||
|
||||
case MenuInputApplyROT13:
|
||||
case MenuWriteApplyROT13:
|
||||
form.InvokeAsyncSafe(form.ApplyROT13);
|
||||
return true;
|
||||
|
||||
|
@@ -1,12 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using CefSharp;
|
||||
using CefSharp.Enums;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
sealed class DragHandlerBrowser : IDragHandler{
|
||||
private readonly RequestHandlerBrowser requestHandler;
|
||||
|
||||
public DragHandlerBrowser(RequestHandlerBrowser requestHandler){
|
||||
this.requestHandler = requestHandler;
|
||||
}
|
||||
|
||||
public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask){
|
||||
void TriggerDragStart(string type, string data = null){
|
||||
browserControl.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", type, data);
|
||||
}
|
||||
|
||||
requestHandler.BlockNextUserNavUrl = dragData.LinkUrl; // empty if not a link
|
||||
|
||||
if (dragData.IsLink){
|
||||
TriggerDragStart("link", dragData.LinkUrl);
|
||||
|
@@ -5,6 +5,12 @@ using TweetDuck.Core.Handling.General;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
class RequestHandlerBase : DefaultRequestHandler{
|
||||
private readonly bool autoReload;
|
||||
|
||||
public RequestHandlerBase(bool autoReload){
|
||||
this.autoReload = autoReload;
|
||||
}
|
||||
|
||||
public override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture){
|
||||
return LifeSpanHandler.HandleLinkClick(browserControl, targetDisposition, targetUrl);
|
||||
}
|
||||
@@ -18,5 +24,11 @@ namespace TweetDuck.Core.Handling{
|
||||
|
||||
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
||||
}
|
||||
|
||||
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
|
||||
if (autoReload){
|
||||
browser.Reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,9 +3,9 @@ using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
sealed class RequestHandlerBrowser : RequestHandlerBase{
|
||||
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
|
||||
browser.Reload();
|
||||
}
|
||||
public string BlockNextUserNavUrl { get; set; }
|
||||
|
||||
public RequestHandlerBrowser() : base(true){}
|
||||
|
||||
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
|
||||
if (request.ResourceType == ResourceType.Script && request.Url.Contains("analytics.")){
|
||||
@@ -16,6 +16,16 @@ namespace TweetDuck.Core.Handling{
|
||||
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
||||
}
|
||||
|
||||
public override bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect){
|
||||
if (userGesture && request.TransitionType == TransitionType.LinkClicked){
|
||||
bool block = request.Url == BlockNextUserNavUrl;
|
||||
BlockNextUserNavUrl = string.Empty;
|
||||
return block;
|
||||
}
|
||||
|
||||
return base.OnBeforeBrowse(browserControl, browser, frame, request, userGesture, isRedirect);
|
||||
}
|
||||
|
||||
public override bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
|
||||
if (request.ResourceType == ResourceType.Image && request.Url.Contains("backgrounds/spinner_blue")){
|
||||
request.Url = TwitterUtils.LoadingSpinner.Url;
|
||||
|
@@ -1,4 +1,6 @@
|
||||
using System.Windows.Forms;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Resources;
|
||||
@@ -21,9 +23,13 @@ namespace TweetDuck.Core.Notification.Example{
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler Ready;
|
||||
|
||||
private readonly TweetNotification exampleNotification;
|
||||
|
||||
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){
|
||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
|
||||
string exampleTweetHTML = ScriptLoader.LoadResource("pages/example.html", true)?.Replace("{avatar}", TweetNotification.AppLogo.Url) ?? string.Empty;
|
||||
|
||||
#if DEBUG
|
||||
@@ -33,6 +39,13 @@ namespace TweetDuck.Core.Notification.Example{
|
||||
exampleNotification = TweetNotification.Example(exampleTweetHTML, 176);
|
||||
}
|
||||
|
||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||
if (!e.IsLoading){
|
||||
Ready?.Invoke(this, EventArgs.Empty);
|
||||
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public override void HideNotification(){
|
||||
Location = ControlExtensions.InvisibleLocation;
|
||||
}
|
||||
|
@@ -1,6 +1,4 @@
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using System;
|
||||
using CefSharp.WinForms;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
@@ -65,7 +63,7 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation;
|
||||
protected bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation;
|
||||
protected virtual bool CanDragWindow => true;
|
||||
|
||||
public new Point Location{
|
||||
@@ -109,15 +107,13 @@ namespace TweetDuck.Core.Notification{
|
||||
public string CurrentQuoteUrl => currentNotification?.QuoteUrl;
|
||||
|
||||
public bool CanViewDetail => currentNotification != null && !string.IsNullOrEmpty(currentNotification.ColumnId) && !string.IsNullOrEmpty(currentNotification.ChirpId);
|
||||
public bool IsPaused => pauseCounter > 0;
|
||||
|
||||
protected bool IsPaused => pauseCounter > 0;
|
||||
protected bool IsCursorOverBrowser => browser.Bounds.Contains(PointToClient(Cursor.Position));
|
||||
|
||||
public bool FreezeTimer { get; set; }
|
||||
public bool ContextMenuOpen { get; set; }
|
||||
|
||||
public event EventHandler Initialized;
|
||||
|
||||
protected FormNotificationBase(FormBrowser owner, bool enableContextMenu){
|
||||
InitializeComponent();
|
||||
|
||||
@@ -128,12 +124,11 @@ namespace TweetDuck.Core.Notification{
|
||||
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBase()
|
||||
RequestHandler = new RequestHandlerBase(false)
|
||||
};
|
||||
|
||||
this.browser.Dock = DockStyle.None;
|
||||
this.browser.ClientSize = ClientSize;
|
||||
this.browser.IsBrowserInitializedChanged += browser_IsBrowserInitializedChanged;
|
||||
|
||||
browser.SetupResourceHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
|
||||
browser.SetupResourceHandler(TweetNotification.AppLogo);
|
||||
@@ -165,12 +160,6 @@ namespace TweetDuck.Core.Notification{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
|
||||
if (e.IsBrowserInitialized){
|
||||
Initialized?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
// notification methods
|
||||
|
||||
public virtual void HideNotification(){
|
||||
|
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
@@ -65,7 +66,7 @@ namespace TweetDuck.Core.Notification{
|
||||
get{
|
||||
switch(Program.UserConfig.NotificationSize){
|
||||
default:
|
||||
return BrowserUtils.Scale(122, SizeScale*(1.0+0.075*FontSizeLevel));
|
||||
return BrowserUtils.Scale(122, SizeScale*(1.0+0.08*FontSizeLevel));
|
||||
|
||||
case TweetNotification.Size.Custom:
|
||||
return Program.UserConfig.CustomNotificationSize.Height;
|
||||
@@ -136,7 +137,14 @@ namespace TweetDuck.Core.Notification{
|
||||
int eventType = wParam.ToInt32();
|
||||
|
||||
if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser){
|
||||
browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Program.UserConfig.NotificationScrollSpeed*0.01), CefEventFlags.None);
|
||||
if (Arguments.HasFlag(Arguments.ArgNotificationScrollWA)){
|
||||
int delta = BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Program.UserConfig.NotificationScrollSpeed*0.01);
|
||||
browser.ExecuteScriptAsync("window.scrollBy", 0, -Math.Round(delta/0.72));
|
||||
}
|
||||
else{
|
||||
browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Program.UserConfig.NotificationScrollSpeed*0.01), CefEventFlags.None);
|
||||
}
|
||||
|
||||
return NativeMethods.HOOK_HANDLED;
|
||||
}
|
||||
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){
|
||||
@@ -255,7 +263,7 @@ namespace TweetDuck.Core.Notification{
|
||||
string html = base.GetTweetHTML(tweet);
|
||||
|
||||
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
||||
html = injection.Inject(html);
|
||||
html = injection.InjectInto(html);
|
||||
}
|
||||
|
||||
return html;
|
||||
|
@@ -35,7 +35,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
||||
}
|
||||
|
||||
using(IFrame frame = args.Browser.MainFrame){
|
||||
ScriptLoader.ExecuteScript(frame, script.Replace("{width}", BrowserUtils.Scale(width, DpiScale).ToString()), "screenshot");
|
||||
ScriptLoader.ExecuteScript(frame, script.Replace("{width}", BrowserUtils.Scale(width, DpiScale).ToString()).Replace("{frames}", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
||||
string html = tweet.GenerateHtml("td-screenshot");
|
||||
|
||||
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
||||
html = injection.Inject(html);
|
||||
html = injection.InjectInto(html);
|
||||
}
|
||||
|
||||
return html;
|
||||
@@ -68,6 +68,10 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!WindowsUtils.IsAeroEnabled){
|
||||
MoveToVisibleLocation(); // TODO make this look nicer I guess
|
||||
}
|
||||
|
||||
IntPtr context = NativeMethods.GetDC(this.Handle);
|
||||
|
||||
|
@@ -27,6 +27,10 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
||||
#if GEN_SCREENSHOT_FRAMES
|
||||
private readonly Timer debugger;
|
||||
private int frameCounter;
|
||||
|
||||
public const int WaitFrames = 60;
|
||||
#else
|
||||
public const int WaitFrames = 5;
|
||||
#endif
|
||||
|
||||
private FormNotificationScreenshotable screenshot;
|
||||
|
@@ -7,7 +7,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 const string DefaultHeadLayout = @"<html class=""scroll-v os-windows dark txt-size--14"" lang=""en-US"" id=""tduck"" data-td-font=""medium"" data-td-theme=""dark""><head><meta charset=""utf-8""><link href=""https://ton.twimg.com/tweetdeck-web/web/dist/bundle.4b1f87e09d.css"" rel=""stylesheet""><style type='text/css'>body { background: rgb(34, 36, 38) !important }</style>";
|
||||
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"));
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace TweetDuck.Core.Notification{
|
||||
build.Append(' ').Append(bodyClasses);
|
||||
}
|
||||
|
||||
build.Append('\'').Append(isExample ? " td-example-notification" : "").Append("><div class='column' style='width:100%!important;height:auto!important;overflow:initial!important;'>");
|
||||
build.Append('\'').Append(isExample ? " td-example-notification" : "").Append("><div class='column' style='width:100%!important;min-height:100vh!important;height:auto!important;overflow:initial!important;'>");
|
||||
build.Append(html);
|
||||
build.Append("</div></body>");
|
||||
build.Append("</html>");
|
||||
|
@@ -42,7 +42,6 @@ namespace TweetDuck.Core.Other.Analytics{
|
||||
{ "Clear Cache Threshold" , Exact(SysConfig.ClearCacheThreshold) },
|
||||
0,
|
||||
{ "Expand Links" , Bool(UserConfig.ExpandLinksOnHover) },
|
||||
{ "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) },
|
||||
|
@@ -8,6 +8,7 @@ using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Handling.General;
|
||||
using TweetDuck.Core.Utils;
|
||||
using System.Text.RegularExpressions;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Core.Other{
|
||||
@@ -15,6 +16,8 @@ namespace TweetDuck.Core.Other{
|
||||
private const string GuideUrl = "https://tweetduck.chylex.com/guide/v2/";
|
||||
private const string GuidePathRegex = @"^guide(?:/v\d+)?(?:/(#.*))?";
|
||||
|
||||
private static readonly ResourceLink DummyPage = new ResourceLink("http://td/dummy", ResourceHandler.FromString(""));
|
||||
|
||||
public static bool CheckGuideUrl(string url, out string hash){
|
||||
if (!url.Contains("//tweetduck.chylex.com/guide")){
|
||||
hash = null;
|
||||
@@ -53,6 +56,7 @@ namespace TweetDuck.Core.Other{
|
||||
}
|
||||
|
||||
private readonly ChromiumWebBrowser browser;
|
||||
private string nextUrl;
|
||||
|
||||
private FormGuide(string url, FormBrowser owner){
|
||||
InitializeComponent();
|
||||
@@ -65,7 +69,7 @@ namespace TweetDuck.Core.Other{
|
||||
MenuHandler = new ContextMenuGuide(owner),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBrowser()
|
||||
RequestHandler = new RequestHandlerBase(true)
|
||||
};
|
||||
|
||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
@@ -75,6 +79,9 @@ namespace TweetDuck.Core.Other{
|
||||
browser.BrowserSettings.BackgroundColor = (uint)BackColor.ToArgb();
|
||||
browser.Dock = DockStyle.None;
|
||||
browser.Location = ControlExtensions.InvisibleLocation;
|
||||
|
||||
browser.SetupResourceHandler(DummyPage);
|
||||
|
||||
Controls.Add(browser);
|
||||
|
||||
Disposed += (sender, args) => {
|
||||
@@ -86,21 +93,26 @@ namespace TweetDuck.Core.Other{
|
||||
}
|
||||
|
||||
private void Reload(string url){
|
||||
nextUrl = url;
|
||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
browser.Dock = DockStyle.None;
|
||||
browser.Location = ControlExtensions.InvisibleLocation;
|
||||
browser.Load("about:blank");
|
||||
browser.Load(url);
|
||||
browser.Load(DummyPage.Url);
|
||||
}
|
||||
|
||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||
if (!e.IsLoading && browser.Address != "about:blank"){
|
||||
this.InvokeAsyncSafe(() => {
|
||||
browser.Location = Point.Empty;
|
||||
browser.Dock = DockStyle.Fill;
|
||||
});
|
||||
if (!e.IsLoading){
|
||||
if (browser.Address == DummyPage.Url){
|
||||
browser.Load(nextUrl);
|
||||
}
|
||||
else{
|
||||
this.InvokeAsyncSafe(() => {
|
||||
browser.Location = Point.Empty;
|
||||
browser.Dock = DockStyle.Fill;
|
||||
});
|
||||
|
||||
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
||||
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +121,6 @@ namespace TweetDuck.Core.Other{
|
||||
}
|
||||
|
||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||
// idiot chromium
|
||||
ScriptLoader.ExecuteScript(e.Frame, "Array.prototype.forEach.call(document.getElementsByTagName('A'), ele => ele.addEventListener('click', e => { e.preventDefault(); window.open(ele.getAttribute('href')); }))", "gen:links");
|
||||
}
|
||||
|
||||
|
@@ -63,10 +63,10 @@
|
||||
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(431, 30);
|
||||
this.labelInfo.Size = new System.Drawing.Size(434, 30);
|
||||
this.labelInfo.TabIndex = 0;
|
||||
this.labelInfo.Text = "When enabled, this data will be sent over a secure network roughly twice a week.\r" +
|
||||
"\nSome numbers in the report were made imprecise on purpose.";
|
||||
this.labelInfo.Text = "When enabled, this data will be sent over a secure network roughly every 14 days." +
|
||||
"\r\nSome numbers in the report were made imprecise on purpose.";
|
||||
//
|
||||
// DialogSettingsAnalytics
|
||||
//
|
||||
|
@@ -41,7 +41,7 @@
|
||||
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnCancel.AutoSize = true;
|
||||
this.btnCancel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.btnCancel.Location = new System.Drawing.Point(214, 148);
|
||||
this.btnCancel.Location = new System.Drawing.Point(215, 146);
|
||||
this.btnCancel.Name = "btnCancel";
|
||||
this.btnCancel.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||
this.btnCancel.Size = new System.Drawing.Size(57, 25);
|
||||
@@ -55,7 +55,7 @@
|
||||
this.btnRestart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnRestart.AutoSize = true;
|
||||
this.btnRestart.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.btnRestart.Location = new System.Drawing.Point(151, 148);
|
||||
this.btnRestart.Location = new System.Drawing.Point(152, 146);
|
||||
this.btnRestart.Name = "btnRestart";
|
||||
this.btnRestart.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||
this.btnRestart.Size = new System.Drawing.Size(57, 25);
|
||||
@@ -74,7 +74,7 @@
|
||||
this.cbLogging.Size = new System.Drawing.Size(70, 19);
|
||||
this.cbLogging.TabIndex = 0;
|
||||
this.cbLogging.Text = "Logging";
|
||||
this.toolTip.SetToolTip(this.cbLogging, "Logging JavaScript output into TD_Console.txt file in the data folder.");
|
||||
this.toolTip.SetToolTip(this.cbLogging, "Logs JavaScript output into TD_Console.txt file in the data folder.");
|
||||
this.cbLogging.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// tbDataFolder
|
||||
@@ -136,7 +136,7 @@
|
||||
this.flowPanel.Location = new System.Drawing.Point(9, 9);
|
||||
this.flowPanel.Margin = new System.Windows.Forms.Padding(0);
|
||||
this.flowPanel.Name = "flowPanel";
|
||||
this.flowPanel.Size = new System.Drawing.Size(266, 138);
|
||||
this.flowPanel.Size = new System.Drawing.Size(266, 136);
|
||||
this.flowPanel.TabIndex = 0;
|
||||
this.flowPanel.WrapContents = false;
|
||||
//
|
||||
@@ -144,7 +144,7 @@
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(284, 185);
|
||||
this.ClientSize = new System.Drawing.Size(284, 183);
|
||||
this.Controls.Add(this.flowPanel);
|
||||
this.Controls.Add(this.btnRestart);
|
||||
this.Controls.Add(this.btnCancel);
|
||||
|
@@ -12,7 +12,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
|
||||
cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging);
|
||||
cbLogging.CheckedChanged += control_Change;
|
||||
|
||||
|
||||
if (Program.IsPortable){
|
||||
tbDataFolder.Text = "Not available in portable version";
|
||||
tbDataFolder.Enabled = false;
|
||||
@@ -33,7 +33,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
if (cbLogging.Checked){
|
||||
Args.AddFlag(Arguments.ArgLogging);
|
||||
}
|
||||
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled){
|
||||
Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text);
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
|
||||
this.notification = notification;
|
||||
|
||||
this.notification.Initialized += (sender, args) => {
|
||||
this.notification.Ready += (sender, args) => {
|
||||
this.InvokeAsyncSafe(() => {
|
||||
this.notification.ShowExampleNotification(true);
|
||||
this.notification.Move += notification_Move;
|
||||
|
@@ -4,6 +4,7 @@ using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
@@ -39,14 +40,16 @@ namespace TweetDuck.Core{
|
||||
private string prevSoundNotificationPath = null;
|
||||
|
||||
public TweetDeckBrowser(FormBrowser owner, TweetDeckBridge bridge){
|
||||
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
|
||||
|
||||
this.browser = new ChromiumWebBrowser(TwitterUtils.TweetDeckURL){
|
||||
DialogHandler = new FileDialogHandler(),
|
||||
DragHandler = new DragHandlerBrowser(),
|
||||
DragHandler = new DragHandlerBrowser(requestHandler),
|
||||
MenuHandler = new ContextMenuBrowser(owner),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
KeyboardHandler = new KeyboardHandlerBrowser(owner),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBrowser()
|
||||
RequestHandler = requestHandler
|
||||
};
|
||||
|
||||
this.browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
@@ -154,6 +157,10 @@ namespace TweetDuck.Core{
|
||||
|
||||
TweetDeckBridge.ResetStaticProperties();
|
||||
|
||||
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)){
|
||||
ScriptLoader.ExecuteScript(frame, @"TD.storage.Account.prototype.requiresConsent = function(){ return false; }", "gen:gdpr");
|
||||
}
|
||||
|
||||
if (Program.UserConfig.FirstRun){
|
||||
ScriptLoader.ExecuteFile(frame, "introduction.js", browser);
|
||||
}
|
||||
|
@@ -11,8 +11,9 @@ using TweetDuck.Data;
|
||||
|
||||
namespace TweetDuck.Core.Utils{
|
||||
static class BrowserUtils{
|
||||
public static string HeaderUserAgent => Program.BrandName+" "+Application.ProductVersion;
|
||||
|
||||
public static string UserAgentVanilla => Program.BrandName+" "+Application.ProductVersion;
|
||||
public static string UserAgentChrome => "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"+Cef.ChromiumVersion+" Safari/537.36";
|
||||
|
||||
public static void SetupCefArgs(IDictionary<string, string> args){
|
||||
if (!Program.SystemConfig.HardwareAcceleration){
|
||||
args["disable-gpu"] = "1";
|
||||
@@ -167,7 +168,7 @@ namespace TweetDuck.Core.Utils{
|
||||
WindowsUtils.EnsureTLS12();
|
||||
|
||||
WebClient client = new WebClient{ Proxy = null };
|
||||
client.Headers[HttpRequestHeader.UserAgent] = HeaderUserAgent;
|
||||
client.Headers[HttpRequestHeader.UserAgent] = UserAgentVanilla;
|
||||
return client;
|
||||
}
|
||||
|
||||
|
@@ -71,6 +71,9 @@ namespace TweetDuck.Core.Utils{
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);
|
||||
|
||||
[DllImport("dwmapi.dll")]
|
||||
public static extern int DwmIsCompositionEnabled(out bool enabled);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool ShowScrollBar(IntPtr hWnd, int wBar, bool bShow);
|
||||
|
@@ -19,11 +19,23 @@ namespace TweetDuck.Core.Utils{
|
||||
return Regex.Replace(str, @"(\p{Ll})(\P{Ll})|(\P{Ll})(\P{Ll}\p{Ll})", "$1$3_$2$4").ToUpper();
|
||||
}
|
||||
|
||||
public static string ConvertRot13(string str){
|
||||
return Regex.Replace(str, @"[a-zA-Z]", match => {
|
||||
int code = match.Value[0];
|
||||
int start = code <= 90 ? 65 : 97;
|
||||
return ((char)(start+(code-start+13)%26)).ToString();
|
||||
});
|
||||
}
|
||||
|
||||
public static int CountOccurrences(string source, string substring){
|
||||
int count = 0, index = 0;
|
||||
if (substring.Length == 0){
|
||||
throw new ArgumentOutOfRangeException(nameof(substring), "Searched substring must not be empty.");
|
||||
}
|
||||
|
||||
int count = 0, index = 0, length = substring.Length;
|
||||
|
||||
while((index = source.IndexOf(substring, index)) != -1){
|
||||
index += substring.Length;
|
||||
index += length;
|
||||
++count;
|
||||
}
|
||||
|
||||
|
@@ -15,10 +15,12 @@ namespace TweetDuck.Core.Utils{
|
||||
private static readonly Lazy<Regex> RegexStripHtmlStyles = new Lazy<Regex>(() => new Regex(@"\s?(?:style|class)="".*?"""), false);
|
||||
private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false);
|
||||
|
||||
private static readonly bool IsWindows8OrNewer;
|
||||
private static bool HasMicrosoftBeenBroughtTo2008Yet;
|
||||
|
||||
public static int CurrentProcessID { get; }
|
||||
public static bool ShouldAvoidToolWindow { get; }
|
||||
|
||||
private static bool HasMicrosoftBeenBroughtTo2008Yet;
|
||||
public static bool IsAeroEnabled => IsWindows8OrNewer || (NativeMethods.DwmIsCompositionEnabled(out bool isCompositionEnabled) == 0 && isCompositionEnabled);
|
||||
|
||||
static WindowsUtils(){
|
||||
using(Process me = Process.GetCurrentProcess()){
|
||||
@@ -26,7 +28,9 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
|
||||
Version ver = Environment.OSVersion.Version;
|
||||
ShouldAvoidToolWindow = ver.Major == 6 && ver.Minor == 2; // windows 8/10
|
||||
IsWindows8OrNewer = ver.Major == 6 && ver.Minor == 2; // windows 8/10
|
||||
|
||||
ShouldAvoidToolWindow = IsWindows8OrNewer;
|
||||
}
|
||||
|
||||
public static void EnsureTLS12(){
|
||||
|
@@ -16,7 +16,7 @@ namespace TweetDuck.Data{
|
||||
this.html = html;
|
||||
}
|
||||
|
||||
public string Inject(string targetHTML){
|
||||
public string InjectInto(string targetHTML){
|
||||
int index = targetHTML.IndexOf(search, StringComparison.Ordinal);
|
||||
|
||||
if (index == -1){
|
||||
|
@@ -84,14 +84,20 @@ namespace TweetDuck.Plugins{
|
||||
|
||||
try{
|
||||
string folderPathName = new DirectoryInfo(rootFolder).FullName;
|
||||
DirectoryInfo currentInfo = new DirectoryInfo(fullPath);
|
||||
DirectoryInfo currentInfo = new DirectoryInfo(fullPath); // initially points to the file, which is convenient for the Attributes check below
|
||||
DirectoryInfo parentInfo = currentInfo.Parent;
|
||||
|
||||
while(currentInfo.Parent != null){
|
||||
if (currentInfo.Parent.FullName == folderPathName){
|
||||
while(parentInfo != null){
|
||||
if (currentInfo.Exists && currentInfo.Attributes.HasFlag(FileAttributes.ReparsePoint)){
|
||||
return string.Empty; // no reason why a plugin should have files/folders with symlinks, junctions, or any other crap
|
||||
}
|
||||
|
||||
if (parentInfo.FullName == folderPathName){
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
currentInfo = currentInfo.Parent;
|
||||
|
||||
currentInfo = parentInfo;
|
||||
parentInfo = currentInfo.Parent;
|
||||
}
|
||||
}
|
||||
catch{
|
||||
|
@@ -19,7 +19,7 @@ namespace TweetDuck{
|
||||
public const string BrandName = "TweetDuck";
|
||||
public const string Website = "https://tweetduck.chylex.com";
|
||||
|
||||
public const string VersionTag = "1.13.5";
|
||||
public const string VersionTag = "1.14.4";
|
||||
|
||||
public static readonly string ProgramPath = AppDomain.CurrentDomain.BaseDirectory;
|
||||
public static readonly bool IsPortable = File.Exists(Path.Combine(ProgramPath, "makeportable"));
|
||||
@@ -133,7 +133,7 @@ namespace TweetDuck{
|
||||
CefSharpSettings.LegacyJavascriptBindingEnabled = true;
|
||||
|
||||
CefSettings settings = new CefSettings{
|
||||
UserAgent = BrowserUtils.HeaderUserAgent,
|
||||
UserAgent = BrowserUtils.UserAgentChrome,
|
||||
BrowserSubprocessPath = BrandName+".Browser.exe",
|
||||
CachePath = StoragePath,
|
||||
UserDataPath = CefDataPath,
|
||||
@@ -142,7 +142,7 @@ namespace TweetDuck{
|
||||
LogSeverity = Arguments.HasFlag(Arguments.ArgLogging) ? LogSeverity.Info : LogSeverity.Disable
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
CommandLineArgs.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
|
||||
BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs);
|
||||
|
||||
@@ -157,7 +157,7 @@ namespace TweetDuck{
|
||||
ExitCleanup();
|
||||
|
||||
// ProgramPath has a trailing backslash
|
||||
string updaterArgs = "/SP- /SILENT /CLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+Arguments.GetCurrentForInstallerCmd()+"\""+(IsPortable ? " /PORTABLE=1" : "");
|
||||
string updaterArgs = "/SP- /SILENT /FORCECLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+Arguments.GetCurrentForInstallerCmd()+"\""+(IsPortable ? " /PORTABLE=1" : "");
|
||||
bool runElevated = !IsPortable || !WindowsUtils.CheckFolderWritePermission(ProgramPath);
|
||||
|
||||
if (WindowsUtils.OpenAssociatedProgram(mainForm.UpdateInstallerPath, updaterArgs, runElevated)){
|
||||
|
@@ -42,5 +42,6 @@ using TweetDuck;
|
||||
[assembly: CLSCompliant(true)]
|
||||
|
||||
#if DEBUG
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("TweetTest.Unit")]
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("UnitTests")]
|
||||
#endif
|
||||
|
47
README.md
47
README.md
@@ -9,53 +9,48 @@
|
||||
The program was built using Visual Studio 2017. Before opening the solution, please make sure you have the following workloads and components installed (optional components that are not listed can be deselected to save space):
|
||||
* **.NET desktop development**
|
||||
* .NET Framework 4 – 4.6 development tools
|
||||
* F# desktop language support
|
||||
* **Desktop development with C++**
|
||||
* VC++ 2017 v141 toolset
|
||||
* VC++ 2017 latest v141 tools
|
||||
|
||||
After opening the solution, download the following NuGet packages by right-clicking on the solution and selecting **Restore NuGet Packages**, or manually running this command in the **Package Manager Console**:
|
||||
After opening the solution, right-click the solution and select **Restore NuGet Packages**, or manually run this command in the **Package Manager Console**:
|
||||
```
|
||||
PM> Install-Package CefSharp.WinForms -Version 64.0.0-CI2508 -Source https://www.myget.org/F/cefsharp/api/v3/index.json
|
||||
PM> Install-Package CefSharp.WinForms -Version 66.0.0-CI2629 -Source https://www.myget.org/F/cefsharp/api/v3/index.json
|
||||
```
|
||||
|
||||
Note that some pre-release builds of CefSharp are not available on NuGet. To correctly restore packages in that case, open **Package Manager Settings**, and add `https://www.myget.org/F/cefsharp/api/v3/index.json` to the list of package sources.
|
||||
|
||||
### Debug
|
||||
|
||||
It is recommended to create a separate data folder for debugging, otherwise you will not be able to run TweetDuck while debugging the solution.
|
||||
The `Debug` configuration uses a separate data folder by default (`%LOCALAPPDATA%\TweetDuckDebug`) to avoid affecting an existing installation of TweetDuck. You can modify this by opening **TweetDuck Properties** in Visual Studio, clicking the **Debug** tab, and changing the **Command line arguments** field.
|
||||
|
||||
To do that, open **TweetDuck Properties**, click the **Debug** tab, make sure your **Configuration** is set to `Active (Debug)` (or just `Debug`), and insert this into the **Command line arguments** field:
|
||||
```
|
||||
-datafolder TweetDuckDebug
|
||||
```
|
||||
While debugging, opening the main menu and clicking **Reload browser** automatically rebuilds all resources in the `Resources/Scripts` and `Resources/Plugins` folders. This allows editing HTML/CSS/JS files and applying the changes without restarting the program, but it will cause a short delay between browser reloads.
|
||||
|
||||
### Build
|
||||
### Release
|
||||
|
||||
To make a release build of TweetDuck, open **Batch Build**, tick all `Release` configurations except for the `UnitTest` project (otherwise the build will fail), and click **Rebuild**. Check the status bar to make sure it says **Rebuild All succeeded**; if not, see the [Troubleshooting](#troubleshooting) section.
|
||||
Open **Batch Build**, tick all `Release` configurations with `x86` platform, and click **Rebuild**. Check the status bar to make sure it says **Rebuild All succeeded**; if not, see the [Troubleshooting](#troubleshooting) section.
|
||||
|
||||
After the build succeeds, the `bin/x86/Release` folder will contain files intended for distribution (no debug symbols or other unnecessary files). You may package these files yourself, or see the [Installers](#installers) section for automated installer generation.
|
||||
|
||||
If you decide to release a custom version publicly, please make it clear that it is not an official release of TweetDuck.
|
||||
The `Release` configuration omits debug symbols and other unnecessary files. You can modify this behavior by opening `TweetDuck.csproj`, and editing the `<Target Name="AfterBuild" Condition="$(ConfigurationName) == Release">` section.
|
||||
|
||||
If you decide to publicly release a custom version, please make it clear that it is not an official release of TweetDuck. There are many references to the official website and this repository, especially in the update system, so search for `chylex.com` and `github.com` in all files and replace them appropriately.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
There are a few quirks in the build process that may catch you off guard:
|
||||
|
||||
- **Plugin files are not updated automatically**
|
||||
- Since official plugins (`Resources/Plugins`) are not included in the project, Visual Studio will not automatically detect changes in the files
|
||||
- To ensure plugins are updated when testing the app, click **Rebuild Solution** before clicking **Start**
|
||||
- **Error: The command (...) exited with code 1**
|
||||
- If the post-build event fails, open the **Output** tab and look for the cause
|
||||
- Determine if there was an IO error while copying files or modifying folders, or whether the final .ps1 script failed (`Encountered an error while running PostBuild.ps1 on line xyz`)
|
||||
- Some files are checked for invalid characters:
|
||||
- `Resources/Plugins/emoji-keyboard/emoji-ordering.txt` line endings must be LF (line feed); any CR (carriage return) in the file will cause a failed build, and you will need to ensure correct line endings in your text editor
|
||||
#### Error: The command (...) exited with code 1
|
||||
- This indicates a failed post-build event, open the **Output** tab for logs
|
||||
- Determine if there was an IO error from the `rmdir` commands, or whether the error was in the **PostBuild.ps1** script (`Encountered an error while running PostBuild.ps1 on line <xyz>`)
|
||||
- Some files are checked for invalid characters:
|
||||
- `Resources/Plugins/emoji-keyboard/emoji-ordering.txt` line endings must be LF (line feed); any CR (carriage return) in the file will cause a failed build, and you will need to ensure correct line endings in your text editor
|
||||
|
||||
### Installers
|
||||
|
||||
TweetDuck uses **Inno Setup** to automate the creation of installers. First, download and install [InnoSetup QuickStart Pack](http://www.jrsoftware.org/isdl.php) (non-unicode; editor and encryption support not required) and the [Inno Download Plugin](https://code.google.com/archive/p/inno-download-plugin).
|
||||
TweetDuck uses **Inno Setup** for installers and updates. First, download and install [InnoSetup QuickStart Pack](http://www.jrsoftware.org/isdl.php) (non-unicode; editor and encryption support not required) and the [Inno Download Plugin](https://code.google.com/archive/p/inno-download-plugin).
|
||||
|
||||
Next, add the Inno Setup installation folder (usually `C:\Program Files (x86)\Inno Setup 5`) into your **PATH** environment variable. You may need to restart File Explorer for the change to take place.
|
||||
|
||||
Now you can generate installers after a build by running `bld/RUN BUILD.bat`. Note that despite the name, this will only package the files, you still need to run the [build](#build) in Visual Studio!
|
||||
Now you can generate installers after a build by running `bld/RUN BUILD.bat`. Note that this will only package the files, you still need to run the [release build](#release) in Visual Studio!
|
||||
|
||||
After the window closes, three installers will be generated inside the `bld/Output` folder:
|
||||
* **TweetDuck.exe**
|
||||
@@ -67,8 +62,8 @@ After the window closes, three installers will be generated inside the `bld/Outp
|
||||
* This is a portable installer that does not need administrator privileges
|
||||
* It automatically creates a `makeportable` file in the program folder, which forces TweetDuck to run in portable mode
|
||||
|
||||
Note: There is a small chance you will see a resource error when running `RUN BUILD.bat`. If that happens, close the console window (which will terminate all Inno Setup processes and leave corrupted installer files in the output folder), and run it again.
|
||||
#### Notes
|
||||
|
||||
### Code Notes
|
||||
> There is a small chance running `RUN BUILD.bat` immediately shows a resource error. If that happens, close the console window (which terminates all Inno Setup processes and leaves corrupted installers in the output folder), and run it again.
|
||||
|
||||
There are many references to the official TweetDuck website and this repository in the code and installers, so if you plan to release your own version, make sure to search for `tweetduck.chylex.com` and `github.com` in the whole repository and replace them appropriately.
|
||||
> Running `RUN BUILD.bat` uses about 400 MB of RAM due to high compression. You can lower this to about 140 MB by opening `gen_full.iss` and `gen_port.iss`, and changing `LZMADictionarySize=15360` to `LZMADictionarySize=4096`.
|
||||
|
BIN
Resources/Design/icon_pushpin.afdesign
Normal file
BIN
Resources/Design/icon_pushpin.afdesign
Normal file
Binary file not shown.
@@ -21,10 +21,6 @@ enabled(){
|
||||
Object.keys(TD.controller.columnManager.getAll()).forEach(func);
|
||||
};
|
||||
|
||||
var replaceMustache = (key, search, replace) => {
|
||||
TD.mustaches[key] = TD.mustaches[key].replace(search, replace);
|
||||
};
|
||||
|
||||
var wasShiftPressed = false;
|
||||
|
||||
var updateShiftState = (pressed) => {
|
||||
@@ -86,33 +82,29 @@ enabled(){
|
||||
</a>`;
|
||||
|
||||
// add column buttons and keyboard shortcut info to UI
|
||||
replaceMustache("column/column_header.mustache", "</header>", `
|
||||
{{^isTemporary}}
|
||||
<a class="column-header-link td-clear-column-shortcut" href="#" data-action="td-clearcolumns-dosingle" style="right:34px">
|
||||
<i class="icon icon-clear-timeline js-show-tip" data-placement="bottom" data-original-title="Clear column (hold Shift to restore)"></i>
|
||||
</a>
|
||||
{{/isTemporary}}
|
||||
</header>`);
|
||||
window.TDPF_injectMustache("column/column_header.mustache", "prepend", "<a data-testid=\"optionsToggle\"", `
|
||||
<a class="js-action-header-button column-header-link" href="#" data-action="td-clearcolumns-dosingle">
|
||||
<i class="icon icon-clear-timeline js-show-tip" data-placement="bottom" data-original-title="Clear column (hold Shift to restore)"></i>
|
||||
</a>`);
|
||||
|
||||
replaceMustache("keyboard_shortcut_list.mustache", "</dl> <dl", `
|
||||
window.TDPF_injectMustache("keyboard_shortcut_list.mustache", "prepend", "</dl> <dl", `
|
||||
<dd class="keyboard-shortcut-definition" style="white-space:nowrap">
|
||||
<span class="text-like-keyboard-key">1</span> … <span class="text-like-keyboard-key">9</span> + <span class="text-like-keyboard-key">Del</span> Clear column 1-9
|
||||
</dd><dd class="keyboard-shortcut-definition">
|
||||
<span class="text-like-keyboard-key">Alt</span> + <span class="text-like-keyboard-key">Del</span> Clear all columns
|
||||
</dd></dl><dl`);
|
||||
</dd>`);
|
||||
|
||||
replaceMustache("menus/column_nav_menu.mustache", "{{_i}}Add column{{/i}}</div> </a> </div>", `{{_i}}Add column{{/i}}</div></a>${this.btnClearAllHTML}</div>`)
|
||||
window.TDPF_injectMustache("menus/column_nav_menu.mustache", "replace", "{{_i}}Add column{{/i}}</div> </a> </div>", `{{_i}}Add column{{/i}}</div></a>${this.btnClearAllHTML}</div>`)
|
||||
|
||||
// load custom style
|
||||
var css = window.TDPF_createCustomStyle(this);
|
||||
css.insert(".js-app-add-column.is-hidden + .clear-columns-btn-all-parent { display: none; }");
|
||||
css.insert(".column-header-links { min-width: 51px !important; }");
|
||||
css.insert("[data-td-icon='icon-message'] .column-header-links { min-width: 110px !important; }");
|
||||
css.insert(".column-navigator-overflow .clear-columns-btn-all-parent { display: none !important; }");
|
||||
css.insert(".column-navigator-overflow { bottom: 224px !important; }");
|
||||
css.insert(".column-title { margin-right: 60px !important; }");
|
||||
css.insert(".mark-all-read-link { right: 59px !important; }");
|
||||
css.insert(".open-compose-dm-link { right: 90px !important; }");
|
||||
css.insert("button[data-action='clear'].btn-options-tray { display: none !important; }");
|
||||
css.insert("[data-td-icon='icon-message'] .column-title { margin-right: 115px !important; }");
|
||||
css.insert("[data-action='td-clearcolumns-dosingle'] { padding: 3px 0 !important; }");
|
||||
css.insert("[data-action='clear'].btn-options-tray { display: none !important; }");
|
||||
css.insert("[data-td-icon='icon-schedule'] .td-clear-column-shortcut { display: none; }");
|
||||
css.insert("[data-td-icon='icon-custom-timeline'] .td-clear-column-shortcut { display: none; }");
|
||||
}
|
||||
|
@@ -396,8 +396,10 @@ enabled(){
|
||||
switch(currentTheme){
|
||||
case "black":
|
||||
this.css.insert(".app-content, .app-columns-container { background-color: #444448 !important }");
|
||||
this.css.insert(".column-header-temp { background-color: transparent !important }");
|
||||
this.css.insert(".column-drag-handle { opacity: 0.5 !important }");
|
||||
this.css.insert(".column-drag-handle:hover { opacity: 1 !important }");
|
||||
this.css.insert(".column-message.is-actionable span:hover > .icon-small-valigned { filter: saturate(20) }");
|
||||
this.css.insert(".scroll-styled-v:not(.scroll-alt)::-webkit-scrollbar-thumb:not(:hover), .scroll-styled-h:not(.scroll-alt)::-webkit-scrollbar-thumb:not(:hover) { background-color: #666 !important }");
|
||||
notificationScrollbarColor = "666";
|
||||
break;
|
||||
@@ -539,8 +541,8 @@ ${iconData.map(entry => `#tduck .icon-${entry[0]}:before{content:\"\\f0${entry[1
|
||||
#tduck .js-docked-compose .js-drawer-close { margin: 20px 0 0 !important }
|
||||
#tduck .search-input-control .icon { font-size: 20px !important; top: -4px !important }
|
||||
|
||||
.column-header .column-type-icon { bottom: 26px !important }
|
||||
.is-options-open .column-type-icon { bottom: 25px !important }
|
||||
.column-type-icon { margin-top: -1px !important }
|
||||
.inline-reply .pull-left .Button--link { margin-top: 3px !important }
|
||||
|
||||
.tweet-action-item .icon-favorite-toggle { font-size: 16px !important; }
|
||||
.tweet-action-item .heartsprite { top: -260% !important; left: -260% !important; transform: scale(0.4, 0.39) translateY(0.5px) !important; }
|
||||
@@ -561,7 +563,7 @@ ${iconData.map(entry => `#tduck .icon-${entry[0]}:before{content:\"\\f0${entry[1
|
||||
if (this.config.columnWidth[0] === '/'){
|
||||
let cols = this.config.columnWidth.slice(1);
|
||||
|
||||
this.css.insert(".column { width: calc((100vw - 205px) / "+cols+" - 6px) !important }");
|
||||
this.css.insert(".column { width: calc((100vw - 205px) / "+cols+" - 6px) !important; min-width: 160px }");
|
||||
this.css.insert(".is-condensed .column { width: calc((100vw - 55px) / "+cols+" - 6px) !important }");
|
||||
}
|
||||
else{
|
||||
|
@@ -44,6 +44,8 @@
|
||||
</optgroup>
|
||||
<option disabled></option>
|
||||
<optgroup label="Dynamic width">
|
||||
<option value="/1">1 column on screen</option>
|
||||
<option value="/2">2 columns on screen</option>
|
||||
<option value="/3">3 columns on screen</option>
|
||||
<option value="/4">4 columns on screen</option>
|
||||
<option value="/5">5 columns on screen</option>
|
||||
@@ -151,8 +153,7 @@
|
||||
<!-- END -->
|
||||
</div>
|
||||
<footer class="padding-vxl txt-center">
|
||||
<button class="js-dismiss btn btn-positive pull-right">
|
||||
<i class="icon icon-check icon-small padding-rs"></i>
|
||||
<button class="js-dismiss Button--primary pull-right">
|
||||
<span class="label">Done</span>
|
||||
</button>
|
||||
</footer>
|
||||
|
@@ -54,7 +54,7 @@ enabled(){
|
||||
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);
|
||||
window.TDPF_injectMustache("compose/docked_compose.mustache", "append", '<div class="cf margin-t--12 margin-b--30">', buttonHTML);
|
||||
|
||||
var maybeDockedComposePanel = $(".js-docked-compose");
|
||||
|
||||
|
@@ -23,25 +23,34 @@ enabled(){
|
||||
return;
|
||||
}
|
||||
|
||||
var section = data.element.closest("section.js-column");
|
||||
let section = data.element.closest("section.js-column");
|
||||
let column = TD.controller.columnManager.get(section.attr("data-column"));
|
||||
|
||||
var column = TD.controller.columnManager.get(section.attr("data-column"));
|
||||
var header = $(".column-title", section);
|
||||
var title = header.children(".column-head-title");
|
||||
let feeds = column.getFeeds();
|
||||
let accountText = "";
|
||||
|
||||
var columnTitle, columnAccount;
|
||||
|
||||
if (title.length){
|
||||
columnTitle = title.text();
|
||||
columnAccount = header.children(".attribution").text();
|
||||
}
|
||||
else{
|
||||
columnTitle = header.children(".column-title-edit-box").val();
|
||||
columnAccount = "";
|
||||
if (feeds.length === 1){
|
||||
let metadata = feeds[0].getMetadata();
|
||||
let id = metadata.ownerId || metadata.id;
|
||||
|
||||
if (id){
|
||||
accountText = TD.cache.names.getScreenName(id);
|
||||
}
|
||||
else{
|
||||
let account = TD.storage.accountController.get(feeds[0].getAccountKey());
|
||||
|
||||
if (account){
|
||||
accountText = "@"+account.getUsername();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let header = $(".column-header-title", section);
|
||||
let title = header.children(".column-heading");
|
||||
let titleText = title.length ? title.text() : header.children(".column-title-edit-box").val();
|
||||
|
||||
try{
|
||||
query = configuration.customSelector(columnTitle, columnAccount, column, section.hasClass("column-temp"));
|
||||
query = configuration.customSelector(titleText, accountText, column, section.hasClass("column-temp"));
|
||||
}catch(e){
|
||||
$TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector threw an error: "+e.message);
|
||||
return;
|
||||
|
@@ -37,7 +37,7 @@ enabled(){
|
||||
var buttonHTML = '<button class="manage-templates-btn needsclick btn btn-on-blue full-width txt-left margin-b--12 padding-v--6 padding-h--12"><i class="icon icon-bookmark"></i><span class="label padding-ls">Manage templates</span></button>';
|
||||
|
||||
this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
|
||||
TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="js-tweet-type-button">', buttonHTML+'<div class="js-tweet-type-button">');
|
||||
window.TDPF_injectMustache("compose/docked_compose.mustache", "prepend", '<div class="js-tweet-type-button">', buttonHTML);
|
||||
|
||||
var dockedComposePanel = $(".js-docked-compose");
|
||||
|
||||
@@ -51,7 +51,7 @@ enabled(){
|
||||
this.css.insert(".manage-templates-btn.active { color: #fff; box-shadow: 0 0 2px 3px #50a5e6; outline: 0; }");
|
||||
|
||||
this.css.insert("#templates-modal-wrap { width: 100%; height: 100%; padding: 49px; position: absolute; z-index: 999; box-sizing: border-box; background-color: rgba(0, 0, 0, 0.5); }");
|
||||
this.css.insert("#templates-modal { width: 100%; height: 100%; background-color: #fff; display: flex; }");
|
||||
this.css.insert("#templates-modal { width: 100%; height: 100%; min-width: 500px; background-color: #fff; display: flex; }");
|
||||
this.css.insert("#templates-modal > div { display: flex; flex-direction: column; }");
|
||||
|
||||
this.css.insert(".templates-modal-bottom { flex: 0 0 auto; padding: 16px; }");
|
||||
@@ -68,7 +68,7 @@ enabled(){
|
||||
this.css.insert("#template-list li .icon:hover { opacity: 1; }");
|
||||
this.css.insert("#template-list li .template-actions { float: right; }");
|
||||
|
||||
this.css.insert("#template-editor { height: 100%; flex: 0 0 auto; width: 25vw; min-width: 150px; max-width: 400px; background-color: #485865; }");
|
||||
this.css.insert("#template-editor { height: 100%; flex: 0 0 auto; width: 25vw; min-width: 225px; max-width: 400px; background-color: #485865; }");
|
||||
this.css.insert(".template-editor-form { flex: 1 1 auto; padding: 12px 16px; font-size: 14px; overflow-y: auto; }");
|
||||
this.css.insert(".template-editor-form .compose-text-title { margin: 24px 0 9px; }");
|
||||
this.css.insert(".template-editor-form .compose-text-title:first-child { margin-top: 0; }");
|
||||
@@ -261,14 +261,14 @@ enabled(){
|
||||
$(".manage-templates-btn").addClass("active");
|
||||
|
||||
let html = `
|
||||
<div id="templates-modal-wrap">
|
||||
<div id="templates-modal-wrap" class="scroll-v scroll-styled-v">
|
||||
<div id="templates-modal">
|
||||
<div id="template-list">
|
||||
<ul></ul>
|
||||
|
||||
<div class="templates-modal-bottom">
|
||||
<button data-action="close" class="btn"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Close</span></button>
|
||||
<button data-action="new-template" class="btn btn-positive"><i class="icon icon-plus icon-small padding-rs"></i><span class="label">New Template</span></button>
|
||||
<button data-action="close" class="Button--secondary"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Close</span></button>
|
||||
<button data-action="new-template" class="Button--primary"><i class="icon icon-plus icon-small padding-rs"></i><span class="label">New Template</span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -300,8 +300,8 @@ enabled(){
|
||||
</div>
|
||||
|
||||
<div class="templates-modal-bottom">
|
||||
<button data-action="editor-cancel" class="btn"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Cancel</span></button>
|
||||
<button data-action="editor-confirm" class="btn btn-positive" style="margin-left:4px"><i class="icon icon-check icon-small padding-rs"></i><span class="label">Confirm</span></button>
|
||||
<button data-action="editor-cancel" class="Button--secondary"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Cancel</span></button>
|
||||
<button data-action="editor-confirm" class="Button--primary" style="margin-left:4px"><i class="icon icon-check icon-small padding-rs"></i><span class="label">Confirm</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -9,14 +9,13 @@ $ErrorActionPreference = "Stop"
|
||||
|
||||
try{
|
||||
$sw = [Diagnostics.Stopwatch]::StartNew()
|
||||
Write-Host "--------------------------"
|
||||
|
||||
if ($version.Equals("")){
|
||||
$version = (Get-Item (Join-Path $targetDir "TweetDuck.exe")).VersionInfo.FileVersion
|
||||
}
|
||||
|
||||
Write-Host "--------------------------"
|
||||
Write-Host "TweetDuck version" $version
|
||||
|
||||
Write-Host "--------------------------"
|
||||
|
||||
# Cleanup
|
||||
@@ -27,90 +26,102 @@ try{
|
||||
|
||||
# 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
|
||||
Copy-Item (Join-Path $projectDir "bld\Resources\LICENSES.txt") -Destination $targetDir -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
|
||||
Copy-Item (Join-Path $projectDir "Resources\Scripts\*") -Recurse -Destination (Join-Path $targetDir "scripts")
|
||||
Copy-Item (Join-Path $projectDir "Resources\Plugins\*") -Recurse -Destination (Join-Path $targetDir "plugins\official") -Exclude ".debug", "emoji-instructions.txt"
|
||||
|
||||
if ($configuration -eq "Debug"){
|
||||
New-Item -ItemType directory -Path $targetDir -Name "plugins\user\.debug" | Out-Null
|
||||
Copy-Item (Join-Path $projectDir "Resources\Plugins\.debug\*") -Destination (Join-Path $targetDir "plugins\user\.debug") -Recurse
|
||||
Copy-Item (Join-Path $projectDir "Resources\Plugins\.debug\*") -Recurse -Destination (Join-Path $targetDir "plugins\user\.debug")
|
||||
}
|
||||
|
||||
# Helper functions
|
||||
|
||||
function Remove-Empty-Lines{
|
||||
Param([Parameter(Mandatory = $True, Position = 1)] $lines)
|
||||
|
||||
ForEach($line in $lines){
|
||||
if ($line -ne ''){
|
||||
$line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Check-Carriage-Return{
|
||||
Param(
|
||||
[Parameter(Mandatory = $True, Position = 1)] $fname
|
||||
)
|
||||
Param([Parameter(Mandatory = $True, Position = 1)] $file)
|
||||
|
||||
$file = @(Get-ChildItem -Path $targetDir -Include $fname -Recurse)[0]
|
||||
|
||||
if ((Get-Content -Path $file.FullName -Raw).Contains("`r")){
|
||||
Throw "$fname cannot contain carriage return"
|
||||
if (!(Test-Path $file)){
|
||||
Throw "$file does not exist"
|
||||
}
|
||||
|
||||
Write-Host "Verified" $file.FullName.Substring($targetDir.Length)
|
||||
if ((Get-Content -Path $file -Raw).Contains("`r")){
|
||||
Throw "$file must not have any carriage return characters"
|
||||
}
|
||||
|
||||
Write-Host "Verified" $file.Substring($targetDir.Length)
|
||||
}
|
||||
|
||||
function Rewrite-File{
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $True, ValueFromPipeline = $True)][array] $lines,
|
||||
[Parameter(Mandatory = $True, Position = 1)] $file
|
||||
)
|
||||
Param([Parameter(Mandatory = $True, Position = 1)] $file,
|
||||
[Parameter(Mandatory = $True, Position = 2)] $lines)
|
||||
|
||||
$lines = Remove-Empty-Lines($lines)
|
||||
$relativePath = $file.FullName.Substring($targetDir.Length)
|
||||
|
||||
if ($relativePath.StartsWith("scripts\")){
|
||||
$lines = (,("#" + $version) + $lines)
|
||||
}
|
||||
|
||||
$lines | Where { $_ -ne '' } | Set-Content -Path $file.FullName
|
||||
[IO.File]::WriteAllLines($file.FullName, $lines)
|
||||
Write-Host "Processed" $relativePath
|
||||
}
|
||||
|
||||
# Post processing
|
||||
|
||||
Check-Carriage-Return("emoji-ordering.txt")
|
||||
Check-Carriage-Return(Join-Path $targetDir "plugins\official\emoji-keyboard\emoji-ordering.txt")
|
||||
|
||||
ForEach($file in Get-ChildItem -Path $targetDir -Filter "*.js" -Exclude "configuration.default.js" -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
$lines = [IO.File]::ReadLines($file.FullName)
|
||||
$lines = $lines | % { $_.TrimStart() }
|
||||
$lines = $lines -Replace '^(.*?)((?<=^|[;{}()])\s?//(?:\s.*|$))?$', '$1'
|
||||
$lines = $lines -Replace '(?<!\w)return(\s.*?)? if (.*?);', 'if ($2)return$1;'
|
||||
,$lines | Rewrite-File $file
|
||||
Rewrite-File $file $lines
|
||||
}
|
||||
|
||||
ForEach($file in Get-ChildItem -Path $targetDir -Filter "*.css" -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
$lines = [IO.File]::ReadLines($file.FullName)
|
||||
$lines = $lines -Replace '\s*/\*.*?\*/', ''
|
||||
$lines = $lines -Replace '^\s+(.+):\s?(.+?)(?:\s?(!important))?;$', '$1:$2$3;'
|
||||
$lines = $lines -Replace '^(\S.*?) {$', '$1{'
|
||||
@(($lines | Where { $_ -ne '' }) -Join ' ') | Rewrite-File $file
|
||||
$lines = $lines -Replace '^(\S.*) {$', '$1{'
|
||||
$lines = $lines -Replace '^\s+(.+?):\s*(.+?)(?:\s*(!important))?;$', '$1:$2$3;'
|
||||
$lines = @((Remove-Empty-Lines($lines)) -Join ' ')
|
||||
Rewrite-File $file $lines
|
||||
}
|
||||
|
||||
ForEach($file in Get-ChildItem -Path $targetDir -Filter "*.html" -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
,($lines | % { $_.TrimStart() }) | Rewrite-File $file
|
||||
$lines = [IO.File]::ReadLines($file.FullName)
|
||||
$lines = $lines | % { $_.TrimStart() }
|
||||
Rewrite-File $file $lines
|
||||
}
|
||||
|
||||
ForEach($file in Get-ChildItem -Path $targetDir -Filter "*.meta" -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
ForEach($file in Get-ChildItem -Path (Join-Path $targetDir "plugins") -Filter "*.meta" -Recurse){
|
||||
$lines = [IO.File]::ReadLines($file.FullName)
|
||||
$lines = $lines -Replace '\{version\}', $version
|
||||
,$lines | Rewrite-File $file
|
||||
Rewrite-File $file $lines
|
||||
}
|
||||
|
||||
Write-Host "-------------------"
|
||||
# Finished
|
||||
|
||||
$sw.Stop()
|
||||
Write-Host "------------------"
|
||||
Write-Host "Finished in" $([math]::Round($sw.Elapsed.TotalMilliseconds)) "ms"
|
||||
Write-Host -------------------
|
||||
Write-Host "------------------"
|
||||
|
||||
}catch{
|
||||
Write-Host "Encountered an error while running PostBuild.ps1 on line" $_.InvocationInfo.ScriptLineNumber
|
||||
Write-Host $_
|
||||
|
18
Resources/PostCefUpdate.ps1
Normal file
18
Resources/PostCefUpdate.ps1
Normal file
@@ -0,0 +1,18 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$MainProj = "..\TweetDuck.csproj"
|
||||
$BrowserProj = "..\subprocess\TweetDuck.Browser.csproj"
|
||||
|
||||
$Match = Select-String -Path $MainProj '<Import Project="packages\\cef\.redist\.x86\.(.*?)\\'
|
||||
$Version = $Match.Matches[0].Groups[1].Value
|
||||
|
||||
Copy-Item "..\packages\cef.redist.x86.${Version}\CEF\devtools_resources.pak" -Destination "..\bld\Resources\" -Force
|
||||
|
||||
$Match = Select-String -Path $MainProj '<Import Project="packages\\CefSharp\.Common\.(.*?)\\'
|
||||
$Version = $Match.Matches[0].Groups[1].Value
|
||||
|
||||
$Contents = [IO.File]::ReadAllText($BrowserProj)
|
||||
$Contents = $Contents -Replace '(?<=<HintPath>\.\.\\packages\\CefSharp\.Common\.)(.*?)(?=\\)', $Version
|
||||
$Contents = $Contents -Replace '(?<=<Reference Include="CefSharp\.BrowserSubprocess\.Core, Version=)(\d+)', $Version.Split(".")[0]
|
||||
|
||||
[IO.File]::WriteAllText($BrowserProj, $Contents)
|
@@ -145,8 +145,21 @@
|
||||
return false;
|
||||
};
|
||||
|
||||
let isSensitive = (tweet) => {
|
||||
let main = tweet.getMainTweet && tweet.getMainTweet();
|
||||
return true if main && main.possiblySensitive; // TODO these don't show media badges when hiding sensitive media
|
||||
|
||||
let related = tweet.getRelatedTweet && tweet.getRelatedTweet();
|
||||
return true if related && related.possiblySensitive;
|
||||
|
||||
let quoted = tweet.quotedTweet;
|
||||
return true if quoted && quoted.possiblySensitive;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
let fixMedia = (html, media) => {
|
||||
return html.find(".js-media a[data-media-entity-id='"+media.mediaId+"']").css("background-image", 'url("'+media.small()+'")').removeClass("is-zoomable");
|
||||
return html.find("a[data-media-entity-id='"+media.mediaId+"'], .media-item").first().removeClass("is-zoomable").css("background-image", 'url("'+media.small()+'")');
|
||||
};
|
||||
|
||||
return function(column, tweet){
|
||||
@@ -160,7 +173,7 @@
|
||||
startRecentTweetTimer();
|
||||
|
||||
if (column.model.getHasNotification()){
|
||||
let sensitive = (tweet.getRelatedTweet() && tweet.getRelatedTweet().possiblySensitive || (tweet.quotedTweet && tweet.quotedTweet.possiblySensitive));
|
||||
let sensitive = isSensitive(tweet);
|
||||
let previews = $TDX.notificationMediaPreviews && (!sensitive || TD.settings.getDisplaySensitiveMedia());
|
||||
|
||||
let html = $(tweet.render({
|
||||
@@ -172,7 +185,8 @@
|
||||
isMediaPreviewLarge: false,
|
||||
isMediaPreviewCompact: false,
|
||||
isMediaPreviewInQuoted: previews,
|
||||
thumbSizeClass: "media-size-medium"
|
||||
thumbSizeClass: "media-size-medium",
|
||||
mediaPreviewSize: "medium"
|
||||
}));
|
||||
|
||||
html.find("footer").last().remove(); // apparently withTweetActions breaks for certain tweets, nice
|
||||
@@ -181,13 +195,15 @@
|
||||
if (previews){
|
||||
html.find(".reverse-image-search").remove();
|
||||
|
||||
let container = html.find(".js-media");
|
||||
|
||||
for(let media of tweet.getMedia()){
|
||||
fixMedia(html, media);
|
||||
fixMedia(container, media);
|
||||
}
|
||||
|
||||
if (tweet.quotedTweet){
|
||||
for(let media of tweet.quotedTweet.getMedia()){
|
||||
fixMedia(html, media).addClass("media-size-medium");
|
||||
fixMedia(container, media).addClass("media-size-medium");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,6 +211,10 @@
|
||||
html.find(".js-media").remove();
|
||||
}
|
||||
|
||||
html.find("a[data-full-url]").each(function(){ // bypass t.co on all links
|
||||
this.href = this.getAttribute("data-full-url");
|
||||
});
|
||||
|
||||
html.find("a[href='#']").each(function(){ // remove <a> tags around links that don't lead anywhere (such as account names the tweet replied to)
|
||||
this.outerHTML = this.innerHTML;
|
||||
});
|
||||
@@ -211,6 +231,12 @@
|
||||
}
|
||||
});
|
||||
|
||||
if (tweet.quotedTweet){
|
||||
html.find("p.txt-mute").filter(function(){
|
||||
return $(this).text() === "Show this thread";
|
||||
}).first().remove();
|
||||
}
|
||||
|
||||
let type = tweet.getChirpType();
|
||||
|
||||
if (type === "follow"){
|
||||
@@ -550,17 +576,17 @@
|
||||
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);
|
||||
$TD.setRightClickedLink("video", media.chooseVideoVariant().url);
|
||||
}
|
||||
else{
|
||||
$TD.setLastRightClickInfo("image", media.large());
|
||||
$TD.setRightClickedLink("image", media.large());
|
||||
}
|
||||
}
|
||||
else if (me.classList.contains("js-gif-play")){
|
||||
$TD.setLastRightClickInfo("video", $(this).closest(".js-media-gif-container").find("video").attr("src"));
|
||||
$TD.setRightClickedLink("video", $(this).closest(".js-media-gif-container").find("video").attr("src"));
|
||||
}
|
||||
else{
|
||||
$TD.setLastRightClickInfo("link", me.getAttribute("data-full-url"));
|
||||
$TD.setRightClickedLink("link", me.getAttribute("data-full-url"));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -680,9 +706,9 @@
|
||||
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(){
|
||||
html.find("p.link-complex-target,p.txt-mute").filter(function(){
|
||||
return $(this).text() === "Show this thread";
|
||||
}).first().remove();
|
||||
}).remove();
|
||||
|
||||
html.addClass($(document.documentElement).attr("class"));
|
||||
html.addClass($(document.body).attr("class"));
|
||||
@@ -699,6 +725,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
let gif = html.find(".js-media-gif-container");
|
||||
|
||||
if (gif.length){
|
||||
gif.css("background-image", 'url("'+chirp.getMedia()[0].small()+'")');
|
||||
}
|
||||
|
||||
let type = chirp.getChirpType();
|
||||
|
||||
if ((type.startsWith("favorite") || type.startsWith("retweet")) && chirp.isAboutYou()){
|
||||
@@ -1001,10 +1033,40 @@
|
||||
};
|
||||
})();
|
||||
|
||||
//
|
||||
// Block: Setup global function to inject custom HTML into mustache templates.
|
||||
//
|
||||
window.TDGF_injectMustache = function(name, operation, search, custom){
|
||||
let replacement;
|
||||
|
||||
switch(operation){
|
||||
case "replace": replacement = custom; break;
|
||||
case "append": replacement = search+custom; break;
|
||||
case "prepend": replacement = custom+search; break;
|
||||
default: throw "Invalid mustache injection operation. Only 'replace', 'append', 'prepend' are supported.";
|
||||
}
|
||||
|
||||
let prev = TD.mustaches[name];
|
||||
|
||||
if (!prev){
|
||||
$TD.crashDebug("Mustache injection is referencing an invalid mustache: "+name);
|
||||
return false;
|
||||
}
|
||||
|
||||
TD.mustaches[name] = prev.replace(search, replacement);
|
||||
|
||||
if (prev === TD.mustaches[name]){
|
||||
$TD.crashDebug("Mustache injection had no effect: "+name);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
//
|
||||
// Block: Let's make retweets lowercase again.
|
||||
//
|
||||
TD.mustaches["status/tweet_single.mustache"] = TD.mustaches["status/tweet_single.mustache"].replace("{{_i}} Retweeted{{/i}}", "{{_i}} retweeted{{/i}}");
|
||||
window.TDGF_injectMustache("status/tweet_single.mustache", "replace", "{{_i}} Retweeted{{/i}}", "{{_i}} retweeted{{/i}}");
|
||||
|
||||
if (ensurePropertyExists(TD, "services", "TwitterActionRetweet", "prototype", "generateText")){
|
||||
TD.services.TwitterActionRetweet.prototype.generateText = appendToFunction(TD.services.TwitterActionRetweet.prototype.generateText, function(){
|
||||
@@ -1073,7 +1135,8 @@
|
||||
}
|
||||
});
|
||||
|
||||
TD.mustaches["status/media_thumb.mustache"] = TD.mustaches["status/media_thumb.mustache"].replace("is-gif", "is-gif is-paused");
|
||||
window.TDGF_injectMustache("status/media_thumb.mustache", "append", "is-gif", " is-paused");
|
||||
|
||||
TD.mustaches["media/native_video.mustache"] = '<div class="js-media-gif-container media-item nbfc is-video" style="background-image:url({{imageSrc}})"><video class="js-media-gif media-item-gif full-width block {{#isPossiblySensitive}}is-invisible{{/isPossiblySensitive}}" loop src="{{videoUrl}}"></video><a class="js-gif-play pin-all is-actionable">{{> media/video_overlay}}</a></div>';
|
||||
|
||||
if (!ensurePropertyExists(TD, "components", "MediaGallery", "prototype", "_loadTweet") ||
|
||||
@@ -1329,11 +1392,20 @@
|
||||
}
|
||||
|
||||
//
|
||||
// Block: Remove column mouse wheel handler, which allows smooth scrolling inside columns, and horizontally scrolling column container when holding Shift.
|
||||
// Block: Fix broken horizontal scrolling of column container when holding Shift. TODO Fix broken smooth scrolling.
|
||||
//
|
||||
if (ensurePropertyExists(TD, "ui", "columns", "setupColumnScrollListeners")){
|
||||
TD.ui.columns.setupColumnScrollListeners = appendToFunction(TD.ui.columns.setupColumnScrollListeners, function(e){
|
||||
$(".js-column[data-column='"+e.model.getKey()+"']").off("mousewheel onmousewheel");
|
||||
TD.ui.columns.setupColumnScrollListeners = appendToFunction(TD.ui.columns.setupColumnScrollListeners, function(column){
|
||||
let ele = $(".js-column[data-column='"+column.model.getKey()+"']");
|
||||
return if !ele.length;
|
||||
|
||||
ele.off("onmousewheel").on("mousewheel", ".scroll-v", function(e){
|
||||
if (e.shiftKey){
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
});
|
||||
|
||||
window.TDGF_prioritizeNewestEvent(ele[0], "mousewheel");
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -3,12 +3,13 @@
|
||||
let css = $(`
|
||||
<style>
|
||||
#td-introduction-modal {
|
||||
display: block;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#td-introduction-modal .mdl {
|
||||
width: 90%;
|
||||
max-width: 830px;
|
||||
min-width: 515px;
|
||||
max-width: 835px;
|
||||
height: 328px;
|
||||
}
|
||||
|
||||
@@ -73,7 +74,7 @@
|
||||
</style>`).appendTo(document.head);
|
||||
|
||||
let ele = $(`
|
||||
<div id="td-introduction-modal" class="ovl">
|
||||
<div id="td-introduction-modal" class="ovl scroll-v scroll-styled-v">
|
||||
<div class="mdl is-inverted-dark">
|
||||
<header class="mdl-header">
|
||||
<h3 class="mdl-header-title">Welcome to TweetDuck</h3>
|
||||
@@ -93,12 +94,12 @@
|
||||
<label for="td-anonymous-data">Send anonymous usage data</label>
|
||||
<label> (<a href="https://github.com/chylex/TweetDuck/wiki/Send-anonymous-data" rel="nofollow">learn more</a>)</label>
|
||||
</div>
|
||||
<button class="btn btn-positive" data-guide><span class="label">Show Guide</span></button>
|
||||
<button class="btn btn-positive"><span class="label">Close</span</button>
|
||||
<button class="Button--primary" data-guide><span class="label">Show Guide</span></button>
|
||||
<button class="Button--secondary"><span class="label">Close</span</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>`).appendTo(".js-app"); /* TODO btn-positive is removed, check all files again */
|
||||
</div>`).appendTo(".js-app");
|
||||
|
||||
let tdUser = null;
|
||||
let loadTweetDuckUser = (onSuccess, onError) => {
|
||||
|
@@ -14,14 +14,14 @@
|
||||
};
|
||||
|
||||
//
|
||||
// Block: Hook into links to bypass default open function and t.co, and handle skipping notification when opening links.
|
||||
// Block: Hook into links to bypass default open function, and handle skipping notification when opening links.
|
||||
//
|
||||
(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"));
|
||||
$TD.openBrowser(ele.href);
|
||||
e.preventDefault();
|
||||
|
||||
if ($TDX.skipOnLinkClick){
|
||||
@@ -38,13 +38,6 @@
|
||||
addEventListener(links, "auxclick", onLinkClick);
|
||||
})();
|
||||
|
||||
//
|
||||
// Block: Allow bypassing of t.co in context menus.
|
||||
//
|
||||
addEventListener(links, "contextmenu", function(e){
|
||||
$TD.setLastRightClickInfo("link", e.currentTarget.getAttribute("data-full-url"));
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Expand shortened links on hover or display tooltip.
|
||||
//
|
||||
@@ -129,11 +122,10 @@
|
||||
// Block: Setup a skip button.
|
||||
//
|
||||
if (!document.body.hasAttribute("td-example-notification")){
|
||||
document.body.insertAdjacentHTML("afterbegin", `
|
||||
document.body.children[0].insertAdjacentHTML("beforeend", `
|
||||
<svg id="td-skip" width="10" height="17" viewBox="0 0 350 600">
|
||||
<path fill="#888" d="M0,151.656l102.208-102.22l247.777,247.775L102.208,544.986L0,442.758l145.546-145.547">
|
||||
</svg>
|
||||
`);
|
||||
</svg>`);
|
||||
|
||||
document.getElementById("td-skip").addEventListener("click", function(){
|
||||
$TD.loadNextNotification();
|
||||
@@ -150,4 +142,9 @@
|
||||
document.body.addEventListener("mouseleave", function(){
|
||||
document.body.classList.remove("td-hover");
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Force a reset of scroll position on every load.
|
||||
//
|
||||
history.scrollRestoration = "manual";
|
||||
})($TD, $TDX);
|
||||
|
@@ -121,4 +121,5 @@
|
||||
window.TDPF_playVideo = window.TDGF_playVideo;
|
||||
window.TDPF_reloadColumns = window.TDGF_reloadColumns;
|
||||
window.TDPF_prioritizeNewestEvent = window.TDGF_prioritizeNewestEvent;
|
||||
window.TDPF_injectMustache = window.TDGF_injectMustache;
|
||||
})();
|
||||
|
@@ -10,7 +10,7 @@
|
||||
let avatarBottom = avatar ? avatar.getBoundingClientRect().bottom : 0;
|
||||
|
||||
$TD.setHeight(Math.floor(Math.max(contentHeight, avatarBottom+9))).then(() => {
|
||||
let framesLeft = 5; // basic render is done in 1 frame, large media take longer
|
||||
let framesLeft = {frames}; // basic render is done in 1 frame, large media take longer
|
||||
|
||||
let onNextFrame = function(){
|
||||
if (--framesLeft < 0){
|
||||
|
@@ -150,6 +150,7 @@ button {
|
||||
|
||||
.activity-header .icon-user-filled {
|
||||
vertical-align: sub !important;
|
||||
margin-right: 4px !important;
|
||||
}
|
||||
|
||||
html[data-td-theme='light'] .stream-item:not(:hover) .js-user-actions-menu {
|
||||
@@ -220,6 +221,50 @@ a[data-full-url] {
|
||||
bottom: 0 !important;
|
||||
}
|
||||
|
||||
/**********************************************************/
|
||||
/* Prevent column icons from being hidden by column title */
|
||||
/**********************************************************/
|
||||
|
||||
.column-header-title {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.column-heading {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.column-header-links {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
[data-td-icon="icon-message"] .column-header-links {
|
||||
min-width: 86px;
|
||||
}
|
||||
|
||||
/************************************************************/
|
||||
/* Fix modal dialogs breaking when window size is too small */
|
||||
/************************************************************/
|
||||
|
||||
.ovl:before, .overlay:before {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.ovl, .overlay {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ovl[style="display: block;"] {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
#tduck .overlay {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/*******************************************/
|
||||
/* Fix general visual issues or annoyances */
|
||||
/*******************************************/
|
||||
@@ -234,6 +279,37 @@ a[data-full-url] {
|
||||
vertical-align: -10% !important;
|
||||
}
|
||||
|
||||
.column-title {
|
||||
/* fix alignment of everything in column headers */
|
||||
padding-top: 1px !important;
|
||||
}
|
||||
|
||||
.column-title .attribution {
|
||||
/* fix alignment of usernames in column headers */
|
||||
margin: 0 0 0 6px !important;
|
||||
}
|
||||
|
||||
.app-navigator .tooltip {
|
||||
/* fix tooltips in navigation */
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.column-header-links .icon {
|
||||
/* fix tooltips in column headers */
|
||||
height: calc(1em + 8px) !important;
|
||||
}
|
||||
|
||||
.column-header-temp:not(.js-column-header) {
|
||||
/* fix missing column header padding in Edit List dialog */
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.js-column-options .btn-options-tray {
|
||||
/* fix underline on buttons in column options */
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
#tduck .nav-user-info .hide-condensed {
|
||||
/* move login account info */
|
||||
margin: 0px !important;
|
||||
@@ -241,7 +317,7 @@ a[data-full-url] {
|
||||
}
|
||||
|
||||
html[data-td-font='smallest'] .sprite-verified-mini {
|
||||
/* fix cut off badge when zoomed in */
|
||||
/* fix cut off badge in timelines */
|
||||
width: 13px !important;
|
||||
height: 13px !important;
|
||||
background-position: -223px -99px !important;
|
||||
@@ -261,11 +337,21 @@ html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
|
||||
background-position: -223px -97px !important;
|
||||
}
|
||||
|
||||
html[data-td-font='smallest'] .fullname-badged:before, html[data-td-font='small'] .fullname-badged:before {
|
||||
/* fix cut off badge in follow chirps */
|
||||
margin-top: -7px !important;
|
||||
}
|
||||
|
||||
.keyboard-shortcut-list {
|
||||
/* fix keyboard navigation alignment */
|
||||
vertical-align: top !important;
|
||||
}
|
||||
|
||||
.column-message.is-actionable span:hover > .icon-small-valigned {
|
||||
/* add a visual response when hovering individual filter icons; black theme uses a value of 20 */
|
||||
filter: saturate(10);
|
||||
}
|
||||
|
||||
.tweet-detail-wrapper .js-media-gif-container {
|
||||
/* GIFs in detail view don't trigger the pointer cursor */
|
||||
cursor: pointer;
|
||||
@@ -290,9 +376,9 @@ html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
|
||||
/* Fix glaring visual issues that twitter hasn't fixed yet smh */
|
||||
/***************************************************************/
|
||||
|
||||
.column-nav-link .attribution {
|
||||
.js-column-nav-list .attribution {
|
||||
/* fix cut off account names */
|
||||
position: absolute !important;
|
||||
line-height: 1.1 !important;
|
||||
}
|
||||
|
||||
#tduck .js-docked-compose .js-drawer-close {
|
||||
@@ -311,25 +397,6 @@ html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
|
||||
vertical-align: -15% !important;
|
||||
}
|
||||
|
||||
/************************************************/
|
||||
/* Fix tooltips in navigation and column header */
|
||||
/************************************************/
|
||||
|
||||
.app-navigator .tooltip {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.js-column-header .column-header-link {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.js-column-header .column-header-link .icon {
|
||||
width: calc(1em + 8px);
|
||||
height: 100%;
|
||||
padding: 9px 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/*******************************************/
|
||||
/* Fix one pixel space below column header */
|
||||
/*******************************************/
|
||||
@@ -342,10 +409,6 @@ html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.is-options-open .column-type-icon {
|
||||
bottom: 27px !important;
|
||||
}
|
||||
|
||||
/********************************************/
|
||||
/* Fix cut off usernames in Messages column */
|
||||
/********************************************/
|
||||
|
@@ -2,7 +2,9 @@
|
||||
/* General */
|
||||
/***********/
|
||||
|
||||
body.td-notification {
|
||||
html, body {
|
||||
height: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
@@ -59,13 +61,25 @@ a[data-full-url] {
|
||||
/* Fix general visual issues or annoyances */
|
||||
/*******************************************/
|
||||
|
||||
html[data-td-font='smallest'] .sprite-verified-mini {
|
||||
/* fix cut off badge in timelines */
|
||||
width: 13px !important;
|
||||
height: 13px !important;
|
||||
background-position: -223px -99px !important;
|
||||
}
|
||||
|
||||
html[data-td-font='smallest'] .badge-verified:before {
|
||||
/* fix cut off badge icon */
|
||||
/* fix cut off badge in notifications */
|
||||
width: 13px !important;
|
||||
height: 13px !important;
|
||||
background-position: -223px -98px !important;
|
||||
}
|
||||
|
||||
html[data-td-font='smallest'] .fullname-badged:before, html[data-td-font='small'] .fullname-badged:before {
|
||||
/* fix cut off badge in follow chirps */
|
||||
margin-top: -7px !important;
|
||||
}
|
||||
|
||||
.account-inline .username {
|
||||
vertical-align: 10% !important;
|
||||
}
|
||||
@@ -88,6 +102,7 @@ html[data-td-font='smallest'] .badge-verified:before {
|
||||
|
||||
.activity-header .icon-user-filled {
|
||||
vertical-align: sub !important;
|
||||
margin-right: 4px !important;
|
||||
}
|
||||
|
||||
.td-notification-padded .item-img {
|
||||
@@ -136,7 +151,7 @@ html[data-td-font='smallest'] .badge-verified:before {
|
||||
|
||||
#td-skip {
|
||||
position: fixed;
|
||||
left: 30px;
|
||||
left: 29px;
|
||||
bottom: 10px;
|
||||
z-index: 1000;
|
||||
cursor: pointer;
|
||||
|
@@ -60,4 +60,16 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
//
|
||||
// Block: Fix broken Cancel button on logout page.
|
||||
//
|
||||
else if (location.pathname === "/logout"){
|
||||
document.addEventListener("DOMContentLoaded", function(){
|
||||
let cancel = document.querySelector(".buttons .cancel");
|
||||
|
||||
if (cancel && cancel.tagName === "A"){
|
||||
cancel.href = "https://tweetdeck.twitter.com/";
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="packages\CefSharp.WinForms.64.0.0-CI2508\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.64.0.0-CI2508\build\CefSharp.WinForms.props')" />
|
||||
<Import Project="packages\CefSharp.Common.64.0.0-CI2508\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.64.0.0-CI2508\build\CefSharp.Common.props')" />
|
||||
<Import Project="packages\cef.redist.x86.3.3282.1731\build\cef.redist.x86.props" Condition="Exists('packages\cef.redist.x86.3.3282.1731\build\cef.redist.x86.props')" />
|
||||
<Import Project="packages\cef.redist.x64.3.3282.1731\build\cef.redist.x64.props" Condition="Exists('packages\cef.redist.x64.3.3282.1731\build\cef.redist.x64.props')" />
|
||||
<Import Project="packages\CefSharp.WinForms.66.0.0-CI2629\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.66.0.0-CI2629\build\CefSharp.WinForms.props')" />
|
||||
<Import Project="packages\CefSharp.Common.66.0.0-CI2629\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.66.0.0-CI2629\build\CefSharp.Common.props')" />
|
||||
<Import Project="packages\cef.redist.x86.3.3359.1772\build\cef.redist.x86.props" Condition="Exists('packages\cef.redist.x86.3.3359.1772\build\cef.redist.x86.props')" />
|
||||
<Import Project="packages\cef.redist.x64.3.3359.1772\build\cef.redist.x64.props" Condition="Exists('packages\cef.redist.x64.3.3359.1772\build\cef.redist.x64.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
@@ -35,6 +35,9 @@
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<StartArguments>-datafolder TweetDuckDebug</StartArguments>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
@@ -365,30 +368,45 @@
|
||||
</ContentWithTargetPath>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Resources\avatar.png" />
|
||||
<None Include="packages.config" />
|
||||
<None Include="Resources\icon-small.ico" />
|
||||
<None Include="Resources\icon-tray-new.ico" />
|
||||
<None Include="Resources\icon-tray.ico" />
|
||||
<None Include="Resources\PostBuild.ps1" />
|
||||
<None Include="Resources\spinner.apng" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\Plugins\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Resources\avatar.png" />
|
||||
<Content Include="Resources\Scripts\code.js" />
|
||||
<Content Include="Resources\Scripts\introduction.js" />
|
||||
<Content Include="Resources\Scripts\notification.js" />
|
||||
<Content Include="Resources\Scripts\pages\error.html" />
|
||||
<Content Include="Resources\Scripts\pages\example.html" />
|
||||
<Content Include="Resources\Scripts\plugins.browser.js" />
|
||||
<Content Include="Resources\Scripts\plugins.js" />
|
||||
<Content Include="Resources\Scripts\plugins.notification.js" />
|
||||
<Content Include="Resources\Scripts\styles\browser.css" />
|
||||
<Content Include="Resources\Scripts\styles\notification.css" />
|
||||
<Content Include="Resources\Scripts\twitter.js" />
|
||||
<Content Include="Resources\Scripts\update.js" />
|
||||
<None Include="Resources\PostBuild.ps1" />
|
||||
<None Include="Resources\Plugins\.debug\.meta" />
|
||||
<None Include="Resources\Plugins\.debug\browser.js" />
|
||||
<None Include="Resources\Plugins\clear-columns\.meta" />
|
||||
<None Include="Resources\Plugins\clear-columns\browser.js" />
|
||||
<None Include="Resources\Plugins\edit-design\.meta" />
|
||||
<None Include="Resources\Plugins\edit-design\browser.js" />
|
||||
<None Include="Resources\Plugins\edit-design\modal.html" />
|
||||
<None Include="Resources\Plugins\edit-design\theme.black.css" />
|
||||
<None Include="Resources\Plugins\emoji-keyboard\.meta" />
|
||||
<None Include="Resources\Plugins\emoji-keyboard\browser.js" />
|
||||
<None Include="Resources\Plugins\emoji-keyboard\emoji-instructions.txt" />
|
||||
<None Include="Resources\Plugins\emoji-keyboard\emoji-ordering.txt" />
|
||||
<None Include="Resources\Plugins\reply-account\.meta" />
|
||||
<None Include="Resources\Plugins\reply-account\browser.js" />
|
||||
<None Include="Resources\Plugins\reply-account\configuration.default.js" />
|
||||
<None Include="Resources\Plugins\templates\.meta" />
|
||||
<None Include="Resources\Plugins\templates\browser.js" />
|
||||
<None Include="Resources\Plugins\timeline-polls\.meta" />
|
||||
<None Include="Resources\Plugins\timeline-polls\browser.js" />
|
||||
<None Include="Resources\Scripts\code.js" />
|
||||
<None Include="Resources\Scripts\introduction.js" />
|
||||
<None Include="Resources\Scripts\notification.js" />
|
||||
<None Include="Resources\Scripts\pages\error.html" />
|
||||
<None Include="Resources\Scripts\pages\example.html" />
|
||||
<None Include="Resources\Scripts\plugins.browser.js" />
|
||||
<None Include="Resources\Scripts\plugins.js" />
|
||||
<None Include="Resources\Scripts\plugins.notification.js" />
|
||||
<None Include="Resources\Scripts\screenshot.js" />
|
||||
<None Include="Resources\Scripts\styles\browser.css" />
|
||||
<None Include="Resources\Scripts\styles\notification.css" />
|
||||
<None Include="Resources\Scripts\twitter.js" />
|
||||
<None Include="Resources\Scripts\update.js" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="subprocess\TweetDuck.Browser.csproj">
|
||||
@@ -419,7 +437,6 @@ powershell -ExecutionPolicy Unrestricted -File "$(ProjectDir)Resources\PostBuild
|
||||
<Exec Command="del "$(TargetDir)*.pdb"" />
|
||||
<Exec Command="del "$(TargetDir)*.xml"" />
|
||||
<Delete Files="$(TargetDir)CefSharp.BrowserSubprocess.exe" />
|
||||
<Delete Files="$(TargetDir)devtools_resources.pak" />
|
||||
<Delete Files="$(TargetDir)widevinecdmadapter.dll" />
|
||||
</Target>
|
||||
<PropertyGroup>
|
||||
@@ -429,13 +446,11 @@ powershell -ExecutionPolicy Unrestricted -File "$(ProjectDir)Resources\PostBuild
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('packages\cef.redist.x64.3.3282.1731\build\cef.redist.x64.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x64.3.3282.1731\build\cef.redist.x64.props'))" />
|
||||
<Error Condition="!Exists('packages\cef.redist.x86.3.3282.1731\build\cef.redist.x86.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x86.3.3282.1731\build\cef.redist.x86.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.Common.64.0.0-CI2508\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.64.0.0-CI2508\build\CefSharp.Common.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.Common.64.0.0-CI2508\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.64.0.0-CI2508\build\CefSharp.Common.targets'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.WinForms.64.0.0-CI2508\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.64.0.0-CI2508\build\CefSharp.WinForms.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.WinForms.64.0.0-CI2508\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.64.0.0-CI2508\build\CefSharp.WinForms.targets'))" />
|
||||
<Error Condition="!Exists('packages\cef.redist.x64.3.3359.1772\build\cef.redist.x64.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x64.3.3359.1772\build\cef.redist.x64.props'))" />
|
||||
<Error Condition="!Exists('packages\cef.redist.x86.3.3359.1772\build\cef.redist.x86.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x86.3.3359.1772\build\cef.redist.x86.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.Common.66.0.0-CI2629\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.66.0.0-CI2629\build\CefSharp.Common.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.Common.66.0.0-CI2629\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.66.0.0-CI2629\build\CefSharp.Common.targets'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.WinForms.66.0.0-CI2629\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.66.0.0-CI2629\build\CefSharp.WinForms.props'))" />
|
||||
</Target>
|
||||
<Import Project="packages\CefSharp.Common.64.0.0-CI2508\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.64.0.0-CI2508\build\CefSharp.Common.targets')" />
|
||||
<Import Project="packages\CefSharp.WinForms.64.0.0-CI2508\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.64.0.0-CI2508\build\CefSharp.WinForms.targets')" />
|
||||
<Import Project="packages\CefSharp.Common.66.0.0-CI2629\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.66.0.0-CI2629\build\CefSharp.Common.targets')" />
|
||||
</Project>
|
@@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck.Video", "video\Tw
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Communication", "lib\TweetLib.Communication\TweetLib.Communication.csproj", "{72473763-4B9D-4FB6-A923-9364B2680F06}"
|
||||
EndProject
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TweetTest.Unit", "lib\TweetTest.Unit\TweetTest.Unit.fsproj", "{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x86 = Debug|x86
|
||||
@@ -28,7 +30,8 @@ Global
|
||||
{B10B0017-819E-4F71-870F-8256B36A26AA}.Release|x86.Build.0 = Release|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Debug|x86.Build.0 = Debug|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Release|x86.ActiveCfg = Release|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Release|x86.ActiveCfg = Debug|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Release|x86.Build.0 = Debug|x86
|
||||
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Debug|x86.Build.0 = Debug|x86
|
||||
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Release|x86.ActiveCfg = Release|x86
|
||||
@@ -37,6 +40,10 @@ Global
|
||||
{72473763-4B9D-4FB6-A923-9364B2680F06}.Debug|x86.Build.0 = Debug|x86
|
||||
{72473763-4B9D-4FB6-A923-9364B2680F06}.Release|x86.ActiveCfg = Release|x86
|
||||
{72473763-4B9D-4FB6-A923-9364B2680F06}.Release|x86.Build.0 = Release|x86
|
||||
{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Debug|x86.Build.0 = Debug|x86
|
||||
{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Release|x86.ActiveCfg = Debug|x86
|
||||
{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Release|x86.Build.0 = Debug|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@@ -22,7 +22,7 @@ namespace TweetDuck.Updates{
|
||||
|
||||
this.VersionTag = versionTag;
|
||||
this.ReleaseNotes = releaseNotes;
|
||||
this.InstallerPath = Path.Combine(installerFolder, $"TweetDuck.{versionTag}.exe");;
|
||||
this.InstallerPath = Path.Combine(installerFolder, $"TweetDuck.{versionTag}.exe");
|
||||
}
|
||||
|
||||
public void BeginSilentDownload(){
|
||||
|
@@ -1,30 +0,0 @@
|
||||
// Copyright <20> 2010-2016 The CefSharp Authors
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
//
|
||||
// * Neither the name of Google Inc. nor the name Chromium Embedded
|
||||
// Framework nor the name CefSharp nor the names of its contributors
|
||||
// may be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@@ -1,9 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
98
bld/Resources/LICENSES.txt
Normal file
98
bld/Resources/LICENSES.txt
Normal file
@@ -0,0 +1,98 @@
|
||||
===============================================================================
|
||||
TweetDuck
|
||||
===============================================================================
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
|
||||
===============================================================================
|
||||
CefSharp
|
||||
===============================================================================
|
||||
|
||||
// Copyright © 2010-2017 The CefSharp Authors
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
//
|
||||
// * Neither the name of Google Inc. nor the name Chromium Embedded
|
||||
// Framework nor the name CefSharp nor the names of its contributors
|
||||
// may be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
|
||||
===============================================================================
|
||||
Chromium Embedded Framework
|
||||
===============================================================================
|
||||
|
||||
// Copyright (c) 2008-2014 Marshall A. Greenblatt. Portions Copyright (c)
|
||||
// 2006-2009 Google Inc. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the name Chromium Embedded
|
||||
// Framework nor the names of its contributors may be used to endorse
|
||||
// or promote products derived from this software without specific prior
|
||||
// written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
Binary file not shown.
@@ -24,12 +24,15 @@ DefaultGroupName={#MyAppName}
|
||||
OutputBaseFilename={#MyAppName}
|
||||
VersionInfoVersion={#MyAppVersion}
|
||||
SetupIconFile=.\Resources\icon.ico
|
||||
CloseApplicationsFilter=*.exe,*.dll,*.pak
|
||||
RestartApplications=False
|
||||
Uninstallable=TDIsUninstallable
|
||||
UninstallDisplayName={#MyAppName}
|
||||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||
Compression=lzma
|
||||
Compression=lzma2/ultra
|
||||
LZMADictionarySize=15360
|
||||
SolidCompression=yes
|
||||
InternalCompressLevel=max
|
||||
InternalCompressLevel=normal
|
||||
MinVersion=0,6.1
|
||||
|
||||
#include <idp.iss>
|
||||
@@ -38,11 +41,13 @@ MinVersion=0,6.1
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalTasks}"; Flags: unchecked
|
||||
Name: "devtools"; Description: "{cm:TaskDevTools}"; GroupDescription: "{cm:AdditionalTasks}"; Flags: unchecked
|
||||
|
||||
[Files]
|
||||
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.pdb,CefSharp.BrowserSubprocess.exe,devtools_resources.pak,widevinecdmadapter.dll"
|
||||
Source: "..\bin\x86\Release\devtools_resources.pak"; DestDir: "{app}"; Flags: ignoreversion; Tasks: devtools
|
||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "devtools_resources.pak"
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUninstallable
|
||||
@@ -58,9 +63,14 @@ Type: filesandordirs; Name: "{app}\scripts"
|
||||
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\Cache"
|
||||
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache"
|
||||
|
||||
[CustomMessages]
|
||||
AdditionalTasks=Additional shortcuts and components:
|
||||
TaskDevTools=Install dev tools
|
||||
|
||||
[Code]
|
||||
var UpdatePath: String;
|
||||
var ForceRedistPrompt: String;
|
||||
var VisitedTasksPage: Boolean;
|
||||
|
||||
function TDGetNetFrameworkVersion: Cardinal; forward;
|
||||
function TDIsVCMissing: Boolean; forward;
|
||||
@@ -71,19 +81,20 @@ function InitializeSetup: Boolean;
|
||||
begin
|
||||
UpdatePath := ExpandConstant('{param:UPDATEPATH}')
|
||||
ForceRedistPrompt := ExpandConstant('{param:PROMPTREDIST}')
|
||||
VisitedTasksPage := False
|
||||
|
||||
if (TDGetNetFrameworkVersion() < 379893) and (MsgBox('{#MyAppName} requires .NET Framework 4.5.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
|
||||
begin
|
||||
Result := False;
|
||||
Exit;
|
||||
Result := False
|
||||
Exit
|
||||
end;
|
||||
|
||||
if (TDIsVCMissing() or (ForceRedistPrompt = '1')) and (MsgBox('Microsoft Visual C++ 2015 appears to be missing, would you like to automatically install it?', mbConfirmation, MB_YESNO) = IDYES) then
|
||||
begin
|
||||
idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/{#VCRedistLink}', ExpandConstant('{tmp}\{#MyAppName}.VC.exe'));
|
||||
idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/{#VCRedistLink}', ExpandConstant('{tmp}\{#MyAppName}.VC.exe'))
|
||||
end;
|
||||
|
||||
Result := True;
|
||||
Result := True
|
||||
end;
|
||||
|
||||
{ Set the installation path if updating, and prepare download plugin if there are any files to download. }
|
||||
@@ -91,12 +102,12 @@ procedure InitializeWizard();
|
||||
begin
|
||||
if (UpdatePath <> '') then
|
||||
begin
|
||||
WizardForm.DirEdit.Text := UpdatePath;
|
||||
WizardForm.DirEdit.Text := UpdatePath
|
||||
end;
|
||||
|
||||
if (idpFilesCount <> 0) then
|
||||
begin
|
||||
idpDownloadAfter(wpReady);
|
||||
idpDownloadAfter(wpReady)
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -106,12 +117,14 @@ begin
|
||||
Result := (PageID = wpSelectDir) and (UpdatePath <> '')
|
||||
end;
|
||||
|
||||
{ Check the desktop icon task if not updating. }
|
||||
{ Check the desktop icon task if not updating, and dev tools task if already installed. }
|
||||
procedure CurPageChanged(CurPageID: Integer);
|
||||
begin
|
||||
if CurPageID = wpSelectTasks then
|
||||
if (CurPageID = wpSelectTasks) and (not VisitedTasksPage) then
|
||||
begin
|
||||
WizardForm.TasksList.Checked[WizardForm.TasksList.Items.Count-1] := (UpdatePath = '');
|
||||
WizardForm.TasksList.Checked[WizardForm.TasksList.Items.Count-2] := (UpdatePath = '')
|
||||
WizardForm.TasksList.Checked[WizardForm.TasksList.Items.Count-1] := FileExists(ExpandConstant('{app}\devtools_resources.pak'))
|
||||
VisitedTasksPage := True
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -120,7 +133,7 @@ procedure CurStepChanged(CurStep: TSetupStep);
|
||||
begin
|
||||
if CurStep = ssInstall then
|
||||
begin
|
||||
TDInstallVCRedist();
|
||||
TDInstallVCRedist()
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -137,9 +150,9 @@ begin
|
||||
|
||||
if (DirExists(ProfileDataFolder) or DirExists(PluginDataFolder)) and (MsgBox('Do you also want to delete your {#MyAppName} profile and plugins?', mbConfirmation, MB_YESNO or MB_DEFBUTTON2) = IDYES) then
|
||||
begin
|
||||
DelTree(ProfileDataFolder, True, True, True);
|
||||
DelTree(PluginDataFolder, True, True, True);
|
||||
DelTree(ExpandConstant('{app}'), True, False, False);
|
||||
DelTree(ProfileDataFolder, True, True, True)
|
||||
DelTree(PluginDataFolder, True, True, True)
|
||||
DelTree(ExpandConstant('{app}'), True, False, False)
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
@@ -157,11 +170,11 @@ var FrameworkVersion: Cardinal;
|
||||
begin
|
||||
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'Software\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', FrameworkVersion) then
|
||||
begin
|
||||
Result := FrameworkVersion;
|
||||
Exit;
|
||||
Result := FrameworkVersion
|
||||
Exit
|
||||
end;
|
||||
|
||||
Result := 0;
|
||||
Result := 0
|
||||
end;
|
||||
|
||||
{ Check if Visual C++ 2015 or 2017 is installed. }
|
||||
@@ -176,20 +189,20 @@ begin
|
||||
begin
|
||||
for Index := 0 to GetArrayLength(Keys)-1 do
|
||||
begin
|
||||
Key := Keys[Index];
|
||||
Key := Keys[Index]
|
||||
|
||||
if RegQueryStringValue(HKEY_LOCAL_MACHINE, 'Software\Classes\Installer\Dependencies\'+Key, 'DisplayName', DisplayName) then
|
||||
begin
|
||||
if (Pos('Microsoft Visual C++', DisplayName) = 1) and (Pos('(x86)', DisplayName) > 1) and ((Pos(' 2015 ', DisplayName) > 1) or (Pos(' 2017 ', DisplayName) > 1)) then
|
||||
begin
|
||||
Result := False;
|
||||
Exit;
|
||||
Result := False
|
||||
Exit
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := True;
|
||||
Result := True
|
||||
end;
|
||||
|
||||
{ Run the Visual C++ installer if downloaded. }
|
||||
@@ -202,26 +215,26 @@ begin
|
||||
|
||||
if FileExists(InstallFile) then
|
||||
begin
|
||||
WizardForm.ProgressGauge.Style := npbstMarquee;
|
||||
WizardForm.ProgressGauge.Style := npbstMarquee
|
||||
|
||||
try
|
||||
if Exec(InstallFile, '/passive /norestart', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then
|
||||
begin
|
||||
if ResultCode <> 0 then
|
||||
begin
|
||||
DeleteFile(InstallFile);
|
||||
Exit;
|
||||
DeleteFile(InstallFile)
|
||||
Exit
|
||||
end;
|
||||
end else
|
||||
begin
|
||||
MsgBox('Could not run the Visual C++ installer, please visit https://github.com/{#MyAppPublisher}/{#MyAppName}/{#VCRedistLink} and download the latest version manually. Error: '+SysErrorMessage(ResultCode), mbCriticalError, MB_OK);
|
||||
|
||||
DeleteFile(InstallFile);
|
||||
Exit;
|
||||
DeleteFile(InstallFile)
|
||||
Exit
|
||||
end;
|
||||
finally
|
||||
WizardForm.ProgressGauge.Style := npbstNormal;
|
||||
DeleteFile(InstallFile);
|
||||
WizardForm.ProgressGauge.Style := npbstNormal
|
||||
DeleteFile(InstallFile)
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
@@ -24,12 +24,15 @@ DefaultGroupName={#MyAppName}
|
||||
OutputBaseFilename={#MyAppName}.Portable
|
||||
VersionInfoVersion={#MyAppVersion}
|
||||
SetupIconFile=.\Resources\icon.ico
|
||||
CloseApplicationsFilter=*.exe,*.dll,*.pak
|
||||
RestartApplications=False
|
||||
Uninstallable=no
|
||||
UsePreviousAppDir=no
|
||||
PrivilegesRequired=lowest
|
||||
Compression=lzma
|
||||
Compression=lzma2/ultra
|
||||
LZMADictionarySize=15360
|
||||
SolidCompression=yes
|
||||
InternalCompressLevel=max
|
||||
InternalCompressLevel=normal
|
||||
MinVersion=0,6.1
|
||||
|
||||
#include <idp.iss>
|
||||
@@ -37,16 +40,25 @@ MinVersion=0,6.1
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "devtools"; Description: "{cm:TaskDevTools}"; GroupDescription: "{cm:AdditionalTasks}"; Flags: unchecked
|
||||
|
||||
[Files]
|
||||
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.pdb,CefSharp.BrowserSubprocess.exe,devtools_resources.pak,widevinecdmadapter.dll"
|
||||
Source: "..\bin\x86\Release\devtools_resources.pak"; DestDir: "{app}"; Flags: ignoreversion; Tasks: devtools
|
||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "devtools_resources.pak"
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall shellexec skipifsilent
|
||||
|
||||
[CustomMessages]
|
||||
AdditionalTasks=Additional components:
|
||||
TaskDevTools=Install dev tools
|
||||
|
||||
[Code]
|
||||
var UpdatePath: String;
|
||||
var ForceRedistPrompt: String;
|
||||
var VisitedTasksPage: Boolean;
|
||||
|
||||
function TDGetNetFrameworkVersion: Cardinal; forward;
|
||||
function TDIsVCMissing: Boolean; forward;
|
||||
@@ -57,19 +69,20 @@ function InitializeSetup: Boolean;
|
||||
begin
|
||||
UpdatePath := ExpandConstant('{param:UPDATEPATH}')
|
||||
ForceRedistPrompt := ExpandConstant('{param:PROMPTREDIST}')
|
||||
VisitedTasksPage := False
|
||||
|
||||
if (TDGetNetFrameworkVersion() < 379893) and (MsgBox('{#MyAppName} requires .NET Framework 4.5.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
|
||||
begin
|
||||
Result := False;
|
||||
Exit;
|
||||
Result := False
|
||||
Exit
|
||||
end;
|
||||
|
||||
if (TDIsVCMissing() or (ForceRedistPrompt = '1')) and (MsgBox('Microsoft Visual C++ 2015 appears to be missing, would you like to automatically install it?', mbConfirmation, MB_YESNO) = IDYES) then
|
||||
begin
|
||||
idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/{#VCRedistLink}', ExpandConstant('{tmp}\{#MyAppName}.VC.exe'));
|
||||
idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/{#VCRedistLink}', ExpandConstant('{tmp}\{#MyAppName}.VC.exe'))
|
||||
end;
|
||||
|
||||
Result := True;
|
||||
Result := True
|
||||
end;
|
||||
|
||||
{ Set the installation path if updating, and prepare download plugin if there are any files to download. }
|
||||
@@ -77,12 +90,12 @@ procedure InitializeWizard();
|
||||
begin
|
||||
if (UpdatePath <> '') then
|
||||
begin
|
||||
WizardForm.DirEdit.Text := UpdatePath;
|
||||
WizardForm.DirEdit.Text := UpdatePath
|
||||
end;
|
||||
|
||||
if (idpFilesCount <> 0) then
|
||||
begin
|
||||
idpDownloadAfter(wpReady);
|
||||
idpDownloadAfter(wpReady)
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -92,6 +105,16 @@ begin
|
||||
Result := (PageID = wpSelectDir) and (UpdatePath <> '')
|
||||
end;
|
||||
|
||||
{ Check the dev tools task if already installed. }
|
||||
procedure CurPageChanged(CurPageID: Integer);
|
||||
begin
|
||||
if (CurPageID = wpSelectTasks) and (not VisitedTasksPage) then
|
||||
begin
|
||||
WizardForm.TasksList.Checked[WizardForm.TasksList.Items.Count-1] := FileExists(ExpandConstant('{app}\devtools_resources.pak'))
|
||||
VisitedTasksPage := True
|
||||
end;
|
||||
end;
|
||||
|
||||
{ Install VC++ if downloaded, and create a 'makeportable' file for portable installs. }
|
||||
procedure CurStepChanged(CurStep: TSetupStep);
|
||||
begin
|
||||
@@ -104,7 +127,7 @@ begin
|
||||
begin
|
||||
if MsgBox('Could not create a ''makeportable'' file in the installation folder. If the file is not present, the installation will not be fully portable.', mbCriticalError, MB_RETRYCANCEL) <> IDRETRY then
|
||||
begin
|
||||
break;
|
||||
break
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
@@ -117,11 +140,11 @@ var FrameworkVersion: Cardinal;
|
||||
begin
|
||||
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'Software\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', FrameworkVersion) then
|
||||
begin
|
||||
Result := FrameworkVersion;
|
||||
Exit;
|
||||
Result := FrameworkVersion
|
||||
Exit
|
||||
end;
|
||||
|
||||
Result := 0;
|
||||
Result := 0
|
||||
end;
|
||||
|
||||
{ Check if Visual C++ 2015 or 2017 is installed. }
|
||||
@@ -136,20 +159,20 @@ begin
|
||||
begin
|
||||
for Index := 0 to GetArrayLength(Keys)-1 do
|
||||
begin
|
||||
Key := Keys[Index];
|
||||
Key := Keys[Index]
|
||||
|
||||
if RegQueryStringValue(HKEY_LOCAL_MACHINE, 'Software\Classes\Installer\Dependencies\'+Key, 'DisplayName', DisplayName) then
|
||||
begin
|
||||
if (Pos('Microsoft Visual C++', DisplayName) = 1) and (Pos('(x86)', DisplayName) > 1) and ((Pos(' 2015 ', DisplayName) > 1) or (Pos(' 2017 ', DisplayName) > 1)) then
|
||||
begin
|
||||
Result := False;
|
||||
Exit;
|
||||
Result := False
|
||||
Exit
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := True;
|
||||
Result := True
|
||||
end;
|
||||
|
||||
{ Run the Visual C++ installer if downloaded. }
|
||||
@@ -162,26 +185,26 @@ begin
|
||||
|
||||
if FileExists(InstallFile) then
|
||||
begin
|
||||
WizardForm.ProgressGauge.Style := npbstMarquee;
|
||||
WizardForm.ProgressGauge.Style := npbstMarquee
|
||||
|
||||
try
|
||||
if Exec(InstallFile, '/passive /norestart', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then
|
||||
begin
|
||||
if ResultCode <> 0 then
|
||||
begin
|
||||
DeleteFile(InstallFile);
|
||||
Exit;
|
||||
DeleteFile(InstallFile)
|
||||
Exit
|
||||
end;
|
||||
end else
|
||||
begin
|
||||
MsgBox('Could not run the Visual C++ installer, please visit https://github.com/{#MyAppPublisher}/{#MyAppName}/{#VCRedistLink} and download the latest version manually. Error: '+SysErrorMessage(ResultCode), mbCriticalError, MB_OK);
|
||||
|
||||
DeleteFile(InstallFile);
|
||||
Exit;
|
||||
DeleteFile(InstallFile)
|
||||
Exit
|
||||
end;
|
||||
finally
|
||||
WizardForm.ProgressGauge.Style := npbstNormal;
|
||||
DeleteFile(InstallFile);
|
||||
WizardForm.ProgressGauge.Style := npbstNormal
|
||||
DeleteFile(InstallFile)
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
141
bld/gen_upd.iss
141
bld/gen_upd.iss
@@ -13,7 +13,7 @@
|
||||
|
||||
[Setup]
|
||||
AppId={{{#MyAppID}}
|
||||
AppName={#MyAppName} Update
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppVerName={#MyAppName} {#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
@@ -25,11 +25,14 @@ DefaultGroupName={#MyAppName}
|
||||
OutputBaseFilename={#MyAppName}.Update
|
||||
VersionInfoVersion={#MyAppVersion}
|
||||
SetupIconFile=.\Resources\icon.ico
|
||||
CloseApplicationsFilter=*.exe,*.dll,*.pak
|
||||
RestartApplications=False
|
||||
Uninstallable=TDIsUninstallable
|
||||
UninstallDisplayName={#MyAppName}
|
||||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||
PrivilegesRequired=lowest
|
||||
Compression=lzma/normal
|
||||
LZMADictionarySize=512
|
||||
SolidCompression=True
|
||||
InternalCompressLevel=normal
|
||||
MinVersion=0,6.1
|
||||
@@ -60,63 +63,10 @@ Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\Cache"
|
||||
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache"
|
||||
|
||||
[InstallDelete]
|
||||
Type: files; Name: "{app}\msvcp120.dll"
|
||||
Type: files; Name: "{app}\msvcr120.dll"
|
||||
Type: files; Name: "{app}\TweetLib.Audio.dll"
|
||||
Type: files; Name: "{app}\CEFSHARP-LICENSE.txt"
|
||||
Type: files; Name: "{app}\LICENSE.txt"
|
||||
Type: filesandordirs; Name: "{app}\scripts"
|
||||
Type: filesandordirs; Name: "{app}\plugins\official"
|
||||
Type: files; Name: "{app}\locales\am.pak"
|
||||
Type: files; Name: "{app}\locales\ar.pak"
|
||||
Type: files; Name: "{app}\locales\bg.pak"
|
||||
Type: files; Name: "{app}\locales\bn.pak"
|
||||
Type: files; Name: "{app}\locales\ca.pak"
|
||||
Type: files; Name: "{app}\locales\cs.pak"
|
||||
Type: files; Name: "{app}\locales\da.pak"
|
||||
Type: files; Name: "{app}\locales\de.pak"
|
||||
Type: files; Name: "{app}\locales\el.pak"
|
||||
Type: files; Name: "{app}\locales\en-GB.pak"
|
||||
Type: files; Name: "{app}\locales\es-419.pak"
|
||||
Type: files; Name: "{app}\locales\es.pak"
|
||||
Type: files; Name: "{app}\locales\et.pak"
|
||||
Type: files; Name: "{app}\locales\fa.pak"
|
||||
Type: files; Name: "{app}\locales\fi.pak"
|
||||
Type: files; Name: "{app}\locales\fil.pak"
|
||||
Type: files; Name: "{app}\locales\fr.pak"
|
||||
Type: files; Name: "{app}\locales\gu.pak"
|
||||
Type: files; Name: "{app}\locales\he.pak"
|
||||
Type: files; Name: "{app}\locales\hi.pak"
|
||||
Type: files; Name: "{app}\locales\hr.pak"
|
||||
Type: files; Name: "{app}\locales\hu.pak"
|
||||
Type: files; Name: "{app}\locales\id.pak"
|
||||
Type: files; Name: "{app}\locales\it.pak"
|
||||
Type: files; Name: "{app}\locales\ja.pak"
|
||||
Type: files; Name: "{app}\locales\kn.pak"
|
||||
Type: files; Name: "{app}\locales\ko.pak"
|
||||
Type: files; Name: "{app}\locales\lt.pak"
|
||||
Type: files; Name: "{app}\locales\lv.pak"
|
||||
Type: files; Name: "{app}\locales\ml.pak"
|
||||
Type: files; Name: "{app}\locales\mr.pak"
|
||||
Type: files; Name: "{app}\locales\ms.pak"
|
||||
Type: files; Name: "{app}\locales\nb.pak"
|
||||
Type: files; Name: "{app}\locales\nl.pak"
|
||||
Type: files; Name: "{app}\locales\pl.pak"
|
||||
Type: files; Name: "{app}\locales\pt-BR.pak"
|
||||
Type: files; Name: "{app}\locales\pt-PT.pak"
|
||||
Type: files; Name: "{app}\locales\ro.pak"
|
||||
Type: files; Name: "{app}\locales\ru.pak"
|
||||
Type: files; Name: "{app}\locales\sk.pak"
|
||||
Type: files; Name: "{app}\locales\sl.pak"
|
||||
Type: files; Name: "{app}\locales\sr.pak"
|
||||
Type: files; Name: "{app}\locales\sv.pak"
|
||||
Type: files; Name: "{app}\locales\sw.pak"
|
||||
Type: files; Name: "{app}\locales\ta.pak"
|
||||
Type: files; Name: "{app}\locales\te.pak"
|
||||
Type: files; Name: "{app}\locales\th.pak"
|
||||
Type: files; Name: "{app}\locales\tr.pak"
|
||||
Type: files; Name: "{app}\locales\uk.pak"
|
||||
Type: files; Name: "{app}\locales\vi.pak"
|
||||
Type: files; Name: "{app}\locales\zh-CN.pak"
|
||||
Type: files; Name: "{app}\locales\zh-TW.pak"
|
||||
|
||||
[Code]
|
||||
function TDIsUninstallable: Boolean; forward;
|
||||
@@ -139,33 +89,34 @@ begin
|
||||
|
||||
if UpdatePath = '' then
|
||||
begin
|
||||
MsgBox('{#MyAppName} installation could not be found on your system.', mbCriticalError, MB_OK);
|
||||
Result := False;
|
||||
Exit;
|
||||
MsgBox('{#MyAppName} installation could not be found on your system.', mbCriticalError, MB_OK)
|
||||
Result := False
|
||||
Exit
|
||||
end;
|
||||
|
||||
if not TDIsMatchingCEFVersion() then
|
||||
begin
|
||||
idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/releases/download/'+TDGetAppVersionClean()+'/'+TDGetFullDownloadFileName(), ExpandConstant('{tmp}\{#MyAppName}.Full.exe'));
|
||||
idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/releases/download/'+TDGetAppVersionClean()+'/'+TDGetFullDownloadFileName(), ExpandConstant('{tmp}\{#MyAppName}.Full.exe'))
|
||||
end;
|
||||
|
||||
if (TDGetNetFrameworkVersion() < 379893) and (MsgBox('{#MyAppName} requires .NET Framework 4.5.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
|
||||
begin
|
||||
Result := False;
|
||||
Exit;
|
||||
Result := False
|
||||
Exit
|
||||
end;
|
||||
|
||||
Result := True;
|
||||
Result := True
|
||||
end;
|
||||
|
||||
{ Prepare download plugin if there are any files to download, and set the installation path. }
|
||||
procedure InitializeWizard();
|
||||
begin
|
||||
WizardForm.DirEdit.Text := UpdatePath;
|
||||
WizardForm.Caption := WizardForm.Caption + ' Update'
|
||||
WizardForm.DirEdit.Text := UpdatePath
|
||||
|
||||
if idpFilesCount <> 0 then
|
||||
begin
|
||||
idpDownloadAfter(wpReady);
|
||||
idpDownloadAfter(wpReady)
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -177,14 +128,14 @@ var PluginDataFolder: String;
|
||||
begin
|
||||
if CurUninstallStep = usPostUninstall then
|
||||
begin
|
||||
ProfileDataFolder := ExpandConstant('{localappdata}\{#MyAppName}');
|
||||
PluginDataFolder := ExpandConstant('{app}\plugins');
|
||||
ProfileDataFolder := ExpandConstant('{localappdata}\{#MyAppName}')
|
||||
PluginDataFolder := ExpandConstant('{app}\plugins')
|
||||
|
||||
if (DirExists(ProfileDataFolder) or DirExists(PluginDataFolder)) and (MsgBox('Do you also want to delete your {#MyAppName} profile and plugins?', mbConfirmation, MB_YESNO or MB_DEFBUTTON2) = IDYES) then
|
||||
begin
|
||||
DelTree(ProfileDataFolder, True, True, True);
|
||||
DelTree(PluginDataFolder, True, True, True);
|
||||
DelTree(ExpandConstant('{app}'), True, False, False);
|
||||
DelTree(ProfileDataFolder, True, True, True)
|
||||
DelTree(PluginDataFolder, True, True, True)
|
||||
DelTree(ExpandConstant('{app}'), True, False, False)
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
@@ -194,12 +145,12 @@ procedure CurStepChanged(CurStep: TSetupStep);
|
||||
begin
|
||||
if CurStep = ssInstall then
|
||||
begin
|
||||
TDExecuteFullDownload();
|
||||
TDExecuteFullDownload()
|
||||
|
||||
if TDIsUninstallable() then
|
||||
begin
|
||||
DeleteFile(ExpandConstant('{app}\unins000.dat'));
|
||||
DeleteFile(ExpandConstant('{app}\unins000.exe'));
|
||||
DeleteFile(ExpandConstant('{app}\unins000.dat'))
|
||||
DeleteFile(ExpandConstant('{app}\unins000.exe'))
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
@@ -213,6 +164,7 @@ end;
|
||||
{ Returns a string used for arguments when running the app after update. }
|
||||
function TDGetRunArgs(Param: String): String;
|
||||
var Args: String;
|
||||
|
||||
begin
|
||||
Args := ExpandConstant('{param:RUNARGS}')
|
||||
StringChangeEx(Args, '::', '"', True)
|
||||
@@ -254,11 +206,11 @@ var FrameworkVersion: Cardinal;
|
||||
begin
|
||||
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'Software\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', FrameworkVersion) then
|
||||
begin
|
||||
Result := FrameworkVersion;
|
||||
Exit;
|
||||
Result := FrameworkVersion
|
||||
Exit
|
||||
end;
|
||||
|
||||
Result := 0;
|
||||
Result := 0
|
||||
end;
|
||||
|
||||
{ Return the name of the full installer file to download from GitHub. }
|
||||
@@ -270,15 +222,8 @@ end;
|
||||
{ Return whether the version of the installed libcef.dll library matches internal one. }
|
||||
function TDIsMatchingCEFVersion: Boolean;
|
||||
var CEFVersion: String;
|
||||
var TmpTDVersion: String;
|
||||
|
||||
begin
|
||||
if GetVersionNumbersString(UpdatePath+'TweetDuck.exe', TmpTDVersion) and (CompareStr(TmpTDVersion, '1.13.0.0') = 0) then
|
||||
begin
|
||||
Result := False;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
Result := (GetVersionNumbersString(UpdatePath+'libcef.dll', CEFVersion) and (CompareStr(CEFVersion, '{#CefVersion}') = 0))
|
||||
end;
|
||||
|
||||
@@ -292,17 +237,17 @@ begin
|
||||
|
||||
while True do
|
||||
begin
|
||||
Substr := Copy(CleanVersion, Length(CleanVersion)-1, 2);
|
||||
Substr := Copy(CleanVersion, Length(CleanVersion)-1, 2)
|
||||
|
||||
if (CompareStr(Substr, '.0') <> 0) then
|
||||
begin
|
||||
break;
|
||||
break
|
||||
end;
|
||||
|
||||
CleanVersion := Copy(CleanVersion, 1, Length(CleanVersion)-2);
|
||||
CleanVersion := Copy(CleanVersion, 1, Length(CleanVersion)-2)
|
||||
end;
|
||||
|
||||
Result := CleanVersion;
|
||||
Result := CleanVersion
|
||||
end;
|
||||
|
||||
{ Run the full package installer if downloaded. }
|
||||
@@ -315,28 +260,28 @@ begin
|
||||
|
||||
if FileExists(InstallFile) then
|
||||
begin
|
||||
WizardForm.ProgressGauge.Style := npbstMarquee;
|
||||
WizardForm.ProgressGauge.Style := npbstMarquee
|
||||
|
||||
try
|
||||
if Exec(InstallFile, '/SP- /SILENT /MERGETASKS="!desktopicon" /UPDATEPATH="'+UpdatePath+'"', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then
|
||||
if Exec(InstallFile, '/SP- /SILENT /FORCECLOSEAPPLICATIONS /UPDATEPATH="'+UpdatePath+'"', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then
|
||||
begin
|
||||
if ResultCode <> 0 then
|
||||
begin
|
||||
DeleteFile(InstallFile);
|
||||
Abort();
|
||||
Exit;
|
||||
DeleteFile(InstallFile)
|
||||
Abort()
|
||||
Exit
|
||||
end;
|
||||
end else
|
||||
begin
|
||||
MsgBox('Could not run the full installer, please visit {#MyAppURL} and download the latest version manually. Error: '+SysErrorMessage(ResultCode), mbCriticalError, MB_OK);
|
||||
MsgBox('Could not run the full installer, please visit {#MyAppURL} and download the latest version manually. Error: '+SysErrorMessage(ResultCode), mbCriticalError, MB_OK)
|
||||
|
||||
DeleteFile(InstallFile);
|
||||
Abort();
|
||||
Exit;
|
||||
DeleteFile(InstallFile)
|
||||
Abort()
|
||||
Exit
|
||||
end;
|
||||
finally
|
||||
WizardForm.ProgressGauge.Style := npbstNormal;
|
||||
DeleteFile(InstallFile);
|
||||
WizardForm.ProgressGauge.Style := npbstNormal
|
||||
DeleteFile(InstallFile)
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
79
lib/TweetTest.Unit/Core/TestBrowserUtils.fs
Normal file
79
lib/TweetTest.Unit/Core/TestBrowserUtils.fs
Normal file
@@ -0,0 +1,79 @@
|
||||
namespace Unit.Core.BrowserUtils
|
||||
|
||||
open Xunit
|
||||
open TweetDuck.Core.Utils
|
||||
|
||||
|
||||
module CheckUrl =
|
||||
type Result = BrowserUtils.UrlCheckResult
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts HTTP protocol`` () =
|
||||
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("http://example.com"))
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts HTTPS protocol`` () =
|
||||
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("https://example.com"))
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts FTP protocol`` () =
|
||||
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("ftp://example.com"))
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts MAILTO protocol`` () =
|
||||
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("mailto://someone@example.com"))
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts URL with port, path, query, and hash`` () =
|
||||
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("http://www.example.co.uk:80/path?key=abc&array[]=5#hash"))
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts IPv4 address`` () =
|
||||
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("http://127.0.0.1"))
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts IPv6 address`` () =
|
||||
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("http://[2001:db8:0:0:0:ff00:42:8329]"))
|
||||
|
||||
[<Fact>]
|
||||
let ``recognizes t.co as tracking URL`` () =
|
||||
Assert.Equal(Result.Tracking, BrowserUtils.CheckUrl("http://t.co/12345"))
|
||||
|
||||
[<Fact>]
|
||||
let ``rejects empty URL`` () =
|
||||
Assert.Equal(Result.Invalid, BrowserUtils.CheckUrl(""))
|
||||
|
||||
[<Fact>]
|
||||
let ``rejects missing protocol`` () =
|
||||
Assert.Equal(Result.Invalid, BrowserUtils.CheckUrl("www.example.com"))
|
||||
|
||||
[<Fact>]
|
||||
let ``rejects banned protocol`` () =
|
||||
Assert.Equal(Result.Invalid, BrowserUtils.CheckUrl("file://example.com"))
|
||||
|
||||
|
||||
module GetFileNameFromUrl =
|
||||
|
||||
[<Fact>]
|
||||
let ``simple file URL returns file name`` () =
|
||||
Assert.Equal("index.html", BrowserUtils.GetFileNameFromUrl("http://example.com/index.html"))
|
||||
|
||||
[<Fact>]
|
||||
let ``file URL with query returns file name`` () =
|
||||
Assert.Equal("index.html", BrowserUtils.GetFileNameFromUrl("http://example.com/index.html?version=2"))
|
||||
|
||||
[<Fact>]
|
||||
let ``file URL w/o extension returns file name`` () =
|
||||
Assert.Equal("index", BrowserUtils.GetFileNameFromUrl("http://example.com/index"))
|
||||
|
||||
[<Fact>]
|
||||
let ``file URL with trailing dot returns file name with dot`` () =
|
||||
Assert.Equal("index.", BrowserUtils.GetFileNameFromUrl("http://example.com/index."))
|
||||
|
||||
[<Fact>]
|
||||
let ``root URL returns null`` () =
|
||||
Assert.Null(BrowserUtils.GetFileNameFromUrl("http://example.com"))
|
||||
|
||||
[<Fact>]
|
||||
let ``path URL returns null`` () =
|
||||
Assert.Null(BrowserUtils.GetFileNameFromUrl("http://example.com/path/"))
|
118
lib/TweetTest.Unit/Core/TestStringUtils.fs
Normal file
118
lib/TweetTest.Unit/Core/TestStringUtils.fs
Normal file
@@ -0,0 +1,118 @@
|
||||
namespace Unit.Core.StringUtils
|
||||
|
||||
open Xunit
|
||||
open TweetDuck.Core.Utils
|
||||
|
||||
|
||||
module ExtractBefore =
|
||||
|
||||
[<Fact>]
|
||||
let ``empty input string returns empty string`` () =
|
||||
Assert.Equal("", StringUtils.ExtractBefore("", ' '))
|
||||
|
||||
[<Fact>]
|
||||
let ``input string w/o searched char returns input string`` () =
|
||||
Assert.Equal("abc", StringUtils.ExtractBefore("abc", ' '))
|
||||
|
||||
[<Fact>]
|
||||
let ``finding searched char returns everything before it`` () =
|
||||
Assert.Equal("abc", StringUtils.ExtractBefore("abc def ghi", ' '))
|
||||
|
||||
[<Theory>]
|
||||
[<InlineData(0, "abc")>]
|
||||
[<InlineData(3, "abc")>]
|
||||
[<InlineData(4, "abc def")>]
|
||||
[<InlineData(7, "abc def")>]
|
||||
[<InlineData(8, "abc def ghi")>]
|
||||
let ``start index works`` (startIndex: int, expectedValue: string) =
|
||||
Assert.Equal(expectedValue, StringUtils.ExtractBefore("abc def ghi", ' ', startIndex))
|
||||
|
||||
|
||||
module ParseInts =
|
||||
open System
|
||||
|
||||
[<Fact>]
|
||||
let ``empty input string returns empty collection`` () =
|
||||
Assert.Empty(StringUtils.ParseInts("", ','))
|
||||
|
||||
[<Fact>]
|
||||
let ``single integer is parsed correctly`` () =
|
||||
Assert.Equal([ 1 ], StringUtils.ParseInts("1", ','))
|
||||
|
||||
[<Fact>]
|
||||
let ``multiple integers are parsed correctly`` () =
|
||||
Assert.Equal([ -3 .. 3 ], StringUtils.ParseInts("-3,-2,-1,0,1,2,3", ','))
|
||||
|
||||
[<Fact>]
|
||||
let ``excessive delimiters are discarded`` () =
|
||||
Assert.Equal([ 1 .. 4 ], StringUtils.ParseInts(",,1,,,2,3,,4,,,", ','))
|
||||
|
||||
[<Fact>]
|
||||
let ``invalid format throws exception`` () =
|
||||
Assert.Throws<FormatException>(fun () -> StringUtils.ParseInts("1,2,a", ',') |> ignore)
|
||||
|
||||
|
||||
module ConvertPascalCaseToScreamingSnakeCase =
|
||||
|
||||
[<Fact>]
|
||||
let ``converts one word`` () =
|
||||
Assert.Equal("HELP", StringUtils.ConvertPascalCaseToScreamingSnakeCase("Help"))
|
||||
|
||||
[<Fact>]
|
||||
let ``converts two words`` () =
|
||||
Assert.Equal("HELP_ME", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMe"))
|
||||
|
||||
[<Fact>]
|
||||
let ``converts many words`` () =
|
||||
Assert.Equal("HELP_ME_PLEASE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMePlease"))
|
||||
|
||||
[<Fact>]
|
||||
let ``converts one uppercase abbreviation`` () =
|
||||
Assert.Equal("HTML_CODE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HTMLCode"))
|
||||
|
||||
[<Fact>]
|
||||
let ``converts many uppercase abbreviations`` () =
|
||||
Assert.Equal("I_LIKE_HTML_AND_CSS", StringUtils.ConvertPascalCaseToScreamingSnakeCase("ILikeHTMLAndCSS"))
|
||||
|
||||
|
||||
module ConvertRot13 =
|
||||
|
||||
[<Fact>]
|
||||
let ``ignores digits and special characters`` () =
|
||||
Assert.Equal("<123'456.789>", StringUtils.ConvertRot13("<123'456.789>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``converts lowercase letters correctly`` () =
|
||||
Assert.Equal("nopqrstuvwxyzabcdefghijklm", StringUtils.ConvertRot13("abcdefghijklmnopqrstuvwxyz"))
|
||||
|
||||
[<Fact>]
|
||||
let ``converts uppercase letters correctly`` () =
|
||||
Assert.Equal("NOPQRSTUVWXYZABCDEFGHIJKLM", StringUtils.ConvertRot13("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))
|
||||
|
||||
[<Fact>]
|
||||
let ``converts mixed character types correctly`` () =
|
||||
Assert.Equal("Uryyb, jbeyq! :)", StringUtils.ConvertRot13("Hello, world! :)"))
|
||||
|
||||
|
||||
module CountOccurrences =
|
||||
open System
|
||||
|
||||
[<Fact>]
|
||||
let ``empty input string returns zero`` () =
|
||||
Assert.Equal(0, StringUtils.CountOccurrences("", "a"))
|
||||
|
||||
[<Fact>]
|
||||
let ``empty searched string throws`` () =
|
||||
Assert.Throws<ArgumentOutOfRangeException>(fun () -> StringUtils.CountOccurrences("hello", "") |> ignore)
|
||||
|
||||
[<Fact>]
|
||||
let ``counts single letter characters correctly`` () =
|
||||
Assert.Equal(3, StringUtils.CountOccurrences("hello world", "l"))
|
||||
|
||||
[<Fact>]
|
||||
let ``counts longer substrings correctly`` () =
|
||||
Assert.Equal(2, StringUtils.CountOccurrences("hello and welcome in hell", "hell"))
|
||||
|
||||
[<Fact>]
|
||||
let ``does not count overlapping substrings`` () =
|
||||
Assert.Equal(2, StringUtils.CountOccurrences("aaaaa", "aa"))
|
112
lib/TweetTest.Unit/Core/TestTwitterUtils.fs
Normal file
112
lib/TweetTest.Unit/Core/TestTwitterUtils.fs
Normal file
@@ -0,0 +1,112 @@
|
||||
namespace Unit.Core.TwitterUtils
|
||||
|
||||
open Xunit
|
||||
open TweetDuck.Core.Utils
|
||||
|
||||
|
||||
[<Collection("RegexAccount")>]
|
||||
module RegexAccount_IsMatch =
|
||||
let isMatch = TwitterUtils.RegexAccount.IsMatch
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts HTTP protocol`` () =
|
||||
Assert.True(isMatch("http://twitter.com/chylexmc"))
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts HTTPS protocol`` () =
|
||||
Assert.True(isMatch("https://twitter.com/chylexmc"))
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts trailing slash`` () =
|
||||
Assert.True(isMatch("https://twitter.com/chylexmc/"))
|
||||
|
||||
[<Fact>]
|
||||
let ``rejects URL with query`` () =
|
||||
Assert.False(isMatch("https://twitter.com/chylexmc?query"))
|
||||
|
||||
[<Fact>]
|
||||
let ``rejects URL with extra path`` () =
|
||||
Assert.False(isMatch("https://twitter.com/chylexmc/status/123"))
|
||||
|
||||
[<Theory>]
|
||||
[<InlineData("signup")>]
|
||||
[<InlineData("tos")>]
|
||||
[<InlineData("privacy")>]
|
||||
[<InlineData("search")>]
|
||||
[<InlineData("search?query")>]
|
||||
[<InlineData("search-home")>]
|
||||
[<InlineData("search-advanced")>]
|
||||
let ``rejects reserved page names`` (name: string) =
|
||||
Assert.False(isMatch("https://twitter.com/"+name))
|
||||
|
||||
[<Theory>]
|
||||
[<InlineData("tosser")>]
|
||||
[<InlineData("searching")>]
|
||||
let ``accepts accounts starting with reserved page names`` (name: string) =
|
||||
Assert.True(isMatch("https://twitter.com/"+name))
|
||||
|
||||
|
||||
[<Collection("RegexAccount")>]
|
||||
module RegexAccount_Match =
|
||||
let extract str = TwitterUtils.RegexAccount.Match(str).Groups.[1].Value
|
||||
|
||||
[<Fact>]
|
||||
let ``extracts account name from simple URL`` () =
|
||||
Assert.Equal("_abc_DEF_123", extract("https://twitter.com/_abc_DEF_123"))
|
||||
|
||||
[<Fact>]
|
||||
let ``extracts account name from URL with trailing slash`` () =
|
||||
Assert.Equal("_abc_DEF_123", extract("https://twitter.com/_abc_DEF_123/"))
|
||||
|
||||
|
||||
module GetMediaLink_Default =
|
||||
let getMediaLinkDefault url = TwitterUtils.GetMediaLink(url, TwitterUtils.ImageQuality.Default)
|
||||
let domain = "https://pbs.twimg.com"
|
||||
|
||||
[<Fact>]
|
||||
let ``does not modify URL w/o extension`` () =
|
||||
Assert.Equal(domain+"/media/123", getMediaLinkDefault(domain+"/media/123"))
|
||||
|
||||
[<Fact>]
|
||||
let ``does not modify URL w/o quality suffix`` () =
|
||||
Assert.Equal(domain+"/media/123.jpg", getMediaLinkDefault(domain+"/media/123.jpg"))
|
||||
|
||||
[<Fact>]
|
||||
let ``does not modify URL with quality suffix`` () =
|
||||
Assert.Equal(domain+"/media/123.jpg:small", getMediaLinkDefault(domain+"/media/123.jpg:small"))
|
||||
|
||||
|
||||
module GetMediaLink_Orig =
|
||||
let getMediaLinkOrig url = TwitterUtils.GetMediaLink(url, TwitterUtils.ImageQuality.Orig)
|
||||
let domain = "https://pbs.twimg.com"
|
||||
|
||||
[<Fact>]
|
||||
let ``appends :orig to valid URL w/o quality suffix`` () =
|
||||
Assert.Equal(domain+"/media/123.jpg:orig", getMediaLinkOrig(domain+"/media/123.jpg"))
|
||||
|
||||
[<Fact>]
|
||||
let ``rewrites :orig into valid URL with quality suffix`` () =
|
||||
Assert.Equal(domain+"/media/123.jpg:orig", getMediaLinkOrig(domain+"/media/123.jpg:small"))
|
||||
|
||||
[<Fact>]
|
||||
let ``does not modify unknown URL w/o quality suffix`` () =
|
||||
Assert.Equal(domain+"/profile_images/123.jpg", getMediaLinkOrig(domain+"/profile_images/123.jpg"))
|
||||
|
||||
[<Fact>]
|
||||
let ``rewrites :orig into unknown URL with quality suffix`` () =
|
||||
Assert.Equal(domain+"/profile_images/123.jpg:orig", getMediaLinkOrig(domain+"/profile_images/123.jpg:small"))
|
||||
|
||||
|
||||
module GetImageFileName =
|
||||
|
||||
[<Fact>]
|
||||
let ``extracts file name from URL w/o quality suffix`` () =
|
||||
Assert.Equal("test.jpg", TwitterUtils.GetImageFileName("http://example.com/test.jpg"))
|
||||
|
||||
[<Fact>]
|
||||
let ``extracts file name from URL with quality suffix`` () =
|
||||
Assert.Equal("test.jpg", TwitterUtils.GetImageFileName("http://example.com/test.jpg:orig"))
|
||||
|
||||
[<Fact>]
|
||||
let ``extracts file name from URL with a port`` () =
|
||||
Assert.Equal("test.jpg", TwitterUtils.GetImageFileName("http://example.com:80/test.jpg"))
|
49
lib/TweetTest.Unit/Data/TestInjectedHTML.fs
Normal file
49
lib/TweetTest.Unit/Data/TestInjectedHTML.fs
Normal file
@@ -0,0 +1,49 @@
|
||||
namespace Unit.Data.InjectedHTML
|
||||
|
||||
open Xunit
|
||||
open TweetDuck.Data
|
||||
|
||||
|
||||
module Inject =
|
||||
let before = InjectedHTML.Position.Before
|
||||
let after = InjectedHTML.Position.After
|
||||
|
||||
[<Fact>]
|
||||
let ``injecting string before searched string works`` () =
|
||||
Assert.Equal("<p>source[left]<br>code</p>", InjectedHTML(before, "<br>", "[left]").InjectInto("<p>source<br>code</p>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``injecting string after searched string works`` () =
|
||||
Assert.Equal("<p>source<br>[right]code</p>", InjectedHTML(after, "<br>", "[right]").InjectInto("<p>source<br>code</p>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``injecting string at the beginning works`` () =
|
||||
Assert.Equal("[start]<p>source<br>code</p>", InjectedHTML(before, "<p>", "[start]").InjectInto("<p>source<br>code</p>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``injecting string at the end works`` () =
|
||||
Assert.Equal("<p>source<br>code</p>[end]", InjectedHTML(after, "</p>", "[end]").InjectInto("<p>source<br>code</p>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``injection only triggers for first occurrence of searched string`` () =
|
||||
Assert.Equal("<p>source[left]<br>code</p><br>", InjectedHTML(before, "<br>", "[left]").InjectInto("<p>source<br>code</p><br>"))
|
||||
Assert.Equal("<p>source<br>[right]code</p><br>", InjectedHTML(after, "<br>", "[right]").InjectInto("<p>source<br>code</p><br>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``empty searched string injects at the beginning`` () =
|
||||
Assert.Equal("[start]<p>source<br>code</p>", InjectedHTML(before, "", "[start]").InjectInto("<p>source<br>code</p>"))
|
||||
Assert.Equal("[start]<p>source<br>code</p>", InjectedHTML(after, "", "[start]").InjectInto("<p>source<br>code</p>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``injecting empty string does not modify source`` () =
|
||||
Assert.Equal("<p>source<br>code</p>", InjectedHTML(before, "<br>", "").InjectInto("<p>source<br>code</p>"))
|
||||
Assert.Equal("<p>source<br>code</p>", InjectedHTML(after, "<br>", "").InjectInto("<p>source<br>code</p>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``failed match does not modify source`` () =
|
||||
Assert.Equal("<p>source<br>code</p>", InjectedHTML(before, "<wrong>", "[left]").InjectInto("<p>source<br>code</p>"))
|
||||
Assert.Equal("<p>source<br>code</p>", InjectedHTML(after, "<wrong>", "[right]").InjectInto("<p>source<br>code</p>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``invalid position does not modify source`` () =
|
||||
Assert.Equal("<p>source<br>code</p>", InjectedHTML(enum<_>(1000), "<br>", "[somewhere]").InjectInto("<p>source<br>code</p>"))
|
57
lib/TweetTest.Unit/Data/TestResult.fs
Normal file
57
lib/TweetTest.Unit/Data/TestResult.fs
Normal file
@@ -0,0 +1,57 @@
|
||||
namespace Unit.Data
|
||||
|
||||
open Xunit
|
||||
open TweetDuck.Data
|
||||
open System
|
||||
|
||||
|
||||
module TestResult_WithValue =
|
||||
let result = Result<int>(10)
|
||||
|
||||
[<Fact>]
|
||||
let ``HasValue returns true`` () =
|
||||
Assert.True(result.HasValue)
|
||||
|
||||
[<Fact>]
|
||||
let ``accessing Value returns the provided value`` () =
|
||||
Assert.Equal(10, result.Value)
|
||||
|
||||
[<Fact>]
|
||||
let ``accessing Exception throws`` () =
|
||||
Assert.Throws<InvalidOperationException>(fun () -> result.Exception |> ignore)
|
||||
|
||||
[<Fact>]
|
||||
let ``Handle calls the correct callback`` () =
|
||||
let passTest = fun _ -> ()
|
||||
let failTest = fun _ -> Assert.True(false)
|
||||
result.Handle(Action<_>(passTest), Action<_>(failTest))
|
||||
|
||||
[<Fact>]
|
||||
let ``Select returns another valid Result`` () =
|
||||
Assert.Equal(20, result.Select(fun x -> x * 2).Value)
|
||||
|
||||
|
||||
module TestResult_WithException =
|
||||
let result = Result<int>(IndexOutOfRangeException("bad"))
|
||||
|
||||
[<Fact>]
|
||||
let ``HasValue returns false`` () =
|
||||
Assert.False(result.HasValue)
|
||||
|
||||
[<Fact>]
|
||||
let ``accessing Value throws`` () =
|
||||
Assert.Throws<InvalidOperationException>(fun () -> result.Value |> ignore)
|
||||
|
||||
[<Fact>]
|
||||
let ``accessing Exception returns the provided exception`` () =
|
||||
Assert.IsType<IndexOutOfRangeException>(result.Exception)
|
||||
|
||||
[<Fact>]
|
||||
let ``Handle calls the correct callback`` () =
|
||||
let passTest = fun _ -> ()
|
||||
let failTest = fun _ -> Assert.True(false)
|
||||
result.Handle(Action<_>(failTest), Action<_>(passTest))
|
||||
|
||||
[<Fact>]
|
||||
let ``Select returns a Result with the same Exception`` () =
|
||||
Assert.Same(result.Exception, result.Select(fun x -> x * 2).Exception)
|
109
lib/TweetTest.Unit/TweetTest.Unit.fsproj
Normal file
109
lib/TweetTest.Unit/TweetTest.Unit.fsproj
Normal file
@@ -0,0 +1,109 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\packages\xunit.runner.visualstudio.2.3.1\build\net20\xunit.runner.visualstudio.props" Condition="Exists('..\..\packages\xunit.runner.visualstudio.2.3.1\build\net20\xunit.runner.visualstudio.props')" />
|
||||
<Import Project="..\..\packages\Microsoft.NET.Test.Sdk.15.7.2\build\net45\Microsoft.Net.Test.Sdk.props" Condition="Exists('..\..\packages\Microsoft.NET.Test.Sdk.15.7.2\build\net45\Microsoft.Net.Test.Sdk.props')" />
|
||||
<Import Project="..\..\packages\xunit.core.2.3.1\build\xunit.core.props" Condition="Exists('..\..\packages\xunit.core.2.3.1\build\xunit.core.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>eee1071a-28fa-48b1-82a1-9cbdc5c3f2c3</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>TweetTest.Unit</RootNamespace>
|
||||
<AssemblyName>TweetTest.Unit</AssemblyName>
|
||||
<UseStandardResourceNames>True</UseStandardResourceNames>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFSharpCoreVersion>4.4.3.0</TargetFSharpCoreVersion>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Name>TweetTest.Unit</Name>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<Tailcalls>false</Tailcalls>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<WarningLevel>3</WarningLevel>
|
||||
<DocumentationFile>
|
||||
</DocumentationFile>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<Choose>
|
||||
<When Condition="'$(VisualStudioVersion)' == '11.0'">
|
||||
<PropertyGroup Condition=" '$(FSharpTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets') ">
|
||||
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
|
||||
</PropertyGroup>
|
||||
</When>
|
||||
<Otherwise>
|
||||
<PropertyGroup Condition=" '$(FSharpTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets') ">
|
||||
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
|
||||
</PropertyGroup>
|
||||
</Otherwise>
|
||||
</Choose>
|
||||
<Import Project="$(FSharpTargetsPath)" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\packages\xunit.core.2.3.1\build\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.core.2.3.1\build\xunit.core.props'))" />
|
||||
<Error Condition="!Exists('..\..\packages\xunit.core.2.3.1\build\xunit.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.core.2.3.1\build\xunit.core.targets'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.NET.Test.Sdk.15.7.2\build\net45\Microsoft.Net.Test.Sdk.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.NET.Test.Sdk.15.7.2\build\net45\Microsoft.Net.Test.Sdk.props'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.NET.Test.Sdk.15.7.2\build\net45\Microsoft.Net.Test.Sdk.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.NET.Test.Sdk.15.7.2\build\net45\Microsoft.Net.Test.Sdk.targets'))" />
|
||||
<Error Condition="!Exists('..\..\packages\xunit.runner.visualstudio.2.3.1\build\net20\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.runner.visualstudio.2.3.1\build\net20\xunit.runner.visualstudio.props'))" />
|
||||
</Target>
|
||||
<Import Project="..\..\packages\xunit.core.2.3.1\build\xunit.core.targets" Condition="Exists('..\..\packages\xunit.core.2.3.1\build\xunit.core.targets')" />
|
||||
<Import Project="..\..\packages\Microsoft.NET.Test.Sdk.15.7.2\build\net45\Microsoft.Net.Test.Sdk.targets" Condition="Exists('..\..\packages\Microsoft.NET.Test.Sdk.15.7.2\build\net45\Microsoft.Net.Test.Sdk.targets')" />
|
||||
<ItemGroup>
|
||||
<Content Include="packages.config" />
|
||||
<Compile Include="Core\TestBrowserUtils.fs" />
|
||||
<Compile Include="Core\TestStringUtils.fs" />
|
||||
<Compile Include="Core\TestTwitterUtils.fs" />
|
||||
<Compile Include="Data\TestInjectedHTML.fs" />
|
||||
<Compile Include="Data\TestResult.fs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.VisualStudio.CodeCoverage.Shim">
|
||||
<HintPath>..\..\packages\Microsoft.CodeCoverage.1.0.3\lib\netstandard1.0\Microsoft.VisualStudio.CodeCoverage.Shim.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="mscorlib" />
|
||||
<Reference Include="FSharp.Core">
|
||||
<Name>FSharp.Core</Name>
|
||||
<AssemblyName>FSharp.Core.dll</AssemblyName>
|
||||
<HintPath>$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\FSharp\.NETFramework\v4.0\$(TargetFSharpCoreVersion)\FSharp.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="xunit.abstractions">
|
||||
<HintPath>..\..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.assert">
|
||||
<HintPath>..\..\packages\xunit.assert.2.3.1\lib\netstandard1.1\xunit.assert.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.core">
|
||||
<HintPath>..\..\packages\xunit.extensibility.core.2.3.1\lib\netstandard1.1\xunit.core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.execution.desktop">
|
||||
<HintPath>..\..\packages\xunit.extensibility.execution.2.3.1\lib\net452\xunit.execution.desktop.dll</HintPath>
|
||||
</Reference>
|
||||
<ProjectReference Include="..\..\TweetDuck.csproj">
|
||||
<Name>TweetDuck</Name>
|
||||
<Project>{2389a7cd-e0d3-4706-8294-092929a33a2d}</Project>
|
||||
<Private>True</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
13
lib/TweetTest.Unit/packages.config
Normal file
13
lib/TweetTest.Unit/packages.config
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.CodeCoverage" version="1.0.3" targetFramework="net452" />
|
||||
<package id="Microsoft.NET.Test.Sdk" version="15.7.2" targetFramework="net452" />
|
||||
<package id="xunit" version="2.3.1" targetFramework="net452" />
|
||||
<package id="xunit.abstractions" version="2.0.1" targetFramework="net452" />
|
||||
<package id="xunit.analyzers" version="0.9.0" targetFramework="net452" />
|
||||
<package id="xunit.assert" version="2.3.1" targetFramework="net452" />
|
||||
<package id="xunit.core" version="2.3.1" targetFramework="net452" />
|
||||
<package id="xunit.extensibility.core" version="2.3.1" targetFramework="net452" />
|
||||
<package id="xunit.extensibility.execution" version="2.3.1" targetFramework="net452" />
|
||||
<package id="xunit.runner.visualstudio" version="2.3.1" targetFramework="net452" developmentDependency="true" />
|
||||
</packages>
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="cef.redist.x64" version="3.3282.1731" targetFramework="net452" xmlns="" />
|
||||
<package id="cef.redist.x86" version="3.3282.1731" targetFramework="net452" xmlns="" />
|
||||
<package id="CefSharp.Common" version="64.0.0-CI2508" targetFramework="net452" xmlns="" />
|
||||
<package id="CefSharp.WinForms" version="64.0.0-CI2508" targetFramework="net452" xmlns="" />
|
||||
<package id="cef.redist.x64" version="3.3359.1772" targetFramework="net452" />
|
||||
<package id="cef.redist.x86" version="3.3359.1772" targetFramework="net452" />
|
||||
<package id="CefSharp.Common" version="66.0.0-CI2629" targetFramework="net452" />
|
||||
<package id="CefSharp.WinForms" version="66.0.0-CI2629" targetFramework="net452" />
|
||||
</packages>
|
@@ -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>
|
||||
@@ -26,9 +26,9 @@
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="CefSharp.BrowserSubprocess.Core, Version=64.0.0.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=x86">
|
||||
<Reference Include="CefSharp.BrowserSubprocess.Core, Version=66.0.0.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=x86">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\CefSharp.Common.64.0.0-CI2508\CefSharp\x86\CefSharp.BrowserSubprocess.Core.dll</HintPath>
|
||||
<HintPath>..\packages\CefSharp.Common.66.0.0-CI2629\CefSharp\x86\CefSharp.BrowserSubprocess.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
|
@@ -1,53 +0,0 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace UnitTests.Core{
|
||||
[TestClass]
|
||||
public class TestBrowserUtils{
|
||||
[TestMethod]
|
||||
public void TestIsValidUrl(){
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com")); // base
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://www.google.com")); // www.
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.co.uk")); // co.uk
|
||||
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("https://google.com")); // https
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("ftp://google.com")); // ftp
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("mailto:someone@google.com")); // mailto
|
||||
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/")); // trailing slash
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/?")); // trailing question mark
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/?a=5&b=x")); // parameters
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/#hash")); // parameters + hash
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/?a=5&b=x#hash")); // parameters + hash
|
||||
|
||||
foreach(string tld in new string[]{ "accountants", "blackfriday", "cancerresearch", "coffee", "cool", "foo", "travelersinsurance" }){
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://test."+tld)); // long and unusual TLDs
|
||||
}
|
||||
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Tracking, BrowserUtils.CheckUrl("http://t.co/abc")); // tracking
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Tracking, BrowserUtils.CheckUrl("https://t.co/abc")); // tracking
|
||||
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("explorer")); // file
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("explorer.exe")); // file
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("://explorer.exe")); // file-sorta
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("file://explorer.exe")); // file-proper
|
||||
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("")); // empty
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("lol")); // random
|
||||
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("gopher://nobody.cares")); // lmao rekt
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestGetFileNameFromUrl(){
|
||||
Assert.AreEqual("index.html", BrowserUtils.GetFileNameFromUrl("http://test.com/index.html"));
|
||||
Assert.AreEqual("index.html", BrowserUtils.GetFileNameFromUrl("http://test.com/index.html?"));
|
||||
Assert.AreEqual("index.html", BrowserUtils.GetFileNameFromUrl("http://test.com/index.html?param1=abc¶m2=false"));
|
||||
|
||||
Assert.AreEqual("index", BrowserUtils.GetFileNameFromUrl("http://test.com/index"));
|
||||
Assert.AreEqual("index.", BrowserUtils.GetFileNameFromUrl("http://test.com/index."));
|
||||
|
||||
Assert.IsNull(BrowserUtils.GetFileNameFromUrl("http://test.com/"));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace UnitTests.Core{
|
||||
[TestClass]
|
||||
public class TestStringUtils{
|
||||
[TestMethod]
|
||||
public void TestExtractBefore(){
|
||||
Assert.AreEqual("missing", StringUtils.ExtractBefore("missing", '_'));
|
||||
Assert.AreEqual("", StringUtils.ExtractBefore("_empty", '_'));
|
||||
Assert.AreEqual("some", StringUtils.ExtractBefore("some_text", '_'));
|
||||
Assert.AreEqual("first", StringUtils.ExtractBefore("first_separator_only", '_'));
|
||||
Assert.AreEqual("start_index", StringUtils.ExtractBefore("start_index_test", '_', 8));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestParseInts(){
|
||||
CollectionAssert.AreEqual(new int[0], StringUtils.ParseInts("", ','));
|
||||
CollectionAssert.AreEqual(new int[]{ 1 }, StringUtils.ParseInts("1", ','));
|
||||
CollectionAssert.AreEqual(new int[]{ 1, 2, 3 }, StringUtils.ParseInts("1,2,3", ','));
|
||||
CollectionAssert.AreEqual(new int[]{ 1, 2, 3 }, StringUtils.ParseInts("1,2,3,", ','));
|
||||
CollectionAssert.AreEqual(new int[]{ 1, 2, 3 }, StringUtils.ParseInts(",1,2,,3,", ','));
|
||||
CollectionAssert.AreEqual(new int[]{ -50, 50 }, StringUtils.ParseInts("-50,50", ','));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestConvertPascalCaseToScreamingSnakeCase(){
|
||||
Assert.AreEqual("HELP", StringUtils.ConvertPascalCaseToScreamingSnakeCase("Help"));
|
||||
Assert.AreEqual("HELP_ME", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMe"));
|
||||
Assert.AreEqual("HELP_ME_PLEASE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMePlease"));
|
||||
|
||||
Assert.AreEqual("HTML_CODE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HTMLCode"));
|
||||
Assert.AreEqual("CHECK_OUT_MY_HTML_CODE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("CheckOutMyHTMLCode"));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,57 +0,0 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace UnitTests.Core{
|
||||
[TestClass]
|
||||
public class TestTwitterUtils{
|
||||
[TestMethod]
|
||||
public void TestAccountRegex(){
|
||||
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc"));
|
||||
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/chylexmc"));
|
||||
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc/"));
|
||||
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/chylexmc/"));
|
||||
|
||||
Assert.AreEqual("chylexmc", TwitterUtils.RegexAccount.Match("http://twitter.com/chylexmc").Groups[1].Value);
|
||||
Assert.AreEqual("123", TwitterUtils.RegexAccount.Match("http://twitter.com/123").Groups[1].Value);
|
||||
Assert.AreEqual("_", TwitterUtils.RegexAccount.Match("http://twitter.com/_").Groups[1].Value);
|
||||
|
||||
Assert.AreEqual("Abc_123", TwitterUtils.RegexAccount.Match("http://twitter.com/Abc_123").Groups[1].Value);
|
||||
Assert.AreEqual("Abc_123", TwitterUtils.RegexAccount.Match("https://twitter.com/Abc_123/").Groups[1].Value);
|
||||
|
||||
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]
|
||||
public void TestImageQualityLink(){
|
||||
Assert.AreEqual("https://pbs.twimg.com/profile_images/123", TwitterUtils.GetMediaLink("https://pbs.twimg.com/profile_images/123", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/profile_images/123", TwitterUtils.GetMediaLink("https://pbs.twimg.com/profile_images/123", TwitterUtils.ImageQuality.Orig));
|
||||
|
||||
Assert.AreEqual("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.GetMediaLink("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.GetMediaLink("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.ImageQuality.Orig));
|
||||
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg", TwitterUtils.ImageQuality.Orig));
|
||||
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.ImageQuality.Orig));
|
||||
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.ImageQuality.Orig));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TweetDuck.Data;
|
||||
|
||||
namespace UnitTests.Data{
|
||||
[TestClass]
|
||||
public class TestInjectedHTML{
|
||||
private static IEnumerable<InjectedHTML.Position> Positions => Enum.GetValues(typeof(InjectedHTML.Position)).Cast<InjectedHTML.Position>();
|
||||
|
||||
[TestMethod]
|
||||
public void TestFailedMatches(){
|
||||
foreach(var pos in Positions){
|
||||
Assert.AreEqual(string.Empty, new InjectedHTML(pos, "b", "b").Inject(string.Empty));
|
||||
Assert.AreEqual("aaaa", new InjectedHTML(pos, "b", "b").Inject("aaaa"));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestEmptySearch(){
|
||||
foreach(var pos in Positions){
|
||||
Assert.AreEqual("b", new InjectedHTML(pos, string.Empty, "b").Inject(string.Empty));
|
||||
Assert.AreEqual("baaaa", new InjectedHTML(pos, string.Empty, "b").Inject("aaaa"));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestEmptyHTML(){
|
||||
foreach(var pos in Positions){
|
||||
Assert.AreEqual(string.Empty, new InjectedHTML(pos, string.Empty, string.Empty).Inject(string.Empty));
|
||||
Assert.AreEqual("aaaa", new InjectedHTML(pos, string.Empty, string.Empty).Inject("aaaa"));
|
||||
Assert.AreEqual("aaaa", new InjectedHTML(pos, "a", string.Empty).Inject("aaaa"));
|
||||
Assert.AreEqual("aaaa", new InjectedHTML(pos, "b", string.Empty).Inject("aaaa"));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestInvalidPosition(){
|
||||
Assert.AreEqual("aaaa", new InjectedHTML((InjectedHTML.Position)(Positions.Count()+1), "a", "b").Inject("aaaa"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestPositions(){
|
||||
Assert.AreEqual("aaabcxaaa", new InjectedHTML(InjectedHTML.Position.Before, "x", "bc").Inject("aaaxaaa"));
|
||||
Assert.AreEqual("aaaxbcaaa", new InjectedHTML(InjectedHTML.Position.After, "x", "bc").Inject("aaaxaaa"));
|
||||
|
||||
Assert.AreEqual("bcxaaa", new InjectedHTML(InjectedHTML.Position.Before, "x", "bc").Inject("xaaa"));
|
||||
Assert.AreEqual("xbcaaa", new InjectedHTML(InjectedHTML.Position.After, "x", "bc").Inject("xaaa"));
|
||||
|
||||
Assert.AreEqual("aaabcx", new InjectedHTML(InjectedHTML.Position.Before, "x", "bc").Inject("aaax"));
|
||||
Assert.AreEqual("aaaxbc", new InjectedHTML(InjectedHTML.Position.After, "x", "bc").Inject("aaax"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestFirstOccurrence(){
|
||||
Assert.AreEqual("bcaaaa", new InjectedHTML(InjectedHTML.Position.Before, "a", "bc").Inject("aaaa"));
|
||||
Assert.AreEqual("abcaaa", new InjectedHTML(InjectedHTML.Position.After, "a", "bc").Inject("aaaa"));
|
||||
|
||||
Assert.AreEqual("bcaaaa", new InjectedHTML(InjectedHTML.Position.Before, "aa", "bc").Inject("aaaa"));
|
||||
Assert.AreEqual("aabcaa", new InjectedHTML(InjectedHTML.Position.After, "aa", "bc").Inject("aaaa"));
|
||||
}
|
||||
}
|
||||
}
|
@@ -24,11 +24,6 @@
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<LangVersion>7</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core">
|
||||
@@ -49,13 +44,9 @@
|
||||
</Choose>
|
||||
<ItemGroup>
|
||||
<Compile Include="Configuration\TestUserConfig.cs" />
|
||||
<Compile Include="Core\TestStringUtils.cs" />
|
||||
<Compile Include="Core\TestTwitterUtils.cs" />
|
||||
<Compile Include="Data\TestCombinedFileStream.cs" />
|
||||
<Compile Include="Core\TestBrowserUtils.cs" />
|
||||
<Compile Include="Data\TestCommandLineArgs.cs" />
|
||||
<Compile Include="Data\TestFileSerializer.cs" />
|
||||
<Compile Include="Data\TestInjectedHTML.cs" />
|
||||
<Compile Include="Data\TestTwoKeyDictionary.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="UnitTestIO.cs" />
|
||||
|
Reference in New Issue
Block a user