Skip to content

Instantly share code, notes, and snippets.

@protagnost
Forked from gandalf3/SE_kbd_script.user.js
Last active May 26, 2021 15:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save protagnost/c3b8911da578ee1bb8eca8e6f51bde88 to your computer and use it in GitHub Desktop.
Save protagnost/c3b8911da578ee1bb8eca8e6f51bde88 to your computer and use it in GitHub Desktop.
kbd formatting button for stackexchange
// ==UserScript==
// @name kbd formatting button for stackexchange
// @namespace https://blender.org
// @include *.stackexchange.com/*
// @include https://stackoverflow.com/*
// @include https://askubuntu.com/*
// @require https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @version 7
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
//Credits to CoDEmanX and iKlsR
//see https://blender.meta.stackexchange.com/q/388/599 for discussion
//calls to GM functions must be outside of injected code, so put them here
function toggle_extra_markdown() {
console.log("checkbox click, was", GM_getValue("extra_markdown", 1))
if (GM_getValue("extra_markdown", 1) == 1) {
GM_setValue("extra_markdown", 0);
}
else {
GM_setValue("extra_markdown", 1);
}
}
function get_prefs() {
return GM_getValue("extra_markdown", 1);
}
//stuff which will be injected with jquery goes in main:
function main() {
var pref_extra_markdown = 0
console.log("running main!");
function startInjection() {
//add kbd button when any of these elements are clicked:
$(document).on('click', 'a.edit-post', waitForButtonRow); //inline editing
$(document).on('click', 'input#answer-from-ask', waitForButtonRow); //answering own question in ask questions page
$(document).on('click', 'input[value="Add Another Answer"]', waitForButtonRow); //adding multiple answers
//review editing:
$(document).on('click', 'input[value="Improve"]', waitForButtonRow); //improving suggested edits
$(document).on('click', 'input[value="Edit"]', waitForButtonRow); //editing close voted questions
//define keyboard shortcut even handler (Ctrl+Y)
$(document).on('keydown', "textarea.wmd-input", function(e) {
if (e.ctrlKey && (e.which === 69)) {
// turns out SE silently binds Ctrl+Y to redo in addition to Ctrl+Shift+Z; needless to say, us both messing with the content at the same time causes havoc, so we stop SE.
// TODO: this doesn't always seem to work, possibly a race condition? May be best to bind to a different key.
// NEW: Now bound to Ctrl+E.
e.stopImmediatePropagation();
insertKbdTag(this);
}
});
waitForButtonRow();
}
function waitForButtonRow() {
console.log("waiting for button row..")
function testForButtonRow() { /*test for a .wmd-button-row every half a second until one is found*/
if (counter < 60) {
if ($(".wmd-button-row").length > 0) { //if button row(s) exist, test each one to see if it already has a kbd button
console.log("found .wmd-button-row");
$(".wmd-button-row").each(function() {console.log("does it have a kbd button? ", $(this).has(".wmd-kbd-button").length);console.log("id", $(this).attr("id"))});
$(".wmd-button-row").each(function() {
if ($(this).has(".wmd-kbd-button").length == 0) { //if no kbd button exists, inject one
console.log("does not contain kbd button, inserting one");
injectButton($(this));
}
});
}
else {
setTimeout(testForButtonRow, 500);
counter++;
}
}
else {
console.log("did not find a place to put kbd button within 30 seconds. giving up.");
return;
}
}
var counter = 0;
setTimeout(testForButtonRow, 500); //bit of spacer time to allow SE js to execute and add button rows.
//TODO: This causes a potential race condition (if SE js takes longer than 500ms), a better workaround would be nice..
}
// Removed function(InjectButton) "abandonded attempt to make it work on unity answers"
function insertKbdTag(txta) {
if (txta.selectionStart == null) return;
var start = txta.selectionStart;
var end = txta.selectionEnd;
var added = 0;
var chars = txta.value;
console.log("chars: " + chars);
/*function to insert mousebutton icon references as needed*/
function insertIcon(txta, mb) {
function addRef(ref) { //function to test if image references exists, and add it if it doesn't
if (txta.value.indexOf(ref) < 0) {
post = post + "\n\n " + ref; //insert image reference at end of post
}
}
console.log("mb", mb);
switch (mb.toUpperCase()) {
case "MW":
addRef("[MW]: https://i.stack.imgur.com/v1vyT.png (Mouse Wheel)");
break;
case "LMB":
addRef("[LMB]: https://i.stack.imgur.com/FwrAW.png (Left Mouse Button)");
break;
case "RMB":
addRef("[RMB]: https://i.stack.imgur.com/LPwD4.png (Right Mouse Button)");
break;
case "MMB":
addRef("[MMB]: https://i.stack.imgur.com/OASpJ.png (Middle Mouse Button)");
break;
case "MWU":
addRef("[MWU]: https://i.stack.imgur.com/7Vtba.png (Mouse Wheel Up)");
break;
case "MWD":
addRef("[MWD]: https://i.stack.imgur.com/doOIR.png (Mouse Wheel Down)");
break;
case "WIN":
addRef("[WIN]: https://i.imgur.com/AAjIi.png (Windows key)"); //use http://i.stack.imgur.com/DHxcg.png for windows 9x logo
break;
case "LINUX":
addRef("[LINUX]: https://i.stack.imgur.com/X9TZA.png (LINUX5EVAH -CharlesL)");
break;
// added Mouse Wheel UP + Down buttons, plus updated the deprecated HTT protocols to HTTPS
}
}
//separate selection from rest of body
var pre = chars.slice(0, start);
var post = chars.slice(end);
if (start != end) {
var sel = chars.slice(start, end);
console.log("sel: " + sel);
sel = sel.match(/(?:\S+|\s)/g); //split string around whitespace without deleting whitespace, thanks to this SO post: https://stackoverflow.com/a/24504047/2730823
console.log("sel: " + sel);
//remove extra spaces and replace them with kbd markdown
//var lastElement = ""; //holds previous element
var wasSpace = 0; //tracks if last element was a space
var endSpaces = 0; //needed for special end cases
var endSpace = 0;
var refined_markdown = "";
for (var char = 0; char < sel.length; char++) {
console.log("element " + char + ": " + "'" + sel[char] + "'")
//if current this element is a space, check to see if it should be replaced with a kbd
if (sel[char] == " ") {
//if previous element was not a space, replace space with kbd
if (wasSpace != 1 && char != 0) {
sel.splice(char, 1, '</kbd><kbd>');
//added += 10;
wasSpace = 1;
endSpace = char;
}
else {
//console.log("asdf42")
//console.log(sel.join(""))
sel.splice(char, 1); //remove extra space
//console.log(sel.join(""))
wasSpace = 1;
char--; //go back one element
}
}
else {
wasSpace = 0;
}
if (wasSpace == 1) {
endSpaces ++;
}
else {
endSpaces = 0;
}
//test if get_prefs is defined, and if it is test if GM_value "extra markdown" is 1. If get_prefs is not defined, use the non-persistent variable:
if (((typeof get_prefs === "function") ? get_prefs() : pref_extra_markdown) == 1 ) {
//console.log("element: " + sel[char])
switch(sel[char].toLowerCase()) {
case "control":
case "ctrl":
refined_markdown = "&#9096; Ctrl";
break;
case "alternate":
case "alt":
refined_markdown = "&#9095; Alt";
break;
case "shift":
refined_markdown = "&#8679; Shift";
break;
case "tab":
refined_markdown = "&#8633; Tab";
break;
case "delete":
case "del":
refined_markdown = "&#8998; Delete";
break;
case "enter":
case "return":
refined_markdown = "&#9166; Enter";
break;
case "backspace":
refined_markdown = "&#10229; Backspace";
break;
case "pageup":
case "pgup":
refined_markdown = "&#8670; Page up";
break;
case "pagedown":
case "pgdn":
refined_markdown = "&#8671; Page down";
break;
case "printscreen":
refined_markdown = "&#9113; Print Screen";
break;
case "up":
refined_markdown = "&#8593; Up arrow";
break;
case "left":
refined_markdown = "&#8592; Left arrow";
break;
case "right":
refined_markdown = "&#8594; Right arrow";
break;
case "down":
refined_markdown = "&#8595; Down arrow";
break;
case "caps":
case "capslock":
refined_markdown = "&#8682; Caps Lock"; //maybe use &#8684; instead?
break;
case "win":
case "windows":
case "windowskey":
case "winkey":
insertIcon(txta, "WIN");
refined_markdown = "![Windows key][WIN]";
break;
case "super":
case "linux":
case "linuxkey":
case "tuxkey":
insertIcon(txta, "LINUX");
refined_markdown = "![Linux key][LINUX]";
break;
case "meta":
refined_markdown = "&#9670; Meta";
break;
//mac thingies
case "command":
case "cmd":
refined_markdown = "&#8984; Cmd";
break;
case "option":
case "opt":
refined_markdown = "&#8997; Opt";
break;
//mouse things
case "wheel":
case "scrollwheel":
case "mousewheel":
case "mw":
insertIcon(txta, "MW");
refined_markdown = "MW![MW][MW]";
break;
case "mmb":
insertIcon(txta, "MMB");
refined_markdown = "MMB ![MMB][MMB]";
break;
case "lmb":
insertIcon(txta, "LMB");
refined_markdown = "LMB ![LMB][LMB]";
break;
case "rmb":
refined_markdown = "RMB ![RMB][RMB]";
insertIcon(txta, "RMB");
break;
case "mwu":
refined_markdown = "MWU ![MWU][MWU]";
insertIcon(txta, "MWU");
break;
case "mwd":
refined_markdown = "MWD ![MWD][MWD]";
insertIcon(txta, "MWD");
break;
// Added the mouse wheel buttons, swapped icon and text
}
console.log("refined_markdown: " + refined_markdown)
console.log("refined_markdown.length: " + refined_markdown.length)
if (refined_markdown.length > 0) {
//added += refined_markdown.length;
sel.splice(char, 1, refined_markdown);
refined_markdown = "";
}
}
}
//handle end case separatly; if there is more than 1 space at the end, the last array item is '</kbd><kbd>'
//that will result in an extra <kbd> pair, so remove it.
if (endSpaces > 0) {
sel.splice(endSpace, 1);
}
}
else { /*if there is no selection, assign sel to an array so that sel.join returns ""*/
var sel = ["",];
}
//put everything back together again
txta.value = pre + "<kbd>" + sel.join("") + "</kbd>" + post;
added = sel.join("").length + 11
//TODO, this is broken. Need to update cursor position calculation
txta.selectionStart = txta.selectionEnd = pre.length + ((start == end) ? 5 : added); //remove the selection and move
$(txta).focus();
updateMarkdownPreview(txta);
/*
// jQuery-way doesn't work :(
var evt = $.Event('keydown');
evt.which = 17;
evt.keyCode = 17; // Ctrl
$(txta).trigger(e);
// another failing attempt
$(txta).trigger({
type: "keydown",
which : 17
});
*/
}
//function to force update the live markdown render
function updateMarkdownPreview(element) {
var keyboardEvent = document.createEvent("KeyboardEvent");
var initMethod = typeof keyboardEvent.initKeyboardEvent !== 'undefined' ? "initKeyboardEvent" : "initKeyEvent";
/*keyboardEvent[initMethod](
"keydown", // event type : keydown, keyup, keypress
true, // bubbles
true, // cancelable
window, // viewArg: should be window
false, // ctrlKeyArg
false, // altKeyArg
false, // shiftKeyArg
false, // metaKeyArg
17, // keyCodeArg : unsigned long the virtual key code, else 0
0 // charCodeArgs : unsigned long the Unicode character associated with the depressed key, else 0
);
element.dispatchEvent(keyboardEvent);*/
//horrible hack so undo after inserting kbd tags only removes kbd tags
//TODO not sure why this works, need to investigate at some point..
keyboardEvent[initMethod](
"keydown", // event type : keydown, keyup, keypress
true, // bubbles
true, // cancelable
document.defaultView, // viewArg: should be window
false, // ctrlKeyArg
false, // altKeyArg
false, // shiftKeyArg
false, // metaKeyArg
66, // keyCodeArg : unsigned long the virtual key code, else 0
0 // charCodeArgs : unsigned long the Unicode character associated with the depressed key, else 0
);
element.dispatchEvent(keyboardEvent);
keyboardEvent[initMethod](
"keydown", // event type : keydown, keyup, keypress
true, // bubbles
true, // cancelable
document.defaultView, // viewArg: should be window
false, // ctrlKeyArg
false, // altKeyArg
false, // shiftKeyArg
false, // metaKeyArg
8, // keyCodeArg : unsigned long the virtual key code, else 0
0 // charCodeArgs : unsigned long the Unicode character associated with the depressed key, else 0
);
element.dispatchEvent(keyboardEvent);
}
startInjection() //call initial startup function (bind keyboard shortcuts, etc.)
}
//get jquery on chrome, thanks to this SO post: http://stackoverflow.com/a/12751531/2730823
if (typeof jQuery === "function") {
console.log ("Running with local copy of jQuery!");
main (jQuery);
}
else {
console.log ("fetching jQuery from some 3rd-party server.");
add_jQuery (main, "1.7.2");
}
function add_jQuery (callbackFn, jqVersion) {
var jqVersion = jqVersion || "1.7.2";
var D = document;
var targ = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
var scriptNode = D.createElement ('script');
scriptNode.src = 'http://ajax.googleapis.com/ajax/libs/jquery/'
+ jqVersion
+ '/jquery.min.js'
;
scriptNode.addEventListener ("load", function () {
var scriptNode = D.createElement ("script");
scriptNode.textContent =
'var gm_jQuery = jQuery.noConflict (true);\n'
+ '(' + callbackFn.toString () + ')(gm_jQuery);'
;
targ.appendChild (scriptNode);
}, false);
targ.appendChild (scriptNode);
}
@protagnost
Copy link
Author

protagnost commented May 26, 2021

This fork adds support for the Mouse Wheel Up and Down movement. It changes the hotkey from Ctrl+Y to Ctrl+E. It also updates HTTP to HTTPS, and swaps the places of the mouse action icons and texts. Lastly, a large chunk of unused code was removed.

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