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

Compare commits

..

15 Commits

Author SHA1 Message Date
a63e210b88 Release 1.13.4 2018-04-15 19:59:35 +02:00
06bd65b7f8 Fix wrong behavior when an update is canceled during download & multiple check errors in some cases 2018-04-15 19:01:39 +02:00
b6c17eb05e Remove unused selectors and classes from styles & add a related TODO note 2018-04-15 18:08:37 +02:00
a3d40fdc2b Push a quick utility to detect unused selectors and classes 2018-04-15 18:08:03 +02:00
c064ef7a30 Improve screenshot reliability 2018-04-15 16:24:26 +02:00
762717da1e Move clear-columns plugin nav button next to 'Add column' button & add isClearable safeguard 2018-04-15 14:39:59 +02:00
b7d3758bea Add error handling when checking updates, and remove unnecessary TODO 2018-04-15 14:03:11 +02:00
d20541fd24 Fix clear-columns plugin to hide the Clear button on scheduled & collection columns 2018-04-14 20:28:48 +02:00
2c2f860f26 Fix issues caused by recent TweetDeck update (notifications, column styles, reply account)
Closes #211
2018-04-14 19:40:51 +02:00
d1db3aa673 Remove command line argument for pre-releases & reorganize restart dialog 2018-04-11 10:39:17 +02:00
cedc52cdf5 Move update notification trigger code to TweetDeckBrowser 2018-04-11 10:01:55 +02:00
33f8eafbcf Remove unused VC120 NuGet package 2018-04-11 09:59:10 +02:00
ad45cf8c72 Begin rewriting update checker to run within C# 2018-04-11 09:59:00 +02:00
f99d035621 Add a Result class that acts as an Either monad for a value or exception 2018-04-10 19:45:41 +02:00
f3072caea8 Fix broken element resizing in the Edit CSS dialog 2018-04-07 13:42:36 +02:00
31 changed files with 474 additions and 321 deletions

View File

@@ -6,7 +6,6 @@ namespace TweetDuck.Configuration{
// public args
public const string ArgDataFolder = "-datafolder";
public const string ArgLogging = "-log";
public const string ArgDebugUpdates = "-debugupdates";
// internal args
public const string ArgRestart = "-restart";

View File

@@ -39,6 +39,7 @@ namespace TweetDuck.Core{
}
public string UpdateInstallerPath { get; private set; }
private bool ignoreUpdateCheckError;
public AnalyticsFile AnalyticsFile => analytics?.File ?? AnalyticsFile.Dummy;
@@ -78,8 +79,9 @@ namespace TweetDuck.Core{
Disposed += (sender, args) => {
Config.MuteToggled -= Config_MuteToggled;
Config.TrayBehaviorChanged -= Config_TrayBehaviorChanged;
browser.Dispose();
updates.Dispose();
contextMenu.Dispose();
notificationScreenshotManager?.Dispose();
@@ -96,6 +98,7 @@ namespace TweetDuck.Core{
UpdateTrayIcon();
this.updates = new UpdateHandler(browser, updaterSettings);
this.updates.CheckFinished += updates_CheckFinished;
this.updates.UpdateAccepted += updates_UpdateAccepted;
this.updates.UpdateDismissed += updates_UpdateDismissed;
@@ -233,6 +236,28 @@ namespace TweetDuck.Core{
}
}
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
this.InvokeAsyncSafe(() => {
e.Result.Handle(update => {
if (!update.IsUpdateDismissed){
if (update.IsUpdateNew){
browser.ShowUpdateNotification(update.VersionTag, update.ReleaseNotes);
}
else{
updates.StartTimer();
}
}
}, ex => {
if (!ignoreUpdateCheckError){
Program.Reporter.HandleException("Update Check Error", "An error occurred while checking for updates.", true, ex);
updates.StartTimer();
}
});
ignoreUpdateCheckError = true;
});
}
private void updates_UpdateAccepted(object sender, UpdateEventArgs e){
this.InvokeAsyncSafe(() => {
FormManager.CloseAllDialogs();
@@ -241,13 +266,21 @@ namespace TweetDuck.Core{
Config.DismissedUpdate = null;
Config.Save();
}
updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
if (update.DownloadStatus == UpdateDownloadStatus.Done){
UpdateInstallerPath = update.InstallerPath;
}
ForceClose();
updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
UpdateDownloadStatus status = update.DownloadStatus;
if (status == UpdateDownloadStatus.Done){
UpdateInstallerPath = update.InstallerPath;
ForceClose();
}
else if (status != UpdateDownloadStatus.Canceled && FormMessage.Error("Update Has Failed", "Could not automatically download the update: "+(update.DownloadError?.Message ?? "unknown error")+"\n\nWould you like to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)){
BrowserUtils.OpenExternalBrowser(Program.Website);
ForceClose();
}
else{
Show();
}
});
});
}
@@ -302,6 +335,7 @@ namespace TweetDuck.Core{
Resources.ScriptLoader.HotSwap();
#endif
ignoreUpdateCheckError = false;
browser.ReloadToTweetDeck();
AnalyticsFile.BrowserReloads.Trigger();
}

View File

@@ -24,19 +24,26 @@ namespace TweetDuck.Core.Notification.Screenshot{
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new ScreenshotBridge(this, SetScreenshotHeight, callback));
browser.LoadingStateChanged += (sender, args) => {
if (!args.IsLoading){
using(IFrame frame = args.Browser.MainFrame){
if (!ScriptLoader.ExecuteFile(frame, "screenshot.js")){
this.InvokeAsyncSafe(callback);
}
}
if (args.IsLoading){
return;
}
string script = ScriptLoader.LoadResource("screenshot.js", true);
if (script == null){
this.InvokeAsyncSafe(callback);
return;
}
using(IFrame frame = args.Browser.MainFrame){
ScriptLoader.ExecuteScript(frame, script.Replace("{width}", ClientSize.Width.ToString()), "screenshot");
}
};
LoadTweet(new TweetNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty));
SetScreenshotHeight(1);
LoadTweet(new TweetNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty));
}
protected override string GetTweetHTML(TweetNotification tweet){
string html = tweet.GenerateHtml("td-screenshot");

View File

@@ -42,7 +42,9 @@
//
// textBoxBrowserCSS
//
this.textBoxBrowserCSS.Dock = System.Windows.Forms.DockStyle.Bottom;
this.textBoxBrowserCSS.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBoxBrowserCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.textBoxBrowserCSS.Location = new System.Drawing.Point(0, 16);
this.textBoxBrowserCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
@@ -124,7 +126,9 @@
//
// textBoxNotificationCSS
//
this.textBoxNotificationCSS.Dock = System.Windows.Forms.DockStyle.Bottom;
this.textBoxNotificationCSS.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBoxNotificationCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.textBoxNotificationCSS.Location = new System.Drawing.Point(0, 16);
this.textBoxNotificationCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);

View File

@@ -28,21 +28,22 @@
this.btnRestart = new System.Windows.Forms.Button();
this.cbLogging = new System.Windows.Forms.CheckBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.cbDebugUpdates = new System.Windows.Forms.CheckBox();
this.tbDataFolder = new System.Windows.Forms.TextBox();
this.tbShortcutTarget = new System.Windows.Forms.TextBox();
this.labelDataFolder = new System.Windows.Forms.Label();
this.labelShortcutTarget = new System.Windows.Forms.Label();
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.flowPanel.SuspendLayout();
this.SuspendLayout();
//
// btnCancel
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.Location = new System.Drawing.Point(215, 163);
this.btnCancel.Location = new System.Drawing.Point(215, 139);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnCancel.Size = new System.Drawing.Size(56, 23);
this.btnCancel.TabIndex = 9;
this.btnCancel.TabIndex = 2;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
@@ -50,11 +51,11 @@
// btnRestart
//
this.btnRestart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnRestart.Location = new System.Drawing.Point(152, 163);
this.btnRestart.Location = new System.Drawing.Point(152, 139);
this.btnRestart.Name = "btnRestart";
this.btnRestart.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnRestart.Size = new System.Drawing.Size(57, 23);
this.btnRestart.TabIndex = 8;
this.btnRestart.TabIndex = 1;
this.btnRestart.Text = "Restart";
this.btnRestart.UseVisualStyleBackColor = true;
this.btnRestart.Click += new System.EventHandler(this.btnRestart_Click);
@@ -62,7 +63,7 @@
// cbLogging
//
this.cbLogging.AutoSize = true;
this.cbLogging.Location = new System.Drawing.Point(12, 12);
this.cbLogging.Location = new System.Drawing.Point(3, 3);
this.cbLogging.Name = "cbLogging";
this.cbLogging.Size = new System.Drawing.Size(64, 17);
this.cbLogging.TabIndex = 0;
@@ -70,25 +71,12 @@
this.toolTip.SetToolTip(this.cbLogging, "Logging JavaScript output into TD_Console.txt file in the data folder.");
this.cbLogging.UseVisualStyleBackColor = true;
//
// cbDebugUpdates
//
this.cbDebugUpdates.AutoSize = true;
this.cbDebugUpdates.Location = new System.Drawing.Point(12, 35);
this.cbDebugUpdates.Name = "cbDebugUpdates";
this.cbDebugUpdates.Size = new System.Drawing.Size(127, 17);
this.cbDebugUpdates.TabIndex = 1;
this.cbDebugUpdates.Text = "Pre-Release Updates";
this.toolTip.SetToolTip(this.cbDebugUpdates, "Allows updating to pre-releases.");
this.cbDebugUpdates.UseVisualStyleBackColor = true;
//
// tbDataFolder
//
this.tbDataFolder.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tbDataFolder.Location = new System.Drawing.Point(15, 83);
this.tbDataFolder.Location = new System.Drawing.Point(3, 51);
this.tbDataFolder.Name = "tbDataFolder";
this.tbDataFolder.Size = new System.Drawing.Size(257, 20);
this.tbDataFolder.TabIndex = 5;
this.tbDataFolder.Size = new System.Drawing.Size(260, 20);
this.tbDataFolder.TabIndex = 2;
this.toolTip.SetToolTip(this.tbDataFolder, "Path to the data folder. Must be either an absolute path,\r\nor a simple folder nam" +
"e that will be created in LocalAppData.");
//
@@ -97,44 +85,57 @@
this.tbShortcutTarget.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tbShortcutTarget.Cursor = System.Windows.Forms.Cursors.Hand;
this.tbShortcutTarget.Location = new System.Drawing.Point(15, 134);
this.tbShortcutTarget.Location = new System.Drawing.Point(3, 102);
this.tbShortcutTarget.Name = "tbShortcutTarget";
this.tbShortcutTarget.ReadOnly = true;
this.tbShortcutTarget.Size = new System.Drawing.Size(257, 20);
this.tbShortcutTarget.TabIndex = 7;
this.tbShortcutTarget.Size = new System.Drawing.Size(260, 20);
this.tbShortcutTarget.TabIndex = 4;
this.tbShortcutTarget.Click += new System.EventHandler(this.tbShortcutTarget_Click);
//
// labelDataFolder
//
this.labelDataFolder.AutoSize = true;
this.labelDataFolder.Location = new System.Drawing.Point(12, 67);
this.labelDataFolder.Location = new System.Drawing.Point(3, 35);
this.labelDataFolder.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelDataFolder.Name = "labelDataFolder";
this.labelDataFolder.Size = new System.Drawing.Size(62, 13);
this.labelDataFolder.TabIndex = 4;
this.labelDataFolder.TabIndex = 1;
this.labelDataFolder.Text = "Data Folder";
//
// labelShortcutTarget
//
this.labelShortcutTarget.AutoSize = true;
this.labelShortcutTarget.Location = new System.Drawing.Point(12, 118);
this.labelShortcutTarget.Location = new System.Drawing.Point(3, 86);
this.labelShortcutTarget.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelShortcutTarget.Name = "labelShortcutTarget";
this.labelShortcutTarget.Size = new System.Drawing.Size(155, 13);
this.labelShortcutTarget.TabIndex = 6;
this.labelShortcutTarget.TabIndex = 3;
this.labelShortcutTarget.Text = "Shortcut Target (click to select)";
//
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.cbLogging);
this.flowPanel.Controls.Add(this.labelDataFolder);
this.flowPanel.Controls.Add(this.tbDataFolder);
this.flowPanel.Controls.Add(this.labelShortcutTarget);
this.flowPanel.Controls.Add(this.tbShortcutTarget);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Margin = new System.Windows.Forms.Padding(0);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(266, 127);
this.flowPanel.TabIndex = 0;
this.flowPanel.WrapContents = false;
//
// DialogSettingsRestart
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 198);
this.Controls.Add(this.tbShortcutTarget);
this.Controls.Add(this.labelShortcutTarget);
this.Controls.Add(this.tbDataFolder);
this.Controls.Add(this.labelDataFolder);
this.Controls.Add(this.cbDebugUpdates);
this.Controls.Add(this.cbLogging);
this.ClientSize = new System.Drawing.Size(284, 174);
this.Controls.Add(this.flowPanel);
this.Controls.Add(this.btnRestart);
this.Controls.Add(this.btnCancel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
@@ -143,8 +144,9 @@
this.Name = "DialogSettingsRestart";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
@@ -154,10 +156,10 @@
private System.Windows.Forms.Button btnRestart;
private System.Windows.Forms.CheckBox cbLogging;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.CheckBox cbDebugUpdates;
private System.Windows.Forms.Label labelDataFolder;
private System.Windows.Forms.TextBox tbDataFolder;
private System.Windows.Forms.TextBox tbShortcutTarget;
private System.Windows.Forms.Label labelShortcutTarget;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
}
}

View File

@@ -11,10 +11,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
InitializeComponent();
cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging);
cbDebugUpdates.Checked = currentArgs.HasFlag(Arguments.ArgDebugUpdates);
cbLogging.CheckedChanged += control_Change;
cbDebugUpdates.CheckedChanged += control_Change;
if (Program.IsPortable){
tbDataFolder.Text = "Not available in portable version";
@@ -36,10 +33,6 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
if (cbLogging.Checked){
Args.AddFlag(Arguments.ArgLogging);
}
if (cbDebugUpdates.Checked){
Args.AddFlag(Arguments.ArgDebugUpdates);
}
if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled){
Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text);

View File

@@ -222,9 +222,13 @@ namespace TweetDuck.Core.Other.Settings{
if (e.EventId == updateCheckEventId){
btnCheckUpdates.Enabled = true;
if (!e.IsUpdateAvailable){
FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK);
}
e.Result.Handle(update => {
if (!update.IsUpdateNew){
FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK);
}
}, ex => {
Program.Reporter.HandleException("Update Check Error", "An error occurred while checking for updates.", true, ex);
});
}
});
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;
@@ -145,6 +146,8 @@ namespace TweetDuck.Core{
UpdateProperties();
TweetDeckBridge.RestoreSessionData(frame);
ScriptLoader.ExecuteFile(frame, "code.js", browser);
ScriptLoader.ExecuteFile(frame, "update.js", browser);
InjectBrowserCSS();
ReinjectCustomCSS(Program.UserConfig.CustomBrowserCSS);
UserConfig_SoundNotificationInfoChanged(null, EventArgs.Empty);
@@ -246,5 +249,9 @@ namespace TweetDuck.Core{
public void ApplyROT13(){
browser.ExecuteScriptAsync("TDGF_applyROT13()");
}
public void ShowUpdateNotification(string versionTag, string releaseNotes){
browser.ExecuteScriptAsync("TDUF_displayNotification", versionTag, Convert.ToBase64String(Encoding.GetEncoding("iso-8859-1").GetBytes(releaseNotes)));
}
}
}

36
Data/Result.cs Normal file
View File

@@ -0,0 +1,36 @@
using System;
namespace TweetDuck.Data{
sealed class Result<T>{
public bool HasValue => exception == null;
public T Value => HasValue ? value : throw new InvalidOperationException("Requested value from a failed result.");
public Exception Exception => exception ?? throw new InvalidOperationException("Requested exception from a successful result.");
private readonly T value;
private readonly Exception exception;
public Result(T value){
this.value = value;
this.exception = null;
}
public Result(Exception exception){
this.value = default(T);
this.exception = exception ?? throw new ArgumentNullException(nameof(exception));
}
public void Handle(Action<T> onSuccess, Action<Exception> onException){
if (HasValue){
onSuccess(value);
}
else{
onException(exception);
}
}
public Result<R> Select<R>(Func<T, R> map){
return HasValue ? new Result<R>(map(value)) : new Result<R>(exception);
}
}
}

View File

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

View File

@@ -9,7 +9,7 @@ Clear columns
chylex
[version]
1.1.1
1.2
[website]
https://tweetduck.chylex.com

View File

@@ -7,12 +7,17 @@ constructor(){
enabled(){
// prepare variables and functions
var clearColumn = (columnName) => {
TD.controller.columnManager.get(columnName).clear();
let col = TD.controller.columnManager.get(columnName);
return if !col.isClearable();
col.clear();
TD.controller.stats.columnActionClick("clear");
};
var resetColumn = (columnName) => {
var col = TD.controller.columnManager.get(columnName);
let col = TD.controller.columnManager.get(columnName);
return if !col.isClearable();
col.model.setClearedTimestamp(0);
col.reloadTweets();
};
@@ -38,7 +43,7 @@ enabled(){
$(document).off("mousemove", this.eventKeyUp);
}
$("#clear-columns-btn-all").text(pressed ? "Restore columns" : "Clear columns");
$("#clear-columns-btn-all-1,#clear-columns-btn-all-2").text(pressed ? "Restore columns" : "Clear columns");
}
};
@@ -81,7 +86,7 @@ enabled(){
// add column buttons and keyboard shortcut info to UI
replaceMustache("column/column_header.mustache", "</header>", [
'{{^isTemporary}}',
'<a class="column-header-link" href="#" data-action="td-clearcolumns-dosingle" style="right:34px">',
'<a class="column-header-link td-clear-column-shortcut" href="#" data-action="td-clearcolumns-dosingle" style="right:34px">',
'<i class="icon icon-clear-timeline js-show-tip" data-placement="bottom" data-original-title="Clear column (hold Shift to restore)"></i>',
'</a>',
'{{/isTemporary}}',
@@ -98,11 +103,15 @@ enabled(){
// load custom style
var css = window.TDPF_createCustomStyle(this);
css.insert(".js-app-add-column.is-hidden + #clear-columns-btn-all-parent-1 { display: none; }");
css.insert(".column-navigator-overflow #clear-columns-btn-all-parent-2 { display: none; }");
css.insert(".column-title { margin-right: 60px !important; }");
css.insert(".column-type-message .column-title { margin-right: 115px !important; }");
css.insert(".mark-all-read-link { right: 59px !important; }");
css.insert(".open-compose-dm-link { right: 90px !important; }");
css.insert("button[data-action='clear'].btn-options-tray { display: none !important; }");
css.insert("[data-td-icon='icon-message'] .column-title { margin-right: 115px !important; }");
css.insert("[data-td-icon='icon-schedule'] .td-clear-column-shortcut { display: none; }");
css.insert("[data-td-icon='icon-custom-timeline'] .td-clear-column-shortcut { display: none; }");
}
ready(){
@@ -113,18 +122,22 @@ ready(){
$(document).on("keyup", this.eventKeyUp);
// add clear all button
$("nav.app-navigator").first().append([
'<a id="clear-columns-btn-all-parent" class="js-header-action link-clean cf app-nav-link padding-h--10" data-title="Clear columns (hold Shift to restore)" data-action="td-clearcolumns-doall">',
'<div class="obj-left margin-l--2"><i class="icon icon-medium icon-clear-timeline"></i></div>',
'<div id="clear-columns-btn-all" class="nbfc padding-ts hide-condensed txt-size--16">Clear columns</div>',
'</a></nav>'
].join(""));
const generateButton = (idParent, idButton) => {
return `
<a id="${idParent}" class="js-header-action link-clean cf app-nav-link padding-h--10" data-title="Clear columns (hold Shift to restore)" data-action="td-clearcolumns-doall">
<div class="obj-left margin-l--2"><i class="icon icon-medium icon-clear-timeline"></i></div>
<div id="${idButton}" class="nbfc padding-ts hide-condensed txt-size--16 app-nav-link-text">Clear columns</div>
</a>`;
};
$(".js-app-add-column").first().after(generateButton("clear-columns-btn-all-parent-1", "clear-columns-btn-all-1"));
$(".js-column-nav-list").first().append(generateButton("clear-columns-btn-all-parent-2", "clear-columns-btn-all-2"));
// setup tooltip handling
var tooltipEvents = $._data($(".js-header-action")[0]).events;
if (tooltipEvents.mouseover && tooltipEvents.mouseover.length && tooltipEvents.mouseout && tooltipEvents.mouseout.length){
$("#clear-columns-btn-all-parent").on("mouseover", tooltipEvents.mouseover[0].handler).on("mouseout", tooltipEvents.mouseout[0].handler);
$("#clear-columns-btn-all-parent-1,#clear-columns-btn-all-parent-2").on("mouseover", tooltipEvents.mouseover[0].handler).on("mouseout", tooltipEvents.mouseout[0].handler);
}
}

View File

@@ -92,63 +92,31 @@ html.dark .btn[disabled],html.dark .btn[disabled]:hover,html.dark .btn[disabled]
html.dark .btn-on-dark:focus{box-shadow:0 0 0 1px #292F33,0 0 0 3px #71C9F8}
html.dark .mdl-content .btn-on-dark:focus{box-shadow:0 0 0 1px #fff,0 0 0 3px #71C9F8}
html.dark .is-inverted-dark .btn:hover,html.dark .is-inverted-dark .btn:focus,html.dark .is-inverted-dark .btn:active,html.dark .is-inverted-dark .btn.is-selected{background-color:#F2F9FF}
html.dark .is-inverted-dark .btn-positive:hover,html.dark .is-inverted-dark .btn-positive-alt:hover,html.dark .is-inverted-dark .btn-fav.s-favorited:hover,html.dark .is-inverted-dark .s-following .follow-btn:hover,html.dark .s-following .is-inverted-dark .follow-btn:hover{background-color:#005FD1}
html.dark .is-inverted-dark .btn-positive:focus,html.dark .is-inverted-dark .btn-positive-alt:focus,html.dark .is-inverted-dark .btn-fav.s-favorited:focus,html.dark .is-inverted-dark .s-following .follow-btn:focus,html.dark .s-following .is-inverted-dark .follow-btn:focus{background-color:#005FD1}
html.dark .is-inverted-dark .btn-positive:active,html.dark .is-inverted-dark .btn-positive-alt:active,html.dark .is-inverted-dark .btn-fav.s-favorited:active,html.dark .is-inverted-dark .s-following .follow-btn:active,html.dark .s-following .is-inverted-dark .follow-btn:active,html.dark .is-inverted-dark .btn-positive.is-selected,html.dark .is-inverted-dark .is-selected.btn-positive-alt,html.dark .is-inverted-dark .is-selected.btn-fav.s-favorited,html.dark .is-inverted-dark .s-following .is-selected.follow-btn,html.dark .s-following .is-inverted-dark .is-selected.follow-btn{background-color:#005FD1}
html.dark .is-inverted-dark .btn-positive-alt:hover,html.dark .is-inverted-dark .btn-fav.s-favorited:hover,html.dark .is-inverted-dark .s-following .follow-btn:hover,html.dark .s-following .is-inverted-dark .follow-btn:hover{background-color:#A01744}
html.dark .is-inverted-dark .btn-positive-alt:focus,html.dark .is-inverted-dark .btn-fav.s-favorited:focus,html.dark .is-inverted-dark .s-following .follow-btn:focus,html.dark .s-following .is-inverted-dark .follow-btn:focus{background-color:#A01744}
html.dark .is-inverted-dark .btn-positive-alt:active,html.dark .is-inverted-dark .btn-fav.s-favorited:active,html.dark .is-inverted-dark .s-following .follow-btn:active,html.dark .s-following .is-inverted-dark .follow-btn:active,html.dark .is-inverted-dark .btn-positive-alt.is-selected,html.dark .is-inverted-dark .is-selected.btn-fav.s-favorited,html.dark .is-inverted-dark .s-following .is-selected.follow-btn,html.dark .s-following .is-inverted-dark .is-selected.follow-btn{background-color:#A01744}
html.dark .is-inverted-dark .btn-negative:hover{background-color:#A01744}
html.dark .is-inverted-dark .btn-negative:focus{background-color:#A01744}
html.dark .is-inverted-dark .btn-negative:active,html.dark .is-inverted-dark .btn-negative.is-selected{background-color:#A01744}
html.dark .is-inverted-dark .btn-tertiary:hover{background-color:#F5F8FA}
html.dark .is-inverted-dark .btn-tertiary:focus{background-color:#F5F8FA}
html.dark .is-inverted-dark .btn-tertiary:active,html.dark .is-inverted-dark .btn-tertiary.is-selected{background-color:#F5F8FA}
html.dark .btn-positive,html.dark .btn-positive-alt,html.dark .btn-fav.s-favorited,html.dark .s-following .follow-btn{color:#fff;background-color:#1DA1F2;border:1px solid #1DA1F2}
html.dark .btn-positive:hover,html.dark .btn-positive-alt:hover,html.dark .btn-fav.s-favorited:hover,html.dark .s-following .follow-btn:hover{color:#fff;background-color:#005FD1;border:1px solid #005FD1}
html.dark .btn-positive:focus,html.dark .btn-positive-alt:focus,html.dark .btn-fav.s-favorited:focus,html.dark .s-following .follow-btn:focus{color:#fff;background-color:#005FD1;border:1px solid #005FD1;box-shadow:0 0 0 1px #fff,0 0 0 3px #71C9F8}
html.dark .btn-positive:active,html.dark .btn-positive-alt:active,html.dark .btn-fav.s-favorited:active,html.dark .s-following .follow-btn:active,html.dark .btn-positive.is-selected,html.dark .is-selected.btn-positive-alt,html.dark .is-selected.btn-fav.s-favorited,html.dark .s-following .is-selected.follow-btn{color:#fff;background-color:#005FD1;border:1px solid #005FD1}
html.dark .btn-positive[disabled],html.dark [disabled].btn-positive-alt,html.dark [disabled].btn-fav.s-favorited,html.dark .s-following [disabled].follow-btn,html.dark .btn-positive[disabled]:hover,html.dark [disabled].btn-positive-alt:hover,html.dark [disabled].btn-fav.s-favorited:hover,html.dark .s-following [disabled].follow-btn:hover,html.dark .btn-positive[disabled]:active,html.dark [disabled].btn-positive-alt:active,html.dark [disabled].btn-fav.s-favorited:active,html.dark .s-following [disabled].follow-btn:active,html.dark .btn-positive.is-disabled,html.dark .is-disabled.btn-positive-alt,html.dark .is-disabled.btn-fav.s-favorited,html.dark .s-following .is-disabled.follow-btn,html.dark .btn-positive.is-disabled:hover,html.dark .is-disabled.btn-positive-alt:hover,html.dark .is-disabled.btn-fav.s-favorited:hover,html.dark .s-following .is-disabled.follow-btn:hover,html.dark .btn-positive.is-disabled:focus,html.dark .is-disabled.btn-positive-alt:focus,html.dark .is-disabled.btn-fav.s-favorited:focus,html.dark .s-following .is-disabled.follow-btn:focus,html.dark .btn-positive.is-disabled:active,html.dark .is-disabled.btn-positive-alt:active,html.dark .is-disabled.btn-fav.s-favorited:active,html.dark .s-following .is-disabled.follow-btn:active{color:#fff;background-color:#1DA1F2;border:1px solid #1DA1F2}
html.dark .btn-compose{color:#fff;background-color:#2b7bb9}
html.dark .btn-compose:hover{color:#fff;background-color:#2b7bb9}
html.dark .btn-compose:focus{color:#fff;background-color:#2b7bb9}
html.dark .btn-compose:active,html.dark .btn-compose.is-selected{color:#fff;background-color:#2b7bb9}
html.dark .btn-positive-alt:hover,html.dark .btn-fav.s-favorited:hover,html.dark .s-following .follow-btn:hover{color:#fff;background-color:#A01744;border:#A01744}
html.dark .btn-positive-alt:active,html.dark .btn-fav.s-favorited:active,html.dark .s-following .follow-btn:active,html.dark .btn-positive-alt.is-selected,html.dark .is-selected.btn-fav.s-favorited,html.dark .s-following .is-selected.follow-btn{color:#fff;background-color:#A01744;border:#A01744}
html.dark .btn-negative{border-color:#E0245E;color:#fff;background-color:#E0245E}
html.dark .btn-negative:hover{color:#fff;background-color:#A01744;border-color:#A01744}
html.dark .btn-negative:focus{color:#fff;background-color:#A01744;border-color:#A01744;box-shadow:0 0 0 1px #fff,0 0 0 3px #F6809A}
html.dark .btn-negative:active,html.dark .btn-negative.is-selected{color:#fff;background-color:#A01744;border-color:#A01744}
html.dark .btn-negative[disabled],html.dark .btn-negative[disabled]:hover,html.dark .btn-negative[disabled]:active,html.dark .btn-negative.is-disabled,html.dark .btn-negative.is-disabled:hover,html.dark .btn-negative.is-disabled:focus,html.dark .btn-negative.is-disabled:active{border-color:#E0245E;color:#fff;background-color:#E0245E}
html.dark .btn-tertiary{border-color:#657786;color:#657786}
html.dark .btn-tertiary:hover{color:#657786;background-color:#F5F8FA;border-color:#657786}
html.dark .btn-tertiary:focus{color:#657786;background-color:#F5F8FA;border-color:#657786;box-shadow:0 0 0 1px #fff,0 0 0 3px #CCD6DD}
html.dark .btn-tertiary:active,html.dark .btn-tertiary.is-selected{color:#657786;background-color:#F5F8FA;border-color:#657786}
html.dark .btn-tertiary[disabled],html.dark .btn-tertiary[disabled]:hover,html.dark .btn-tertiary[disabled]:active,html.dark .btn-tertiary.is-disabled,html.dark .btn-tertiary.is-disabled:hover,html.dark .btn-tertiary.is-disabled:focus,html.dark .btn-tertiary.is-disabled:active{color:#AAB8C2;border-color:#e1e8ed;background-color:#eaeaea}
html.dark .is-inverted-dark .btn-fav.s-favorited:hover,html.dark .is-inverted-dark .s-following .follow-btn:hover,html.dark .s-following .is-inverted-dark .follow-btn:hover{background-color:#005FD1}
html.dark .is-inverted-dark .btn-fav.s-favorited:focus,html.dark .is-inverted-dark .s-following .follow-btn:focus,html.dark .s-following .is-inverted-dark .follow-btn:focus{background-color:#005FD1}
html.dark .is-inverted-dark .btn-fav.s-favorited:active,html.dark .is-inverted-dark .s-following .follow-btn:active,html.dark .s-following .is-inverted-dark .follow-btn:active,html.dark .is-inverted-dark .is-selected.btn-fav.s-favorited,html.dark .is-inverted-dark .s-following .is-selected.follow-btn,html.dark .s-following .is-inverted-dark .is-selected.follow-btn{background-color:#005FD1}
html.dark .is-inverted-dark .btn-fav.s-favorited:hover,html.dark .is-inverted-dark .s-following .follow-btn:hover,html.dark .s-following .is-inverted-dark .follow-btn:hover{background-color:#A01744}
html.dark .is-inverted-dark .btn-fav.s-favorited:focus,html.dark .is-inverted-dark .s-following .follow-btn:focus,html.dark .s-following .is-inverted-dark .follow-btn:focus{background-color:#A01744}
html.dark .is-inverted-dark .btn-fav.s-favorited:active,html.dark .is-inverted-dark .s-following .follow-btn:active,html.dark .s-following .is-inverted-dark .follow-btn:active,html.dark .is-inverted-dark .is-selected.btn-fav.s-favorited,html.dark .is-inverted-dark .s-following .is-selected.follow-btn,html.dark .s-following .is-inverted-dark .is-selected.follow-btn{background-color:#A01744}
html.dark .btn-fav.s-favorited,html.dark .s-following .follow-btn{color:#fff;background-color:#1DA1F2;border:1px solid #1DA1F2}
html.dark .btn-fav.s-favorited:hover,html.dark .s-following .follow-btn:hover{color:#fff;background-color:#005FD1;border:1px solid #005FD1}
html.dark .btn-fav.s-favorited:focus,html.dark .s-following .follow-btn:focus{color:#fff;background-color:#005FD1;border:1px solid #005FD1;box-shadow:0 0 0 1px #fff,0 0 0 3px #71C9F8}
html.dark .btn-fav.s-favorited:active,html.dark .s-following .follow-btn:active,html.dark .is-selected.btn-fav.s-favorited,html.dark .s-following .is-selected.follow-btn{color:#fff;background-color:#005FD1;border:1px solid #005FD1}
html.dark [disabled].btn-fav.s-favorited,html.dark .s-following [disabled].follow-btn,html.dark [disabled].btn-fav.s-favorited:hover,html.dark .s-following [disabled].follow-btn:hover,html.dark [disabled].btn-fav.s-favorited:active,html.dark .s-following [disabled].follow-btn:active,html.dark .is-disabled.btn-fav.s-favorited,html.dark .s-following .is-disabled.follow-btn,html.dark .is-disabled.btn-fav.s-favorited:hover,html.dark .s-following .is-disabled.follow-btn:hover,html.dark .is-disabled.btn-fav.s-favorited:focus,html.dark .s-following .is-disabled.follow-btn:focus,html.dark .is-disabled.btn-fav.s-favorited:active,html.dark .s-following .is-disabled.follow-btn:active{color:#fff;background-color:#1DA1F2;border:1px solid #1DA1F2}
html.dark .btn-fav.s-favorited:hover,html.dark .s-following .follow-btn:hover{color:#fff;background-color:#A01744;border:#A01744}
html.dark .btn-fav.s-favorited:active,html.dark .s-following .follow-btn:active,html.dark .is-selected.btn-fav.s-favorited,html.dark .s-following .is-selected.follow-btn{color:#fff;background-color:#A01744;border:#A01744}
html.dark .btn-on-blue{color:#fff;background-color:#66757f}
html.dark .btn-on-blue:hover{color:#fff;background-color:#66757f}
html.dark .btn-on-blue:focus{color:#fff;background-color:#66757f;box-shadow:0 0 2px 3px #50a5e6}
html.dark .btn-on-blue:active,html.dark .btn-on-blue.is-selected{color:#fff;background-color:#434c51}
html.dark .btn-on-blue[disabled],html.dark .btn-on-blue[disabled]:hover,html.dark .btn-on-blue[disabled]:active,html.dark .btn-on-blue.is-disabled,html.dark .btn-on-blue.is-disabled:hover,html.dark .btn-on-blue.is-disabled:focus,html.dark .btn-on-blue.is-disabled:active{color:#fff;background-color:#66757f}
html.dark .btn-neutral-negative{color:#d29b9a}
html.dark .btn-neutral-negative:hover,html.dark .btn-neutral-negative:focus{color:#d29b9a}
html.dark .btn-neutral-negative[disabled],html.dark .btn-neutral-negative[disabled]:hover,html.dark .btn-neutral-negative[disabled]:active,html.dark .btn-neutral-negative.is-disabled,html.dark .btn-neutral-negative.is-disabled:hover,html.dark .btn-neutral-negative.is-disabled:focus,html.dark .btn-neutral-negative.is-disabled:active{color:#d29b9a}
html.dark .btn-neutral-positive{color:#8bd}
html.dark .btn-neutral-positive:hover,html.dark .btn-neutral-positive:focus{color:#8bd}
html.dark .btn-neutral-positive[disabled],html.dark .btn-neutral-positive[disabled]:hover,html.dark .btn-neutral-positive[disabled]:active,html.dark .btn-neutral-positive.is-disabled,html.dark .btn-neutral-positive.is-disabled:hover,html.dark .btn-neutral-positive.is-disabled:focus,html.dark .btn-neutral-positive.is-disabled:active{color:#8bd}
html.dark .btn-options-tray{color:#e1e8ed}
html.dark .btn-options-tray:hover,html.dark .btn-options-tray:focus{color:#8bd}
html.dark .btn-options-tray[disabled],html.dark .btn-options-tray[disabled]:hover,html.dark .btn-options-tray[disabled]:active,html.dark .btn-options-tray.is-disabled,html.dark .btn-options-tray.is-disabled:hover,html.dark .btn-options-tray.is-disabled:focus,html.dark .btn-options-tray.is-disabled:active{color:#8bd}
html.dark .btn-bg-positive{background-color:rgba(102,117,127,0.5)}
html.dark .btn-bg-positive:hover,html.dark .btn-bg-positive:focus{background-color:rgba(102,117,127,0.5)}
html.dark .btn-bg-positive[disabled],html.dark .btn-bg-positive[disabled]:hover,html.dark .btn-bg-positive[disabled]:active,html.dark .btn-bg-positive.is-disabled,html.dark .btn-bg-positive.is-disabled:hover,html.dark .btn-bg-positive.is-disabled:focus,html.dark .btn-bg-positive.is-disabled:active{background-color:rgba(102,117,127,0.5)}
html.dark .btn-bg-negative{background-color:#5d5457}
html.dark .btn-bg-negative:hover,html.dark .btn-bg-negative:focus{background-color:#5d5457}
html.dark .btn-bg-negative[disabled],html.dark .btn-bg-negative[disabled]:hover,html.dark .btn-bg-negative[disabled]:active,html.dark .btn-bg-negative.is-disabled,html.dark .btn-bg-negative.is-disabled:hover,html.dark .btn-bg-negative.is-disabled:focus,html.dark .btn-bg-negative.is-disabled:active{background-color:#5d5457}
html.dark .btn-bg-white{background-color:#fff;color:#55acee}
html.dark .btn-bg-white:hover,html.dark .btn-bg-white:focus{background-color:#fff;color:#55acee}
html.dark .follow-btn .icon,html.dark .follow-btn .Icon{color:#1DA1F2}
html.dark .input-group-button{border:1px solid #e1e8ed}
html.dark .account-profile-header{background-color:#1DA1F2}
html.dark .account-settings-bt{border-top:1px solid #e1e8ed}
html.dark .account-settings-bb{border-bottom:1px solid #e1e8ed}
@@ -440,12 +408,6 @@ html.dark .char-count:disabled{color:#777}
html.dark .over-char-count:disabled{color:#be1931}
html.dark .cmp-replyto{background-color:#eaeaea;border-top:1px solid #ddd}
html.dark .s-link-added.s-photo-added p:last-child{border-top:1px solid #ddd}
html.dark .accs li{background:#eaeaea;border:1px solid #e1e8ed}
html.dark .accs li:hover{background:#e1e8ed}
html.dark .accs .icon,html.dark .accs .Icon{color:#999}
html.dark .accs .acc-selected{background-color:#55acee;border:1px solid #e1e8ed}
html.dark .accs .acc-selected i{color:#fff}
html.dark .accs .acc-selected:hover{border-color:#e1e8ed;background-color:#50a5e6}
html.dark .inline-reply{background-color:#485865;color:#fff}
html.dark .inline-reply .btn-neutral,html.dark .inline-reply .character-count{color:#fff}
html.dark .reply-triangle{border-color:transparent transparent #485865}

View File

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

View File

@@ -12,10 +12,16 @@ enabled(){
if (configuration.useAdvancedSelector){
if (configuration.customSelector){
if (configuration.customSelector.toString().startsWith("function (column){")){
let customSelectorDef = configuration.customSelector.toString();
if (customSelectorDef.startsWith("function (column){")){
$TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector needs to be updated due to TweetDeck changes, please read the default configuration file for the updated guide");
return;
}
else if (customSelectorDef.startsWith("function (type,")){
$TD.alert("warning", "Plugin reply-account has invalid configuration: the type parameter is no longer present due to TweetDeck changes, please read the default configuration file for the updated guide");
return;
}
var section = data.element.closest("section.js-column");
@@ -35,7 +41,7 @@ enabled(){
}
try{
query = configuration.customSelector(column.getColumnType(), columnTitle, columnAccount, column, section.hasClass("column-temp"));
query = configuration.customSelector(columnTitle, columnAccount, column, section.hasClass("column-temp"));
}catch(e){
$TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector threw an error: "+e.message);
return;

View File

@@ -30,14 +30,19 @@
* https://tweetduck.chylex.com/guide/#dev-tools
*
*
* The 'type' parameter is TweetDeck column type. Here is the full list of column types, note that some are
* unused and have misleading names (for example, Home columns are 'col_timeline' instead of 'col_home'):
* col_timeline, col_interactions, col_mentions, col_followers, col_search, col_list,
* col_customtimeline, col_messages, col_usertweets, col_favorites, col_activity,
* col_dataminr, col_home, col_me, col_inbox, col_scheduled, col_unknown
* In order to check the column type, use the 'column.isOfType' function. It is recommended to always put it
* last in an 'if' statement, because it is much more demanding than checking the title/account.
*
* Here is the full list of column types, note that some are unused and have misleading names.
* (for example, Home columns are 'col_timeline' instead of 'col_home')
*
* col_activity, col_customtimeline, col_dataminr, col_favorites, col_followers, col_home,
* col_inbox, col_interactions, col_list, col_livevideo, col_me, col_mentions,
* col_messages, col_scheduled, col_search, col_timeline, col_usertweets, col_unknown
*
* If you want to see your current column types, run this in your browser console:
* TD.controller.columnManager.getAllOrdered().map(obj => obj.getColumnType());
*
* (c=>c.columnManager.getAllOrdered().map(o=>Object.keys(c.stats.columnNamespaces).find(t=>o.isOfType(t))).map(t=>t==""+void 0?"col_unknown":t))(TD.controller)
*
*
* The 'title' parameter is the column title. Some are fixed (such as 'Home' or 'Notifications'),
@@ -61,16 +66,16 @@
useAdvancedSelector: false,
customSelector: function(type, title, account, column, isTemporary){
customSelector: function(title, account, column, isTemporary){
console.info(arguments); // Prints all arguments into the console
if (type === "col_search" && title === "TweetDuck"){
if (title === "TweetDuck" && column.isOfType("col_search")){
// This is a search column that looks for 'TweetDuck' in the tweets,
// search columns are normally linked to the preferred account
// so this forces the @TryTweetDuck account to be used instead
return "@TryTweetDuck";
}
else if (type === "col_timeline" && account === "@chylexcz"){
else if (account === "@chylexcz" && column.isOfType("col_timeline")){
// This is a Home column of my test account @chylexcz,
// but I want to reply to tweets from my official account
return "@chylexmc";

View File

@@ -98,6 +98,13 @@
return value;
};
//
// Function: Retrieves column name
const getColumnName = function(column){
let cached = column._tduck_type || (column._tduck_type = Object.keys(columnTypes).find(type => column.isOfType(type)));
return columnTypes[cached] || "";
};
//
// Function: Event callback for a new tweet.
//
@@ -220,7 +227,7 @@
let tweetUrl = source ? source.getChirpURL() : "";
let quoteUrl = source && source.quotedTweet ? source.quotedTweet.getChirpURL() : "";
$TD.onTweetPopup(column.model.privateState.apiid, chirpId, columnTypes[column.getColumnType()] || "", html.html(), duration, tweetUrl, quoteUrl);
$TD.onTweetPopup(column.model.privateState.apiid, chirpId, getColumnName(column), html.html(), duration, tweetUrl, quoteUrl);
}
if (column.model.getHasSound()){
@@ -1310,6 +1317,11 @@
};
}
//
// Block: Fix columns missing any identifiable attributes to allow individual styles.
//
TD.mustaches["column.mustache"] = TD.mustaches["column.mustache"].replace("{{columnclass}}\"", "{{columnclass}}\" data-td-icon=\"{{columniconclass}}\"");
//
// Block: Remove column mouse wheel handler, which allows smooth scrolling inside columns, and horizontally scrolling column container when holding Shift.
//

View File

@@ -98,7 +98,7 @@
</footer>
</div>
</div>
</div>`).appendTo(".js-app");
</div>`).appendTo(".js-app"); /* TODO btn-positive is removed, check all files again */
let tdUser = null;
let loadTweetDuckUser = (onSuccess, onError) => {

View File

@@ -1,6 +1,6 @@
(function($TD){
let ele = document.getElementsByTagName("article")[0];
ele.style.width = window.innerWidth+"px";
ele.style.width = "{width}px";
ele.style.position = "absolute";
let contentHeight = ele.offsetHeight;
@@ -9,6 +9,7 @@
let avatar = ele.querySelector(".tweet-avatar");
let avatarBottom = avatar ? avatar.getBoundingClientRect().bottom : 0;
$TD.setHeight(Math.floor(Math.max(contentHeight, avatarBottom+9)));
setTimeout($TD.triggerScreenshot, document.getElementsByTagName("iframe").length ? 267 : 67);
$TD.setHeight(Math.floor(Math.max(contentHeight, avatarBottom+9))).then(() => {
setTimeout($TD.triggerScreenshot, document.getElementsByTagName("iframe").length ? 267 : 67);
});
})($TD_NotificationScreenshot);

View File

@@ -50,7 +50,7 @@ button, .btn, .mdl, .mdl-content, .popover, .lst-modal, .tooltip-inner {
border-radius: 0 !important;
}
.dropdown-menu, .list-item-last, .quoted-tweet, .input-group-button, input, textarea, select, .prf-header, .accs li, .accs img {
.dropdown-menu, .list-item-last, .quoted-tweet, input, textarea, select, .prf-header {
border-radius: 0 !important;
}
@@ -261,11 +261,6 @@ html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
background-position: -223px -97px !important;
}
.accs-header {
/* fix retweet account selector heading */
padding-left: 0 !important;
}
.keyboard-shortcut-list {
/* fix keyboard navigation alignment */
vertical-align: top !important;
@@ -276,12 +271,6 @@ html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
cursor: pointer;
}
.inline-reply .btn-square, .rpl-actions .btn-square {
/* remove effects from buttons under reply input... this keeps happening for some stupid reason */
background: transparent !important;
box-shadow: none !important;
}
.js-add-to-customtimeline-input {
/* the custom timeline input shadow is behaving super weird when focused */
box-shadow: none !important;
@@ -361,18 +350,18 @@ html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
/* Fix cut off usernames in Messages column */
/********************************************/
.column-type-message.is-shifted-1 .column-title-container {
[data-td-icon="icon-message"].is-shifted-1 .column-title-container {
height: 100%;
border-bottom-color: transparent;
}
#tduck .column-type-message.is-shifted-1 .column-title-items {
#tduck [data-td-icon="icon-message"].is-shifted-1 .column-title-items {
height: 100%;
margin-left: 4px !important;
padding-top: 1px;
}
.column-type-message.is-shifted-1 .username {
[data-td-icon="icon-message"].is-shifted-1 .username {
vertical-align: bottom;
}

View File

@@ -1,33 +1,9 @@
(function($, $TDU){
//
// Variable: Current timeout ID for update checking.
//
var updateCheckTimeoutID;
//
// Constant: Update exe file name.
//
const updateFileName = "TweetDuck.Update.exe";
//
// Constant: Url that returns JSON data about latest version.
//
const updateCheckUrlLatest = "https://api.github.com/repos/chylex/TweetDuck/releases/latest";
//
// Constant: Url that returns JSON data about all versions, including prereleases.
//
const updateCheckUrlAll = "https://api.github.com/repos/chylex/TweetDuck/releases";
//
// Constant: Fallback url in case the update installer file is missing.
//
const updateDownloadFallback = "https://tweetduck.chylex.com";
//
// Function: Creates the update notification element. Removes the old one if already exists.
//
var displayNotification = function(version, download, changelog){
var displayNotification = function(version, changelog){
// styles
var css = $("#tweetduck-update-css");
@@ -167,7 +143,7 @@
<div id='tweetduck-changelog'>
<div id='tweetduck-changelog-box'>
<h2>TweetDuck Update ${version}</h2>
${changelog}
${markdown(atob(changelog))}
</div>
</div>
`).appendTo(document.body).css("display", "none");
@@ -219,17 +195,11 @@
buttonDiv.children(".tdu-btn-download").click(function(){
hide();
if (download){
$TDU.onUpdateAccepted();
}
else{
$TDU.openBrowser(updateDownloadFallback);
}
$TDU.onUpdateAccepted();
});
buttonDiv.children(".tdu-btn-later").click(function(){
clearTimeout(updateCheckTimeoutID);
$TDU.onUpdateDelayed();
slide();
});
@@ -245,15 +215,6 @@
return ele;
};
//
// Function: Returns milliseconds until the start of the next hour, with an extra offset in seconds that can skip an hour if the clock would roll over too soon.
//
var getTimeUntilNextHour = function(extra){
var now = new Date();
var offset = new Date(+now+extra*1000);
return new Date(offset.getFullYear(), offset.getMonth(), offset.getDate(), offset.getHours()+1, 0, 0)-now;
};
//
// Function: Ghetto-converts markdown to HTML.
//
@@ -273,33 +234,6 @@
.replace(/\n\r?\n\r?/g, "<br>");
};
//
// Function: Runs an update check and updates all DOM elements appropriately.
//
var runUpdateCheck = function(eventID, versionTag, dismissedVersionTag, allowPre){
clearTimeout(updateCheckTimeoutID);
updateCheckTimeoutID = setTimeout($TDU.triggerUpdateCheck, getTimeUntilNextHour(60*30)); // 30 minute offset
$.getJSON(allowPre ? updateCheckUrlAll : updateCheckUrlLatest, function(response){
var release = allowPre ? response[0] : response;
var tagName = release.tag_name;
var hasUpdate = tagName !== versionTag && tagName !== dismissedVersionTag && release.assets.length > 0;
if (hasUpdate){
var obj = release.assets.find(asset => asset.name === updateFileName) || { browser_download_url: "" };
displayNotification(tagName, obj.browser_download_url, markdown(release.body));
if (eventID){ // ignore undefined and 0
$TDU.onUpdateCheckFinished(eventID, tagName, obj.browser_download_url);
}
}
else if (eventID){ // ignore undefined and 0
$TDU.onUpdateCheckFinished(eventID, null, null);
}
});
};
//
// Block: Check updates on startup.
//
@@ -310,5 +244,5 @@
//
// Block: Setup global functions.
//
window.TDUF_runUpdateCheck = runUpdateCheck;
window.TDUF_displayNotification = displayNotification;
})($, $TDU);

View File

@@ -0,0 +1,26 @@
using System.Text.RegularExpressions;
HashSet<string> ReadSelectors(string file){
return new HashSet<string>(
File.ReadAllLines(file)
.Where(line => line.Contains('{'))
.Select(line => line.Substring(0, line.IndexOf('{')).Trim())
.SelectMany(lines => lines.Split(new char[]{ ',', ' ' }, StringSplitOptions.RemoveEmptyEntries))
);
}
HashSet<string> ExtractClasses(HashSet<string> selectors){
return new HashSet<string>(
selectors.SelectMany(selector => Regex.Matches(selector, @"\.[a-zA-Z0-9_-]+").Cast<Match>().Select(match => match.Value))
);
}
void PrintAll(IEnumerable<string> data){
foreach(string line in data){
Print(line);
}
}
void PrintMissing(HashSet<string> all, HashSet<string> subset){
PrintAll(subset.Where(ele => !all.Contains(ele)));
}

View File

@@ -69,6 +69,7 @@
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Management" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
@@ -259,6 +260,7 @@
</Compile>
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
<Compile Include="Data\ResourceLink.cs" />
<Compile Include="Data\Result.cs" />
<Compile Include="Data\Serialization\FileSerializer.cs" />
<Compile Include="Data\InjectedHTML.cs" />
<Compile Include="Data\Serialization\ITypeConverter.cs" />
@@ -315,6 +317,7 @@
<Compile Include="Core\Management\BrowserCache.cs" />
<Compile Include="Core\Utils\BrowserUtils.cs" />
<Compile Include="Core\Utils\NativeMethods.cs" />
<Compile Include="Updates\UpdateCheckClient.cs" />
<Compile Include="Updates\UpdateDownloadStatus.cs" />
<Compile Include="Updates\UpdateHandler.cs" />
<Compile Include="Updates\UpdateInfo.cs" />

View File

@@ -1,13 +1,14 @@
using System;
using TweetDuck.Data;
namespace TweetDuck.Updates.Events{
sealed class UpdateCheckEventArgs : EventArgs{
public int EventId { get; }
public bool IsUpdateAvailable { get; }
public Result<UpdateInfo> Result { get; }
public UpdateCheckEventArgs(int eventId, bool isUpdateAvailable){
public UpdateCheckEventArgs(int eventId, Result<UpdateInfo> result){
this.EventId = eventId;
this.IsUpdateAvailable = isUpdateAvailable;
this.Result = result;
}
}
}

View File

@@ -1,7 +1,5 @@
using System;
using System.Windows.Forms;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils;
namespace TweetDuck.Updates{
sealed partial class FormUpdateDownload : Form{
@@ -22,21 +20,11 @@ namespace TweetDuck.Updates{
}
private void timerDownloadCheck_Tick(object sender, EventArgs e){
if (updateInfo.DownloadStatus == UpdateDownloadStatus.Done){
if (updateInfo.DownloadStatus.IsFinished()){
timerDownloadCheck.Stop();
DialogResult = DialogResult.OK;
Close();
}
else if (updateInfo.DownloadStatus == UpdateDownloadStatus.Failed){
timerDownloadCheck.Stop();
if (FormMessage.Error("Update Has Failed", "Could not download the update: "+(updateInfo.DownloadError?.Message ?? "unknown error")+"\n\nDo you want to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)){
BrowserUtils.OpenExternalBrowser(Program.Website);
DialogResult = DialogResult.OK;
}
Close();
}
}
}
}

View File

@@ -0,0 +1,63 @@
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web.Script.Serialization;
using TweetDuck.Core.Utils;
using JsonObject = System.Collections.Generic.IDictionary<string, object>;
namespace TweetDuck.Updates{
sealed class UpdateCheckClient{
private const string ApiLatestRelease = "https://api.github.com/repos/chylex/TweetDuck/releases/latest";
private const string UpdaterAssetName = "TweetDuck.Update.exe";
private readonly UpdaterSettings settings;
public UpdateCheckClient(UpdaterSettings settings){
this.settings = settings;
}
public Task<UpdateInfo> Check(){
TaskCompletionSource<UpdateInfo> result = new TaskCompletionSource<UpdateInfo>();
WebClient client = BrowserUtils.CreateWebClient();
client.Headers[HttpRequestHeader.Accept] = "application/vnd.github.v3+json";
client.DownloadStringTaskAsync(ApiLatestRelease).ContinueWith(task => {
if (task.IsCanceled){
result.SetCanceled();
}
else if (task.IsFaulted){
result.SetException(task.Exception.InnerException);
}
else{
try{
result.SetResult(ParseFromJson(task.Result));
}catch(Exception e){
result.SetException(e);
}
}
});
return result.Task;
}
private UpdateInfo ParseFromJson(string json){
bool IsUpdaterAsset(JsonObject obj){
return UpdaterAssetName == (string)obj["name"];
}
string AssetDownloadUrl(JsonObject obj){
return (string)obj["browser_download_url"];
}
JsonObject root = (JsonObject)new JavaScriptSerializer().DeserializeObject(json);
string versionTag = (string)root["tag_name"];
string releaseNotes = (string)root["body"];
string downloadUrl = ((Array)root["assets"]).Cast<JsonObject>().Where(IsUpdaterAsset).Select(AssetDownloadUrl).FirstOrDefault();
return new UpdateInfo(settings, versionTag, releaseNotes, downloadUrl);
}
}
}

View File

@@ -2,7 +2,15 @@
public enum UpdateDownloadStatus{
None = 0,
InProgress,
Done,
Failed
Canceled,
AssetMissing,
Failed,
Done
}
public static class UpdateDownloadStatusExtensions{
public static bool IsFinished(this UpdateDownloadStatus status){
return status == UpdateDownloadStatus.AssetMissing || status == UpdateDownloadStatus.Done || status == UpdateDownloadStatus.Failed;
}
}
}

View File

@@ -1,21 +1,23 @@
using CefSharp;
using System;
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other.Interfaces;
using TweetDuck.Core.Utils;
using TweetDuck.Resources;
using TweetDuck.Data;
using TweetDuck.Updates.Events;
namespace TweetDuck.Updates{
sealed class UpdateHandler{
sealed class UpdateHandler : IDisposable{
public const int CheckCodeUpdatesDisabled = -1;
public const int CheckCodeNotOnTweetDeck = -2;
private readonly ITweetDeckBrowser browser;
private readonly UpdaterSettings settings;
private readonly UpdateCheckClient client;
private readonly ITweetDeckBrowser browser;
private readonly Timer timer;
public event EventHandler<UpdateEventArgs> UpdateAccepted;
public event EventHandler<UpdateEventArgs> UpdateDelayed;
public event EventHandler<UpdateEventArgs> UpdateDismissed;
public event EventHandler<UpdateCheckEventArgs> CheckFinished;
@@ -23,15 +25,43 @@ namespace TweetDuck.Updates{
private UpdateInfo lastUpdateInfo;
public UpdateHandler(ITweetDeckBrowser browser, UpdaterSettings settings){
this.browser = browser;
this.settings = settings;
this.client = new UpdateCheckClient(settings);
this.browser = browser;
this.browser.RegisterBridge("$TDU", new Bridge(this));
browser.OnFrameLoaded(OnFrameLoaded);
browser.RegisterBridge("$TDU", new Bridge(this));
this.timer = new Timer();
this.timer.Tick += timer_Tick;
}
private void OnFrameLoaded(IFrame frame){
ScriptLoader.ExecuteFile(frame, "update.js"); // TODO can't show error on failure
public void Dispose(){
timer.Dispose();
}
private void timer_Tick(object sender, EventArgs e){
timer.Stop();
Check(false);
}
public void StartTimer(){
if (timer.Enabled){
return;
}
timer.Stop();
if (Program.UserConfig.EnableUpdateCheck){
DateTime now = DateTime.Now;
TimeSpan nextHour = now.AddSeconds(60*(60-now.Minute)-now.Second)-now;
if (nextHour.TotalMinutes < 15){
nextHour = nextHour.Add(TimeSpan.FromHours(1));
}
timer.Interval = (int)Math.Ceiling(nextHour.TotalMilliseconds);
timer.Start();
}
}
public int Check(bool force){
@@ -43,17 +73,24 @@ namespace TweetDuck.Updates{
if (!browser.IsTweetDeckWebsite){
return CheckCodeNotOnTweetDeck;
}
int nextEventId = unchecked(++lastEventId);
Task<UpdateInfo> checkTask = client.Check();
browser.ExecuteFunction("TDUF_runUpdateCheck", (int)unchecked(++lastEventId), Program.VersionTag, settings.DismissedUpdate ?? string.Empty, settings.AllowPreReleases);
return lastEventId;
checkTask.ContinueWith(task => HandleUpdateCheckSuccessful(nextEventId, task.Result), TaskContinuationOptions.OnlyOnRanToCompletion);
checkTask.ContinueWith(task => HandleUpdateCheckFailed(nextEventId, task.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted);
return nextEventId;
}
return CheckCodeUpdatesDisabled;
}
public void BeginUpdateDownload(Form ownerForm, UpdateInfo updateInfo, Action<UpdateInfo> onSuccess){
if (updateInfo.DownloadStatus == UpdateDownloadStatus.Done){
onSuccess(updateInfo);
public void BeginUpdateDownload(Form ownerForm, UpdateInfo updateInfo, Action<UpdateInfo> onFinished){
UpdateDownloadStatus status = updateInfo.DownloadStatus;
if (status == UpdateDownloadStatus.Done || status == UpdateDownloadStatus.AssetMissing){
onFinished(updateInfo);
}
else{
FormUpdateDownload downloadForm = new FormUpdateDownload(updateInfo);
@@ -64,14 +101,12 @@ namespace TweetDuck.Updates{
};
downloadForm.FormClosed += (sender, args) => {
if (downloadForm.DialogResult != DialogResult.OK){
updateInfo.CancelDownload();
}
downloadForm.Dispose();
if (downloadForm.DialogResult == DialogResult.OK){ // success or manual download
onSuccess(updateInfo);
}
else{
ownerForm.Show();
}
onFinished(updateInfo);
};
downloadForm.Show();
@@ -85,17 +120,39 @@ namespace TweetDuck.Updates{
}
}
private void TriggerUpdateAcceptedEvent(UpdateEventArgs args){
UpdateAccepted?.Invoke(this, args);
private void HandleUpdateCheckSuccessful(int eventId, UpdateInfo info){
if (info.IsUpdateNew && !info.IsUpdateDismissed){
CleanupDownload();
lastUpdateInfo = info;
lastUpdateInfo.BeginSilentDownload();
}
CheckFinished?.Invoke(this, new UpdateCheckEventArgs(eventId, new Result<UpdateInfo>(info)));
}
private void TriggerUpdateDismissedEvent(UpdateEventArgs args){
settings.DismissedUpdate = args.UpdateInfo.VersionTag;
UpdateDismissed?.Invoke(this, args);
private void HandleUpdateCheckFailed(int eventId, Exception exception){
CheckFinished?.Invoke(this, new UpdateCheckEventArgs(eventId, new Result<UpdateInfo>(exception)));
}
private void TriggerCheckFinishedEvent(UpdateCheckEventArgs args){
CheckFinished?.Invoke(this, args);
private void TriggerUpdateAcceptedEvent(){
if (lastUpdateInfo != null){
UpdateAccepted?.Invoke(this, new UpdateEventArgs(lastUpdateInfo));
}
}
private void TriggerUpdateDelayedEvent(){
if (lastUpdateInfo != null){
UpdateDelayed?.Invoke(this, new UpdateEventArgs(lastUpdateInfo));
}
}
private void TriggerUpdateDismissedEvent(){
if (lastUpdateInfo != null){
settings.DismissedUpdate = lastUpdateInfo.VersionTag;
UpdateDismissed?.Invoke(this, new UpdateEventArgs(lastUpdateInfo));
CleanupDownload();
}
}
public sealed class Bridge{
@@ -109,31 +166,16 @@ namespace TweetDuck.Updates{
owner.Check(false);
}
public void OnUpdateCheckFinished(int eventId, string versionTag, string downloadUrl){
if (versionTag != null && (owner.lastUpdateInfo == null || owner.lastUpdateInfo.VersionTag != versionTag)){
owner.CleanupDownload();
owner.lastUpdateInfo = new UpdateInfo(owner.settings, eventId, versionTag, downloadUrl);
owner.lastUpdateInfo.BeginSilentDownload();
}
owner.TriggerCheckFinishedEvent(new UpdateCheckEventArgs(eventId, owner.lastUpdateInfo != null));
public void OnUpdateAccepted(){
owner.TriggerUpdateAcceptedEvent();
}
public void OnUpdateAccepted(){
if (owner.lastUpdateInfo != null){
owner.TriggerUpdateAcceptedEvent(new UpdateEventArgs(owner.lastUpdateInfo));
}
public void OnUpdateDelayed(){
owner.TriggerUpdateDelayedEvent();
}
public void OnUpdateDismissed(){
if (owner.lastUpdateInfo != null){
owner.TriggerUpdateDismissedEvent(new UpdateEventArgs(owner.lastUpdateInfo));
owner.CleanupDownload();
}
}
public void OpenBrowser(string url){
BrowserUtils.OpenExternalBrowser(url);
owner.TriggerUpdateDismissedEvent();
}
}
}

View File

@@ -5,40 +5,43 @@ using TweetDuck.Core.Utils;
namespace TweetDuck.Updates{
sealed class UpdateInfo{
public int EventId { get; }
public string VersionTag { get; }
public string ReleaseNotes { get; }
public string InstallerPath { get; }
public bool IsUpdateNew => VersionTag != Program.VersionTag;
public bool IsUpdateDismissed => VersionTag == settings.DismissedUpdate;
public UpdateDownloadStatus DownloadStatus { get; private set; }
public Exception DownloadError { get; private set; }
private readonly string installerFolder;
private readonly UpdaterSettings settings;
private readonly string downloadUrl;
private WebClient currentDownload;
public UpdateInfo(UpdaterSettings settings, int eventId, string versionTag, string downloadUrl){
this.installerFolder = settings.InstallerDownloadFolder;
public UpdateInfo(UpdaterSettings settings, string versionTag, string releaseNotes, string downloadUrl){
this.settings = settings;
this.downloadUrl = downloadUrl;
this.EventId = eventId;
this.VersionTag = versionTag;
this.InstallerPath = Path.Combine(installerFolder, "TweetDuck."+versionTag+".exe");
this.ReleaseNotes = releaseNotes;
this.InstallerPath = Path.Combine(settings.InstallerDownloadFolder, "TweetDuck."+versionTag+".exe");
}
public void BeginSilentDownload(){
if (DownloadStatus == UpdateDownloadStatus.None || DownloadStatus == UpdateDownloadStatus.Failed){
DownloadStatus = UpdateDownloadStatus.InProgress;
try{
Directory.CreateDirectory(installerFolder);
}catch(Exception e){
DownloadError = e;
DownloadStatus = UpdateDownloadStatus.Failed;
if (string.IsNullOrEmpty(downloadUrl)){
DownloadError = new InvalidDataException("Missing installer asset.");
DownloadStatus = UpdateDownloadStatus.AssetMissing;
return;
}
if (string.IsNullOrEmpty(downloadUrl)){
DownloadError = new UriFormatException("Could not determine URL of the update installer");
try{
Directory.CreateDirectory(settings.InstallerDownloadFolder);
}catch(Exception e){
DownloadError = e;
DownloadStatus = UpdateDownloadStatus.Failed;
return;
}
@@ -68,5 +71,18 @@ namespace TweetDuck.Updates{
// rip
}
}
public void CancelDownload(){
DeleteInstaller();
DownloadStatus = UpdateDownloadStatus.Canceled;
}
public override bool Equals(object obj){
return obj is UpdateInfo info && VersionTag == info.VersionTag;
}
public override int GetHashCode(){
return VersionTag.GetHashCode();
}
}
}

View File

@@ -1,8 +1,7 @@
namespace TweetDuck.Updates{
sealed class UpdaterSettings{
public string InstallerDownloadFolder { get; }
public bool AllowPreReleases { get; set; }
public string DismissedUpdate { get; set; }
public UpdaterSettings(string installerDownloadFolder){

View File

@@ -4,5 +4,4 @@
<package id="cef.redist.x86" version="3.3282.1731" targetFramework="net452" xmlns="" />
<package id="CefSharp.Common" version="64.0.0-CI2508" targetFramework="net452" xmlns="" />
<package id="CefSharp.WinForms" version="64.0.0-CI2508" targetFramework="net452" xmlns="" />
<package id="Microsoft.VC120.CRT.JetBrains" version="12.0.21005.2" targetFramework="net452" xmlns="" />
</packages>