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

Compare commits

..

32 Commits
1.9.2 ... 1.10

Author SHA1 Message Date
0195378c10 Release 1.10 2017-09-28 17:09:46 +02:00
bc804c6a53 Allow linking to items in the guide 2017-09-28 13:33:54 +02:00
76b15f1971 Fix wrong emoji names & issues with emoji keyboard on while using :emoji_name: 2017-09-28 01:00:03 +02:00
c4d43c9d5b Fix bugs in :emoji_name: update (character count & duplicate keyboard modal) 2017-09-27 23:56:12 +02:00
e8d3e530de Update guide with a screenshot of the main menu 2017-09-27 17:08:51 +02:00
e145adec58 Bump emoji keyboard plugin version 2017-09-27 14:55:08 +02:00
e2dad3e477 Automatically convert :emoji_name: in emoji keyboard plugin
Closes #160
2017-09-27 13:25:05 +02:00
27bdbde171 Optimize images and scripts in the guide 2017-09-25 21:39:04 +02:00
e9ec27169c Improve guide for small width screens and screen readers 2017-09-25 21:12:47 +02:00
2e24cb634c Add favicon and meta tags to the guide 2017-09-25 19:47:00 +02:00
beb9046055 Add a button to send feedback / bug report to Feedback tab in Options 2017-09-25 17:23:16 +02:00
e57301952c Rewrite introduction text and update styles 2017-09-25 15:59:19 +02:00
7411279e48 Update guide URL 2017-09-25 14:22:19 +02:00
16acfa85b5 Bump project and plugin versions 2017-09-25 14:21:51 +02:00
41ef37f3f0 Fix GIFs in detail view not having the pointer cursor 2017-09-24 23:41:39 +02:00
00d8538726 Fix middle-clicking GIFs not opening the tweet externally
Closes #169
2017-09-24 23:35:31 +02:00
6eeb3f9895 Fix error when hovering a quoted tweet with a video 2017-09-24 23:22:20 +02:00
d19dca6ea5 Add a (currently unimplemented) anonymous data collection option 2017-09-24 15:31:44 +02:00
2008ccdaa4 Add an introduction modal & guide 2017-09-23 23:33:06 +02:00
ba2e62de3a Remove a warning in the reply-account plugin's default config 2017-09-23 10:17:55 +02:00
2b62eb254d Fix quoted tweet and media border radius in notifications & screenshots 2017-09-21 13:39:51 +02:00
31f72b7957 Move browser CSS to a file & optimize CSS injection 2017-09-21 13:19:21 +02:00
fdc4616875 Minify CSS files after build 2017-09-21 10:50:13 +02:00
b7de261d25 Move notification CSS to a file, refactor FontSizeLevel & TweetDeckBridge props 2017-09-21 10:34:57 +02:00
ae78a5a026 Fix screenshots of reply threads 2017-09-21 09:50:48 +02:00
fd2cf5d4d7 Make memory cleanup detect recent activity and fix modals 2017-09-21 09:06:55 +02:00
9f0997be1a Refactor some JS 2017-09-21 08:51:41 +02:00
dbade7f854 Fix crash when clicking video overlay after a playback error 2017-09-17 19:06:12 +02:00
3cdc1e190a Fix configuration.default.js files being minified on build 2017-09-14 19:40:41 +02:00
36bede7211 Tweak build instructions (wording and formatting) 2017-09-12 23:06:40 +02:00
46689bb700 Rewrite build and setup instructions 2017-09-12 22:51:59 +02:00
13e1a6543c Release 1.9.2 2017-09-12 20:29:40 +02:00
52 changed files with 1664 additions and 195 deletions

View File

@@ -44,6 +44,9 @@ namespace TweetDuck.Configuration{
// CONFIGURATION DATA // CONFIGURATION DATA
public bool FirstRun { get; set; } = true;
public bool AllowDataCollection { get; set; } = false;
public WindowState BrowserWindow { get; set; } = new WindowState(); public WindowState BrowserWindow { get; set; } = new WindowState();
public WindowState PluginsWindow { get; set; } = new WindowState(); public WindowState PluginsWindow { get; set; } = new WindowState();

View File

@@ -11,6 +11,9 @@ using TweetDuck.Resources;
namespace TweetDuck.Core.Bridge{ namespace TweetDuck.Core.Bridge{
sealed class TweetDeckBridge{ sealed class TweetDeckBridge{
public static string FontSizeClass;
public static string NotificationHeadContents;
public static string LastHighlightedTweetUrl = string.Empty; public static string LastHighlightedTweetUrl = string.Empty;
public static string LastHighlightedQuoteUrl = string.Empty; public static string LastHighlightedQuoteUrl = string.Empty;
private static string LastHighlightedTweetAuthors = 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); private static readonly Dictionary<string, string> SessionData = new Dictionary<string, string>(2);
public static void ResetStaticProperties(){ public static void ResetStaticProperties(){
FontSizeClass = NotificationHeadContents = null;
LastHighlightedTweetUrl = LastHighlightedQuoteUrl = LastHighlightedTweetAuthors = LastHighlightedTweetImages = string.Empty; LastHighlightedTweetUrl = LastHighlightedQuoteUrl = LastHighlightedTweetAuthors = LastHighlightedTweetImages = string.Empty;
} }
@@ -46,16 +50,18 @@ namespace TweetDuck.Core.Bridge{
this.notification = notification; this.notification = notification;
} }
public void LoadFontSizeClass(string fsClass){ public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
form.InvokeAsyncSafe(() => { form.InvokeAsyncSafe(() => {
TweetNotification.SetFontSizeClass(fsClass); form.OnIntroductionClosed(showGuide, allowDataCollection);
}); });
} }
public void LoadFontSizeClass(string fsClass){
form.InvokeAsyncSafe(() => FontSizeClass = fsClass);
}
public void LoadNotificationHeadContents(string headContents){ public void LoadNotificationHeadContents(string headContents){
form.InvokeAsyncSafe(() => { form.InvokeAsyncSafe(() => NotificationHeadContents = headContents);
TweetNotification.SetHeadTag(headContents);
});
} }
public void SetLastRightClickInfo(string type, string link){ public void SetLastRightClickInfo(string type, string link){

View File

@@ -202,6 +202,7 @@ namespace TweetDuck.Core{
UpdateProperties(PropertyBridge.Environment.Browser); UpdateProperties(PropertyBridge.Environment.Browser);
TweetDeckBridge.RestoreSessionData(e.Frame); TweetDeckBridge.RestoreSessionData(e.Frame);
ScriptLoader.ExecuteFile(e.Frame, "code.js"); ScriptLoader.ExecuteFile(e.Frame, "code.js");
InjectBrowserCSS();
ReinjectCustomCSS(Config.CustomBrowserCSS); ReinjectCustomCSS(Config.CustomBrowserCSS);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser); plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser);
@@ -210,6 +211,10 @@ namespace TweetDuck.Core{
if (Program.SystemConfig.EnableBrowserGCReload){ if (Program.SystemConfig.EnableBrowserGCReload){
memoryUsageTracker.Start(this, e.Browser, Program.SystemConfig.BrowserMemoryThreshold); 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 // javascript calls
public void InjectBrowserCSS(){
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css").TrimEnd());
}
public void ReinjectCustomCSS(string css){ public void ReinjectCustomCSS(string css){
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty); browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
} }
@@ -439,6 +448,18 @@ namespace TweetDuck.Core{
// callback handlers // 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(){ public void OpenContextMenu(){
contextMenu.Show(this, PointToClient(Cursor.Position)); contextMenu.Show(this, PointToClient(Cursor.Position));
} }

View File

@@ -20,7 +20,7 @@ namespace TweetDuck.Core{
public static void CloseAllDialogs(){ public static void CloseAllDialogs(){
foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){ 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(); form.Close();
} }
} }

View 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);
}
}
}
}

View File

@@ -4,6 +4,7 @@ using System;
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling; using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General; using TweetDuck.Core.Handling.General;
@@ -12,6 +13,18 @@ using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
partial class FormNotificationBase : Form{ partial class FormNotificationBase : Form{
protected static int FontSizeLevel{
get{
switch(TweetDeckBridge.FontSizeClass){
case "largest": return 4;
case "large": return 3;
case "small": return 1;
case "smallest": return 0;
default: return 2;
}
}
}
protected Point PrimaryLocation{ protected Point PrimaryLocation{
get{ get{
UserConfig config = Program.UserConfig; UserConfig config = Program.UserConfig;

View File

@@ -33,7 +33,7 @@ namespace TweetDuck.Core.Notification{
public bool RequiresResize{ public bool RequiresResize{
get{ 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{ set{
@@ -43,7 +43,7 @@ namespace TweetDuck.Core.Notification{
} }
else{ else{
prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer; prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer;
prevFontSize = TweetNotification.FontSizeLevel; prevFontSize = FontSizeLevel;
} }
} }
} }
@@ -52,7 +52,7 @@ namespace TweetDuck.Core.Notification{
get{ get{
switch(Program.UserConfig.NotificationSize){ switch(Program.UserConfig.NotificationSize){
default: 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: case TweetNotification.Size.Custom:
return Program.UserConfig.CustomNotificationSize.Width; return Program.UserConfig.CustomNotificationSize.Width;
@@ -64,7 +64,7 @@ namespace TweetDuck.Core.Notification{
get{ get{
switch(Program.UserConfig.NotificationSize){ switch(Program.UserConfig.NotificationSize){
default: 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: case TweetNotification.Size.Custom:
return Program.UserConfig.CustomNotificationSize.Height; return Program.UserConfig.CustomNotificationSize.Height;

View File

@@ -1,27 +1,14 @@
using System; using System;
using System.Text; using System.Text;
using TweetDuck.Core.Bridge;
using TweetDuck.Resources; using TweetDuck.Resources;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
sealed class TweetNotification{ sealed class TweetNotification{
private static string FontSizeClass { get; set; }
private static string HeadTag { get; set; }
private const string DefaultFontSizeClass = "medium"; 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 DefaultHeadContents = @"<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{ private static readonly string CustomCSS = ScriptLoader.LoadResource("styles/notification.css");
get{
switch(FontSizeClass){
case "largest": return 4;
case "large": return 3;
case "medium": return 2;
case "small": return 1;
default: return 0;
}
}
}
private static string ExampleTweetHTML; private static string ExampleTweetHTML;
@@ -39,14 +26,6 @@ namespace TweetDuck.Core.Notification{
} }
} }
public static void SetFontSizeClass(string newFSClass){
FontSizeClass = newFSClass;
}
public static void SetHeadTag(string headContents){
HeadTag = headContents;
}
public enum Position{ public enum Position{
TopLeft, TopRight, BottomLeft, BottomRight, Custom TopLeft, TopRight, BottomLeft, BottomRight, Custom
} }
@@ -88,8 +67,8 @@ namespace TweetDuck.Core.Notification{
public string GenerateHtml(string bodyClasses = null, bool enableCustomCSS = true){ public string GenerateHtml(string bodyClasses = null, bool enableCustomCSS = true){
StringBuilder build = new StringBuilder(); StringBuilder build = new StringBuilder();
build.Append("<!DOCTYPE html>"); build.Append("<!DOCTYPE html>");
build.Append("<html class='os-windows txt-base-").Append(FontSizeClass ?? DefaultFontSizeClass).Append("'>"); build.Append("<html class='os-windows txt-base-").Append(TweetDeckBridge.FontSizeClass ?? DefaultFontSizeClass).Append("'>");
build.Append("<head>").Append(HeadTag ?? DefaultHeadTag); build.Append("<head>").Append(TweetDeckBridge.NotificationHeadContents ?? DefaultHeadContents);
if (enableCustomCSS){ if (enableCustomCSS){
build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>"); build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>");

View File

@@ -131,11 +131,14 @@ namespace TweetDuck.Core.Other {
this.Controls.Add(this.labelDescription); this.Controls.Add(this.labelDescription);
this.Controls.Add(this.pictureLogo); this.Controls.Add(this.pictureLogo);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.HelpButton = true;
this.MaximizeBox = false; this.MaximizeBox = false;
this.MinimizeBox = false; this.MinimizeBox = false;
this.Name = "FormAbout"; this.Name = "FormAbout";
this.ShowIcon = false; this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 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(); ((System.ComponentModel.ISupportInitialize)(this.pictureLogo)).EndInit();
this.tablePanelLinks.ResumeLayout(false); this.tablePanelLinks.ResumeLayout(false);
this.tablePanelLinks.PerformLayout(); this.tablePanelLinks.PerformLayout();

View File

@@ -1,4 +1,6 @@
using System.Windows.Forms; using System.ComponentModel;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other{ namespace TweetDuck.Core.Other{
@@ -21,5 +23,19 @@ namespace TweetDuck.Core.Other{
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){ private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
BrowserUtils.OpenExternalBrowserUnsafe(e.Link.LinkData as string); BrowserUtils.OpenExternalBrowserUnsafe(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
View 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
View 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);
}
}
}

View File

@@ -37,6 +37,7 @@ namespace TweetDuck.Core.Other{
AddButton("System Tray", () => new TabSettingsTray()); AddButton("System Tray", () => new TabSettingsTray());
AddButton("Notifications", () => new TabSettingsNotifications(browser.CreateNotificationForm(false))); AddButton("Notifications", () => new TabSettingsNotifications(browser.CreateNotificationForm(false)));
AddButton("Sounds", () => new TabSettingsSounds()); AddButton("Sounds", () => new TabSettingsSounds());
AddButton("Feedback", () => new TabSettingsFeedback());
AddButton("Advanced", () => new TabSettingsAdvanced(browser.ReinjectCustomCSS)); AddButton("Advanced", () => new TabSettingsAdvanced(browser.ReinjectCustomCSS));
SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]); SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]);

View File

@@ -147,7 +147,15 @@ namespace TweetDuck.Core.Other.Management{
} }
private void process_Exited(object sender, EventArgs e){ private void process_Exited(object sender, EventArgs e){
switch(currentProcess.ExitCode){ int exitCode = currentProcess.ExitCode;
currentProcess.Dispose();
currentProcess = null;
currentPipe.Dispose();
currentPipe = null;
switch(exitCode){
case 3: // CODE_LAUNCH_FAIL 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)){ 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); BrowserUtils.OpenExternalBrowser(lastUrl);
@@ -163,12 +171,6 @@ namespace TweetDuck.Core.Other.Management{
break; break;
} }
currentProcess.Dispose();
currentProcess = null;
currentPipe.Dispose();
currentPipe = null;
owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe); owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
} }

View 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;
}
}

View 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.OpenExternalBrowserUnsafe("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.OpenExternalBrowserUnsafe("https://github.com/chylex/TweetDuck/wiki/Send-anonymous-data");
}
}
}

View File

@@ -21,7 +21,7 @@ namespace TweetDuck{
public const string BrandName = "TweetDuck"; public const string BrandName = "TweetDuck";
public const string Website = "https://tweetduck.chylex.com"; public const string Website = "https://tweetduck.chylex.com";
public const string VersionTag = "1.9.1"; public const string VersionTag = "1.10";
public static readonly bool IsPortable = File.Exists("makeportable"); public static readonly bool IsPortable = File.Exists("makeportable");

View File

@@ -1,11 +1,56 @@
# Build Instructions # 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 CefSharp.WinForms -Version 57.0.0
PM> Install-Package Microsoft.VC120.CRT.JetBrains 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, open the **Output** view and see which part of the build failed.
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.
### 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

403
Resources/Guide/index.html Normal file
View File

@@ -0,0 +1,403 @@
<!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 &amp; 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 &amp; 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 first to bring the reply into the large 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 scroll through the emoji, click on the emoji to insert them, and change the skin tone.</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 &ndash; 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 &ndash; 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
View 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
View 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%;
}

View File

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

View File

@@ -108,7 +108,7 @@ enabled(){
let menu = $(".js-dropdown-content").children("ul").first(); let menu = $(".js-dropdown-content").children("ul").first();
return if menu.length === 0; return if menu.length === 0;
let itemTD = menu.children("[data-std]").first(); let itemTD = menu.children("[data-tweetduck]").first();
return if itemTD.length === 0; return if itemTD.length === 0;
if (!itemTD.prev().hasClass("drp-h-divider")){ if (!itemTD.prev().hasClass("drp-h-divider")){
@@ -116,7 +116,7 @@ enabled(){
} }
let itemEditDesign = $('<li class="is-selectable"><a href="#" data-action>Edit layout &amp; design</a></li>'); let itemEditDesign = $('<li class="is-selectable"><a href="#" data-action>Edit layout &amp; design</a></li>');
itemTD.after(itemEditDesign); itemEditDesign.insertAfter(itemTD);
itemEditDesign.on("click", "a", this.openEditDesignDialog); itemEditDesign.on("click", "a", this.openEditDesignDialog);

View File

@@ -9,7 +9,7 @@ Emoji keyboard
chylex chylex
[version] [version]
1.3.1 1.4
[website] [website]
https://tweetduck.chylex.com https://tweetduck.chylex.com

View File

@@ -66,6 +66,7 @@ enabled(){
this.currentSpanner = null; this.currentSpanner = null;
var wasSearchFocused = false; var wasSearchFocused = false;
var lastEmojiKeyword, lastEmojiPosition, lastEmojiLength;
var hideKeyboard = (refocus) => { var hideKeyboard = (refocus) => {
$(this.currentKeyboard).remove(); $(this.currentKeyboard).remove();
@@ -81,8 +82,14 @@ enabled(){
$(".emoji-keyboard-popup-btn").removeClass("is-selected"); $(".emoji-keyboard-popup-btn").removeClass("is-selected");
if (refocus){ 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 => { var generateEmojiHTML = skinTone => {
@@ -229,7 +236,7 @@ enabled(){
}; };
var insertEmoji = (src, alt) => { var insertEmoji = (src, alt) => {
let input = $(".js-compose-text", ".js-docked-compose"); let input = this.composeInput;
let val = input.val(); let val = input.val();
let posStart = input[0].selectionStart; let posStart = input[0].selectionStart;
@@ -241,6 +248,8 @@ enabled(){
input[0].selectionStart = posStart+alt.length; input[0].selectionStart = posStart+alt.length;
input[0].selectionEnd = posStart+alt.length; input[0].selectionEnd = posStart+alt.length;
lastEmojiKeyword = null;
if (wasSearchFocused){ if (wasSearchFocused){
$(".emoji-keyboard-search").children("input").focus(); $(".emoji-keyboard-search").children("input").focus();
} }
@@ -270,6 +279,92 @@ 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);
return if !/^[a-z_]+$/i.test(search);
let keywords = search.split("_").filter(kw => kw.length > 0).map(kw => kw.toLowerCase());
return if keywords.length === 0;
let foundName = me.emojiNames.filter(name => keywords.every(kw => name.includes(kw)));
if (foundName.length === 0){
return;
}
lastEmojiKeyword = `:${search}:`;
lastEmojiPosition = lastEmojiLength = 0;
if (foundName.length === 1){
let foundIndex = me.emojiNames.indexOf(foundName[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 (foundName.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){ this.composeInputFocusEvent = function(e){
wasSearchFocused = false; wasSearchFocused = false;
}; };
@@ -300,15 +395,18 @@ enabled(){
ready(){ ready(){
this.composeDrawer = $("[data-drawer='compose']"); 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 = $(".js-compose-scroller", ".js-docked-compose").first().children().first();
this.composePanelScroller.on("scroll", this.composerScrollEvent); this.composePanelScroller.on("scroll", this.composerScrollEvent);
$(".emoji-keyboard-popup-btn").on("click", this.emojiKeyboardButtonClickEvent); $(".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("click", this.documentClickEvent);
$(document).on("keydown", this.documentKeyEvent); $(document).on("keydown", this.documentKeyEvent);
$(document).on("uiComposeImageAdded", this.uploadFilesEvent); $(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); this.composeDrawer.on("uiComposeTweetSending", this.composerSendingEvent);
// HTML generation // HTML generation
@@ -429,10 +527,12 @@ disabled(){
$(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonClickEvent); $(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonClickEvent);
$(".emoji-keyboard-popup-btn").remove(); $(".emoji-keyboard-popup-btn").remove();
$(".js-compose-text", ".js-docked-compose").off("focus", this.composeInputFocusEvent);
$(document).off("click", this.documentClickEvent); $(document).off("click", this.documentClickEvent);
$(document).off("keydown", this.documentKeyEvent); $(document).off("keydown", this.documentKeyEvent);
$(document).off("uiComposeImageAdded", this.uploadFilesEvent); $(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); this.composeDrawer.off("uiComposeTweetSending", this.composerSendingEvent);
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache; TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;

View File

@@ -8,7 +8,7 @@ Custom reply account
chylex chylex
[version] [version]
1.2.1 1.2.2
[website] [website]
https://tweetduck.chylex.com https://tweetduck.chylex.com

View File

@@ -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 * Simple way of configuring the plugin
* ------------------------------------ * ------------------------------------

View File

@@ -11,12 +11,22 @@ function Rewrite-File{
Write-Host "Processed" $file.FullName.Substring($dir.Length) Write-Host "Processed" $file.FullName.Substring($dir.Length)
} }
ForEach($file in Get-ChildItem -Include *.js -Recurse){ ForEach($file in Get-ChildItem -Include *.js -Exclude configuration.default.js -Recurse){
$lines = Get-Content -Path $file.FullName $lines = Get-Content -Path $file.FullName
$lines = ($lines | % { $_.TrimStart() }) -Replace '^(.*?)((?<=^|[;{}()])\s?//(?:\s.*|$))?$', '$1' -Replace '(?<!\w)return(\s.*?)? if (.*?);', 'if ($2)return$1;' $lines = $lines | % { $_.TrimStart() }
$lines = $lines -Replace '^(.*?)((?<=^|[;{}()])\s?//(?:\s.*|$))?$', '$1'
$lines = $lines -Replace '(?<!\w)return(\s.*?)? if (.*?);', 'if ($2)return$1;'
,$lines | Rewrite-File $file ,$lines | Rewrite-File $file
} }
ForEach($file in Get-ChildItem -Include *.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 -Include *.html -Recurse){ ForEach($file in Get-ChildItem -Include *.html -Recurse){
$lines = Get-Content -Path $file.FullName $lines = Get-Content -Path $file.FullName
,($lines | % { $_.TrimStart() }) | Rewrite-File $file ,($lines | % { $_.TrimStart() }) | Rewrite-File $file

View File

@@ -270,7 +270,9 @@
tags.push("<style type='text/css'>"); tags.push("<style type='text/css'>");
tags.push("body { background: "+getClassStyleProperty("column", "background-color")+" }"); // set background color 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("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(".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 { 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 tags.push(".activity-header .tweet-timestamp { line-height: unset }"); // fix timestamp position in notifications
tags.push("</style>"); tags.push("</style>");
@@ -328,9 +330,8 @@
let menu = $(".js-dropdown-content").children("ul").first(); let menu = $(".js-dropdown-content").children("ul").first();
return if menu.length === 0; 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 = $('<li class="is-selectable" data-tweetduck><a href="#" data-action>TweetDuck</a></li>');
button.insertBefore(menu.children(".drp-h-divider").last());
let button = menu.children("[data-std]");
button.on("click", "a", function(){ button.on("click", "a", function(){
$TD.openContextMenu(); $TD.openContextMenu();
@@ -349,10 +350,6 @@
// Block: Expand shortened links on hover or display tooltip. // Block: Expand shortened links on hover or display tooltip.
// //
(function(){ (function(){
var cutStart = function(str, search){
return str.startsWith(search) ? str.substr(search.length) : str;
};
var prevMouseX = -1, prevMouseY = -1; var prevMouseX = -1, prevMouseY = -1;
var tooltipTimer, tooltipDisplayed; var tooltipTimer, tooltipDisplayed;
@@ -364,13 +361,8 @@
if ($TDX.expandLinksOnHover){ if ($TDX.expandLinksOnHover){
tooltipTimer = window.setTimeout(function(){ 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.attr("td-prev-text", text);
me.text(expanded); me.text(me.attr("data-full-url").replace(/^https?:\/\/(www\.)?/, ""));
}, 200); }, 200);
} }
else{ else{
@@ -503,8 +495,8 @@
} }
}; };
var processMedia = function(media){ var processMedia = function(chirp){
return media.filter(item => !item.isAnimatedGif).map(item => item.entity.media_url_https+":small").join(";"); return chirp.getMedia().filter(item => !item.isAnimatedGif).map(item => item.entity.media_url_https+":small").join(";");
}; };
app.delegate("section.js-column", { app.delegate("section.js-column", {
@@ -531,7 +523,7 @@
let tweetUrl = tweet.getChirpURL(); let tweetUrl = tweet.getChirpURL();
let quoteUrl = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : ""; let quoteUrl = tweet.quotedTweet ? tweet.quotedTweet.getChirpURL() : "";
let authors = tweet.quotedTweet ? [ tweet.getMainUser().screenName, tweet.quotedTweet.getMainUser().screenName ].join(";") : tweet.getMainUser().screenName; 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); updateHighlightedTweet(me, tweet, tweetUrl || "", quoteUrl || "", authors, imageList);
} }
@@ -597,6 +589,7 @@
if (isReply){ if (isReply){
selectedTweet.find(".is-conversation").removeClass("is-conversation"); selectedTweet.find(".is-conversation").removeClass("is-conversation");
selectedTweet.find(".thread").remove();
} }
selectedTweet.find(".js-poll-link").remove(); selectedTweet.find(".js-poll-link").remove();
@@ -833,74 +826,24 @@
// Block: Inject custom CSS and layout into the page. // Block: Inject custom CSS and layout into the page.
// //
(function(){ (function(){
let styleOfficial = document.createElement("style"); var createStyle = function(id, styles){
document.head.appendChild(styleOfficial); let ele = document.createElement("style");
ele.id = id;
let addRule = (rule) => { ele.innerText = styles;
styleOfficial.sheet.insertRule(rule, 0); document.head.appendChild(ele);
}; };
addRule("a[data-full-url] { word-break: break-all; }"); // break long urls window.TDGF_injectBrowserCSS = function(styles){
addRule(".keyboard-shortcut-list { vertical-align: top; }"); // fix keyboard navigation alignment if (!document.getElementById("tweetduck-browser-css")){
addRule(".account-inline .username { vertical-align: 10%; }"); // move usernames a bit higher createStyle("tweetduck-browser-css", styles);
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_reinjectCustomCSS = function(styles){ window.TDGF_reinjectCustomCSS = function(styles){
$("#tweetduck-custom-css").remove(); $("#tweetduck-custom-css").remove();
if (styles && styles.length){ if (styles && styles.length){
$(document.head).append("<style type='text/css' id='tweetduck-custom-css'>"+styles+"</style>"); createStyle("tweetduck-custom-css", styles);
} }
}; };
})(); })();
@@ -935,19 +878,38 @@
$TD.playVideo(url); $TD.playVideo(url);
}; };
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");
}
app.delegate(".js-gif-play", {
click: function(e){
let src = !e.ctrlKey && $(this).closest(".js-media-gif-container").find("video").attr("src"); let src = !e.ctrlKey && $(this).closest(".js-media-gif-container").find("video").attr("src");
if (src){ if (src){
playVideo(src); playVideo(src);
} }
else{ else{
let parent = $(e.target).closest(".js-tweet").first(); $TD.openBrowser(getVideoTweetLink($(this)));
let link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
$TD.openBrowser(link.attr("href"));
} }
e.stopPropagation(); 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"); TD.mustaches["status/media_thumb.mustache"] = TD.mustaches["status/media_thumb.mustache"].replace("is-gif", "is-gif is-paused");
@@ -1120,31 +1082,34 @@
}); });
}; };
(function(){
var lastActivity = Date.now();
$(document).click(function(e){
lastActivity = Date.now();
});
window.TDGF_tryRunCleanup = function(){ window.TDGF_tryRunCleanup = function(){
// no recent activity
return false if Date.now()-lastActivity < 15e3;
// no modals are visible // 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 // all columns are in a default state
return false if $("section.js-column").is(".is-shifted-1,.is-shifted-2"); return false if $("section.js-column").is(".is-shifted-1,.is-shifted-2");
// all textareas are empty // all textareas are empty
if ($("textarea").is(function(){ return false if Array.prototype.some.call(document.getElementsByTagName("textarea"), ele => ele.value.length > 0);
return $(this).val().length > 0;
})){
return false;
}
// all columns are scrolled to top // all columns are scrolled to top
if ($(".js-column-scroller").is(function(){ return false if Array.prototype.some.call(document.getElementsByClassName("js-column-scroller"), ele => ele.scrollTop > 0);
return $(this).scrollTop() > 0;
})){
return false;
}
// cleanup // cleanup
window.TDGF_reload(); window.TDGF_reload();
return true; return true;
}; };
})();
if (window.TD_SESSION && window.TD_SESSION.gc){ if (window.TD_SESSION && window.TD_SESSION.gc){
var state; var state;

View 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>&nbsp;(<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);

View File

@@ -42,10 +42,6 @@
// Block: Expand shortened links on hover or display tooltip. // Block: Expand shortened links on hover or display tooltip.
// //
(function(){ (function(){
var cutStart = function(str, search){
return str.startsWith(search) ? str.substr(search.length) : str;
};
var prevMouseX = -1, prevMouseY = -1; var prevMouseX = -1, prevMouseY = -1;
var tooltipTimer, tooltipDisplayed; var tooltipTimer, tooltipDisplayed;
@@ -60,13 +56,8 @@
if ($TDX.expandLinksOnHover){ if ($TDX.expandLinksOnHover){
tooltipTimer = window.setTimeout(function(){ 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.setAttribute("td-prev-text", text);
me.innerHTML = expanded; me.innerHTML = url.replace(/^https?:\/\/(www\.)?/, "");
}, 200); }, 200);
} }
else{ else{
@@ -112,11 +103,11 @@
// Block: Setup a skip button. // Block: Setup a skip button.
// //
if (!document.body.hasAttribute("td-example-notification")){ if (!document.body.hasAttribute("td-example-notification")){
document.body.insertAdjacentHTML("afterbegin", [ 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">', <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">', <path fill="#888" d="M0,151.656l102.208-102.22l247.777,247.775L102.208,544.986L0,442.758l145.546-145.547">
'</svg>' </svg>
].join("")); `);
document.getElementById("td-skip").addEventListener("click", function(){ document.getElementById("td-skip").addEventListener("click", function(){
$TD.loadNextNotification(); $TD.loadNextNotification();

View File

@@ -0,0 +1,239 @@
/***********************/
/* 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, .media-item, .media-preview, .tooltip-inner {
border-radius: 1px !important;
}
.compose-text-container, .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%;
}
.txt-base-smallest .sprite-verified-mini {
/* fix cut off badge when zoomed in */
width: 13px !important;
height: 13px !important;
background-position: -223px -99px !important;
}
.txt-base-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;
}
/********************************************************************/
/* Fix glaring visual issues because twitter is fucking incompetent */
/********************************************************************/
.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;
}
/************************************************/
/* 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;
}

View 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;
}

View File

@@ -88,6 +88,7 @@
<SubType>Component</SubType> <SubType>Component</SubType>
</Compile> </Compile>
<Compile Include="Core\FormManager.cs" /> <Compile Include="Core\FormManager.cs" />
<Compile Include="Core\Handling\ContextMenuGuide.cs" />
<Compile Include="Core\Handling\DragHandlerBrowser.cs" /> <Compile Include="Core\Handling\DragHandlerBrowser.cs" />
<Compile Include="Core\Handling\General\BrowserProcessHandler.cs" /> <Compile Include="Core\Handling\General\BrowserProcessHandler.cs" />
<Compile Include="Core\Handling\ContextMenuBase.cs" /> <Compile Include="Core\Handling\ContextMenuBase.cs" />
@@ -133,6 +134,12 @@
<Compile Include="Core\Other\FormAbout.Designer.cs"> <Compile Include="Core\Other\FormAbout.Designer.cs">
<DependentUpon>FormAbout.cs</DependentUpon> <DependentUpon>FormAbout.cs</DependentUpon>
</Compile> </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"> <Compile Include="Core\Other\FormMessage.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@@ -171,6 +178,12 @@
<DependentUpon>DialogSettingsRestart.cs</DependentUpon> <DependentUpon>DialogSettingsRestart.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Core\Other\Management\BrowserProcesses.cs" /> <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"> <Compile Include="Core\Other\Settings\TabSettingsTray.cs">
<SubType>UserControl</SubType> <SubType>UserControl</SubType>
</Compile> </Compile>
@@ -333,12 +346,15 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Resources\Scripts\code.js" /> <Content Include="Resources\Scripts\code.js" />
<Content Include="Resources\Scripts\introduction.js" />
<Content Include="Resources\Scripts\notification.js" /> <Content Include="Resources\Scripts\notification.js" />
<Content Include="Resources\Scripts\pages\error.html" /> <Content Include="Resources\Scripts\pages\error.html" />
<Content Include="Resources\Scripts\pages\example.html" /> <Content Include="Resources\Scripts\pages\example.html" />
<Content Include="Resources\Scripts\plugins.browser.js" /> <Content Include="Resources\Scripts\plugins.browser.js" />
<Content Include="Resources\Scripts\plugins.js" /> <Content Include="Resources\Scripts\plugins.js" />
<Content Include="Resources\Scripts\plugins.notification.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\twitter.js" />
<Content Include="Resources\Scripts\update.js" /> <Content Include="Resources\Scripts\update.js" />
</ItemGroup> </ItemGroup>

View File

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