1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-04-09 06:15:49 +02:00

Fix screenshotting wrong tweet if cursor moves away from right-clicked tweet too quickly

This commit is contained in:
chylex 2021-12-24 09:44:22 +01:00
parent a8e7f065cf
commit dfde38ea3b
Signed by: chylex
GPG Key ID: 4DE42C8F19A80548
12 changed files with 146 additions and 89 deletions

View File

@ -60,8 +60,8 @@ public void SetRightClickedLink(string type, string url) {
ContextMenuBase.CurrentInfo.SetLink(type, url);
}
public void SetRightClickedChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages) {
ContextMenuBase.CurrentInfo.SetChirp(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
public void SetRightClickedChirp(string columnId, string chirpId, string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages) {
ContextMenuBase.CurrentInfo.SetChirp(columnId, chirpId, tweetUrl, quoteUrl, chirpAuthors, chirpImages);
}
public void DisplayTooltip(string text) {

View File

@ -15,8 +15,8 @@ public void SetLink(string type, string url) {
link = string.IsNullOrEmpty(url) ? null : new LinkInfo(type, url);
}
public void SetChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages) {
chirp = string.IsNullOrEmpty(tweetUrl) ? (ChirpInfo?) null : new ChirpInfo(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
public void SetChirp(string columnId, string chirpId, string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages) {
chirp = string.IsNullOrEmpty(tweetUrl) ? (ChirpInfo?) null : new ChirpInfo(columnId, chirpId, tweetUrl, quoteUrl, chirpAuthors, chirpImages);
}
public ContextData Reset() {
@ -53,6 +53,9 @@ public LinkInfo(string type, string url) {
}
public readonly struct ChirpInfo {
public string ColumnId { get; }
public string ChirpId { get; }
public string TweetUrl { get; }
public string QuoteUrl { get; }
@ -62,7 +65,9 @@ public readonly struct ChirpInfo {
private readonly string chirpAuthors;
private readonly string chirpImages;
public ChirpInfo(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages) {
public ChirpInfo(string columnId, string chirpId, string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages) {
this.ColumnId = columnId;
this.ChirpId = chirpId;
this.TweetUrl = tweetUrl;
this.QuoteUrl = quoteUrl;
this.chirpAuthors = chirpAuthors;

View File

@ -406,8 +406,8 @@ public void AddSearchColumn(string query) {
browser.AddSearchColumn(query);
}
public void TriggerTweetScreenshot() {
browser.TriggerTweetScreenshot();
public void TriggerTweetScreenshot(string columnId, string chirpId) {
browser.TriggerTweetScreenshot(columnId, chirpId);
}
public void ReloadColumns() {

View File

@ -126,7 +126,10 @@ public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser b
return true;
case MenuScreenshotTweet:
form.InvokeAsyncSafe(form.TriggerTweetScreenshot);
var chirp = Context.Chirp;
form.InvokeAsyncSafe(() => form.TriggerTweetScreenshot(chirp.ColumnId, chirp.ChirpId));
return true;
case MenuOpenQuotedTweetUrl:

View File

@ -247,8 +247,8 @@ public void AddSearchColumn(string query) {
browser.ExecuteJsAsync("TDGF_performSearch", query);
}
public void TriggerTweetScreenshot() {
browser.ExecuteJsAsync("TDGF_triggerScreenshot()");
public void TriggerTweetScreenshot(string columnId, string chirpId) {
browser.ExecuteJsAsync("TDGF_triggerScreenshot", columnId, chirpId);
}
public void ReloadColumns() {

View File

@ -23,7 +23,7 @@ if (!("$TDX" in window)) {
* @property {function} openContextMenu
* @property {function(showGuide: boolean)} onIntroductionClosed
* @property {function(videoUrl: string, tweetUrl: string, username: string, callback: function)} playVideo
* @property {function(tweetUrl: string, quoteUrl: string, chirpAuthors: string, chirpImages: string)} setRightClickedChirp
* @property {function(columnId: string, chirpId: string, tweetUrl: string, quoteUrl: string, chirpAuthors: string, chirpImages: string)} setRightClickedChirp
* @property {function(type: string, url: string)} setRightClickedLink
* @property {function} showTweetDetail
* @property {function(html: string, width: number)} screenshotTweet

View File

@ -40,6 +40,7 @@ if (!("TD" in window)) {
* @property {TD_Column_Model} model
* @property {boolean} notificationsDisabled
* @property {function} reloadTweets
* @property {{ columnWidth: number }} visibility
*/
/**

View File

@ -0,0 +1,30 @@
import { TD } from "../../api/td.js";
export const COLUMN_NOT_FOUND = "column";
export const TWEET_NOT_FOUND = "tweet";
/**
* @param {string} columnId
* @param {string} chirpId
* @param {function(column: TD_Column, chirp: ChirpBase)} onSuccess
* @param {function("column"|"tweet")} onError
*/
export function retrieveTweet(columnId, chirpId, onSuccess, onError) {
const column = TD.controller.columnManager.getByApiid(columnId);
if (!column) {
onError(COLUMN_NOT_FOUND);
return;
}
const chirp = column.findMostInterestingChirp(chirpId);
if (chirp) {
onSuccess(column, chirp);
}
else {
TD.controller.clients.getPreferredClient().show(chirpId, function(chirp) {
onSuccess(column, chirp);
}, function() {
onError(TWEET_NOT_FOUND);
});
}
}

View File

@ -3,12 +3,14 @@ import { $ } from "../../api/jquery.js";
import { isAppReady, onAppReady } from "../../api/ready.js";
import { TD } from "../../api/td.js";
import { checkPropertyExists } from "../../api/utils.js";
import { COLUMN_NOT_FOUND, retrieveTweet, TWEET_NOT_FOUND } from "./retrieve_tweet.js";
function isSupported() {
return checkPropertyExists(TD, "ui", "updates", "showDetailView") &&
checkPropertyExists(TD, "controller", "columnManager", "showColumn") &&
checkPropertyExists(TD, "controller", "columnManager", "getByApiid") &&
checkPropertyExists(TD, "controller", "clients", "getPreferredClient");
checkPropertyExists(TD, "controller", "clients", "getPreferredClient") &&
checkPropertyExists(TD, "services", "TwitterClient", "prototype", "show");
}
/**
@ -33,28 +35,16 @@ function showTweetDetailImpl(columnId, chirpId, fallbackUrl) {
return;
}
const column = TD.controller.columnManager.getByApiid(columnId);
if (!column) {
if (confirm("error|The column which contained the tweet no longer exists. Would you like to open the tweet in your browser instead?")) {
retrieveTweet(columnId, chirpId, showTweetDetailInternal, error => {
// noinspection NestedConditionalExpressionJS
const message = error === COLUMN_NOT_FOUND ? "The column which contained the tweet no longer exists." :
error === TWEET_NOT_FOUND ? "Could not retrieve the requested tweet." :
null;
if (message && confirm("error|" + message + " Would you like to open the tweet in your browser instead?")) {
$TD.openBrowser(fallbackUrl);
}
return;
}
const chirp = column.findMostInterestingChirp(chirpId);
if (chirp) {
showTweetDetailInternal(column, chirp);
}
else {
TD.controller.clients.getPreferredClient().show(chirpId, function(chirp) {
showTweetDetailInternal(column, chirp);
}, function() {
if (confirm("error|Could not retrieve the requested tweet. Would you like to open the tweet in your browser instead?")) {
$TD.openBrowser(fallbackUrl);
}
});
}
});
}
/**

View File

@ -1,65 +1,90 @@
import { $TD } from "../api/bridge.js";
import { $ } from "../api/jquery.js";
import { TD } from "../api/td.js";
import { checkPropertyExists } from "../api/utils.js";
import { getClassStyleProperty } from "./globals/get_class_style_property.js";
import { getHoveredTweet } from "./globals/get_hovered_tweet.js";
import { COLUMN_NOT_FOUND, retrieveTweet, TWEET_NOT_FOUND } from "./globals/retrieve_tweet.js";
function isSupported() {
return checkPropertyExists(TD, "controller", "columnManager", "getByApiid") &&
checkPropertyExists(TD, "controller", "clients", "getPreferredClient") &&
checkPropertyExists(TD, "services", "TwitterClient", "prototype", "show");
}
/**
* @param {TD_Column} column
* @param {ChirpBase} chirp
*/
function screenshotTweetInternal(column, chirp) {
const html = $(chirp.render({
withFooter: false,
withTweetActions: false,
isInConvo: false,
isFavorite: false,
isRetweeted: false, // keeps retweet mark above tweet
isPossiblySensitive: false,
mediaPreviewSize: column.getMediaPreviewSize()
}));
html.find("footer").last().remove(); // apparently withTweetActions breaks for certain tweets, nice
html.find(".td-screenshot-remove").remove();
html.find("p.link-complex-target,p.txt-mute").filter(function() {
return $(this).text() === "Show this thread";
}).remove();
html.addClass($(document.documentElement).attr("class"));
html.addClass($(document.body).attr("class"));
html.css("background-color", getClassStyleProperty("stream-item", "background-color"));
html.css("border", "none");
for (const selector of [ ".js-quote-detail", ".js-media-preview-container", ".js-media" ]) {
const ele = html.find(selector);
if (ele.length) {
ele[0].style.setProperty("margin-bottom", "2px", "important");
break;
}
}
const gif = html.find(".js-media-gif-container");
if (gif.length) {
gif.css("background-image", "url(\"" + chirp.getMedia()[0].small() + "\")");
}
const type = chirp.getChirpType();
if ((type.startsWith("favorite") || type.startsWith("retweet")) && chirp.isAboutYou()) {
html.addClass("td-notification-padded");
}
$TD.screenshotTweet(html[0].outerHTML, column.visibility.columnWidth);
}
/**
* @param {string} columnId
* @param {string} chirpId
*/
function screenshotTweetImpl(columnId, chirpId) {
retrieveTweet(columnId, chirpId, screenshotTweetInternal, error => {
if (error === COLUMN_NOT_FOUND) {
alert("error|The column which contained the tweet no longer exists.");
}
else if (error === TWEET_NOT_FOUND) {
alert("error|Could not retrieve the requested tweet.");
}
});
}
export default function() {
/**
* Screenshots the hovered tweet to clipboard.
* Screenshots the specified tweet to clipboard.
* @param {string} columnId
* @param {string} chirpId
*/
window.TDGF_triggerScreenshot = function() {
const hovered = getHoveredTweet();
if (!hovered) {
return;
}
const columnWidth = $(hovered.column.ele).width();
const tweet = hovered.wrap || hovered.obj;
const html = $(tweet.render({
withFooter: false,
withTweetActions: false,
isInConvo: false,
isFavorite: false,
isRetweeted: false, // keeps retweet mark above tweet
isPossiblySensitive: false,
mediaPreviewSize: hovered.column.obj.getMediaPreviewSize()
}));
html.find("footer").last().remove(); // apparently withTweetActions breaks for certain tweets, nice
html.find(".td-screenshot-remove").remove();
html.find("p.link-complex-target,p.txt-mute").filter(function() {
return $(this).text() === "Show this thread";
}).remove();
html.addClass($(document.documentElement).attr("class"));
html.addClass($(document.body).attr("class"));
html.css("background-color", getClassStyleProperty("stream-item", "background-color"));
html.css("border", "none");
for (const selector of [ ".js-quote-detail", ".js-media-preview-container", ".js-media" ]) {
const ele = html.find(selector);
if (ele.length) {
ele[0].style.setProperty("margin-bottom", "2px", "important");
break;
}
}
const gif = html.find(".js-media-gif-container");
if (gif.length) {
gif.css("background-image", "url(\"" + tweet.getMedia()[0].small() + "\")");
}
const type = tweet.getChirpType();
if ((type.startsWith("favorite") || type.startsWith("retweet")) && tweet.isAboutYou()) {
html.addClass("td-notification-padded");
}
$TD.screenshotTweet(html[0].outerHTML, columnWidth);
window.TDGF_triggerScreenshot = isSupported() ? screenshotTweetImpl : function() {
alert("error|This feature is not available due to an internal error.");
};
};

View File

@ -17,13 +17,15 @@ function handleTweetContextMenu() {
const quote = tweet.quotedTweet;
if (tweet.chirpType === TD.services.ChirpBase.TWEET) {
const columnId = hovered.column.obj.model.privateState.apiid;
const tweetUrl = tweet.getChirpURL();
const quoteUrl = quote && quote.getChirpURL();
const chirpAuthors = quote ? [ tweet.getMainUser().screenName, quote.getMainUser().screenName ].join(";") : tweet.getMainUser().screenName;
const chirpImages = tweet.hasImage() ? processMedia(tweet) : quote?.hasImage() ? processMedia(quote) : "";
$TD.setRightClickedChirp(tweetUrl || "", quoteUrl || "", chirpAuthors, chirpImages);
$TD.setRightClickedChirp(columnId, tweet.id, tweetUrl || "", quoteUrl || "", chirpAuthors, chirpImages);
}
else if (tweet instanceof TD.services.TwitterActionFollow) {
$TD.setRightClickedLink("link", tweet.following.getProfileURL());

View File

@ -434,6 +434,7 @@
<Content Include="Resources\Content\tweetdeck\globals\prioritize_newest_event.js" />
<Content Include="Resources\Content\tweetdeck\globals\reload_browser.js" />
<Content Include="Resources\Content\tweetdeck\globals\reload_columns.js" />
<Content Include="Resources\Content\tweetdeck\globals\retrieve_tweet.js" />
<Content Include="Resources\Content\tweetdeck\globals\show_tweet_detail.js" />
<Content Include="Resources\Content\tweetdeck\handle_extra_mouse_buttons.js" />
<Content Include="Resources\Content\tweetdeck\hook_theme_settings.js" />