Compare commits
72 Commits
Author | SHA1 | Date | |
---|---|---|---|
327ef1cbee | |||
15eb823c7f | |||
54613e5242 | |||
df1352cbe3 | |||
0559afd972 | |||
afffca020e | |||
d663cc3f64 | |||
110d41e393 | |||
1a8823f592 | |||
6374a852b0 | |||
a10c7dd7c3 | |||
547c7ea417 | |||
760607995a | |||
4704197c09 | |||
093ac1ac40 | |||
9ed8b0d904 | |||
7346ce370d | |||
adefdadc19 | |||
703bce2d00 | |||
97928ecd84 | |||
be9ea7f64a | |||
ec2aaa8789 | |||
ab14b72526 | |||
d8e304f3c1 | |||
ea53ce361f | |||
2fce80b347 | |||
373c0b1cc3 | |||
e5e1b7e608 | |||
7e9221c9e0 | |||
6b849f854e | |||
831f6bc744 | |||
d282a7a537 | |||
fb2f1e3031 | |||
00a0da3df3 | |||
8c447b1ffb | |||
a4841175e8 | |||
9b139132a1 | |||
4a404ecabc | |||
aee758b559 | |||
be060d0386 | |||
0195378c10 | |||
bc804c6a53 | |||
76b15f1971 | |||
c4d43c9d5b | |||
e8d3e530de | |||
e145adec58 | |||
e2dad3e477 | |||
27bdbde171 | |||
e9ec27169c | |||
2e24cb634c | |||
beb9046055 | |||
e57301952c | |||
7411279e48 | |||
16acfa85b5 | |||
41ef37f3f0 | |||
00d8538726 | |||
6eeb3f9895 | |||
d19dca6ea5 | |||
2008ccdaa4 | |||
ba2e62de3a | |||
2b62eb254d | |||
31f72b7957 | |||
fdc4616875 | |||
b7de261d25 | |||
ae78a5a026 | |||
fd2cf5d4d7 | |||
9f0997be1a | |||
dbade7f854 | |||
3cdc1e190a | |||
36bede7211 | |||
46689bb700 | |||
13e1a6543c |
5
.gitattributes
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
*.txt text eof=lf
|
||||
*.cs diff=csharp
|
@@ -44,6 +44,9 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
// CONFIGURATION DATA
|
||||
|
||||
public bool FirstRun { get; set; } = true;
|
||||
public bool AllowDataCollection { get; set; } = false;
|
||||
|
||||
public WindowState BrowserWindow { get; set; } = new WindowState();
|
||||
public WindowState PluginsWindow { get; set; } = new WindowState();
|
||||
|
||||
|
@@ -11,6 +11,9 @@ using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Core.Bridge{
|
||||
sealed class TweetDeckBridge{
|
||||
public static string FontSize { get; private set; }
|
||||
public static string NotificationHeadLayout { get; private set; }
|
||||
|
||||
public static string LastHighlightedTweetUrl = string.Empty;
|
||||
public static string LastHighlightedQuoteUrl = string.Empty;
|
||||
private static string LastHighlightedTweetAuthors = string.Empty;
|
||||
@@ -22,6 +25,7 @@ namespace TweetDuck.Core.Bridge{
|
||||
private static readonly Dictionary<string, string> SessionData = new Dictionary<string, string>(2);
|
||||
|
||||
public static void ResetStaticProperties(){
|
||||
FontSize = NotificationHeadLayout = null;
|
||||
LastHighlightedTweetUrl = LastHighlightedQuoteUrl = LastHighlightedTweetAuthors = LastHighlightedTweetImages = string.Empty;
|
||||
}
|
||||
|
||||
@@ -46,15 +50,16 @@ namespace TweetDuck.Core.Bridge{
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
public void LoadFontSizeClass(string fsClass){
|
||||
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
|
||||
form.InvokeAsyncSafe(() => {
|
||||
TweetNotification.SetFontSizeClass(fsClass);
|
||||
form.OnIntroductionClosed(showGuide, allowDataCollection);
|
||||
});
|
||||
}
|
||||
|
||||
public void LoadNotificationHeadContents(string headContents){
|
||||
public void LoadNotificationLayout(string fontSize, string headLayout){
|
||||
form.InvokeAsyncSafe(() => {
|
||||
TweetNotification.SetHeadTag(headContents);
|
||||
FontSize = fontSize;
|
||||
NotificationHeadLayout = headLayout;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -112,8 +117,8 @@ namespace TweetDuck.Core.Bridge{
|
||||
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
|
||||
}
|
||||
|
||||
public void PlayVideo(string url){
|
||||
form.InvokeAsyncSafe(() => form.PlayVideo(url));
|
||||
public void PlayVideo(string url, string username){
|
||||
form.InvokeAsyncSafe(() => form.PlayVideo(url, username));
|
||||
}
|
||||
|
||||
public void FixClipboard(){
|
||||
|
@@ -202,6 +202,7 @@ namespace TweetDuck.Core{
|
||||
UpdateProperties(PropertyBridge.Environment.Browser);
|
||||
TweetDeckBridge.RestoreSessionData(e.Frame);
|
||||
ScriptLoader.ExecuteFile(e.Frame, "code.js");
|
||||
InjectBrowserCSS();
|
||||
ReinjectCustomCSS(Config.CustomBrowserCSS);
|
||||
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser);
|
||||
|
||||
@@ -210,6 +211,10 @@ namespace TweetDuck.Core{
|
||||
if (Program.SystemConfig.EnableBrowserGCReload){
|
||||
memoryUsageTracker.Start(this, e.Browser, Program.SystemConfig.BrowserMemoryThreshold);
|
||||
}
|
||||
|
||||
if (Config.FirstRun){
|
||||
ScriptLoader.ExecuteFile(e.Frame, "introduction.js");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,6 +430,10 @@ namespace TweetDuck.Core{
|
||||
|
||||
// javascript calls
|
||||
|
||||
public void InjectBrowserCSS(){
|
||||
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css").TrimEnd());
|
||||
}
|
||||
|
||||
public void ReinjectCustomCSS(string css){
|
||||
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
|
||||
}
|
||||
@@ -439,6 +448,18 @@ namespace TweetDuck.Core{
|
||||
|
||||
// callback handlers
|
||||
|
||||
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
|
||||
if (Config.FirstRun){
|
||||
Config.FirstRun = false;
|
||||
Config.AllowDataCollection = allowDataCollection;
|
||||
Config.Save();
|
||||
}
|
||||
|
||||
if (showGuide){
|
||||
ShowChildForm(new FormGuide());
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenContextMenu(){
|
||||
contextMenu.Show(this, PointToClient(Cursor.Position));
|
||||
}
|
||||
@@ -520,7 +541,7 @@ namespace TweetDuck.Core{
|
||||
soundNotification.Play(Config.NotificationSoundPath);
|
||||
}
|
||||
|
||||
public void PlayVideo(string url){
|
||||
public void PlayVideo(string url, string username){
|
||||
if (string.IsNullOrEmpty(url)){
|
||||
videoPlayer?.Close();
|
||||
return;
|
||||
@@ -535,7 +556,7 @@ namespace TweetDuck.Core{
|
||||
};
|
||||
}
|
||||
|
||||
videoPlayer.Launch(url);
|
||||
videoPlayer.Launch(url, username);
|
||||
}
|
||||
|
||||
public void HideVideoOverlay(){
|
||||
|
@@ -20,7 +20,7 @@ namespace TweetDuck.Core{
|
||||
|
||||
public static void CloseAllDialogs(){
|
||||
foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){
|
||||
if (form is FormSettings || form is FormPlugins || form is FormAbout){
|
||||
if (form is FormSettings || form is FormPlugins || form is FormAbout || form is FormGuide){
|
||||
form.Close();
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,9 @@ using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using TweetDuck.Core.Other;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
abstract class ContextMenuBase : IContextMenuHandler{
|
||||
@@ -31,10 +33,11 @@ namespace TweetDuck.Core.Handling{
|
||||
private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand)26500;
|
||||
private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand)26501;
|
||||
private const CefMenuCommand MenuCopyUsername = (CefMenuCommand)26502;
|
||||
private const CefMenuCommand MenuOpenMediaUrl = (CefMenuCommand)26503;
|
||||
private const CefMenuCommand MenuCopyMediaUrl = (CefMenuCommand)26504;
|
||||
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26505;
|
||||
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26506;
|
||||
private const CefMenuCommand MenuViewImage = (CefMenuCommand)26503;
|
||||
private const CefMenuCommand MenuOpenMediaUrl = (CefMenuCommand)26504;
|
||||
private const CefMenuCommand MenuCopyMediaUrl = (CefMenuCommand)26505;
|
||||
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26506;
|
||||
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26507;
|
||||
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
|
||||
|
||||
private string[] lastHighlightedTweetAuthors;
|
||||
@@ -79,6 +82,7 @@ namespace TweetDuck.Core.Handling{
|
||||
model.AddSeparator();
|
||||
}
|
||||
else if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){
|
||||
model.AddItem(MenuViewImage, "View image in photo viewer");
|
||||
model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
|
||||
model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
|
||||
model.AddItem(MenuSaveMedia, TextSave("image"));
|
||||
@@ -114,9 +118,35 @@ namespace TweetDuck.Core.Handling{
|
||||
SetClipboardText(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
|
||||
break;
|
||||
|
||||
case MenuViewImage:
|
||||
string url = GetMediaLink(parameters);
|
||||
string file = Path.Combine(BrowserCache.CacheFolder, TwitterUtils.GetImageFileName(url));
|
||||
|
||||
void ViewFile(){
|
||||
string ext = Path.GetExtension(file);
|
||||
|
||||
if (TwitterUtils.ValidImageExtensions.Contains(ext)){
|
||||
WindowsUtils.OpenAssociatedProgram(file);
|
||||
}
|
||||
else{
|
||||
FormMessage.Error("Image Download", "Invalid file extension "+ext, FormMessage.OK);
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists(file)){
|
||||
ViewFile();
|
||||
}
|
||||
else{
|
||||
BrowserUtils.DownloadFileAsync(TwitterUtils.GetMediaLink(url, ImageQuality), file, ViewFile, ex => {
|
||||
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MenuSaveMedia:
|
||||
if (IsVideo){
|
||||
TwitterUtils.DownloadVideo(GetMediaLink(parameters));
|
||||
TwitterUtils.DownloadVideo(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault());
|
||||
}
|
||||
else{
|
||||
TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
|
||||
|
15
Core/Handling/ContextMenuGuide.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
sealed class ContextMenuGuide : ContextMenuBase{
|
||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||
model.Clear();
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
|
||||
if (HasDevTools){
|
||||
AddSeparator(model);
|
||||
AddDebugMenuItems(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -48,7 +48,7 @@ namespace TweetDuck.Core.Handling{
|
||||
}
|
||||
|
||||
if (HasDevTools){
|
||||
model.AddSeparator();
|
||||
AddSeparator(model);
|
||||
AddDebugMenuItems(model);
|
||||
}
|
||||
|
||||
|
@@ -4,11 +4,18 @@ using CefSharp;
|
||||
namespace TweetDuck.Core.Handling{
|
||||
sealed class DragHandlerBrowser : IDragHandler{
|
||||
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);
|
||||
}
|
||||
|
||||
if (dragData.IsLink){
|
||||
browserControl.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", "link", dragData.LinkUrl);
|
||||
TriggerDragStart("link", dragData.LinkUrl);
|
||||
}
|
||||
else if (dragData.IsFragment){
|
||||
TriggerDragStart("text", dragData.FragmentText.Trim());
|
||||
}
|
||||
else{
|
||||
browserControl.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", "unknown");
|
||||
TriggerDragStart("unknown");
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@@ -4,6 +4,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;
|
||||
using TweetDuck.Core.Handling.General;
|
||||
@@ -12,6 +13,18 @@ using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
partial class FormNotificationBase : Form{
|
||||
protected static int FontSizeLevel{
|
||||
get{
|
||||
switch(TweetDeckBridge.FontSize){
|
||||
case "largest": return 4;
|
||||
case "large": return 3;
|
||||
case "small": return 1;
|
||||
case "smallest": return 0;
|
||||
default: return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Point PrimaryLocation{
|
||||
get{
|
||||
UserConfig config = Program.UserConfig;
|
||||
|
@@ -33,7 +33,7 @@ namespace TweetDuck.Core.Notification{
|
||||
|
||||
public bool RequiresResize{
|
||||
get{
|
||||
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != TweetNotification.FontSizeLevel || CanResizeWindow;
|
||||
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != FontSizeLevel || CanResizeWindow;
|
||||
}
|
||||
|
||||
set{
|
||||
@@ -43,7 +43,7 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
else{
|
||||
prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer;
|
||||
prevFontSize = TweetNotification.FontSizeLevel;
|
||||
prevFontSize = FontSizeLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ namespace TweetDuck.Core.Notification{
|
||||
get{
|
||||
switch(Program.UserConfig.NotificationSize){
|
||||
default:
|
||||
return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*TweetNotification.FontSizeLevel));
|
||||
return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*FontSizeLevel));
|
||||
|
||||
case TweetNotification.Size.Custom:
|
||||
return Program.UserConfig.CustomNotificationSize.Width;
|
||||
@@ -64,7 +64,7 @@ namespace TweetDuck.Core.Notification{
|
||||
get{
|
||||
switch(Program.UserConfig.NotificationSize){
|
||||
default:
|
||||
return BrowserUtils.Scale(122, SizeScale*(1.0+0.075*TweetNotification.FontSizeLevel));
|
||||
return BrowserUtils.Scale(122, SizeScale*(1.0+0.075*FontSizeLevel));
|
||||
|
||||
case TweetNotification.Size.Custom:
|
||||
return Program.UserConfig.CustomNotificationSize.Height;
|
||||
|
@@ -22,7 +22,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
||||
browser.LoadingStateChanged += (sender, args) => {
|
||||
if (!args.IsLoading){
|
||||
using(IFrame frame = args.Browser.MainFrame){
|
||||
ScriptLoader.ExecuteScript(frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 129)", "gen:screenshot");
|
||||
ScriptLoader.ExecuteScript(frame, "window.setTimeout($TD_NotificationScreenshot.trigger, document.getElementsByTagName('iframe').length ? 267 : 67)", "gen:screenshot");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,27 +1,12 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
sealed class TweetNotification{
|
||||
private static string FontSizeClass { get; set; }
|
||||
private static string HeadTag { get; set; }
|
||||
|
||||
private const string DefaultFontSizeClass = "medium";
|
||||
private const string DefaultHeadTag = @"<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 CustomCSS = @"body:before{content:none}body{overflow-y:auto}.scroll-styled-v::-webkit-scrollbar{width:7px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}.scroll-styled-v::-webkit-scrollbar-track{border-left:0}#td-skip{opacity:0;cursor:pointer;transition:opacity 0.15s ease}.td-hover #td-skip{opacity:0.75}#td-skip:hover{opacity:1}.media-size-medium{height:calc(100vh - 16px)!important;max-height:240px;border-radius:1px!important}.js-quote-detail .media-size-medium{height:calc(100vh - 28px)!important;}.js-media.margin-vm, .js-media-preview-container.margin-vm{margin-bottom:0!important}";
|
||||
|
||||
public static int FontSizeLevel{
|
||||
get{
|
||||
switch(FontSizeClass){
|
||||
case "largest": return 4;
|
||||
case "large": return 3;
|
||||
case "medium": return 2;
|
||||
case "small": return 1;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
private const string DefaultHeadLayout = @"<html class='os-windows txt-size--14' data-td-font='medium' data-td-theme='dark'><head><meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>";
|
||||
private static readonly string CustomCSS = ScriptLoader.LoadResource("styles/notification.css");
|
||||
|
||||
private static string ExampleTweetHTML;
|
||||
|
||||
@@ -39,14 +24,6 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetFontSizeClass(string newFSClass){
|
||||
FontSizeClass = newFSClass;
|
||||
}
|
||||
|
||||
public static void SetHeadTag(string headContents){
|
||||
HeadTag = headContents;
|
||||
}
|
||||
|
||||
public enum Position{
|
||||
TopLeft, TopRight, BottomLeft, BottomRight, Custom
|
||||
}
|
||||
@@ -88,8 +65,7 @@ namespace TweetDuck.Core.Notification{
|
||||
public string GenerateHtml(string bodyClasses = null, bool enableCustomCSS = true){
|
||||
StringBuilder build = new StringBuilder();
|
||||
build.Append("<!DOCTYPE html>");
|
||||
build.Append("<html class='os-windows txt-base-").Append(FontSizeClass ?? DefaultFontSizeClass).Append("'>");
|
||||
build.Append("<head>").Append(HeadTag ?? DefaultHeadTag);
|
||||
build.Append(TweetDeckBridge.NotificationHeadLayout ?? DefaultHeadLayout);
|
||||
|
||||
if (enableCustomCSS){
|
||||
build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>");
|
||||
|
3
Core/Other/FormAbout.Designer.cs
generated
@@ -131,11 +131,14 @@ namespace TweetDuck.Core.Other {
|
||||
this.Controls.Add(this.labelDescription);
|
||||
this.Controls.Add(this.pictureLogo);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.HelpButton = true;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "FormAbout";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.HelpButtonClicked += new System.ComponentModel.CancelEventHandler(this.FormAbout_HelpButtonClicked);
|
||||
this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.FormAbout_HelpRequested);
|
||||
((System.ComponentModel.ISupportInitialize)(this.pictureLogo)).EndInit();
|
||||
this.tablePanelLinks.ResumeLayout(false);
|
||||
this.tablePanelLinks.PerformLayout();
|
||||
|
@@ -1,4 +1,6 @@
|
||||
using System.Windows.Forms;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other{
|
||||
@@ -19,7 +21,21 @@ namespace TweetDuck.Core.Other{
|
||||
}
|
||||
|
||||
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe(e.Link.LinkData as string);
|
||||
BrowserUtils.OpenExternalBrowser(e.Link.LinkData as string);
|
||||
}
|
||||
|
||||
private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent){
|
||||
ShowGuide();
|
||||
}
|
||||
|
||||
private void FormAbout_HelpButtonClicked(object sender, CancelEventArgs e){
|
||||
e.Cancel = true;
|
||||
ShowGuide();
|
||||
}
|
||||
|
||||
private void ShowGuide(){
|
||||
new FormGuide().Show();
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
44
Core/Other/FormGuide.Designer.cs
generated
Normal file
@@ -0,0 +1,44 @@
|
||||
namespace TweetDuck.Core.Other {
|
||||
partial class FormGuide {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// FormGuide
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(34)))), ((int)(((byte)(34)))), ((int)(((byte)(34)))));
|
||||
this.ClientSize = new System.Drawing.Size(424, 282);
|
||||
this.Icon = global::TweetDuck.Properties.Resources.icon;
|
||||
this.MinimumSize = new System.Drawing.Size(440, 320);
|
||||
this.Name = "FormGuide";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
71
Core/Other/FormGuide.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Handling.General;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other{
|
||||
sealed partial class FormGuide : Form{
|
||||
private const string GuideUrl = "https://tweetduck.chylex.com/guide/v1/";
|
||||
|
||||
private readonly ChromiumWebBrowser browser;
|
||||
|
||||
public FormGuide(){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName+" Guide";
|
||||
|
||||
FormBrowser owner = FormManager.TryFind<FormBrowser>();
|
||||
|
||||
if (owner != null){
|
||||
Size = new Size(owner.Size.Width*3/4, owner.Size.Height*3/4);
|
||||
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
|
||||
}
|
||||
|
||||
this.browser = new ChromiumWebBrowser(GuideUrl){
|
||||
MenuHandler = new ContextMenuGuide(),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBrowser()
|
||||
};
|
||||
|
||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
browser.FrameLoadStart += browser_FrameLoadStart;
|
||||
|
||||
browser.BrowserSettings.BackgroundColor = (uint)BackColor.ToArgb();
|
||||
browser.Dock = DockStyle.None;
|
||||
browser.Location = ControlExtensions.InvisibleLocation;
|
||||
Controls.Add(browser);
|
||||
|
||||
Disposed += (sender, args) => {
|
||||
Program.UserConfig.ZoomLevelChanged -= Config_ZoomLevelChanged;
|
||||
browser.Dispose();
|
||||
};
|
||||
|
||||
Program.UserConfig.ZoomLevelChanged += Config_ZoomLevelChanged;
|
||||
}
|
||||
|
||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||
if (!e.IsLoading){
|
||||
this.InvokeAsyncSafe(() => {
|
||||
browser.Location = Point.Empty;
|
||||
browser.Dock = DockStyle.Fill;
|
||||
});
|
||||
|
||||
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
|
||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
|
||||
}
|
||||
|
||||
private void Config_ZoomLevelChanged(object sender, EventArgs e){
|
||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
|
||||
}
|
||||
}
|
||||
}
|
@@ -37,6 +37,7 @@ namespace TweetDuck.Core.Other{
|
||||
AddButton("System Tray", () => new TabSettingsTray());
|
||||
AddButton("Notifications", () => new TabSettingsNotifications(browser.CreateNotificationForm(false)));
|
||||
AddButton("Sounds", () => new TabSettingsSounds());
|
||||
AddButton("Feedback", () => new TabSettingsFeedback());
|
||||
AddButton("Advanced", () => new TabSettingsAdvanced(browser.ReinjectCustomCSS));
|
||||
|
||||
SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]);
|
||||
|
@@ -25,6 +25,7 @@ namespace TweetDuck.Core.Other.Management{
|
||||
|
||||
private readonly Form owner;
|
||||
private string lastUrl;
|
||||
private string lastUsername;
|
||||
|
||||
private Process currentProcess;
|
||||
private DuplexPipe.Server currentPipe;
|
||||
@@ -35,13 +36,14 @@ namespace TweetDuck.Core.Other.Management{
|
||||
this.owner.FormClosing += owner_FormClosing;
|
||||
}
|
||||
|
||||
public void Launch(string url){
|
||||
public void Launch(string url, string username){
|
||||
if (Running){
|
||||
Destroy();
|
||||
isClosing = false;
|
||||
}
|
||||
|
||||
lastUrl = url;
|
||||
lastUsername = username;
|
||||
|
||||
try{
|
||||
currentPipe = DuplexPipe.CreateServer();
|
||||
@@ -84,7 +86,7 @@ namespace TweetDuck.Core.Other.Management{
|
||||
break;
|
||||
|
||||
case "download":
|
||||
TwitterUtils.DownloadVideo(lastUrl);
|
||||
TwitterUtils.DownloadVideo(lastUrl, lastUsername);
|
||||
break;
|
||||
|
||||
case "rip":
|
||||
@@ -147,21 +149,7 @@ namespace TweetDuck.Core.Other.Management{
|
||||
}
|
||||
|
||||
private void process_Exited(object sender, EventArgs e){
|
||||
switch(currentProcess.ExitCode){
|
||||
case 3: // CODE_LAUNCH_FAIL
|
||||
if (FormMessage.Error("Video Playback Error", "Error launching video player, this may be caused by missing Windows Media Player. Do you want to open the video in a browser?", FormMessage.Yes, FormMessage.No)){
|
||||
BrowserUtils.OpenExternalBrowser(lastUrl);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 4: // CODE_MEDIA_ERROR
|
||||
if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in a browser?", FormMessage.Yes, FormMessage.No)){
|
||||
BrowserUtils.OpenExternalBrowser(lastUrl);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
int exitCode = currentProcess.ExitCode;
|
||||
|
||||
currentProcess.Dispose();
|
||||
currentProcess = null;
|
||||
@@ -169,6 +157,22 @@ namespace TweetDuck.Core.Other.Management{
|
||||
currentPipe.Dispose();
|
||||
currentPipe = null;
|
||||
|
||||
switch(exitCode){
|
||||
case 3: // CODE_LAUNCH_FAIL
|
||||
if (FormMessage.Error("Video Playback Error", "Error launching video player, this may be caused by missing Windows Media Player. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
|
||||
BrowserUtils.OpenExternalBrowser(lastUrl);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 4: // CODE_MEDIA_ERROR
|
||||
if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
|
||||
BrowserUtils.OpenExternalBrowser(lastUrl);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
|
||||
}
|
||||
|
||||
|
@@ -35,7 +35,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
}
|
||||
|
||||
private void btnOpenWiki_Click(object sender, EventArgs e){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe("https://github.com/chylex/TweetDuck/wiki");
|
||||
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/wiki");
|
||||
}
|
||||
|
||||
private void btnApply_Click(object sender, EventArgs e){
|
||||
|
@@ -19,7 +19,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
}
|
||||
|
||||
private void btnHelp_Click(object sender, EventArgs e){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe("http://peter.sh/experiments/chromium-command-line-switches/");
|
||||
BrowserUtils.OpenExternalBrowser("http://peter.sh/experiments/chromium-command-line-switches/");
|
||||
}
|
||||
|
||||
private void btnApply_Click(object sender, EventArgs e){
|
||||
|
134
Core/Other/Settings/TabSettingsFeedback.Designer.cs
generated
Normal file
@@ -0,0 +1,134 @@
|
||||
namespace TweetDuck.Core.Other.Settings {
|
||||
partial class TabSettingsFeedback {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Component Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.panelFeedback = new System.Windows.Forms.Panel();
|
||||
this.labelDataCollectionLink = new System.Windows.Forms.LinkLabel();
|
||||
this.checkDataCollection = new System.Windows.Forms.CheckBox();
|
||||
this.labelDataCollection = new System.Windows.Forms.Label();
|
||||
this.labelFeedback = new System.Windows.Forms.Label();
|
||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.btnSendFeedback = new System.Windows.Forms.Button();
|
||||
this.panelFeedback.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// panelFeedback
|
||||
//
|
||||
this.panelFeedback.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.panelFeedback.Controls.Add(this.btnSendFeedback);
|
||||
this.panelFeedback.Controls.Add(this.labelDataCollectionLink);
|
||||
this.panelFeedback.Controls.Add(this.checkDataCollection);
|
||||
this.panelFeedback.Controls.Add(this.labelDataCollection);
|
||||
this.panelFeedback.Location = new System.Drawing.Point(9, 31);
|
||||
this.panelFeedback.Name = "panelFeedback";
|
||||
this.panelFeedback.Size = new System.Drawing.Size(322, 80);
|
||||
this.panelFeedback.TabIndex = 1;
|
||||
//
|
||||
// labelDataCollectionLink
|
||||
//
|
||||
this.labelDataCollectionLink.AutoSize = true;
|
||||
this.labelDataCollectionLink.LinkArea = new System.Windows.Forms.LinkArea(1, 10);
|
||||
this.labelDataCollectionLink.LinkBehavior = System.Windows.Forms.LinkBehavior.HoverUnderline;
|
||||
this.labelDataCollectionLink.Location = new System.Drawing.Point(141, 60);
|
||||
this.labelDataCollectionLink.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
|
||||
this.labelDataCollectionLink.Name = "labelDataCollectionLink";
|
||||
this.labelDataCollectionLink.Size = new System.Drawing.Size(66, 17);
|
||||
this.labelDataCollectionLink.TabIndex = 3;
|
||||
this.labelDataCollectionLink.TabStop = true;
|
||||
this.labelDataCollectionLink.Text = "(learn more)";
|
||||
this.labelDataCollectionLink.UseCompatibleTextRendering = true;
|
||||
this.labelDataCollectionLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.labelDataCollectionLink_LinkClicked);
|
||||
//
|
||||
// checkDataCollection
|
||||
//
|
||||
this.checkDataCollection.AutoSize = true;
|
||||
this.checkDataCollection.Location = new System.Drawing.Point(6, 59);
|
||||
this.checkDataCollection.Margin = new System.Windows.Forms.Padding(6, 5, 0, 3);
|
||||
this.checkDataCollection.Name = "checkDataCollection";
|
||||
this.checkDataCollection.Size = new System.Drawing.Size(135, 17);
|
||||
this.checkDataCollection.TabIndex = 2;
|
||||
this.checkDataCollection.Text = "Send Anonymous Data";
|
||||
this.checkDataCollection.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// labelDataCollection
|
||||
//
|
||||
this.labelDataCollection.AutoSize = true;
|
||||
this.labelDataCollection.Location = new System.Drawing.Point(3, 41);
|
||||
this.labelDataCollection.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||
this.labelDataCollection.Name = "labelDataCollection";
|
||||
this.labelDataCollection.Size = new System.Drawing.Size(79, 13);
|
||||
this.labelDataCollection.TabIndex = 1;
|
||||
this.labelDataCollection.Text = "Data Collection";
|
||||
//
|
||||
// labelFeedback
|
||||
//
|
||||
this.labelFeedback.AutoSize = true;
|
||||
this.labelFeedback.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.labelFeedback.Location = new System.Drawing.Point(6, 8);
|
||||
this.labelFeedback.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0);
|
||||
this.labelFeedback.Name = "labelFeedback";
|
||||
this.labelFeedback.Size = new System.Drawing.Size(80, 20);
|
||||
this.labelFeedback.TabIndex = 0;
|
||||
this.labelFeedback.Text = "Feedback";
|
||||
//
|
||||
// btnSendFeedback
|
||||
//
|
||||
this.btnSendFeedback.AutoSize = true;
|
||||
this.btnSendFeedback.Location = new System.Drawing.Point(5, 3);
|
||||
this.btnSendFeedback.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||
this.btnSendFeedback.Name = "btnSendFeedback";
|
||||
this.btnSendFeedback.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
this.btnSendFeedback.Size = new System.Drawing.Size(164, 23);
|
||||
this.btnSendFeedback.TabIndex = 0;
|
||||
this.btnSendFeedback.Text = "Send Feedback / Bug Report";
|
||||
this.btnSendFeedback.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// TabSettingsFeedback
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.Controls.Add(this.panelFeedback);
|
||||
this.Controls.Add(this.labelFeedback);
|
||||
this.Name = "TabSettingsFeedback";
|
||||
this.Size = new System.Drawing.Size(340, 122);
|
||||
this.panelFeedback.ResumeLayout(false);
|
||||
this.panelFeedback.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Panel panelFeedback;
|
||||
private System.Windows.Forms.CheckBox checkDataCollection;
|
||||
private System.Windows.Forms.Label labelDataCollection;
|
||||
private System.Windows.Forms.Label labelFeedback;
|
||||
private System.Windows.Forms.ToolTip toolTip;
|
||||
private System.Windows.Forms.LinkLabel labelDataCollectionLink;
|
||||
private System.Windows.Forms.Button btnSendFeedback;
|
||||
}
|
||||
}
|
30
Core/Other/Settings/TabSettingsFeedback.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings{
|
||||
sealed partial class TabSettingsFeedback : BaseTabSettings{
|
||||
public TabSettingsFeedback(){
|
||||
InitializeComponent();
|
||||
|
||||
checkDataCollection.Checked = Config.AllowDataCollection;
|
||||
}
|
||||
|
||||
public override void OnReady(){
|
||||
btnSendFeedback.Click += btnSendFeedback_Click;
|
||||
checkDataCollection.CheckedChanged += checkDataCollection_CheckedChanged;
|
||||
}
|
||||
|
||||
private void btnSendFeedback_Click(object sender, EventArgs e){
|
||||
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/issues/new");
|
||||
}
|
||||
|
||||
private void checkDataCollection_CheckedChanged(object sender, EventArgs e){
|
||||
Config.AllowDataCollection = checkDataCollection.Checked;
|
||||
}
|
||||
|
||||
private void labelDataCollectionLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
|
||||
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/wiki/Send-anonymous-data");
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,7 +8,7 @@ namespace TweetDuck.Core.Utils{
|
||||
static class BrowserCache{
|
||||
private static bool ClearOnExit { get; set; }
|
||||
|
||||
private static readonly string CacheFolder = Path.Combine(Program.StoragePath, "Cache");
|
||||
public static readonly string CacheFolder = Path.Combine(Program.StoragePath, "Cache");
|
||||
|
||||
private static IEnumerable<string> CacheFiles{
|
||||
get{
|
||||
|
@@ -70,12 +70,12 @@ namespace TweetDuck.Core.Utils{
|
||||
|
||||
switch(CheckUrl(url)){
|
||||
case UrlCheckResult.Fine:
|
||||
OpenExternalBrowserUnsafe(url);
|
||||
WindowsUtils.OpenAssociatedProgram(url);
|
||||
break;
|
||||
|
||||
case UrlCheckResult.Tracking:
|
||||
if (FormMessage.Warning("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n"+url, FormMessage.Yes, FormMessage.No)){
|
||||
OpenExternalBrowserUnsafe(url);
|
||||
WindowsUtils.OpenAssociatedProgram(url);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -86,10 +86,6 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
}
|
||||
|
||||
public static void OpenExternalBrowserUnsafe(string url){
|
||||
using(Process.Start(url)){}
|
||||
}
|
||||
|
||||
public static string GetFileNameFromUrl(string url){
|
||||
string file = Path.GetFileName(new Uri(url).AbsolutePath);
|
||||
return string.IsNullOrEmpty(file) ? null : file;
|
||||
|
@@ -21,6 +21,10 @@ namespace TweetDuck.Core.Utils{
|
||||
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
|
||||
};
|
||||
|
||||
public static readonly string[] ValidImageExtensions = {
|
||||
".jpg", ".jpeg", ".png", ".gif"
|
||||
};
|
||||
|
||||
public enum ImageQuality{
|
||||
Default, Orig
|
||||
}
|
||||
@@ -53,6 +57,10 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetImageFileName(string url){
|
||||
return BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(url));
|
||||
}
|
||||
|
||||
public static void DownloadImage(string url, string username, ImageQuality quality){
|
||||
DownloadImages(new string[]{ url }, username, quality);
|
||||
}
|
||||
@@ -65,7 +73,7 @@ namespace TweetDuck.Core.Utils{
|
||||
string firstImageLink = GetMediaLink(urls[0], quality);
|
||||
int qualityIndex = firstImageLink.IndexOf(':', firstImageLink.LastIndexOf('/'));
|
||||
|
||||
string file = BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(firstImageLink));
|
||||
string file = GetImageFileName(firstImageLink);
|
||||
string ext = Path.GetExtension(file); // includes dot
|
||||
|
||||
string[] fileNameParts = qualityIndex == -1 ? new string[]{
|
||||
@@ -103,7 +111,7 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
}
|
||||
|
||||
public static void DownloadVideo(string url){
|
||||
public static void DownloadVideo(string url, string username){
|
||||
string filename = BrowserUtils.GetFileNameFromUrl(url);
|
||||
string ext = Path.GetExtension(filename);
|
||||
|
||||
@@ -111,7 +119,7 @@ namespace TweetDuck.Core.Utils{
|
||||
AutoUpgradeEnabled = true,
|
||||
OverwritePrompt = true,
|
||||
Title = "Save video",
|
||||
FileName = filename,
|
||||
FileName = string.IsNullOrEmpty(username) ? filename : $"{username} {filename}",
|
||||
Filter = "Video"+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
|
||||
}){
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Management;
|
||||
@@ -49,17 +50,22 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
}
|
||||
|
||||
public static Process StartProcess(string file, string arguments, bool runElevated){
|
||||
ProcessStartInfo processInfo = new ProcessStartInfo{
|
||||
public static bool OpenAssociatedProgram(string file, string arguments = "", bool runElevated = false){
|
||||
try{
|
||||
using(Process.Start(new ProcessStartInfo{
|
||||
FileName = file,
|
||||
Arguments = arguments
|
||||
};
|
||||
|
||||
if (runElevated){
|
||||
processInfo.Verb = "runas";
|
||||
Arguments = arguments,
|
||||
Verb = runElevated ? "runas" : string.Empty,
|
||||
ErrorDialog = true
|
||||
})){
|
||||
return true;
|
||||
}
|
||||
}catch(Win32Exception e) when (e.NativeErrorCode == 0x000004C7){ // operation canceled by the user
|
||||
return false;
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Error opening file", e.Message, true, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return Process.Start(processInfo);
|
||||
}
|
||||
|
||||
public static bool TrySleepUntil(Func<bool> test, int timeoutMillis, int timeStepMillis){
|
||||
@@ -104,7 +110,7 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
|
||||
public static void ClipboardStripHtmlStyles(){
|
||||
if (!Clipboard.ContainsText(TextDataFormat.Html)){
|
||||
if (!Clipboard.ContainsText(TextDataFormat.Html) || !Clipboard.ContainsText(TextDataFormat.UnicodeText)){
|
||||
return;
|
||||
}
|
||||
|
||||
|
17
Program.cs
@@ -21,7 +21,7 @@ namespace TweetDuck{
|
||||
public const string BrandName = "TweetDuck";
|
||||
public const string Website = "https://tweetduck.chylex.com";
|
||||
|
||||
public const string VersionTag = "1.9.1";
|
||||
public const string VersionTag = "1.10.2";
|
||||
|
||||
public static readonly bool IsPortable = File.Exists("makeportable");
|
||||
|
||||
@@ -166,9 +166,13 @@ namespace TweetDuck{
|
||||
string updaterArgs = "/SP- /SILENT /CLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+Arguments.GetCurrentForInstallerCmd()+"\""+(IsPortable ? " /PORTABLE=1" : "");
|
||||
bool runElevated = !IsPortable || !WindowsUtils.CheckFolderWritePermission(ProgramPath);
|
||||
|
||||
WindowsUtils.StartProcess(mainForm.UpdateInstallerPath, updaterArgs, runElevated);
|
||||
if (WindowsUtils.OpenAssociatedProgram(mainForm.UpdateInstallerPath, updaterArgs, runElevated)){
|
||||
Application.Exit();
|
||||
}
|
||||
else{
|
||||
RestartWithArgsInternal(Arguments.GetCurrentClean());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetDataStoragePath(){
|
||||
@@ -211,11 +215,14 @@ namespace TweetDuck{
|
||||
FormBrowser browserForm = Application.OpenForms.OfType<FormBrowser>().FirstOrDefault();
|
||||
if (browserForm == null)return;
|
||||
|
||||
args.AddFlag(Arguments.ArgRestart);
|
||||
|
||||
browserForm.ForceClose();
|
||||
ExitCleanup();
|
||||
|
||||
ExitCleanup();
|
||||
RestartWithArgsInternal(args);
|
||||
}
|
||||
|
||||
private static void RestartWithArgsInternal(CommandLineArgs args){
|
||||
args.AddFlag(Arguments.ArgRestart);
|
||||
Process.Start(Application.ExecutablePath, args.ToString());
|
||||
Application.Exit();
|
||||
}
|
||||
|
68
README.md
@@ -1,11 +1,73 @@
|
||||
# Support
|
||||
|
||||
[Follow TweetDuck on Twitter](https://twitter.com/TryTweetDuck) | [Support via PayPal](https://paypal.me/chylex) | [Support via Patreon](https://www.patreon.com/chylex)
|
||||
|
||||
# Build Instructions
|
||||
|
||||
The program was built using Visual Studio 2017. After opening the solution, make sure you have **CefSharp.WinForms** and **Microsoft.VC120.CRT.JetBrains** included - if not, download them using NuGet.
|
||||
### Setup
|
||||
|
||||
The program was built using Visual Studio 2017. Before opening the solution, please make sure you have the following workloads and components installed (optional components that are not listed can be deselected to save space):
|
||||
* **.NET desktop development**
|
||||
* .NET Framework 4 – 4.6 development tools
|
||||
* **Desktop development with C++**
|
||||
* VC++ 2017 v141 toolset
|
||||
|
||||
After opening the solution, download the following NuGet packages by right-clicking on the solution and selecting **Restore NuGet Packages**, or manually running these commands in the **Package Manager Console**:
|
||||
```
|
||||
PM> Install-Package CefSharp.WinForms -Version 57.0.0
|
||||
PM> Install-Package Microsoft.VC120.CRT.JetBrains
|
||||
```
|
||||
|
||||
When building the `Release` configuration, the folder will only contain files intended for distribution (no debug symbols or other unnecessary files). You may package the files yourself, or use `bld/RUN BUILD.bat` to generate installer files using Inno Setup (make sure the Inno Setup binaries are on your PATH).
|
||||
### Debug
|
||||
|
||||
Built files are available in **bin/x86**, installer files are generated in **bld/Output**. If you decide to release a custom version publicly, please make it clear that it is not the original TweetDuck.
|
||||
It is recommended to create a separate data folder for debugging, otherwise you will not be able to run TweetDuck while debugging the solution.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
### 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
|
||||
|
||||
### 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).
|
||||
|
||||
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!
|
||||
|
||||
After the window closes, three installers will be generated inside the `bld/Output` folder:
|
||||
* **TweetDuck.exe**
|
||||
* This is the main installer that creates entries in the Start Menu & Programs and Features, and an optional desktop icon
|
||||
* **TweetDuck.Update.exe**
|
||||
* This is a lightweight update installer that only contains the most important files that usually change across releases
|
||||
* It will automatically download and apply the full installer if the user's current version of CEF does not match (the download link is in `gen_upd.iss` and points to this repository by default)
|
||||
* **TweetDuck.Portable.exe**
|
||||
* 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.
|
||||
|
||||
### Code Notes
|
||||
|
||||
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.
|
||||
|
BIN
Resources/Guide/img/app-menu.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
Resources/Guide/img/column-clear-header.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
Resources/Guide/img/column-clear-preferences.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
Resources/Guide/img/column-preferences.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
Resources/Guide/img/icon.ico
Normal file
After Width: | Height: | Size: 100 KiB |
BIN
Resources/Guide/img/new-tweet-emoji.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Resources/Guide/img/new-tweet-pin.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
Resources/Guide/img/new-tweet-template-advanced.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
Resources/Guide/img/new-tweet-template-basic.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
Resources/Guide/img/options-manage-export.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
Resources/Guide/img/options-manage-reset.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
Resources/Guide/img/options-manage.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
Resources/Guide/img/options-notifications-location.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
Resources/Guide/img/options-notifications-size.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
Resources/Guide/img/options-sounds.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
Resources/Guide/img/settings-dropdown.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
Resources/Guide/img/settings-editdesign.png
Normal file
After Width: | Height: | Size: 23 KiB |
404
Resources/Guide/index.html
Normal file
@@ -0,0 +1,404 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>TweetDuck - Guide</title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="robots" content="index,follow">
|
||||
<meta name="author" content="chylex">
|
||||
<meta name="description" content="TweetDuck guide">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="icon" href="img/icon.ico">
|
||||
<link rel="stylesheet" href="style.css" type="text/css">
|
||||
|
||||
<script type="text/javascript" src="script.js" async></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="guide">
|
||||
<section>
|
||||
|
||||
<article>
|
||||
<h2>General</h2>
|
||||
|
||||
<details>
|
||||
<summary id="main-menu">How to open the main menu</summary>
|
||||
<div>
|
||||
<p>The main menu gives you access to the following items:</p>
|
||||
<img src="img/app-menu.png" alt="">
|
||||
<p>There are two ways to open the main menu:</p>
|
||||
<ul>
|
||||
<li>Click <em>Settings</em> in the left panel, then select <em>TweetDuck</em></li>
|
||||
<li><em>Right-click anywhere</em> and you will either see the listed options, or a <em>TweetDuck</em> entry that contains these options</li>
|
||||
</ul>
|
||||
<img src="img/settings-dropdown.png" alt="">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="customize-theme">How to customize the theme and layout</summary>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Click <em>Settings</em> in the left panel</li>
|
||||
<li>Continue to <em>Edit layout & design</em></li>
|
||||
<li>Now you can customize many aspects of the website; note that unlike the default TweetDeck settings, the column width and font size can be configured here in much greater detail</li>
|
||||
</ol>
|
||||
<img src="img/settings-editdesign.png" alt="">
|
||||
<p>This is done using an official plugin called <em>Edit layout & design</em>, which is enabled by default. If the plugin is disabled, you can still access the default TweetDeck settings:</p>
|
||||
<ol>
|
||||
<li>Click <em>Settings</em> on the bottom left</li>
|
||||
<li>Continue to <em>Settings</em> (again)</li>
|
||||
<li>Here you can customize the theme, column width, and font size</li>
|
||||
</ol>
|
||||
<img src="img/settings-dropdown.png" alt="">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="emoji">How to add emoji to tweets</summary>
|
||||
<div>
|
||||
<p>When writing a new tweet, click the heart icon to open an emoji picker. If you're writing a reply, click the <em>Popout</em> icon to bring the reply into the New Tweet panel.</p>
|
||||
<p>Then you can immediately type into the <em>Search</em> field, which accepts keywords separated by space. Pressing <em>Enter</em> in the search field (when not empty) will insert the first result into your tweet. Pressing <em>Escape</em> closes the emoji picker.</p>
|
||||
<p>You can also use your mouse to change the skin tone, scroll through the emoji, and click on the emoji to insert them.</p>
|
||||
<p>Typing <em>:emoji_name:</em> in the New Tweet panel will automatically search for an emoji using those keywords. If a single emoji is found, it will be inserted into the tweet (press <em>Backspace</em> or <em>Escape</em> to revert it). If multiple emojis are found, it will open the emoji picker where you can refine the search, or press <em>Escape</em> to go back.</p>
|
||||
<p>Emoji are provided by an official plugin called <em>Emoji keyboard</em>, which is enabled by default. The heart icon will not show if the plugin is disabled.</p>
|
||||
<img src="img/new-tweet-emoji.png" alt="">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="templates">How to use tweet templates</summary>
|
||||
<div>
|
||||
<p>To create a simple template to use when writing a new tweet or reply:</p>
|
||||
<ol>
|
||||
<li>Click <em>Manage templates</em> in the New Tweet panel; if you're writing a reply, click the <em>Popout</em> icon first to bring the reply into the large panel</li>
|
||||
<li>Click <em>New template</em> on the bottom right</li>
|
||||
<li>Fill in the template name and contents</li>
|
||||
<li>Click <em>Confirm</em> to create the template</li>
|
||||
</ol>
|
||||
<p>After you create a template, it will be added to the list. There are two icons next to each entry:</p>
|
||||
<ul>
|
||||
<li>Click the <em>pencil icon to edit</em> the template</li>
|
||||
<li>Click the <em>cross icon to delete</em> the template</li>
|
||||
</ul>
|
||||
<p>To use the template, <em>click the template name</em> to replace your current tweet text with the template, or click while holding <em>Shift</em> to append the template to your tweet instead. You can use the Shift+click functionality to quickly chain multiple templates.</p>
|
||||
<img src="img/new-tweet-template-basic.png" alt="">
|
||||
<p>When writing a template, you can use special <em>tokens</em> listed in the <em>Advanced</em> section. Here is an example of one of the tokens:</p>
|
||||
<img src="img/new-tweet-template-advanced.png" alt="">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="upload-from-clipboard">How to upload images from clipboard</summary>
|
||||
<div>
|
||||
<p>When writing a tweet/reply, press <em>Ctrl+V</em> to upload an image from clipboard. You can use this to quickly paste a selection from an image editor such as Paint, or after copying an image in your browser.</p>
|
||||
<p>Make sure you're in the tweet input field before you press Ctrl+V, otherwise the keyboard shortcut won't trigger.</p>
|
||||
<p>Note that this will only work when your clipboard contains the image itself; it will not work if you copy a file or URL.</p>
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<h2>Columns</h2>
|
||||
|
||||
<details>
|
||||
<summary id="copy-tweet-links">How to copy links to tweets</summary>
|
||||
<div>
|
||||
<p>When you right-click anywhere inside a tweet, you will be given these options:</p>
|
||||
<ul>
|
||||
<li>Open tweet in browser</li>
|
||||
<li><em>Copy tweet address</em></li>
|
||||
<li>Screenshot tweet to clipboard</li>
|
||||
</ul>
|
||||
<p>If the tweet contains a quote, you will also be able to directly open the quote or copy its address.</p>
|
||||
<p>Note that these options will not appear when you right-click a private message.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="download-media">How to download images and videos</summary>
|
||||
<div>
|
||||
<p>When you right-click an image or a video thumbnail with a purple play button, you will be given these options:</p>
|
||||
<ul>
|
||||
<li>Open image/video in browser</li>
|
||||
<li>Copy image/video address</li>
|
||||
<li><em>Save image/video as...</em></li>
|
||||
</ul>
|
||||
<p>TweetDuck will attempt to fetch the highest quality image when you click any of these options. You can disable that by going to the <em>main menu</em>, selecting <em>Options</em>, and then unchecking <em>Best Image Quality</em>.</p>
|
||||
<p>Whenever possible, the username and quality are included in the filename by default, for convenience. After you select a folder and click Save, the image/video will be downloaded in the background. There is no notification for when the download finishes, but you will be notified if it fails.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="screenshot-tweets">How to take screenshots of individual tweets</summary>
|
||||
<div>
|
||||
<p>When you right-click anywhere inside a tweet, you will be given these options:</p>
|
||||
<ul>
|
||||
<li>Open tweet in browser</li>
|
||||
<li>Copy tweet address</li>
|
||||
<li><em>Screenshot tweet to clipboard</em></li>
|
||||
</ul>
|
||||
<p>Taking a screenshot may take several seconds, especially if it contains large images or previews. After taking a screenshot, you can paste it into an image editor such as Paint.</p>
|
||||
<p>Note that these options will not appear when you right-click a private message.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="clear-columns">How to clear and restore column contents</summary>
|
||||
<div>
|
||||
<p>TweetDeck lets you clear a column, which can help keep things organized by hiding tweets you have already read. To clear a column, click the <em>slider icon</em> on top of the column, and then click the <em>Clear</em> button.</p>
|
||||
<p>If you need to revert this decision and restore a column, normally you would need to delete and re-create it, but in TweetDuck you can simply hold Shift which turns the Clear button into a <em>Restore</em> button.</p>
|
||||
<img src="img/column-clear-preferences.png" alt="">
|
||||
<p>If you clear columns frequently, you can enable an official plugin that lets you clear columns much quicker:</p>
|
||||
<ol>
|
||||
<li>Open the <em>main menu</em>, and select <em>Plugins</em> to open the list of available plugins</li>
|
||||
<li>Find an entry that says <em>Clear columns</em>, it will be somewhere near the bottom as the plugin is disabled by default</li>
|
||||
<li>Click <em>Enable</em> on the right side to enable the plugin and reload the browser</li>
|
||||
</ol>
|
||||
<p>Now you can clear...</p>
|
||||
<ul>
|
||||
<li>...a single column by clicking the <em>droplet icon</em> on top of each column</li>
|
||||
<li>...a single column by holding the <em>1-9 number key</em> and pressing <em>Delete</em></li>
|
||||
<li>...all columns by clicking <em>Clear columns</em> in the left panel</li>
|
||||
<li>...all columns by pressing <em>Alt+Delete</em></li>
|
||||
</ul>
|
||||
<img src="img/column-clear-header.png" alt="">
|
||||
<p>As mentioned before, hold Shift with any of these to restore the columns instead. Note that some keyboard layouts require using the Shift key when pressing number keys; if that is the case for you, please use the numpad or mouse instead.</p>
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<h2>Notifications</h2>
|
||||
|
||||
<details>
|
||||
<summary id="enable-notifications">How to enable desktop or sound notifications</summary>
|
||||
<div>
|
||||
<p>New columns have disabled notifications by default. To enable them, click the <em>slider icon</em> on top of the column, and expand the <em>Preferences</em> section.</p>
|
||||
<p>Now you can toggle either, or both of the notification options:</p>
|
||||
<img src="img/column-preferences.png" alt="">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="move-resize-notifications">How to move or resize desktop notifications</summary>
|
||||
<div>
|
||||
<p>Open the <em>main menu</em>, select <em>Options</em>, and then click the <em>Notifications</em> tab. Here, you can customize many aspects of desktop notifications.</p>
|
||||
<p>Scroll down to the <em>Location</em> section where you can customize where the notification shows up. You can either pick one of the 4 corners of your screen and the distance from the corner, or select <em>Custom</em> and then you'll be able to freely move the example notification window.</p>
|
||||
<img src="img/options-notifications-location.png" alt="">
|
||||
<p>Scroll down to the <em>Size</em> section to customize the size of the notification window. By default, TweetDuck sets the size based on your font size setting, the zoom level you can customize in the General tab, and your system DPI. If you pick <em>Custom</em>, you will be able to freely resize the example notification window.</p>
|
||||
<img src="img/options-notifications-size.png" alt="">
|
||||
<p>Note that moving and resizing the notification only works while you're inside the Options dialog, that is to prevent accidental clicks messing up your settings.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="sound-notifications">How to customize sound notifications</summary>
|
||||
<div>
|
||||
<p>Open the <em>main menu</em>, select <em>Options</em>, and then click the <em>Sounds</em> tab. Here, you can pick a sound file that will be used instead of the default TweetDeck sound notification.</p>
|
||||
<p>Keep in mind that you're only linking to the sound file, so make sure not to delete the file afterwards, otherwise TweetDuck won't find it anymore.</p>
|
||||
<img src="img/options-sounds.png" alt="">
|
||||
<p>If you're unable to select MP3 files or other common audio file types, please ensure that you have Windows Media Player installed on your system, otherwise you will only be able to select basic WAV files.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="mute-notifications">How to temporarily mute all notifications</summary>
|
||||
<div>
|
||||
<p>There are two ways you can mute/unmute notifications:</p>
|
||||
<ul>
|
||||
<li>Open the <em>main menu</em> and click <em>Mute notifications</em></li>
|
||||
<li>If you've enabled the <em>tray icon</em>, right-click it and then click <em>Mute notifications</em></li>
|
||||
</ul>
|
||||
<p>The option persists across restarts – if you mute notifications and then restart TweetDuck, don't forget to unmute the notifications again.</p>
|
||||
<p>Unmuting notifications will display all missed desktop notifications (unless TweetDuck was restarted in the meantime).</p>
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
|
||||
</section>
|
||||
<section>
|
||||
|
||||
<article>
|
||||
<h2>Options</h2>
|
||||
|
||||
<details>
|
||||
<summary id="configure-tweetduck">How to configure TweetDuck</summary>
|
||||
<div>
|
||||
<p>Open the <em>main menu</em> and select <em>Options</em>. Here you can configure various parts of TweetDuck; the dialog is split into several tabs:</p>
|
||||
<ul>
|
||||
<li><em>General</em> tab for user interface, zoom, and update options</li>
|
||||
<li><em>System Tray</em> tab to enable and configure the tray icon</li>
|
||||
<li><em>Notifications</em> tab to configure desktop notifications</li>
|
||||
<li><em>Sounds</em> tab to set a custom sound notification</li>
|
||||
<li><em>Advanced</em> tab for highly technical options</li>
|
||||
</ul>
|
||||
<p>You can move your cursor over most options to display a <em>tooltip with a detailed explanation</em> of what that option does.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="manage-plugins">How to view and manage plugins</summary>
|
||||
<div>
|
||||
<p>TweetDuck has several offical plugins that extend the website and notifications with new functionality.</p>
|
||||
<p>Open the <em>main menu</em> and select <em>Plugins</em> to open the plugin list. Here you can see what each plugin does, and enable/disable them individually. </p>
|
||||
<p>If you want to install a custom plugin, click <em>Open Plugin Folder</em>. A plugin is a folder that contains a <em>.meta</em> file and several others, make sure you copy and paste the folder itself into the opened plugin folder. To verify that you installed it correctly, click <em>Reload All</em> (which also reloads the website) and the plugin should appear.</p>
|
||||
<p>Please, be careful when installing new plugins, and ensure that you get them from trustworthy sources. If you're unsure about a plugin, feel free to <a href="https://github.com/chylex/TweetDuck/issues/new" rel="nofollow">create an issue</a> and upload the plugin there (<a href="https://github.com" rel="nofollow">GitHub</a> account required).</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="manage-profile">How to backup your profile or move it to another computer</summary>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Open the <em>main menu</em>, select <em>Options</em>, and click <em>Manage Options</em> on the bottom left</li>
|
||||
<li>Select <em>Export profile</em> and proceed with <em>Next</em></li>
|
||||
<li>Select items you want to save in your profile (note that <em>Plugin Data</em> includes data from official plugins, such as those that let you customize the website or create tweet templates)</li>
|
||||
<li>Click <em>Export Profile</em></li>
|
||||
</ol>
|
||||
<img src="img/options-manage.png" alt="" style="margin-right:6px">
|
||||
<img src="img/options-manage-export.png" alt="">
|
||||
<p>You can save your profile into a cloud storage (Dropbox, Google Drive, etc.) or an external drive, for example. When you want to restore it, follow the same steps but select <em>Import profile</em> and then select the file instead.</p>
|
||||
<p>When importing a profile, you will be again able to pick which items you want to restore. You can for example export a full profile including your login session, but then only import program options and plugin data if you want to login to a different account.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="restore-options">How to restore default options</summary>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Open the <em>main menu</em>, select <em>Options</em>, and click <em>Manage Options</em> on the bottom left</li>
|
||||
<li>Select <em>Restore defaults</em> and proceed with <em>Next</em></li>
|
||||
<li>Select items you want reset (note that <em>Plugin Data</em> includes data from official plugins, such as those that let you customize the website or create tweet templates)</li>
|
||||
<li>Click <em>Restore Defaults</em></li>
|
||||
</ol>
|
||||
<img src="img/options-manage.png" alt="" style="margin-right:6px">
|
||||
<img src="img/options-manage-reset.png" alt="">
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<h2>Efficiency</h2>
|
||||
|
||||
<details>
|
||||
<summary id="keyboard-shortcuts">How to use keyboard shortcuts</summary>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Click <em>Settings</em> in the left panel</li>
|
||||
<li>Continue to <em>Keyboard shortcuts</em></li>
|
||||
<li>Here you can see most available keyboard shortcuts you can use in the browser window</li>
|
||||
</ol>
|
||||
<img src="img/settings-dropdown.png" alt="">
|
||||
<p>You can also often hold Ctrl or Shift to trigger alternative actions:</p>
|
||||
<ul>
|
||||
<li>When <em>selecting accounts</em>, hold <em>Shift</em> to select multiple accounts (can be configured in the Options)</li>
|
||||
<li>When <em>clearing columns</em>, hold <em>Shift</em> to restore the column instead</li>
|
||||
<li>When <em>clicking video thumbnails</em>, hold <em>Ctrl</em> to open them in your browser</li>
|
||||
</ul>
|
||||
<p>Finally, if you click into a desktop notification window, you can use these keyboard shortcuts:</p>
|
||||
<ul>
|
||||
<li><em>Enter</em> to skip the current notification</li>
|
||||
<li><em>Escape</em> to close the window (skips all notifications in the queue)</li>
|
||||
<li><em>Space</em> to pause/unpause the timer</li>
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="extra-mouse-buttons">How to use the forward / back mouse buttons</summary>
|
||||
<div>
|
||||
<p>If you have a mouse that supports the forward and back buttons, you can use them in both the browser and a desktop notification. All you need to do is move the cursor over the window (even if it's not focused), and press one of the buttons.</p>
|
||||
<p>In the browser:</p>
|
||||
<ul>
|
||||
<li>Press <em>forward</em> over a <em>tweet to open it in detail view</em> (unlike clicking, this will not trigger links or media thumbnails)</li>
|
||||
<li>Press <em>back</em> anywhere to <em>close modal dialogs</em> or the <em>New Tweet panel</em>, or over a <em>column to return back from detail view</em> (if there are no dialogs or panels open, pressing the button outside a column will trigger it for all columns at once)</li>
|
||||
</ul>
|
||||
<p>In the desktop notification:</p>
|
||||
<ul>
|
||||
<li>Press <em>forward</em> to skip the current notification</li>
|
||||
<li>Press <em>back</em> to close the window (skips all notifications in the queue)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="pin-new-tweet">How to keep the New Tweet panel open</summary>
|
||||
<div>
|
||||
<p>Open the New Tweet panel, and click the <em>pin icon</em> on top. When the pin points to the left, the panel will stay open after tweeting or restarting TweetDuck.</p>
|
||||
<img src="img/new-tweet-pin.png" alt="">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="popout-replies">How to instantly popout replies</summary>
|
||||
<div>
|
||||
<p><em>Middle-click the reply icon</em> to instantly open your reply in the New Tweet panel.</p>
|
||||
<p>Middle-clicks are usually done by pressing your mouse wheel as if it was a button. When using a laptop touchpad or certain mice, the ways of triggering a middle click vary.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="reply-account">How to change which account will be pre-selected for replies</summary>
|
||||
<div>
|
||||
<p>By default, TweetDeck pre-selects the account mentioned in the column header. The ability to change this is provided by an official plugin which is disabled by default, as it's a bit more difficult to setup, but it can be very powerful. To enable the plugin:</p>
|
||||
<ol>
|
||||
<li>Open the <em>main menu</em> and select <em>Plugins</em></li>
|
||||
<li>Find an entry that says <em>Custom reply account</em>, it will be somewhere near the bottom as the plugin is disabled by default</li>
|
||||
<li>Click <em>Enable</em> on the right side to enable the plugin</li>
|
||||
</ol>
|
||||
<p>After you enable the plugin, it will use your preferred account for all replies by default. If that's your intention, you can simply enable the plugin and leave it, otherwise continue reading:</p>
|
||||
<ol>
|
||||
<li>Click <em>Configure</em> next to the plugin to open a folder with the configuration file
|
||||
<li>Open <em>configuration.js</em> in a text editor that can edit and save JavaScript or any pure text files, therefore office suits or WordPad are not suitable; if you don't have any specific editor, use Notepad.</li>
|
||||
<li>The configuration file includes very detailed instructions – you can use one of the <em>presets</em>, a <em>specific account</em> for all replies, or use JavaScript to <em>fully customize</em> the reply behavior</li>
|
||||
</ol>
|
||||
<p>After editing the configuration, return back to Plugins and click <em>Reload All</em> on the bottom left. Now you can close Plugins and test if replies work the way you want.</p>
|
||||
<p>Note that this will not affect the Messages column, that one will always pre-select the account which received the private message.</p>
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<h2>Advanced</h2>
|
||||
|
||||
<details>
|
||||
<summary id="dev-tools">How to open Chrome Dev Tools</summary>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Open the <em>main menu</em>, select <em>Options</em>, and click the <em>Advanced</em> tab</li>
|
||||
<li>Click <em>Open Program Folder</em></li>
|
||||
<li>Download <a href="https://github.com/chylex/TweetDuck/raw/master/bld/Resources/devtools_resources.pak" rel="nofollow">devtools_resources.pak</a> and place it into the opened folder</li>
|
||||
<li>Click <em>Restart the Program</em></li>
|
||||
<li>Now, open the <em>main menu</em> again and you should see <em>Open dev tools</em>; you can also right-click inside a notification and see the same option (make sure to pause the notification first by clicking Freeze in the context menu)</li>
|
||||
</ol>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="custom-css">How to customize styles using CSS</summary>
|
||||
<div>
|
||||
<ol>
|
||||
<li>Open the <em>main menu</em>, select <em>Options</em>, and click the <em>Advanced</em> tab</li>
|
||||
<li>Click <em>Edit CSS</em></li>
|
||||
</ol>
|
||||
<p>Now you can write custom CSS into the <em>Browser</em> and <em>Notification</em> sections.</p>
|
||||
<p>Note that the Browser section will immediately take effect as you type. You can also still access the browser and Dev Tools, as the dialog does not block the browser window.</p>
|
||||
<p>Basic knowledge of HTML and CSS is recommended. Mozilla Development Network has a huge library of resources on both <a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/HTML_basics" rel="nofollow">HTML</a> and <a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/CSS_basics" rel="nofollow">CSS</a>.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary id="plugin-development">How to develop plugins</summary>
|
||||
<div>
|
||||
<p>Before creating a plugin, you should have at least basic knowledge of <a href="https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web" rel="nofollow">web development</a> (namely HTML, CSS, JavaScript), and several JS libraries TweetDeck uses, such as <a href="https://learn.jquery.com" rel="nofollow">jQuery 2</a>, <a href="https://mustache.github.io" rel="nofollow">Mustache</a>, <a href="https://github.com/ded/klass" rel="nofollow">klass</a>, and <a href="https://flightjs.github.io/" rel="nofollow">Flight</a>.</p>
|
||||
<p>Working with the TweetDeck source code involves a lot of reverse-engineering. You can visit <a href="https://github.com/DeckHack/discoveries" rel="nofollow">DeckHack</a> which is working to document its source code, and view <a href="https://github.com/chylex/TweetDuck/tree/master/Resources/Scripts" rel="nofollow">TweetDuck sources</a> which includes all scripts and official plugins.</p>
|
||||
<p>Once you're ready to start creating your own plugins, visit the official <a href="https://github.com/chylex/TweetDuck/wiki/Plugins%EA%9E%89-1.-The-Basics" rel="nofollow">plugin development documentation</a> which will explain the structure of a plugin, and show you all TweetDuck-specific functionality you cannot normally use in browsers.</p>
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
84
Resources/Guide/script.js
Normal file
@@ -0,0 +1,84 @@
|
||||
var init = function(){
|
||||
if (!("open" in document.getElementsByTagName("details")[0])){
|
||||
var elements = document.getElementsByTagName("details");
|
||||
|
||||
var onClick = function(e){
|
||||
var summary = e.target;
|
||||
var parent = e.target.parentElement;
|
||||
var contents = e.target.nextElementSibling;
|
||||
|
||||
if (parent.hasAttribute("open")){
|
||||
parent.removeAttribute("open");
|
||||
summary.setAttribute("aria-expanded", "false");
|
||||
contents.setAttribute("aria-hidden", "true");
|
||||
contents.style.display = "none";
|
||||
}
|
||||
else{
|
||||
parent.setAttribute("open", "");
|
||||
summary.setAttribute("aria-expanded", "true");
|
||||
contents.setAttribute("aria-hidden", "false");
|
||||
contents.style.display = "block";
|
||||
}
|
||||
};
|
||||
|
||||
var onKey = function(e){
|
||||
if (e.keyCode === 13 || e.keyCode === 32){
|
||||
onClick(e);
|
||||
}
|
||||
};
|
||||
|
||||
for(var index = 0; index < elements.length; index++){
|
||||
var ele = elements[index];
|
||||
|
||||
if (ele.childElementCount === 2){
|
||||
var summary = ele.children[0];
|
||||
var contents = ele.children[1];
|
||||
|
||||
ele.style.display = "block";
|
||||
ele.setAttribute("role", "group");
|
||||
|
||||
summary.setAttribute("role", "button");
|
||||
summary.setAttribute("aria-expanded", "false");
|
||||
summary.setAttribute("tabindex", 0);
|
||||
summary.addEventListener("click", onClick);
|
||||
summary.addEventListener("keydown", onKey);
|
||||
|
||||
contents.setAttribute("aria-hidden", "true");
|
||||
contents.style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ("WebkitAppearance" in document.documentElement.style){
|
||||
var elements = document.getElementsByTagName("summary");
|
||||
|
||||
var onMouseDown = function(e){
|
||||
e.target.classList.add("webkit-workaround");
|
||||
};
|
||||
|
||||
var onMouseUp = function(e){
|
||||
e.target.classList.remove("webkit-workaround");
|
||||
e.target.blur();
|
||||
};
|
||||
|
||||
for(var index = 0; index < elements.length; index++){
|
||||
elements[index].addEventListener("mousedown", onMouseDown);
|
||||
elements[index].addEventListener("mouseup", onMouseUp);
|
||||
}
|
||||
}
|
||||
|
||||
if (location.hash.length > 1){
|
||||
var element = document.getElementById(location.hash.substring(1));
|
||||
|
||||
if (element && element.tagName === "SUMMARY"){
|
||||
element.click();
|
||||
element.scrollIntoView(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (document.readyState !== "loading"){
|
||||
init();
|
||||
}
|
||||
else{
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
}
|
132
Resources/Guide/style.css
Normal file
@@ -0,0 +1,132 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, Verdana, sans-serif;
|
||||
background-color: #222;
|
||||
color: #ddd;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#guide {
|
||||
width: 100%;
|
||||
max-width: 1440px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
#guide section {
|
||||
flex: 1 1 0;
|
||||
margin: 0 24px;
|
||||
min-width: 360px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media(max-width: 408px) {
|
||||
#guide section {
|
||||
min-width: calc(100vw - 48px);
|
||||
}
|
||||
}
|
||||
|
||||
#guide h2 {
|
||||
margin: 20px 0 10px;
|
||||
font-size: 32px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#guide details {
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
#guide details[open] {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
#guide summary {
|
||||
display: inline-block;
|
||||
margin: 0 0 11px;
|
||||
padding: 4px 10px;
|
||||
font-size: 19px;
|
||||
font-weight: bold;
|
||||
color: #afdfff;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
#guide summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#guide summary.webkit-workaround {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#guide summary::before {
|
||||
content: "▶";
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
vertical-align: top;
|
||||
margin-right: 6px;
|
||||
padding-top: 2px;
|
||||
transform-origin: 35% 50%;
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
#guide summary:hover, #guide details[open] summary {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#guide details[open] summary::before {
|
||||
transform: rotateZ(90deg) translate(0.5px, -3px);
|
||||
}
|
||||
|
||||
#guide details > div {
|
||||
margin-bottom: 22px;
|
||||
padding: 0 25px 20px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
#guide details > div > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#guide details > div > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
em {
|
||||
color: #f8d88b;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #8bc6f8;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
p, li {
|
||||
line-height: 130%;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 26px;
|
||||
}
|
||||
|
||||
ol {
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 2px groove rgba(255, 255, 255, 0.6);
|
||||
max-width: 100%;
|
||||
}
|
@@ -8,10 +8,10 @@ Edit layout & design
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.1.2
|
||||
1.1.5
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
||||
[requires]
|
||||
1.7
|
||||
1.10.2
|
@@ -12,6 +12,7 @@ enabled(){
|
||||
moveTweetActionsToRight: true,
|
||||
themeColorTweaks: true,
|
||||
revertIcons: true,
|
||||
increaseQuoteTextSize: false,
|
||||
smallComposeTextSize: false,
|
||||
optimizeAnimations: true,
|
||||
avatarRadius: 2
|
||||
@@ -108,7 +109,7 @@ enabled(){
|
||||
let menu = $(".js-dropdown-content").children("ul").first();
|
||||
return if menu.length === 0;
|
||||
|
||||
let itemTD = menu.children("[data-std]").first();
|
||||
let itemTD = menu.children("[data-tweetduck]").first();
|
||||
return if itemTD.length === 0;
|
||||
|
||||
if (!itemTD.prev().hasClass("drp-h-divider")){
|
||||
@@ -116,7 +117,7 @@ enabled(){
|
||||
}
|
||||
|
||||
let itemEditDesign = $('<li class="is-selectable"><a href="#" data-action>Edit layout & design</a></li>');
|
||||
itemTD.after(itemEditDesign);
|
||||
itemEditDesign.insertAfter(itemTD);
|
||||
|
||||
itemEditDesign.on("click", "a", this.openEditDesignDialog);
|
||||
|
||||
@@ -331,7 +332,7 @@ enabled(){
|
||||
this.css.insert("#general_settings .cf { display: none !important }");
|
||||
this.css.insert("#settings-modal .js-setting-list li:nth-child(3) { border-bottom: 1px solid #ccd6dd }");
|
||||
|
||||
this.css.insert(".txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: "+this.config.fontSize+" !important }");
|
||||
this.css.insert("html[data-td-font] { font-size: "+this.config.fontSize+" !important }");
|
||||
this.css.insert(".avatar { border-radius: "+this.config.avatarRadius+"% !important }");
|
||||
|
||||
let notificationScrollbarColor = null;
|
||||
@@ -366,6 +367,10 @@ enabled(){
|
||||
this.css.insert(".tweet-actions > li:nth-child(4) { margin-right: 2px !important }");
|
||||
}
|
||||
|
||||
if (this.config.increaseQuoteTextSize){
|
||||
this.css.insert(".quoted-tweet { font-size: 1em !important }");
|
||||
}
|
||||
|
||||
if (this.config.smallComposeTextSize){
|
||||
this.css.insert(".compose-text { font-size: 12px !important; height: 120px !important }");
|
||||
}
|
||||
@@ -507,9 +512,13 @@ enabled(){
|
||||
|
||||
$TDP.injectIntoNotificationsBefore(this.$token, "css", "</head>", `
|
||||
<style type='text/css'>
|
||||
.txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: ${this.config.fontSize} !important }
|
||||
html[data-td-font] { font-size: ${this.config.fontSize} !important }
|
||||
.avatar { border-radius: ${this.config.avatarRadius}% !important }
|
||||
|
||||
${this.config.increaseQuoteTextSize ? `
|
||||
.quoted-tweet { font-size: 1em !important }
|
||||
` : ``}
|
||||
|
||||
${this.config.revertIcons ? `
|
||||
@font-face { font-family: 'tweetdeckold'; src: url(\"https://ton.twimg.com/tweetdeck-web/web/assets/fonts/tweetdeck-regular-webfont.5f4ea87976.woff\") format(\"woff\"); font-weight: normal; font-style: normal }
|
||||
.icon-reply:before{content:"\\f006";font-family:tweetdeckold}
|
||||
|
@@ -61,15 +61,13 @@
|
||||
<option value="16px">Largest (16px)</option>
|
||||
<option value="custom">Custom</option>
|
||||
</select>
|
||||
|
||||
<!-- ADVANCED -->
|
||||
|
||||
<label class="txt-uppercase touch-larger-label">
|
||||
<b>Advanced</b>
|
||||
<label class="checkbox">
|
||||
<input data-td-key="increaseQuoteTextSize" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Increase quoted tweet font size
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input data-td-key="optimizeAnimations" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Use more memory for smoother animations
|
||||
<input data-td-key="smallComposeTextSize" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Small tweet input font size
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -102,9 +100,15 @@
|
||||
<input data-td-key="revertIcons" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Revert icon design
|
||||
</label>
|
||||
|
||||
<!-- ADVANCED -->
|
||||
|
||||
<label class="txt-uppercase touch-larger-label">
|
||||
<b>Advanced</b>
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input data-td-key="smallComposeTextSize" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Small compose tweet font size
|
||||
<input data-td-key="optimizeAnimations" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Use more memory for smoother animations
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -163,11 +167,12 @@
|
||||
.td-modal-inner-cols .l-column {
|
||||
padding: 15px 9px;
|
||||
box-sizing: border-box;
|
||||
width: 225px;
|
||||
font-size: 0; /* fix custom font size breaking the modal layout */
|
||||
}
|
||||
|
||||
.td-modal-inner-cols .l-column:nth-child(2) {
|
||||
width: 250px;
|
||||
.td-modal-inner-cols .l-column:nth-child(3) {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.td-modal-inner-full {
|
||||
@@ -199,6 +204,14 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.td-modal-content select + label.checkbox {
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
.td-modal-content input.js-theme-checkbox, .td-modal-content input.js-theme-radio {
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
/* Avatar shape */
|
||||
|
||||
.td-avatar-shape-container {
|
||||
|
@@ -9,7 +9,7 @@ Emoji keyboard
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.3.1
|
||||
1.4.1
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
@@ -66,6 +66,7 @@ enabled(){
|
||||
this.currentSpanner = null;
|
||||
|
||||
var wasSearchFocused = false;
|
||||
var lastEmojiKeyword, lastEmojiPosition, lastEmojiLength;
|
||||
|
||||
var hideKeyboard = (refocus) => {
|
||||
$(this.currentKeyboard).remove();
|
||||
@@ -81,8 +82,14 @@ enabled(){
|
||||
$(".emoji-keyboard-popup-btn").removeClass("is-selected");
|
||||
|
||||
if (refocus){
|
||||
$(".js-compose-text", ".js-docked-compose").focus();
|
||||
this.composeInput.focus();
|
||||
|
||||
if (lastEmojiKeyword && lastEmojiPosition === 0){
|
||||
document.execCommand("insertText", false, lastEmojiKeyword);
|
||||
}
|
||||
}
|
||||
|
||||
lastEmojiKeyword = null;
|
||||
};
|
||||
|
||||
var generateEmojiHTML = skinTone => {
|
||||
@@ -229,7 +236,7 @@ enabled(){
|
||||
};
|
||||
|
||||
var insertEmoji = (src, alt) => {
|
||||
let input = $(".js-compose-text", ".js-docked-compose");
|
||||
let input = this.composeInput;
|
||||
|
||||
let val = input.val();
|
||||
let posStart = input[0].selectionStart;
|
||||
@@ -241,6 +248,8 @@ enabled(){
|
||||
input[0].selectionStart = posStart+alt.length;
|
||||
input[0].selectionEnd = posStart+alt.length;
|
||||
|
||||
lastEmojiKeyword = null;
|
||||
|
||||
if (wasSearchFocused){
|
||||
$(".emoji-keyboard-search").children("input").focus();
|
||||
}
|
||||
@@ -270,6 +279,95 @@ enabled(){
|
||||
}
|
||||
};
|
||||
|
||||
this.composeInputKeyDownEvent = function(e){
|
||||
if (lastEmojiKeyword && (e.keyCode === 8 || e.keyCode === 27)){ // backspace, escape
|
||||
let ele = $(this)[0];
|
||||
|
||||
if (ele.selectionStart === lastEmojiPosition){
|
||||
ele.selectionStart -= lastEmojiLength; // selects the emoji
|
||||
document.execCommand("insertText", false, lastEmojiKeyword);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
lastEmojiKeyword = null;
|
||||
}
|
||||
};
|
||||
|
||||
this.composeInputKeyPressEvent = function(e){
|
||||
if (String.fromCharCode(e.which) === ':'){
|
||||
let ele = $(this);
|
||||
let val = ele.val();
|
||||
|
||||
let firstColon = val.lastIndexOf(':', ele[0].selectionStart);
|
||||
return if firstColon === -1;
|
||||
|
||||
let search = val.substring(firstColon+1, ele[0].selectionStart).toLowerCase();
|
||||
return if !/^[a-z_]+$/.test(search);
|
||||
|
||||
let keywords = search.split("_").filter(kw => kw.length > 0).map(kw => kw.toLowerCase());
|
||||
return if keywords.length === 0;
|
||||
|
||||
let foundNames = me.emojiNames.filter(name => keywords.every(kw => name.includes(kw)));
|
||||
|
||||
if (foundNames.length === 0){
|
||||
return;
|
||||
}
|
||||
else if (foundNames.length > 1 && foundNames.includes(search)){
|
||||
foundNames = [ search ];
|
||||
}
|
||||
|
||||
lastEmojiKeyword = `:${search}:`;
|
||||
lastEmojiPosition = lastEmojiLength = 0;
|
||||
|
||||
if (foundNames.length === 1){
|
||||
let foundIndex = me.emojiNames.indexOf(foundNames[0]);
|
||||
let foundEmoji;
|
||||
|
||||
for(let array of [ me.emojiData1, me.emojiData2[me.selectedSkinTone], me.emojiData3 ]){
|
||||
let realArray = array.filter(ele => ele !== "___");
|
||||
|
||||
if (foundIndex >= realArray.length){
|
||||
foundIndex -= realArray.length;
|
||||
}
|
||||
else{
|
||||
foundEmoji = realArray[foundIndex];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundEmoji){
|
||||
e.preventDefault();
|
||||
|
||||
ele.val(val.substring(0, firstColon)+foundEmoji+val.substring(ele[0].selectionStart));
|
||||
ele[0].selectionEnd = ele[0].selectionStart = firstColon+foundEmoji.length;
|
||||
ele.trigger("change");
|
||||
ele.focus();
|
||||
|
||||
lastEmojiPosition = firstColon+foundEmoji.length;
|
||||
lastEmojiLength = foundEmoji.length;
|
||||
}
|
||||
}
|
||||
else if (foundNames.length > 1){
|
||||
e.preventDefault();
|
||||
ele.val(val.substring(0, firstColon)+val.substring(ele[0].selectionStart));
|
||||
ele[0].selectionEnd = ele[0].selectionStart = firstColon;
|
||||
ele.trigger("change");
|
||||
|
||||
if (!me.currentKeyboard){
|
||||
$(".emoji-keyboard-popup-btn").click();
|
||||
}
|
||||
|
||||
$(".emoji-keyboard-search").children("input").focus().val("");
|
||||
document.execCommand("insertText", false, keywords.join(" "));
|
||||
}
|
||||
}
|
||||
else{
|
||||
lastEmojiKeyword = null;
|
||||
}
|
||||
};
|
||||
|
||||
this.composeInputFocusEvent = function(e){
|
||||
wasSearchFocused = false;
|
||||
};
|
||||
@@ -300,15 +398,18 @@ enabled(){
|
||||
|
||||
ready(){
|
||||
this.composeDrawer = $("[data-drawer='compose']");
|
||||
this.composeInput = $(".js-compose-text", ".js-docked-compose");
|
||||
|
||||
this.composePanelScroller = $(".js-compose-scroller", ".js-docked-compose").first().children().first();
|
||||
this.composePanelScroller.on("scroll", this.composerScrollEvent);
|
||||
|
||||
$(".emoji-keyboard-popup-btn").on("click", this.emojiKeyboardButtonClickEvent);
|
||||
$(".js-compose-text", ".js-docked-compose").on("focus", this.composeInputFocusEvent);
|
||||
$(document).on("click", this.documentClickEvent);
|
||||
$(document).on("keydown", this.documentKeyEvent);
|
||||
$(document).on("uiComposeImageAdded", this.uploadFilesEvent);
|
||||
this.composeInput.on("keydown", this.composeInputKeyDownEvent);
|
||||
this.composeInput.on("keypress", this.composeInputKeyPressEvent);
|
||||
this.composeInput.on("focus", this.composeInputFocusEvent);
|
||||
this.composeDrawer.on("uiComposeTweetSending", this.composerSendingEvent);
|
||||
|
||||
// HTML generation
|
||||
@@ -429,10 +530,12 @@ disabled(){
|
||||
$(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonClickEvent);
|
||||
$(".emoji-keyboard-popup-btn").remove();
|
||||
|
||||
$(".js-compose-text", ".js-docked-compose").off("focus", this.composeInputFocusEvent);
|
||||
$(document).off("click", this.documentClickEvent);
|
||||
$(document).off("keydown", this.documentKeyEvent);
|
||||
$(document).off("uiComposeImageAdded", this.uploadFilesEvent);
|
||||
this.composeInput.off("keydown", this.composeInputKeyDownEvent);
|
||||
this.composeInput.off("keypress", this.composeInputKeyPressEvent);
|
||||
this.composeInput.off("focus", this.composeInputFocusEvent);
|
||||
this.composeDrawer.off("uiComposeTweetSending", this.composerSendingEvent);
|
||||
|
||||
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
|
||||
|
@@ -8,7 +8,7 @@ Custom reply account
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.2.1
|
||||
1.2.4
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
@@ -20,4 +20,4 @@ configuration.js
|
||||
configuration.default.js
|
||||
|
||||
[requires]
|
||||
1.3.3
|
||||
1.10.3
|
@@ -6,7 +6,7 @@ enabled(){
|
||||
this.lastSelectedAccount = null;
|
||||
|
||||
this.uiComposeTweetEvent = (e, data) => {
|
||||
return if data.type !== "reply" || data.popFromInline || !("element" in data);
|
||||
return if !(data.type === "reply" || (data.type === "tweet" && "quotedTweet" in data)) || data.popFromInline || !("element" in data);
|
||||
|
||||
var query;
|
||||
|
||||
@@ -17,7 +17,7 @@ enabled(){
|
||||
return;
|
||||
}
|
||||
|
||||
var section = data.element.closest("section.column");
|
||||
var section = data.element.closest("section.js-column");
|
||||
|
||||
var column = TD.controller.columnManager.get(section.attr("data-column"));
|
||||
var header = $(".column-title", section);
|
||||
@@ -35,7 +35,7 @@ enabled(){
|
||||
}
|
||||
|
||||
try{
|
||||
query = configuration.customSelector(column.getColumnType(), columnTitle, columnAccount, column);
|
||||
query = configuration.customSelector(column.getColumnType(), columnTitle, columnAccount, column, section.hasClass("column-temp"));
|
||||
}catch(e){
|
||||
$TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector threw an error: "+e.message);
|
||||
return;
|
||||
|
@@ -1,12 +1,4 @@
|
||||
{
|
||||
/*
|
||||
* WARNING
|
||||
* -------
|
||||
*
|
||||
* Make sure you are editing 'configuration.js' and not the default configuration file, as the default one will be replaced with each update.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Simple way of configuring the plugin
|
||||
* ------------------------------------
|
||||
@@ -29,11 +21,14 @@
|
||||
* This assumes a basic knowledge of JavaScript and jQuery.
|
||||
*
|
||||
* 1. Set value of 'useAdvancedSelector' to true
|
||||
* 2. Uncomment the 'customSelector' function, and replace the example code with your desired behavior
|
||||
* 2. Replace the example code in 'customSelector' function with your desired behavior
|
||||
*
|
||||
* The 'customSelector' function should return a string in one of the formats supported by 'defaultAccount'.
|
||||
* If it returns anything else (for example, false or undefined), it falls back to 'defaultAccount' behavior.
|
||||
*
|
||||
* When writing a custom function, you can install Dev Tools to access the browser console:
|
||||
* https://tweetduck.chylex.com/guide/#dev-tools
|
||||
*
|
||||
*
|
||||
* The 'type' parameter is TweetDeck column type. Here is the full list of column types, note that some are
|
||||
* unused and have misleading names (for example, Home columns are 'col_timeline' instead of 'col_home'):
|
||||
@@ -54,28 +49,33 @@
|
||||
* contain other text (for example, the Scheduled column contains the string 'All accounts').
|
||||
*
|
||||
*
|
||||
* The 'column' parameter is a TweetDeck column object. If you want to see all properties of the object,
|
||||
* run the following code in your browser console, which will return an array containing all of your
|
||||
* current column objects in order:
|
||||
* The 'column' parameter is a TweetDeck column object. If you want to see the object structure,
|
||||
* run the following code in the console for an array containing all of your column objects:
|
||||
* TD.controller.columnManager.getAllOrdered()
|
||||
*
|
||||
*
|
||||
* The 'isTemporary' parameter is true if the column is not attached to the main column list,
|
||||
* for example when clicking on a profile and viewing their tweets in a modal dialog.
|
||||
*
|
||||
*/
|
||||
|
||||
useAdvancedSelector: false,
|
||||
|
||||
/*customSelector: function(type, title, account, column){
|
||||
customSelector: function(type, title, account, column, isTemporary){
|
||||
console.info(arguments); // Prints all arguments into the console
|
||||
|
||||
if (type === "col_search" && title === "TweetDuck"){
|
||||
// This is a search column that looks for 'TweetDuck' in the tweets,
|
||||
// search columns are normally linked to the preferred account
|
||||
// so this forces the @TryTweetDuck account to be used instead.
|
||||
// so this forces the @TryTweetDuck account to be used instead
|
||||
return "@TryTweetDuck";
|
||||
}
|
||||
else if (type === "col_timeline" && account === "@chylexcz"){
|
||||
// This is a Home column of my test account @chylexcz,
|
||||
// but I want to reply to tweets from my official account.
|
||||
// but I want to reply to tweets from my official account
|
||||
return "@chylexmc";
|
||||
}
|
||||
|
||||
// otherwise returns 'undefined' which falls back to 'defaultAccount' behavior
|
||||
}*/
|
||||
}
|
||||
}
|
@@ -3,6 +3,18 @@ $ErrorActionPreference = "Stop"
|
||||
|
||||
Set-Location $dir
|
||||
|
||||
function Check-Carriage-Return{
|
||||
Param([Parameter(Mandatory = $True, Position = 1)] $fname)
|
||||
|
||||
$file = @(Get-ChildItem -Include $fname -Recurse)[0]
|
||||
|
||||
if ((Get-Content -Path $file.FullName -Raw).Contains("`r")){
|
||||
Throw "$fname cannot contain carriage return"
|
||||
}
|
||||
|
||||
Write-Host "Verified" $file.FullName.Substring($dir.Length)
|
||||
}
|
||||
|
||||
function Rewrite-File{
|
||||
[CmdletBinding()]
|
||||
Param([Parameter(Mandatory = $True, ValueFromPipeline = $True)][array] $lines, [Parameter(Mandatory = $True, Position = 1)] $file)
|
||||
@@ -11,13 +23,31 @@ function Rewrite-File{
|
||||
Write-Host "Processed" $file.FullName.Substring($dir.Length)
|
||||
}
|
||||
|
||||
ForEach($file in Get-ChildItem -Include *.js -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
$lines = ($lines | % { $_.TrimStart() }) -Replace '^(.*?)((?<=^|[;{}()])\s?//(?:\s.*|$))?$', '$1' -Replace '(?<!\w)return(\s.*?)? if (.*?);', 'if ($2)return$1;'
|
||||
,$lines | Rewrite-File $file
|
||||
}
|
||||
try{
|
||||
Check-Carriage-Return("emoji-ordering.txt")
|
||||
|
||||
ForEach($file in Get-ChildItem -Include *.html -Recurse){
|
||||
ForEach($file in Get-ChildItem -Filter *.js -Exclude configuration.default.js -Recurse){
|
||||
$lines = Get-Content -Path $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
|
||||
}
|
||||
|
||||
ForEach($file in Get-ChildItem -Filter *.css -Recurse){
|
||||
$lines = Get-Content -Path $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
|
||||
}
|
||||
|
||||
ForEach($file in Get-ChildItem -Filter *.html -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
,($lines | % { $_.TrimStart() }) | Rewrite-File $file
|
||||
}
|
||||
}catch{
|
||||
Write-Host "Encountered an error while running PostBuild.ps1 on line" $_.InvocationInfo.ScriptLineNumber
|
||||
Write-Host $_
|
||||
Exit 1
|
||||
}
|
||||
|
@@ -14,6 +14,11 @@
|
||||
//
|
||||
var onAppReady = [];
|
||||
|
||||
//
|
||||
// Variable: DOM HTML element.
|
||||
//
|
||||
var doc = document.documentElement;
|
||||
|
||||
//
|
||||
// Variable: DOM object containing the main app element.
|
||||
//
|
||||
@@ -258,47 +263,62 @@
|
||||
})();
|
||||
|
||||
//
|
||||
// Function: Retrieves the tags to be put into <head> for notification HTML code.
|
||||
// Block: Hook into settings object to detect when the settings change, and update html attributes and notification layout.
|
||||
//
|
||||
var getNotificationHeadContents = function(){
|
||||
let tags = [];
|
||||
(function(){
|
||||
let refreshSettings = function(){
|
||||
let fontSizeName = TD.settings.getFontSize();
|
||||
let themeName = TD.settings.getTheme();
|
||||
|
||||
$(document.head).children("link[rel='stylesheet']:not([title]),link[title='"+TD.settings.getTheme()+"'],meta[charset],meta[http-equiv]").each(function(){
|
||||
let tags = [
|
||||
`<html class='os-windows ${(doc.getAttribute("class").match(/txt-\S+/) || [ "txt-size--14" ])[0]}' data-td-font='${fontSizeName}' data-td-theme='${themeName}'><head>`
|
||||
];
|
||||
|
||||
$(document.head).children("link[rel='stylesheet']:not([title]),link[title='"+themeName+"'],meta[charset],meta[http-equiv]").each(function(){
|
||||
tags.push($(this)[0].outerHTML);
|
||||
});
|
||||
|
||||
tags.push("<style type='text/css'>");
|
||||
tags.push("body { background: "+getClassStyleProperty("column", "background-color")+" }"); // set background color
|
||||
tags.push("a[data-full-url] { word-break: break-all }"); // break long urls
|
||||
tags.push(".txt-base-smallest .badge-verified:before { width: 13px !important; height: 13px !important; background-position: -223px -98px !important; }"); // fix cut off badge icon
|
||||
tags.push(".media-item, .media-preview { border-radius: 1px !important }"); // square-ify media
|
||||
tags.push(".quoted-tweet { border-radius: 0 !important }"); // square-ify quoted tweets
|
||||
tags.push(".activity-header { align-items: center !important; margin-bottom: 4px }"); // tweak alignment of avatar and text in notifications
|
||||
tags.push(".activity-header .tweet-timestamp { line-height: unset }"); // fix timestamp position in notifications
|
||||
|
||||
if (fontSizeName === "smallest"){
|
||||
tags.push(".badge-verified:before { width: 13px !important; height: 13px !important; background-position: -223px -98px !important }"); // fix cut off badge icon
|
||||
}
|
||||
|
||||
tags.push("</style>");
|
||||
|
||||
return tags.join("");
|
||||
doc.setAttribute("data-td-font", fontSizeName);
|
||||
doc.setAttribute("data-td-theme", themeName);
|
||||
$TD.loadNotificationLayout(fontSizeName, tags.join(""));
|
||||
};
|
||||
|
||||
//
|
||||
// Block: Hook into settings object to detect when the settings change.
|
||||
//
|
||||
TD.settings.setFontSize = appendToFunction(TD.settings.setFontSize, function(name){
|
||||
$TD.loadFontSizeClass(name);
|
||||
setTimeout(refreshSettings, 0);
|
||||
});
|
||||
|
||||
TD.settings.setTheme = appendToFunction(TD.settings.setTheme, function(name){
|
||||
document.documentElement.setAttribute("data-td-theme", name);
|
||||
|
||||
setTimeout(function(){
|
||||
$TD.loadNotificationHeadContents(getNotificationHeadContents());
|
||||
}, 0);
|
||||
setTimeout(refreshSettings, 0);
|
||||
});
|
||||
|
||||
onAppReady.push(function(){
|
||||
document.documentElement.setAttribute("data-td-theme", TD.settings.getTheme());
|
||||
onAppReady.push(refreshSettings);
|
||||
})();
|
||||
|
||||
$TD.loadFontSizeClass(TD.settings.getFontSize());
|
||||
$TD.loadNotificationHeadContents(getNotificationHeadContents());
|
||||
});
|
||||
//
|
||||
// Block: Fix OS name.
|
||||
//
|
||||
if (ensurePropertyExists(TD, "util", "getOSName")){
|
||||
TD.util.getOSName = function(){
|
||||
return "windows";
|
||||
};
|
||||
|
||||
doc.classList.remove("os-");
|
||||
doc.classList.add("os-windows");
|
||||
}
|
||||
|
||||
//
|
||||
// Block: Enable popup notifications.
|
||||
@@ -328,9 +348,8 @@
|
||||
let menu = $(".js-dropdown-content").children("ul").first();
|
||||
return if menu.length === 0;
|
||||
|
||||
menu.children(".drp-h-divider").last().before('<li class="is-selectable" data-std><a href="#" data-action="tweetduck">TweetDuck</a></li>');
|
||||
|
||||
let button = menu.children("[data-std]");
|
||||
let button = $('<li class="is-selectable" data-tweetduck><a href="#" data-action>TweetDuck</a></li>');
|
||||
button.insertBefore(menu.children(".drp-h-divider").last());
|
||||
|
||||
button.on("click", "a", function(){
|
||||
$TD.openContextMenu();
|
||||
@@ -349,10 +368,6 @@
|
||||
// Block: Expand shortened links on hover or display tooltip.
|
||||
//
|
||||
(function(){
|
||||
var cutStart = function(str, search){
|
||||
return str.startsWith(search) ? str.substr(search.length) : str;
|
||||
};
|
||||
|
||||
var prevMouseX = -1, prevMouseY = -1;
|
||||
var tooltipTimer, tooltipDisplayed;
|
||||
|
||||
@@ -364,13 +379,8 @@
|
||||
|
||||
if ($TDX.expandLinksOnHover){
|
||||
tooltipTimer = window.setTimeout(function(){
|
||||
let expanded = me.attr("data-full-url");
|
||||
expanded = cutStart(expanded, "https://");
|
||||
expanded = cutStart(expanded, "http://");
|
||||
expanded = cutStart(expanded, "www.");
|
||||
|
||||
me.attr("td-prev-text", text);
|
||||
me.text(expanded);
|
||||
me.text(me.attr("data-full-url").replace(/^https?:\/\/(www\.)?/, ""));
|
||||
}, 200);
|
||||
}
|
||||
else{
|
||||
@@ -503,8 +513,8 @@
|
||||
}
|
||||
};
|
||||
|
||||
var processMedia = function(media){
|
||||
return media.filter(item => !item.isAnimatedGif).map(item => item.entity.media_url_https+":small").join(";");
|
||||
var processMedia = function(chirp){
|
||||
return chirp.getMedia().filter(item => !item.isAnimatedGif).map(item => item.entity.media_url_https+":small").join(";");
|
||||
};
|
||||
|
||||
app.delegate("section.js-column", {
|
||||
@@ -531,7 +541,7 @@
|
||||
let tweetUrl = tweet.getChirpURL();
|
||||
let quoteUrl = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
|
||||
let authors = tweet.quotedTweet ? [ tweet.getMainUser().screenName, tweet.quotedTweet.getMainUser().screenName ].join(";") : tweet.getMainUser().screenName;
|
||||
let imageList = tweet.quotedTweet ? processMedia(tweet.quotedTweet.getMedia()) : tweet.hasImage() ? processMedia(tweet.getMedia()) : "";
|
||||
let imageList = tweet.quotedTweet && tweet.quotedTweet.hasImage() ? processMedia(tweet.quotedTweet) : tweet.hasImage() ? processMedia(tweet) : "";
|
||||
|
||||
updateHighlightedTweet(me, tweet, tweetUrl || "", quoteUrl || "", authors, imageList);
|
||||
}
|
||||
@@ -588,7 +598,7 @@
|
||||
setImportantProperty(selectedTweet.find(".js-media-preview-container"), "margin-bottom", "4px");
|
||||
selectedTweet.find(".js-translate-call-to-action").first().remove();
|
||||
selectedTweet.find(".js-tweet").first().nextAll().remove();
|
||||
selectedTweet.find("footer").last().prevUntil(":not(.txt-mute.txt-small)").addBack().remove(); // footer, date, location
|
||||
selectedTweet.find("footer").last().prevUntil(":not(.txt-mute)").addBack().remove(); // footer, date, location
|
||||
}
|
||||
else{
|
||||
setImportantProperty(selectedTweet.find(".js-media-preview-container"), "margin-bottom", "10px");
|
||||
@@ -597,6 +607,7 @@
|
||||
|
||||
if (isReply){
|
||||
selectedTweet.find(".is-conversation").removeClass("is-conversation");
|
||||
selectedTweet.find(".thread").remove();
|
||||
}
|
||||
|
||||
selectedTweet.find(".js-poll-link").remove();
|
||||
@@ -691,7 +702,7 @@
|
||||
let isDraggingValid = false;
|
||||
|
||||
window.TDGF_onGlobalDragStart = function(type, data){
|
||||
isDraggingValid = type === "link" && tweetRegex.test(data);
|
||||
isDraggingValid = (type === "link" || type === "text") && tweetRegex.test(data);
|
||||
};
|
||||
|
||||
app.delegate("section.js-column", {
|
||||
@@ -803,23 +814,63 @@
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Make middle click on tweet reply icon open the compose drawer.
|
||||
// Block: Make middle click on tweet reply icon open the compose drawer, retweet icon trigger a quote, and favorite icon open a 'Like from accounts...' modal.
|
||||
//
|
||||
app.delegate(".js-reply-action", "mousedown", function(e){
|
||||
if (e.which === 2){
|
||||
if ($("[data-drawer='compose']").hasClass("is-hidden")){
|
||||
$(document).trigger("uiDrawerShowDrawer", {
|
||||
drawer: "compose",
|
||||
withAnimation: true
|
||||
app.delegate(".tweet-action,.tweet-detail-action", "auxclick", function(e){
|
||||
return if e.which !== 2;
|
||||
|
||||
let column = TD.controller.columnManager.get($(this).closest("section.js-column").attr("data-column"));
|
||||
return if !column;
|
||||
|
||||
let ele = $(this).closest("article");
|
||||
let tweet = column.findChirp(ele.attr("data-tweet-id")) || column.findChirp(ele.attr("data-key"));
|
||||
return if !tweet;
|
||||
|
||||
switch($(this).attr("rel")){
|
||||
case "reply":
|
||||
let main = tweet.getMainTweet();
|
||||
|
||||
$(document).trigger("uiDockedComposeTweet", {
|
||||
type: "reply",
|
||||
from: [ tweet.account.getKey() ],
|
||||
inReplyTo: {
|
||||
id: tweet.id,
|
||||
htmlText: main.htmlText,
|
||||
user: {
|
||||
screenName: main.user.screenName,
|
||||
name: main.user.name,
|
||||
profileImageURL: main.user.profileImageURL
|
||||
}
|
||||
},
|
||||
mentions: tweet.getReplyUsers(),
|
||||
element: ele
|
||||
});
|
||||
|
||||
window.setTimeout(() => $(this).trigger("click"), 1);
|
||||
break;
|
||||
|
||||
case "favorite":
|
||||
$(document).trigger("uiShowFavoriteFromOptions", { tweet });
|
||||
break;
|
||||
|
||||
case "retweet":
|
||||
TD.controller.stats.quoteTweet();
|
||||
|
||||
$(document).trigger("uiComposeTweet", {
|
||||
type: "tweet",
|
||||
from: [ tweet.account.getKey() ],
|
||||
quotedTweet: tweet.getMainTweet(),
|
||||
element: ele // triggers reply-account plugin
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
@@ -833,74 +884,24 @@
|
||||
// Block: Inject custom CSS and layout into the page.
|
||||
//
|
||||
(function(){
|
||||
let styleOfficial = document.createElement("style");
|
||||
document.head.appendChild(styleOfficial);
|
||||
|
||||
let addRule = (rule) => {
|
||||
styleOfficial.sheet.insertRule(rule, 0);
|
||||
var createStyle = function(id, styles){
|
||||
let ele = document.createElement("style");
|
||||
ele.id = id;
|
||||
ele.innerText = styles;
|
||||
document.head.appendChild(ele);
|
||||
};
|
||||
|
||||
addRule("a[data-full-url] { word-break: break-all; }"); // break long urls
|
||||
addRule(".keyboard-shortcut-list { vertical-align: top; }"); // fix keyboard navigation alignment
|
||||
addRule(".account-inline .username { vertical-align: 10%; }"); // move usernames a bit higher
|
||||
addRule(".character-count-compose { width: 40px !important; }"); // fix strangely wide character count element
|
||||
addRule(".is-video a:not([href*='youtu']) .icon-bg-dot, .is-gif .icon-bg-dot { color: #9f51cf; }"); // change play icon on mp4s
|
||||
|
||||
addRule(".column-nav-link .attribution { position: absolute; }"); // fix cut off account names
|
||||
addRule(".txt-base-smallest .sprite-verified-mini { width: 13px !important; height: 13px !important; background-position: -223px -99px !important; }"); // fix cut off badge icon when zoomed in
|
||||
addRule(".txt-base-smallest .badge-verified:before { width: 13px !important; height: 13px !important; background-position: -223px -98px !important; }"); // fix cut off badge icon in notifications
|
||||
|
||||
addRule(".btn, .mdl, .mdl-content, .app-search-fake, .app-search-input, .popover, .lst-modal, .media-item, .media-preview, .tooltip-inner { border-radius: 1px !important; }"); // square-ify buttons, dialogs, menus, media previews
|
||||
addRule(".compose-text-container, .dropdown-menu, .list-item-last, .quoted-tweet, .input-group-button, input, textarea, select { border-radius: 0 !important; }"); // square-ify dropdowns, inputs, quoted tweets, and account selectors
|
||||
addRule(".prf-header { border-radius: 0; }"); // fix user account header border
|
||||
|
||||
addRule(".accs li, .accs img { border-radius: 0 !important; }"); // square-ify retweet account selector
|
||||
addRule(".accs-header { padding-left: 0 !important; }"); // fix retweet account selector heading
|
||||
|
||||
addRule(".scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb, .antiscroll-scrollbar { border-radius: 0; }"); // square-ify scroll bars
|
||||
addRule(".antiscroll-scrollbar-vertical { margin-top: 0; }"); // square-ify scroll bars
|
||||
addRule(".antiscroll-scrollbar-horizontal { margin-left: 0; }"); // square-ify scroll bars
|
||||
addRule(".scroll-styled-v:not(.antiscroll-inner)::-webkit-scrollbar { width: 8px; }"); // square-ify scroll bars
|
||||
addRule(".scroll-styled-h:not(.antiscroll-inner)::-webkit-scrollbar { height: 8px; }"); // square-ify scroll bars
|
||||
addRule(".app-columns-container::-webkit-scrollbar { height: 9px !important; }"); // square-ify scroll bars
|
||||
|
||||
addRule(".is-condensed .app-header-inner { padding-top: 10px !important; }"); // add extra padding to menu buttons when condensed
|
||||
addRule(".is-condensed .btn-compose { padding: 8px !important; }"); // fix compose button icon when condensed
|
||||
addRule(".app-header:not(.is-condensed) .nav-user-info { padding: 0 5px; }"); // add padding to user info
|
||||
|
||||
addRule(".app-title { display: none; }"); // hide TweetDeck logo
|
||||
addRule(".nav-user-info { bottom: 10px !important; }"); // move user info
|
||||
addRule(".app-navigator { bottom: 50px !important; }"); // move navigation
|
||||
addRule(".column-navigator-overflow { bottom: 192px !important; }"); // move column list
|
||||
addRule(".app-navigator .tooltip { display: none !important; }"); // hide broken tooltips in the menu
|
||||
|
||||
addRule(".column .column-header { height: 49px !important; }"); // fix one pixel space below column header
|
||||
addRule(".column:not(.is-options-open) .column-header { border-bottom: none; }"); // fix one pixel space below column header
|
||||
addRule(".is-options-open .column-type-icon { bottom: 27px; }"); // fix one pixel space below column header
|
||||
|
||||
addRule(".activity-header { align-items: center !important; margin-bottom: 4px; }"); // tweak alignment of avatar and text in notifications
|
||||
addRule(".activity-header .tweet-timestamp { line-height: unset; }"); // fix timestamp position in notifications
|
||||
addRule(".account-bio.padding-t--5 { padding-top: 2px !important; }"); // decrease padding on follow notifications
|
||||
|
||||
addRule("html[data-td-theme='light'] .stream-item:not(:hover) .js-user-actions-menu { color: #000; border-color: #000; opacity: 0.25; }"); // make follow notification button nicer
|
||||
addRule("html[data-td-theme='dark'] .stream-item:not(:hover) .js-user-actions-menu { color: #fff; border-color: #fff; opacity: 0.25; }"); // make follow notification button nicer
|
||||
|
||||
addRule(".app-columns-container::-webkit-scrollbar-track { border-left: 0; }"); // remove weird border in the column container scrollbar
|
||||
addRule(".app-columns-container { bottom: 0 !important; }"); // move column container scrollbar to bottom to fit updated style
|
||||
|
||||
addRule(".js-column-header .column-header-link { padding: 0; }"); // fix column header tooltip hover box
|
||||
addRule(".js-column-header .column-header-link .icon { padding: 9px 4px; width: calc(1em + 8px); height: 100%; box-sizing: border-box; }"); // fix column header tooltip hover box
|
||||
|
||||
addRule("#td-compose-drawer-pin { margin: 17px 4px 0 0; transition: transform 0.1s ease; fill: #fff; float: right; cursor: pointer; }"); // replace 'stay open' checkbox with a pin icon
|
||||
addRule(".js-docked-compose footer { display: none; }"); // replace 'stay open' checkbox with a pin icon
|
||||
addRule(".compose-content { bottom: 0 !important; }"); // replace 'stay open' checkbox with a pin icon
|
||||
addRule(".js-docked-compose .js-drawer-close { margin: 20px 0 0 !important; }"); // fix close drawer button because twitter is fucking incompetent
|
||||
window.TDGF_injectBrowserCSS = function(styles){
|
||||
if (!document.getElementById("tweetduck-browser-css")){
|
||||
createStyle("tweetduck-browser-css", styles);
|
||||
}
|
||||
};
|
||||
|
||||
window.TDGF_reinjectCustomCSS = function(styles){
|
||||
$("#tweetduck-custom-css").remove();
|
||||
|
||||
if (styles && styles.length){
|
||||
$(document.head).append("<style type='text/css' id='tweetduck-custom-css'>"+styles+"</style>");
|
||||
createStyle("tweetduck-custom-css", styles);
|
||||
}
|
||||
};
|
||||
})();
|
||||
@@ -927,27 +928,50 @@
|
||||
// Block: Setup video player hooks.
|
||||
//
|
||||
(function(){
|
||||
var playVideo = function(url){
|
||||
window.TDGF_playVideo = function(url, username){
|
||||
$('<div id="td-video-player-overlay" class="ovl" style="display:block"></div>').on("click contextmenu", function(){
|
||||
$TD.playVideo(null);
|
||||
$TD.playVideo(null, null);
|
||||
}).appendTo(app);
|
||||
|
||||
$TD.playVideo(url);
|
||||
$TD.playVideo(url, username || null);
|
||||
};
|
||||
|
||||
app.delegate(".js-gif-play", "click", function(e){
|
||||
var getVideoTweetLink = function(obj){
|
||||
let parent = obj.closest(".js-tweet").first();
|
||||
let link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
|
||||
return link.attr("href");
|
||||
}
|
||||
|
||||
var getUsername = function(tweet){
|
||||
return tweet && (tweet.quotedTweet || tweet).getMainUser().screenName;
|
||||
}
|
||||
|
||||
app.delegate(".js-gif-play", {
|
||||
click: function(e){
|
||||
let src = !e.ctrlKey && $(this).closest(".js-media-gif-container").find("video").attr("src");
|
||||
|
||||
if (src){
|
||||
playVideo(src);
|
||||
window.TDGF_playVideo(src, getUsername(highlightedTweetObj));
|
||||
}
|
||||
else{
|
||||
let parent = $(e.target).closest(".js-tweet").first();
|
||||
let link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
|
||||
$TD.openBrowser(link.attr("href"));
|
||||
$TD.openBrowser(getVideoTweetLink($(this)));
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
},
|
||||
|
||||
mousedown: function(e){
|
||||
if (e.button === 1){
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
mouseup: function(e){
|
||||
if (e.button === 1){
|
||||
$TD.openBrowser(getVideoTweetLink($(this)));
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TD.mustaches["status/media_thumb.mustache"] = TD.mustaches["status/media_thumb.mustache"].replace("is-gif", "is-gif is-paused");
|
||||
@@ -963,7 +987,7 @@
|
||||
let media = this.chirp.getMedia().find(media => media.mediaId === this.clickedMediaEntityId);
|
||||
|
||||
if (media && media.isVideo && media.service === "twitter"){
|
||||
playVideo(media.chooseVideoVariant().url);
|
||||
window.TDGF_playVideo(media.chooseVideoVariant().url, getUsername(this.chirp));
|
||||
cancelModal = true;
|
||||
}
|
||||
});
|
||||
@@ -1120,31 +1144,34 @@
|
||||
});
|
||||
};
|
||||
|
||||
(function(){
|
||||
var lastActivity = Date.now();
|
||||
|
||||
$(document).click(function(e){
|
||||
lastActivity = Date.now();
|
||||
});
|
||||
|
||||
window.TDGF_tryRunCleanup = function(){
|
||||
// no recent activity
|
||||
return false if Date.now()-lastActivity < 15e3;
|
||||
|
||||
// no modals are visible
|
||||
return false if $("#open-modal").is(":visible") || !$(".js-modals-container").is(":empty");
|
||||
return false if $(".js-modal").is(":visible") || !$(".js-modals-container").is(":empty");
|
||||
|
||||
// all columns are in a default state
|
||||
return false if $("section.js-column").is(".is-shifted-1,.is-shifted-2");
|
||||
|
||||
// all textareas are empty
|
||||
if ($("textarea").is(function(){
|
||||
return $(this).val().length > 0;
|
||||
})){
|
||||
return false;
|
||||
}
|
||||
return false if Array.prototype.some.call(document.getElementsByTagName("textarea"), ele => ele.value.length > 0);
|
||||
|
||||
// all columns are scrolled to top
|
||||
if ($(".js-column-scroller").is(function(){
|
||||
return $(this).scrollTop() > 0;
|
||||
})){
|
||||
return false;
|
||||
}
|
||||
return false if Array.prototype.some.call(document.getElementsByClassName("js-column-scroller"), ele => ele.scrollTop > 0);
|
||||
|
||||
// cleanup
|
||||
window.TDGF_reload();
|
||||
return true;
|
||||
};
|
||||
})();
|
||||
|
||||
if (window.TD_SESSION && window.TD_SESSION.gc){
|
||||
var state;
|
||||
|
97
Resources/Scripts/introduction.js
Normal file
@@ -0,0 +1,97 @@
|
||||
(function($, $TD){
|
||||
$(document).one("TD.ready", function(){
|
||||
let css = $(`
|
||||
<style>
|
||||
#td-introduction-modal {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#td-introduction-modal .mdl {
|
||||
width: 90%;
|
||||
max-width: 800px;
|
||||
height: 372px;
|
||||
}
|
||||
|
||||
#td-introduction-modal .mdl-header-title {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#td-introduction-modal .mdl-content {
|
||||
padding: 4px 16px 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#td-introduction-modal p {
|
||||
margin: 12px 0;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
#td-introduction-modal p strong {
|
||||
font-weight: normal;
|
||||
text-shadow: 0 0 #000;
|
||||
}
|
||||
|
||||
#td-introduction-modal footer {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
#td-introduction-modal button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
#td-introduction-modal .anondata {
|
||||
float: left;
|
||||
margin: 5px 7px;
|
||||
}
|
||||
|
||||
#td-introduction-modal .anondata input {
|
||||
vertical-align: -10%;
|
||||
}
|
||||
|
||||
#td-introduction-modal .anondata label {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>`).appendTo(document.head);
|
||||
|
||||
let ele = $(`
|
||||
<div id="td-introduction-modal" class="ovl">
|
||||
<div class="mdl is-inverted-dark">
|
||||
<header class="mdl-header">
|
||||
<h3 class="mdl-header-title">Welcome to TweetDuck</h3>
|
||||
<a href="#" class="mdl-dismiss link-normal-dark"><i class="icon icon-close"></i></a>
|
||||
</header>
|
||||
<div class="mdl-inner">
|
||||
<div class="mdl-content">
|
||||
<p>Thank you for downloading TweetDuck!</p>
|
||||
<p><strong>Right-click anywhere</strong> or click <strong>Settings – TweetDuck</strong> in the left panel to open the main menu, where you can access the <strong>Options</strong>, <strong>Plugins</strong>, and more.</p>
|
||||
<p>You can also right-click links, media, tweets, notifications, etc. to access their context menu.</p>
|
||||
<p>If you're using TweetDuck for the first time, check out the <strong>guide</strong> that answers common questions and showcases important features. You can open the main menu, select <strong>About TweetDuck</strong>, and click the help button to view the guide later.</p>
|
||||
<p>Before you continue, please consider helping development by allowing TweetDuck to send anonymous data in the future. You can always disable it in <strong>Options – Feedback</strong>.</p>
|
||||
</div>
|
||||
<footer class="txt-right">
|
||||
<div class="anondata">
|
||||
<input id="td-anonymous-data" type="checkbox" checked>
|
||||
<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>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>`).appendTo(".js-app");
|
||||
|
||||
ele.find("button, a.mdl-dismiss").click(function(){
|
||||
let showGuide = $(this)[0].hasAttribute("data-guide");
|
||||
let allowDataCollection = $("#td-anonymous-data").is(":checked");
|
||||
|
||||
ele.fadeOut(200, function(){
|
||||
$TD.onIntroductionClosed(showGuide, allowDataCollection);
|
||||
ele.remove();
|
||||
css.remove();
|
||||
});
|
||||
});
|
||||
});
|
||||
})($, $TD);
|
@@ -42,10 +42,6 @@
|
||||
// Block: Expand shortened links on hover or display tooltip.
|
||||
//
|
||||
(function(){
|
||||
var cutStart = function(str, search){
|
||||
return str.startsWith(search) ? str.substr(search.length) : str;
|
||||
};
|
||||
|
||||
var prevMouseX = -1, prevMouseY = -1;
|
||||
var tooltipTimer, tooltipDisplayed;
|
||||
|
||||
@@ -60,13 +56,8 @@
|
||||
|
||||
if ($TDX.expandLinksOnHover){
|
||||
tooltipTimer = window.setTimeout(function(){
|
||||
var expanded = url;
|
||||
expanded = cutStart(expanded, "https://");
|
||||
expanded = cutStart(expanded, "http://");
|
||||
expanded = cutStart(expanded, "www.");
|
||||
|
||||
me.setAttribute("td-prev-text", text);
|
||||
me.innerHTML = expanded;
|
||||
me.innerHTML = url.replace(/^https?:\/\/(www\.)?/, "");
|
||||
}, 200);
|
||||
}
|
||||
else{
|
||||
@@ -112,11 +103,11 @@
|
||||
// Block: Setup a skip button.
|
||||
//
|
||||
if (!document.body.hasAttribute("td-example-notification")){
|
||||
document.body.insertAdjacentHTML("afterbegin", [
|
||||
'<svg id="td-skip" xmlns="http://www.w3.org/2000/svg" width="10" height="17" viewBox="0 0 350 600" style="position:fixed;left:30px;bottom:10px;z-index:1000">',
|
||||
'<path fill="#888" d="M0,151.656l102.208-102.22l247.777,247.775L102.208,544.986L0,442.758l145.546-145.547">',
|
||||
'</svg>'
|
||||
].join(""));
|
||||
document.body.insertAdjacentHTML("afterbegin", `
|
||||
<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>
|
||||
`);
|
||||
|
||||
document.getElementById("td-skip").addEventListener("click", function(){
|
||||
$TD.loadNextNotification();
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<div class="js-tweet tweet">
|
||||
<header class="tweet-header">
|
||||
<time class="tweet-timestamp js-timestamp pull-right txt-mute">
|
||||
<a target="_blank" rel="url" href="https://twitter.com/chylexmc" class="txt-small">0s</a>
|
||||
<a target="_blank" rel="url" href="https://twitter.com/chylexmc" class="txt-size-variable--12">0s</a>
|
||||
</time>
|
||||
<a target="_blank" rel="user" href="https://twitter.com/chylexmc" class="account-link link-complex block">
|
||||
<div class="obj-left item-img tweet-img">
|
||||
|
@@ -87,14 +87,14 @@
|
||||
};
|
||||
|
||||
//
|
||||
// Block: Setup global function to change plugin state.
|
||||
// Block: Setup a function to change plugin state.
|
||||
//
|
||||
window.TDPF_setPluginState = function(identifier, enable){
|
||||
window.TD_PLUGINS.setState(window.TD_PLUGINS.findObject(identifier), enable);
|
||||
};
|
||||
|
||||
//
|
||||
// Block: Setup global function to reload the page.
|
||||
// Block: Setup a function to reload the page.
|
||||
//
|
||||
window.TDPF_requestReload = function(){
|
||||
if (!isReloading){
|
||||
@@ -102,4 +102,9 @@
|
||||
isReloading = true;
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Block: Setup a function to allow plugins to play video.
|
||||
//
|
||||
window.TDPF_playVideo = window.TDGF_playVideo;
|
||||
})();
|
||||
|
263
Resources/Scripts/styles/browser.css
Normal file
@@ -0,0 +1,263 @@
|
||||
/***********************/
|
||||
/* Redesign scrollbars */
|
||||
/***********************/
|
||||
|
||||
.scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb, .antiscroll-scrollbar {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.antiscroll-scrollbar-vertical {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.antiscroll-scrollbar-horizontal {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.scroll-styled-v:not(.antiscroll-inner)::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.scroll-styled-h:not(.antiscroll-inner)::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.app-columns-container::-webkit-scrollbar {
|
||||
height: 9px !important;
|
||||
}
|
||||
|
||||
.app-columns-container::-webkit-scrollbar-track {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.app-columns-container {
|
||||
bottom: 0 !important;
|
||||
}
|
||||
|
||||
/********************/
|
||||
/* Square-ify stuff */
|
||||
/********************/
|
||||
|
||||
.btn, .mdl, .mdl-content, .app-search-fake, .app-search-input, .popover, .lst-modal, .tooltip-inner {
|
||||
border-radius: 1px !important;
|
||||
}
|
||||
|
||||
.media-item, .media-preview, .media-image, .js-media-added .br--4 {
|
||||
border-radius: 1px !important;
|
||||
}
|
||||
|
||||
.compose-text-container, .compose-reply-tweet, .compose-message-recipient-input-container, .compose-message-recipient, .compose-media-bar-holder, .media-grid-container, .js-quote-tweet-holder {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.dropdown-menu, .list-item-last, .quoted-tweet, .input-group-button, input, textarea, select, .prf-header, .accs li, .accs img {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/***********************/
|
||||
/* Hide TweetDeck logo */
|
||||
/***********************/
|
||||
|
||||
.app-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-user-info {
|
||||
bottom: 10px !important;
|
||||
}
|
||||
|
||||
.app-navigator {
|
||||
bottom: 50px !important;
|
||||
}
|
||||
|
||||
.column-navigator-overflow {
|
||||
bottom: 192px !important;
|
||||
}
|
||||
|
||||
/*************************************/
|
||||
/* Tweak collapsed left panel layout */
|
||||
/*************************************/
|
||||
|
||||
.is-condensed .app-header-inner {
|
||||
padding-top: 10px !important;
|
||||
}
|
||||
|
||||
.is-condensed .btn-compose {
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
.app-header:not(.is-condensed) .nav-user-info {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
/****************************************/
|
||||
/* Tweak notification layout and design */
|
||||
/****************************************/
|
||||
|
||||
.activity-header {
|
||||
align-items: center !important;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.activity-header .tweet-timestamp {
|
||||
line-height: unset;
|
||||
}
|
||||
|
||||
.account-bio.padding-t--5 {
|
||||
/* follow notification padding */
|
||||
padding-top: 2px !important;
|
||||
}
|
||||
|
||||
html[data-td-theme='light'] .stream-item:not(:hover) .js-user-actions-menu {
|
||||
color: #000;
|
||||
border-color: #000;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
html[data-td-theme='dark'] .stream-item:not(:hover) .js-user-actions-menu {
|
||||
color: #fff;
|
||||
border-color: #fff;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
/***********************/
|
||||
/* Tweaks for features */
|
||||
/***********************/
|
||||
|
||||
a[data-full-url] {
|
||||
/* break long urls */
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.character-count-compose {
|
||||
/* fix strangely wide character count element */
|
||||
width: 40px !important;
|
||||
}
|
||||
|
||||
.is-video a:not([href*='youtu']) .icon-bg-dot, .is-gif .icon-bg-dot {
|
||||
/* change play icon on mp4s */
|
||||
color: #9f51cf;
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
/* Replace 'Stay open' with a pin icon */
|
||||
/***************************************/
|
||||
|
||||
#td-compose-drawer-pin {
|
||||
margin: 17px 4px 0 0;
|
||||
float: right;
|
||||
fill: #fff;
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
.js-docked-compose footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.compose-content {
|
||||
bottom: 0 !important;
|
||||
}
|
||||
|
||||
/*******************************************/
|
||||
/* Fix general visual issues or annoyances */
|
||||
/*******************************************/
|
||||
|
||||
.account-inline .username {
|
||||
/* move usernames a bit higher */
|
||||
vertical-align: 10%;
|
||||
}
|
||||
|
||||
html[data-td-font='smallest'] .sprite-verified-mini {
|
||||
/* fix cut off badge when zoomed in */
|
||||
width: 13px !important;
|
||||
height: 13px !important;
|
||||
background-position: -223px -99px !important;
|
||||
}
|
||||
|
||||
html[data-td-font='smallest'] .badge-verified:before {
|
||||
/* fix cut off badge in notifications */
|
||||
width: 13px !important;
|
||||
height: 13px !important;
|
||||
background-position: -223px -98px !important;
|
||||
}
|
||||
|
||||
.accs-header {
|
||||
/* fix retweet account selector heading */
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
.keyboard-shortcut-list {
|
||||
/* fix keyboard navigation alignment */
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.tweet-detail-wrapper .js-media-gif-container {
|
||||
/* GIFs in detail view don't trigger the pointer cursor */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.is-inverted-dark .inline-reply .btn:hover {
|
||||
/* Reply buttons in modals are bork */
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/***************************************************************/
|
||||
/* Fix glaring visual issues that twitter hasn't fixed yet smh */
|
||||
/***************************************************************/
|
||||
|
||||
.column-nav-link .attribution {
|
||||
/* fix cut off account names */
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.js-docked-compose .js-drawer-close {
|
||||
/* fix close drawer button position */
|
||||
margin: 20px 0 0 !important;
|
||||
}
|
||||
|
||||
.compose-reply-tweet-remove {
|
||||
/* fix close reply button position; this would make sense if the reply account name became full size, but it still cuts off where the button used to be */
|
||||
top: 3px !important;
|
||||
right: -2px !important;
|
||||
}
|
||||
|
||||
#account-safeguard-checkbox, #inline-account-safeguard-checkbox {
|
||||
/* fix "Ready to Tweet/send" alignment with the checkbox */
|
||||
vertical-align: -15%;
|
||||
}
|
||||
|
||||
/************************************************/
|
||||
/* Fix tooltips in navigation and column header */
|
||||
/************************************************/
|
||||
|
||||
.app-navigator .tooltip {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.js-column-header .column-header-link {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.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 */
|
||||
/*******************************************/
|
||||
|
||||
.column .column-header {
|
||||
height: 49px !important;
|
||||
}
|
||||
|
||||
.column:not(.is-options-open) .column-header {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.is-options-open .column-type-icon {
|
||||
bottom: 27px;
|
||||
}
|
57
Resources/Scripts/styles/notification.css
Normal file
@@ -0,0 +1,57 @@
|
||||
/* General */
|
||||
|
||||
body:before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.scroll-styled-v::-webkit-scrollbar {
|
||||
width: 7px;
|
||||
}
|
||||
|
||||
.scroll-styled-v::-webkit-scrollbar-thumb {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.scroll-styled-v::-webkit-scrollbar-track {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
/* Media */
|
||||
|
||||
.media-size-medium {
|
||||
max-height: 240px;
|
||||
height: calc(100vh - 16px) !important;
|
||||
border-radius: 1px !important;
|
||||
}
|
||||
|
||||
.js-quote-detail .media-size-medium {
|
||||
height: calc(100vh - 28px) !important;
|
||||
}
|
||||
|
||||
.js-media.margin-vm, .js-media-preview-container.margin-vm {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* Skip button */
|
||||
|
||||
#td-skip {
|
||||
position: fixed;
|
||||
left: 30px;
|
||||
bottom: 10px;
|
||||
z-index: 1000;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.td-hover #td-skip {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
#td-skip:hover {
|
||||
opacity: 1;
|
||||
}
|
@@ -88,6 +88,7 @@
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Core\FormManager.cs" />
|
||||
<Compile Include="Core\Handling\ContextMenuGuide.cs" />
|
||||
<Compile Include="Core\Handling\DragHandlerBrowser.cs" />
|
||||
<Compile Include="Core\Handling\General\BrowserProcessHandler.cs" />
|
||||
<Compile Include="Core\Handling\ContextMenuBase.cs" />
|
||||
@@ -133,6 +134,12 @@
|
||||
<Compile Include="Core\Other\FormAbout.Designer.cs">
|
||||
<DependentUpon>FormAbout.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\FormGuide.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\FormGuide.Designer.cs">
|
||||
<DependentUpon>FormGuide.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\FormMessage.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -171,6 +178,12 @@
|
||||
<DependentUpon>DialogSettingsRestart.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\Management\BrowserProcesses.cs" />
|
||||
<Compile Include="Core\Other\Settings\TabSettingsFeedback.cs">
|
||||
<SubType>UserControl</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\Settings\TabSettingsFeedback.Designer.cs">
|
||||
<DependentUpon>TabSettingsFeedback.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\Settings\TabSettingsTray.cs">
|
||||
<SubType>UserControl</SubType>
|
||||
</Compile>
|
||||
@@ -333,12 +346,15 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
@@ -31,7 +31,7 @@ namespace TweetDuck.Updates{
|
||||
timerDownloadCheck.Stop();
|
||||
|
||||
if (FormMessage.Error("Update Has Failed", "Could not download the update: "+(updateInfo.DownloadError?.Message ?? "unknown error")+"\n\nDo you want to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe(Program.Website);
|
||||
BrowserUtils.OpenExternalBrowser(Program.Website);
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
|
@@ -5,7 +5,7 @@ using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Video{
|
||||
static class Program{
|
||||
internal const string Version = "1.2.0.0";
|
||||
internal const string Version = "1.2.1.0";
|
||||
|
||||
// referenced in VideoPlayer
|
||||
// set by task manager -- public const int CODE_PROCESS_KILLED = 1;
|
||||
|