Skip to content

Instantly share code, notes, and snippets.

@tyalie
Last active February 27, 2022 11:44
Show Gist options
  • Save tyalie/ba0bb604a752206bb5b3252a82a14724 to your computer and use it in GitHub Desktop.
Save tyalie/ba0bb604a752206bb5b3252a82a14724 to your computer and use it in GitHub Desktop.
Send with Ctrl-Enter on web.whatsapp.com

An annoyance of mine has always been that web.whatsapp.com doesn't allow switching ctrl-enter and the enter key. As every other chat application that I use has this feature it has happened more often than not that I accidentally send a message whilst trying to write a multiline one. Which is especially annoying considering that WhatsApp messages cannot be edited.

How it works

This script provides a solution for that. By overriding the EventTarget.addEventListener I can completely override how listeners should work and wrap them - if needed - in my own methods that prepare the events before giving them to the real listeners.

Interestingly enough the newest WhatsApp web version will use the Event.isTrusted property (which is provided by browser) in order to check whether to handle the event or not. Previous version didn't have that. And that is why the code is a bit longer than expected, as I first needed to create an object that looks like a keyevent, tastes like one, but has all it's properties on r/w even isTrusted.

So here we go.

Using it

This is a tampermonkey script, so installing it with it will provide the functionality. I recommend installing the plugin using the raw link of the code in order to allow for updates and such.

// ==UserScript==
// @name Switch ctrl-enter and enter
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://web.whatsapp.com/
// @icon https://www.google.com/s2/favicons?domain=whatsapp.com
// @run-at document-start
// @grant none
// ==/UserScript==
/** Clone an object even with all it's non owned properties
* From https://stackoverflow.com/a/57109239/5759814 */
const SimplePropertyRetriever = {
getOwnAndPrototypeEnumerablesAndNonenumerables: function(obj) {
return this._getPropertyNames(obj, true, true, this._enumerableAndNotEnumerable);
},
_enumerableAndNotEnumerable: function(obj, prop) {
return true;
},
// Inspired by http://stackoverflow.com/a/8024294/271577
_getPropertyNames: function getAllPropertyNames(obj, iterateSelfBool, iteratePrototypeBool, includePropCb) {
var props = [];
do {
if (iterateSelfBool) {
Object.getOwnPropertyNames(obj).forEach(function(prop) {
if (props.indexOf(prop) === -1 && includePropCb(obj, prop)) {
props.push(prop);
}
});
}
if (!iteratePrototypeBool) {
break;
}
iterateSelfBool = true;
} while (obj = Object.getPrototypeOf(obj));
return props;
}
};
/** Clone an event and inject corresponding listener handlers
*
* Cloning the event and having a r/w object at the end allows
* us to circumvent the 'isTrusted' check on keyevents as we can
* now just inject that with whatever we want. On actuall event's
* that wouldn't be possible as these are managed by the browser itself.
*/
window.clone_event = function(e) {
let new_event = {};
SimplePropertyRetriever.getOwnAndPrototypeEnumerablesAndNonenumerables(e).forEach((k) => {
new_event[k] = e[k];
});
// also allow stop propagation and such
new_event.preventDefault = function () {
if (this.cancelable) {
this.returnValue = false;
this.defaultPrevented = true;
}
e.preventDefault();
}
new_event.stopPropagation = function () {
this.cancelBubble = true;
e.stopPropagation();
}
return new_event;
};
(function() {
// 'use strict';
/* we need to override the addEventListener handler in
order to wrap corresponding functions into our own handlers */
window.EventTarget.prototype.addEventListenerBase = EventTarget.prototype.addEventListener;
window.EventTarget.prototype.addEventListener = function(type, listener, ...args) {
let new_listener = listener;
// ony keydown events on #app are important for sending messages
if (type.includes("key")) {
let need_to_send = false;
new_listener = function (e, ...args) {
// only handle enter key
if (e.keyCode == 13) {
window.old_event = e;
// check whether my target is actually the message textbox
const is_textbox = e.target.getAttribute("contenteditable") == "true"
&& e.target.getAttribute("role") == "textbox"
&& e.target.hasAttribute("title");
const has_emoji_open = document.querySelector("span[class~='emojik'][class~='wa'][data-emoji-index][data-unicode]") !== null;
const should_edit = e.key == "Enter" && is_textbox && !has_emoji_open;
if (should_edit) {
e = window.clone_event(e);
e.ctrlKey = !e.ctrlKey;
}
if (parseFloat(navigator.userAgent.split('Firefox/').pop(), 10) >= 92) {
/* Hot fix: the ctrl-enter input in whatsapp web is
kinda broken rn as a new line will not be entered
immidiently - this behaviour can be forced after typing
in an emoji. so stop propagating the enter and let
the input field handle it for now.
this issue affects Firefox only*/
if (e.ctrlKey) {
e.stopPropagation();
}
}
}
listener(e, ...args);
};
}
this.addEventListenerBase(type, new_listener, ...args);
};
window.EventTarget.prototype.removeEventListenerBase = EventTarget.prototype.removeEventListener;
window.EventTarget.prototype.removeEventListener = function(type, listener, ...args) {
this.removeEventListenerBase(type, listener, ...args);
}
console.log("[Info] Initialized the ctrl-enter / enter switch");
})();

bug fixes

There currently is an ugly bugfix in the script as WhatsApp Web (Beta) doesn't handle new line correctly as of right now? Try typing in:

:love:<ENTER>foo<ENTER>bar

You'll see that the new line only appears after the next character has been input. And lets not talk about how new line is broken after emoji input even with the hot fix...

This seems thoo to be a Firefox only issue. Chromium doesn't have this behaviour (although new lines immediately after emojis are still handled weirdly there)

Updates

2022-02-23

Newest WhatsApp initialises the user input directly at the beginning. As such script needed to restart earlier. Also app isn't the main target anymore for key presses.

@Korb
Copy link

Korb commented Feb 27, 2022

Script not recognized as compatible with Tampermonkey 4.13.6136 (1 May 2021), Mozilla Firefox 98.0b9 (64-bit).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment