mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 01:32:10 +02:00
Compare commits
56 Commits
Author | SHA1 | Date | |
---|---|---|---|
fb13695ca5 | |||
20c76d06f7 | |||
339a11f649 | |||
0989400d87 | |||
52aacf602d | |||
54d70a6a17 | |||
d980e09e0f | |||
2e4cb12817 | |||
7b91cb2e96 | |||
95c04a8abc | |||
25822fefdb | |||
d800ee2d28 | |||
2a51371aca | |||
ee5d1a47dc | |||
b330b74347 | |||
11fa13f0bb | |||
21400d72b3 | |||
a710cb9d4f | |||
3326ad52ce | |||
c9560df851 | |||
74cb45118e | |||
c79bf19e51 | |||
961bec0a2f | |||
89e4977cd1 | |||
bfe16475db | |||
915d36867c | |||
48435af407 | |||
86b6ec5212 | |||
775e70bc45 | |||
9f565447d0 | |||
88d27bc29d | |||
172ae87ac6 | |||
91d572235e | |||
64d32dcb75 | |||
564b4283b6 | |||
ca4d374a81 | |||
a753806d7b | |||
bd1692cea3 | |||
b7ce089f08 | |||
8a6b47c5db | |||
9f1fc4df18 | |||
c018a2a7bc | |||
a1aebab114 | |||
e30702e1d8 | |||
008ff4b055 | |||
d7bba22e19 | |||
2b9a910533 | |||
118ebcc627 | |||
c741767b11 | |||
4a09358e14 | |||
3f4ea1af08 | |||
35bb196832 | |||
cb5b50dd42 | |||
8652272526 | |||
0f32504fde | |||
4735c21fc0 |
@@ -45,8 +45,7 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
public bool EnableUpdateCheck { get; set; }
|
||||
public string DismissedUpdate { get; set; }
|
||||
|
||||
[Obsolete] public PluginConfig Plugins { get; set; } // TODO remove eventually
|
||||
|
||||
public WindowState PluginsWindow { get; set; }
|
||||
|
||||
public string CustomCefArgs { get; set; }
|
||||
@@ -237,7 +236,7 @@ namespace TweetDuck.Configuration{
|
||||
Program.Reporter.Log(e.ToString());
|
||||
}
|
||||
else if (firstException != null){
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not open the backup configuration file. If you continue, you may lose your settings and list of enabled plugins.", true, e);
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not open the backup configuration file. If you continue, your program options will be reset.", true, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -11,7 +11,6 @@ using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Notification.Screenshot;
|
||||
using TweetDuck.Core.Notification.Sound;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
@@ -20,6 +19,7 @@ using TweetDuck.Plugins.Events;
|
||||
using TweetDuck.Resources;
|
||||
using TweetDuck.Updates;
|
||||
using TweetDuck.Updates.Events;
|
||||
using TweetLib.Audio.Utils;
|
||||
|
||||
namespace TweetDuck.Core{
|
||||
sealed partial class FormBrowser : Form{
|
||||
@@ -38,7 +38,7 @@ namespace TweetDuck.Core{
|
||||
private FormWindowState prevState;
|
||||
|
||||
private TweetScreenshotManager notificationScreenshotManager;
|
||||
private ISoundNotificationPlayer soundNotification;
|
||||
private SoundNotification soundNotification;
|
||||
|
||||
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){
|
||||
InitializeComponent();
|
||||
@@ -312,12 +312,12 @@ namespace TweetDuck.Core{
|
||||
using(FormMessage form = new FormMessage("Notification Sound Error", "Could not play custom notification sound."+Environment.NewLine+e.Message, MessageBoxIcon.Error)){
|
||||
form.CancelButton = form.AddButton("Ignore");
|
||||
|
||||
Button btnOpenSettings = form.AddButton("Open Settings");
|
||||
Button btnOpenSettings = form.AddButton("View Options");
|
||||
btnOpenSettings.Width += 16;
|
||||
btnOpenSettings.Location = new Point(btnOpenSettings.Location.X-16, btnOpenSettings.Location.Y);
|
||||
|
||||
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnOpenSettings){
|
||||
OpenSettings(FormSettings.TabIndexNotification);
|
||||
OpenSettings(FormSettings.TabIndexSounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -427,7 +427,7 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
|
||||
if (soundNotification == null){
|
||||
soundNotification = SoundNotification.New();
|
||||
soundNotification = new SoundNotification();
|
||||
soundNotification.PlaybackError += soundNotification_PlaybackError;
|
||||
}
|
||||
|
||||
|
@@ -20,7 +20,7 @@ namespace TweetDuck.Core.Handling{
|
||||
|
||||
private const string TitleReloadBrowser = "Reload browser";
|
||||
private const string TitleMuteNotifications = "Mute notifications";
|
||||
private const string TitleSettings = "Settings";
|
||||
private const string TitleSettings = "Options";
|
||||
private const string TitlePlugins = "Plugins";
|
||||
private const string TitleAboutProgram = "About "+Program.BrandName;
|
||||
|
||||
|
@@ -5,5 +5,13 @@ namespace TweetDuck.Core.Handling{
|
||||
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
|
||||
browser.Reload();
|
||||
}
|
||||
|
||||
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
|
||||
if (request.ResourceType == ResourceType.Script && request.Url.Contains("analytics.")){
|
||||
return CefReturnValue.Cancel;
|
||||
}
|
||||
|
||||
return CefReturnValue.Continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Core.Notification.Sound{
|
||||
interface ISoundNotificationPlayer : IDisposable{
|
||||
string SupportedFormats { get; }
|
||||
|
||||
event EventHandler<PlaybackErrorEventArgs> PlaybackError;
|
||||
|
||||
void Play(string file);
|
||||
void Stop();
|
||||
}
|
||||
}
|
@@ -1,28 +1,29 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using TweetDuck.Core.Notification.Sound;
|
||||
using System;
|
||||
using TweetLib.Audio;
|
||||
using TweetLib.Audio.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
static class SoundNotification{
|
||||
private static bool? IsWMPAvailable;
|
||||
sealed class SoundNotification : IDisposable{
|
||||
public string SupportedFormats => player.SupportedFormats;
|
||||
public event EventHandler<PlaybackErrorEventArgs> PlaybackError;
|
||||
|
||||
public static ISoundNotificationPlayer New(){
|
||||
if (IsWMPAvailable.HasValue){
|
||||
if (IsWMPAvailable.Value){
|
||||
return new SoundPlayerImplWMP();
|
||||
}
|
||||
else{
|
||||
return new SoundPlayerImplFallback();
|
||||
}
|
||||
}
|
||||
private readonly AudioPlayer player;
|
||||
|
||||
try{
|
||||
SoundPlayerImplWMP implWMP = new SoundPlayerImplWMP();
|
||||
IsWMPAvailable = true;
|
||||
return implWMP;
|
||||
}catch(COMException){
|
||||
IsWMPAvailable = false;
|
||||
return new SoundPlayerImplFallback();
|
||||
}
|
||||
public SoundNotification(){
|
||||
this.player = AudioPlayer.New();
|
||||
this.player.PlaybackError += Player_PlaybackError;
|
||||
}
|
||||
|
||||
public void Play(string file){
|
||||
player.Play(file);
|
||||
}
|
||||
|
||||
private void Player_PlaybackError(object sender, PlaybackErrorEventArgs e){
|
||||
PlaybackError?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
player.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ using TweetDuck.Updates;
|
||||
|
||||
namespace TweetDuck.Core.Other{
|
||||
sealed partial class FormSettings : Form{
|
||||
public const int TabIndexNotification = 1;
|
||||
public const int TabIndexSounds = 2;
|
||||
|
||||
private readonly FormBrowser browser;
|
||||
private readonly Dictionary<Type, BaseTabSettings> tabs = new Dictionary<Type, BaseTabSettings>(4);
|
||||
@@ -16,7 +16,7 @@ namespace TweetDuck.Core.Other{
|
||||
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, int startTabIndex = 0){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName+" Settings";
|
||||
Text = Program.BrandName+" Options";
|
||||
|
||||
this.browser = browser;
|
||||
this.browser.PauseNotification();
|
||||
|
@@ -13,7 +13,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
public virtual void OnClosing(){}
|
||||
|
||||
protected static void PromptRestart(){
|
||||
if (MessageBox.Show("The application must restart for the setting to take place. Do you want to restart now?", Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes){
|
||||
if (MessageBox.Show("The application must restart for the option to take place. Do you want to restart now?", Program.BrandName+" Options", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes){
|
||||
Program.Restart();
|
||||
}
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
public DialogSettingsCSS(Action<string> reinjectBrowserCSS){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName+" Settings - CSS";
|
||||
Text = Program.BrandName+" Options - CSS";
|
||||
|
||||
this.reinjectBrowserCSS = reinjectBrowserCSS;
|
||||
|
||||
|
@@ -10,7 +10,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
public DialogSettingsCefArgs(){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName+" Settings - CEF Arguments";
|
||||
Text = Program.BrandName+" Options - CEF Arguments";
|
||||
|
||||
textBoxArgs.EnableMultilineShortcuts();
|
||||
textBoxArgs.Text = Program.UserConfig.CustomCefArgs ?? "";
|
||||
@@ -31,7 +31,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
}
|
||||
|
||||
int count = CommandLineArgsParser.ReadCefArguments(CefArgs).Count;
|
||||
string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "All arguments will be removed from the settings. Continue?" : count+(count == 1 ? " argument" : " arguments")+" will be added to the settings. Continue?";
|
||||
string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "All current arguments will be removed. Continue?" : count+(count == 1 ? " argument was" : " arguments were")+" detected. Continue?";
|
||||
|
||||
if (MessageBox.Show(prompt, "Confirm CEF Arguments", MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.OK){
|
||||
DialogResult = DialogResult.OK;
|
||||
|
@@ -66,9 +66,8 @@
|
||||
this.cbConfig.Name = "cbConfig";
|
||||
this.cbConfig.Size = new System.Drawing.Size(106, 17);
|
||||
this.cbConfig.TabIndex = 0;
|
||||
this.cbConfig.Text = "Program Settings";
|
||||
this.toolTip.SetToolTip(this.cbConfig, "Interface, notification, and update settings.\r\nIncludes a list of disabled plugin" +
|
||||
"s.");
|
||||
this.cbConfig.Text = "Program Options";
|
||||
this.toolTip.SetToolTip(this.cbConfig, "Interface, notification, and update options.");
|
||||
this.cbConfig.UseVisualStyleBackColor = true;
|
||||
this.cbConfig.CheckedChanged += new System.EventHandler(this.cbConfig_CheckedChanged);
|
||||
//
|
||||
|
@@ -79,8 +79,8 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
|
||||
if (form.DialogResult == DialogResult.OK){
|
||||
Config.CustomCefArgs = form.CefArgs;
|
||||
form.Dispose();
|
||||
PromptRestart();
|
||||
form.Dispose();
|
||||
}
|
||||
else form.Dispose();
|
||||
};
|
||||
@@ -131,8 +131,8 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
OverwritePrompt = true,
|
||||
DefaultExt = "tdsettings",
|
||||
FileName = Program.BrandName+".tdsettings",
|
||||
Title = "Export "+Program.BrandName+" Settings",
|
||||
Filter = Program.BrandName+" Settings (*.tdsettings)|*.tdsettings"
|
||||
Title = "Export "+Program.BrandName+" Profile",
|
||||
Filter = Program.BrandName+" Profile (*.tdsettings)|*.tdsettings"
|
||||
}){
|
||||
if (dialog.ShowDialog() != DialogResult.OK){
|
||||
return;
|
||||
@@ -146,7 +146,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
ExportManager manager = new ExportManager(file, plugins);
|
||||
|
||||
if (!manager.Export(flags)){
|
||||
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting "+Program.BrandName+" settings.", true, manager.LastException);
|
||||
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting "+Program.BrandName+" profile.", true, manager.LastException);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,8 +156,8 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
using(OpenFileDialog dialog = new OpenFileDialog{
|
||||
AutoUpgradeEnabled = true,
|
||||
DereferenceLinks = true,
|
||||
Title = "Import "+Program.BrandName+" Settings",
|
||||
Filter = Program.BrandName+" Settings (*.tdsettings)|*.tdsettings"
|
||||
Title = "Import "+Program.BrandName+" Profile",
|
||||
Filter = Program.BrandName+" Profile (*.tdsettings)|*.tdsettings"
|
||||
}){
|
||||
if (dialog.ShowDialog() != DialogResult.OK){
|
||||
return;
|
||||
@@ -183,12 +183,12 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
}
|
||||
else{
|
||||
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing "+Program.BrandName+" settings.", true, manager.LastException);
|
||||
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing "+Program.BrandName+" profile.", true, manager.LastException);
|
||||
}
|
||||
}
|
||||
|
||||
private void btnReset_Click(object sender, EventArgs e){
|
||||
if (MessageBox.Show("This will reset all of your program settings. Plugins will not be affected. Do you want to proceed?", "Reset "+Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
||||
if (MessageBox.Show("This will reset all of your program options. Plugins will not be affected. Do you want to proceed?", "Reset "+Program.BrandName+" Options", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
||||
Program.ResetConfig();
|
||||
((FormSettings)ParentForm).ReloadUI();
|
||||
}
|
||||
|
@@ -3,16 +3,16 @@ using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Notification.Sound;
|
||||
using TweetLib.Audio.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings{
|
||||
partial class TabSettingsSounds : BaseTabSettings{
|
||||
private readonly ISoundNotificationPlayer soundNotification;
|
||||
private readonly SoundNotification soundNotification;
|
||||
|
||||
public TabSettingsSounds(){
|
||||
InitializeComponent();
|
||||
|
||||
soundNotification = SoundNotification.New();
|
||||
soundNotification = new SoundNotification();
|
||||
soundNotification.PlaybackError += sound_PlaybackError;
|
||||
|
||||
tbCustomSound.Text = Config.NotificationSoundPath;
|
||||
|
@@ -73,22 +73,6 @@ namespace TweetDuck.Plugins{
|
||||
if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
|
||||
string dataFolder = GetPluginFolder(PluginFolder.Data);
|
||||
|
||||
if (!Directory.Exists(dataFolder)){ // config migration
|
||||
string originalFile = Path.Combine(GetPluginFolder(PluginFolder.Root), ConfigFile);
|
||||
|
||||
if (File.Exists(originalFile)){
|
||||
try{
|
||||
Directory.CreateDirectory(dataFolder);
|
||||
File.Copy(originalFile, configPath, false);
|
||||
File.Delete(originalFile); // will fail without write perms in program folder, ignore if so
|
||||
}catch{
|
||||
// ignore
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try{
|
||||
Directory.CreateDirectory(dataFolder);
|
||||
File.Copy(defaultConfigPath, configPath, false);
|
||||
|
@@ -5,9 +5,7 @@ using System.Text;
|
||||
using TweetDuck.Plugins.Events;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
[Serializable]
|
||||
sealed class PluginConfig{
|
||||
[field:NonSerialized]
|
||||
public event EventHandler<PluginChangedStateEventArgs> InternalPluginChangedState; // should only be accessed from PluginManager
|
||||
|
||||
public IEnumerable<string> DisabledPlugins => Disabled;
|
||||
@@ -18,14 +16,6 @@ namespace TweetDuck.Plugins{
|
||||
"official/reply-account"
|
||||
};
|
||||
|
||||
public void ImportLegacy(PluginConfig config){
|
||||
Disabled.Clear();
|
||||
|
||||
foreach(string plugin in config.Disabled){
|
||||
Disabled.Add(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetEnabled(Plugin plugin, bool enabled){
|
||||
if ((enabled && Disabled.Remove(plugin.Identifier)) || (!enabled && Disabled.Add(plugin.Identifier))){
|
||||
InternalPluginChangedState?.Invoke(this, new PluginChangedStateEventArgs(plugin, enabled));
|
||||
|
@@ -42,29 +42,14 @@ namespace TweetDuck.Plugins{
|
||||
this.Config = new PluginConfig();
|
||||
this.Bridge = new PluginBridge(this);
|
||||
|
||||
LoadConfig();
|
||||
Config.Load(configPath);
|
||||
|
||||
Config.InternalPluginChangedState += Config_InternalPluginChangedState;
|
||||
Program.UserConfigReplaced += Program_UserConfigReplaced;
|
||||
}
|
||||
|
||||
private void LoadConfig(){
|
||||
#pragma warning disable 612
|
||||
if (Program.UserConfig.Plugins != null){
|
||||
Config.ImportLegacy(Program.UserConfig.Plugins);
|
||||
Config.Save(configPath);
|
||||
|
||||
Program.UserConfig.Plugins = null;
|
||||
Program.UserConfig.Save();
|
||||
}
|
||||
#pragma warning restore 612
|
||||
else{
|
||||
Config.Load(configPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void Program_UserConfigReplaced(object sender, EventArgs e){
|
||||
LoadConfig();
|
||||
Config.Load(configPath);
|
||||
Reload();
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
using System.Text;
|
||||
using System.Globalization;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
@@ -8,22 +8,29 @@ namespace TweetDuck.Plugins{
|
||||
}
|
||||
|
||||
public static string GeneratePlugin(string pluginIdentifier, string pluginContents, int pluginToken, PluginEnvironment environment){
|
||||
StringBuilder build = new StringBuilder(2*pluginIdentifier.Length+pluginContents.Length+165);
|
||||
|
||||
build.Append("(function(").Append(environment.GetScriptVariables()).Append("){");
|
||||
|
||||
build.Append("let tmp={");
|
||||
build.Append("id:\"").Append(pluginIdentifier).Append("\",");
|
||||
build.Append("obj:new class extends PluginBase{").Append(pluginContents).Append("}");
|
||||
build.Append("};");
|
||||
|
||||
build.Append("tmp.obj.$id=\"").Append(pluginIdentifier).Append("\";");
|
||||
build.Append("tmp.obj.$token=").Append(pluginToken).Append(";");
|
||||
build.Append("window.TD_PLUGINS.install(tmp);");
|
||||
|
||||
build.Append("})(").Append(environment.GetScriptVariables()).Append(");");
|
||||
|
||||
return build.ToString();
|
||||
return PluginGen
|
||||
.Replace("%params", environment.GetScriptVariables())
|
||||
.Replace("%id", pluginIdentifier)
|
||||
.Replace("%token", pluginToken.ToString(CultureInfo.InvariantCulture))
|
||||
.Replace("%contents", pluginContents);
|
||||
}
|
||||
|
||||
private const string PluginGen = "(function(%params,$d){let tmp={id:'%id',obj:new class extends PluginBase{%contents}};$d(tmp.obj,'$id',{value:'%id'});$d(tmp.obj,'$token',{value:%token});window.TD_PLUGINS.install(tmp);})(%params,Object.defineProperty);";
|
||||
|
||||
/* PluginGen
|
||||
|
||||
(function(%params, $i, $d){
|
||||
let tmp = {
|
||||
id: '%id',
|
||||
obj: new class extends PluginBase{%contents}
|
||||
};
|
||||
|
||||
$d(tmp.obj, '$id', { value: '%id' });
|
||||
$d(tmp.obj, '$token', { value: %token });
|
||||
|
||||
window.TD_PLUGINS.install(tmp);
|
||||
})(%params, Object.defineProperty);
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
24
Program.cs
24
Program.cs
@@ -20,8 +20,8 @@ namespace TweetDuck{
|
||||
public const string BrandName = "TweetDuck";
|
||||
public const string Website = "https://tweetduck.chylex.com";
|
||||
|
||||
public const string VersionTag = "1.7.6";
|
||||
public const string VersionFull = "1.7.6.0";
|
||||
public const string VersionTag = "1.8";
|
||||
public const string VersionFull = "1.8.0.0";
|
||||
|
||||
public static readonly Version Version = new Version(VersionTag);
|
||||
public static readonly bool IsPortable = File.Exists("makeportable");
|
||||
@@ -29,17 +29,19 @@ namespace TweetDuck{
|
||||
public static readonly string ProgramPath = AppDomain.CurrentDomain.BaseDirectory;
|
||||
public static readonly string StoragePath = IsPortable ? Path.Combine(ProgramPath, "portable", "storage") : GetDataStoragePath();
|
||||
|
||||
public static readonly string UserConfigFilePath = Path.Combine(StoragePath, "TD_UserConfig.cfg");
|
||||
public static readonly string SystemConfigFilePath = Path.Combine(StoragePath, "TD_SystemConfig.cfg");
|
||||
public static readonly string PluginDataPath = Path.Combine(StoragePath, "TD_Plugins");
|
||||
public static readonly string PluginConfigFilePath = Path.Combine(StoragePath, "TD_PluginConfig.cfg");
|
||||
private static readonly string ErrorLogFilePath = Path.Combine(StoragePath, "TD_Log.txt");
|
||||
private static readonly string ConsoleLogFilePath = Path.Combine(StoragePath, "TD_Console.txt");
|
||||
private static readonly string InstallerPath = Path.Combine(StoragePath, "TD_Updates");
|
||||
|
||||
public static readonly string ScriptPath = Path.Combine(ProgramPath, "scripts");
|
||||
public static readonly string PluginPath = Path.Combine(ProgramPath, "plugins");
|
||||
|
||||
public static readonly string UserConfigFilePath = Path.Combine(StoragePath, "TD_UserConfig.cfg");
|
||||
public static readonly string SystemConfigFilePath = Path.Combine(StoragePath, "TD_SystemConfig.cfg");
|
||||
public static readonly string PluginConfigFilePath = Path.Combine(StoragePath, "TD_PluginConfig.cfg");
|
||||
|
||||
public static readonly string PluginDataPath = Path.Combine(StoragePath, "TD_Plugins");
|
||||
private static readonly string InstallerPath = Path.Combine(StoragePath, "TD_Updates");
|
||||
|
||||
private static string ErrorLogFilePath => Path.Combine(StoragePath, "TD_Log.txt");
|
||||
private static string ConsoleLogFilePath => Path.Combine(StoragePath, "TD_Console.txt");
|
||||
|
||||
public static uint WindowRestoreMessage;
|
||||
|
||||
private static readonly LockManager LockManager = new LockManager(Path.Combine(StoragePath, ".lock"));
|
||||
@@ -231,7 +233,7 @@ namespace TweetDuck{
|
||||
File.Delete(UserConfigFilePath);
|
||||
File.Delete(UserConfig.GetBackupFile(UserConfigFilePath));
|
||||
}catch(Exception e){
|
||||
Reporter.HandleException("Configuration Reset Error", "Could not delete configuration files to reset the settings.", true, e);
|
||||
Reporter.HandleException("Configuration Reset Error", "Could not delete configuration files to reset the options.", true, e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -39,7 +39,7 @@ using TweetDuck;
|
||||
|
||||
[assembly: NeutralResourcesLanguage("en")]
|
||||
|
||||
[assembly: CLSCompliant(false)]
|
||||
[assembly: CLSCompliant(true)]
|
||||
|
||||
#if DEBUG
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("UnitTests")]
|
||||
|
@@ -3,7 +3,7 @@ Clear columns
|
||||
|
||||
[description]
|
||||
- Adds buttons and keyboard shortcuts to quickly clear columns
|
||||
- Hold Shift when clicking or using a keyboard shortcut to reset the column instead
|
||||
- Hold Shift when clicking or using a keyboard shortcut to restore the column instead
|
||||
|
||||
[author]
|
||||
chylex
|
||||
|
@@ -38,7 +38,7 @@ enabled(){
|
||||
$(document).off("mousemove", this.eventKeyUp);
|
||||
}
|
||||
|
||||
$("#clear-columns-btn-all").text(pressed ? "Reset all" : "Clear all");
|
||||
$("#clear-columns-btn-all").text(pressed ? "Restore columns" : "Clear columns");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,7 +84,7 @@ enabled(){
|
||||
replaceMustache("column/column_header.mustache", "</header>", [
|
||||
'{{^isTemporary}}',
|
||||
'<a class="column-header-link" href="#" data-action="td-clearcolumns-dosingle" style="right:34px">',
|
||||
'<i class="icon icon-clear-timeline"></i>',
|
||||
'<i class="icon icon-clear-timeline js-show-tip" data-placement="bottom" data-original-title="Clear column (hold Shift to restore)"></i>',
|
||||
'</a>',
|
||||
'{{/isTemporary}}',
|
||||
'</header>'
|
||||
@@ -94,7 +94,7 @@ enabled(){
|
||||
'<dd class="keyboard-shortcut-definition" style="white-space:nowrap">',
|
||||
'<span class="text-like-keyboard-key">1</span> … <span class="text-like-keyboard-key">9</span> + <span class="text-like-keyboard-key">Del</span> Clear column 1-9',
|
||||
'</dd><dd class="keyboard-shortcut-definition">',
|
||||
'<span class="text-like-keyboard-key">Alt</span> + <span class="text-like-keyboard-key">Del</span> Clear all',
|
||||
'<span class="text-like-keyboard-key">Alt</span> + <span class="text-like-keyboard-key">Del</span> Clear all columns',
|
||||
'</dd></dl><dl'
|
||||
].join(""));
|
||||
|
||||
@@ -116,11 +116,18 @@ ready(){
|
||||
|
||||
// add clear all button
|
||||
$("nav.app-navigator").first().append([
|
||||
'<a class="link-clean cf app-nav-link padding-h--10" data-title="Clear all" data-action="td-clearcolumns-doall">',
|
||||
'<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 all</div>',
|
||||
'<div id="clear-columns-btn-all" class="nbfc padding-ts hide-condensed txt-size--16">Clear columns</div>',
|
||||
'</a></nav>'
|
||||
].join(""));
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
disabled(){
|
||||
|
@@ -8,7 +8,7 @@ Edit layout & design
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.0
|
||||
1.1
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
@@ -7,6 +7,7 @@ constructor(){
|
||||
enabled(){
|
||||
// elements & data
|
||||
this.css = null;
|
||||
this.icons = null;
|
||||
this.htmlModal = null;
|
||||
this.config = null;
|
||||
|
||||
@@ -18,11 +19,14 @@ enabled(){
|
||||
revertReplies: false,
|
||||
themeColorTweaks: true,
|
||||
roundedScrollBars: false,
|
||||
revertIcons: true,
|
||||
smallComposeTextSize: false,
|
||||
optimizeAnimations: true,
|
||||
avatarRadius: 10
|
||||
};
|
||||
|
||||
this.firstTimeLoad = null;
|
||||
|
||||
// modal dialog loading
|
||||
$TDP.readFileRoot(this.$token, "modal.html").then(contents => {
|
||||
this.htmlModal = contents;
|
||||
@@ -32,30 +36,58 @@ enabled(){
|
||||
|
||||
// configuration
|
||||
const configFile = "config.json";
|
||||
|
||||
this.tmpConfig = null;
|
||||
this.currentStage = 0;
|
||||
|
||||
var loadConfigObject = obj => {
|
||||
this.tmpConfig = obj || {};
|
||||
|
||||
if (TD.ready){
|
||||
this.onAppReady();
|
||||
this.onStageReady = () => {
|
||||
if (this.currentStage === 0){
|
||||
this.currentStage = 1;
|
||||
}
|
||||
|
||||
this.injectDeciderReplyHook(this.tmpConfig.revertReplies);
|
||||
};
|
||||
|
||||
this.onAppReady = () => {
|
||||
if (this.tmpConfig !== null){
|
||||
else if (this.tmpConfig !== null){
|
||||
this.config = $.extend(this.defaultConfig, this.tmpConfig);
|
||||
this.tmpConfig = null;
|
||||
this.reinjectAll();
|
||||
|
||||
if (this.firstTimeLoad){
|
||||
$TDP.writeFile(this.$token, configFile, JSON.stringify(this.config));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var loadConfigObject = obj => {
|
||||
this.tmpConfig = obj || {};
|
||||
this.firstTimeLoad = obj === null;
|
||||
|
||||
this.onStageReady();
|
||||
this.injectDeciderReplyHook(obj && obj.revertReplies);
|
||||
};
|
||||
|
||||
if (this.$$wasLoadedBefore){
|
||||
this.onStageReady();
|
||||
}
|
||||
else{
|
||||
$(document).one("dataSettingsValues", () => {
|
||||
switch(TD.settings.getColumnWidth()){
|
||||
case "wide": this.defaultConfig.columnWidth = "350px"; break;
|
||||
case "narrow": this.defaultConfig.columnWidth = "270px"; break;
|
||||
}
|
||||
|
||||
switch(TD.settings.getFontSize()){
|
||||
case "small": this.defaultConfig.fontSize = "13px"; break;
|
||||
case "medium": this.defaultConfig.fontSize = "14px"; break;
|
||||
case "large": this.defaultConfig.fontSize = "15px"; break;
|
||||
case "largest": this.defaultConfig.fontSize = "16px"; break;
|
||||
}
|
||||
|
||||
this.$$wasLoadedBefore = true;
|
||||
this.onStageReady();
|
||||
});
|
||||
}
|
||||
|
||||
$TDP.checkFileExists(this.$token, configFile).then(exists => {
|
||||
if (!exists){
|
||||
loadConfigObject(null);
|
||||
$TDP.writeFile(this.$token, configFile, JSON.stringify(this.defaultConfig));
|
||||
}
|
||||
else{
|
||||
$TDP.readFile(this.$token, configFile, true).then(contents => {
|
||||
@@ -208,7 +240,7 @@ enabled(){
|
||||
_render: () => $(this.htmlModal),
|
||||
destroy: function(){
|
||||
if (this.reloadPage){
|
||||
location.reload();
|
||||
window.TDPF_requestReload();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -227,7 +259,7 @@ enabled(){
|
||||
};
|
||||
|
||||
TD.decider.updateForGuestId();
|
||||
this.$pluginSettings.requiresPageReload = enable;
|
||||
this.$requiresReload = enable;
|
||||
};
|
||||
|
||||
// animation optimization
|
||||
@@ -282,13 +314,13 @@ enabled(){
|
||||
};
|
||||
|
||||
this.onWindowFocusEvent = () => {
|
||||
if (this.config.optimizeAnimations){
|
||||
if (this.config && this.config.optimizeAnimations){
|
||||
injectOptimizations(true);
|
||||
}
|
||||
};
|
||||
|
||||
this.onWindowBlurEvent = () => {
|
||||
if (this.config.optimizeAnimations){
|
||||
if (this.config && this.config.optimizeAnimations){
|
||||
disableOptimizations();
|
||||
clearOptimizationTimer();
|
||||
}
|
||||
@@ -301,6 +333,11 @@ enabled(){
|
||||
}
|
||||
|
||||
this.css = window.TDPF_createCustomStyle(this);
|
||||
|
||||
if (this.icons){
|
||||
document.head.removeChild(this.icons);
|
||||
this.icons = null;
|
||||
}
|
||||
};
|
||||
|
||||
this.reinjectAll = () => {
|
||||
@@ -338,8 +375,9 @@ enabled(){
|
||||
|
||||
if (this.config.hideTweetActions){
|
||||
this.css.insert(".tweet-action { opacity: 0; }");
|
||||
this.css.insert(".tweet-actions.is-visible .tweet-action { opacity: 0.5; }");
|
||||
this.css.insert(".is-favorite .tweet-action, .is-retweet .tweet-action { opacity: 0.5; visibility: visible !important }");
|
||||
this.css.insert(".tweet:hover .tweet-action, .is-favorite .tweet-action[rel='favorite'], .is-retweet .tweet-action[rel='retweet'] { opacity: 1; visibility: visible !important }");
|
||||
this.css.insert(".tweet:hover .tweet-action, .tweet-action.is-selected, .is-favorite .tweet-action[rel='favorite'], .is-retweet .tweet-action[rel='retweet'] { opacity: 1 !important; visibility: visible !important }");
|
||||
}
|
||||
|
||||
if (this.config.moveTweetActionsToRight){
|
||||
@@ -367,6 +405,98 @@ enabled(){
|
||||
this.css.insert(".activity-header + .tweet .tweet-context .obj-left { margin-right: 5px }");
|
||||
}
|
||||
|
||||
if (this.config.revertIcons){
|
||||
this.icons = document.createElement("style");
|
||||
this.icons.innerHTML = `
|
||||
@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-twitter-bird:before{content:"\\f000";font-family:tweetdeckold}
|
||||
.icon-mention:before{content:"\\f001";font-family:tweetdeckold}
|
||||
.icon-following:before{content:"\\f002";font-family:tweetdeckold}
|
||||
.icon-message:before{content:"\\f003";font-family:tweetdeckold}
|
||||
.icon-home:before{content:"\\f004";font-family:tweetdeckold}
|
||||
.icon-hashtag:before{content:"\\f005";font-family:tweetdeckold}
|
||||
.icon-reply:before{content:"\\f006";font-family:tweetdeckold}
|
||||
.icon-favorite:before{content:"\\f055";font-family:tweetdeckold}
|
||||
.icon-retweet:before{content:"\\f008";font-family:tweetdeckold}
|
||||
.icon-drafts:before{content:"\\f009";font-family:tweetdeckold}
|
||||
.icon-search:before{content:"\\f00a";font-family:tweetdeckold}
|
||||
.icon-trash:before{content:"\\f00c";font-family:tweetdeckold}
|
||||
.icon-close:before{content:"\\f00d";font-family:tweetdeckold}
|
||||
.icon-arrow-r:before,.Icon--caretRight:before{content:"\\f00e";font-family:tweetdeckold}
|
||||
.icon-arrow-l:before,.Icon--caretLeft:before{content:"\\f00f";font-family:tweetdeckold}
|
||||
.icon-protected:before{content:"\\f013";font-family:tweetdeckold}
|
||||
.icon-list:before{content:"\\f014";font-family:tweetdeckold}
|
||||
.icon-camera:before{content:"\\f015";font-family:tweetdeckold}
|
||||
.icon-more:before{content:"\\f016";font-family:tweetdeckold}
|
||||
.icon-settings:before{content:"\\f018";font-family:tweetdeckold}
|
||||
.icon-notifications:before{content:"\\f019";font-family:tweetdeckold}
|
||||
.icon-user-dd:before{content:"\\f01a";font-family:tweetdeckold}
|
||||
.icon-activity:before{content:"\\f01c";font-family:tweetdeckold}
|
||||
.icon-trending:before{content:"\\f01d";font-family:tweetdeckold}
|
||||
.icon-minus:before{content:"\\f01e";font-family:tweetdeckold}
|
||||
.icon-plus:before{content:"\\f01f";font-family:tweetdeckold}
|
||||
.icon-geo:before{content:"\\f020";font-family:tweetdeckold}
|
||||
.icon-check:before{content:"\\f021";font-family:tweetdeckold}
|
||||
.icon-schedule:before{content:"\\f022";font-family:tweetdeckold}
|
||||
.icon-dot:before{content:"\\f023";font-family:tweetdeckold}
|
||||
.icon-user:before{content:"\\f024";font-family:tweetdeckold}
|
||||
.icon-content:before{content:"\\f025";font-family:tweetdeckold}
|
||||
.icon-arrow-d:before,.Icon--caretDown:before{content:"\\f026";font-family:tweetdeckold}
|
||||
.icon-arrow-u:before{content:"\\f027";font-family:tweetdeckold}
|
||||
.icon-share:before{content:"\\f028";font-family:tweetdeckold}
|
||||
.icon-info:before{content:"\\f029";font-family:tweetdeckold}
|
||||
.icon-verified:before{content:"\\f02a";font-family:tweetdeckold}
|
||||
.icon-translator:before{content:"\\f02b";font-family:tweetdeckold}
|
||||
.icon-blocked:before{content:"\\f02c";font-family:tweetdeckold}
|
||||
.icon-constrain:before{content:"\\f02d";font-family:tweetdeckold}
|
||||
.icon-play-video:before{content:"\\f02e";font-family:tweetdeckold}
|
||||
.icon-empty:before{content:"\\f02f";font-family:tweetdeckold}
|
||||
.icon-clear-input:before{content:"\\f030";font-family:tweetdeckold}
|
||||
.icon-compose:before{content:"\\f031";font-family:tweetdeckold}
|
||||
.icon-mark-read:before{content:"\\f032";font-family:tweetdeckold}
|
||||
.icon-arrow-r-double:before{content:"\\f033";font-family:tweetdeckold}
|
||||
.icon-arrow-l-double:before{content:"\\f034";font-family:tweetdeckold}
|
||||
.icon-follow:before{content:"\\f035";font-family:tweetdeckold}
|
||||
.icon-image:before{content:"\\f036";font-family:tweetdeckold}
|
||||
.icon-popout:before{content:"\\f037";font-family:tweetdeckold}
|
||||
.icon-move:before{content:"\\f039";font-family:tweetdeckold}
|
||||
.icon-compose-grid:before{content:"\\f03a";font-family:tweetdeckold}
|
||||
.icon-compose-minigrid:before{content:"\\f03b";font-family:tweetdeckold}
|
||||
.icon-compose-list:before{content:"\\f03c";font-family:tweetdeckold}
|
||||
.icon-edit:before{content:"\\f040";font-family:tweetdeckold}
|
||||
.icon-clear-timeline:before{content:"\\f041";font-family:tweetdeckold}
|
||||
.icon-sliders:before{content:"\\f042";font-family:tweetdeckold}
|
||||
.icon-custom-timeline:before{content:"\\f043";font-family:tweetdeckold}
|
||||
.icon-compose-dm:before{content:"\\f044";font-family:tweetdeckold}
|
||||
.icon-bg-dot:before{content:"\\f045";font-family:tweetdeckold}
|
||||
.icon-user-team-mgr:before{content:"\\f046";font-family:tweetdeckold}
|
||||
.icon-user-switch:before{content:"\\f047";font-family:tweetdeckold}
|
||||
.icon-conversation:before{content:"\\f048";font-family:tweetdeckold}
|
||||
.icon-dataminr:before{content:"\\f049";font-family:tweetdeckold}
|
||||
.icon-link:before{content:"\\f04a";font-family:tweetdeckold}
|
||||
.icon-flash:before{content:"\\f050";font-family:tweetdeckold}
|
||||
.icon-pointer-u:before{content:"\\f051";font-family:tweetdeckold}
|
||||
.icon-analytics:before{content:"\\f054";font-family:tweetdeckold}
|
||||
.icon-heart:before{content:"\\f055";font-family:tweetdeckold}
|
||||
.icon-calendar:before{content:"\\f056";font-family:tweetdeckold}
|
||||
.icon-attachment:before{content:"\\f057";font-family:tweetdeckold}
|
||||
.icon-play:before{content:"\\f058";font-family:tweetdeckold}
|
||||
.icon-bookmark:before{content:"\\f059";font-family:tweetdeckold}
|
||||
.icon-play-badge:before{content:"\\f060";font-family:tweetdeckold}
|
||||
.icon-gif-badge:before{content:"\\f061";font-family:tweetdeckold}
|
||||
.icon-poll:before{content:"\\f062";font-family:tweetdeckold}
|
||||
|
||||
.column-header .column-type-icon { bottom: 26px !important }`;
|
||||
|
||||
document.head.appendChild(this.icons);
|
||||
}
|
||||
|
||||
if (this.config.columnWidth[0] === '/'){
|
||||
let cols = this.config.columnWidth.slice(1);
|
||||
|
||||
@@ -399,13 +529,25 @@ enabled(){
|
||||
default: TD.settings.setFontSize(parseInt(this.config.fontSize, 10) >= 16 ? "largest" : "smallest"); break;
|
||||
}
|
||||
|
||||
$TDP.injectIntoNotificationsBefore(this.$token, "css", "</head>", [
|
||||
"<style type='text/css'>",
|
||||
".txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: "+this.config.fontSize+" !important }",
|
||||
".avatar { border-radius: "+this.config.avatarRadius+"% !important }",
|
||||
(this.config.revertReplies ? ".activity-header + .tweet .tweet-context { margin-left: -35px } .activity-header + .tweet .tweet-context .obj-left { margin-right: 5px }" : ""),
|
||||
"</style>"
|
||||
].join(""));
|
||||
$TDP.injectIntoNotificationsBefore(this.$token, "css", "</head>", `
|
||||
<style type='text/css'>
|
||||
.txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: ${this.config.fontSize} !important }
|
||||
.avatar { border-radius: ${this.config.avatarRadius}% !important }
|
||||
|
||||
${this.config.revertReplies ? `
|
||||
.activity-header + .tweet .tweet-context { margin-left: -35px }
|
||||
.activity-header + .tweet .tweet-context .obj-left { margin-right: 5px }
|
||||
` : ``}
|
||||
|
||||
${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}
|
||||
.icon-favorite:before{content:"\\f055";font-family:tweetdeckold}
|
||||
.icon-retweet:before{content:"\\f008";font-family:tweetdeckold}
|
||||
.icon-follow:before{content:"\\f035";font-family:tweetdeckold}
|
||||
.icon-user-dd:before{content:"\\f01a";font-family:tweetdeckold}
|
||||
` : ``}
|
||||
</style>`);
|
||||
};
|
||||
|
||||
this.uiShowActionsMenuEvent = () => {
|
||||
@@ -416,21 +558,6 @@ enabled(){
|
||||
}
|
||||
|
||||
ready(){
|
||||
// configuration
|
||||
switch(TD.settings.getColumnWidth()){
|
||||
case "wide": this.defaultConfig.columnWidth = "350px"; break;
|
||||
case "narrow": this.defaultConfig.columnWidth = "270px"; break;
|
||||
}
|
||||
|
||||
switch(TD.settings.getFontSize()){
|
||||
case "small": this.defaultConfig.fontSize = "13px"; break;
|
||||
case "medium": this.defaultConfig.fontSize = "14px"; break;
|
||||
case "large": this.defaultConfig.fontSize = "15px"; break;
|
||||
case "largest": this.defaultConfig.fontSize = "16px"; break;
|
||||
}
|
||||
|
||||
this.onAppReady();
|
||||
|
||||
// optimization events
|
||||
$(window).on("focus", this.onWindowFocusEvent);
|
||||
$(window).on("blur", this.onWindowBlurEvent);
|
||||
@@ -448,6 +575,10 @@ disabled(){
|
||||
this.css.remove();
|
||||
}
|
||||
|
||||
if (this.icons){
|
||||
document.head.removeChild(this.icons);
|
||||
}
|
||||
|
||||
if (this.optimizations){
|
||||
this.optimizations.remove();
|
||||
}
|
||||
|
@@ -106,6 +106,10 @@
|
||||
<input data-td-key="roundedScrollBars" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Rounded scroll bars
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input data-td-key="revertIcons" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Revert icon design
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input data-td-key="smallComposeTextSize" class="js-theme-checkbox touch-larger-label" type="checkbox">
|
||||
Small compose tweet font size
|
||||
|
@@ -9,7 +9,7 @@ Emoji keyboard
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.0
|
||||
1.1
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
@@ -1,5 +1,6 @@
|
||||
enabled(){
|
||||
this.selectedSkinTone = "";
|
||||
this.currentKeywords = [];
|
||||
|
||||
this.skinToneList = [
|
||||
"", "1F3FB", "1F3FC", "1F3FD", "1F3FE", "1F3FF"
|
||||
@@ -20,9 +21,10 @@ enabled(){
|
||||
|
||||
this.emojiURL = "https://ton.twimg.com/tweetdeck-web/web/assets/emoji/";
|
||||
|
||||
this.emojiHTML1 = ""; // no skin tones, prepended
|
||||
this.emojiHTML2 = {}; // contains emojis with skin tones
|
||||
this.emojiHTML3 = ""; // no skin tones, appended
|
||||
this.emojiData1 = []; // no skin tones, prepended
|
||||
this.emojiData2 = {}; // contains emojis with skin tones
|
||||
this.emojiData3 = []; // no skin tones, appended
|
||||
this.emojiNames = [];
|
||||
|
||||
var me = this;
|
||||
|
||||
@@ -33,6 +35,8 @@ enabled(){
|
||||
this.css.insert(".emoji-keyboard-list { height: 10.14em; padding: 0.1em; box-sizing: border-box; overflow-y: auto }");
|
||||
this.css.insert(".emoji-keyboard-list .separator { height: 26px }");
|
||||
this.css.insert(".emoji-keyboard-list img { padding: 0.1em !important; width: 1em; height: 1em; vertical-align: -0.1em; cursor: pointer }");
|
||||
this.css.insert(".emoji-keyboard-search { height: auto; padding: 4px 10px 8px; background-color: #292f33; border-radius: 2px 2px 0 0 }");
|
||||
this.css.insert(".emoji-keyboard-search input { width: 100%; border-radius: 1px; }");
|
||||
this.css.insert(".emoji-keyboard-skintones { height: 1.3em; text-align: center; background-color: #292f33; border-radius: 0 0 2px 2px }");
|
||||
this.css.insert(".emoji-keyboard-skintones div { width: 0.8em; height: 0.8em; margin: 0.25em 0.1em; border-radius: 50%; display: inline-block; box-sizing: border-box; cursor: pointer }");
|
||||
this.css.insert(".emoji-keyboard-skintones .sel { border: 2px solid rgba(0, 0, 0, 0.35); box-shadow: 0 0 2px 0 rgba(255, 255, 255, 0.65), 0 0 1px 0 rgba(255, 255, 255, 0.4) inset }");
|
||||
@@ -54,26 +58,70 @@ enabled(){
|
||||
// keyboard generation
|
||||
|
||||
this.currentKeyboard = null;
|
||||
this.currentSpanner = null;
|
||||
|
||||
var hideKeyboard = () => {
|
||||
$(this.currentKeyboard).remove();
|
||||
this.currentKeyboard = null;
|
||||
|
||||
$(this.currentSpanner).remove();
|
||||
this.currentSpanner = null;
|
||||
|
||||
this.composePanelScroller.trigger("scroll");
|
||||
|
||||
$(".emoji-keyboard-popup-btn").removeClass("is-selected");
|
||||
$(".js-compose-text").first().focus();
|
||||
};
|
||||
|
||||
var generateEmojiHTML = skinTone => {
|
||||
return (this.emojiHTML1+this.emojiHTML2[skinTone]+this.emojiHTML3).replace(/u#/g, this.emojiURL);
|
||||
let index = 0;
|
||||
let html = [ "<p style='font-size:13px;color:#444;margin:4px;text-align:center'>Please, note that most emoji will not show up properly in the text box above, but they will display in the tweet.</p>" ];
|
||||
|
||||
for(let array of [ this.emojiData1, this.emojiData2[skinTone], this.emojiData3 ]){
|
||||
for(let emoji of array){
|
||||
if (emoji === "___"){
|
||||
html.push("<div class='separator'></div>");
|
||||
}
|
||||
else{
|
||||
html.push(TD.util.cleanWithEmoji(emoji).replace(' class="emoji" draggable="false"', ''));
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return html.join("");
|
||||
};
|
||||
|
||||
var updateFilters = () => {
|
||||
let keywords = this.currentKeywords;
|
||||
let container = $(this.currentKeyboard.children[1]);
|
||||
|
||||
let emoji = container.children("img");
|
||||
let info = container.children("p:first");
|
||||
let separators = container.children("div");
|
||||
|
||||
if (keywords.length === 0){
|
||||
info.css("display", "block");
|
||||
separators.css("display", "block");
|
||||
emoji.css("display", "inline");
|
||||
}
|
||||
else{
|
||||
info.css("display", "none");
|
||||
separators.css("display", "none");
|
||||
|
||||
emoji.css("display", "none");
|
||||
emoji.filter(index => keywords.every(kw => me.emojiNames[index].includes(kw))).css("display", "inline");
|
||||
}
|
||||
};
|
||||
|
||||
var selectSkinTone = skinTone => {
|
||||
let selectedEle = this.currentKeyboard.children[1].querySelector("[data-tone='"+this.selectedSkinTone+"']");
|
||||
let selectedEle = this.currentKeyboard.children[2].querySelector("[data-tone='"+this.selectedSkinTone+"']");
|
||||
selectedEle && selectedEle.classList.remove("sel");
|
||||
|
||||
this.selectedSkinTone = skinTone;
|
||||
this.currentKeyboard.children[0].innerHTML = generateEmojiHTML(skinTone);
|
||||
this.currentKeyboard.children[1].querySelector("[data-tone='"+this.selectedSkinTone+"']").classList.add("sel");
|
||||
this.currentKeyboard.children[1].innerHTML = generateEmojiHTML(skinTone);
|
||||
this.currentKeyboard.children[2].querySelector("[data-tone='"+this.selectedSkinTone+"']").classList.add("sel");
|
||||
updateFilters();
|
||||
};
|
||||
|
||||
this.generateKeyboard = (input, left, top) => {
|
||||
@@ -103,10 +151,19 @@ enabled(){
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
var search = document.createElement("div");
|
||||
search.innerHTML = "<input type='text' placeholder='Search...'>";
|
||||
search.classList.add("emoji-keyboard-search");
|
||||
|
||||
var skintones = document.createElement("div");
|
||||
skintones.innerHTML = me.skinToneData.map(entry => "<div data-tone='"+entry[0]+"' style='background-color:"+entry[1]+"'></div>").join("");
|
||||
skintones.classList.add("emoji-keyboard-skintones");
|
||||
|
||||
outer.appendChild(search);
|
||||
outer.appendChild(keyboard);
|
||||
outer.appendChild(skintones);
|
||||
$(".js-app").append(outer);
|
||||
|
||||
skintones.addEventListener("click", function(e){
|
||||
if (e.target.hasAttribute("data-tone")){
|
||||
selectSkinTone(e.target.getAttribute("data-tone") || "");
|
||||
@@ -115,23 +172,36 @@ enabled(){
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
outer.appendChild(keyboard);
|
||||
outer.appendChild(skintones);
|
||||
document.body.appendChild(outer);
|
||||
search.addEventListener("click", function(e){
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
var searchInput = search.children[0];
|
||||
searchInput.focus();
|
||||
|
||||
searchInput.addEventListener("input", function(e){
|
||||
me.currentKeywords = e.target.value.split(" ").filter(kw => kw.length > 0).map(kw => kw.toLowerCase());
|
||||
updateFilters();
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
searchInput.addEventListener("focus", function(){
|
||||
$(this).select();
|
||||
});
|
||||
|
||||
this.currentKeyboard = outer;
|
||||
selectSkinTone(this.selectedSkinTone);
|
||||
|
||||
this.currentSpanner = document.createElement("div");
|
||||
this.currentSpanner.style.height = ($(this.currentKeyboard).height()-10)+"px";
|
||||
$(".emoji-keyboard-popup-btn").parent().after(this.currentSpanner);
|
||||
|
||||
this.composePanelScroller.trigger("scroll");
|
||||
};
|
||||
|
||||
this.prevTryPasteImage = window.TDGF_tryPasteImage;
|
||||
var prevTryPasteImageF = this.prevTryPasteImage;
|
||||
|
||||
window.TDGF_tryPasteImage = function(){
|
||||
if (me.currentKeyboard){
|
||||
hideKeyboard();
|
||||
}
|
||||
|
||||
return prevTryPasteImageF.apply(this, arguments);
|
||||
var getKeyboardTop = () => {
|
||||
let button = $(".emoji-keyboard-popup-btn");
|
||||
return button.offset().top+button.outerHeight()+me.composePanelScroller.scrollTop()+8;
|
||||
};
|
||||
|
||||
// event handlers
|
||||
@@ -139,18 +209,22 @@ enabled(){
|
||||
this.emojiKeyboardButtonClickEvent = function(e){
|
||||
if (me.currentKeyboard){
|
||||
hideKeyboard();
|
||||
$(this).blur();
|
||||
}
|
||||
else{
|
||||
var pos = $(this).offset();
|
||||
me.generateKeyboard($(".js-compose-text").first(), pos.left, pos.top+$(this).outerHeight()+8);
|
||||
|
||||
me.generateKeyboard($(".js-compose-text").first(), $(this).offset().left, getKeyboardTop());
|
||||
$(this).addClass("is-selected");
|
||||
}
|
||||
|
||||
$(this).blur();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
this.composerScrollEvent = function(e){
|
||||
if (me.currentKeyboard){
|
||||
me.currentKeyboard.style.marginTop = (-$(this).scrollTop())+"px";
|
||||
}
|
||||
};
|
||||
|
||||
this.documentClickEvent = function(e){
|
||||
if (me.currentKeyboard && !e.target.classList.contains("js-compose-text")){
|
||||
hideKeyboard();
|
||||
@@ -164,18 +238,21 @@ enabled(){
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* TODO
|
||||
* ----
|
||||
* add emoji search if I can be bothered
|
||||
* lazy emoji loading
|
||||
*/
|
||||
this.uploadFilesEvent = function(e){
|
||||
if (me.currentKeyboard){
|
||||
me.currentKeyboard.style.top = getKeyboardTop()+"px";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ready(){
|
||||
this.composePanelScroller = $(".js-compose-scroller", ".js-docked-compose").first().children().first();
|
||||
this.composePanelScroller.on("scroll", this.composerScrollEvent);
|
||||
|
||||
$(".emoji-keyboard-popup-btn").on("click", this.emojiKeyboardButtonClickEvent);
|
||||
$(document).on("click", this.documentClickEvent);
|
||||
$(document).on("keydown", this.documentKeyEvent);
|
||||
$(document).on("uiComposeImageAdded", this.uploadFilesEvent);
|
||||
|
||||
// HTML generation
|
||||
|
||||
@@ -190,18 +267,14 @@ ready(){
|
||||
};
|
||||
|
||||
$TDP.readFileRoot(this.$token, "emoji-ordering.txt").then(contents => {
|
||||
let generated1 = [];
|
||||
let generated2 = {};
|
||||
let generated3 = [];
|
||||
|
||||
for(let skinTone of this.skinToneList){
|
||||
generated2[skinTone] = [];
|
||||
this.emojiData2[skinTone] = [];
|
||||
}
|
||||
|
||||
// declaration inserters
|
||||
|
||||
let addDeclaration1 = decl => {
|
||||
generated1.push(decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join(""));
|
||||
this.emojiData1.push(decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join(""));
|
||||
};
|
||||
|
||||
let addDeclaration2 = (tone, decl) => {
|
||||
@@ -209,16 +282,16 @@ ready(){
|
||||
|
||||
if (tone === null){
|
||||
for(let skinTone of this.skinToneList){
|
||||
generated2[skinTone].push(gen);
|
||||
this.emojiData2[skinTone].push(gen);
|
||||
}
|
||||
}
|
||||
else{
|
||||
generated2[tone].push(gen);
|
||||
this.emojiData2[tone].push(gen);
|
||||
}
|
||||
};
|
||||
|
||||
let addDeclaration3 = decl => {
|
||||
generated3.push(decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join(""));
|
||||
this.emojiData3.push(decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join(""));
|
||||
};
|
||||
|
||||
// line reading
|
||||
@@ -228,9 +301,9 @@ ready(){
|
||||
for(let line of contents.split("\n")){
|
||||
if (line[0] === '@'){
|
||||
switch(skinToneState){
|
||||
case 0: generated1.push("___"); break;
|
||||
case 1: this.skinToneList.forEach(skinTone => generated2[skinTone].push("___")); break;
|
||||
case 2: generated3.push("___"); break;
|
||||
case 0: this.emojiData1.push("___"); break;
|
||||
case 1: this.skinToneList.forEach(skinTone => this.emojiData2[skinTone].push("___")); break;
|
||||
case 2: this.emojiData3.push("___"); break;
|
||||
}
|
||||
|
||||
if (line[1] === '1'){
|
||||
@@ -239,9 +312,15 @@ ready(){
|
||||
else if (line[1] === '2'){
|
||||
skinToneState = 2;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (skinToneState === 1){
|
||||
let decl = line.slice(0, line.indexOf(';'));
|
||||
|
||||
let semicolon = line.indexOf(';');
|
||||
let decl = line.slice(0, semicolon);
|
||||
let desc = line.slice(semicolon+1).toLowerCase();
|
||||
|
||||
if (skinToneState === 1){
|
||||
let skinIndex = decl.indexOf('$');
|
||||
|
||||
if (skinIndex !== -1){
|
||||
@@ -249,35 +328,24 @@ ready(){
|
||||
let declPost = decl.slice(skinIndex+1);
|
||||
|
||||
for(let skinTone of this.skinToneNonDefaultList){
|
||||
generated2[skinTone].pop();
|
||||
this.emojiData2[skinTone].pop();
|
||||
addDeclaration2(skinTone, declPre+skinTone+declPost);
|
||||
}
|
||||
}
|
||||
else{
|
||||
addDeclaration2(null, decl);
|
||||
this.emojiNames.push(desc);
|
||||
}
|
||||
}
|
||||
else if (skinToneState === 2){
|
||||
addDeclaration3(line.slice(0, line.indexOf(';')));
|
||||
addDeclaration3(decl);
|
||||
this.emojiNames.push(desc);
|
||||
}
|
||||
else if (skinToneState === 0){
|
||||
addDeclaration1(line.slice(0, line.indexOf(';')));
|
||||
addDeclaration1(decl);
|
||||
this.emojiNames.push(desc);
|
||||
}
|
||||
}
|
||||
|
||||
// final processing
|
||||
|
||||
let urlRegex = new RegExp(this.emojiURL.replace(/\./g, "\\."), "g");
|
||||
let process = str => TD.util.cleanWithEmoji(str).replace(/ class=\"emoji\" draggable=\"false\"/g, "").replace(urlRegex, "u#").replace(/___/g, "<div class='separator'></div>");
|
||||
|
||||
let start = "<p style='font-size:13px;color:#444;margin:4px;text-align:center'>Please, note that most emoji will not show up properly in the text box above, but they will display in the tweet.</p>";
|
||||
this.emojiHTML1 = start+process(generated1.join(""));
|
||||
|
||||
for(let skinTone of this.skinToneList){
|
||||
this.emojiHTML2[skinTone] = process(generated2[skinTone].join(""));
|
||||
}
|
||||
|
||||
this.emojiHTML3 = process(generated3.join(""));
|
||||
}).catch(err => {
|
||||
$TD.alert("error", "Problem loading emoji keyboard: "+err.message);
|
||||
});
|
||||
@@ -290,12 +358,17 @@ disabled(){
|
||||
$(this.currentKeyboard).remove();
|
||||
}
|
||||
|
||||
window.TDGF_tryPasteImage = this.prevTryPasteImage;
|
||||
if (this.currentSpanner){
|
||||
$(this.currentSpanner).remove();
|
||||
}
|
||||
|
||||
this.composePanelScroller.off("scroll", this.composerScrollEvent);
|
||||
|
||||
$(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonClickEvent);
|
||||
$(".emoji-keyboard-popup-btn").remove();
|
||||
|
||||
$(document).off("click", this.documentClickEvent);
|
||||
$(document).off("keydown", this.documentKeyEvent);
|
||||
$(document).off("uiComposeImageAdded", this.uploadFilesEvent);
|
||||
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
|
||||
}
|
||||
|
70
Resources/Plugins/emoji-keyboard/emoji-instructions.txt
Normal file
70
Resources/Plugins/emoji-keyboard/emoji-instructions.txt
Normal file
@@ -0,0 +1,70 @@
|
||||
Emoji list: http://unicode.org/emoji/charts/emoji-ordering.html
|
||||
Emoji order: http://unicode.org/emoji/charts/emoji-ordering.txt
|
||||
|
||||
|
||||
------------------------
|
||||
Remove unnecessary info:
|
||||
|
||||
Search: \s;.+?#.+?\s
|
||||
Replace: ;
|
||||
|
||||
Search: U+
|
||||
Replace:
|
||||
|
||||
|
||||
-----------------------------
|
||||
Replace skin tone variations:
|
||||
|
||||
Example:
|
||||
|
||||
1F9D2;child
|
||||
1F9D2 1F3FB; child: light skin tone
|
||||
1F9D2 1F3FC; child: medium-light skin tone
|
||||
1F9D2 1F3FD; child: medium skin tone
|
||||
1F9D2 1F3FE; child: medium-dark skin tone
|
||||
1F9D2 1F3FF; child: dark skin tone
|
||||
|
||||
1F9D2;child
|
||||
1F9D2 $;child
|
||||
|
||||
TODO: Update this section with exact regexes
|
||||
|
||||
|
||||
----------------
|
||||
Move some emoji:
|
||||
|
||||
1F443 $;nose
|
||||
> 1F91D;handshake
|
||||
1F463;footprints
|
||||
|
||||
1F939 $ 200D 2640 FE0F;woman juggling
|
||||
> 1F6CC;person in bed
|
||||
> 1F6CC $;person in bed
|
||||
> 1F6C0;person taking bath
|
||||
> 1F6C0 $;person taking bath
|
||||
1F46B;man and woman holding hands
|
||||
|
||||
|
||||
------------------
|
||||
Remove some emoji:
|
||||
|
||||
1F469 $ 200D 1F692;woman firefighter
|
||||
> remove all non-gendered duplicates below here
|
||||
|
||||
3030;wavy dash
|
||||
> remove copyright
|
||||
> remove registered trademark
|
||||
> remove trademark
|
||||
0023 FE0F 20E3;keycap
|
||||
|
||||
1F441;eye
|
||||
> remove eye in speech bubble
|
||||
1F445;tongue
|
||||
|
||||
|
||||
-------------------------
|
||||
Add preprocessor symbols:
|
||||
|
||||
@ = group separator
|
||||
@1 = enable skin tones below
|
||||
@2 = disable skin tones below
|
File diff suppressed because it is too large
Load Diff
17
Resources/Plugins/templates/.meta
Normal file
17
Resources/Plugins/templates/.meta
Normal file
@@ -0,0 +1,17 @@
|
||||
[name]
|
||||
Templates
|
||||
|
||||
[description]
|
||||
- Adds a templating system for tweets
|
||||
|
||||
[author]
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.0
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
||||
[requires]
|
||||
1.5.3
|
472
Resources/Plugins/templates/browser.js
Normal file
472
Resources/Plugins/templates/browser.js
Normal file
@@ -0,0 +1,472 @@
|
||||
enabled(){
|
||||
let me = this;
|
||||
|
||||
// configuration
|
||||
|
||||
this.config = {
|
||||
templates: {} // identifier: { name, contents }
|
||||
};
|
||||
|
||||
const configFile = "config.json";
|
||||
|
||||
$TDP.checkFileExists(this.$token, configFile).then(exists => {
|
||||
if (!exists){
|
||||
$TDP.writeFile(this.$token, configFile, JSON.stringify(this.config));
|
||||
}
|
||||
else{
|
||||
$TDP.readFile(this.$token, configFile, true).then(contents => {
|
||||
try{
|
||||
$.extend(true, this.config, JSON.parse(contents));
|
||||
}catch(err){
|
||||
// why :(
|
||||
}
|
||||
}).catch(err => {
|
||||
$TD.alert("error", "Problem loading configuration for the template plugin: "+err.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.saveConfig = () => {
|
||||
$TDP.writeFile(this.$token, configFile, JSON.stringify(this.config)).catch(err => {
|
||||
$TD.alert("error", "Problem saving configuration for the template plugin: "+err.message);
|
||||
});
|
||||
};
|
||||
|
||||
// button
|
||||
|
||||
var buttonHTML = '<button class="manage-templates-btn needsclick btn btn-on-blue full-width txt-left margin-b--12 padding-v--9"><i class="icon icon-bookmark"></i><span class="label padding-ls">Manage templates</span></button>';
|
||||
|
||||
this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
|
||||
TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="js-tweet-type-button">', buttonHTML+'<div class="js-tweet-type-button">');
|
||||
|
||||
var dockedComposePanel = $(".js-docked-compose");
|
||||
|
||||
if (dockedComposePanel.length){
|
||||
dockedComposePanel.find(".js-tweet-type-button").first().before(buttonHTML);
|
||||
}
|
||||
|
||||
// css
|
||||
|
||||
this.css = window.TDPF_createCustomStyle(this);
|
||||
this.css.insert(".manage-templates-btn.active { color: #fff; box-shadow: 0 0 2px 3px #50a5e6; outline: 0; }");
|
||||
|
||||
this.css.insert(".templates-modal-wrap { width: 100%; height: 100%; padding: 49px; position: absolute; z-index: 999; box-sizing: border-box; background-color: rgba(0, 0, 0, 0.5); }");
|
||||
this.css.insert(".templates-modal { width: 100%; height: 100%; background-color: #fff; display: flex; }");
|
||||
this.css.insert(".templates-modal > div { display: flex; flex-direction: column; }");
|
||||
this.css.insert(".templates-modal-bottom { flex: 0 0 auto; padding: 16px; text-align: right; }");
|
||||
this.css.insert(".templates-modal-bottom button { margin-left: 4px; }");
|
||||
|
||||
this.css.insert(".template-list { height: 100%; flex: 1 1 auto; }");
|
||||
this.css.insert(".template-list ul { list-style-type: none; font-size: 24px; color: #222; flex: 1 1 auto; padding: 12px; overflow-y: auto; }");
|
||||
this.css.insert(".template-list li { display: block; width: 100%; padding: 4px 8px; box-sizing: border-box; }");
|
||||
this.css.insert(".template-list li[data-template] { cursor: pointer; }");
|
||||
this.css.insert(".template-list li[data-template]:hover { background-color: #d8d8d8; }");
|
||||
this.css.insert(".template-list li span { white-space: nowrap; }");
|
||||
this.css.insert(".template-list li .icon { opacity: 0.6; margin-left: 4px; padding: 3px; }");
|
||||
this.css.insert(".template-list li .icon:hover { opacity: 1; }");
|
||||
this.css.insert(".template-list li .template-actions { float: right; }");
|
||||
|
||||
this.css.insert(".template-editor { height: 100%; flex: 0 0 auto; width: 25vw; min-width: 150px; max-width: 400px; background-color: #485865; }");
|
||||
this.css.insert(".template-editor-form { flex: 1 1 auto; padding: 12px 16px; font-size: 14px; overflow-y: auto; }");
|
||||
this.css.insert(".template-editor-form .compose-text-title { margin: 24px 0 9px; }");
|
||||
this.css.insert(".template-editor-form .compose-text-title:first-child { margin-top: 0; }");
|
||||
this.css.insert(".template-editor-form input, .template-editor-form textarea { color: #111; background-color: #fff; border: none; border-radius: 0; }");
|
||||
this.css.insert(".template-editor-form input:focus, .template-editor-form textarea:focus { box-shadow: inset 0 1px 3px rgba(17, 17, 17, 0.1), 0 0 8px rgba(80, 165, 230, 0.6); }");
|
||||
this.css.insert(".template-editor-form textarea { height: 146px; font-size: 14px; padding: 10px; resize: none; }");
|
||||
this.css.insert(".template-editor-form .template-editor-tips-button { cursor: pointer; }");
|
||||
this.css.insert(".template-editor-form .template-editor-tips-button .icon { font-size: 12px; vertical-align: -5%; margin-left: 4px; }");
|
||||
this.css.insert(".template-editor-form .template-editor-tips { display: none; }");
|
||||
this.css.insert(".template-editor-form .template-editor-tips p { margin: 10px 0; }");
|
||||
this.css.insert(".template-editor-form .template-editor-tips p:first-child { margin-top: 0; }");
|
||||
this.css.insert(".template-editor-form .template-editor-tips li:nth-child(2n+1) { margin-top: 5px; padding-left: 6px; font-family: monospace; }");
|
||||
this.css.insert(".template-editor-form .template-editor-tips li:nth-child(2n) { margin-top: 1px; padding-left: 14px; opacity: 0.66; }");
|
||||
|
||||
this.css.insert(".invisible { display: none !important; }");
|
||||
|
||||
// template implementation
|
||||
|
||||
var readTemplateTokens = (contents, tokenData) => {
|
||||
let startIndex = -1;
|
||||
let endIndex = -1;
|
||||
|
||||
let data = [];
|
||||
let tokenNames = Object.keys(tokenData);
|
||||
|
||||
for(let currentIndex = 0; currentIndex < contents.length; currentIndex++){
|
||||
if (contents[currentIndex] === '\\'){
|
||||
contents = contents.substring(0, currentIndex)+contents.substring(currentIndex+1);
|
||||
continue;
|
||||
}
|
||||
else if (contents[currentIndex] !== '{'){
|
||||
continue;
|
||||
}
|
||||
|
||||
startIndex = currentIndex+1;
|
||||
|
||||
for(; startIndex < contents.length; startIndex++){
|
||||
if (!tokenNames.some(name => contents[startIndex] === name[startIndex-currentIndex-1])){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
endIndex = startIndex;
|
||||
|
||||
let token = contents.substring(currentIndex+1, startIndex);
|
||||
let replacement = tokenData[token] || "";
|
||||
|
||||
let entry = [ token, currentIndex ];
|
||||
|
||||
if (contents[endIndex] === '#'){
|
||||
++endIndex;
|
||||
|
||||
let bracketCount = 1;
|
||||
|
||||
for(; endIndex < contents.length; endIndex++){
|
||||
if (contents[endIndex] === '{'){
|
||||
++bracketCount;
|
||||
}
|
||||
else if (contents[endIndex] === '}'){
|
||||
if (--bracketCount === 0){
|
||||
entry.push(contents.substring(startIndex+1, endIndex));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (contents[endIndex] === '#'){
|
||||
entry.push(contents.substring(startIndex+1, endIndex));
|
||||
startIndex = endIndex;
|
||||
}
|
||||
else if (contents[endIndex] === '\\'){
|
||||
contents = contents.substring(0, endIndex)+contents.substring(endIndex+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (contents[endIndex] !== '}'){
|
||||
continue;
|
||||
}
|
||||
|
||||
data.push(entry);
|
||||
|
||||
contents = contents.substring(0, currentIndex)+replacement+contents.substring(endIndex+1);
|
||||
currentIndex += replacement.length;
|
||||
}
|
||||
|
||||
return [ contents, data ];
|
||||
};
|
||||
|
||||
var doAjaxRequest = (index, url, evaluator) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!url){
|
||||
resolve([ index, "{ajax}" ]);
|
||||
return;
|
||||
}
|
||||
|
||||
$.get(url, function(data){
|
||||
if (evaluator){
|
||||
resolve([ index, eval(evaluator.replace(/\$/g, "'"+data.replace(/(["'\\\n\r\u2028\u2029])/g, "\\$1")+"'"))]);
|
||||
}
|
||||
else{
|
||||
resolve([ index, data ]);
|
||||
}
|
||||
}, "text").fail(function(){
|
||||
resolve([ index, "" ]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var useTemplate = (contents, append) => {
|
||||
let ele = $(".js-compose-text");
|
||||
if (ele.length === 0)return;
|
||||
|
||||
let value = append ? ele.val()+contents : contents;
|
||||
let prevLength = value.length;
|
||||
|
||||
let tokens = null;
|
||||
|
||||
[value, tokens] = readTemplateTokens(value, {
|
||||
"cursor": "",
|
||||
"ajax": "(...)"
|
||||
});
|
||||
|
||||
ele.val(value);
|
||||
ele.trigger("change");
|
||||
ele.focus();
|
||||
|
||||
ele[0].selectionStart = ele[0].selectionEnd = value.length;
|
||||
|
||||
let promises = [];
|
||||
let indexOffset = 0;
|
||||
|
||||
for(let token of tokens){
|
||||
switch(token[0]){
|
||||
case "cursor":
|
||||
let [, index1, length ] = token;
|
||||
ele[0].selectionStart = index1;
|
||||
ele[0].selectionEnd = index1+(length | 0 || 0);
|
||||
break;
|
||||
|
||||
case "ajax":
|
||||
let [, index2, evaluator, url ] = token;
|
||||
|
||||
if (!url){
|
||||
url = evaluator;
|
||||
evaluator = null;
|
||||
}
|
||||
|
||||
promises.push(doAjaxRequest(index2, url, evaluator));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (promises.length > 0){
|
||||
let selStart = ele[0].selectionStart;
|
||||
let selEnd = ele[0].selectionEnd;
|
||||
|
||||
ele.prop("disabled", true);
|
||||
|
||||
Promise.all(promises).then(values => {
|
||||
const placeholderLen = 5; // "(...)".length
|
||||
let indexOffset = 0;
|
||||
|
||||
for(let value of values){
|
||||
let diff = value[1].length-placeholderLen;
|
||||
let realIndex = indexOffset+value[0];
|
||||
|
||||
let val = ele.val();
|
||||
ele.val(val.substring(0, realIndex)+value[1]+val.substring(realIndex+placeholderLen));
|
||||
|
||||
indexOffset += diff;
|
||||
}
|
||||
|
||||
ele.prop("disabled", false);
|
||||
ele.trigger("change");
|
||||
ele.focus();
|
||||
|
||||
ele[0].selectionStart = selStart+indexOffset;
|
||||
ele[0].selectionEnd = selEnd+indexOffset;
|
||||
});
|
||||
}
|
||||
|
||||
if (!append){
|
||||
hideTemplateModal();
|
||||
}
|
||||
};
|
||||
|
||||
// modal dialog
|
||||
|
||||
this.editingTemplate = null;
|
||||
|
||||
var showTemplateModal = () => {
|
||||
$(".manage-templates-btn").addClass("active");
|
||||
|
||||
let html = `
|
||||
<div class="templates-modal-wrap">
|
||||
<div class="templates-modal">
|
||||
<div class="template-list">
|
||||
<ul></ul>
|
||||
|
||||
<div class="templates-modal-bottom">
|
||||
<button data-action="new-template" class="btn btn-positive"><i class="icon icon-plus icon-small padding-rs"></i><span class="label">New Template</span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="template-editor invisible">
|
||||
<div class="template-editor-form">
|
||||
<div class="compose-text-title">Template Name</div>
|
||||
<input name="template-name" type="text">
|
||||
|
||||
<div class="compose-text-title">Contents</div>
|
||||
<textarea name="template-contents" class="compose-text scroll-v scroll-styled-v scroll-styled-h scroll-alt"></textarea>
|
||||
|
||||
<div class="compose-text-title template-editor-tips-button">Advanced <i class="icon icon-arrow-d"></i></div>
|
||||
<div class="template-editor-tips">
|
||||
<p>You can use the following tokens. All tokens except for <span style="font-family: monospace">{ajax}</span> can only be used once.</p>
|
||||
<ul>
|
||||
<li>{cursor}</li>
|
||||
<li>Location where the cursor is placed</li>
|
||||
<li>{cursor#<selectionlength>}</li>
|
||||
<li>Places cursor and selects a set amount of characters</li>
|
||||
<li>{ajax#<url>}</li>
|
||||
<li>Replaced with the result of a cross-origin ajax request</li>
|
||||
<li>{ajax#<eval>#<url>}</li>
|
||||
<li>Allows parsing the ajax request using <span style="font-family: monospace">$</span> as the placeholder for the result<br>Example: <span style="font-family: monospace">$.substring(0,5)</span></li>
|
||||
</ul>
|
||||
<p>To use special characters in the tweet text, escape them with a backslash:
|
||||
<br><span style="font-family: monospace"> \\{ \\} \\# \\\\</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="templates-modal-bottom">
|
||||
<button data-action="editor-cancel" class="btn"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Cancel</span></button>
|
||||
<button data-action="editor-confirm" class="btn btn-positive"><i class="icon icon-check icon-small padding-rs"></i><span class="label">Confirm</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
/* TODO possibly implement this later
|
||||
|
||||
<li>{paste}</li>
|
||||
<li>Paste text or an image from clipboard</li>
|
||||
<li>{paste#text}</li>
|
||||
<li>Paste only if clipboard has text</li>
|
||||
<li>{paste#image}</li>
|
||||
<li>Paste only if clipboard has an image</li>
|
||||
|
||||
*/
|
||||
|
||||
$(".js-app-content").prepend(html);
|
||||
|
||||
let ele = $(".templates-modal-wrap").first();
|
||||
|
||||
ele.on("click", "li[data-template]", function(e){
|
||||
let template = me.config.templates[$(this).attr("data-template")];
|
||||
useTemplate(template.contents, e.shiftKey);
|
||||
});
|
||||
|
||||
ele.on("click", "li[data-template] i[data-action]", function(e){
|
||||
let identifier = $(this).closest("li").attr("data-template");
|
||||
|
||||
switch($(this).attr("data-action")){
|
||||
case "edit-template":
|
||||
let editor = $(".template-editor");
|
||||
|
||||
if (editor.hasClass("invisible")){
|
||||
toggleEditor();
|
||||
}
|
||||
|
||||
let template = me.config.templates[identifier];
|
||||
$("[name='template-name']", editor).val(template.name);
|
||||
$("[name='template-contents']", editor).val(template.contents);
|
||||
|
||||
me.editingTemplate = identifier;
|
||||
break;
|
||||
|
||||
case "delete-template":
|
||||
delete me.config.templates[identifier];
|
||||
onTemplatesUpdated(true);
|
||||
|
||||
if (me.editingTemplate === identifier){
|
||||
me.editingTemplate = null;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
ele.on("click", ".template-editor-tips-button", function(e){
|
||||
$(this).children(".icon").toggleClass("icon-arrow-d icon-arrow-u");
|
||||
ele.find(".template-editor-tips").toggle();
|
||||
});
|
||||
|
||||
ele.on("click", "button", function(e){
|
||||
switch($(this).attr("data-action")){
|
||||
case "new-template":
|
||||
case "editor-cancel":
|
||||
toggleEditor();
|
||||
break;
|
||||
|
||||
case "editor-confirm":
|
||||
let editor = $(".template-editor");
|
||||
|
||||
if (me.editingTemplate !== null){
|
||||
delete me.config.templates[me.editingTemplate];
|
||||
}
|
||||
|
||||
let name = $("[name='template-name']", editor).val();
|
||||
let identifier = name.toLowerCase().replace(/[^a-z0-9]/g, "")+"-"+(Math.random().toString(36).substring(2, 7));
|
||||
|
||||
if (name.trim().length === 0){
|
||||
alert("Please, include a name for your template.");
|
||||
$("[name='template-name']", editor).focus();
|
||||
return;
|
||||
}
|
||||
|
||||
me.config.templates[identifier] = {
|
||||
name: name,
|
||||
contents: $("[name='template-contents']", editor).val()
|
||||
};
|
||||
|
||||
toggleEditor();
|
||||
onTemplatesUpdated(true);
|
||||
break;
|
||||
}
|
||||
|
||||
$(this).blur();
|
||||
});
|
||||
|
||||
onTemplatesUpdated(false);
|
||||
};
|
||||
|
||||
var hideTemplateModal = function(){
|
||||
$(".templates-modal-wrap").remove();
|
||||
$(".manage-templates-btn").removeClass("active");
|
||||
};
|
||||
|
||||
var toggleEditor = function(){
|
||||
let editor = $(".template-editor");
|
||||
$("[name]", editor).val("");
|
||||
|
||||
if ($("button[data-action='new-template']", ".template-list").add(editor).toggleClass("invisible").hasClass("invisible")){
|
||||
me.editingTemplate = null;
|
||||
}
|
||||
};
|
||||
|
||||
var onTemplatesUpdated = (save) => {
|
||||
let eles = [];
|
||||
|
||||
for(let identifier of Object.keys(this.config.templates)){
|
||||
eles.push(`<li data-template="${identifier}">
|
||||
<span class="template-name">${this.config.templates[identifier].name}</span>
|
||||
<span class="template-actions"><i class="icon icon-edit" data-action="edit-template"></i><i class="icon icon-close" data-action="delete-template"></i></span>
|
||||
</li>`);
|
||||
}
|
||||
|
||||
if (eles.length === 0){
|
||||
eles.push("<li>No templates available</li>");
|
||||
}
|
||||
|
||||
$(".template-list").children("ul").html(eles.join(""));
|
||||
|
||||
if (save){
|
||||
this.saveConfig();
|
||||
}
|
||||
};
|
||||
|
||||
// event handlers
|
||||
|
||||
this.manageTemplatesButtonClickEvent = function(e){
|
||||
if ($(".templates-modal-wrap").length){
|
||||
hideTemplateModal();
|
||||
}
|
||||
else{
|
||||
showTemplateModal();
|
||||
}
|
||||
|
||||
$(this).blur();
|
||||
};
|
||||
|
||||
this.drawerToggleEvent = function(e, data){
|
||||
if (data.activeDrawer === null){
|
||||
hideTemplateModal();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ready(){
|
||||
$(".manage-templates-btn").on("click", this.manageTemplatesButtonClickEvent);
|
||||
$(document).on("uiDrawerActive", this.drawerToggleEvent);
|
||||
}
|
||||
|
||||
disabled(){
|
||||
this.css.remove();
|
||||
|
||||
$(".manage-templates-btn").remove();
|
||||
$(".templates-modal-wrap").remove();
|
||||
|
||||
$(document).off("uiDrawerActive", this.drawerToggleEvent);
|
||||
|
||||
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
|
||||
}
|
@@ -530,7 +530,7 @@
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Hold Shift to reset cleared column.
|
||||
// Block: Hold Shift to restore cleared column.
|
||||
//
|
||||
(function(){
|
||||
var holdingShift = false;
|
||||
@@ -538,7 +538,7 @@
|
||||
var updateShiftState = (pressed) => {
|
||||
if (pressed != holdingShift){
|
||||
holdingShift = pressed;
|
||||
$("button[data-action='clear']").children("span").text(holdingShift ? "Reset" : "Clear");
|
||||
$("button[data-action='clear']").children("span").text(holdingShift ? "Restore" : "Clear");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -595,6 +595,26 @@
|
||||
});
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Make middle click on tweet reply icon open the compose drawer.
|
||||
//
|
||||
app.delegate(".js-reply-action", "mousedown", function(e){
|
||||
if (e.which === 2){
|
||||
if ($("[data-drawer='compose']").hasClass("is-hidden")){
|
||||
$(document).trigger("uiDrawerShowDrawer", {
|
||||
drawer: "compose",
|
||||
withAnimation: true
|
||||
});
|
||||
|
||||
window.setTimeout(() => $(this).trigger("click"), 1);
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Work around clipboard HTML formatting.
|
||||
//
|
||||
@@ -617,12 +637,18 @@
|
||||
styleOfficial.sheet.insertRule(".app-navigator .tooltip { display: none !important; }", 0); // hide broken tooltips in the menu
|
||||
styleOfficial.sheet.insertRule(".account-inline .username { vertical-align: 10%; }", 0); // move usernames a bit higher
|
||||
|
||||
styleOfficial.sheet.insertRule(".column .column-header { height: 49px !important; }", 0); // fix one pixel space below column header
|
||||
styleOfficial.sheet.insertRule(".column:not(.is-options-open) .column-header { border-bottom: none; }", 0); // fix one pixel space below column header
|
||||
|
||||
styleOfficial.sheet.insertRule(".activity-header { align-items: center !important; margin-bottom: 4px; }", 0); // tweak alignment of avatar and text in notifications
|
||||
styleOfficial.sheet.insertRule(".activity-header .tweet-timestamp { line-height: unset }", 0); // fix timestamp position in notifications
|
||||
|
||||
styleOfficial.sheet.insertRule(".app-columns-container::-webkit-scrollbar-track { border-left: 0; }", 0); // remove weird border in the column container scrollbar
|
||||
styleOfficial.sheet.insertRule(".app-columns-container { bottom: 0 !important; }", 0); // move column container scrollbar to bottom to fit updated style
|
||||
|
||||
styleOfficial.sheet.insertRule(".js-column-header .column-header-link { padding: 0; }", 0); // fix column header tooltip hover box
|
||||
styleOfficial.sheet.insertRule(".js-column-header .column-header-link .icon { padding: 9px 4px; width: calc(1em + 8px); height: 100%; box-sizing: border-box; }", 0); // fix column header tooltip hover box
|
||||
|
||||
styleOfficial.sheet.insertRule(".is-video a:not([href*='youtu']), .is-gif .js-media-gif-container { cursor: alias; }", 0); // change cursor on unsupported videos
|
||||
styleOfficial.sheet.insertRule(".is-video a:not([href*='youtu']) .icon-bg-dot, .is-gif .icon-bg-dot { color: #bd3d37; }", 0); // change play icon color on unsupported videos
|
||||
|
||||
|
@@ -1,12 +1,14 @@
|
||||
(function(){
|
||||
var isReloading = false;
|
||||
|
||||
//
|
||||
// Class: Abstract plugin base class.
|
||||
//
|
||||
window.PluginBase = class{
|
||||
constructor(pluginSettings){
|
||||
this.$pluginSettings = pluginSettings || {};
|
||||
this.$requiresReload = !!(pluginSettings && pluginSettings.requiresPageReload);
|
||||
}
|
||||
|
||||
|
||||
enabled(){}
|
||||
ready(){}
|
||||
disabled(){}
|
||||
@@ -49,11 +51,11 @@
|
||||
}
|
||||
|
||||
setState(plugin, enable){
|
||||
let reloading = plugin.obj.$pluginSettings.requiresPageReload;
|
||||
let reloading = plugin.obj.$requiresReload;
|
||||
|
||||
if (enable && this.isDisabled(plugin)){
|
||||
if (reloading){
|
||||
location.reload();
|
||||
window.TDPF_requestReload();
|
||||
}
|
||||
else{
|
||||
this.disabled.splice(this.disabled.indexOf(plugin.id), 1);
|
||||
@@ -63,11 +65,17 @@
|
||||
}
|
||||
else if (!enable && !this.isDisabled(plugin)){
|
||||
if (reloading){
|
||||
location.reload();
|
||||
window.TDPF_requestReload();
|
||||
}
|
||||
else{
|
||||
this.disabled.push(plugin.id);
|
||||
plugin.obj.disabled();
|
||||
|
||||
for(let key of Object.keys(plugin.obj)){
|
||||
if (key[0] !== '$'){
|
||||
delete plugin.obj[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,4 +92,14 @@
|
||||
window.TDPF_setPluginState = function(identifier, enable){
|
||||
window.TD_PLUGINS.setState(window.TD_PLUGINS.findObject(identifier), enable);
|
||||
};
|
||||
})();
|
||||
|
||||
//
|
||||
// Block: Setup global function to reload the page.
|
||||
//
|
||||
window.TDPF_requestReload = function(){
|
||||
if (!isReloading){
|
||||
window.setTimeout(() => location.reload(), 1);
|
||||
isReloading = true;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
@@ -51,4 +51,4 @@
|
||||
obj.element = element;
|
||||
return obj;
|
||||
};
|
||||
})($TDP);
|
||||
})($TDP);
|
||||
|
@@ -1,21 +1,16 @@
|
||||
(function(){
|
||||
//
|
||||
// Class: Abstract plugin base class.
|
||||
//
|
||||
window.PluginBase = class{
|
||||
constructor(pluginSettings){
|
||||
this.$pluginSettings = pluginSettings || {};
|
||||
}
|
||||
//
|
||||
// Class: Abstract plugin base class.
|
||||
//
|
||||
window.PluginBase = class{
|
||||
constructor(){}
|
||||
run(){}
|
||||
};
|
||||
|
||||
run(){}
|
||||
};
|
||||
|
||||
//
|
||||
// Variable: Main object for containing and managing plugins.
|
||||
//
|
||||
window.TD_PLUGINS = {
|
||||
install: function(plugin){
|
||||
plugin.obj.run();
|
||||
}
|
||||
};
|
||||
})();
|
||||
//
|
||||
// Variable: Main object for containing and managing plugins.
|
||||
//
|
||||
window.TD_PLUGINS = {
|
||||
install: function(plugin){
|
||||
plugin.obj.run();
|
||||
}
|
||||
};
|
||||
|
@@ -123,10 +123,6 @@
|
||||
<DependentUpon>FormNotificationTweet.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Notification\SoundNotification.cs" />
|
||||
<Compile Include="Core\Notification\Sound\SoundPlayerImplFallback.cs" />
|
||||
<Compile Include="Core\Notification\Sound\SoundPlayerImplWMP.cs" />
|
||||
<Compile Include="Core\Notification\Sound\ISoundNotificationPlayer.cs" />
|
||||
<Compile Include="Core\Notification\Sound\PlaybackErrorEventArgs.cs" />
|
||||
<Compile Include="Core\Notification\TweetNotification.cs" />
|
||||
<Compile Include="Core\Other\FormAbout.cs">
|
||||
<SubType>Form</SubType>
|
||||
@@ -208,7 +204,6 @@
|
||||
</Compile>
|
||||
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
|
||||
<Compile Include="Core\Utils\InjectedHTML.cs" />
|
||||
<Compile Include="Core\Utils\NativeCoreAudio.cs" />
|
||||
<Compile Include="Core\Utils\TwoKeyDictionary.cs" />
|
||||
<Compile Include="Core\Utils\WindowState.cs" />
|
||||
<Compile Include="Core\Utils\WindowsUtils.cs" />
|
||||
@@ -340,17 +335,10 @@
|
||||
<Content Include="Resources\Scripts\update.js" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<COMReference Include="WMPLib">
|
||||
<Guid>{6BF52A50-394A-11D3-B153-00C04F79FAA6}</Guid>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<Isolated>False</Isolated>
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="lib\TweetLib.Audio\TweetLib.Audio.csproj">
|
||||
<Project>{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}</Project>
|
||||
<Name>TweetLib.Audio</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="subprocess\TweetDuck.Browser.csproj">
|
||||
<Project>{b10b0017-819e-4f71-870f-8256b36a26aa}</Project>
|
||||
<Name>TweetDuck.Browser</Name>
|
||||
@@ -362,7 +350,7 @@
|
||||
xcopy "$(ProjectDir)LICENSE.md" "$(TargetDir)" /Y
|
||||
del "$(TargetDir)LICENSE.txt"
|
||||
ren "$(TargetDir)LICENSE.md" "LICENSE.txt"
|
||||
xcopy "$(ProjectDir)Libraries\CEFSHARP-LICENSE.txt" "$(TargetDir)" /Y
|
||||
xcopy "$(ProjectDir)bld\Resources\CEFSHARP-LICENSE.txt" "$(TargetDir)" /Y
|
||||
xcopy "$(ProjectDir)packages\Microsoft.VC120.CRT.JetBrains.12.0.21005.2\DotFiles\msvcp120.dll" "$(TargetDir)" /Y
|
||||
xcopy "$(ProjectDir)packages\Microsoft.VC120.CRT.JetBrains.12.0.21005.2\DotFiles\msvcr120.dll" "$(TargetDir)" /Y
|
||||
rmdir "$(TargetDir)scripts" /S /Q
|
||||
@@ -373,8 +361,8 @@ mkdir "$(TargetDir)plugins"
|
||||
mkdir "$(TargetDir)plugins\official"
|
||||
mkdir "$(TargetDir)plugins\user"
|
||||
xcopy "$(ProjectDir)Resources\Plugins\*" "$(TargetDir)plugins\official\" /E /Y
|
||||
rmdir "$(ProjectDir)\bin\Debug"
|
||||
rmdir "$(ProjectDir)\bin\Release"
|
||||
rmdir "$(ProjectDir)bin\Debug"
|
||||
rmdir "$(ProjectDir)bin\Release"
|
||||
|
||||
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26430.6
|
||||
VisualStudioVersion = 15.0.26430.12
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck", "TweetDuck.csproj", "{2389A7CD-E0D3-4706-8294-092929A33A2D}"
|
||||
EndProject
|
||||
@@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck.Browser", "subpro
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "tests\UnitTests.csproj", "{A958FA7A-4A2C-42A7-BFA0-159343483F4E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Audio", "lib\TweetLib.Audio\TweetLib.Audio.csproj", "{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x86 = Debug|x86
|
||||
@@ -26,6 +28,10 @@ Global
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Debug|x86.Build.0 = Debug|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Release|x86.ActiveCfg = Release|x86
|
||||
{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}.Debug|x86.Build.0 = Debug|x86
|
||||
{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}.Release|x86.ActiveCfg = Release|x86
|
||||
{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@@ -39,7 +39,7 @@ Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{
|
||||
|
||||
[Files]
|
||||
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.pdb,CefSharp.BrowserSubprocess.exe,devtools_resources.pak,d3dcompiler_43.dll,widevinecdmadapter.dll"
|
||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.pdb,CefSharp.BrowserSubprocess.exe,devtools_resources.pak,widevinecdmadapter.dll"
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUninstallable
|
||||
@@ -48,13 +48,10 @@ Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks:
|
||||
[Run]
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall shellexec skipifsilent
|
||||
|
||||
[InstallDelete]
|
||||
Type: files; Name: "{app}\td-log.txt"
|
||||
Type: filesandordirs; Name: "{app}\plugins\official\design-revert"
|
||||
Type: filesandordirs; Name: "{localappdata}\TD_Plugins\official\design-revert"
|
||||
|
||||
[UninstallDelete]
|
||||
Type: files; Name: "{app}\debug.log"
|
||||
Type: files; Name: "{app}\*.*"
|
||||
Type: filesandordirs; Name: "{app}\locales"
|
||||
Type: filesandordirs; Name: "{app}\scripts"
|
||||
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\Cache"
|
||||
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache"
|
||||
|
||||
|
@@ -36,15 +36,11 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Files]
|
||||
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.pdb,CefSharp.BrowserSubprocess.exe,devtools_resources.pak,d3dcompiler_43.dll,widevinecdmadapter.dll"
|
||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.pdb,CefSharp.BrowserSubprocess.exe,devtools_resources.pak,widevinecdmadapter.dll"
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall shellexec skipifsilent
|
||||
|
||||
[InstallDelete]
|
||||
Type: filesandordirs; Name: "{app}\plugins\official\design-revert"
|
||||
Type: filesandordirs; Name: "{app}\portable\storage\TD_Plugins\official\design-revert"
|
||||
|
||||
[Code]
|
||||
var UpdatePath: String;
|
||||
|
||||
|
@@ -29,9 +29,9 @@ Uninstallable=TDIsUninstallable
|
||||
UninstallDisplayName={#MyAppName}
|
||||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||
PrivilegesRequired=lowest
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
InternalCompressLevel=max
|
||||
Compression=lzma/normal
|
||||
SolidCompression=True
|
||||
InternalCompressLevel=normal
|
||||
MinVersion=0,6.1
|
||||
|
||||
#include <idp.iss>
|
||||
@@ -41,7 +41,10 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Files]
|
||||
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.pdb,*.dll,*.pak,*.bin,*.dat,CefSharp.BrowserSubprocess.exe"
|
||||
Source: "..\bin\x86\Release\TweetDuck.*"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\bin\x86\Release\TweetLib.*"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\bin\x86\Release\scripts\*.*"; DestDir: "{app}\scripts"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "..\bin\x86\Release\plugins\*.*"; DestDir: "{app}\plugins"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUninstallable
|
||||
@@ -49,17 +52,6 @@ Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUnin
|
||||
[Run]
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Parameters: "{code:TDGetRunArgs}"; Flags: nowait postinstall shellexec
|
||||
|
||||
[InstallDelete]
|
||||
Type: files; Name: "{app}\*.xml"
|
||||
Type: files; Name: "{app}\*.js"
|
||||
Type: files; Name: "{app}\d3dcompiler_43.dll"
|
||||
Type: files; Name: "{app}\widevinecdmadapter.dll"
|
||||
Type: files; Name: "{app}\devtools_resources.pak"
|
||||
Type: files; Name: "{app}\CefSharp.BrowserSubprocess.exe"
|
||||
Type: files; Name: "{app}\td-log.txt"
|
||||
Type: files; Name: "{app}\debug.log"
|
||||
Type: files; Name: "{localappdata}\{#MyAppName}\ChromeDWriteFontCache"
|
||||
|
||||
[UninstallDelete]
|
||||
Type: files; Name: "{app}\*.*"
|
||||
Type: filesandordirs; Name: "{app}\locales"
|
||||
|
37
lib/TweetLib.Audio/AudioPlayer.cs
Normal file
37
lib/TweetLib.Audio/AudioPlayer.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using TweetLib.Audio.Impl;
|
||||
using TweetLib.Audio.Utils;
|
||||
|
||||
namespace TweetLib.Audio{
|
||||
public abstract class AudioPlayer : IDisposable{
|
||||
private static bool? IsWMPAvailable;
|
||||
|
||||
public static AudioPlayer New(){
|
||||
if (IsWMPAvailable.HasValue){
|
||||
if (IsWMPAvailable.Value){
|
||||
return new SoundPlayerImplWMP();
|
||||
}
|
||||
else{
|
||||
return new SoundPlayerImplFallback();
|
||||
}
|
||||
}
|
||||
|
||||
try{
|
||||
SoundPlayerImplWMP implWMP = new SoundPlayerImplWMP();
|
||||
IsWMPAvailable = true;
|
||||
return implWMP;
|
||||
}catch(COMException){
|
||||
IsWMPAvailable = false;
|
||||
return new SoundPlayerImplFallback();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract string SupportedFormats { get; }
|
||||
public abstract event EventHandler<PlaybackErrorEventArgs> PlaybackError;
|
||||
|
||||
public abstract void Play(string file);
|
||||
public abstract void Stop();
|
||||
public abstract void Dispose();
|
||||
}
|
||||
}
|
@@ -1,12 +1,13 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Media;
|
||||
using TweetLib.Audio.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Notification.Sound{
|
||||
sealed class SoundPlayerImplFallback : ISoundNotificationPlayer{
|
||||
string ISoundNotificationPlayer.SupportedFormats => "*.wav";
|
||||
namespace TweetLib.Audio.Impl{
|
||||
sealed class SoundPlayerImplFallback : AudioPlayer{
|
||||
public override string SupportedFormats => "*.wav";
|
||||
|
||||
public event EventHandler<PlaybackErrorEventArgs> PlaybackError;
|
||||
public override event EventHandler<PlaybackErrorEventArgs> PlaybackError;
|
||||
|
||||
private readonly SoundPlayer player;
|
||||
private bool ignorePlaybackError;
|
||||
@@ -17,7 +18,7 @@ namespace TweetDuck.Core.Notification.Sound{
|
||||
};
|
||||
}
|
||||
|
||||
void ISoundNotificationPlayer.Play(string file){
|
||||
public override void Play(string file){
|
||||
if (player.SoundLocation != file){
|
||||
player.SoundLocation = file;
|
||||
ignorePlaybackError = false;
|
||||
@@ -34,11 +35,11 @@ namespace TweetDuck.Core.Notification.Sound{
|
||||
}
|
||||
}
|
||||
|
||||
void ISoundNotificationPlayer.Stop(){
|
||||
public override void Stop(){
|
||||
player.Stop();
|
||||
}
|
||||
|
||||
void IDisposable.Dispose(){
|
||||
public override void Dispose(){
|
||||
player.Dispose();
|
||||
}
|
||||
|
@@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetLib.Audio.Utils;
|
||||
using WMPLib;
|
||||
|
||||
namespace TweetDuck.Core.Notification.Sound{
|
||||
sealed class SoundPlayerImplWMP : ISoundNotificationPlayer{
|
||||
string ISoundNotificationPlayer.SupportedFormats => "*.wav;*.mp3;*.mp2;*.m4a;*.mid;*.midi;*.rmi;*.wma;*.aif;*.aifc;*.aiff;*.snd;*.au";
|
||||
namespace TweetLib.Audio.Impl{
|
||||
sealed class SoundPlayerImplWMP : AudioPlayer{
|
||||
public override string SupportedFormats => "*.wav;*.mp3;*.mp2;*.m4a;*.mid;*.midi;*.rmi;*.wma;*.aif;*.aifc;*.aiff;*.snd;*.au";
|
||||
|
||||
public event EventHandler<PlaybackErrorEventArgs> PlaybackError;
|
||||
public override event EventHandler<PlaybackErrorEventArgs> PlaybackError;
|
||||
|
||||
private readonly WindowsMediaPlayer player;
|
||||
private bool wasTryingToPlay;
|
||||
@@ -29,7 +29,7 @@ namespace TweetDuck.Core.Notification.Sound{
|
||||
player.MediaError += player_MediaError;
|
||||
}
|
||||
|
||||
void ISoundNotificationPlayer.Play(string file){
|
||||
public override void Play(string file){
|
||||
wasTryingToPlay = true;
|
||||
|
||||
try{
|
||||
@@ -48,7 +48,7 @@ namespace TweetDuck.Core.Notification.Sound{
|
||||
}
|
||||
}
|
||||
|
||||
void ISoundNotificationPlayer.Stop(){
|
||||
public override void Stop(){
|
||||
try{
|
||||
player.controls.stop();
|
||||
}catch{
|
||||
@@ -56,7 +56,7 @@ namespace TweetDuck.Core.Notification.Sound{
|
||||
}
|
||||
}
|
||||
|
||||
void IDisposable.Dispose(){
|
||||
public override void Dispose(){
|
||||
player.close();
|
||||
Marshal.ReleaseComObject(player);
|
||||
}
|
35
lib/TweetLib.Audio/Properties/AssemblyInfo.cs
Normal file
35
lib/TweetLib.Audio/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("TweetDuck Audio Library")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("TweetDuck Audio Library")]
|
||||
[assembly: AssemblyCopyright("")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("e9e1fd1b-f480-45b7-9970-be2ecfd309ac")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
50
lib/TweetLib.Audio/TweetLib.Audio.csproj
Normal file
50
lib/TweetLib.Audio/TweetLib.Audio.csproj
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{E9E1FD1B-F480-45B7-9970-BE2ECFD309AC}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>TweetLib.Audio</RootNamespace>
|
||||
<AssemblyName>TweetLib.Audio</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ResolveComReferenceSilent>True</ResolveComReferenceSilent>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AudioPlayer.cs" />
|
||||
<Compile Include="Utils\NativeCoreAudio.cs" />
|
||||
<Compile Include="Utils\PlaybackErrorEventArgs.cs" />
|
||||
<Compile Include="Impl\SoundPlayerImplFallback.cs" />
|
||||
<Compile Include="Impl\SoundPlayerImplWMP.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<COMReference Include="WMPLib">
|
||||
<Guid>{6BF52A50-394A-11D3-B153-00C04F79FAA6}</Guid>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<Isolated>False</Isolated>
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
@@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace TweetDuck.Core.Utils{
|
||||
namespace TweetLib.Audio.Utils{
|
||||
static class NativeCoreAudio{
|
||||
private const int EDATAFLOW_RENDER = 0;
|
||||
private const int EROLE_MULTIMEDIA = 1;
|
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Core.Notification.Sound{
|
||||
sealed class PlaybackErrorEventArgs : EventArgs{
|
||||
namespace TweetLib.Audio.Utils{
|
||||
public sealed class PlaybackErrorEventArgs : EventArgs{
|
||||
public string Message { get; }
|
||||
public bool Ignore { get; set; }
|
||||
|
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
[assembly: AssemblyVersion("1.0.1.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.1.0")]
|
||||
|
@@ -35,7 +35,7 @@
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>
|
||||
</PostBuildEvent>
|
||||
<PostBuildEvent>call "$(DevEnvDir)..\..\VC\Auxiliary\Build\vcvars32.bat"
|
||||
editbin /largeaddressaware /TSAWARE "$(TargetPath)"</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
</Project>
|
@@ -14,7 +14,6 @@ namespace UnitTests.Core.Utils{
|
||||
foreach(var pos in Positions){
|
||||
Assert.AreEqual(string.Empty, new InjectedHTML(pos, "b", "b").Inject(string.Empty));
|
||||
Assert.AreEqual("aaaa", new InjectedHTML(pos, "b", "b").Inject("aaaa"));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user