2012-11-30 18:33:32 +01:00
|
|
|
var util = (function () {
|
|
|
|
|
|
|
|
var exports = {};
|
|
|
|
|
|
|
|
// From MDN: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/random
|
|
|
|
exports.random_int = function random_int(min, max) {
|
|
|
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
|
|
};
|
|
|
|
|
2013-02-20 00:49:21 +01:00
|
|
|
// Like C++'s std::lower_bound. Returns the first index at which
|
|
|
|
// `value` could be inserted without changing the ordering. Assumes
|
|
|
|
// the array is sorted.
|
|
|
|
//
|
|
|
|
// `first` and `last` are indices and `less` is an optionally-specified
|
|
|
|
// function that returns true if
|
|
|
|
// array[i] < value
|
|
|
|
// for some i and false otherwise.
|
|
|
|
//
|
|
|
|
// Usage: lower_bound(array, value, [less])
|
|
|
|
// lower_bound(array, first, last, value, [less])
|
|
|
|
exports.lower_bound = function (array, arg1, arg2, arg3, arg4) {
|
2016-12-02 17:09:31 +01:00
|
|
|
var first;
|
|
|
|
var last;
|
|
|
|
var value;
|
|
|
|
var less;
|
2013-02-20 00:49:21 +01:00
|
|
|
if (arg3 === undefined) {
|
|
|
|
first = 0;
|
|
|
|
last = array.length;
|
|
|
|
value = arg1;
|
|
|
|
less = arg2;
|
|
|
|
} else {
|
|
|
|
first = arg1;
|
|
|
|
last = arg2;
|
|
|
|
value = arg3;
|
|
|
|
less = arg4;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (less === undefined) {
|
|
|
|
less = function (a, b) { return a < b; };
|
|
|
|
}
|
|
|
|
|
|
|
|
var len = last - first;
|
|
|
|
var middle;
|
|
|
|
var step;
|
|
|
|
while (len > 0) {
|
|
|
|
step = Math.floor(len / 2);
|
|
|
|
middle = first + step;
|
2014-03-11 20:17:33 +01:00
|
|
|
if (less(array[middle], value, middle)) {
|
2013-02-20 00:49:21 +01:00
|
|
|
first = middle;
|
2016-11-30 19:05:04 +01:00
|
|
|
first += 1;
|
2013-02-20 00:49:21 +01:00
|
|
|
len = len - step - 1;
|
2016-06-09 23:02:49 +02:00
|
|
|
} else {
|
2013-02-20 00:49:21 +01:00
|
|
|
len = step;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return first;
|
|
|
|
};
|
|
|
|
|
2017-03-16 21:40:10 +01:00
|
|
|
// Produces an easy-to-read preview on an HTML element. Currently
|
|
|
|
// only used for including in error report emails; be sure to discuss
|
|
|
|
// with other developers before using it in a user-facing context
|
|
|
|
// because it is not XSS-safe.
|
|
|
|
exports.preview_node = function (node) {
|
|
|
|
if (node.constructor === jQuery) {
|
|
|
|
node = node[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
var tag = node.tagName.toLowerCase();
|
|
|
|
var className = node.className.length ? node.className : false;
|
|
|
|
var id = node.id.length ? node.id : false;
|
|
|
|
|
|
|
|
var node_preview = "<" + tag +
|
|
|
|
(id ? " id='" + id + "'" : "") +
|
|
|
|
(className ? " class='" + className + "'" : "") +
|
|
|
|
"></" + tag + ">";
|
|
|
|
|
|
|
|
return node_preview;
|
|
|
|
};
|
|
|
|
|
2016-08-27 04:13:35 +02:00
|
|
|
exports.same_stream_and_topic = function util_same_stream_and_topic(a, b) {
|
|
|
|
// Streams and topics are case-insensitive.
|
2017-02-23 03:53:02 +01:00
|
|
|
return ((a.stream_id === b.stream_id) &&
|
2013-02-26 23:09:15 +01:00
|
|
|
(a.subject.toLowerCase() === b.subject.toLowerCase()));
|
|
|
|
};
|
|
|
|
|
2016-06-08 06:06:57 +02:00
|
|
|
exports.is_pm_recipient = function (email, message) {
|
|
|
|
var recipients = message.reply_to.toLowerCase().split(',');
|
|
|
|
return recipients.indexOf(email.toLowerCase()) !== -1;
|
|
|
|
};
|
|
|
|
|
2016-06-10 20:15:08 +02:00
|
|
|
exports.extract_pm_recipients = function (recipients) {
|
2016-11-15 06:40:19 +01:00
|
|
|
return _.filter(recipients.split(/\s*[,;]\s*/), function (recipient) {
|
|
|
|
return recipient.trim() !== "";
|
|
|
|
});
|
2016-06-10 20:15:08 +02:00
|
|
|
};
|
|
|
|
|
2013-02-26 23:09:15 +01:00
|
|
|
exports.same_recipient = function util_same_recipient(a, b) {
|
2013-08-01 17:47:48 +02:00
|
|
|
if ((a === undefined) || (b === undefined)) {
|
2013-02-26 23:09:15 +01:00
|
|
|
return false;
|
2013-08-01 17:47:48 +02:00
|
|
|
}
|
|
|
|
if (a.type !== b.type) {
|
2013-02-26 23:09:15 +01:00
|
|
|
return false;
|
2013-08-01 17:47:48 +02:00
|
|
|
}
|
2013-02-26 23:09:15 +01:00
|
|
|
|
|
|
|
switch (a.type) {
|
|
|
|
case 'private':
|
2017-02-25 00:57:09 +01:00
|
|
|
if (a.to_user_ids === undefined) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return a.to_user_ids === b.to_user_ids;
|
2013-02-26 23:09:15 +01:00
|
|
|
case 'stream':
|
2016-08-27 04:13:35 +02:00
|
|
|
return exports.same_stream_and_topic(a, b);
|
2013-02-26 23:09:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// should never get here
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.same_sender = function util_same_sender(a, b) {
|
|
|
|
return ((a !== undefined) && (b !== undefined) &&
|
2016-03-26 13:16:30 +01:00
|
|
|
(a.sender_email.toLowerCase() === b.sender_email.toLowerCase()));
|
2013-02-26 23:09:15 +01:00
|
|
|
};
|
|
|
|
|
2013-06-17 20:31:31 +02:00
|
|
|
exports.normalize_recipients = function (recipients) {
|
2013-07-26 20:27:06 +02:00
|
|
|
// Converts a string listing emails of message recipients
|
|
|
|
// into a canonical formatting: emails sorted ASCIIbetically
|
|
|
|
// with exactly one comma and no spaces between each.
|
2016-11-19 00:33:32 +01:00
|
|
|
recipients = _.map(recipients.split(','), function (s) { return s.trim(); });
|
|
|
|
recipients = _.map(recipients, function (s) { return s.toLowerCase(); });
|
2013-07-29 22:49:42 +02:00
|
|
|
recipients = _.filter(recipients, function (s) { return s.length > 0; });
|
2013-06-17 20:31:31 +02:00
|
|
|
recipients.sort();
|
|
|
|
return recipients.join(',');
|
|
|
|
};
|
|
|
|
|
2013-03-28 22:32:17 +01:00
|
|
|
// Avoid URI decode errors by removing characters from the end
|
|
|
|
// one by one until the decode succeeds. This makes sense if
|
|
|
|
// we are decoding input that the user is in the middle of
|
|
|
|
// typing.
|
|
|
|
exports.robust_uri_decode = function (str) {
|
|
|
|
var end = str.length;
|
|
|
|
while (end > 0) {
|
|
|
|
try {
|
|
|
|
return decodeURIComponent(str.substring(0, end));
|
|
|
|
} catch (e) {
|
2013-08-01 17:47:48 +02:00
|
|
|
if (!(e instanceof URIError)) {
|
2013-03-28 22:32:17 +01:00
|
|
|
throw e;
|
2013-08-01 17:47:48 +02:00
|
|
|
}
|
2016-11-30 19:05:04 +01:00
|
|
|
end -= 1;
|
2013-03-28 22:32:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return '';
|
|
|
|
};
|
|
|
|
|
2017-02-12 04:30:55 +01:00
|
|
|
exports.rtrim = function (str) {
|
|
|
|
return str.replace(/\s+$/, '');
|
|
|
|
};
|
|
|
|
|
2013-05-03 19:16:50 +02:00
|
|
|
// If we can, use a locale-aware sorter. However, if the browser
|
|
|
|
// doesn't support the ECMAScript Internationalization API
|
|
|
|
// Specification, do a dumb string comparison because
|
|
|
|
// String.localeCompare is really slow.
|
|
|
|
exports.strcmp = (function () {
|
|
|
|
try {
|
|
|
|
var collator = new Intl.Collator();
|
|
|
|
return collator.compare;
|
|
|
|
} catch (e) {
|
2016-12-05 02:35:14 +01:00
|
|
|
// continue regardless of error
|
2013-05-03 19:16:50 +02:00
|
|
|
}
|
|
|
|
|
2016-12-05 07:02:18 +01:00
|
|
|
return function util_strcmp(a, b) {
|
2013-05-03 19:16:50 +02:00
|
|
|
return (a < b ? -1 : (a > b ? 1 : 0));
|
|
|
|
};
|
|
|
|
}());
|
|
|
|
|
2013-07-05 17:43:56 +02:00
|
|
|
exports.escape_regexp = function (string) {
|
2013-07-01 23:51:03 +02:00
|
|
|
// code from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
|
|
|
|
// Modified to escape the ^ to appease jslint. :/
|
|
|
|
return string.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, "\\$1");
|
|
|
|
};
|
|
|
|
|
2013-05-07 19:07:05 +02:00
|
|
|
exports.array_compare = function util_array_compare(a, b) {
|
|
|
|
if (a.length !== b.length) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
var i;
|
2016-11-30 19:05:04 +01:00
|
|
|
for (i = 0; i < a.length; i += 1) {
|
2013-05-07 19:07:05 +02:00
|
|
|
if (a[i] !== b[i]) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
2013-08-06 21:34:43 +02:00
|
|
|
/* Represents a value that is expensive to compute and should be
|
|
|
|
* computed on demand and then cached. The value can be forcefully
|
|
|
|
* recalculated on the next call to get() by calling reset().
|
|
|
|
*
|
|
|
|
* You must supply a option to the constructor called compute_value
|
|
|
|
* which should be a function that computes the uncached value.
|
|
|
|
*/
|
|
|
|
var unassigned_value_sentinel = {};
|
|
|
|
exports.CachedValue = function (opts) {
|
|
|
|
this._value = unassigned_value_sentinel;
|
|
|
|
_.extend(this, opts);
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.CachedValue.prototype = {
|
|
|
|
get: function CachedValue_get() {
|
|
|
|
if (this._value === unassigned_value_sentinel) {
|
|
|
|
this._value = this.compute_value();
|
|
|
|
}
|
|
|
|
return this._value;
|
|
|
|
},
|
|
|
|
|
|
|
|
reset: function CachedValue_reset() {
|
|
|
|
this._value = unassigned_value_sentinel;
|
2017-01-12 00:17:43 +01:00
|
|
|
},
|
2013-08-06 21:34:43 +02:00
|
|
|
};
|
2013-07-11 23:42:44 +02:00
|
|
|
|
2014-03-07 00:44:15 +01:00
|
|
|
exports.execute_early = function (func) {
|
|
|
|
if (page_params.test_suite) {
|
|
|
|
$(document).one('phantom_page_loaded', func);
|
|
|
|
} else {
|
|
|
|
$(func);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-08-09 22:38:45 +02:00
|
|
|
exports.is_all_or_everyone_mentioned = function (message_content) {
|
2016-08-15 22:24:40 +02:00
|
|
|
var all_everyone_re = /(^|\s)(@\*{2}(all|everyone)\*{2})|(@(all|everyone))($|\s)/;
|
2016-08-09 22:38:45 +02:00
|
|
|
return all_everyone_re.test(message_content);
|
|
|
|
};
|
|
|
|
|
2016-12-13 20:03:23 +01:00
|
|
|
exports.move_array_elements_to_front = function util_move_array_elements_to_front(array, selected) {
|
|
|
|
var i;
|
|
|
|
var selected_hash = {};
|
|
|
|
for (i = 0; i < selected.length; i += 1) {
|
|
|
|
selected_hash[selected[i]] = true;
|
|
|
|
}
|
|
|
|
var selected_elements = [];
|
|
|
|
var unselected_elements = [];
|
|
|
|
for (i = 0; i < array.length; i += 1) {
|
|
|
|
if (selected_hash[array[i]]) {
|
|
|
|
selected_elements.push(array[i]);
|
|
|
|
} else {
|
|
|
|
unselected_elements.push(array[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Add the unselected elements after the selected ones
|
|
|
|
return selected_elements.concat(unselected_elements);
|
|
|
|
};
|
|
|
|
|
2017-03-09 02:34:45 +01:00
|
|
|
// check by the userAgent string if a user's client is likely mobile.
|
|
|
|
exports.is_mobile = function () {
|
|
|
|
var regex = "Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini";
|
|
|
|
return new RegExp(regex, "i").test(window.navigator.userAgent);
|
|
|
|
};
|
|
|
|
|
2017-05-25 18:04:42 +02:00
|
|
|
exports.prefix_sort = function (query, objs, get_item) {
|
|
|
|
// Based on Bootstrap typeahead's default sorter, but taking into
|
|
|
|
// account case sensitivity on "begins with"
|
|
|
|
var beginswithCaseSensitive = [];
|
|
|
|
var beginswithCaseInsensitive = [];
|
|
|
|
var noMatch = [];
|
|
|
|
|
|
|
|
var obj = objs.shift();
|
|
|
|
while (obj) {
|
|
|
|
var item;
|
|
|
|
if (get_item) {
|
|
|
|
item = get_item(obj);
|
|
|
|
} else {
|
|
|
|
item = obj;
|
|
|
|
}
|
|
|
|
if (item.indexOf(query) === 0) {
|
|
|
|
beginswithCaseSensitive.push(obj);
|
|
|
|
} else if (item.toLowerCase().indexOf(query.toLowerCase()) === 0) {
|
|
|
|
beginswithCaseInsensitive.push(obj);
|
|
|
|
} else {
|
|
|
|
noMatch.push(obj);
|
|
|
|
}
|
|
|
|
obj = objs.shift();
|
|
|
|
}
|
|
|
|
return { matches: beginswithCaseSensitive.concat(beginswithCaseInsensitive),
|
|
|
|
rest: noMatch };
|
|
|
|
};
|
|
|
|
|
2012-11-30 18:33:32 +01:00
|
|
|
return exports;
|
2017-05-25 18:04:42 +02:00
|
|
|
|
2012-11-30 18:33:32 +01:00
|
|
|
}());
|
2013-08-06 19:06:45 +02:00
|
|
|
if (typeof module !== 'undefined') {
|
|
|
|
module.exports = util;
|
|
|
|
}
|