2016-02-28 09:30:07 +01:00
|
|
|
/*global escape, locales, Jed */
|
2015-01-16 22:07:27 +01:00
|
|
|
(function (root, factory) {
|
2016-02-23 08:13:30 +01:00
|
|
|
define([
|
|
|
|
"jquery",
|
|
|
|
"jquery.browser",
|
|
|
|
"underscore",
|
2016-02-27 17:16:04 +01:00
|
|
|
"converse-templates"
|
2016-02-23 08:13:30 +01:00
|
|
|
], factory);
|
2016-02-28 01:37:38 +01:00
|
|
|
}(this, function ($, dummy, _, templates) {
|
2014-11-16 12:47:30 +01:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
var XFORM_TYPE_MAP = {
|
|
|
|
'text-private': 'password',
|
2015-10-29 08:12:13 +01:00
|
|
|
'text-single': 'text',
|
2014-11-16 12:47:30 +01:00
|
|
|
'fixed': 'label',
|
|
|
|
'boolean': 'checkbox',
|
|
|
|
'hidden': 'hidden',
|
|
|
|
'jid-multi': 'textarea',
|
|
|
|
'list-single': 'dropdown',
|
|
|
|
'list-multi': 'dropdown'
|
|
|
|
};
|
|
|
|
|
2016-05-28 13:13:49 +02:00
|
|
|
var isImage = function (url) {
|
|
|
|
var deferred = new $.Deferred();
|
|
|
|
$("<img>", {
|
|
|
|
src: url,
|
|
|
|
error: deferred.reject,
|
|
|
|
load: deferred.resolve
|
|
|
|
});
|
|
|
|
return deferred.promise();
|
|
|
|
};
|
|
|
|
|
2014-11-17 13:55:52 +01:00
|
|
|
$.expr[':'].emptyVal = function(obj){
|
|
|
|
return obj.value === '';
|
|
|
|
};
|
|
|
|
|
2014-10-06 20:23:59 +02:00
|
|
|
$.fn.hasScrollBar = function() {
|
|
|
|
if (!$.contains(document, this.get(0))) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if(this.parent().height() < this.get(0).scrollHeight) {
|
|
|
|
return true;
|
|
|
|
}
|
2014-08-07 22:18:44 +02:00
|
|
|
return false;
|
2014-10-06 20:23:59 +02:00
|
|
|
};
|
2014-10-13 21:15:25 +02:00
|
|
|
|
2016-05-28 13:13:49 +02:00
|
|
|
$.fn.throttledHTML = _.throttle($.fn.html, 500);
|
|
|
|
|
2014-10-13 21:15:25 +02:00
|
|
|
$.fn.addHyperlinks = function () {
|
|
|
|
if (this.length > 0) {
|
|
|
|
this.each(function (i, obj) {
|
2016-05-28 13:28:32 +02:00
|
|
|
var prot, escaped_url;
|
2016-05-28 13:13:49 +02:00
|
|
|
var $obj = $(obj);
|
|
|
|
var x = $obj.html();
|
2016-05-28 13:28:32 +02:00
|
|
|
var list = x.match(/\b(https?:\/\/|www\.|https?:\/\/www\.)[^\s<]{2,200}\b/g );
|
|
|
|
if (list) {
|
|
|
|
for (i=0; i<list.length; i++) {
|
|
|
|
prot = list[i].indexOf('http://') === 0 || list[i].indexOf('https://') === 0 ? '' : 'http://';
|
|
|
|
escaped_url = encodeURI(decodeURI(list[i])).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
|
|
|
|
x = x.replace(list[i], '<a target="_blank" rel="noopener" href="' + prot + escaped_url + '">'+ list[i] + '</a>' );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$obj.html(x);
|
|
|
|
_.each(list, function (url) {
|
|
|
|
isImage(url).then(function () {
|
|
|
|
var prot = url.indexOf('http://') === 0 || url.indexOf('https://') === 0 ? '' : 'http://';
|
|
|
|
var escaped_url = encodeURI(decodeURI(url)).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
|
|
|
|
var new_url = '<a target="_blank" rel="noopener" href="' + prot + escaped_url + '">'+ url + '</a>';
|
|
|
|
event.target.className = 'chat-image';
|
|
|
|
x = x.replace(new_url, event.target.outerHTML);
|
|
|
|
$obj.throttledHTML(x);
|
|
|
|
});
|
2016-05-28 13:13:49 +02:00
|
|
|
});
|
2014-10-13 21:15:25 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
};
|
2014-10-13 22:02:55 +02:00
|
|
|
|
2015-07-11 12:18:05 +02:00
|
|
|
$.fn.addEmoticons = function (allowed) {
|
|
|
|
if (allowed) {
|
2015-07-10 15:02:48 +02:00
|
|
|
if (this.length > 0) {
|
|
|
|
this.each(function (i, obj) {
|
|
|
|
var text = $(obj).html();
|
|
|
|
text = text.replace(/>:\)/g, '<span class="emoticon icon-evil"></span>');
|
|
|
|
text = text.replace(/:\)/g, '<span class="emoticon icon-smiley"></span>');
|
|
|
|
text = text.replace(/:\-\)/g, '<span class="emoticon icon-smiley"></span>');
|
|
|
|
text = text.replace(/;\)/g, '<span class="emoticon icon-wink"></span>');
|
|
|
|
text = text.replace(/;\-\)/g, '<span class="emoticon icon-wink"></span>');
|
|
|
|
text = text.replace(/:D/g, '<span class="emoticon icon-grin"></span>');
|
|
|
|
text = text.replace(/:\-D/g, '<span class="emoticon icon-grin"></span>');
|
|
|
|
text = text.replace(/:P/g, '<span class="emoticon icon-tongue"></span>');
|
|
|
|
text = text.replace(/:\-P/g, '<span class="emoticon icon-tongue"></span>');
|
|
|
|
text = text.replace(/:p/g, '<span class="emoticon icon-tongue"></span>');
|
|
|
|
text = text.replace(/:\-p/g, '<span class="emoticon icon-tongue"></span>');
|
|
|
|
text = text.replace(/8\)/g, '<span class="emoticon icon-cool"></span>');
|
|
|
|
text = text.replace(/:S/g, '<span class="emoticon icon-confused"></span>');
|
|
|
|
text = text.replace(/:\\/g, '<span class="emoticon icon-wondering"></span>');
|
|
|
|
text = text.replace(/:\/ /g, '<span class="emoticon icon-wondering"></span>');
|
|
|
|
text = text.replace(/>:\(/g, '<span class="emoticon icon-angry"></span>');
|
|
|
|
text = text.replace(/:\(/g, '<span class="emoticon icon-sad"></span>');
|
|
|
|
text = text.replace(/:\-\(/g, '<span class="emoticon icon-sad"></span>');
|
|
|
|
text = text.replace(/:O/g, '<span class="emoticon icon-shocked"></span>');
|
|
|
|
text = text.replace(/:\-O/g, '<span class="emoticon icon-shocked"></span>');
|
|
|
|
text = text.replace(/\=\-O/g, '<span class="emoticon icon-shocked"></span>');
|
|
|
|
text = text.replace(/\(\^.\^\)b/g, '<span class="emoticon icon-thumbs-up"></span>');
|
|
|
|
text = text.replace(/<3/g, '<span class="emoticon icon-heart"></span>');
|
|
|
|
$(obj).html(text);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
2014-10-13 22:02:55 +02:00
|
|
|
var utils = {
|
|
|
|
// Translation machinery
|
|
|
|
// ---------------------
|
2014-10-29 17:13:54 +01:00
|
|
|
__: function (str) {
|
2016-02-28 01:37:38 +01:00
|
|
|
if (typeof Jed === "undefined") {
|
|
|
|
return str;
|
|
|
|
}
|
2016-02-20 16:06:12 +01:00
|
|
|
// FIXME: this can be refactored to take the i18n obj as a
|
|
|
|
// parameter.
|
2014-10-13 22:02:55 +02:00
|
|
|
// Translation factory
|
2015-01-16 22:07:27 +01:00
|
|
|
if (typeof this.i18n === "undefined") {
|
2014-10-13 22:02:55 +02:00
|
|
|
this.i18n = locales.en;
|
|
|
|
}
|
2015-01-16 22:33:18 +01:00
|
|
|
if (typeof this.i18n === "string") {
|
|
|
|
this.i18n = $.parseJSON(this.i18n);
|
|
|
|
}
|
2015-01-16 22:07:27 +01:00
|
|
|
if (typeof this.jed === "undefined") {
|
|
|
|
this.jed = new Jed(this.i18n);
|
|
|
|
}
|
|
|
|
var t = this.jed.translate(str);
|
2014-10-13 22:02:55 +02:00
|
|
|
if (arguments.length>1) {
|
|
|
|
return t.fetch.apply(t, [].slice.call(arguments,1));
|
|
|
|
} else {
|
|
|
|
return t.fetch();
|
|
|
|
}
|
2014-10-29 17:13:54 +01:00
|
|
|
},
|
2014-10-13 22:02:55 +02:00
|
|
|
|
|
|
|
___: function (str) {
|
|
|
|
/* XXX: This is part of a hack to get gettext to scan strings to be
|
2016-02-19 11:43:46 +01:00
|
|
|
* translated. Strings we cannot send to the function above because
|
|
|
|
* they require variable interpolation and we don't yet have the
|
|
|
|
* variables at scan time.
|
|
|
|
*
|
|
|
|
* See actionInfoMessages in src/converse-muc.js
|
|
|
|
*/
|
2014-10-13 22:02:55 +02:00
|
|
|
return str;
|
2014-11-16 12:47:30 +01:00
|
|
|
},
|
|
|
|
|
2016-07-18 13:41:31 +02:00
|
|
|
isLocaleAvailable: function (locale, available) {
|
|
|
|
/* Check whether the locale or sub locale (e.g. en-US, en) is supported.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (Function) available - returns a boolean indicating whether the locale is supported
|
|
|
|
*/
|
|
|
|
if (available(locale)) {
|
|
|
|
return locale;
|
|
|
|
} else {
|
|
|
|
var sublocale = locale.split("-")[0];
|
|
|
|
if (sublocale !== locale && available(sublocale)) {
|
|
|
|
return sublocale;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
detectLocale: function (library_check) {
|
|
|
|
/* Determine which locale is supported by the user's system as well
|
|
|
|
* as by the relevant library (e.g. converse.js or moment.js).
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (Function) library_check - returns a boolean indicating whether the locale is supported
|
|
|
|
*/
|
|
|
|
var locale, i;
|
|
|
|
if (window.navigator.userLanguage) {
|
|
|
|
locale = utils.isLocaleAvailable(window.navigator.userLanguage, library_check);
|
|
|
|
}
|
|
|
|
if (window.navigator.languages && !locale) {
|
|
|
|
for (i=0; i<window.navigator.languages.length && !locale; i++) {
|
|
|
|
locale = utils.isLocaleAvailable(window.navigator.languages[i], library_check);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (window.navigator.browserLanguage && !locale) {
|
|
|
|
locale = utils.isLocaleAvailable(window.navigator.browserLanguage, library_check);
|
|
|
|
}
|
|
|
|
if (window.navigator.language && !locale) {
|
|
|
|
locale = utils.isLocaleAvailable(window.navigator.language, library_check);
|
|
|
|
}
|
|
|
|
if (window.navigator.systemLanguage && !locale) {
|
|
|
|
locale = utils.isLocaleAvailable(window.navigator.systemLanguage, library_check);
|
|
|
|
}
|
|
|
|
return locale || 'en';
|
|
|
|
},
|
|
|
|
|
2016-05-30 20:19:10 +02:00
|
|
|
isOTRMessage: function (message) {
|
|
|
|
var $body = $(message).children('body'),
|
|
|
|
text = ($body.length > 0 ? $body.text() : undefined);
|
|
|
|
return text && !!text.match(/^\?OTR/);
|
|
|
|
},
|
|
|
|
|
2016-03-28 12:49:52 +02:00
|
|
|
isHeadlineMessage: function (message) {
|
|
|
|
var $message = $(message),
|
|
|
|
from_jid = $message.attr('from');
|
|
|
|
if ($message.attr('type') === 'headline' ||
|
2016-03-31 10:54:09 +02:00
|
|
|
// Some servers (I'm looking at you Prosody) don't set the message
|
|
|
|
// type to "headline" when sending server messages. For now we
|
|
|
|
// check if an @ signal is included, and if not, we assume it's
|
|
|
|
// a headline message.
|
|
|
|
( $message.attr('type') !== 'error' &&
|
|
|
|
typeof from_jid !== 'undefined' &&
|
|
|
|
from_jid.indexOf('@') === -1
|
|
|
|
)) {
|
2016-03-28 12:49:52 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2016-02-23 08:13:30 +01:00
|
|
|
refreshWebkit: function () {
|
|
|
|
/* This works around a webkit bug. Refreshes the browser's viewport,
|
|
|
|
* otherwise chatboxes are not moved along when one is closed.
|
|
|
|
*/
|
|
|
|
if ($.browser.webkit) {
|
|
|
|
var conversejs = document.getElementById('conversejs');
|
|
|
|
conversejs.style.display = 'none';
|
|
|
|
var tmp = conversejs.offsetHeight; // jshint ignore:line
|
|
|
|
conversejs.style.display = 'block';
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2014-11-17 09:44:42 +01:00
|
|
|
webForm2xForm: function (field) {
|
|
|
|
/* Takes an HTML DOM and turns it into an XForm field.
|
2015-01-16 22:07:27 +01:00
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (DOMElement) field - the field to convert
|
|
|
|
*/
|
2014-11-17 09:44:42 +01:00
|
|
|
var $input = $(field), value;
|
|
|
|
if ($input.is('[type=checkbox]')) {
|
|
|
|
value = $input.is(':checked') && 1 || 0;
|
|
|
|
} else if ($input.is('textarea')) {
|
|
|
|
value = [];
|
|
|
|
var lines = $input.val().split('\n');
|
|
|
|
for( var vk=0; vk<lines.length; vk++) {
|
|
|
|
var val = $.trim(lines[vk]);
|
|
|
|
if (val === '')
|
|
|
|
continue;
|
|
|
|
value.push(val);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
value = $input.val();
|
|
|
|
}
|
|
|
|
return $(templates.field({
|
|
|
|
name: $input.attr('name'),
|
|
|
|
value: value
|
|
|
|
}))[0];
|
|
|
|
},
|
|
|
|
|
2016-02-19 10:36:01 +01:00
|
|
|
contains: function (attr, query) {
|
|
|
|
return function (item) {
|
|
|
|
if (typeof attr === 'object') {
|
|
|
|
var value = false;
|
|
|
|
_.each(attr, function (a) {
|
|
|
|
value = value || item.get(a).toLowerCase().indexOf(query.toLowerCase()) !== -1;
|
|
|
|
});
|
|
|
|
return value;
|
|
|
|
} else if (typeof attr === 'string') {
|
|
|
|
return item.get(attr).toLowerCase().indexOf(query.toLowerCase()) !== -1;
|
|
|
|
} else {
|
|
|
|
throw new TypeError('contains: wrong attribute type. Must be string or array.');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2014-11-24 20:35:00 +01:00
|
|
|
xForm2webForm: function ($field, $stanza) {
|
2014-11-16 12:47:30 +01:00
|
|
|
/* Takes a field in XMPP XForm (XEP-004: Data Forms) format
|
2015-01-16 22:07:27 +01:00
|
|
|
* and turns it into a HTML DOM field.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (XMLElement) field - the field to convert
|
|
|
|
*/
|
2014-11-24 20:35:00 +01:00
|
|
|
|
|
|
|
// FIXME: take <required> into consideration
|
|
|
|
var options = [], j, $options, $values, value, values;
|
|
|
|
|
2015-10-25 18:49:35 +01:00
|
|
|
if ($field.attr('type') === 'list-single' || $field.attr('type') === 'list-multi') {
|
2014-11-16 12:47:30 +01:00
|
|
|
values = [];
|
|
|
|
$values = $field.children('value');
|
|
|
|
for (j=0; j<$values.length; j++) {
|
|
|
|
values.push($($values[j]).text());
|
|
|
|
}
|
|
|
|
$options = $field.children('option');
|
|
|
|
for (j=0; j<$options.length; j++) {
|
|
|
|
value = $($options[j]).find('value').text();
|
|
|
|
options.push(templates.select_option({
|
|
|
|
value: value,
|
|
|
|
label: $($options[j]).attr('label'),
|
2014-11-24 20:35:00 +01:00
|
|
|
selected: (values.indexOf(value) >= 0),
|
|
|
|
required: $field.find('required').length
|
2014-11-16 12:47:30 +01:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
return templates.form_select({
|
|
|
|
name: $field.attr('var'),
|
|
|
|
label: $field.attr('label'),
|
|
|
|
options: options.join(''),
|
2015-10-25 18:49:35 +01:00
|
|
|
multiple: ($field.attr('type') === 'list-multi'),
|
2014-11-24 20:35:00 +01:00
|
|
|
required: $field.find('required').length
|
2014-11-16 12:47:30 +01:00
|
|
|
});
|
2015-10-25 18:49:35 +01:00
|
|
|
} else if ($field.attr('type') === 'fixed') {
|
2014-11-24 20:35:00 +01:00
|
|
|
return $('<p class="form-help">').text($field.find('value').text());
|
2015-10-25 18:49:35 +01:00
|
|
|
} else if ($field.attr('type') === 'jid-multi') {
|
2014-11-16 12:47:30 +01:00
|
|
|
return templates.form_textarea({
|
|
|
|
name: $field.attr('var'),
|
|
|
|
label: $field.attr('label') || '',
|
2014-11-24 20:35:00 +01:00
|
|
|
value: $field.find('value').text(),
|
|
|
|
required: $field.find('required').length
|
2014-11-16 12:47:30 +01:00
|
|
|
});
|
2015-10-25 18:49:35 +01:00
|
|
|
} else if ($field.attr('type') === 'boolean') {
|
2014-11-16 12:47:30 +01:00
|
|
|
return templates.form_checkbox({
|
|
|
|
name: $field.attr('var'),
|
|
|
|
type: XFORM_TYPE_MAP[$field.attr('type')],
|
|
|
|
label: $field.attr('label') || '',
|
2014-11-24 20:35:00 +01:00
|
|
|
checked: $field.find('value').text() === "1" && 'checked="1"' || '',
|
|
|
|
required: $field.find('required').length
|
2014-11-16 12:47:30 +01:00
|
|
|
});
|
2014-11-26 21:26:28 +01:00
|
|
|
} else if ($field.attr('type') && $field.attr('var') === 'username') {
|
|
|
|
return templates.form_username({
|
|
|
|
domain: ' @'+this.domain,
|
|
|
|
name: $field.attr('var'),
|
|
|
|
type: XFORM_TYPE_MAP[$field.attr('type')],
|
|
|
|
label: $field.attr('label') || '',
|
|
|
|
value: $field.find('value').text(),
|
|
|
|
required: $field.find('required').length
|
|
|
|
});
|
2014-11-24 20:35:00 +01:00
|
|
|
} else if ($field.attr('type')) {
|
2014-11-16 12:47:30 +01:00
|
|
|
return templates.form_input({
|
|
|
|
name: $field.attr('var'),
|
|
|
|
type: XFORM_TYPE_MAP[$field.attr('type')],
|
|
|
|
label: $field.attr('label') || '',
|
2014-11-24 20:35:00 +01:00
|
|
|
value: $field.find('value').text(),
|
|
|
|
required: $field.find('required').length
|
2014-11-16 12:47:30 +01:00
|
|
|
});
|
2014-11-24 20:35:00 +01:00
|
|
|
} else {
|
|
|
|
if ($field.attr('var') === 'ocr') { // Captcha
|
|
|
|
return _.reduce(_.map($field.find('uri'),
|
|
|
|
$.proxy(function (uri) {
|
|
|
|
return templates.form_captcha({
|
|
|
|
label: this.$field.attr('label'),
|
|
|
|
name: this.$field.attr('var'),
|
|
|
|
data: this.$stanza.find('data[cid="'+uri.textContent.replace(/^cid:/, '')+'"]').text(),
|
|
|
|
type: uri.getAttribute('type'),
|
|
|
|
required: this.$field.find('required').length
|
|
|
|
});
|
|
|
|
}, {'$stanza': $stanza, '$field': $field})
|
|
|
|
),
|
|
|
|
function (memo, num) { return memo + num; }, ''
|
|
|
|
);
|
|
|
|
}
|
2014-11-16 12:47:30 +01:00
|
|
|
}
|
2014-10-13 22:02:55 +02:00
|
|
|
}
|
|
|
|
};
|
2016-02-19 10:36:01 +01:00
|
|
|
|
|
|
|
utils.contains.not = function (attr, query) {
|
|
|
|
return function (item) {
|
|
|
|
return !(utils.contains(attr, query)(item));
|
|
|
|
};
|
|
|
|
};
|
2014-10-13 22:02:55 +02:00
|
|
|
return utils;
|
2015-01-16 22:07:27 +01:00
|
|
|
}));
|