From c6130e002e482be4aaefb0f762b62c9957c152af Mon Sep 17 00:00:00 2001 From: Fergal Moran Date: Thu, 7 Feb 2013 19:03:09 +0000 Subject: [PATCH] Added activity to main feed --- dss/settings.py | 2 + spa/api/v1/ActivityResource.py | 26 +- spa/api/v1/BackboneCompatibleResource.py | 13 +- spa/api/v1/CommentResource.py | 11 +- spa/api/v1/MixResource.py | 2 +- spa/models/MixPlay.py | 6 + spa/models/_Activity.py | 8 + static/css/deepsouthsounds.css | 8 + static/js/app/views/sidebar.js | 2 +- static/js/com.podnoms.utils.js | 34 +- static/js/libs/globalize.js | 1609 +++++++++++++++++++++ static/js/old__dss_sound_handler.js | 135 -- templates/base.html | 1 + templates/views/ActivityListItemView.html | 34 +- templates/views/SidebarView.html | 2 +- 15 files changed, 1701 insertions(+), 192 deletions(-) create mode 100644 static/js/libs/globalize.js delete mode 100644 static/js/old__dss_sound_handler.js diff --git a/dss/settings.py b/dss/settings.py index c262d41..f3111f2 100644 --- a/dss/settings.py +++ b/dss/settings.py @@ -228,6 +228,8 @@ PIPELINE_CSS = { } INTERNAL_IPS = ('127.0.0.1', '86.44.166.21') GOOGLE_ANALYTICS_CODE = localsettings.GOOGLE_ANALYTICS_CODE +#TASTYPIE_DATETIME_FORMATTING = 'iso-8601' +TASTYPIE_DATETIME_FORMATTING = 'rfc-2822' SENDFILE_BACKEND = localsettings.SENDFILE_BACKEND SENDFILE_ROOT = os.path.join(MEDIA_ROOT, 'mixes') diff --git a/spa/api/v1/ActivityResource.py b/spa/api/v1/ActivityResource.py index c82d329..949908f 100644 --- a/spa/api/v1/ActivityResource.py +++ b/spa/api/v1/ActivityResource.py @@ -5,9 +5,9 @@ from spa.models._Activity import _Activity class ActivityResource(BackboneCompatibleResource): - class Meta: - queryset = _Activity.objects.all() + #queryset = _Activity.objects.filter(pk=3442).order_by('-date') + queryset = _Activity.objects.all().order_by('-date') resource_name = 'activity' authorization = Authorization() authentication = Authentication() @@ -18,13 +18,14 @@ class ActivityResource(BackboneCompatibleResource): def dehydrate(self, bundle): try: - if bundle.obj.user is not None: - bundle.data["message"] = "%s %s a %s on %s" %\ - (bundle.obj.user.get_full_name(), - bundle.obj.get_verb_passed(), - bundle.obj.get_object_singular(), - bundle.obj.date) - return bundle + bundle.data["verb"] = bundle.obj.get_verb_passed(), + bundle.data["object"] = bundle.obj.get_object_singular(), + bundle.data["item_name"] = bundle.obj.get_object_name(), + bundle.data["item_url"] = bundle.obj.get_object_url(), + bundle.data["user_name"] = bundle.obj.user.get_full_name(), + bundle.data["user_profile"] = bundle.obj.user.get_profile().get_profile_url(), + bundle.data["user_image"] = bundle.obj.user.get_profile().get_small_profile_image() + return bundle except AttributeError, ae: self.logger.debug("AttributeError: Error dehydrating activity, %s" % ae.message) @@ -32,3 +33,10 @@ class ActivityResource(BackboneCompatibleResource): self.logger.debug("TypeError: Error dehydrating activity, %s" % te.message) except Exception, ee: self.logger.debug("Exception: Error dehydrating activity, %s" % ee.message) + return None + + def alter_list_data_to_serialize(self, request, data): + return [i for i in data['objects'] if i is not None and i.obj.user is not None and i.obj.get_object_name is not None and i.obj.get_object_url is not None] + + def dehydrate_date(self, bundle): + return self.humanize_date(bundle.obj.date) diff --git a/spa/api/v1/BackboneCompatibleResource.py b/spa/api/v1/BackboneCompatibleResource.py index d7c2053..29680eb 100644 --- a/spa/api/v1/BackboneCompatibleResource.py +++ b/spa/api/v1/BackboneCompatibleResource.py @@ -1,12 +1,15 @@ import logging -from django.conf.urls import url -from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned -from tastypie import fields -from tastypie.http import HttpGone, HttpMultipleChoices +import datetime +import humanize from tastypie.resources import ModelResource -from tastypie.utils import trailing_slash class BackboneCompatibleResource(ModelResource): logger = logging.getLogger(__name__) pass + + def humanize_date(self, date): + if (datetime.datetime.now() - date) <= datetime.timedelta(days=1): + return humanize.naturaltime(date) + else: + return humanize.naturalday(date) \ No newline at end of file diff --git a/spa/api/v1/CommentResource.py b/spa/api/v1/CommentResource.py index f493425..198b7a7 100644 --- a/spa/api/v1/CommentResource.py +++ b/spa/api/v1/CommentResource.py @@ -1,13 +1,11 @@ -import datetime -import humanize from tastypie import fields from tastypie.authentication import Authentication from tastypie.authorization import Authorization from spa.api.v1.BackboneCompatibleResource import BackboneCompatibleResource from spa.models.Comment import Comment -from tastypie.resources import ModelResource -class CommentResource(ModelResource): + +class CommentResource(BackboneCompatibleResource): mix = fields.ToOneField('spa.api.v1.MixResource.MixResource', 'mix') class Meta: @@ -25,10 +23,7 @@ class CommentResource(ModelResource): return super(CommentResource, self).obj_create(bundle, request, user=request.user) def dehydrate_date_created(self, bundle): - if (datetime.datetime.now() - bundle.obj.date_created) <= datetime.timedelta(days=1): - return humanize.naturaltime(bundle.obj.date_created) - else: - return humanize.naturalday(bundle.obj.date_created) + return self.humanize_date(bundle.obj.date_created) def dehydrate(self, bundle): bundle.data['avatar_image'] = bundle.obj.user.get_profile().get_small_profile_image() diff --git a/spa/api/v1/MixResource.py b/spa/api/v1/MixResource.py index 8057fdd..a322242 100644 --- a/spa/api/v1/MixResource.py +++ b/spa/api/v1/MixResource.py @@ -17,7 +17,7 @@ from spa.models.Mix import Mix class MixResource(BackboneCompatibleResource): comments = fields.ToManyField('spa.api.v1.CommentResource.CommentResource', 'comments') - #activity = fields.ToManyField('spa.api.v1.ActivityResource.ActivityResource', 'activity', 'mix', null=True) + #downloads = fields.ToManyField('spa.api.v1.ActivityResource.ActivityResource', 'downloads') class Meta: queryset = Mix.objects.filter(is_active=True) diff --git a/spa/models/MixPlay.py b/spa/models/MixPlay.py index 889f880..906d287 100644 --- a/spa/models/MixPlay.py +++ b/spa/models/MixPlay.py @@ -9,3 +9,9 @@ class MixPlay(_Activity): def get_object_singular(self): return "mix" + + def get_object_name(self): + return self.mix.title + + def get_object_url(self): + return self.mix.get_absolute_url() \ No newline at end of file diff --git a/spa/models/_Activity.py b/spa/models/_Activity.py index 1a86355..3e694d4 100644 --- a/spa/models/_Activity.py +++ b/spa/models/_Activity.py @@ -26,3 +26,11 @@ class _Activity(_BaseModel): @abc.abstractmethod def get_object_plural(self): return + + @abc.abstractmethod + def get_object_name(self): + return + + @abc.abstractmethod + def get_object_url(self): + return diff --git a/static/css/deepsouthsounds.css b/static/css/deepsouthsounds.css index 10fd282..13ce065 100644 --- a/static/css/deepsouthsounds.css +++ b/static/css/deepsouthsounds.css @@ -38,6 +38,9 @@ img.event-content { .bordered { border-bottom: 3px solid #CEC3B3; } +.bordered-faint { + border-bottom: 1px solid #CEC3B3; +} .page-header { -moz-border-bottom-colors: none; @@ -583,4 +586,9 @@ div.event-content td { .mix-profile-insert { +} + +.activity-list-item{ + margin-bottom: 5px; + margin-left: 5px; } \ No newline at end of file diff --git a/static/js/app/views/sidebar.js b/static/js/app/views/sidebar.js index 67ef98c..01acd11 100644 --- a/static/js/app/views/sidebar.js +++ b/static/js/app/views/sidebar.js @@ -34,7 +34,7 @@ window.SidebarView = Backbone.View.extend({ var content = new ActivityListView({ collection: activity }).el; - $('.sidebar-content-activity', this.el).html(content.el); + $('#sidebar-content-activity', this.el).html(content); } }); return this; diff --git a/static/js/com.podnoms.utils.js b/static/js/com.podnoms.utils.js index c2bc436..f4b83cc 100644 --- a/static/js/com.podnoms.utils.js +++ b/static/js/com.podnoms.utils.js @@ -12,7 +12,7 @@ if (!com.podnoms) com.podnoms = {}; com.podnoms.utils = { // Asynchronously load templates located in separate .html files - loadTemplate:function (views, callback) { + loadTemplate: function (views, callback) { var deferreds = []; $.each(views, function (index, view) { if (window[view]) { @@ -25,11 +25,11 @@ com.podnoms.utils = { }); $.when.apply(null, deferreds).done(callback); }, - trackPageView:function (url) { + trackPageView: function (url) { if (!(typeof(_gag) == "undefined")) _gaq.push(['_trackPageview', "/" + url]); }, - displayValidationErrors:function (messages) { + displayValidationErrors: function (messages) { for (var key in messages) { if (messages.hasOwnProperty(key)) { this.addValidationError(key, messages[key]); @@ -37,20 +37,20 @@ com.podnoms.utils = { } this.showAlert('Warning!', 'Fix validation errors and try again', 'alert-warning'); }, - addValidationError:function (field, message) { + addValidationError: function (field, message) { var controlGroup = $('#' + field).parent().parent(); controlGroup.addClass('error'); $('.help-inline', controlGroup).html(message); }, - removeValidationError:function (field) { + removeValidationError: function (field) { var controlGroup = $('#' + field).parent().parent(); controlGroup.removeClass('error'); $('.help-inline', controlGroup).html(''); }, - showError:function (title, message) { + showError: function (title, message) { this.showAlert(title, message, 'alert-error', true); }, - showAlert:function (title, text, klass, fade) { + showAlert: function (title, text, klass, fade) { $('.alert').removeClass("alert-error alert-warning alert-success alert-info"); $('.alert').addClass(klass); $('.alert').html('' + title + ' ' + text); @@ -63,33 +63,37 @@ com.podnoms.utils = { this.hideAlert(); }); }, - hideAlert:function () { + hideAlert: function () { $('.alert').fadeOut('slow', function () { }); }, - pad2:function (number) { + pad2: function (number) { return (number < 10 ? '0' : '') + number; }, - getDateAsToday:function () { + getDateAsToday: function () { var currentTime = new Date(); var day = currentTime.getDate(); var month = currentTime.getMonth() + 1; var year = currentTime.getFullYear(); return (com.podnoms.utils.pad2(day) + "/" + com.podnoms.utils.pad2(month) + "/" + year); }, - isEmpty:function (val) { + formatJSONDate: function (jsonDate) { + var date = new Date(parseInt(jsonDate.substr(6))); + return date; + }, + isEmpty: function (val) { return (val === undefined || val == null || val.length <= 0) ? true : false; }, - setHashbangHeader:function (xhr) { + setHashbangHeader: function (xhr) { xhr.setRequestHeader('X-FB-Nonsense', 'Argle-Bargle'); }, - generateGuid:function () { + generateGuid: function () { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }, - downloadURL:function downloadURL(url) { + downloadURL: function downloadURL(url) { var iframe; iframe = document.getElementById("hiddenDownloader"); if (iframe === null) { @@ -103,7 +107,7 @@ com.podnoms.utils = { }; jQuery.extend({ - handleError:function (s, xhr, status, e) { + handleError: function (s, xhr, status, e) { // If a local callback was specified, fire it if (s.error) { s.error.call(s.context || window, xhr, status, e); diff --git a/static/js/libs/globalize.js b/static/js/libs/globalize.js new file mode 100644 index 0000000..48a9edd --- /dev/null +++ b/static/js/libs/globalize.js @@ -0,0 +1,1609 @@ +/*! + * Globalize + * + * http://github.com/jquery/globalize + * + * Copyright Software Freedom Conservancy, Inc. + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + */ + +(function (window, undefined) { + + var Globalize, + // private variables + regexHex, + regexInfinity, + regexParseFloat, + regexTrim, + // private JavaScript utility functions + arrayIndexOf, + endsWith, + extend, + isArray, + isFunction, + isObject, + startsWith, + trim, + truncate, + zeroPad, + // private Globalization utility functions + appendPreOrPostMatch, + expandFormat, + formatDate, + formatNumber, + getTokenRegExp, + getEra, + getEraYear, + parseExact, + parseNegativePattern; + +// Global variable (Globalize) or CommonJS module (globalize) + Globalize = function (cultureSelector) { + return new Globalize.prototype.init(cultureSelector); + }; + + if (typeof require !== "undefined" && + typeof exports !== "undefined" && + typeof module !== "undefined") { + // Assume CommonJS + module.exports = Globalize; + } else { + // Export as global variable + window.Globalize = Globalize; + } + + Globalize.cultures = {}; + + Globalize.prototype = { + constructor: Globalize, + init: function (cultureSelector) { + this.cultures = Globalize.cultures; + this.cultureSelector = cultureSelector; + + return this; + } + }; + Globalize.prototype.init.prototype = Globalize.prototype; + +// 1. When defining a culture, all fields are required except the ones stated as optional. +// 2. Each culture should have a ".calendars" object with at least one calendar named "standard" +// which serves as the default calendar in use by that culture. +// 3. Each culture should have a ".calendar" object which is the current calendar being used, +// it may be dynamically changed at any time to one of the calendars in ".calendars". + Globalize.cultures[ "default" ] = { + // A unique name for the culture in the form - + name: "en", + // the name of the culture in the english language + englishName: "English", + // the name of the culture in its own language + nativeName: "English", + // whether the culture uses right-to-left text + isRTL: false, + // "language" is used for so-called "specific" cultures. + // For example, the culture "es-CL" means "Spanish, in Chili". + // It represents the Spanish-speaking culture as it is in Chili, + // which might have different formatting rules or even translations + // than Spanish in Spain. A "neutral" culture is one that is not + // specific to a region. For example, the culture "es" is the generic + // Spanish culture, which may be a more generalized version of the language + // that may or may not be what a specific culture expects. + // For a specific culture like "es-CL", the "language" field refers to the + // neutral, generic culture information for the language it is using. + // This is not always a simple matter of the string before the dash. + // For example, the "zh-Hans" culture is netural (Simplified Chinese). + // And the "zh-SG" culture is Simplified Chinese in Singapore, whose lanugage + // field is "zh-CHS", not "zh". + // This field should be used to navigate from a specific culture to it's + // more general, neutral culture. If a culture is already as general as it + // can get, the language may refer to itself. + language: "en", + // numberFormat defines general number formatting rules, like the digits in + // each grouping, the group separator, and how negative numbers are displayed. + numberFormat: { + // [negativePattern] + // Note, numberFormat.pattern has no "positivePattern" unlike percent and currency, + // but is still defined as an array for consistency with them. + // negativePattern: one of "(n)|-n|- n|n-|n -" + pattern: [ "-n" ], + // number of decimal places normally shown + decimals: 2, + // string that separates number groups, as in 1,000,000 + ",": ",", + // string that separates a number from the fractional portion, as in 1.99 + ".": ".", + // array of numbers indicating the size of each number group. + // TODO: more detailed description and example + groupSizes: [ 3 ], + // symbol used for positive numbers + "+": "+", + // symbol used for negative numbers + "-": "-", + // symbol used for NaN (Not-A-Number) + "NaN": "NaN", + // symbol used for Negative Infinity + negativeInfinity: "-Infinity", + // symbol used for Positive Infinity + positiveInfinity: "Infinity", + percent: { + // [negativePattern, positivePattern] + // negativePattern: one of "-n %|-n%|-%n|%-n|%n-|n-%|n%-|-% n|n %-|% n-|% -n|n- %" + // positivePattern: one of "n %|n%|%n|% n" + pattern: [ "-n %", "n %" ], + // number of decimal places normally shown + decimals: 2, + // array of numbers indicating the size of each number group. + // TODO: more detailed description and example + groupSizes: [ 3 ], + // string that separates number groups, as in 1,000,000 + ",": ",", + // string that separates a number from the fractional portion, as in 1.99 + ".": ".", + // symbol used to represent a percentage + symbol: "%" + }, + currency: { + // [negativePattern, positivePattern] + // negativePattern: one of "($n)|-$n|$-n|$n-|(n$)|-n$|n-$|n$-|-n $|-$ n|n $-|$ n-|$ -n|n- $|($ n)|(n $)" + // positivePattern: one of "$n|n$|$ n|n $" + pattern: [ "($n)", "$n" ], + // number of decimal places normally shown + decimals: 2, + // array of numbers indicating the size of each number group. + // TODO: more detailed description and example + groupSizes: [ 3 ], + // string that separates number groups, as in 1,000,000 + ",": ",", + // string that separates a number from the fractional portion, as in 1.99 + ".": ".", + // symbol used to represent currency + symbol: "$" + } + }, + // calendars defines all the possible calendars used by this culture. + // There should be at least one defined with name "standard", and is the default + // calendar used by the culture. + // A calendar contains information about how dates are formatted, information about + // the calendar's eras, a standard set of the date formats, + // translations for day and month names, and if the calendar is not based on the Gregorian + // calendar, conversion functions to and from the Gregorian calendar. + calendars: { + standard: { + // name that identifies the type of calendar this is + name: "Gregorian_USEnglish", + // separator of parts of a date (e.g. "/" in 11/05/1955) + "/": "/", + // separator of parts of a time (e.g. ":" in 05:44 PM) + ":": ":", + // the first day of the week (0 = Sunday, 1 = Monday, etc) + firstDay: 0, + days: { + // full day names + names: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], + // abbreviated day names + namesAbbr: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], + // shortest day names + namesShort: [ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" ] + }, + months: { + // full month names (13 months for lunar calendards -- 13th month should be "" if not lunar) + names: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" ], + // abbreviated month names + namesAbbr: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" ] + }, + // AM and PM designators in one of these forms: + // The usual view, and the upper and lower case versions + // [ standard, lowercase, uppercase ] + // The culture does not use AM or PM (likely all standard date formats use 24 hour time) + // null + AM: [ "AM", "am", "AM" ], + PM: [ "PM", "pm", "PM" ], + eras: [ + // eras in reverse chronological order. + // name: the name of the era in this culture (e.g. A.D., C.E.) + // start: when the era starts in ticks (gregorian, gmt), null if it is the earliest supported era. + // offset: offset in years from gregorian calendar + { + "name": "A.D.", + "start": null, + "offset": 0 + } + ], + // when a two digit year is given, it will never be parsed as a four digit + // year greater than this year (in the appropriate era for the culture) + // Set it as a full year (e.g. 2029) or use an offset format starting from + // the current year: "+19" would correspond to 2029 if the current year 2010. + twoDigitYearMax: 2029, + // set of predefined date and time patterns used by the culture + // these represent the format someone in this culture would expect + // to see given the portions of the date that are shown. + patterns: { + // short date pattern + d: "M/d/yyyy", + // long date pattern + D: "dddd, MMMM dd, yyyy", + // short time pattern + t: "h:mm tt", + // long time pattern + T: "h:mm:ss tt", + // long date, short time pattern + f: "dddd, MMMM dd, yyyy h:mm tt", + // long date, long time pattern + F: "dddd, MMMM dd, yyyy h:mm:ss tt", + // month/day pattern + M: "MMMM dd", + // month/year pattern + Y: "yyyy MMMM", + // S is a sortable format that does not vary by culture + S: "yyyy\u0027-\u0027MM\u0027-\u0027dd\u0027T\u0027HH\u0027:\u0027mm\u0027:\u0027ss" + } + // optional fields for each calendar: + /* + monthsGenitive: + Same as months but used when the day preceeds the month. + Omit if the culture has no genitive distinction in month names. + For an explaination of genitive months, see http://blogs.msdn.com/michkap/archive/2004/12/25/332259.aspx + convert: + Allows for the support of non-gregorian based calendars. This convert object is used to + to convert a date to and from a gregorian calendar date to handle parsing and formatting. + The two functions: + fromGregorian( date ) + Given the date as a parameter, return an array with parts [ year, month, day ] + corresponding to the non-gregorian based year, month, and day for the calendar. + toGregorian( year, month, day ) + Given the non-gregorian year, month, and day, return a new Date() object + set to the corresponding date in the gregorian calendar. + */ + } + }, + // For localized strings + messages: {} + }; + + Globalize.cultures[ "default" ].calendar = Globalize.cultures[ "default" ].calendars.standard; + + Globalize.cultures.en = Globalize.cultures[ "default" ]; + + Globalize.cultureSelector = "en"; + +// +// private variables +// + + regexHex = /^0x[a-f0-9]+$/i; + regexInfinity = /^[+\-]?infinity$/i; + regexParseFloat = /^[+\-]?\d*\.?\d*(e[+\-]?\d+)?$/; + regexTrim = /^\s+|\s+$/g; + +// +// private JavaScript utility functions +// + + arrayIndexOf = function (array, item) { + if (array.indexOf) { + return array.indexOf(item); + } + for (var i = 0, length = array.length; i < length; i++) { + if (array[i] === item) { + return i; + } + } + return -1; + }; + + endsWith = function (value, pattern) { + return value.substr(value.length - pattern.length) === pattern; + }; + + extend = function () { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if (typeof target === "boolean") { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if (typeof target !== "object" && !isFunction(target)) { + target = {}; + } + + for (; i < length; i++) { + // Only deal with non-null/undefined values + if ((options = arguments[ i ]) != null) { + // Extend the base object + for (name in options) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if (target === copy) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if (deep && copy && ( isObject(copy) || (copyIsArray = isArray(copy)) )) { + if (copyIsArray) { + copyIsArray = false; + clone = src && isArray(src) ? src : []; + + } else { + clone = src && isObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = extend(deep, clone, copy); + + // Don't bring in undefined values + } else if (copy !== undefined) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; + }; + + isArray = Array.isArray || function (obj) { + return Object.prototype.toString.call(obj) === "[object Array]"; + }; + + isFunction = function (obj) { + return Object.prototype.toString.call(obj) === "[object Function]"; + }; + + isObject = function (obj) { + return Object.prototype.toString.call(obj) === "[object Object]"; + }; + + startsWith = function (value, pattern) { + return value.indexOf(pattern) === 0; + }; + + trim = function (value) { + return ( value + "" ).replace(regexTrim, ""); + }; + + truncate = function (value) { + if (isNaN(value)) { + return NaN; + } + return Math[ value < 0 ? "ceil" : "floor" ](value); + }; + + zeroPad = function (str, count, left) { + var l; + for (l = str.length; l < count; l += 1) { + str = ( left ? ("0" + str) : (str + "0") ); + } + return str; + }; + +// +// private Globalization utility functions +// + + appendPreOrPostMatch = function (preMatch, strings) { + // appends pre- and post- token match strings while removing escaped characters. + // Returns a single quote count which is used to determine if the token occurs + // in a string literal. + var quoteCount = 0, + escaped = false; + for (var i = 0, il = preMatch.length; i < il; i++) { + var c = preMatch.charAt(i); + switch (c) { + case "\'": + if (escaped) { + strings.push("\'"); + } + else { + quoteCount++; + } + escaped = false; + break; + case "\\": + if (escaped) { + strings.push("\\"); + } + escaped = !escaped; + break; + default: + strings.push(c); + escaped = false; + break; + } + } + return quoteCount; + }; + + expandFormat = function (cal, format) { + // expands unspecified or single character date formats into the full pattern. + format = format || "F"; + var pattern, + patterns = cal.patterns, + len = format.length; + if (len === 1) { + pattern = patterns[ format ]; + if (!pattern) { + throw "Invalid date format string \'" + format + "\'."; + } + format = pattern; + } + else if (len === 2 && format.charAt(0) === "%") { + // %X escape format -- intended as a custom format string that is only one character, not a built-in format. + format = format.charAt(1); + } + return format; + }; + + formatDate = function (value, format, culture) { + var cal = culture.calendar, + convert = cal.convert, + ret; + + if (!format || !format.length || format === "i") { + if (culture && culture.name.length) { + if (convert) { + // non-gregorian calendar, so we cannot use built-in toLocaleString() + ret = formatDate(value, cal.patterns.F, culture); + } + else { + var eraDate = new Date(value.getTime()), + era = getEra(value, cal.eras); + eraDate.setFullYear(getEraYear(value, cal, era)); + ret = eraDate.toLocaleString(); + } + } + else { + ret = value.toString(); + } + return ret; + } + + var eras = cal.eras, + sortable = format === "s"; + format = expandFormat(cal, format); + + // Start with an empty string + ret = []; + var hour, + zeros = [ "0", "00", "000" ], + foundDay, + checkedDay, + dayPartRegExp = /([^d]|^)(d|dd)([^d]|$)/g, + quoteCount = 0, + tokenRegExp = getTokenRegExp(), + converted; + + function padZeros(num, c) { + var r, s = num + ""; + if (c > 1 && s.length < c) { + r = ( zeros[c - 2] + s); + return r.substr(r.length - c, c); + } + else { + r = s; + } + return r; + } + + function hasDay() { + if (foundDay || checkedDay) { + return foundDay; + } + foundDay = dayPartRegExp.test(format); + checkedDay = true; + return foundDay; + } + + function getPart(date, part) { + if (converted) { + return converted[ part ]; + } + switch (part) { + case 0: + return date.getFullYear(); + case 1: + return date.getMonth(); + case 2: + return date.getDate(); + default: + throw "Invalid part value " + part; + } + } + + if (!sortable && convert) { + converted = convert.fromGregorian(value); + } + + for (; ;) { + // Save the current index + var index = tokenRegExp.lastIndex, + // Look for the next pattern + ar = tokenRegExp.exec(format); + + // Append the text before the pattern (or the end of the string if not found) + var preMatch = format.slice(index, ar ? ar.index : format.length); + quoteCount += appendPreOrPostMatch(preMatch, ret); + + if (!ar) { + break; + } + + // do not replace any matches that occur inside a string literal. + if (quoteCount % 2) { + ret.push(ar[0]); + continue; + } + + var current = ar[ 0 ], + clength = current.length; + + switch (current) { + case "ddd": + //Day of the week, as a three-letter abbreviation + case "dddd": + // Day of the week, using the full name + var names = ( clength === 3 ) ? cal.days.namesAbbr : cal.days.names; + ret.push(names[value.getDay()]); + break; + case "d": + // Day of month, without leading zero for single-digit days + case "dd": + // Day of month, with leading zero for single-digit days + foundDay = true; + ret.push( + padZeros(getPart(value, 2), clength) + ); + break; + case "MMM": + // Month, as a three-letter abbreviation + case "MMMM": + // Month, using the full name + var part = getPart(value, 1); + ret.push( + ( cal.monthsGenitive && hasDay() ) ? + ( cal.monthsGenitive[ clength === 3 ? "namesAbbr" : "names" ][ part ] ) : + ( cal.months[ clength === 3 ? "namesAbbr" : "names" ][ part ] ) + ); + break; + case "M": + // Month, as digits, with no leading zero for single-digit months + case "MM": + // Month, as digits, with leading zero for single-digit months + ret.push( + padZeros(getPart(value, 1) + 1, clength) + ); + break; + case "y": + // Year, as two digits, but with no leading zero for years less than 10 + case "yy": + // Year, as two digits, with leading zero for years less than 10 + case "yyyy": + // Year represented by four full digits + part = converted ? converted[ 0 ] : getEraYear(value, cal, getEra(value, eras), sortable); + if (clength < 4) { + part = part % 100; + } + ret.push( + padZeros(part, clength) + ); + break; + case "h": + // Hours with no leading zero for single-digit hours, using 12-hour clock + case "hh": + // Hours with leading zero for single-digit hours, using 12-hour clock + hour = value.getHours() % 12; + if (hour === 0) hour = 12; + ret.push( + padZeros(hour, clength) + ); + break; + case "H": + // Hours with no leading zero for single-digit hours, using 24-hour clock + case "HH": + // Hours with leading zero for single-digit hours, using 24-hour clock + ret.push( + padZeros(value.getHours(), clength) + ); + break; + case "m": + // Minutes with no leading zero for single-digit minutes + case "mm": + // Minutes with leading zero for single-digit minutes + ret.push( + padZeros(value.getMinutes(), clength) + ); + break; + case "s": + // Seconds with no leading zero for single-digit seconds + case "ss": + // Seconds with leading zero for single-digit seconds + ret.push( + padZeros(value.getSeconds(), clength) + ); + break; + case "t": + // One character am/pm indicator ("a" or "p") + case "tt": + // Multicharacter am/pm indicator + part = value.getHours() < 12 ? ( cal.AM ? cal.AM[0] : " " ) : ( cal.PM ? cal.PM[0] : " " ); + ret.push(clength === 1 ? part.charAt(0) : part); + break; + case "f": + // Deciseconds + case "ff": + // Centiseconds + case "fff": + // Milliseconds + ret.push( + padZeros(value.getMilliseconds(), 3).substr(0, clength) + ); + break; + case "z": + // Time zone offset, no leading zero + case "zz": + // Time zone offset with leading zero + hour = value.getTimezoneOffset() / 60; + ret.push( + ( hour <= 0 ? "+" : "-" ) + padZeros(Math.floor(Math.abs(hour)), clength) + ); + break; + case "zzz": + // Time zone offset with leading zero + hour = value.getTimezoneOffset() / 60; + ret.push( + ( hour <= 0 ? "+" : "-" ) + padZeros(Math.floor(Math.abs(hour)), 2) + + // Hard coded ":" separator, rather than using cal.TimeSeparator + // Repeated here for consistency, plus ":" was already assumed in date parsing. + ":" + padZeros(Math.abs(value.getTimezoneOffset() % 60), 2) + ); + break; + case "g": + case "gg": + if (cal.eras) { + ret.push( + cal.eras[ getEra(value, eras) ].name + ); + } + break; + case "/": + ret.push(cal["/"]); + break; + default: + throw "Invalid date format pattern \'" + current + "\'."; + } + } + return ret.join(""); + }; + +// formatNumber + (function () { + var expandNumber; + + expandNumber = function (number, precision, formatInfo) { + var groupSizes = formatInfo.groupSizes, + curSize = groupSizes[ 0 ], + curGroupIndex = 1, + factor = Math.pow(10, precision), + rounded = Math.round(number * factor) / factor; + + if (!isFinite(rounded)) { + rounded = number; + } + number = rounded; + + var numberString = number + "", + right = "", + split = numberString.split(/e/i), + exponent = split.length > 1 ? parseInt(split[1], 10) : 0; + numberString = split[ 0 ]; + split = numberString.split("."); + numberString = split[ 0 ]; + right = split.length > 1 ? split[ 1 ] : ""; + + var l; + if (exponent > 0) { + right = zeroPad(right, exponent, false); + numberString += right.slice(0, exponent); + right = right.substr(exponent); + } + else if (exponent < 0) { + exponent = -exponent; + numberString = zeroPad(numberString, exponent + 1, true); + right = numberString.slice(-exponent, numberString.length) + right; + numberString = numberString.slice(0, -exponent); + } + + if (precision > 0) { + right = formatInfo[ "." ] + + ( (right.length > precision) ? right.slice(0, precision) : zeroPad(right, precision) ); + } + else { + right = ""; + } + + var stringIndex = numberString.length - 1, + sep = formatInfo[ "," ], + ret = ""; + + while (stringIndex >= 0) { + if (curSize === 0 || curSize > stringIndex) { + return numberString.slice(0, stringIndex + 1) + ( ret.length ? (sep + ret + right) : right ); + } + ret = numberString.slice(stringIndex - curSize + 1, stringIndex + 1) + ( ret.length ? (sep + ret) : "" ); + + stringIndex -= curSize; + + if (curGroupIndex < groupSizes.length) { + curSize = groupSizes[ curGroupIndex ]; + curGroupIndex++; + } + } + + return numberString.slice(0, stringIndex + 1) + sep + ret + right; + }; + + formatNumber = function (value, format, culture) { + if (!isFinite(value)) { + if (value === Infinity) { + return culture.numberFormat.positiveInfinity; + } + if (value === -Infinity) { + return culture.numberFormat.negativeInfinity; + } + return culture.numberFormat.NaN; + } + if (!format || format === "i") { + return culture.name.length ? value.toLocaleString() : value.toString(); + } + format = format || "D"; + + var nf = culture.numberFormat, + number = Math.abs(value), + precision = -1, + pattern; + if (format.length > 1) precision = parseInt(format.slice(1), 10); + + var current = format.charAt(0).toUpperCase(), + formatInfo; + + switch (current) { + case "D": + pattern = "n"; + number = truncate(number); + if (precision !== -1) { + number = zeroPad("" + number, precision, true); + } + if (value < 0) number = "-" + number; + break; + case "N": + formatInfo = nf; + /* falls through */ + case "C": + formatInfo = formatInfo || nf.currency; + /* falls through */ + case "P": + formatInfo = formatInfo || nf.percent; + pattern = value < 0 ? formatInfo.pattern[ 0 ] : ( formatInfo.pattern[1] || "n" ); + if (precision === -1) precision = formatInfo.decimals; + number = expandNumber(number * (current === "P" ? 100 : 1), precision, formatInfo); + break; + default: + throw "Bad number format specifier: " + current; + } + + var patternParts = /n|\$|-|%/g, + ret = ""; + for (; ;) { + var index = patternParts.lastIndex, + ar = patternParts.exec(pattern); + + ret += pattern.slice(index, ar ? ar.index : pattern.length); + + if (!ar) { + break; + } + + switch (ar[0]) { + case "n": + ret += number; + break; + case "$": + ret += nf.currency.symbol; + break; + case "-": + // don't make 0 negative + if (/[1-9]/.test(number)) { + ret += nf[ "-" ]; + } + break; + case "%": + ret += nf.percent.symbol; + break; + } + } + + return ret; + }; + + }()); + + getTokenRegExp = function () { + // regular expression for matching date and time tokens in format strings. + return (/\/|dddd|ddd|dd|d|MMMM|MMM|MM|M|yyyy|yy|y|hh|h|HH|H|mm|m|ss|s|tt|t|fff|ff|f|zzz|zz|z|gg|g/g); + }; + + getEra = function (date, eras) { + if (!eras) return 0; + var start, ticks = date.getTime(); + for (var i = 0, l = eras.length; i < l; i++) { + start = eras[ i ].start; + if (start === null || ticks >= start) { + return i; + } + } + return 0; + }; + + getEraYear = function (date, cal, era, sortable) { + var year = date.getFullYear(); + if (!sortable && cal.eras) { + // convert normal gregorian year to era-shifted gregorian + // year by subtracting the era offset + year -= cal.eras[ era ].offset; + } + return year; + }; + +// parseExact + (function () { + var expandYear, + getDayIndex, + getMonthIndex, + getParseRegExp, + outOfRange, + toUpper, + toUpperArray; + + expandYear = function (cal, year) { + // expands 2-digit year into 4 digits. + if (year < 100) { + var now = new Date(), + era = getEra(now), + curr = getEraYear(now, cal, era), + twoDigitYearMax = cal.twoDigitYearMax; + twoDigitYearMax = typeof twoDigitYearMax === "string" ? new Date().getFullYear() % 100 + parseInt(twoDigitYearMax, 10) : twoDigitYearMax; + year += curr - ( curr % 100 ); + if (year > twoDigitYearMax) { + year -= 100; + } + } + return year; + }; + + getDayIndex = function (cal, value, abbr) { + var ret, + days = cal.days, + upperDays = cal._upperDays; + if (!upperDays) { + cal._upperDays = upperDays = [ + toUpperArray(days.names), + toUpperArray(days.namesAbbr), + toUpperArray(days.namesShort) + ]; + } + value = toUpper(value); + if (abbr) { + ret = arrayIndexOf(upperDays[1], value); + if (ret === -1) { + ret = arrayIndexOf(upperDays[2], value); + } + } + else { + ret = arrayIndexOf(upperDays[0], value); + } + return ret; + }; + + getMonthIndex = function (cal, value, abbr) { + var months = cal.months, + monthsGen = cal.monthsGenitive || cal.months, + upperMonths = cal._upperMonths, + upperMonthsGen = cal._upperMonthsGen; + if (!upperMonths) { + cal._upperMonths = upperMonths = [ + toUpperArray(months.names), + toUpperArray(months.namesAbbr) + ]; + cal._upperMonthsGen = upperMonthsGen = [ + toUpperArray(monthsGen.names), + toUpperArray(monthsGen.namesAbbr) + ]; + } + value = toUpper(value); + var i = arrayIndexOf(abbr ? upperMonths[1] : upperMonths[0], value); + if (i < 0) { + i = arrayIndexOf(abbr ? upperMonthsGen[1] : upperMonthsGen[0], value); + } + return i; + }; + + getParseRegExp = function (cal, format) { + // converts a format string into a regular expression with groups that + // can be used to extract date fields from a date string. + // check for a cached parse regex. + var re = cal._parseRegExp; + if (!re) { + cal._parseRegExp = re = {}; + } + else { + var reFormat = re[ format ]; + if (reFormat) { + return reFormat; + } + } + + // expand single digit formats, then escape regular expression characters. + var expFormat = expandFormat(cal, format).replace(/([\^\$\.\*\+\?\|\[\]\(\)\{\}])/g, "\\\\$1"), + regexp = [ "^" ], + groups = [], + index = 0, + quoteCount = 0, + tokenRegExp = getTokenRegExp(), + match; + + // iterate through each date token found. + while ((match = tokenRegExp.exec(expFormat)) !== null) { + var preMatch = expFormat.slice(index, match.index); + index = tokenRegExp.lastIndex; + + // don't replace any matches that occur inside a string literal. + quoteCount += appendPreOrPostMatch(preMatch, regexp); + if (quoteCount % 2) { + regexp.push(match[0]); + continue; + } + + // add a regex group for the token. + var m = match[ 0 ], + len = m.length, + add; + switch (m) { + case "dddd": + case "ddd": + case "MMMM": + case "MMM": + case "gg": + case "g": + add = "(\\D+)"; + break; + case "tt": + case "t": + add = "(\\D*)"; + break; + case "yyyy": + case "fff": + case "ff": + case "f": + add = "(\\d{" + len + "})"; + break; + case "dd": + case "d": + case "MM": + case "M": + case "yy": + case "y": + case "HH": + case "H": + case "hh": + case "h": + case "mm": + case "m": + case "ss": + case "s": + add = "(\\d\\d?)"; + break; + case "zzz": + add = "([+-]?\\d\\d?:\\d{2})"; + break; + case "zz": + case "z": + add = "([+-]?\\d\\d?)"; + break; + case "/": + add = "(\\/)"; + break; + default: + throw "Invalid date format pattern \'" + m + "\'."; + } + if (add) { + regexp.push(add); + } + groups.push(match[0]); + } + appendPreOrPostMatch(expFormat.slice(index), regexp); + regexp.push("$"); + + // allow whitespace to differ when matching formats. + var regexpStr = regexp.join("").replace(/\s+/g, "\\s+"), + parseRegExp = { "regExp": regexpStr, "groups": groups }; + + // cache the regex for this format. + return re[ format ] = parseRegExp; + }; + + outOfRange = function (value, low, high) { + return value < low || value > high; + }; + + toUpper = function (value) { + // "he-IL" has non-breaking space in weekday names. + return value.split("\u00A0").join(" ").toUpperCase(); + }; + + toUpperArray = function (arr) { + var results = []; + for (var i = 0, l = arr.length; i < l; i++) { + results[ i ] = toUpper(arr[i]); + } + return results; + }; + + parseExact = function (value, format, culture) { + // try to parse the date string by matching against the format string + // while using the specified culture for date field names. + value = trim(value); + var cal = culture.calendar, + // convert date formats into regular expressions with groupings. + // use the regexp to determine the input format and extract the date fields. + parseInfo = getParseRegExp(cal, format), + match = new RegExp(parseInfo.regExp).exec(value); + if (match === null) { + return null; + } + // found a date format that matches the input. + var groups = parseInfo.groups, + era = null, year = null, month = null, date = null, weekDay = null, + hour = 0, hourOffset, min = 0, sec = 0, msec = 0, tzMinOffset = null, + pmHour = false; + // iterate the format groups to extract and set the date fields. + for (var j = 0, jl = groups.length; j < jl; j++) { + var matchGroup = match[ j + 1 ]; + if (matchGroup) { + var current = groups[ j ], + clength = current.length, + matchInt = parseInt(matchGroup, 10); + switch (current) { + case "dd": + case "d": + // Day of month. + date = matchInt; + // check that date is generally in valid range, also checking overflow below. + if (outOfRange(date, 1, 31)) return null; + break; + case "MMM": + case "MMMM": + month = getMonthIndex(cal, matchGroup, clength === 3); + if (outOfRange(month, 0, 11)) return null; + break; + case "M": + case "MM": + // Month. + month = matchInt - 1; + if (outOfRange(month, 0, 11)) return null; + break; + case "y": + case "yy": + case "yyyy": + year = clength < 4 ? expandYear(cal, matchInt) : matchInt; + if (outOfRange(year, 0, 9999)) return null; + break; + case "h": + case "hh": + // Hours (12-hour clock). + hour = matchInt; + if (hour === 12) hour = 0; + if (outOfRange(hour, 0, 11)) return null; + break; + case "H": + case "HH": + // Hours (24-hour clock). + hour = matchInt; + if (outOfRange(hour, 0, 23)) return null; + break; + case "m": + case "mm": + // Minutes. + min = matchInt; + if (outOfRange(min, 0, 59)) return null; + break; + case "s": + case "ss": + // Seconds. + sec = matchInt; + if (outOfRange(sec, 0, 59)) return null; + break; + case "tt": + case "t": + // AM/PM designator. + // see if it is standard, upper, or lower case PM. If not, ensure it is at least one of + // the AM tokens. If not, fail the parse for this format. + pmHour = cal.PM && ( matchGroup === cal.PM[0] || matchGroup === cal.PM[1] || matchGroup === cal.PM[2] ); + if ( + !pmHour && ( + !cal.AM || ( matchGroup !== cal.AM[0] && matchGroup !== cal.AM[1] && matchGroup !== cal.AM[2] ) + ) + ) return null; + break; + case "f": + // Deciseconds. + case "ff": + // Centiseconds. + case "fff": + // Milliseconds. + msec = matchInt * Math.pow(10, 3 - clength); + if (outOfRange(msec, 0, 999)) return null; + break; + case "ddd": + // Day of week. + case "dddd": + // Day of week. + weekDay = getDayIndex(cal, matchGroup, clength === 3); + if (outOfRange(weekDay, 0, 6)) return null; + break; + case "zzz": + // Time zone offset in +/- hours:min. + var offsets = matchGroup.split(/:/); + if (offsets.length !== 2) return null; + hourOffset = parseInt(offsets[0], 10); + if (outOfRange(hourOffset, -12, 13)) return null; + var minOffset = parseInt(offsets[1], 10); + if (outOfRange(minOffset, 0, 59)) return null; + tzMinOffset = ( hourOffset * 60 ) + ( startsWith(matchGroup, "-") ? -minOffset : minOffset ); + break; + case "z": + case "zz": + // Time zone offset in +/- hours. + hourOffset = matchInt; + if (outOfRange(hourOffset, -12, 13)) return null; + tzMinOffset = hourOffset * 60; + break; + case "g": + case "gg": + var eraName = matchGroup; + if (!eraName || !cal.eras) return null; + eraName = trim(eraName.toLowerCase()); + for (var i = 0, l = cal.eras.length; i < l; i++) { + if (eraName === cal.eras[i].name.toLowerCase()) { + era = i; + break; + } + } + // could not find an era with that name + if (era === null) return null; + break; + } + } + } + var result = new Date(), defaultYear, convert = cal.convert; + defaultYear = convert ? convert.fromGregorian(result)[ 0 ] : result.getFullYear(); + if (year === null) { + year = defaultYear; + } + else if (cal.eras) { + // year must be shifted to normal gregorian year + // but not if year was not specified, its already normal gregorian + // per the main if clause above. + year += cal.eras[( era || 0 )].offset; + } + // set default day and month to 1 and January, so if unspecified, these are the defaults + // instead of the current day/month. + if (month === null) { + month = 0; + } + if (date === null) { + date = 1; + } + // now have year, month, and date, but in the culture's calendar. + // convert to gregorian if necessary + if (convert) { + result = convert.toGregorian(year, month, date); + // conversion failed, must be an invalid match + if (result === null) return null; + } + else { + // have to set year, month and date together to avoid overflow based on current date. + result.setFullYear(year, month, date); + // check to see if date overflowed for specified month (only checked 1-31 above). + if (result.getDate() !== date) return null; + // invalid day of week. + if (weekDay !== null && result.getDay() !== weekDay) { + return null; + } + } + // if pm designator token was found make sure the hours fit the 24-hour clock. + if (pmHour && hour < 12) { + hour += 12; + } + result.setHours(hour, min, sec, msec); + if (tzMinOffset !== null) { + // adjust timezone to utc before applying local offset. + var adjustedMin = result.getMinutes() - ( tzMinOffset + result.getTimezoneOffset() ); + // Safari limits hours and minutes to the range of -127 to 127. We need to use setHours + // to ensure both these fields will not exceed this range. adjustedMin will range + // somewhere between -1440 and 1500, so we only need to split this into hours. + result.setHours(result.getHours() + parseInt(adjustedMin / 60, 10), adjustedMin % 60); + } + return result; + }; + }()); + + parseNegativePattern = function (value, nf, negativePattern) { + var neg = nf[ "-" ], + pos = nf[ "+" ], + ret; + switch (negativePattern) { + case "n -": + neg = " " + neg; + pos = " " + pos; + /* falls through */ + case "n-": + if (endsWith(value, neg)) { + ret = [ "-", value.substr(0, value.length - neg.length) ]; + } + else if (endsWith(value, pos)) { + ret = [ "+", value.substr(0, value.length - pos.length) ]; + } + break; + case "- n": + neg += " "; + pos += " "; + /* falls through */ + case "-n": + if (startsWith(value, neg)) { + ret = [ "-", value.substr(neg.length) ]; + } + else if (startsWith(value, pos)) { + ret = [ "+", value.substr(pos.length) ]; + } + break; + case "(n)": + if (startsWith(value, "(") && endsWith(value, ")")) { + ret = [ "-", value.substr(1, value.length - 2) ]; + } + break; + } + return ret || [ "", value ]; + }; + +// +// public instance functions +// + + Globalize.prototype.findClosestCulture = function (cultureSelector) { + return Globalize.findClosestCulture.call(this, cultureSelector); + }; + + Globalize.prototype.format = function (value, format, cultureSelector) { + return Globalize.format.call(this, value, format, cultureSelector); + }; + + Globalize.prototype.localize = function (key, cultureSelector) { + return Globalize.localize.call(this, key, cultureSelector); + }; + + Globalize.prototype.parseInt = function (value, radix, cultureSelector) { + return Globalize.parseInt.call(this, value, radix, cultureSelector); + }; + + Globalize.prototype.parseFloat = function (value, radix, cultureSelector) { + return Globalize.parseFloat.call(this, value, radix, cultureSelector); + }; + + Globalize.prototype.culture = function (cultureSelector) { + return Globalize.culture.call(this, cultureSelector); + }; + +// +// public singleton functions +// + + Globalize.addCultureInfo = function (cultureName, baseCultureName, info) { + + var base = {}, + isNew = false; + + if (typeof cultureName !== "string") { + // cultureName argument is optional string. If not specified, assume info is first + // and only argument. Specified info deep-extends current culture. + info = cultureName; + cultureName = this.culture().name; + base = this.cultures[ cultureName ]; + } else if (typeof baseCultureName !== "string") { + // baseCultureName argument is optional string. If not specified, assume info is second + // argument. Specified info deep-extends specified culture. + // If specified culture does not exist, create by deep-extending default + info = baseCultureName; + isNew = ( this.cultures[ cultureName ] == null ); + base = this.cultures[ cultureName ] || this.cultures[ "default" ]; + } else { + // cultureName and baseCultureName specified. Assume a new culture is being created + // by deep-extending an specified base culture + isNew = true; + base = this.cultures[ baseCultureName ]; + } + + this.cultures[ cultureName ] = extend(true, {}, + base, + info + ); + // Make the standard calendar the current culture if it's a new culture + if (isNew) { + this.cultures[ cultureName ].calendar = this.cultures[ cultureName ].calendars.standard; + } + }; + + Globalize.findClosestCulture = function (name) { + var match; + if (!name) { + return this.findClosestCulture(this.cultureSelector) || this.cultures[ "default" ]; + } + if (typeof name === "string") { + name = name.split(","); + } + if (isArray(name)) { + var lang, + cultures = this.cultures, + list = name, + i, l = list.length, + prioritized = []; + for (i = 0; i < l; i++) { + name = trim(list[i]); + var pri, parts = name.split(";"); + lang = trim(parts[0]); + if (parts.length === 1) { + pri = 1; + } + else { + name = trim(parts[1]); + if (name.indexOf("q=") === 0) { + name = name.substr(2); + pri = parseFloat(name); + pri = isNaN(pri) ? 0 : pri; + } + else { + pri = 1; + } + } + prioritized.push({ lang: lang, pri: pri }); + } + prioritized.sort(function (a, b) { + if (a.pri < b.pri) { + return 1; + } else if (a.pri > b.pri) { + return -1; + } + return 0; + }); + // exact match + for (i = 0; i < l; i++) { + lang = prioritized[ i ].lang; + match = cultures[ lang ]; + if (match) { + return match; + } + } + + // neutral language match + for (i = 0; i < l; i++) { + lang = prioritized[ i ].lang; + do { + var index = lang.lastIndexOf("-"); + if (index === -1) { + break; + } + // strip off the last part. e.g. en-US => en + lang = lang.substr(0, index); + match = cultures[ lang ]; + if (match) { + return match; + } + } + while (1); + } + + // last resort: match first culture using that language + for (i = 0; i < l; i++) { + lang = prioritized[ i ].lang; + for (var cultureKey in cultures) { + var culture = cultures[ cultureKey ]; + if (culture.language == lang) { + return culture; + } + } + } + } + else if (typeof name === "object") { + return name; + } + return match || null; + }; + + Globalize.format = function (value, format, cultureSelector) { + var culture = this.findClosestCulture(cultureSelector); + if (value instanceof Date) { + value = formatDate(value, format, culture); + } + else if (typeof value === "number") { + value = formatNumber(value, format, culture); + } + return value; + }; + + Globalize.localize = function (key, cultureSelector) { + return this.findClosestCulture(cultureSelector).messages[ key ] || + this.cultures[ "default" ].messages[ key ]; + }; + + Globalize.parseDate = function (value, formats, culture) { + culture = this.findClosestCulture(culture); + + var date, prop, patterns; + if (formats) { + if (typeof formats === "string") { + formats = [ formats ]; + } + if (formats.length) { + for (var i = 0, l = formats.length; i < l; i++) { + var format = formats[ i ]; + if (format) { + date = parseExact(value, format, culture); + if (date) { + break; + } + } + } + } + } else { + patterns = culture.calendar.patterns; + for (prop in patterns) { + date = parseExact(value, patterns[prop], culture); + if (date) { + break; + } + } + } + + return date || null; + }; + + Globalize.parseInt = function (value, radix, cultureSelector) { + return truncate(Globalize.parseFloat(value, radix, cultureSelector)); + }; + + Globalize.parseFloat = function (value, radix, cultureSelector) { + // radix argument is optional + if (typeof radix !== "number") { + cultureSelector = radix; + radix = 10; + } + + var culture = this.findClosestCulture(cultureSelector); + var ret = NaN, + nf = culture.numberFormat; + + if (value.indexOf(culture.numberFormat.currency.symbol) > -1) { + // remove currency symbol + value = value.replace(culture.numberFormat.currency.symbol, ""); + // replace decimal seperator + value = value.replace(culture.numberFormat.currency["."], culture.numberFormat["."]); + } + + //Remove percentage character from number string before parsing + if (value.indexOf(culture.numberFormat.percent.symbol) > -1) { + value = value.replace(culture.numberFormat.percent.symbol, ""); + } + + // remove spaces: leading, trailing and between - and number. Used for negative currency pt-BR + value = value.replace(/ /g, ""); + + // allow infinity or hexidecimal + if (regexInfinity.test(value)) { + ret = parseFloat(value); + } + else if (!radix && regexHex.test(value)) { + ret = parseInt(value, 16); + } + else { + + // determine sign and number + var signInfo = parseNegativePattern(value, nf, nf.pattern[0]), + sign = signInfo[ 0 ], + num = signInfo[ 1 ]; + + // #44 - try parsing as "(n)" + if (sign === "" && nf.pattern[0] !== "(n)") { + signInfo = parseNegativePattern(value, nf, "(n)"); + sign = signInfo[ 0 ]; + num = signInfo[ 1 ]; + } + + // try parsing as "-n" + if (sign === "" && nf.pattern[0] !== "-n") { + signInfo = parseNegativePattern(value, nf, "-n"); + sign = signInfo[ 0 ]; + num = signInfo[ 1 ]; + } + + sign = sign || "+"; + + // determine exponent and number + var exponent, + intAndFraction, + exponentPos = num.indexOf("e"); + if (exponentPos < 0) exponentPos = num.indexOf("E"); + if (exponentPos < 0) { + intAndFraction = num; + exponent = null; + } + else { + intAndFraction = num.substr(0, exponentPos); + exponent = num.substr(exponentPos + 1); + } + // determine decimal position + var integer, + fraction, + decSep = nf[ "." ], + decimalPos = intAndFraction.indexOf(decSep); + if (decimalPos < 0) { + integer = intAndFraction; + fraction = null; + } + else { + integer = intAndFraction.substr(0, decimalPos); + fraction = intAndFraction.substr(decimalPos + decSep.length); + } + // handle groups (e.g. 1,000,000) + var groupSep = nf[ "," ]; + integer = integer.split(groupSep).join(""); + var altGroupSep = groupSep.replace(/\u00A0/g, " "); + if (groupSep !== altGroupSep) { + integer = integer.split(altGroupSep).join(""); + } + // build a natively parsable number string + var p = sign + integer; + if (fraction !== null) { + p += "." + fraction; + } + if (exponent !== null) { + // exponent itself may have a number patternd + var expSignInfo = parseNegativePattern(exponent, nf, "-n"); + p += "e" + ( expSignInfo[0] || "+" ) + expSignInfo[ 1 ]; + } + if (regexParseFloat.test(p)) { + ret = parseFloat(p); + } + } + return ret; + }; + + Globalize.culture = function (cultureSelector) { + // setter + if (typeof cultureSelector !== "undefined") { + this.cultureSelector = cultureSelector; + } + // getter + return this.findClosestCulture(cultureSelector) || this.cultures[ "default" ]; + }; + +}(this)); \ No newline at end of file diff --git a/static/js/old__dss_sound_handler.js b/static/js/old__dss_sound_handler.js deleted file mode 100644 index 0e17222..0000000 --- a/static/js/old__dss_sound_handler.js +++ /dev/null @@ -1,135 +0,0 @@ -function DssSoundHandler() { - var _currentSound = null; - var _currentId = -1; - this.stop_sound = function () { - if (_currentSound) { - this.togglePlaying(this.getPlayingId()); - _currentSound.stop(); - _currentId = -1; - } - }; - this.getPlayingId = function () { - return _currentId; - }; - this.isPlaying = function () { - if (_currentSound != null) - return _currentSound.playState == 1; - }; - - this.togglePlaying = function (id) { - this.togglePlayState(id); - return this.togglePlayVisual(id); - }; - - this.togglePlayVisual = function (id) { - var button = $('#play-pause-button-small-' + id); - var mode = button.data("mode"); - if (mode == "play" || mode == "resume") { - button.data('mode', 'pause'); - button.removeClass('play-button-small-playing'); - button.addClass('play-button-small-paused'); - } else { - button.data('mode', 'resume'); - button.removeClass('play-button-small-paused'); - button.addClass('play-button-small-playing'); - } - return mode; - }; - this.togglePlayState = function (id) { - var button = $('#play-pause-button-small-' + id); - var mode = button.data("mode"); - if (mode == 'pause') - this.pauseSound(); - else if (mode == 'resume') - this.resumeSound(); - }; - this.playSound = (function (itemId, stream_url) { - - _currentId = itemId; - var waveformTop = $('#waveform-' + _currentId).position().top; - var waveformWidth = $('#waveform-' + _currentId).width(); - - $('#player-seekhead').css('top', waveformTop); - if (_currentSound) _currentSound.stop(); - soundManager.destroySound('current_sound'); - _currentSound = soundManager.createSound({ - id:'current_sound', - url:stream_url, - volume:50, - stream:true, - whileloading:function () { - var percentageFinished = (_currentSound.bytesLoaded / _currentSound.bytesTotal) * 100; - var percentageWidth = (waveformWidth / 100) * percentageFinished; - $('#progress-player-' + _currentId).css('width', percentageWidth); - }, - whileplaying:function () { - /* Should move to an aggregator viewChanged callback */ - if (_currentId == -1){ - _currentId = itemId; - this._setupEvents(itemId, _currentSound); - } - waveformTop = $('#waveform-' + _currentId).position().top; - waveformWidth = $('#waveform-' + _currentId).width(); - $('#playhead-player-' + _currentId).css('top', waveformTop); - $('#progress-player-' + _currentId).css('top', waveformTop); - $('#player-seekhead').css('top', waveformTop); - - var currentPosition = _currentSound.position; - var totalLength = _currentSound.duration; - var percentageFinished = (currentPosition / totalLength) * 100; - var percentageWidth = (waveformWidth / 100) * percentageFinished; - $('#playhead-player-' + _currentId).css('width', percentageWidth); - } - }); - _currentSound.loaded = false; - _currentSound.readyState = 0; - dssSoundHandler._setupEvents(_currentId, _currentSound); - _currentSound.play(); - }); - this.playLive = function () { - this.stop_sound(); - _currentSound = soundManager.createSound({ - id:'current_sound', - url:com.podnoms.settings.liveStreamRoot, - volume:50, - stream:true, - useMovieStar:true - }); - _currentSound.play(); - }; - this.pauseSound = function () { - this.togglePlaying(); - if (_currentSound) { - _currentSound.pause(); - } - }; - this.resumeSound = function () { - this.togglePlaying(); - if (_currentSound) - _currentSound.resume(); - }; - this.getPosition = function () { - if (_currentSound) - return _currentSound.position; - }; - this._setupEvents = function (itemId, sound) { - $('#waveform-' + itemId).mousemove(function (event) { - $('#player-seekhead').show(); - $('#player-seekhead').css('left', (event.pageX) + 'px').fadeIn('fast'); - }); - $('#waveform-' + itemId).mousedown(function (event) { - var width = $('#waveform-image-' + itemId).width(); - if (sound != null) { - var fullLength = sound.duration; - var left = $('#waveform-image-' + itemId).offset().left; - var clickPerc = ((event.pageX - left) / width) * 100; - sound.setPosition((fullLength / 100) * clickPerc); - } - }); - $('#waveform-' + itemId).mouseleave(function (event) { - $('#player-seekhead').hide(); - }); - }; -} -dssSoundHandler = new DssSoundHandler(); -window.dssSoundHandler = dssSoundHandler; diff --git a/templates/base.html b/templates/base.html index c76f677..1f8bcd2 100644 --- a/templates/base.html +++ b/templates/base.html @@ -96,6 +96,7 @@ + diff --git a/templates/views/ActivityListItemView.html b/templates/views/ActivityListItemView.html index 367294d..740af0c 100644 --- a/templates/views/ActivityListItemView.html +++ b/templates/views/ActivityListItemView.html @@ -1,17 +1,17 @@ -{% load account_tags %} - -
- - - - -
- - <%= item.user_name %> - -
-

<%= item.activity %>

- <%= item.date_created %> -
-
-
+{% load account_tags %} +
+ + + + +
+
+ + <%= item.user_name %> + + <%= item.verb %> <%= item.item_name %> +
<%= item.date %>
+
+
+
+ diff --git a/templates/views/SidebarView.html b/templates/views/SidebarView.html index 204087f..1499ec2 100644 --- a/templates/views/SidebarView.html +++ b/templates/views/SidebarView.html @@ -5,7 +5,7 @@
- +
{% include 'inc/side-player.html' %}