(function() { "use strict"; var offendersDirectives = angular.module('offendersDirectives', []); function getdomTreeHTML(tree) { return '
' + getdomTreeInnerHTML(tree) + '
'; } function getdomTreeInnerHTML(tree) { return recursiveHtmlBuilder(tree); } function recursiveHtmlBuilder(tree) { var html = ''; var keys = Object.keys(tree); keys.forEach(function(key) { if (isNaN(tree[key])) { html += '
' + key + '' + recursiveHtmlBuilder(tree[key]) + '
'; } else if (tree[key] > 1) { html += '
' + key + ' (x' + tree[key] + ')
'; } else { html += '
' + key + '
'; } }); return html; } offendersDirectives.directive('domTree', function() { return { restrict: 'E', scope: { tree: '=' }, template: '
', replace: true, link: function(scope, element) { element.append(getdomTreeInnerHTML(scope.tree)); } }; }); function getDomElementButtonHTML(obj, onASingleLine) { if (obj.tree && !onASingleLine) { return '
' + getDomElementButtonInnerHTML(obj, onASingleLine) + '
'; } else { return '
' + getDomElementButtonInnerHTML(obj, onASingleLine) + '
'; } } function getDomElementButtonInnerHTML(obj, onASingleLine) { if (obj.type === 'html' || obj.type === 'body' || obj.type === 'head' || obj.type === 'window' || obj.type === 'document' || obj.type === 'fragment') { return obj.type; } if (obj.type === 'notAnElement') { return 'Incorrect element'; } var html = ''; if (obj.type === 'domElement') { html = 'DOM element ' + obj.element + ''; } else if (obj.type === 'fragmentElement') { html = 'Fragment element ' + obj.element + ''; } else if (obj.type === 'createdElement') { html = 'Created element ' + obj.element + ''; } if (obj.tree && !onASingleLine) { html += getdomTreeHTML(obj.tree); } return html; } offendersDirectives.directive('domElementButton', function() { return { restrict: 'E', scope: { obj: '=' }, template: '
', replace: true, link: function(scope, element) { element.append(getDomElementButtonInnerHTML(scope.obj)); } }; }); function getJQueryContextButtonHTML(context, onASingleLine) { if (context.length === 0) { return 'Empty jQuery object'; } if (context.length === 1) { return getDomElementButtonHTML(context.elements[0], onASingleLine); } var html = context.length + ' elements (' + getDomElementButtonHTML(context.elements[0], onASingleLine) + ', ' + getDomElementButtonHTML(context.elements[1], onASingleLine); if (context.length === 3) { html += ', ' + getDomElementButtonHTML(context.elements[0], onASingleLine); } else if (context.length > 3) { html += ' and ' + (context.length - 2) + ' more...'; } return html + ')'; } function isJQuery(node) { return node.data.type.indexOf('jQuery ') === 0; } function getNonJQueryHTML(node, onASingleLine) { var type = node.data.type; if (node.windowPerformance) { switch (type) { case 'documentScroll': return '(triggering the scroll event on document)'; case 'windowScroll': return '(triggering the scroll event on window)'; case 'window.onscroll': return '(calling the window.onscroll function)'; default: return ''; } } if (!node.data.callDetails) { return ''; } var args = node.data.callDetails.arguments; var ctxt = node.data.callDetails.context; switch (type) { case 'getElementById': case 'createElement': return '' + args[0] + ''; case 'getElementsByClassName': case 'getElementsByTagName': case 'querySelector': case 'querySelectorAll': return '' + args[0] + ' on ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine); case 'appendChild': return 'append ' + getDomElementButtonHTML(args[0], onASingleLine) + ' to ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine); case 'insertBefore': return 'insert ' + getDomElementButtonHTML(args[0], onASingleLine) + ' into ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine) + ' before ' + getDomElementButtonHTML(args[1], onASingleLine); case 'addEventListener': return 'bind ' + args[0] + ' to ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine); case 'getComputedStyle': return getDomElementButtonHTML(args[0], onASingleLine) + (args[1] || ''); case 'error': return args[0]; case 'jQuery - onDOMReady': return '(function)'; case 'documentScroll': return 'The scroll event just triggered on document'; case 'windowScroll': return 'The scroll event just triggered on window'; case 'window.onscroll': return 'The window.onscroll function just got called'; default: return ''; } } function getJQueryHTML(node, onASingleLine) { var type = node.data.type; var unescapedArgs = node.data.callDetails.arguments; var args = []; var ctxt = node.data.callDetails.context; // escape HTML in args for (var i = 0 ; i < 4 ; i ++) { if (unescapedArgs[i] !== undefined) { args[i] = escapeHTML(unescapedArgs[i]); } } if (type === 'jQuery loaded' || type === 'jQuery version change') { return args[0]; } switch (type) { case 'jQuery - onDOMReady': case 'jQuery - windowOnLoad': return '(function)'; case 'jQuery - Sizzle call': return '' + args[0] + ' on ' + getDomElementButtonHTML(ctxt.elements[0], onASingleLine); case 'jQuery - find': if (ctxt && ctxt.length === 1 && ctxt.elements[0].type !== 'document') { return '' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } else { return '' + args[0] + ''; } break; case 'jQuery - html': if (args[0] !== undefined) { return 'set content "' + args[0] + '" to ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } else { return 'get content from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - append': return 'append ' + joinArgs(args) + ' to ' + getJQueryContextButtonHTML(ctxt, onASingleLine); case 'jQuery - appendTo': return 'append ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to ' + args[0] + ''; case 'jQuery - prepend': return 'prepend ' + joinArgs(args) + ' to ' + getJQueryContextButtonHTML(ctxt, onASingleLine); case 'jQuery - prependTo': return 'prepend ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to ' + args[0] + ''; case 'jQuery - before': return 'insert ' + joinArgs(args) + ' before ' + getJQueryContextButtonHTML(ctxt, onASingleLine); case 'jQuery - insertBefore': return 'insert ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' before ' + args[0] + ''; case 'jQuery - after': return 'insert ' + joinArgs(args) + ' after ' + getJQueryContextButtonHTML(ctxt, onASingleLine); case 'jQuery - insertAfter': return 'insert ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' after ' + args[0] + ''; case 'jQuery - remove': case 'jQuery - detach': if (args[0]) { return getJQueryContextButtonHTML(ctxt, onASingleLine) + ' filtered by ' + args[0] + ''; } else { return getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - empty': case 'jQuery - clone': case 'jQuery - unwrap': case 'jQuery - show': case 'jQuery - hide': case 'jQuery - animate': case 'jQuery - fadeIn': case 'jQuery - fadeOut': case 'jQuery - fadeTo': case 'jQuery - fadeToggle': case 'jQuery - slideDown': case 'jQuery - slideUp': case 'jQuery - slideToggle': return getJQueryContextButtonHTML(ctxt, onASingleLine); case 'jQuery - replaceWith': return 'replace ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' with ' + args[0] + ''; case 'jQuery - replaceAll': return 'replace ' + args[0] + ' with ' + getJQueryContextButtonHTML(ctxt, onASingleLine); case 'jQuery - text': if (args[0] !== undefined) { return 'set text "' + args[0] + '" to ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } else { return 'get text from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - wrap': case 'jQuery - wrapAll': return 'wrap ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' within ' + args[0] + ''; case 'jQuery - wrapInner': return 'wrap the content of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' within ' + args[0] + ''; case 'jQuery - css': case 'jQuery - attr': case 'jQuery - prop': if (isStringOfObject(args[0])) { return 'set ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } else if (args[1]) { return 'set ' + args[0] + ' : ' + args[1] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } else { return 'get ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - offset': case 'jQuery - height': case 'jQuery - innerHeight': case 'jQuery - width': case 'jQuery - innerWidth': case 'jQuery - scrollLeft': case 'jQuery - scrollTop': case 'jQuery - position': if (args[0]) { return 'set ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } else { return 'get from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - outerHeight': case 'jQuery - outerWidth': if (args[0] && args[0] !== 'true') { return 'set ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } else if (args[0] === 'true') { return 'get from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' (with include margins option)'; } else { return 'get from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - toggle': if (args[0] === 'true') { return getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to visible'; } else if (args[0] === 'false') { return getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to hidden'; } else { return getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - on': case 'jQuery - one': if (isStringOfObject(args[0])) { return '' + args[0].replace(/"\(function\)"/g, '(function)') + ''; } else if (args[1] && isPureString(args[1])) { return 'bind ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by ' + args[1] + ''; } else { return 'bind ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - off': if (args[0]) { if (args[1]) { return 'unbind ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by ' + args[1] + ''; } else { return 'unbind ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } } else { return 'unbind all events'; } break; case 'jQuery - live': case 'jQuery - bind': return 'bind ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); case 'jQuery - die': case 'jQuery - unbind': if (args[0]) { return 'unbind ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } else { return 'unbind all events'; } break; case 'jQuery - delegate': return 'bind ' + args[1] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by ' + args[0] + ''; case 'jQuery - undelegate': if (args[0]) { if (args[1]) { return 'unbind ' + args[1] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by ' + args[0] + ''; } else { return 'unbind namespace ' + args[0] + ''; } } else { return 'unbind all events'; } break; case 'jQuery - blur': case 'jQuery - change': case 'jQuery - click': case 'jQuery - dblclick': case 'jQuery - focus': case 'jQuery - keydown': case 'jQuery - keypress': case 'jQuery - keyup': case 'jQuery - mousedown': case 'jQuery - mouseenter': case 'jQuery - mouseleave': case 'jQuery - mousemove': case 'jQuery - mouseout': case 'jQuery - mouseover': case 'jQuery - mouseup': case 'jQuery - resize': case 'jQuery - scroll': case 'jQuery - select': case 'jQuery - submit': if (args[0]) { return 'bind on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } else { return 'triggered on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - error': case 'jQuery - focusin': case 'jQuery - focusout': case 'jQuery - hover': case 'jQuery - load': case 'jQuery - unload': return 'bind on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); case 'jQuery - removeAttr': case 'jQuery - removeProp': return 'remove ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); case 'jQuery - val': if (args[0]) { return 'set value ' + args[0] + ' to ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } else { return 'get value from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - hasClass': case 'jQuery - addClass': case 'jQuery - removeClass': return '' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); case 'jQuery - toggleClass': if (args[0]) { if (args[1]) { return 'toggle ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' to ' + args[1] + ''; } else { return 'toggle ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } } else { return 'magic no-argument toggleClass'; } break; case 'jQuery - children': if (args[0]) { return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' filtered by ' + args[0] + ''; } else { return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - closest': if (args[1]) { return 'closest ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' in context ' + args[1] + ''; } else { return 'closest ' + args[0] + ' from ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - next': case 'jQuery - nextAll': if (args[0]) { return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching ' + args[0] + ''; } else { return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - nextUntil': if (args[0]) { if (args[1]) { return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + ' and matching ' + args[1] + ''; } else { return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + ''; } } else { return 'after ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - offsetParent': return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine); case 'jQuery - prev': case 'jQuery - prevAll': if (args[0]) { return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching ' + args[0] + ''; } else { return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - prevUntil': if (args[0]) { if (args[1]) { return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + ' and matching ' + args[1] + ''; } else { return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + ''; } } else { return 'before ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - parent': case 'jQuery - parents': if (args[0]) { return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching ' + args[0] + ''; } else { return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - parentsUntil': if (args[0]) { if (args[1]) { return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + ' and matching ' + args[1] + ''; } else { return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' until ' + args[0] + ''; } } else { return 'of ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; case 'jQuery - siblings': if (args[0]) { return 'near ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + ' matching ' + args[0] + ''; } else { return 'near ' + getJQueryContextButtonHTML(ctxt, onASingleLine); } break; default: return ''; } } function escapeHTML(html) { var entityMap = { "&": "&", "<": "<", ">": ">", '"': '"', "'": ''', "/": '/' }; return String(html).replace(/[&<>"'\/]/g, function (s) { return entityMap[s]; }); } function joinArgs(args) { var html = '' + args[0] + ''; if (args[1]) { html += ', ' + args[1] + ''; if (args[2]) { html += ', ' + args[2] + ''; if (args[3]) { html += ', and more...'; } } } return html; } function isStringOfObject(str) { return typeof str === 'string' && str[0] === '{' && str[str.length - 1] === '}'; } function isPureString(str) { return typeof str === 'string' && str[0] !== '{' && str !== '(function)' && str !== '[Object]' && str !== '[Array]' && str !== 'true' && str !== 'false' && str !== 'undefined' && str !== 'unknown'; } function getTimelineParamsHTML(node, onASingleLine) { if (isJQuery(node)) { return getJQueryHTML(node, onASingleLine); } else { return getNonJQueryHTML(node, onASingleLine); } } function getBacktraceHTML(backtrace) { var html = ''; var parsedBacktrace = parseBacktrace(backtrace); if (!parsedBacktrace || parsedBacktrace.length === 0) { html += '
can\'t find any backtrace :/
'; } else { for (var i = 0 ; i < parsedBacktrace.length ; i++) { html += '
'; html += '
' + (parsedBacktrace[i].fnName || '(anonymous function)') + '
'; html += '
' + getUrlLink(parsedBacktrace[i].filePath, 40) + '
'; if (parsedBacktrace[i].column) { html += '
' + parsedBacktrace[i].line + ':' + parsedBacktrace[i].column + '
'; } else { html += '
line ' + parsedBacktrace[i].line + '
'; } html += '
'; } } return html; } function parseBacktrace(str) { if (!str) { return null; } var out = []; var splited = str.split(' / '); try { splited.forEach(function(trace) { var fnName = null, fileAndLine; var withFnResult = /^([^\s\(]+) \((.+:\d+)\)$/.exec(trace); if (withFnResult === null) { // Try the PhantomJS 2 format withFnResult = /^([^\s\(]+) \((.+:\d+:\d+)\)$/.exec(trace); } if (withFnResult === null) { // Yet another PhantomJS 2 format? withFnResult = /^([^\s\(]+|global code)@(.+:\d+:\d+)$/.exec(trace); } if (withFnResult === null) { // Try the PhantomJS 2 ERROR format withFnResult = /^([^\s\(]+) (http.+:\d+)$/.exec(trace); } if (withFnResult === null) { fileAndLine = trace; } else { fnName = withFnResult[1]; fileAndLine = withFnResult[2]; } // And now the second part var fileAndLineSplit = /^(.*):(\d+):(\d+)$/.exec(fileAndLine); if (fileAndLineSplit === null) { fileAndLineSplit = /^(.*):(\d+)$/.exec(fileAndLine); } var filePath = fileAndLineSplit[1]; var line = fileAndLineSplit[2]; var column = fileAndLineSplit[3]; // Filter phantomas code if (filePath.indexOf('phantomjs://') === -1) { out.push({ fnName: fnName, filePath: filePath, line: line, column: column }); } }); } catch(e) { return null; } return out; } function getTimelineDetailsHTML(node) { var html = ''; if (node.data.type != 'jQuery loaded' && node.data.type != 'jQuery version change' && !node.windowPerformance) { if (node.warning || node.error) { html += '
'; } else { html += '
'; } html += '
'; html += '
'; if (node.data.callDetails.context && node.data.callDetails.context.length === 0) { html += '

Called on 0 jQuery element

Useless function call, as the jQuery object is empty.

'; } else if (node.eventNotDelegated) { html += '

This binding should use Event Delegation instead of binding each element one by one.

'; } if (node.data.resultsNumber === 0) { html += '

The query returned 0 results. Could it be unused or dead code?

'; } else if (node.data.resultsNumber > 0) { html += '

The query returned ' + node.data.resultsNumber + ' ' + (node.data.resultsNumber > 1 ? 'results' : 'result') + '.

'; } if (node.data.backtrace) { html += '

Backtrace

'; html += '
'; html += getBacktraceHTML(node.data.backtrace); html += '
'; } html += '
'; } return html; } offendersDirectives.directive('profilerLine', ['$filter', function($filter) { var numberWithCommas = $filter('number'); function getProfilerLineHTML(index, node) { return '
' + (index + 1) + '
' + '
' + node.data.type + (node.children ? '
' + recursiveChildrenHTML(node) + '
' : '') + '
' + '
' + getTimelineParamsHTML(node, false) + '
' + '
' + getTimelineDetailsHTML(node) + '
' + '
' + numberWithCommas(node.data.timestamp, 0) + ' ms
'; } function recursiveChildrenHTML(node) { var html = ''; if (node.children) { node.children.forEach(function(child) { html += '
' + child.data.type + '
' + getTimelineParamsHTML(child, true) + '
' + recursiveChildrenHTML(child) + '
'; }); } return html; } function onDetailsClick(row) { // Close if it's alreay open if (row.classList.contains('showDetails')) { closeDetails(row); return; } // Close any other open details overlay var openOnes = document.getElementsByClassName('showDetails'); if (openOnes.length > 0) { openOnes[0].classList.remove('showDetails'); } // Make it appear row.classList.add('showDetails'); // Bind the close button row.querySelector('.closeBtn').addEventListener('click', function() { closeDetails(row); }); } function closeDetails(row) { row.classList.remove('showDetails'); // Unbind the close button row.querySelector('.closeBtn').removeEventListener('click', closeDetails); } return { restrict: 'E', scope: { index: '=', node: '=' }, template: '
', replace: true, link: function(scope, element) { if (scope.node.error) { element.addClass('jsError'); } else if (scope.node.windowPerformance) { element.addClass('windowPerformance'); } element.append(getProfilerLineHTML(scope.index, scope.node)); element[0].id = 'line_' + scope.index; if (scope.node.warning) { element[0].classList.add('warning'); if (scope.node.queryWithoutResults) { element[0].classList.add('queryWithoutResults'); } if (scope.node.jQueryCallOnEmptyObject) { element[0].classList.add('jQueryCallOnEmptyObject'); } if (scope.node.eventNotDelegated) { element[0].classList.add('eventNotDelegated'); } } // Bind click on the details icon var detailsIcon = element[0].querySelector('.details div'); if (detailsIcon) { detailsIcon.addEventListener('click', function() { onDetailsClick(this.parentNode.parentNode); }); } } }; }]); function shortenUrl(url, maxLength) { if (!maxLength) { maxLength = 110; } // Why dividing by 2.1? Because it adds a 5% margin. var leftLength = Math.floor((maxLength - 5) / 2.1); var rightLength = Math.ceil((maxLength - 5) / 2.1); return (url.length > maxLength) ? url.substr(0, leftLength) + ' ... ' + url.substr(-rightLength) : url; } offendersDirectives.filter('shortenUrl', function() { return shortenUrl; }); function getUrlLink(url, maxLength) { return '' + shortenUrl(url, maxLength) + ''; } offendersDirectives.directive('urlLink', function() { return { restrict: 'E', scope: { url: '=', maxLength: '=' }, template: '{{url | shortenUrl:maxLength}}', replace: true }; }); offendersDirectives.filter('encodeURIComponent', function() { return window.encodeURIComponent; }); offendersDirectives.directive('fileAndLine', function() { return { restrict: 'E', scope: { file: '=', line: '=', column: '=' }, template: '<inline CSS> @ {{line}}:{{column}}', replace: true }; }); offendersDirectives.directive('fileAndLineButton', function() { return { restrict: 'E', scope: { file: '=', line: '=', column: '=' }, template: '
css file
', replace: true }; }); offendersDirectives.filter('bytes', function() { return function(bytes) { if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) { return '-'; } var kilo = bytes / 1024; if (kilo < 1) { return bytes + ' bytes'; } if (kilo < 100) { return kilo.toFixed(1) + ' KB'; } if (kilo < 1024) { return kilo.toFixed(0) + ' KB'; } var mega = kilo / 1024; if (mega < 10) { return mega.toFixed(2) + ' MB'; } return mega.toFixed(1) + ' MB'; }; }); })();