nutcracker-web 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +63 -0
- data/README.md +56 -0
- data/Rakefile +26 -0
- data/assets/javascripts/application.js +10 -0
- data/assets/javascripts/collections/clusters.js.coffee +4 -0
- data/assets/javascripts/collections/nodes.js.coffee +19 -0
- data/assets/javascripts/models/cluster.js.coffee +16 -0
- data/assets/javascripts/models/node.js.coffee +14 -0
- data/assets/javascripts/models/overview.js.coffee +27 -0
- data/assets/javascripts/nutcracker.js.coffee +20 -0
- data/assets/javascripts/routers/nutcracker_router.js.coffee +29 -0
- data/assets/javascripts/utils/region_manager.js.coffee +12 -0
- data/assets/javascripts/utils/underscore_ext.js.coffee +6 -0
- data/assets/javascripts/vendor/backbone.js +1571 -0
- data/assets/javascripts/vendor/bootstrap.js +2276 -0
- data/assets/javascripts/vendor/google_chart.js.coffee +124 -0
- data/assets/javascripts/vendor/humanize.js +459 -0
- data/assets/javascripts/vendor/jquery.js +8842 -0
- data/assets/javascripts/vendor/underscore.js +1227 -0
- data/assets/javascripts/views/cluster.js.coffee +58 -0
- data/assets/javascripts/views/config.js.coffee +6 -0
- data/assets/javascripts/views/footer.js.coffee +8 -0
- data/assets/javascripts/views/navbar.js.coffee +28 -0
- data/assets/javascripts/views/node.js.coffee +54 -0
- data/assets/javascripts/views/overview.js.coffee +6 -0
- data/assets/stylesheets/application.css +24 -0
- data/assets/stylesheets/bootstrap-responsive.css +1109 -0
- data/assets/stylesheets/bootstrap.css +6158 -0
- data/assets/templates/cluster.jst.eco +87 -0
- data/assets/templates/config.jst.eco +15 -0
- data/assets/templates/footer.jst.eco +1 -0
- data/assets/templates/navbar.jst.eco +55 -0
- data/assets/templates/node.jst.eco +73 -0
- data/assets/templates/overview.jst.eco +32 -0
- data/lib/nutcracker/web/app.rb +51 -0
- data/lib/nutcracker/web/version.rb +5 -0
- data/lib/nutcracker/web.rb +29 -0
- metadata +122 -0
@@ -0,0 +1,124 @@
|
|
1
|
+
class Backbone.GoogleChart extends Backbone.View
|
2
|
+
###
|
3
|
+
# Initialize a new GoogleChart object
|
4
|
+
#
|
5
|
+
# Example:
|
6
|
+
# lineChart = new Backbone.GoogleChart({
|
7
|
+
# chartType: 'ColumnChart',
|
8
|
+
# dataTable: [['Germany', 'USA', 'Brazil', 'Canada', 'France', 'RU'],
|
9
|
+
# [700, 300, 400, 500, 600, 800]],
|
10
|
+
# options: {'title': 'Countries'},
|
11
|
+
# });
|
12
|
+
#
|
13
|
+
# $('body').append( lineChart.render().el );
|
14
|
+
#
|
15
|
+
# For the complete list of options please checkout
|
16
|
+
# https://developers.google.com/chart/interactive/docs/reference#chartwrapperobject
|
17
|
+
#
|
18
|
+
###
|
19
|
+
initialize: ( options ) ->
|
20
|
+
chartOptions = _.extend({},options)
|
21
|
+
['el','id','attributes','className','tagName'].map (key)=>
|
22
|
+
delete chartOptions[key]
|
23
|
+
|
24
|
+
google.load 'visualization', '1', packages: ['corechart'], callback: =>
|
25
|
+
@onGoogleLoad "loaded"
|
26
|
+
|
27
|
+
@onGoogleLoad =>
|
28
|
+
@google = google.visualization
|
29
|
+
if chartOptions.dataTable instanceof Array
|
30
|
+
chartOptions.dataTable = @google.arrayToDataTable chartOptions.dataTable
|
31
|
+
(chartOptions.beforeDraw||->) @, chartOptions
|
32
|
+
|
33
|
+
if formatter = chartOptions.formatter
|
34
|
+
[0..chartOptions.dataTable.getNumberOfRows()-1].map (index)=>
|
35
|
+
formatter.columns.map (column)=>
|
36
|
+
chartOptions.dataTable.setFormattedValue(
|
37
|
+
index, column, formatter.callback(
|
38
|
+
chartOptions.dataTable.getValue index, column
|
39
|
+
)
|
40
|
+
)
|
41
|
+
|
42
|
+
@wrapper = new @google.ChartWrapper chartOptions
|
43
|
+
['ready','select', 'error'].map @listen
|
44
|
+
|
45
|
+
###
|
46
|
+
# Execute a callback once google visualization fully loaded
|
47
|
+
###
|
48
|
+
onGoogleLoad: ( callback )=>
|
49
|
+
if callback == "loaded"
|
50
|
+
@googleLoaded = true
|
51
|
+
_( @onGoogleLoadItems ).map (fn)-> fn()
|
52
|
+
else
|
53
|
+
if @googleLoaded
|
54
|
+
callback()
|
55
|
+
else
|
56
|
+
(@onGoogleLoadItems ||= []).push callback
|
57
|
+
|
58
|
+
###
|
59
|
+
# Execute a callback once a given element ID appears in DOM ( mini livequery ).
|
60
|
+
#
|
61
|
+
# We need it because GoogleChart object only draw itself on DOM elements
|
62
|
+
# so we first need to wait for our element to be added to the DOM before
|
63
|
+
# we call GoogleChart.draw().
|
64
|
+
#
|
65
|
+
# Usage:
|
66
|
+
# Backbone.GoogleChart.watch("#myid", function() { console.log("I'm in") });
|
67
|
+
# $("body").append("<div id='myid'></div>"); // 'I"m in' should be printed to console
|
68
|
+
#
|
69
|
+
###
|
70
|
+
@watch: ( id, fn ) =>
|
71
|
+
(@_list ||= {})[id] = fn
|
72
|
+
return if @_watching
|
73
|
+
@_watching = true
|
74
|
+
|
75
|
+
timeout = 10
|
76
|
+
(func = =>
|
77
|
+
_(@_list).map ( fn, id ) =>
|
78
|
+
fn() || delete @_list[id] if $(id)[0]
|
79
|
+
if _(@_list).isEmpty()
|
80
|
+
@_watching = false
|
81
|
+
else
|
82
|
+
setTimeout(func, timeout+=10)
|
83
|
+
)()
|
84
|
+
|
85
|
+
###
|
86
|
+
# Returns the wrapping element id
|
87
|
+
# if no id was specified on initialization a random one will be returned
|
88
|
+
###
|
89
|
+
id: =>
|
90
|
+
@el?.id or @randomID()
|
91
|
+
|
92
|
+
###
|
93
|
+
# "Instruct" the current graph instance to draw itself once its visiable on DOM
|
94
|
+
# return the current instance
|
95
|
+
###
|
96
|
+
render: =>
|
97
|
+
@onGoogleLoad =>
|
98
|
+
@constructor.watch "##{@el.id}", =>
|
99
|
+
@wrapper.draw @el.id
|
100
|
+
this
|
101
|
+
|
102
|
+
###
|
103
|
+
# Register for ChartWrapper events
|
104
|
+
# For the complete event list please look at the events section under
|
105
|
+
# https://developers.google.com/chart/interactive/docs/reference#chartwrapperobject
|
106
|
+
#
|
107
|
+
# By default the ready, select and error events are register automatically on initialization
|
108
|
+
# so instead of calling this function directly consider this:
|
109
|
+
# graph = new Backbone.GoogleChart({chartOptions: options});
|
110
|
+
# graph.on("select",function(graph) { console.log("Someone click on me!") })
|
111
|
+
# graph.on("error",function(graph) { console.log("Oops") })
|
112
|
+
# graph.on("ready",function(graph) { console.log("I'm ready!") })
|
113
|
+
#
|
114
|
+
###
|
115
|
+
listen: ( event ) =>
|
116
|
+
@google.events.addListener @wrapper, event, =>
|
117
|
+
@trigger event, @wrapper.getChart()
|
118
|
+
|
119
|
+
###
|
120
|
+
# Generate a random ID, gc_XXX
|
121
|
+
###
|
122
|
+
randomID: ->
|
123
|
+
_.uniqueId "gc_"
|
124
|
+
|
@@ -0,0 +1,459 @@
|
|
1
|
+
// https://github.com/taijinlee/humanize
|
2
|
+
(function() {
|
3
|
+
|
4
|
+
// Baseline setup
|
5
|
+
// --------------
|
6
|
+
|
7
|
+
// Establish the root object, `window` in the browser, or `global` on the server.
|
8
|
+
var root = this;
|
9
|
+
|
10
|
+
// Save the previous value of the `humanize` variable.
|
11
|
+
var previousHumanize = root.humanize;
|
12
|
+
|
13
|
+
var humanize = {};
|
14
|
+
|
15
|
+
if (typeof exports !== 'undefined') {
|
16
|
+
if (typeof module !== 'undefined' && module.exports) {
|
17
|
+
exports = module.exports = humanize;
|
18
|
+
}
|
19
|
+
exports.humanize = humanize;
|
20
|
+
} else {
|
21
|
+
if (typeof define === 'function' && define.amd) {
|
22
|
+
define('humanize', function() {
|
23
|
+
return humanize;
|
24
|
+
});
|
25
|
+
}
|
26
|
+
root.humanize = humanize;
|
27
|
+
}
|
28
|
+
|
29
|
+
humanize.noConflict = function() {
|
30
|
+
root.humanize = previousHumanize;
|
31
|
+
return this;
|
32
|
+
};
|
33
|
+
|
34
|
+
humanize.pad = function(str, count, padChar, type) {
|
35
|
+
str += '';
|
36
|
+
if (!padChar) {
|
37
|
+
padChar = ' ';
|
38
|
+
} else if (padChar.length > 1) {
|
39
|
+
padChar = padChar.charAt(0);
|
40
|
+
}
|
41
|
+
type = (type === undefined) ? 'left' : 'right';
|
42
|
+
|
43
|
+
if (type === 'right') {
|
44
|
+
while (str.length < count) {
|
45
|
+
str = str + padChar;
|
46
|
+
}
|
47
|
+
} else {
|
48
|
+
// default to left
|
49
|
+
while (str.length < count) {
|
50
|
+
str = padChar + str;
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
return str;
|
55
|
+
};
|
56
|
+
|
57
|
+
// gets current unix time
|
58
|
+
humanize.time = function() {
|
59
|
+
return new Date().getTime() / 1000;
|
60
|
+
};
|
61
|
+
|
62
|
+
/**
|
63
|
+
* PHP-inspired date
|
64
|
+
*/
|
65
|
+
|
66
|
+
/* jan feb mar apr may jun jul aug sep oct nov dec */
|
67
|
+
var dayTableCommon = [ 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 ];
|
68
|
+
var dayTableLeap = [ 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 ];
|
69
|
+
// var mtable_common[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
70
|
+
// static int ml_table_leap[13] = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
71
|
+
|
72
|
+
|
73
|
+
humanize.date = function(format, timestamp) {
|
74
|
+
var jsdate = ((timestamp === undefined) ? new Date() : // Not provided
|
75
|
+
(timestamp instanceof Date) ? new Date(timestamp) : // JS Date()
|
76
|
+
new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int)
|
77
|
+
);
|
78
|
+
|
79
|
+
var formatChr = /\\?([a-z])/gi;
|
80
|
+
var formatChrCb = function (t, s) {
|
81
|
+
return f[t] ? f[t]() : s;
|
82
|
+
};
|
83
|
+
|
84
|
+
var shortDayTxt = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
85
|
+
var monthTxt = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
86
|
+
|
87
|
+
var f = {
|
88
|
+
/* Day */
|
89
|
+
// Day of month w/leading 0; 01..31
|
90
|
+
d: function () { return humanize.pad(f.j(), 2, '0'); },
|
91
|
+
|
92
|
+
// Shorthand day name; Mon..Sun
|
93
|
+
D: function () { return f.l().slice(0, 3); },
|
94
|
+
|
95
|
+
// Day of month; 1..31
|
96
|
+
j: function () { return jsdate.getDate(); },
|
97
|
+
|
98
|
+
// Full day name; Monday..Sunday
|
99
|
+
l: function () { return shortDayTxt[f.w()]; },
|
100
|
+
|
101
|
+
// ISO-8601 day of week; 1[Mon]..7[Sun]
|
102
|
+
N: function () { return f.w() || 7; },
|
103
|
+
|
104
|
+
// Ordinal suffix for day of month; st, nd, rd, th
|
105
|
+
S: function () {
|
106
|
+
var j = f.j();
|
107
|
+
return j > 4 && j < 21 ? 'th' : {1: 'st', 2: 'nd', 3: 'rd'}[j % 10] || 'th';
|
108
|
+
},
|
109
|
+
|
110
|
+
// Day of week; 0[Sun]..6[Sat]
|
111
|
+
w: function () { return jsdate.getDay(); },
|
112
|
+
|
113
|
+
// Day of year; 0..365
|
114
|
+
z: function () {
|
115
|
+
return (f.L() ? dayTableLeap[f.n()] : dayTableCommon[f.n()]) + f.j() - 1;
|
116
|
+
},
|
117
|
+
|
118
|
+
/* Week */
|
119
|
+
// ISO-8601 week number
|
120
|
+
W: function () {
|
121
|
+
// days between midweek of this week and jan 4
|
122
|
+
// (f.z() - f.N() + 1 + 3.5) - 3
|
123
|
+
var midWeekDaysFromJan4 = f.z() - f.N() + 1.5;
|
124
|
+
// 1 + number of weeks + rounded week
|
125
|
+
return humanize.pad(1 + Math.floor(Math.abs(midWeekDaysFromJan4) / 7) + (midWeekDaysFromJan4 % 7 > 3.5 ? 1 : 0), 2, '0');
|
126
|
+
},
|
127
|
+
|
128
|
+
/* Month */
|
129
|
+
// Full month name; January..December
|
130
|
+
F: function () { return monthTxt[jsdate.getMonth()]; },
|
131
|
+
|
132
|
+
// Month w/leading 0; 01..12
|
133
|
+
m: function () { return humanize.pad(f.n(), 2, '0'); },
|
134
|
+
|
135
|
+
// Shorthand month name; Jan..Dec
|
136
|
+
M: function () { return f.F().slice(0, 3); },
|
137
|
+
|
138
|
+
// Month; 1..12
|
139
|
+
n: function () { return jsdate.getMonth() + 1; },
|
140
|
+
|
141
|
+
// Days in month; 28..31
|
142
|
+
t: function () { return (new Date(f.Y(), f.n(), 0)).getDate(); },
|
143
|
+
|
144
|
+
/* Year */
|
145
|
+
// Is leap year?; 0 or 1
|
146
|
+
L: function () { return new Date(f.Y(), 1, 29).getMonth() === 1 ? 1 : 0; },
|
147
|
+
|
148
|
+
// ISO-8601 year
|
149
|
+
o: function () {
|
150
|
+
var n = f.n();
|
151
|
+
var W = f.W();
|
152
|
+
return f.Y() + (n === 12 && W < 9 ? -1 : n === 1 && W > 9);
|
153
|
+
},
|
154
|
+
|
155
|
+
// Full year; e.g. 1980..2010
|
156
|
+
Y: function () { return jsdate.getFullYear(); },
|
157
|
+
|
158
|
+
// Last two digits of year; 00..99
|
159
|
+
y: function () { return (String(f.Y())).slice(-2); },
|
160
|
+
|
161
|
+
/* Time */
|
162
|
+
// am or pm
|
163
|
+
a: function () { return jsdate.getHours() > 11 ? 'pm' : 'am'; },
|
164
|
+
|
165
|
+
// AM or PM
|
166
|
+
A: function () { return f.a().toUpperCase(); },
|
167
|
+
|
168
|
+
// Swatch Internet time; 000..999
|
169
|
+
B: function () {
|
170
|
+
var unixTime = jsdate.getTime() / 1000;
|
171
|
+
var secondsPassedToday = unixTime % 86400 + 3600; // since it's based off of UTC+1
|
172
|
+
if (secondsPassedToday < 0) { secondsPassedToday += 86400; }
|
173
|
+
var beats = ((secondsPassedToday) / 86.4) % 1000;
|
174
|
+
if (unixTime < 0) {
|
175
|
+
return Math.ceil(beats);
|
176
|
+
}
|
177
|
+
return Math.floor(beats);
|
178
|
+
},
|
179
|
+
|
180
|
+
// 12-Hours; 1..12
|
181
|
+
g: function () { return f.G() % 12 || 12; },
|
182
|
+
|
183
|
+
// 24-Hours; 0..23
|
184
|
+
G: function () { return jsdate.getHours(); },
|
185
|
+
|
186
|
+
// 12-Hours w/leading 0; 01..12
|
187
|
+
h: function () { return humanize.pad(f.g(), 2, '0'); },
|
188
|
+
|
189
|
+
// 24-Hours w/leading 0; 00..23
|
190
|
+
H: function () { return humanize.pad(f.G(), 2, '0'); },
|
191
|
+
|
192
|
+
// Minutes w/leading 0; 00..59
|
193
|
+
i: function () { return humanize.pad(jsdate.getMinutes(), 2, '0'); },
|
194
|
+
|
195
|
+
// Seconds w/leading 0; 00..59
|
196
|
+
s: function () { return humanize.pad(jsdate.getSeconds(), 2, '0'); },
|
197
|
+
|
198
|
+
// Microseconds; 000000-999000
|
199
|
+
u: function () { return humanize.pad(jsdate.getMilliseconds() * 1000, 6, '0'); },
|
200
|
+
|
201
|
+
// Whether or not the date is in daylight savings time
|
202
|
+
/*
|
203
|
+
I: function () {
|
204
|
+
// Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC.
|
205
|
+
// If they are not equal, then DST is observed.
|
206
|
+
var Y = f.Y();
|
207
|
+
return 0 + ((new Date(Y, 0) - Date.UTC(Y, 0)) !== (new Date(Y, 6) - Date.UTC(Y, 6)));
|
208
|
+
},
|
209
|
+
*/
|
210
|
+
|
211
|
+
// Difference to GMT in hour format; e.g. +0200
|
212
|
+
O: function () {
|
213
|
+
var tzo = jsdate.getTimezoneOffset();
|
214
|
+
var tzoNum = Math.abs(tzo);
|
215
|
+
return (tzo > 0 ? '-' : '+') + humanize.pad(Math.floor(tzoNum / 60) * 100 + tzoNum % 60, 4, '0');
|
216
|
+
},
|
217
|
+
|
218
|
+
// Difference to GMT w/colon; e.g. +02:00
|
219
|
+
P: function () {
|
220
|
+
var O = f.O();
|
221
|
+
return (O.substr(0, 3) + ':' + O.substr(3, 2));
|
222
|
+
},
|
223
|
+
|
224
|
+
// Timezone offset in seconds (-43200..50400)
|
225
|
+
Z: function () { return -jsdate.getTimezoneOffset() * 60; },
|
226
|
+
|
227
|
+
// Full Date/Time, ISO-8601 date
|
228
|
+
c: function () { return 'Y-m-d\\TH:i:sP'.replace(formatChr, formatChrCb); },
|
229
|
+
|
230
|
+
// RFC 2822
|
231
|
+
r: function () { return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb); },
|
232
|
+
|
233
|
+
// Seconds since UNIX epoch
|
234
|
+
U: function () { return jsdate.getTime() / 1000 || 0; }
|
235
|
+
};
|
236
|
+
|
237
|
+
return format.replace(formatChr, formatChrCb);
|
238
|
+
};
|
239
|
+
|
240
|
+
|
241
|
+
/**
|
242
|
+
* format number by adding thousands separaters and significant digits while rounding
|
243
|
+
*/
|
244
|
+
humanize.numberFormat = function(number, decimals, decPoint, thousandsSep) {
|
245
|
+
decimals = isNaN(decimals) ? 2 : Math.abs(decimals);
|
246
|
+
decPoint = (decPoint === undefined) ? '.' : decPoint;
|
247
|
+
thousandsSep = (thousandsSep === undefined) ? ',' : thousandsSep;
|
248
|
+
|
249
|
+
var sign = number < 0 ? '-' : '';
|
250
|
+
number = Math.abs(+number || 0);
|
251
|
+
|
252
|
+
var intPart = parseInt(number.toFixed(decimals), 10) + '';
|
253
|
+
var j = intPart.length > 3 ? intPart.length % 3 : 0;
|
254
|
+
|
255
|
+
return sign + (j ? intPart.substr(0, j) + thousandsSep : '') + intPart.substr(j).replace(/(\d{3})(?=\d)/g, '$1' + thousandsSep) + (decimals ? decPoint + Math.abs(number - intPart).toFixed(decimals).slice(2) : '');
|
256
|
+
};
|
257
|
+
|
258
|
+
|
259
|
+
/**
|
260
|
+
* For dates that are the current day or within one day, return 'today', 'tomorrow' or 'yesterday', as appropriate.
|
261
|
+
* Otherwise, format the date using the passed in format string.
|
262
|
+
*
|
263
|
+
* Examples (when 'today' is 17 Feb 2007):
|
264
|
+
* 16 Feb 2007 becomes yesterday.
|
265
|
+
* 17 Feb 2007 becomes today.
|
266
|
+
* 18 Feb 2007 becomes tomorrow.
|
267
|
+
* Any other day is formatted according to given argument or the DATE_FORMAT setting if no argument is given.
|
268
|
+
*/
|
269
|
+
humanize.naturalDay = function(timestamp, format) {
|
270
|
+
timestamp = (timestamp === undefined) ? humanize.time() : timestamp;
|
271
|
+
format = (format === undefined) ? 'Y-m-d' : format;
|
272
|
+
|
273
|
+
var oneDay = 86400;
|
274
|
+
var d = new Date();
|
275
|
+
var today = (new Date(d.getFullYear(), d.getMonth(), d.getDate())).getTime() / 1000;
|
276
|
+
|
277
|
+
if (timestamp < today && timestamp >= today - oneDay) {
|
278
|
+
return 'yesterday';
|
279
|
+
} else if (timestamp >= today && timestamp < today + oneDay) {
|
280
|
+
return 'today';
|
281
|
+
} else if (timestamp >= today + oneDay && timestamp < today + 2 * oneDay) {
|
282
|
+
return 'tomorrow';
|
283
|
+
}
|
284
|
+
|
285
|
+
return humanize.date(format, timestamp);
|
286
|
+
};
|
287
|
+
|
288
|
+
/**
|
289
|
+
* returns a string representing how many seconds, minutes or hours ago it was or will be in the future
|
290
|
+
* Will always return a relative time, most granular of seconds to least granular of years. See unit tests for more details
|
291
|
+
*/
|
292
|
+
humanize.relativeTime = function(timestamp) {
|
293
|
+
timestamp = (timestamp === undefined) ? humanize.time() : timestamp;
|
294
|
+
|
295
|
+
var currTime = humanize.time();
|
296
|
+
var timeDiff = currTime - timestamp;
|
297
|
+
|
298
|
+
// within 2 seconds
|
299
|
+
if (timeDiff < 2 && timeDiff > -2) {
|
300
|
+
return (timeDiff >= 0 ? 'just ' : '') + 'now';
|
301
|
+
}
|
302
|
+
|
303
|
+
// within a minute
|
304
|
+
if (timeDiff < 60 && timeDiff > -60) {
|
305
|
+
return (timeDiff >= 0 ? Math.floor(timeDiff) + ' seconds ago' : 'in ' + Math.floor(-timeDiff) + ' seconds');
|
306
|
+
}
|
307
|
+
|
308
|
+
// within 2 minutes
|
309
|
+
if (timeDiff < 120 && timeDiff > -120) {
|
310
|
+
return (timeDiff >= 0 ? 'about a minute ago' : 'in about a minute');
|
311
|
+
}
|
312
|
+
|
313
|
+
// within an hour
|
314
|
+
if (timeDiff < 3600 && timeDiff > -3600) {
|
315
|
+
return (timeDiff >= 0 ? Math.floor(timeDiff / 60) + ' minutes ago' : 'in ' + Math.floor(-timeDiff / 60) + ' minutes');
|
316
|
+
}
|
317
|
+
|
318
|
+
// within 2 hours
|
319
|
+
if (timeDiff < 7200 && timeDiff > -7200) {
|
320
|
+
return (timeDiff >= 0 ? 'about an hour ago' : 'in about an hour');
|
321
|
+
}
|
322
|
+
|
323
|
+
// within 24 hours
|
324
|
+
if (timeDiff < 86400 && timeDiff > -86400) {
|
325
|
+
return (timeDiff >= 0 ? Math.floor(timeDiff / 3600) + ' hours ago' : 'in ' + Math.floor(-timeDiff / 3600) + ' hours');
|
326
|
+
}
|
327
|
+
|
328
|
+
// within 2 days
|
329
|
+
var days2 = 2 * 86400;
|
330
|
+
if (timeDiff < days2 && timeDiff > -days2) {
|
331
|
+
return (timeDiff >= 0 ? '1 day ago' : 'in 1 day');
|
332
|
+
}
|
333
|
+
|
334
|
+
// within 29 days
|
335
|
+
var days29 = 29 * 86400;
|
336
|
+
if (timeDiff < days29 && timeDiff > -days29) {
|
337
|
+
return (timeDiff >= 0 ? Math.floor(timeDiff / 86400) + ' days ago' : 'in ' + Math.floor(-timeDiff / 86400) + ' days');
|
338
|
+
}
|
339
|
+
|
340
|
+
// within 60 days
|
341
|
+
var days60 = 60 * 86400;
|
342
|
+
if (timeDiff < days60 && timeDiff > -days60) {
|
343
|
+
return (timeDiff >= 0 ? 'about a month ago' : 'in about a month');
|
344
|
+
}
|
345
|
+
|
346
|
+
var currTimeYears = parseInt(humanize.date('Y', currTime), 10);
|
347
|
+
var timestampYears = parseInt(humanize.date('Y', timestamp), 10);
|
348
|
+
var currTimeMonths = currTimeYears * 12 + parseInt(humanize.date('n', currTime), 10);
|
349
|
+
var timestampMonths = timestampYears * 12 + parseInt(humanize.date('n', timestamp), 10);
|
350
|
+
|
351
|
+
// within a year
|
352
|
+
var monthDiff = currTimeMonths - timestampMonths;
|
353
|
+
if (monthDiff < 12 && monthDiff > -12) {
|
354
|
+
return (monthDiff >= 0 ? monthDiff + ' months ago' : 'in ' + (-monthDiff) + ' months');
|
355
|
+
}
|
356
|
+
|
357
|
+
var yearDiff = currTimeYears - timestampYears;
|
358
|
+
if (yearDiff < 2 && yearDiff > -2) {
|
359
|
+
return (yearDiff >= 0 ? 'a year ago' : 'in a year');
|
360
|
+
}
|
361
|
+
|
362
|
+
return (yearDiff >= 0 ? yearDiff + ' years ago' : 'in ' + (-yearDiff) + ' years');
|
363
|
+
};
|
364
|
+
|
365
|
+
/**
|
366
|
+
* Converts an integer to its ordinal as a string.
|
367
|
+
*
|
368
|
+
* 1 becomes 1st
|
369
|
+
* 2 becomes 2nd
|
370
|
+
* 3 becomes 3rd etc
|
371
|
+
*/
|
372
|
+
humanize.ordinal = function(number) {
|
373
|
+
number = parseInt(number, 10);
|
374
|
+
number = isNaN(number) ? 0 : number;
|
375
|
+
var sign = number < 0 ? '-' : '';
|
376
|
+
number = Math.abs(number);
|
377
|
+
var tens = number % 100;
|
378
|
+
|
379
|
+
return sign + number + (tens > 4 && tens < 21 ? 'th' : {1: 'st', 2: 'nd', 3: 'rd'}[number % 10] || 'th');
|
380
|
+
};
|
381
|
+
|
382
|
+
/**
|
383
|
+
* Formats the value like a 'human-readable' file size (i.e. '13 KB', '4.1 MB', '102 bytes', etc).
|
384
|
+
*
|
385
|
+
* For example:
|
386
|
+
* If value is 123456789, the output would be 117.7 MB.
|
387
|
+
*/
|
388
|
+
humanize.filesize = function(filesize, kilo, decimals, decPoint, thousandsSep) {
|
389
|
+
kilo = (kilo === undefined) ? 1024 : kilo;
|
390
|
+
decimals = isNaN(decimals) ? 2 : Math.abs(decimals);
|
391
|
+
decPoint = (decPoint === undefined) ? '.' : decPoint;
|
392
|
+
thousandsSep = (thousandsSep === undefined) ? ',' : thousandsSep;
|
393
|
+
if (filesize <= 0) { return '0 bytes'; }
|
394
|
+
|
395
|
+
var thresholds = [1];
|
396
|
+
var units = ['bytes', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb'];
|
397
|
+
if (filesize < kilo) { return humanize.numberFormat(filesize, 0) + ' ' + units[0]; }
|
398
|
+
|
399
|
+
for (var i = 1; i < units.length; i++) {
|
400
|
+
thresholds[i] = thresholds[i-1] * kilo;
|
401
|
+
if (filesize < thresholds[i]) {
|
402
|
+
return humanize.numberFormat(filesize / thresholds[i-1], decimals, decPoint, thousandsSep) + ' ' + units[i-1];
|
403
|
+
}
|
404
|
+
}
|
405
|
+
|
406
|
+
// use the last unit if we drop out to here
|
407
|
+
return humanize.numberFormat(filesize / thresholds[units.length - 1], decimals, decPoint, thousandsSep) + ' ' + units[units.length - 1];
|
408
|
+
};
|
409
|
+
|
410
|
+
/**
|
411
|
+
* Replaces line breaks in plain text with appropriate HTML
|
412
|
+
* A single newline becomes an HTML line break (<br />) and a new line followed by a blank line becomes a paragraph break (</p>).
|
413
|
+
*
|
414
|
+
* For example:
|
415
|
+
* If value is Joel\nis a\n\nslug, the output will be <p>Joel<br />is a</p><p>slug</p>
|
416
|
+
*/
|
417
|
+
humanize.linebreaks = function(str) {
|
418
|
+
// remove beginning and ending newlines
|
419
|
+
str = str.replace(/^([\n|\r]*)/, '');
|
420
|
+
str = str.replace(/([\n|\r]*)$/, '');
|
421
|
+
|
422
|
+
// normalize all to \n
|
423
|
+
str = str.replace(/(\r\n|\n|\r)/g, "\n");
|
424
|
+
|
425
|
+
// any consecutive new lines more than 2 gets turned into p tags
|
426
|
+
str = str.replace(/(\n{2,})/g, '</p><p>');
|
427
|
+
|
428
|
+
// any that are singletons get turned into br
|
429
|
+
str = str.replace(/\n/g, '<br />');
|
430
|
+
return '<p>' + str + '</p>';
|
431
|
+
};
|
432
|
+
|
433
|
+
/**
|
434
|
+
* Converts all newlines in a piece of plain text to HTML line breaks (<br />).
|
435
|
+
*/
|
436
|
+
humanize.nl2br = function(str) {
|
437
|
+
return str.replace(/(\r\n|\n|\r)/g, '<br />');
|
438
|
+
};
|
439
|
+
|
440
|
+
/**
|
441
|
+
* Truncates a string if it is longer than the specified number of characters.
|
442
|
+
* Truncated strings will end with a translatable ellipsis sequence ('…').
|
443
|
+
*/
|
444
|
+
humanize.truncatechars = function(string, length) {
|
445
|
+
if (string.length <= length) { return string; }
|
446
|
+
return string.substr(0, length) + '…';
|
447
|
+
};
|
448
|
+
|
449
|
+
/**
|
450
|
+
* Truncates a string after a certain number of words.
|
451
|
+
* Newlines within the string will be removed.
|
452
|
+
*/
|
453
|
+
humanize.truncatewords = function(string, numWords) {
|
454
|
+
var words = string.split(' ');
|
455
|
+
if (words.length < numWords) { return string; }
|
456
|
+
return words.slice(0, numWords).join(' ') + '…';
|
457
|
+
};
|
458
|
+
|
459
|
+
}).call(this);
|