mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 10:32:10 +02:00
Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
327ef1cbee | |||
15eb823c7f | |||
54613e5242 | |||
df1352cbe3 | |||
0559afd972 | |||
afffca020e | |||
d663cc3f64 | |||
110d41e393 | |||
1a8823f592 | |||
6374a852b0 | |||
a10c7dd7c3 | |||
547c7ea417 | |||
760607995a | |||
4704197c09 | |||
093ac1ac40 | |||
9ed8b0d904 | |||
7346ce370d | |||
adefdadc19 | |||
703bce2d00 | |||
97928ecd84 | |||
be9ea7f64a | |||
ec2aaa8789 | |||
ab14b72526 | |||
d8e304f3c1 |
5
.gitattributes
vendored
Normal file
5
.gitattributes
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
*.txt text eof=lf
|
||||
*.cs diff=csharp
|
@@ -117,8 +117,8 @@ namespace TweetDuck.Core.Bridge{
|
||||
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
|
||||
}
|
||||
|
||||
public void PlayVideo(string url){
|
||||
form.InvokeAsyncSafe(() => form.PlayVideo(url));
|
||||
public void PlayVideo(string url, string username){
|
||||
form.InvokeAsyncSafe(() => form.PlayVideo(url, username));
|
||||
}
|
||||
|
||||
public void FixClipboard(){
|
||||
|
@@ -541,7 +541,7 @@ namespace TweetDuck.Core{
|
||||
soundNotification.Play(Config.NotificationSoundPath);
|
||||
}
|
||||
|
||||
public void PlayVideo(string url){
|
||||
public void PlayVideo(string url, string username){
|
||||
if (string.IsNullOrEmpty(url)){
|
||||
videoPlayer?.Close();
|
||||
return;
|
||||
@@ -556,7 +556,7 @@ namespace TweetDuck.Core{
|
||||
};
|
||||
}
|
||||
|
||||
videoPlayer.Launch(url);
|
||||
videoPlayer.Launch(url, username);
|
||||
}
|
||||
|
||||
public void HideVideoOverlay(){
|
||||
|
@@ -7,7 +7,9 @@ using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using TweetDuck.Core.Other;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
abstract class ContextMenuBase : IContextMenuHandler{
|
||||
@@ -31,10 +33,11 @@ namespace TweetDuck.Core.Handling{
|
||||
private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand)26500;
|
||||
private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand)26501;
|
||||
private const CefMenuCommand MenuCopyUsername = (CefMenuCommand)26502;
|
||||
private const CefMenuCommand MenuOpenMediaUrl = (CefMenuCommand)26503;
|
||||
private const CefMenuCommand MenuCopyMediaUrl = (CefMenuCommand)26504;
|
||||
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26505;
|
||||
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26506;
|
||||
private const CefMenuCommand MenuViewImage = (CefMenuCommand)26503;
|
||||
private const CefMenuCommand MenuOpenMediaUrl = (CefMenuCommand)26504;
|
||||
private const CefMenuCommand MenuCopyMediaUrl = (CefMenuCommand)26505;
|
||||
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26506;
|
||||
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26507;
|
||||
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
|
||||
|
||||
private string[] lastHighlightedTweetAuthors;
|
||||
@@ -79,6 +82,7 @@ namespace TweetDuck.Core.Handling{
|
||||
model.AddSeparator();
|
||||
}
|
||||
else if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){
|
||||
model.AddItem(MenuViewImage, "View image in photo viewer");
|
||||
model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
|
||||
model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
|
||||
model.AddItem(MenuSaveMedia, TextSave("image"));
|
||||
@@ -114,9 +118,35 @@ namespace TweetDuck.Core.Handling{
|
||||
SetClipboardText(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
|
||||
break;
|
||||
|
||||
case MenuViewImage:
|
||||
string url = GetMediaLink(parameters);
|
||||
string file = Path.Combine(BrowserCache.CacheFolder, TwitterUtils.GetImageFileName(url));
|
||||
|
||||
void ViewFile(){
|
||||
string ext = Path.GetExtension(file);
|
||||
|
||||
if (TwitterUtils.ValidImageExtensions.Contains(ext)){
|
||||
WindowsUtils.OpenAssociatedProgram(file);
|
||||
}
|
||||
else{
|
||||
FormMessage.Error("Image Download", "Invalid file extension "+ext, FormMessage.OK);
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists(file)){
|
||||
ViewFile();
|
||||
}
|
||||
else{
|
||||
BrowserUtils.DownloadFileAsync(TwitterUtils.GetMediaLink(url, ImageQuality), file, ViewFile, ex => {
|
||||
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MenuSaveMedia:
|
||||
if (IsVideo){
|
||||
TwitterUtils.DownloadVideo(GetMediaLink(parameters));
|
||||
TwitterUtils.DownloadVideo(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault());
|
||||
}
|
||||
else{
|
||||
TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
|
||||
|
@@ -21,7 +21,7 @@ namespace TweetDuck.Core.Other{
|
||||
}
|
||||
|
||||
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe(e.Link.LinkData as string);
|
||||
BrowserUtils.OpenExternalBrowser(e.Link.LinkData as string);
|
||||
}
|
||||
|
||||
private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent){
|
||||
|
@@ -25,6 +25,7 @@ namespace TweetDuck.Core.Other.Management{
|
||||
|
||||
private readonly Form owner;
|
||||
private string lastUrl;
|
||||
private string lastUsername;
|
||||
|
||||
private Process currentProcess;
|
||||
private DuplexPipe.Server currentPipe;
|
||||
@@ -35,13 +36,14 @@ namespace TweetDuck.Core.Other.Management{
|
||||
this.owner.FormClosing += owner_FormClosing;
|
||||
}
|
||||
|
||||
public void Launch(string url){
|
||||
public void Launch(string url, string username){
|
||||
if (Running){
|
||||
Destroy();
|
||||
isClosing = false;
|
||||
}
|
||||
|
||||
lastUrl = url;
|
||||
lastUsername = username;
|
||||
|
||||
try{
|
||||
currentPipe = DuplexPipe.CreateServer();
|
||||
@@ -84,7 +86,7 @@ namespace TweetDuck.Core.Other.Management{
|
||||
break;
|
||||
|
||||
case "download":
|
||||
TwitterUtils.DownloadVideo(lastUrl);
|
||||
TwitterUtils.DownloadVideo(lastUrl, lastUsername);
|
||||
break;
|
||||
|
||||
case "rip":
|
||||
@@ -157,14 +159,14 @@ namespace TweetDuck.Core.Other.Management{
|
||||
|
||||
switch(exitCode){
|
||||
case 3: // CODE_LAUNCH_FAIL
|
||||
if (FormMessage.Error("Video Playback Error", "Error launching video player, this may be caused by missing Windows Media Player. Do you want to open the video in 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 your browser?", FormMessage.Yes, FormMessage.No)){
|
||||
BrowserUtils.OpenExternalBrowser(lastUrl);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 4: // CODE_MEDIA_ERROR
|
||||
if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in a browser?", FormMessage.Yes, FormMessage.No)){
|
||||
if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
|
||||
BrowserUtils.OpenExternalBrowser(lastUrl);
|
||||
}
|
||||
|
||||
|
@@ -35,7 +35,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
}
|
||||
|
||||
private void btnOpenWiki_Click(object sender, EventArgs e){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe("https://github.com/chylex/TweetDuck/wiki");
|
||||
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/wiki");
|
||||
}
|
||||
|
||||
private void btnApply_Click(object sender, EventArgs e){
|
||||
|
@@ -19,7 +19,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
}
|
||||
|
||||
private void btnHelp_Click(object sender, EventArgs e){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe("http://peter.sh/experiments/chromium-command-line-switches/");
|
||||
BrowserUtils.OpenExternalBrowser("http://peter.sh/experiments/chromium-command-line-switches/");
|
||||
}
|
||||
|
||||
private void btnApply_Click(object sender, EventArgs e){
|
||||
|
@@ -16,7 +16,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
|
||||
private void btnSendFeedback_Click(object sender, EventArgs e){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe("https://github.com/chylex/TweetDuck/issues/new");
|
||||
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/issues/new");
|
||||
}
|
||||
|
||||
private void checkDataCollection_CheckedChanged(object sender, EventArgs e){
|
||||
@@ -24,7 +24,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
|
||||
private void labelDataCollectionLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe("https://github.com/chylex/TweetDuck/wiki/Send-anonymous-data");
|
||||
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/wiki/Send-anonymous-data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ namespace TweetDuck.Core.Utils{
|
||||
static class BrowserCache{
|
||||
private static bool ClearOnExit { get; set; }
|
||||
|
||||
private static readonly string CacheFolder = Path.Combine(Program.StoragePath, "Cache");
|
||||
public static readonly string CacheFolder = Path.Combine(Program.StoragePath, "Cache");
|
||||
|
||||
private static IEnumerable<string> CacheFiles{
|
||||
get{
|
||||
|
@@ -70,12 +70,12 @@ namespace TweetDuck.Core.Utils{
|
||||
|
||||
switch(CheckUrl(url)){
|
||||
case UrlCheckResult.Fine:
|
||||
OpenExternalBrowserUnsafe(url);
|
||||
WindowsUtils.OpenAssociatedProgram(url);
|
||||
break;
|
||||
|
||||
case UrlCheckResult.Tracking:
|
||||
if (FormMessage.Warning("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n"+url, FormMessage.Yes, FormMessage.No)){
|
||||
OpenExternalBrowserUnsafe(url);
|
||||
WindowsUtils.OpenAssociatedProgram(url);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -86,10 +86,6 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
}
|
||||
|
||||
public static void OpenExternalBrowserUnsafe(string url){
|
||||
using(Process.Start(url)){}
|
||||
}
|
||||
|
||||
public static string GetFileNameFromUrl(string url){
|
||||
string file = Path.GetFileName(new Uri(url).AbsolutePath);
|
||||
return string.IsNullOrEmpty(file) ? null : file;
|
||||
|
@@ -21,6 +21,10 @@ namespace TweetDuck.Core.Utils{
|
||||
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
|
||||
};
|
||||
|
||||
public static readonly string[] ValidImageExtensions = {
|
||||
".jpg", ".jpeg", ".png", ".gif"
|
||||
};
|
||||
|
||||
public enum ImageQuality{
|
||||
Default, Orig
|
||||
}
|
||||
@@ -53,6 +57,10 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetImageFileName(string url){
|
||||
return BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(url));
|
||||
}
|
||||
|
||||
public static void DownloadImage(string url, string username, ImageQuality quality){
|
||||
DownloadImages(new string[]{ url }, username, quality);
|
||||
}
|
||||
@@ -65,7 +73,7 @@ namespace TweetDuck.Core.Utils{
|
||||
string firstImageLink = GetMediaLink(urls[0], quality);
|
||||
int qualityIndex = firstImageLink.IndexOf(':', firstImageLink.LastIndexOf('/'));
|
||||
|
||||
string file = BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(firstImageLink));
|
||||
string file = GetImageFileName(firstImageLink);
|
||||
string ext = Path.GetExtension(file); // includes dot
|
||||
|
||||
string[] fileNameParts = qualityIndex == -1 ? new string[]{
|
||||
@@ -103,7 +111,7 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
}
|
||||
|
||||
public static void DownloadVideo(string url){
|
||||
public static void DownloadVideo(string url, string username){
|
||||
string filename = BrowserUtils.GetFileNameFromUrl(url);
|
||||
string ext = Path.GetExtension(filename);
|
||||
|
||||
@@ -111,7 +119,7 @@ namespace TweetDuck.Core.Utils{
|
||||
AutoUpgradeEnabled = true,
|
||||
OverwritePrompt = true,
|
||||
Title = "Save video",
|
||||
FileName = filename,
|
||||
FileName = string.IsNullOrEmpty(username) ? filename : $"{username} {filename}",
|
||||
Filter = "Video"+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
|
||||
}){
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Management;
|
||||
@@ -49,17 +50,22 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
}
|
||||
|
||||
public static Process StartProcess(string file, string arguments, bool runElevated){
|
||||
ProcessStartInfo processInfo = new ProcessStartInfo{
|
||||
public static bool OpenAssociatedProgram(string file, string arguments = "", bool runElevated = false){
|
||||
try{
|
||||
using(Process.Start(new ProcessStartInfo{
|
||||
FileName = file,
|
||||
Arguments = arguments
|
||||
};
|
||||
|
||||
if (runElevated){
|
||||
processInfo.Verb = "runas";
|
||||
Arguments = arguments,
|
||||
Verb = runElevated ? "runas" : string.Empty,
|
||||
ErrorDialog = true
|
||||
})){
|
||||
return true;
|
||||
}
|
||||
}catch(Win32Exception e) when (e.NativeErrorCode == 0x000004C7){ // operation canceled by the user
|
||||
return false;
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Error opening file", e.Message, true, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return Process.Start(processInfo);
|
||||
}
|
||||
|
||||
public static bool TrySleepUntil(Func<bool> test, int timeoutMillis, int timeStepMillis){
|
||||
@@ -104,7 +110,7 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
|
||||
public static void ClipboardStripHtmlStyles(){
|
||||
if (!Clipboard.ContainsText(TextDataFormat.Html)){
|
||||
if (!Clipboard.ContainsText(TextDataFormat.Html) || !Clipboard.ContainsText(TextDataFormat.UnicodeText)){
|
||||
return;
|
||||
}
|
||||
|
||||
|
17
Program.cs
17
Program.cs
@@ -21,7 +21,7 @@ namespace TweetDuck{
|
||||
public const string BrandName = "TweetDuck";
|
||||
public const string Website = "https://tweetduck.chylex.com";
|
||||
|
||||
public const string VersionTag = "1.10.1";
|
||||
public const string VersionTag = "1.10.2";
|
||||
|
||||
public static readonly bool IsPortable = File.Exists("makeportable");
|
||||
|
||||
@@ -166,9 +166,13 @@ namespace TweetDuck{
|
||||
string updaterArgs = "/SP- /SILENT /CLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+Arguments.GetCurrentForInstallerCmd()+"\""+(IsPortable ? " /PORTABLE=1" : "");
|
||||
bool runElevated = !IsPortable || !WindowsUtils.CheckFolderWritePermission(ProgramPath);
|
||||
|
||||
WindowsUtils.StartProcess(mainForm.UpdateInstallerPath, updaterArgs, runElevated);
|
||||
if (WindowsUtils.OpenAssociatedProgram(mainForm.UpdateInstallerPath, updaterArgs, runElevated)){
|
||||
Application.Exit();
|
||||
}
|
||||
else{
|
||||
RestartWithArgsInternal(Arguments.GetCurrentClean());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetDataStoragePath(){
|
||||
@@ -211,11 +215,14 @@ namespace TweetDuck{
|
||||
FormBrowser browserForm = Application.OpenForms.OfType<FormBrowser>().FirstOrDefault();
|
||||
if (browserForm == null)return;
|
||||
|
||||
args.AddFlag(Arguments.ArgRestart);
|
||||
|
||||
browserForm.ForceClose();
|
||||
ExitCleanup();
|
||||
|
||||
ExitCleanup();
|
||||
RestartWithArgsInternal(args);
|
||||
}
|
||||
|
||||
private static void RestartWithArgsInternal(CommandLineArgs args){
|
||||
args.AddFlag(Arguments.ArgRestart);
|
||||
Process.Start(Application.ExecutablePath, args.ToString());
|
||||
Application.Exit();
|
||||
}
|
||||
|
25
README.md
25
README.md
@@ -2,10 +2,6 @@
|
||||
|
||||
[Follow TweetDuck on Twitter](https://twitter.com/TryTweetDuck) | [Support via PayPal](https://paypal.me/chylex) | [Support via Patreon](https://www.patreon.com/chylex)
|
||||
|
||||
<a target='_blank' rel='nofollow' href='https://app.codesponsor.io/link/2W76v7hRmUe7NfaeUKq9K8Wq/chylex/TweetDuck'>
|
||||
<img alt='Sponsor' width='888' height='68' src='https://app.codesponsor.io/embed/2W76v7hRmUe7NfaeUKq9K8Wq/chylex/TweetDuck.svg' />
|
||||
</a>
|
||||
|
||||
# Build Instructions
|
||||
|
||||
### Setup
|
||||
@@ -33,21 +29,34 @@ To do that, open **TweetDuck Properties**, click the **Debug** tab, make sure yo
|
||||
|
||||
### 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.
|
||||
To make a release build of TweetDuck, open **Batch Build**, tick all `Release` configurations except for the `UnitTest` project (otherwise the build will fail), and click **Rebuild**. Check the status bar to make sure it says **Rebuild All succeeded**; if not, see the [Troubleshooting](#troubleshooting) section.
|
||||
|
||||
After the build succeeds, the **bin/x86/Release** folder will contain files intended for distribution (no debug symbols or other unnecessary files). You may package these files yourself, or see the [Installers](#Installers) section for automated installer generation.
|
||||
After the build succeeds, the `bin/x86/Release` folder will contain files intended for distribution (no debug symbols or other unnecessary files). You may package these files yourself, or see the [Installers](#installers) section for automated installer generation.
|
||||
|
||||
If you decide to release a custom version publicly, please make it clear that it is not an official release of TweetDuck.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
There are a few quirks in the build process that may catch you off guard:
|
||||
|
||||
- **Plugin files are not updated automatically**
|
||||
- Since official plugins (`Resources/Plugins`) are not included in the project, Visual Studio will not automatically detect changes in the files
|
||||
- To ensure plugins are updated when testing the app, click **Rebuild Solution** before clicking **Start**
|
||||
- **Error: The command (...) exited with code 1**
|
||||
- If the post-build event fails, open the **Output** tab and look for the cause
|
||||
- Determine if there was an IO error while copying files or modifying folders, or whether the final .ps1 script failed (`Encountered an error while running PostBuild.ps1 on line xyz`)
|
||||
- Some files are checked for invalid characters:
|
||||
- `Resources/Plugins/emoji-keyboard/emoji-ordering.txt` line endings must be LF (line feed); any CR (carriage return) in the file will cause a failed build, and you will need to ensure correct line endings in your text editor
|
||||
|
||||
### Installers
|
||||
|
||||
TweetDuck uses **Inno Setup** to automate the creation of installers. First, download and install [InnoSetup QuickStart Pack](http://www.jrsoftware.org/isdl.php) (non-unicode; editor and encryption support not required) and the [Inno Download Plugin](https://code.google.com/archive/p/inno-download-plugin).
|
||||
|
||||
Next, add the Inno Setup installation folder (usually `C:\Program Files (x86)\Inno Setup 5`) into your **PATH** environment variable. You may need to restart File Explorer for the change to take place.
|
||||
|
||||
Now you can generate installers after a build by running **bld/RUN BUILD.bat**. Note that despite the name, this will only package the files, you still need to run the [build](#Build) in Visual Studio!
|
||||
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:
|
||||
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**
|
||||
|
@@ -8,7 +8,7 @@ Edit layout & design
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.1.4
|
||||
1.1.5
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
@@ -12,6 +12,7 @@ enabled(){
|
||||
moveTweetActionsToRight: true,
|
||||
themeColorTweaks: true,
|
||||
revertIcons: true,
|
||||
increaseQuoteTextSize: false,
|
||||
smallComposeTextSize: false,
|
||||
optimizeAnimations: true,
|
||||
avatarRadius: 2
|
||||
@@ -366,6 +367,10 @@ enabled(){
|
||||
this.css.insert(".tweet-actions > li:nth-child(4) { margin-right: 2px !important }");
|
||||
}
|
||||
|
||||
if (this.config.increaseQuoteTextSize){
|
||||
this.css.insert(".quoted-tweet { font-size: 1em !important }");
|
||||
}
|
||||
|
||||
if (this.config.smallComposeTextSize){
|
||||
this.css.insert(".compose-text { font-size: 12px !important; height: 120px !important }");
|
||||
}
|
||||
@@ -510,6 +515,10 @@ enabled(){
|
||||
html[data-td-font] { font-size: ${this.config.fontSize} !important }
|
||||
.avatar { border-radius: ${this.config.avatarRadius}% !important }
|
||||
|
||||
${this.config.increaseQuoteTextSize ? `
|
||||
.quoted-tweet { font-size: 1em !important }
|
||||
` : ``}
|
||||
|
||||
${this.config.revertIcons ? `
|
||||
@font-face { font-family: 'tweetdeckold'; src: url(\"https://ton.twimg.com/tweetdeck-web/web/assets/fonts/tweetdeck-regular-webfont.5f4ea87976.woff\") format(\"woff\"); font-weight: normal; font-style: normal }
|
||||
.icon-reply:before{content:"\\f006";font-family:tweetdeckold}
|
||||
|
@@ -61,15 +61,13 @@
|
||||
<option value="16px">Largest (16px)</option>
|
||||
<option value="custom">Custom</option>
|
||||
</select>
|
||||
|
||||
<!-- ADVANCED -->
|
||||
|
||||
<label class="txt-uppercase touch-larger-label">
|
||||
<b>Advanced</b>
|
||||
<label class="checkbox">
|
||||
<input data-td-key="increaseQuoteTextSize" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Increase quoted tweet font size
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input data-td-key="optimizeAnimations" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Use more memory for smoother animations
|
||||
<input data-td-key="smallComposeTextSize" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Small tweet input font size
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -102,9 +100,15 @@
|
||||
<input data-td-key="revertIcons" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Revert icon design
|
||||
</label>
|
||||
|
||||
<!-- ADVANCED -->
|
||||
|
||||
<label class="txt-uppercase touch-larger-label">
|
||||
<b>Advanced</b>
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input data-td-key="smallComposeTextSize" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Small compose tweet font size
|
||||
<input data-td-key="optimizeAnimations" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Use more memory for smoother animations
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -163,11 +167,12 @@
|
||||
.td-modal-inner-cols .l-column {
|
||||
padding: 15px 9px;
|
||||
box-sizing: border-box;
|
||||
width: 225px;
|
||||
font-size: 0; /* fix custom font size breaking the modal layout */
|
||||
}
|
||||
|
||||
.td-modal-inner-cols .l-column:nth-child(2) {
|
||||
width: 250px;
|
||||
.td-modal-inner-cols .l-column:nth-child(3) {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.td-modal-inner-full {
|
||||
@@ -199,6 +204,14 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.td-modal-content select + label.checkbox {
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
.td-modal-content input.js-theme-checkbox, .td-modal-content input.js-theme-radio {
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
/* Avatar shape */
|
||||
|
||||
.td-avatar-shape-container {
|
||||
|
@@ -9,7 +9,7 @@ Emoji keyboard
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.4
|
||||
1.4.1
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
@@ -303,23 +303,26 @@ enabled(){
|
||||
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 search = val.substring(firstColon+1, ele[0].selectionStart).toLowerCase();
|
||||
return if !/^[a-z_]+$/.test(search);
|
||||
|
||||
let keywords = search.split("_").filter(kw => kw.length > 0).map(kw => kw.toLowerCase());
|
||||
return if keywords.length === 0;
|
||||
|
||||
let foundName = me.emojiNames.filter(name => keywords.every(kw => name.includes(kw)));
|
||||
let foundNames = me.emojiNames.filter(name => keywords.every(kw => name.includes(kw)));
|
||||
|
||||
if (foundName.length === 0){
|
||||
if (foundNames.length === 0){
|
||||
return;
|
||||
}
|
||||
else if (foundNames.length > 1 && foundNames.includes(search)){
|
||||
foundNames = [ search ];
|
||||
}
|
||||
|
||||
lastEmojiKeyword = `:${search}:`;
|
||||
lastEmojiPosition = lastEmojiLength = 0;
|
||||
|
||||
if (foundName.length === 1){
|
||||
let foundIndex = me.emojiNames.indexOf(foundName[0]);
|
||||
if (foundNames.length === 1){
|
||||
let foundIndex = me.emojiNames.indexOf(foundNames[0]);
|
||||
let foundEmoji;
|
||||
|
||||
for(let array of [ me.emojiData1, me.emojiData2[me.selectedSkinTone], me.emojiData3 ]){
|
||||
@@ -346,7 +349,7 @@ enabled(){
|
||||
lastEmojiLength = foundEmoji.length;
|
||||
}
|
||||
}
|
||||
else if (foundName.length > 1){
|
||||
else if (foundNames.length > 1){
|
||||
e.preventDefault();
|
||||
ele.val(val.substring(0, firstColon)+val.substring(ele[0].selectionStart));
|
||||
ele[0].selectionEnd = ele[0].selectionStart = firstColon;
|
||||
|
@@ -8,7 +8,7 @@ Custom reply account
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.2.3
|
||||
1.2.4
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
@@ -20,4 +20,4 @@ configuration.js
|
||||
configuration.default.js
|
||||
|
||||
[requires]
|
||||
1.8
|
||||
1.10.3
|
@@ -6,7 +6,7 @@ enabled(){
|
||||
this.lastSelectedAccount = null;
|
||||
|
||||
this.uiComposeTweetEvent = (e, data) => {
|
||||
return if data.type !== "reply" || data.popFromInline || !("element" in data);
|
||||
return if !(data.type === "reply" || (data.type === "tweet" && "quotedTweet" in data)) || data.popFromInline || !("element" in data);
|
||||
|
||||
var query;
|
||||
|
||||
|
@@ -3,6 +3,18 @@ $ErrorActionPreference = "Stop"
|
||||
|
||||
Set-Location $dir
|
||||
|
||||
function Check-Carriage-Return{
|
||||
Param([Parameter(Mandatory = $True, Position = 1)] $fname)
|
||||
|
||||
$file = @(Get-ChildItem -Include $fname -Recurse)[0]
|
||||
|
||||
if ((Get-Content -Path $file.FullName -Raw).Contains("`r")){
|
||||
Throw "$fname cannot contain carriage return"
|
||||
}
|
||||
|
||||
Write-Host "Verified" $file.FullName.Substring($dir.Length)
|
||||
}
|
||||
|
||||
function Rewrite-File{
|
||||
[CmdletBinding()]
|
||||
Param([Parameter(Mandatory = $True, ValueFromPipeline = $True)][array] $lines, [Parameter(Mandatory = $True, Position = 1)] $file)
|
||||
@@ -11,23 +23,31 @@ function Rewrite-File{
|
||||
Write-Host "Processed" $file.FullName.Substring($dir.Length)
|
||||
}
|
||||
|
||||
ForEach($file in Get-ChildItem -Include *.js -Exclude configuration.default.js -Recurse){
|
||||
try{
|
||||
Check-Carriage-Return("emoji-ordering.txt")
|
||||
|
||||
ForEach($file in Get-ChildItem -Filter *.js -Exclude configuration.default.js -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
$lines = $lines | % { $_.TrimStart() }
|
||||
$lines = $lines -Replace '^(.*?)((?<=^|[;{}()])\s?//(?:\s.*|$))?$', '$1'
|
||||
$lines = $lines -Replace '(?<!\w)return(\s.*?)? if (.*?);', 'if ($2)return$1;'
|
||||
,$lines | Rewrite-File $file
|
||||
}
|
||||
}
|
||||
|
||||
ForEach($file in Get-ChildItem -Include *.css -Recurse){
|
||||
ForEach($file in Get-ChildItem -Filter *.css -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
$lines = $lines -Replace '\s*/\*.*?\*/', ''
|
||||
$lines = $lines -Replace '^\s+(.+):\s?(.+?)(?:\s?(!important))?;$', '$1:$2$3;'
|
||||
$lines = $lines -Replace '^(\S.*?) {$', '$1{'
|
||||
@(($lines | Where { $_ -ne '' }) -Join ' ') | Rewrite-File $file
|
||||
}
|
||||
}
|
||||
|
||||
ForEach($file in Get-ChildItem -Include *.html -Recurse){
|
||||
ForEach($file in Get-ChildItem -Filter *.html -Recurse){
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
,($lines | % { $_.TrimStart() }) | Rewrite-File $file
|
||||
}
|
||||
}catch{
|
||||
Write-Host "Encountered an error while running PostBuild.ps1 on line" $_.InvocationInfo.ScriptLineNumber
|
||||
Write-Host $_
|
||||
Exit 1
|
||||
}
|
||||
|
@@ -814,25 +814,63 @@
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Make middle click on tweet reply icon open the compose drawer. Only works for non-temporary columns.
|
||||
// Block: Make middle click on tweet reply icon open the compose drawer, retweet icon trigger a quote, and favorite icon open a 'Like from accounts...' modal.
|
||||
//
|
||||
app.delegate(".js-reply-action", "auxclick", function(e){
|
||||
if (e.which === 2){
|
||||
let column = $(this).closest(".js-column");
|
||||
app.delegate(".tweet-action,.tweet-detail-action", "auxclick", function(e){
|
||||
return if e.which !== 2;
|
||||
|
||||
if (column && column.hasClass("column") && $("[data-drawer='compose']").hasClass("is-hidden")){
|
||||
$(document).trigger("uiDrawerShowDrawer", {
|
||||
drawer: "compose",
|
||||
withAnimation: true
|
||||
let column = TD.controller.columnManager.get($(this).closest("section.js-column").attr("data-column"));
|
||||
return if !column;
|
||||
|
||||
let ele = $(this).closest("article");
|
||||
let tweet = column.findChirp(ele.attr("data-tweet-id")) || column.findChirp(ele.attr("data-key"));
|
||||
return if !tweet;
|
||||
|
||||
switch($(this).attr("rel")){
|
||||
case "reply":
|
||||
let main = tweet.getMainTweet();
|
||||
|
||||
$(document).trigger("uiDockedComposeTweet", {
|
||||
type: "reply",
|
||||
from: [ tweet.account.getKey() ],
|
||||
inReplyTo: {
|
||||
id: tweet.id,
|
||||
htmlText: main.htmlText,
|
||||
user: {
|
||||
screenName: main.user.screenName,
|
||||
name: main.user.name,
|
||||
profileImageURL: main.user.profileImageURL
|
||||
}
|
||||
},
|
||||
mentions: tweet.getReplyUsers(),
|
||||
element: ele
|
||||
});
|
||||
|
||||
window.setTimeout(() => $(this).trigger("click"), 1);
|
||||
break;
|
||||
|
||||
case "favorite":
|
||||
$(document).trigger("uiShowFavoriteFromOptions", { tweet });
|
||||
break;
|
||||
|
||||
case "retweet":
|
||||
TD.controller.stats.quoteTweet();
|
||||
|
||||
$(document).trigger("uiComposeTweet", {
|
||||
type: "tweet",
|
||||
from: [ tweet.account.getKey() ],
|
||||
quotedTweet: tweet.getMainTweet(),
|
||||
element: ele // triggers reply-account plugin
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
@@ -890,12 +928,12 @@
|
||||
// Block: Setup video player hooks.
|
||||
//
|
||||
(function(){
|
||||
var playVideo = function(url){
|
||||
window.TDGF_playVideo = function(url, username){
|
||||
$('<div id="td-video-player-overlay" class="ovl" style="display:block"></div>').on("click contextmenu", function(){
|
||||
$TD.playVideo(null);
|
||||
$TD.playVideo(null, null);
|
||||
}).appendTo(app);
|
||||
|
||||
$TD.playVideo(url);
|
||||
$TD.playVideo(url, username || null);
|
||||
};
|
||||
|
||||
var getVideoTweetLink = function(obj){
|
||||
@@ -904,12 +942,16 @@
|
||||
return link.attr("href");
|
||||
}
|
||||
|
||||
var getUsername = function(tweet){
|
||||
return tweet && (tweet.quotedTweet || tweet).getMainUser().screenName;
|
||||
}
|
||||
|
||||
app.delegate(".js-gif-play", {
|
||||
click: function(e){
|
||||
let src = !e.ctrlKey && $(this).closest(".js-media-gif-container").find("video").attr("src");
|
||||
|
||||
if (src){
|
||||
playVideo(src);
|
||||
window.TDGF_playVideo(src, getUsername(highlightedTweetObj));
|
||||
}
|
||||
else{
|
||||
$TD.openBrowser(getVideoTweetLink($(this)));
|
||||
@@ -945,7 +987,7 @@
|
||||
let media = this.chirp.getMedia().find(media => media.mediaId === this.clickedMediaEntityId);
|
||||
|
||||
if (media && media.isVideo && media.service === "twitter"){
|
||||
playVideo(media.chooseVideoVariant().url);
|
||||
window.TDGF_playVideo(media.chooseVideoVariant().url, getUsername(this.chirp));
|
||||
cancelModal = true;
|
||||
}
|
||||
});
|
||||
|
@@ -87,14 +87,14 @@
|
||||
};
|
||||
|
||||
//
|
||||
// Block: Setup global function to change plugin state.
|
||||
// Block: Setup a function to change plugin state.
|
||||
//
|
||||
window.TDPF_setPluginState = function(identifier, enable){
|
||||
window.TD_PLUGINS.setState(window.TD_PLUGINS.findObject(identifier), enable);
|
||||
};
|
||||
|
||||
//
|
||||
// Block: Setup global function to reload the page.
|
||||
// Block: Setup a function to reload the page.
|
||||
//
|
||||
window.TDPF_requestReload = function(){
|
||||
if (!isReloading){
|
||||
@@ -102,4 +102,9 @@
|
||||
isReloading = true;
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Block: Setup a function to allow plugins to play video.
|
||||
//
|
||||
window.TDPF_playVideo = window.TDGF_playVideo;
|
||||
})();
|
||||
|
@@ -46,7 +46,7 @@
|
||||
border-radius: 1px !important;
|
||||
}
|
||||
|
||||
.compose-text-container, .compose-reply-tweet, .compose-message-recipient-input-container, .compose-message-recipient, .compose-media-bar-holder, .media-grid-container {
|
||||
.compose-text-container, .compose-reply-tweet, .compose-message-recipient-input-container, .compose-message-recipient, .compose-media-bar-holder, .media-grid-container, .js-quote-tweet-holder {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
|
@@ -31,7 +31,7 @@ namespace TweetDuck.Updates{
|
||||
timerDownloadCheck.Stop();
|
||||
|
||||
if (FormMessage.Error("Update Has Failed", "Could not download the update: "+(updateInfo.DownloadError?.Message ?? "unknown error")+"\n\nDo you want to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe(Program.Website);
|
||||
BrowserUtils.OpenExternalBrowser(Program.Website);
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user