NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name BlendExchanger // @include *blender.stackexchange.com/* // @description Upload .blends to Blend-Exchange without leaving StackExchange // @version 2.0.2 // @license GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt // @namespace blender.org // @updateURL https://openuserjs.org/meta/gandalf3/BlendExchanger.meta.js // @downloadURL https://openuserjs.org/src/scripts/gandalf3/BlendExchanger.user.js // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V%2B0%2FAAABaFBMVEUAAAAAAABUpudZo%2BD9%2Ff0BbMcKfdp1wO9bpOBNoeNClts3jdQ3kdgje8hbqt00jdjW1tYAe94dh9wAbca3t7f09PTQ0NDy8vLOzs7RaBPo6Oj3s3LTdSdotO%2FIyMj1rWlKdqz3sGrRbBr0eAD%2F%2F%2F%2F8%2BvmB0PmAzPhbpOCxw9tIisb028XtzLEzZKLahDv2ljrPZxKZ5f%2BW4v%2F9%2Ff2N2f16xvZvvPJRr%2BQzmtpJltJElNDr2Mjv1L5Hhr67u7sxdLWvr68fXqPtoV3clVvqnFfUeS7PZQ%2FOXQNXy%2Fvf7PJKufBmu%2B3s7OxisOs%2FrOhWsuZWpuH3699Epd%2BnwtxMnNjX19d5stbU1NTn3dP75dD75c%2BPrslKjMZDgrzz17s0eLnozLdwnKzqu5b5wInTqomllIH4un7kp3X0qWLjnWHypmD3qF2RclvQjVj2olHjk1DqlUr2mUDskj7ZfjT0jCj0iSLWcyDqdgVv3a2kAAAAFXRSTlMCAOv7fExK%2FfHl3dbMyKWdknRRNgeqtpszAAABIklEQVQY023R1XLCQBSA4YQCdW%2BybJcQQ%2BO4u9Xd3d319bskpcx0%2BC%2F24rs4M2cPQZKkp1Qo0FakGYHNW%2FJMW7kIK2zebQBA1Z0FoI1jHnpzAaNQ007%2BkKYoCuCMl5t6bn%2FY8Ys5Nw663%2B%2FYh4PIoMNCyLM8YhFCl%2FkYExlwmnj7ZTyj873o%2BqGfYRLh%2FglzptJAaDdULs7MJZh4LGxzYlQge3Xv48rFY2me8QdttnGCliFfqTz5OFXlpOVFf3AKz7yAPGzUXuWQyoWk5EogSOAgC8%2Bq2od4FPXt5JMbgZ4WPgoylYXapyh%2Bi%2Bm1NMb2RopQf9N1fSmVifd11ly9FppN4zSV6R3tIKBazW6NDE3%2BR4rGH9wFCTM7PoILZz52y34AnZsz2CzgIFUAAAAASUVORK5CYII%3D // ==/UserScript== // ==OpenUserJS== // @author gandalf3 // ==/OpenUserJS== // Additional credits: GiantCowFilms, CoDEmanX, and iKlsR // Shortcut is Alt+B by default, you can change it here if you wish function shortcutShouldFire(ev) { return (ev.altKey && (ev.key == 'b')); } // URL of blend-exchange embedded upload view const SiteURL = "https://blend-exchange.com" const EmbedURL = SiteURL + "/embedUpload/?qurl=" + document.URL; //Quiet debugging prints console.log = function() {}; function main() { console.log("running main!"); function startInjection() { // We can't know the url of the question if it hasn't been asked yet, so don't just abort if (document.URL.indexOf("blender.stackexchange.com/questions/ask") >= 1) { console.log('Disabling BlendExchanger because you\'re currently asking a question.' + 'Come back and edit it once you\'ve posted it (so that we know the URL)'); return; } //try and add .blend button when any of these elements are clicked: document.querySelectorAll('a.edit-post, ' + 'input[value="Add Another Answer"], ' + //adding multiple answers 'input[value="Improve"], ' + //improving suggested edits 'input[value="Edit"]' //editing close voted questions ).forEach(elem => { elem.addEventListener('click', waitForButtonRow); }); //define keyboard shortcut event handler (Alt+B) document.querySelectorAll('textarea.wmd-input').forEach(elem => { elem.addEventListener('keydown', ev => { if (shortcutShouldFire(ev)) { ev.preventDefault(); insertBlendFileDialog(this, EmbedURL); } }); }); waitForButtonRow(); } // Repeatedly check for button rows and inject buttons into every one we find function waitForButtonRow() { console.log("waiting for button row.."); function testForButtonRow() { if (counter > 60) { console.log("did not find a place to put blend button within 30 seconds. giving up."); return; } let button_rows = document.getElementsByClassName('wmd-button-row'); if (button_rows.length <= 0) { setTimeout(testForButtonRow, 500); counter++; return; } for (button_row of button_rows) { //if no blend button already exists, inject one if (button_row.getElementsByClassName("wmd-blend-button").length == 0) { injectButton(button_row); } } } var counter = 0; setTimeout(testForButtonRow, 500); } function injectButton(buttonRow) { var blendButtonId = 'wmd-blend-button' + buttonRow.getAttribute("id").replace(/[^0-9]+/g, ""); var li = document.createElement('li'); li.setAttribute('id', blendButtonId); li.setAttribute('title', 'Upload .blend to Blend-Exchange (Alt+B)'); li.classList.add('wmd-button', 'wmd-blend-button'); var respond_to_click = true; li.addEventListener('click', () => { //prevent user from clicking too many times too quickly; ignore secondary clicks which occur within 500ms console.log(respond_to_click); if (respond_to_click == true) { respond_to_click = false; insertBlendFileDialog(buttonRow.parentNode.parentNode.querySelector(".wmd-input"), EmbedURL); setTimeout(function() { respond_to_click = true }, 500); } }); //insert out button after the image button let image_button = buttonRow.querySelector("[id^=wmd-image]"); buttonRow.insertBefore(li, image_button.nextSibling); //Add image element with base64 encoded png icon let icon = document.createElement('img'); icon.setAttribute('src','data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V%2B0%2FAAABaFBMVEUAAAAAAA\ BUpudZo%2BD9%2Ff0BbMcKfdp1wO9bpOBNoeNClts3jdQ3kdgje8hbqt00jdjW1tYAe94dh9wAbca3t7f09PTQ0ND\ y8vLOzs7RaBPo6Oj3s3LTdSdotO%2FIyMj1rWlKdqz3sGrRbBr0eAD%2F%2F%2F%2F8%2BvmB0PmAzPhbpOCxw9tIi\ sb028XtzLEzZKLahDv2ljrPZxKZ5f%2BW4v%2F9%2Ff2N2f16xvZvvPJRr%2BQzmtpJltJElNDr2Mjv1L5Hhr67u7sx\ dLWvr68fXqPtoV3clVvqnFfUeS7PZQ%2FOXQNXy%2Fvf7PJKufBmu%2B3s7OxisOs%2FrOhWsuZWpuH3699Epd%2Bnwt\ xMnNjX19d5stbU1NTn3dP75dD75c%2BPrslKjMZDgrzz17s0eLnozLdwnKzqu5b5wInTqomllIH4un7kp3X0qWLjnWHyp\ mD3qF2RclvQjVj2olHjk1DqlUr2mUDskj7ZfjT0jCj0iSLWcyDqdgVv3a2kAAAAFXRSTlMCAOv7fExK%2FfHl3dbMyKWdk\ nRRNgeqtpszAAABIklEQVQY023R1XLCQBSA4YQCdW%2BybJcQQ%2BO4u9Xd3d319bskpcx0%2BC%2F24rs4M2cPQZKkp1Qo\ 0FakGYHNW%2FJMW7kIK2zebQBA1Z0FoI1jHnpzAaNQ007%2BkKYoCuCMl5t6bn%2FY8Ys5Nw663%2B%2FYh4PIoMNCyLM8Yh\ FCl%2FkYExlwmnj7ZTyj873o%2BqGfYRLh%2FglzptJAaDdULs7MJZh4LGxzYlQge3Xv48rFY2me8QdttnGCliFfqTz5OFXlp\ OVFf3AKz7yAPGzUXuWQyoWk5EogSOAgC8%2Bq2od4FPXt5JMbgZ4WPgoylYXapyh%2Bi%2Bm1NMb2RopQf9N1fSmVifd11ly9F\ ppN4zSV6R3tIKBazW6NDE3%2BR4rGH9wFCTM7PoILZz52y34AnZsz2CzgIFUAAAAASUVORK5CYII%3D'); li.appendChild(icon); } function insertBlendFileDialog(txta, url) { //popup size var popupWidth = 640; var popupHeight = 400; var left = ((window.outerWidth/2)+window.screenX)-(popupWidth/2); var top = ((window.outerHeight/2)+window.screenY)-(popupHeight/2); console.log("left", left); console.log("top", top); var blendUploadWindow = window.open(url, "Blend-Exchange wormhole portal vortex uploader thingy", "width=" + popupWidth + ",height=" + popupHeight + ",toolbar=no,menubar=no,location=no,status=no,scrollbars=yes,resizable=no,left=" + left + ",top=" + top); window.addEventListener("message", function (event) { //It is necessary to check the origin form stopping foreign pages hijacking the event if (event.data.name == "embedSource" && event.origin == SiteURL) { //Get the embed code embedCode = event.data.content; insertBlendFile(txta, embedCode); //Check if window is closed, if not close it if (false == blendUploadWindow.closed) { blendUploadWindow.close(); } } else { console.log("Unknown event fired", event); } }); }; function insertBlendFile(txta,embedCode) { if (txta.selectionStart == null) { console.log("selectionStart is null? aborting"); return; } var start = txta.selectionStart; var end = txta.selectionEnd; var chars = txta.value; console.log("chars: " + chars); //separate selection from rest of body var pre = chars.slice(0, start); var post = chars.slice(end); //put everything back together again txta.value = pre + embedCode + post; //highlight newly added embed code txta.selectionStart = pre.length; txta.selectionEnd = pre.length + embedCode.length; txta.focus(); updateMarkdownPreview(txta); } //function to force update the live markdown render function updateMarkdownPreview(element) { var keyboardEvent = document.createEvent("KeyboardEvent"); var initMethod = typeof keyboardEvent.initKeyboardEvent !== 'undefined' ? "initKeyboardEvent" : "initKeyEvent"; //horrible hack so undo after inserting blend tags only removes blend tags 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 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 window, // 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 } main();