您需要先安装一个扩展,例如 篡改猴、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.0
- // @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.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
- '#ctl00_ctl00_ContentPlaceHolder1_EditProductHistoryDialog1_txtEditProductHistoryDescription { ' +
- 'width: 350px /* original: (none specified) */ !important ; }\n' +
- // <END>
- '';
- // new "producthistory-template" template
- var templateProductHistory = function() {
- /**HEREDOC
- <script type="producthistory-template">
- <# 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 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>
- </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.
- // 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() {
- eval("ShoppingEngine." + this + " = " + ShoppingEngine[this].toString().replace("src='Images/icon-taxfree.png", "src='\"+ STATIC_URL +\"images/icon-taxfree.png'"));
- });
- };
- /*
- * 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) {
- // replace template content
- scripts[i].innerHTML = newContent.toString().replace(/^[\r\n\s]*<script[^>]*>|<\/script>[\r\n\s]*$/g, '');
- return;
- }
- }
- }
- };
- // code injection
- var injectCode = function(code){
- var tmpScript = document.createElement('script');
- tmpScript.id = '__iC_script-'+Math.random().toString().slice(2);
- tmpScript.type = 'text/javascript';
- tmpScript.textContent = (function() {
- return [
- ';('+(function () {
- /*code*/
- var thisScript = document.getElementById('/*scriptId*/');
- if (thisScript) { thisScript.parentNode.removeChild(thisScript); } // <-- oh no, you didn't!!
- }).toString()+')();',
- { 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){
- if (typeof(func) !== 'function') return;
- injectCode('('+func.toString()+')();');
- };
- // code injection, specialized
- var replaceUnsafeFunc = function(targetName, newFuncImpl){
- // inspiration: https://greasyfork.org/en/scripts/2599-gm-2-port-function-override-helper/code
- var tmpScript = document.createElement('script');
- tmpScript.id = '__rUF_script-'+Math.random().toString().slice(2);
- tmpScript.type = 'text/javascript';
- tmpScript.textContent = (function() {
- return [
- ';('+(function () {
- window/*target*/ = /*newFunc*/window;
- var thisScript = document.getElementById('/*scriptId*/');
- if (thisScript) { thisScript.parentNode.removeChild(thisScript); } // <-- oh no, you didn't!!
- }).toString()+')();',
- { 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){
- return base.replace('/*'+mapping.k+'*/', 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);
- // set initial width of "Product History Management" dialog
- replaceUnsafeFunc('onload', function(){ $("#manageproducthistoryform").dialog("option", "width", "80%"); });
- // 2015-09-17: fixes for the "shoppingeditpopup" dialog
- injectFuncCode(shoppingeditpopup_fix1);
- injectFuncCode(shoppingeditpopup_fix2);
- // 2015-09-17: fixes for "icon-taxfree.png" URLs
- injectFuncCode(iconTaxFreeURLs_fix);
- } catch(err) {
- console.log(err);
- }
- /*
- * The End
- */
- console.log('User script "' + USERSCRIPT_NAME + '" has completed.');
- })();