diff --git a/app/assets/javascripts/discourse/helpers/application_helpers.js b/app/assets/javascripts/discourse/helpers/application_helpers.js index 8cf2d37f5..db53759df 100644 --- a/app/assets/javascripts/discourse/helpers/application_helpers.js +++ b/app/assets/javascripts/discourse/helpers/application_helpers.js @@ -291,18 +291,14 @@ Handlebars.registerHelper('number', function(property, options) { title = I18n.t(options.hash.numberKey, { number: orig }); } - // Round off the thousands to one decimal place - var n = orig; - if (orig > 999 && !options.hash.noTitle) { - n = (orig / 1000).toFixed(1) + "K"; - } - var classNames = 'number'; if (options.hash['class']) { classNames += ' ' + Ember.Handlebars.get(this, options.hash['class'], options); } var result = " max) { - fn.memoizeLength = 0; - fn.memoize = {}; - } - var result = fn.apply(this, args); - fn.memoize[hash] = result; - return result; - } - }; - }; - - breakUp = function(str, hint){ - var rval = []; - var prev = str[0]; - var cur; - var brk = "​"; - - var hintPos = []; - if(hint) { - hint = hint.toLowerCase().split(/\s+/).reverse(); - var current = 0; - while(hint.length > 0) { - var word = hint.pop(); - if(word !== str.substr(current, word.length).toLowerCase()) { - break; - } - current += word.length; - hintPos.push(current); - } - } - - rval.push(prev); - for (var i=1;i1 && prev.match(/[A-Z]/) && cur.match(/[a-z]/)){ - rval.pop(); - rval.push(brk); - rval.push(prev); - } else if(prev.match(/[^A-Za-z0-9]/) && cur.match(/[a-zA-Z0-9]/)){ - rval.push(brk); - } else if(hintPos.indexOf(i) > -1) { - rval.push(brk); - } - - rval.push(cur); - prev = cur; - } - - return rval.join(""); - - }; - - breakUp = cappedMemoize(breakUp, 100); - - shortDate = function(date){ - return moment(date).format(I18n.t("dates.medium.date_year")); - }; - - shortDateNoYear = function(date) { - return moment(date).format(I18n.t("dates.tiny.date_month")); - }; - - tinyDateYear = function(date) { - return moment(date).format(I18n.t("dates.tiny.date_year")); - }; - - // http://stackoverflow.com/questions/196972/convert-string-to-title-case-with-javascript - // TODO: locale support ? - toTitleCase = function toTitleCase(str) { - return str.replace(/\w\S*/g, function(txt){ - return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); - }); - }; - - longDate = function(dt) { - if (!dt) return; - return moment(dt).longDate(); - }; - - updateRelativeAge = function(elems) { - // jQuery .each - elems.each(function(){ - var $this = $(this); - $this.html(relativeAge(new Date($this.data('time')), {format: $this.data('format'), wrapInSpan: false})); - }); - }; - - autoUpdatingRelativeAge = function(date,options) { - - if (!date) return ""; - - options = options || {}; - var format = options.format || "tiny"; - - var append = ""; - - if(format === 'medium') { - append = " date"; - if(options.leaveAgo) { - format = 'medium-with-ago'; - } - options.wrapInSpan = false; - } - - var relAge = relativeAge(date, options); - - if (format === 'tiny' && relativeAgeTinyShowsYear(relAge)) { - append += " with-year"; - } - - if (options.title) { - append += "' title='" + longDate(date); - } - - return "" + relAge + ""; - }; - - - relativeAgeTiny = function(date){ - var format = "tiny"; - var distance = Math.round((new Date() - date) / 1000); - var distanceInMinutes = Math.round(distance / 60.0); - - var formatted; - var t = function(key,opts){ - return I18n.t("dates." + format + "." + key, opts); - }; - - switch(true){ - - case(distanceInMinutes < 1): - formatted = t("less_than_x_minutes", {count: 1}); - break; - case(distanceInMinutes >= 1 && distanceInMinutes <= 44): - formatted = t("x_minutes", {count: distanceInMinutes}); - break; - case(distanceInMinutes >= 45 && distanceInMinutes <= 89): - formatted = t("about_x_hours", {count: 1}); - break; - case(distanceInMinutes >= 90 && distanceInMinutes <= 1439): - formatted = t("about_x_hours", {count: Math.round(distanceInMinutes / 60.0)}); - break; - case(Discourse.SiteSettings.relative_date_duration === 0 && distanceInMinutes <= 525599): - formatted = shortDateNoYear(date); - break; - case(distanceInMinutes >= 1440 && distanceInMinutes <= 2519): - formatted = t("x_days", {count: 1}); - break; - case(distanceInMinutes >= 2520 && distanceInMinutes <= ((Discourse.SiteSettings.relative_date_duration||14) * 1440)): - formatted = t("x_days", {count: Math.round(distanceInMinutes / 1440.0)}); - break; - default: - if(date.getFullYear() === new Date().getFullYear()) { - formatted = shortDateNoYear(date); - } else { - formatted = tinyDateYear(date); - } - break; - } - - return formatted; - }; +var updateRelativeAge, autoUpdatingRelativeAge, relativeAge, relativeAgeTiny, + relativeAgeMedium, relativeAgeMediumSpan, longDate, toTitleCase, + shortDate, shortDateNoYear, tinyDateYear, breakUp, relativeAgeTinyShowsYear; /* - * Returns true if the given tiny date string includes the year. - * Useful for checking if the string isn't so tiny. - */ - relativeAgeTinyShowsYear = function(relativeAgeString) { - return relativeAgeString.match(/'[\d]{2}$/); - }; +* memoize.js +* by @philogb and @addyosmani +* with further optimizations by @mathias +* and @DmitryBaranovsk +* perf tests: http://bit.ly/q3zpG3 +* Released under an MIT license. +* +* modified with cap by Sam +*/ +var cappedMemoize = function ( fn, max ) { + fn.maxMemoize = max; + fn.memoizeLength = 0; - relativeAgeMediumSpan = function(distance, leaveAgo) { - var formatted, distanceInMinutes; - - distanceInMinutes = Math.round(distance / 60.0); - - var t = function(key, opts){ - return I18n.t("dates.medium" + (leaveAgo?"_with_ago":"") + "." + key, opts); + return function () { + var args = Array.prototype.slice.call(arguments), + hash = "", + i = args.length; + var currentArg = null; + while (i--) { + currentArg = args[i]; + hash += (currentArg === new Object(currentArg)) ? + JSON.stringify(currentArg) : currentArg; + if(!fn.memoize) { + fn.memoize = {}; + } + } + if (hash in fn.memoize) { + return fn.memoize[hash]; + } else { + fn.memoizeLength++; + if(fn.memoizeLength > max) { + fn.memoizeLength = 0; + fn.memoize = {}; + } + var result = fn.apply(this, args); + fn.memoize[hash] = result; + return result; + } }; +}; - switch(true){ - case(distanceInMinutes >= 1 && distanceInMinutes <= 56): - formatted = t("x_minutes", {count: distanceInMinutes}); - break; - case(distanceInMinutes >= 56 && distanceInMinutes <= 89): - formatted = t("x_hours", {count: 1}); - break; - case(distanceInMinutes >= 90 && distanceInMinutes <= 1379): - formatted = t("x_hours", {count: Math.round(distanceInMinutes / 60.0)}); - break; - case(distanceInMinutes >= 1380 && distanceInMinutes <= 2159): - formatted = t("x_days", {count: 1}); - break; - case(distanceInMinutes >= 2160): - formatted = t("x_days", {count: Math.round((distanceInMinutes - 720.0) / 1440.0)}); - break; - } - return formatted || '&mdash'; - }; +breakUp = function(str, hint){ + var rval = []; + var prev = str[0]; + var cur; + var brk = "​"; - relativeAgeMedium = function(date, options){ - var displayDate, fiveDaysAgo, oneMinuteAgo, fullReadable, leaveAgo; - var wrapInSpan = options.wrapInSpan === false ? false : true; - - leaveAgo = options.leaveAgo; - var distance = Math.round((new Date() - date) / 1000); - - if (!date) { - return "—"; - } - - fullReadable = longDate(date); - displayDate = ""; - fiveDaysAgo = 432000; - oneMinuteAgo = 60; - - if (distance < oneMinuteAgo) { - displayDate = I18n.t("now"); - } else if (distance > fiveDaysAgo) { - if ((new Date()).getFullYear() !== date.getFullYear()) { - displayDate = shortDate(date); - } else { - displayDate = shortDateNoYear(date); + var hintPos = []; + if(hint) { + hint = hint.toLowerCase().split(/\s+/).reverse(); + var current = 0; + while(hint.length > 0) { + var word = hint.pop(); + if(word !== str.substr(current, word.length).toLowerCase()) { + break; } + current += word.length; + hintPos.push(current); + } + } + + rval.push(prev); + for (var i=1;i1 && prev.match(/[A-Z]/) && cur.match(/[a-z]/)){ + rval.pop(); + rval.push(brk); + rval.push(prev); + } else if(prev.match(/[^A-Za-z0-9]/) && cur.match(/[a-zA-Z0-9]/)){ + rval.push(brk); + } else if(hintPos.indexOf(i) > -1) { + rval.push(brk); + } + + rval.push(cur); + prev = cur; + } + + return rval.join(""); + +}; + +breakUp = cappedMemoize(breakUp, 100); + +shortDate = function(date){ + return moment(date).format(I18n.t("dates.medium.date_year")); +}; + +shortDateNoYear = function(date) { + return moment(date).format(I18n.t("dates.tiny.date_month")); +}; + +tinyDateYear = function(date) { + return moment(date).format(I18n.t("dates.tiny.date_year")); +}; + +// http://stackoverflow.com/questions/196972/convert-string-to-title-case-with-javascript +// TODO: locale support ? +toTitleCase = function toTitleCase(str) { + return str.replace(/\w\S*/g, function(txt){ + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); +}; + +longDate = function(dt) { + if (!dt) return; + return moment(dt).longDate(); +}; + +updateRelativeAge = function(elems) { + // jQuery .each + elems.each(function(){ + var $this = $(this); + $this.html(relativeAge(new Date($this.data('time')), {format: $this.data('format'), wrapInSpan: false})); + }); +}; + +autoUpdatingRelativeAge = function(date,options) { + + if (!date) return ""; + + options = options || {}; + var format = options.format || "tiny"; + + var append = ""; + + if(format === 'medium') { + append = " date"; + if(options.leaveAgo) { + format = 'medium-with-ago'; + } + options.wrapInSpan = false; + } + + var relAge = relativeAge(date, options); + + if (format === 'tiny' && relativeAgeTinyShowsYear(relAge)) { + append += " with-year"; + } + + if (options.title) { + append += "' title='" + longDate(date); + } + + return "" + relAge + ""; +}; + + +relativeAgeTiny = function(date){ + var format = "tiny"; + var distance = Math.round((new Date() - date) / 1000); + var distanceInMinutes = Math.round(distance / 60.0); + + var formatted; + var t = function(key,opts){ + return I18n.t("dates." + format + "." + key, opts); + }; + + switch(true){ + + case(distanceInMinutes < 1): + formatted = t("less_than_x_minutes", {count: 1}); + break; + case(distanceInMinutes >= 1 && distanceInMinutes <= 44): + formatted = t("x_minutes", {count: distanceInMinutes}); + break; + case(distanceInMinutes >= 45 && distanceInMinutes <= 89): + formatted = t("about_x_hours", {count: 1}); + break; + case(distanceInMinutes >= 90 && distanceInMinutes <= 1439): + formatted = t("about_x_hours", {count: Math.round(distanceInMinutes / 60.0)}); + break; + case(Discourse.SiteSettings.relative_date_duration === 0 && distanceInMinutes <= 525599): + formatted = shortDateNoYear(date); + break; + case(distanceInMinutes >= 1440 && distanceInMinutes <= 2519): + formatted = t("x_days", {count: 1}); + break; + case(distanceInMinutes >= 2520 && distanceInMinutes <= ((Discourse.SiteSettings.relative_date_duration||14) * 1440)): + formatted = t("x_days", {count: Math.round(distanceInMinutes / 1440.0)}); + break; + default: + if(date.getFullYear() === new Date().getFullYear()) { + formatted = shortDateNoYear(date); } else { - displayDate = relativeAgeMediumSpan(distance, leaveAgo); + formatted = tinyDateYear(date); } - if(wrapInSpan) { - return "" + displayDate + ""; + break; + } + + return formatted; +}; + +/* + * Returns true if the given tiny date string includes the year. + * Useful for checking if the string isn't so tiny. + */ +relativeAgeTinyShowsYear = function(relativeAgeString) { + return relativeAgeString.match(/'[\d]{2}$/); +}; + +relativeAgeMediumSpan = function(distance, leaveAgo) { + var formatted, distanceInMinutes; + + distanceInMinutes = Math.round(distance / 60.0); + + var t = function(key, opts){ + return I18n.t("dates.medium" + (leaveAgo?"_with_ago":"") + "." + key, opts); + }; + + switch(true){ + case(distanceInMinutes >= 1 && distanceInMinutes <= 56): + formatted = t("x_minutes", {count: distanceInMinutes}); + break; + case(distanceInMinutes >= 56 && distanceInMinutes <= 89): + formatted = t("x_hours", {count: 1}); + break; + case(distanceInMinutes >= 90 && distanceInMinutes <= 1379): + formatted = t("x_hours", {count: Math.round(distanceInMinutes / 60.0)}); + break; + case(distanceInMinutes >= 1380 && distanceInMinutes <= 2159): + formatted = t("x_days", {count: 1}); + break; + case(distanceInMinutes >= 2160): + formatted = t("x_days", {count: Math.round((distanceInMinutes - 720.0) / 1440.0)}); + break; + } + return formatted || '&mdash'; +}; + +relativeAgeMedium = function(date, options){ + var displayDate, fiveDaysAgo, oneMinuteAgo, fullReadable, leaveAgo; + var wrapInSpan = options.wrapInSpan === false ? false : true; + + leaveAgo = options.leaveAgo; + var distance = Math.round((new Date() - date) / 1000); + + if (!date) { + return "—"; + } + + fullReadable = longDate(date); + displayDate = ""; + fiveDaysAgo = 432000; + oneMinuteAgo = 60; + + if (distance < oneMinuteAgo) { + displayDate = I18n.t("now"); + } else if (distance > fiveDaysAgo) { + if ((new Date()).getFullYear() !== date.getFullYear()) { + displayDate = shortDate(date); } else { - return displayDate; + displayDate = shortDateNoYear(date); } - }; + } else { + displayDate = relativeAgeMediumSpan(distance, leaveAgo); + } + if(wrapInSpan) { + return "" + displayDate + ""; + } else { + return displayDate; + } +}; - // mostly lifted from rails with a few amendments - relativeAge = function(date, options) { - options = options || {}; - var format = options.format || "tiny"; +// mostly lifted from rails with a few amendments +relativeAge = function(date, options) { + options = options || {}; + var format = options.format || "tiny"; - if(format === "tiny") { - return relativeAgeTiny(date, options); - } else if (format === "medium") { - return relativeAgeMedium(date, options); - } else if (format === 'medium-with-ago') { - return relativeAgeMedium(date, _.extend(options, {format: 'medium', leaveAgo: true})); - } + if(format === "tiny") { + return relativeAgeTiny(date, options); + } else if (format === "medium") { + return relativeAgeMedium(date, options); + } else if (format === 'medium-with-ago') { + return relativeAgeMedium(date, _.extend(options, {format: 'medium', leaveAgo: true})); + } - return "UNKNOWN FORMAT"; - }; + return "UNKNOWN FORMAT"; +}; - return { - longDate: longDate, - relativeAge: relativeAge, - autoUpdatingRelativeAge: autoUpdatingRelativeAge, - updateRelativeAge: updateRelativeAge, - toTitleCase: toTitleCase, - shortDate: shortDate, - breakUp: breakUp, - cappedMemoize: cappedMemoize - }; -})(); +var number = function(val) { + val = parseInt(val, 10); + if (isNaN(val)) val = 0; + + if (val > 999) { + return (val / 1000).toFixed(1) + "K"; + } + return val.toString(); +}; + +Discourse.Formatter = { + longDate: longDate, + relativeAge: relativeAge, + autoUpdatingRelativeAge: autoUpdatingRelativeAge, + updateRelativeAge: updateRelativeAge, + toTitleCase: toTitleCase, + shortDate: shortDate, + breakUp: breakUp, + cappedMemoize: cappedMemoize, + number: number +}; diff --git a/app/assets/javascripts/discourse/views/post_view.js b/app/assets/javascripts/discourse/views/post_view.js index a280530b4..4db634c57 100644 --- a/app/assets/javascripts/discourse/views/post_view.js +++ b/app/assets/javascripts/discourse/views/post_view.js @@ -123,24 +123,23 @@ Discourse.PostView = Discourse.GroupedView.extend(Ember.Evented, { var self = this, link_counts = this.get('post.link_counts'); - if (link_counts) { - _.each(link_counts, function(lc) { - if (lc.clicks > 0) { - self.$(".cooked a[href]").each(function() { - var link = $(this); - if (link.attr('href') === lc.url) { - // don't display badge counts on category badge - if (link.closest('.badge-category').length === 0) { - // nor in oneboxes (except when we force it) - if (link.closest(".onebox-result").length === 0 || link.hasClass("track-link")) { - link.append("" + lc.clicks + ""); - } - } - } - }); + if (!link_counts) return; + + link_counts.forEach(function(lc) { + if (!lc.clicks || lc.clicks < 1) return; + + self.$(".cooked a[href]").each(function() { + var link = $(this); + if (link.attr('href') === lc.url) { + // don't display badge counts on category badge + if (link.closest('.badge-category').length === 0 && (link.closest(".onebox-result").length === 0 || link.hasClass("track-link"))) { + link.append("" + Discourse.Formatter.number(lc.clicks) + ""); + } } }); - } + }); }, actions: { diff --git a/test/javascripts/lib/formatter_test.js b/test/javascripts/lib/formatter_test.js index 5c93bc04e..c7e872364 100644 --- a/test/javascripts/lib/formatter_test.js +++ b/test/javascripts/lib/formatter_test.js @@ -199,3 +199,10 @@ test("breakUp", function(){ equal(b("bobmarleytoo","Bob Marley Too"), "bob​marley​too"); }); + +test("number", function() { + equal(Discourse.Formatter.number(123), "123", "it returns a string version of the number"); + equal(Discourse.Formatter.number("123"), "123", "it works with a string command"); + equal(Discourse.Formatter.number(NaN), "0", "it reeturns 0 for NaN"); + equal(Discourse.Formatter.number(3333), "3.3K", "it abbreviates thousands"); +});