您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Collection of HTML/CSS enhancements for various bugs and/or shortcomings of the Shopping Lists page (/ShoppingList.aspx)
当前为
// ==UserScript== // @name OutOfMilk.com Shopping List Enhancements // @version 0.2.6 // @description Collection of HTML/CSS enhancements for various bugs and/or shortcomings of the Shopping Lists page (/ShoppingList.aspx) // @namespace https://greasyfork.org/en/users/15562 // @author Jonathan Brochu (https://greasyfork.org/en/users/15562) // @license GPLv3 or later (http://www.gnu.org/licenses/gpl-3.0.en.html) // @include http://outofmilk.com/ShoppingList.aspx* // @include http://www.outofmilk.com/ShoppingList.aspx* // @include https://outofmilk.com/ShoppingList.aspx* // @include https://www.outofmilk.com/ShoppingList.aspx* // @grant GM_addStyle // ==/UserScript== /*** * History: * * 0.2.6 Changes made: * - Improved declarations of tool methods injectCode(), injectFuncCode() * and replaceUnsafeFunc() to denote optional arguments supported for * each. Also, reworked their code a bit. * - For method replaceUnsafeFunc(), added support for two new optional * arguments: the first one to specify the parent object whose function * must be replaced, when it's not in the global scope (i.e. window); * and the second to specify a delay before which the function * replacement should be done, i.e. for objects or functions created * (or exposed) through deferred scripts. * - For methods injectCode() and injectFuncCode(), added as new optional * argument the possibility to specify an execution delay, just like * for replaceUnsafeFunc(). * - Added the possibility to specify a UPC when adding a new item to the * shopping list. To do so, we add a new UPC field to the "New Item" * form and then replace that form's handling method with a version * supporting the new field. * Unfortunately, for the time being it seems like the web service * ignores any value used as the UPC (though to begin with the spec was * already there to show how it should be passed when making the call). * (2018-08-06) * 0.2.5 Changes made: * - Tweaked the UPC parsing code a bit. * - In the future, this script should be modified to allow users to * provide their own link templates for looking up products by their * stored UPC codes. * (2016-08-09) *not publicly released * 0.2.4 Change made: * - Completed the UPC parsing code, including expanding UPC-E barcodes, * as part of the product links building added in version 0.2.3. * (2016-06-21) *not publicly released * 0.2.3 Change made: * - On the "Edit Product History" dialog, added a column for links to * the corresponding product on a grocery store website (IGA.net). * (2016-06-02) *not publicly released * 0.2.2 Change made: * - For the fix around the "Tax Free" checkbox of the "Edit Product * History" dialog, a jQuery function call was failing so we're now * using its DOM native equivalent. * (2016-05-06) * 0.2.1 Changes made: * - Fixed the always-unchecked "Tax Free" checkbox for the dialog * "Edit Product History". * - Updated element id for the "Description" field of the dialog * "Edit Product History". * (2016-01-24) * 0.2.0 Changes made: * - Updated the "producthistory-template" template to match column * names recently added by outofmilk.com, while at the same time * disabling the CSS code previously used to display custom column * headers. * - Updated the "producthistory-template" template so that, like the * original one, "Yes" & "No" are used as values for the "Tax Free?" * column instead of "true" & "false". * - Fixed the non-working "Tax Free" checkbox and empty "How Much?" * dropdown list for dialog "shoppingeditpopup". * - Fixed the URL used for the "icon-taxfree.png" image whenever * adding or editing an item that is tax free. * (2015-09-17) * 0.1.7 Changes made: * - Updated script for use with the repository [greasyfork.org]. * - No change made to the code. * (2015-09-14) * 0.1.6 Changes made: * - Kept being annoyed that everytime I added/changed a UPC from the * product history it wouldn't update, so now I re-implement method * saveProductHistory() (from "ProductManagement.js?v=...") with the * added tweaks (after all, I'm the one adding the column). Also, * updated the producthistory template with a new class for that * column. * NOTE: I'll have to watch out for any updates/changes to the site * as I replace the whole function, since obviously I cannot * just patch the existing code. Oh, wait... * NODO: I know they say "eval() is evil()", but what if I'd say the * words "toString()", "String.replace()" and "eval()" in that * particular order... OK, I'll leave that hanging in the air. * - Took the opportunity to fix the call that sets the initial width * of the "Product History Management" dialog, which was failing with * errors of the sort "Permission denied to access property". * (2015-06-30) * 0.1.5 Change made: * - Added outofmilk.com as a possible domain for include URLs. * (2015-04-02) * 0.1.4 Changes made: * - Changed how the initial width of the "Product History Management" * dialog is set. * - Implemented changes to add a "UPC" column to the product history * table (by changing its jQuery UI dialog template; this is possible * since the web service's "GetAllProductHistoryItems" method already * returns the stored UPC value for each history item). * - Removed keep-alive code since no longer necessary. * (2013-08-19) * 0.1.3 Changes made: * - Removed "!important" when setting the (initial) width property of * the "Product History Management" dialog (since the specified width * isn't meant to be permanent). * (2013-04-05) * 0.1.2 Changes made: * - Added javascript code to keep the session alive (without the * need to refresh the page). * (2013-04-04) * 0.1.1 Changes made: * - Added column names for dialog "Product History Management". * - Changed text alignment for (newly-named) column "Tax Exempt". * (2013-04-04) * 0.1.0 First implementation. (2013-04-02) * */ (function() { // constants var USERSCRIPT_NAME = 'OutOfMilk.com Shopping List Enhancements'; /* * The Payload */ // css definitions var css_fixes = '@namespace url(http://www.w3.org/1999/xhtml);\n' + // Changes & Overrides // background overlays for modal dialog with fixed postion '.ui-widget-overlay { position: fixed /* original: absolute */ !important ; }\n' + // "Product History Management" dialog - increase initial width // '-> now done through javascript // //'div[aria-describedby="manageproducthistoryform"] { width: 80% /* original: 600px */ ; }\n' + // "Product History Management" dialog - take full parent's width for table within dialog 'table.producthistory-table { width: 100% /* original: 550px */ !important ; }\n' + // "Product History Management" dialog - column headers // 2015-09-17: Column headers not needed anymore (done through the template itself) ///'table#producthistorytable.table-default > tr:nth-child(1) > th:nth-child(1) > strong:before { ' + /// 'content: "Item" /* original: (none specified) */ !important ; }\n' + ///'table#producthistorytable.table-default > tr:nth-child(1) > th:nth-child(3) > strong:before { ' + /// 'content: "Tax Exempt" /* original: (none specified) */ !important ; }\n' + ///'table#producthistorytable.table-default > tr:nth-child(1) > th:nth-child(4) > strong:before { ' + /// 'content: "Category" /* original: (none specified) */ !important ; }\n' + ///'table#producthistorytable.table-default > tr:nth-child(1) > th:nth-child(5) > strong:before { ' + /// 'content: "UPC" /* original: (none specified) */ !important ; }\n' + 'table#producthistorytable.table-default > tr:nth-child(1) > th:nth-child(6):before { ' + /// 'content: "Actions" /* original: (none specified) */ !important ; ' + 'text-align: center /* original: left (through inheritance) */ !important ; ' + '}\n' + 'table#producthistorytable.table-default > tr:nth-child(1) > th:nth-child(5) { ' + 'text-align: center /* original: left (through inheritance) */ !important ; ' + '}\n' + // "Product History Management" dialog - values for column "Tax Exempt" centered horizontally 'td.producthistorytaxfree { text-align: center /* original: left (through inheritance) */ !important ; }\n' + // "Edit Product History" dialog - wider "Description" field '#txtEditProductHistoryDescription { ' + 'width: 380px /* original: (none specified) */ !important ; }\n' + // <END> ''; // new "producthistory-template" template var templateProductHistory = function() { /**HEREDOC <script type="producthistory-template"> <## if (!String.prototype.reverse) { String.prototype.reverse = function() { return this.split('').reverse().join(''); }; } var eanCheckDigit = function(s) { var result = 0; var rs = s.reverse(); for (counter = 0; counter < rs.length; counter++) { result = result + parseInt(rs.charAt(counter)) * Math.pow(3, ((counter+1) % 2)); } return (10 - (result % 10)) % 10; }; ##> <# if(this.dataobjects.length > 0) { #> <tr> <th><strong>Description</strong></th> <th><strong>Price</strong></th> <th><strong>Tax Free?</strong></th> <th><strong>Category</strong></th> <th><strong>UPC</strong></th> <th><strong>Links</strong></th> <th colspan="3">Actions</th> </tr> <# $.each(this.dataobjects, function(i, object) { #> <tr> <td class="producthistoryid hidden"><#= object.ID #></td> <td> <span class="producthistorydescription"><#= trimDescription(object.Description,60,"<acronym title=\"" + object.Description + "\">...</acronym>") #></span> </td> <td class="producthistoryprice"> <span><#= FormatNumberCurrency(object.Price) #></span> </td> <td class="producthistorytaxfree"> <span> <# if (object.TaxFree) { #> Yes <# } else { #> No <# } #> </span> </td> <td class="producthistorycategory"> <span><#= object.CategoryName #></span> </td> <td class="producthistoryupc"> <span><#= object.UPC #></span> <## object.UPC = String(object.UPC); if (object.UPC.length >= 12) { object.UPC12 = object.UPC.substr(-12,12); if (object.UPC12.substr(0,2) === '00') { object.UPC12 = object.UPC12.substr(1,11); object.UPC12 += eanCheckDigit(object.UPC12); } } else if (object.UPC.length === 6 && object.UPC.substr(0,1) === "F") { object.UPC12 = '000000' + object.UPC.substr(1,5); object.UPC12 += eanCheckDigit(object.UPC12); } else if (object.UPC.length === 8) { object.UPC12 = object.UPC.charAt(0); switch (object.UPC.charAt(6)) { case '0': case '1': case '2': object.UPC12 += object.UPC.substr(1, 2) + object.UPC.substr(6, 1) + '0000' + object.UPC.substr(3, 3) + object.UPC.substr(7, 1); break; case '3': object.UPC12 += object.UPC.substr(1, 3) + '00000' + object.UPC.substr(4, 2) + object.UPC.substr(7, 1); break; case '4': object.UPC12 += object.UPC.substr(1, 4) + '00000' + object.UPC.substr(5, 1) + object.UPC.substr(7, 1); break; case '5': case '6': case '7': case '8': case '9': object.UPC12 += object.UPC.substr(1, 5) + '0000' + object.UPC.substr(6, 1) + object.UPC.substr(7, 1); break; default: object.UPC12 = ''; } } else { object.UPC12 = ''; } ##> </td> <td class="producthistorylink"> <span><# if (object.UPC12.length > 0) { #> <a target="_blank" href="https://www.iga.net/en/search?k=00<#= object.UPC12.substr(0, 11) #>"><strong>IGA</strong></a> <# } else { #> <# } #></span> </td> <td> <a href="javascript:void(0);" class="btn-default addproducthistory"><span>Add To List</span></a> </td> <td> <a href="javascript:void(0);" class="btn-default editproducthistory"><span>Edit</span></a> </td> <td class="last-column"> <a href="javascript:void(0);" class="btn-default deleteproducthistory"><span>Delete</span></a> </td> </tr> <# }); #> <# } else { #> <tr> <td colspan="5">There are no items to display</td> </tr> <# } #> </script> HEREDOC**/ }; // new implementation of saveProductHistory() var mySaveProductHistory = function($this) { if(validateProductHistoryForm()){ var ID = $(".editproducthistoryid").html(); var description = $(".editproducthistorydescription").val(); var price = $(".editproducthistoryprice").val(); var taxfree = $(".editproducthistorytaxfree input").is(":checked"); var upc = $(".editproducthistoryupc").val(); var Params = { "ID": ID, "description":description, "price": price, "upc":upc, "taxfree": taxfree }; var jQueryParams = JSON.stringify(Params); $.ajax({ type: "POST", url: "Services/GenericService.asmx/UpdateProductHistoryItem", data: jQueryParams, contentType: "application/json; charset=utf-8", dataType: "json", success: function (msg) { if(msg.d === false){ $(".producthistoryitemvalidation").html("An item already exists with this description and price!"); } else { var $element = $(".producthistoryid:contains("+ID+")").parents("tr"); $element.find(".producthistorydescription").html(description); $element.find(".producthistoryprice").html(FormatNumberCurrency(price)); /* added --> */ $element.find(".producthistoryupc").html(upc); if(taxfree) { $element.find(".producthistorytaxfree").html("Yes"); } else { $element.find(".producthistorytaxfree").html("No"); } $this.dialog("close"); } }, failure: function (msg) { } }); } }; // of course, I could also do something like: /* eval('var mySaveProductHistory = ' + unsafeWindow.saveProductHistory.toString().replace(/(html\(FormatNumberCurrency\(price\)\);)/, '$1\n$$element.find(".producthistoryupc").html(upc);') ); */ // but, would blindly patching code be actually better than // replacing a whole function? Yeah, I thought so. // 2018-08-06: new implementation of ShoppingEngine.addShoppingItem() to add a UPC field // NOTE: Not my code; I only added not even half a line var myAddShoppingItem = function() { if (ShoppingEngine.validateShoppingItemForm($(".shoppinglistitem"), false) != false) { var $element = $(".shoppinglistitem"); var description = $element.find(".itemdescription").val(); var quantity = $element.find(".itemquantity").val(); var unit = $element.find(".itemunit").val(); var unittext = $element.find(".itemunit option:selected").text(); var price = $element.find(".itemprice").val(); var note = $element.find(".itemnote").val(); var taxfree = $element.find(".taxfree input").is(":checked"); var category = $element.find(".itemcategory").val(); // 2018-08-06: Added a UPC field var UPC = $element.find(".itemupc").val() || ''; if (usedAutoComplete == undefined) { usedAutoComplete = false; } var Params = { "ID": ListID, "usedAutoComplete": usedAutoComplete, "description": description, "quantity": quantity, "unit": unit, "price": price, "note": note, "taxfree": taxfree, "upc": UPC, "category": category, "promoproviderpromotionid": "", "promoproviderstaticid": 0, "useMinOrdinal": false, "isEmailPromo": false }; var jQueryParams = JSON.stringify(Params); $.growlUI(gettext('Please Wait...'), gettext('Adding your item to the list!')); $.ajax({ type: "POST", url: "Services/GenericService.asmx/AddShoppingItem", data: jQueryParams, contentType: "application/json; charset=utf-8", dataType: "json", success: function (msg) { var Data = JSON.parse(msg.d); var $template = $(".itemtemplate").clone(); var flagsText = ""; if (taxfree || note.length > 0) { flagsText = "<div class='additionalitems clearfix'>"; if (taxfree) { flagsText = flagsText + " <acronym title='" + globalRes.TaxFree + "'><img src='"+ STATIC_URL +"images/icon-taxfree.png' /></acronym>"; } if (note.length > 0) { flagsText = flagsText + " <acronym title=\"" + note.replace(/\"/g, '\'') + "\"><img src='"+ STATIC_URL +"images/icon-note.png' /></acronym>"; } flagsText = flagsText + "</div>"; } $template.find(".cell-title").html(flagsText).text(description); $template.find(".cell-qty").html(FormatNumber(quantity) + " " + unittext); $template.find(".cell-price").html(FormatNumberCurrency(Data.Price)); $template.find(".productid").html(Data.ID); $template.find(".itemguid").html(Data.GUID); $template.find(".createddate").html(Data.Created); $template.removeClass("hidden"); $template.removeClass("itemtemplate"); $template.attr("id", "item_" + Data.ID); //Find all categories on the list and then insert before if we have a category, and insert after the beginning if we don't. //if we have a category but it's not actually on the list, that means it was a newly created category list from the add item //so we will check for that and do a full list refresh if that's the case var foundCategory = false; if (Data.Category) { $(".category").each(function () { var catID = $(this).attr("id").split("cat_")[1]; if (Data.Category == catID) { $template.insertAfter($(this)); foundCategory = true; } }) } else { //if there was no category set this to true so that we dont have a full refresh foundCategory = true; $template.insertAfter("#table_totals"); } $("#sortable tbody").sortable('refresh', { items: 'tr:not(.tabletop):not(.itemtemplate)' }); ListEngine.bindListItemsEdit(); ShoppingEngine.updateShoppingListPrice(); ListEngine.bindContextMenus(); sortLists(true); usedAutoComplete = false; if (!foundCategory) { $("#btn-refresh-list").click(); } //Bind the click event for toggling an items status ListEngine.toggleItemStatus(); }, error: function (msg) { Apprise(gettext("Failed to insert item. Please try again.") + "<br /><br />" + gettext("If this problem persists, please contact us at") + " <a href='https://outofmilk.zendesk.com/hc/en-us/requests/new'>Support Request</a>", { 'confirm': true }); } }); return true; } else { return false; } }; // 2015-09-17: fix for the "Tax Free" checkbox not working in the "shoppingeditpopup" dialog var shoppingeditpopup_fix1 = function() { // find the "Tax Free" checkbox and its <div> container var $element = $(".shoppingeditpopup"), taxfree = $element.find("input#checkbox1"), taxfreeParentDiv = taxfree.parent(); // add the missing "taxfree" class, such that in method // ShoppingEngine.updateShoppingItem() the line: // --- // var taxfree = $element.find(".taxfree input").is(":checked"); // --- // works properly taxfreeParentDiv.addClass('taxfree'); }; // 2015-09-17: fix for the empty <select> element in the "shoppingeditpopup" dialog var shoppingeditpopup_fix2 = function() { // find the "How Much?" <select> element for units (removing any children in the process) var $element = $(".shoppingeditpopup"), editUnits = $element.find("select#drpUnitsEdit").find("option").remove().end(), itemUnitOptions = $(".shoppinglistitem").find("select.itemunit").find("option"); // copy the options for the "shoppinglistitem" dialog $.each(itemUnitOptions, function() { editUnits.append($("<option />").val($(this).val()).text($(this).text())); }); }; // 2015-09-17: methods ShoppingEngine.addShoppingItem() & ShoppingEngine.updateShoppingItem() // don't use proper links for "icon-taxfree.png"; fix that var iconTaxFreeURLs_fix = function() { // patch using .toString() & eval() ///$.each(['addShoppingItem', 'updateShoppingItem'], function() { // 2018-08-06: Since we're now using our own implementation of ShoppingEngine.addShoppingItem(), // we took care to correct that bug too $.each(['updateShoppingItem'], function() { eval("ShoppingEngine." + this + " = " + ShoppingEngine[this].toString().replace("src='Images/icon-taxfree.png", "src='\"+ STATIC_URL +\"images/icon-taxfree.png'")); }); }; // 2016-01-24: fix for the missing "producthistorytaxfree" class in the "editproducthistoryform" dialog var editproducthistory_fix1 = function() { // find the "Tax Free" <input type="checkbox"> element and its <td> parent var taxfree = $("#txtEditProductHistorytaxfree"), taxfreeParentTD = taxfree.parent(); // add the missing "producthistorytaxfree" class, so that the lines // $(".editproducthistorytaxfree input").attr("checked", true); // $(".editproducthistorytaxfree input").attr("checked", false); // work as intended taxfreeParentTD.addClass('editproducthistorytaxfree'); }; // 2016-01-24: fix for the "Tax Free" checkbox being always unchecked in the "editproducthistoryform" dialog var editproducthistory_fix2 = function() { $(".manageproducts").on("click", function() { $(".editproducthistory").off(); $(".editproducthistory").on("click", function() { var $parent = $(this).parents("tr"); var ID = $parent.find(".producthistoryid").html(); var description = $parent.find(".producthistorydescription").html(); var price = $parent.find(".producthistoryprice").html(); var Params = { "ID": ID }; var jQueryParams = JSON.stringify(Params); $.growlUI('<img src="' + STATIC_URL + 'images/monster-help.png" />Please Wait...', 'Loading Product History item...'); $.ajax({ type: "POST", url: "Services/GenericService.asmx/GetProductHistoryItem", data: jQueryParams, contentType: "application/json; charset=utf-8", dataType: "json", success: function (msg) { $(".editproducthistorydescription").val(msg.d.Description); $(".editproducthistoryprice").val(FormatNumber(msg.d.Price)); $(".editproducthistoryid").html(msg.d.ID); $(".editproducthistoryupc").val(msg.d.UPC); $(".editproducthistorytaxfree input").removeAttribute("checked"); if (msg.d.TaxFree) { $(".editproducthistorytaxfree input").checked = true; } else { $(".editproducthistorytaxfree input").checked = false; } $("#editproducthistoryform").find('input').keypress(function (e) { if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) { $(".ui-dialog[aria-labelledby='ui-dialog-title-editproducthistoryform']").find('.ui-dialog-buttonpane').find('button:first').click(); return false; } }); $("#editproducthistoryform").dialog("open"); }, failure: function (msg) { } }); }); }); }; // 2018-08-06: fix for adding a UPC field to the "New Item" form var listadditemform_fix = function() { // get form var $element = jQuery('.shoppinglistitem').first(); // make sure we don't already have the UPC field present if ($element.length > 0 && $element.find('.itemupc').length == 0) { var itemPriceElem = $element.find('.itemprice').first(); if (!itemPriceElem) { return; } itemPriceElem.parent().after('<strong><span>UPC (if any)</span><input type="text" value="" class="textbox itemupc"></strong>'); } }; /* * The Tools */ // heredoc parser var getHeredoc = function(container, identifier) { // **WARNING**: Inputs not filtered (e.g. types, illegal chars within regex, etc.) var re = new RegExp("/\\*\\*" + identifier + "[\\n\\r]+[\\s\\S]*?[\\n\\r]+" + identifier + "\\*\\*/", "m"); var str = container.toString(); str = re.exec(str).toString(); str = str.replace(new RegExp("/\\*\\*" + identifier + "[\\n\\r]+",'m'),'').toString(); return str.replace(new RegExp("[\\n\\r]+" +identifier + "\\*\\*/",'m'),'').toString(); }; // template substitution var replaceDialogTemplate = function(templateName, newContent) { var scripts = document.getElementsByTagName('script'); if (scripts.length > 0) { for (var i = 0; i < scripts.length; i++) { if (scripts[i].getAttribute('type') == templateName) { var newText = newContent.toString(); // remove comments newText = newText.replace(/\/\*(?:\r\n|\r|\n|.)+?\*\//gm, '').replace(/\/\/.+$/g, ''); // remove empty lines newText = newText.replace(/$\s*\r\n/g, ''); // make sure switch() blocks have their first case statement on the same line newText = newText.replace(/switch\s*\([^\{]+\)(\s*)\{(\s*)case/gm, function(match, p1, p2) { return match.replace(p1, ' ').replace(p2, ' '); }); // process custom <## [..] ##> blocks newText = newText.replace(/\<##\s*((?:\r\n|\r|\n|.)+?)\s*##\>/gm, function(match, p) { return p.split(/\r\n|\r|\n/g).map(function(item, idx) { return item.replace(/^(\s*)(.*)/g, function(match, p1, p2) { return p1 + '<# ' + p2.trim() + ' #>'; }); }).join("\r\n"); }); // replace template content newText = newText.replace(/^[\r\n\s]*<script[^>]*>|<\/script>[\r\n\s]*$/g, ''); scripts[i].innerHTML = newText; return; } } } }; // code injection var injectCode = function(code /* , idUniq = '', execDelay = 0 */){ var idUniq = '', execDelay = 0, tmpScript = document.createElement('script'); tmpScript.id = '__iC_script-'+Math.random().toString().slice(2); // argument #3 (optional): <script> element ID suffix if (arguments.length > 1) { idUniq = '_' + /[$_a-zA-Z][$_a-zA-Z0-9]*/.exec(arguments[1])[0] || ''; } tmpScript.id = tmpScript.id + idUniq; // argument #3 (optional): execution delay if (arguments.length > 2) { execDelay = (arguments[2] === 'domready' ? arguments[2] : (parseInt(arguments[2]) || 0)); } tmpScript.type = 'text/javascript'; tmpScript.textContent = (function() { return [ ';'+( execDelay === 'domready' ? '$(document).ready(' : (0+execDelay > 0 ? 'window.setTimeout(' : '(') )+(function () { /*code*/ var thisScript = document.getElementById('/*scriptId*/'); if (thisScript) { thisScript.parentNode.removeChild(thisScript); } // <-- oh no, you didn't!! }).toString()+( execDelay === 'domready' ? ')' : (0+execDelay > 0 ? ', '+execDelay+')' : ')()') )+';', { k: 'code', v: (typeof(code) == 'string' && code.trim().length > 0 ? code : '/*failed*/') }, { k: 'scriptId', v: tmpScript.id } ].reduce(function(base, mapping){ return base.replace('/*'+mapping.k+'*/', mapping.v); }); })(); document.head.appendChild(tmpScript); }; var injectFuncCode = function(func /* , idUniq = '', execDelay = 0 */){ if (typeof(func) !== 'function') return; var idUniq = (arguments.length > 1 ? arguments[1] : ''), execDelay = (arguments.length > 2 ? (arguments[2] === 'domready' ? arguments[2] : (parseInt(arguments[2]) || 0)) : 0), wrapper = '('+func.toString()+')();'; injectCode(wrapper, idUniq, execDelay); }; // code injection, specialized var replaceUnsafeFunc = function(targetName, newFuncImpl /* , idUniq = '', thisScope = 'window', execDelay = 0 */){ // inspiration: https://greasyfork.org/en/scripts/2599-gm-2-port-function-override-helper/code var idUniq = '', thisScope = 'window', execDelay = 0, tmpScript = document.createElement('script'); tmpScript.id = '__rUF_script-'+Math.random().toString().slice(2); // argument #3 (optional): <script> element ID suffix if (arguments.length > 2) { idUniq = '_' + /[$_a-zA-Z][$_a-zA-Z0-9]*/.exec(arguments[2])[0] || ''; } tmpScript.id = tmpScript.id + idUniq; // argument #4 (optional): scope of function to replace; by default, window (global) if (arguments.length > 3) { thisScope = ''+arguments[3].replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '') || 'window'; } // argument #5 (optional): execution delay if (arguments.length > 4) { execDelay = (arguments[4] === 'domready' ? arguments[4] : (parseInt(arguments[4]) || 0)); } tmpScript.type = 'text/javascript'; tmpScript.textContent = (function() { return [ ';'+( execDelay === 'domready' ? '$(document).ready(' : (0+execDelay > 0 ? 'window.setTimeout(' : '(') )+(function () { try { window/*scope*/ /*target*/ = /*newFunc*/window; } catch(_err) { console.log('Error in replaceUnsafeFunc() payload with id "/*scriptId*/": ' + _err.message); } var thisScript = document.getElementById('/*scriptId*/'); if (thisScript) { thisScript.parentNode.removeChild(thisScript); } // <-- oh no, you didn't!! }).toString()+( execDelay === 'domready' ? ')' : (0+execDelay > 0 ? ', '+execDelay+')' : ')()') )+';', { k: 'scope', v: '.'+thisScope }, { k: 'target', v: '.'+(typeof(targetName) == 'string' && targetName.trim().length > 0 ? targetName : '_void') }, { k: 'newFunc', v: (typeof(newFuncImpl) == 'function' ? newFuncImpl : function(){}).toString()+';//' }, { k: 'scriptId', v: tmpScript.id } ].reduce(function(base, mapping){ // 2018-08-06: replaced [String].replace() with [String].split().join() ///return base.replace('/*'+mapping.k+'*/', mapping.v); return base.split('/*'+mapping.k+'*/').join(''+mapping.v); }); })(); document.head.appendChild(tmpScript); }; // reference some outside objects window.console = window.console || (function() { if (typeof(unsafeWindow) == 'undefined') return { 'log': function() {} }; return unsafeWindow.console; })(); // self-explanatory document.addStyle = function(css) { if (typeof(GM_addStyle) != 'undefined') { GM_addStyle(css); } else { var heads = this.getElementsByTagName('head'); if (heads.length > 0) { var node = this.createElement('style'); node.type = 'text/css'; node.appendChild(this.createTextNode(css)); heads[0].appendChild(node); } } }; /* * The Action */ // css injection document.addStyle(css_fixes); // javascript patching try { // replace template "producthistory-template" replaceDialogTemplate('producthistory-template', getHeredoc(templateProductHistory, 'HEREDOC')); // replace saveProductHistory() replaceUnsafeFunc('saveProductHistory', mySaveProductHistory, 'mySaveProductHistory'); // 2018-08-06: replace ShoppingEngine.addShoppingItem() replaceUnsafeFunc('addShoppingItem', myAddShoppingItem, 'myAddShoppingItem', 'ShoppingEngine', 'domready'); // 2018-08-06: add the UPC text field to the "New Item" form injectFuncCode(listadditemform_fix, 'listadditemform_fix'); // set initial width of "Product History Management" dialog ///replaceUnsafeFunc('onload', function(){ $("#manageproducthistoryform").dialog("option", "width", "80%"); }, 'onload'); // 2018-08-06: now making use of new features added to injectCode() and injectFuncCode() injectFuncCode(function(){ $("#manageproducthistoryform").dialog("option", "width", "80%"); }, 'manageproducthistoryform_fix', 'domready'); // 2015-09-17: fixes for the "shoppingeditpopup" dialog injectFuncCode(shoppingeditpopup_fix1, 'shoppingeditpopup_fix1'); injectFuncCode(shoppingeditpopup_fix2, 'shoppingeditpopup_fix2'); // 2016-01-24: fixes for the "editproducthistoryform" dialog injectFuncCode(editproducthistory_fix1, 'editproducthistory_fix1'); injectFuncCode(editproducthistory_fix2, 'editproducthistory_fix2'); // 2015-09-17: fixes for "icon-taxfree.png" URLs injectFuncCode(iconTaxFreeURLs_fix, 'iconTaxFreeURLs_fix'); } catch(err) { console.log(err); } /* * The End */ console.log('User script "' + USERSCRIPT_NAME + '" has completed.'); })();