xmpp.chapril.org-conversejs/dist/converse-no-dependencies.js

33430 lines
1.8 MiB
JavaScript
Raw Normal View History

2017-03-05 10:45:18 +01:00
/** Converse.js
*
* An XMPP chat client that runs in the browser.
*
2018-03-05 14:43:53 +01:00
* Version: 3.3.4
2017-03-05 10:45:18 +01:00
*/
/* jshint ignore:start */
(function (root, factory) {
2018-03-25 21:21:43 +02:00
root.converse = factory();
2017-03-05 10:45:18 +01:00
}(this, function () {
//almond, and your modules will be inlined here
/* jshint ignore:end */
/**
* @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
2016-05-03 17:37:10 +02:00
* Released under MIT license, http://github.com/requirejs/almond/LICENSE
*/
//Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed.
/*global setTimeout: false */
var requirejs, require, define;
(function (undef) {
var main, req, makeMap, handlers,
defined = {},
waiting = {},
config = {},
defining = {},
hasOwn = Object.prototype.hasOwnProperty,
aps = [].slice,
jsSuffixRegExp = /\.js$/;
function hasProp(obj, prop) {
return hasOwn.call(obj, prop);
}
/**
* Given a relative module name, like ./something, normalize it to
* a real name that can be mapped to a path.
* @param {String} name the relative name
* @param {String} baseName a real name that the name arg is relative
* to.
* @returns {String} normalized name
*/
function normalize(name, baseName) {
var nameParts, nameSegment, mapValue, foundMap, lastIndex,
2016-05-03 17:37:10 +02:00
foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
baseParts = baseName && baseName.split("/"),
map = config.map,
starMap = (map && map['*']) || {};
//Adjust any relative paths.
2016-05-03 17:37:10 +02:00
if (name) {
name = name.split('/');
lastIndex = name.length - 1;
// If wanting node ID compatibility, strip .js from end
// of IDs. Have to do this here, and not in nameToUrl
// because node allows either .js or non .js to map
// to same file.
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
}
// Starts with a '.' so need the baseName
if (name[0].charAt(0) === '.' && baseParts) {
//Convert baseName to array, and lop off the last part,
//so that . matches that 'directory' and not name of the baseName's
//module. For instance, baseName of 'one/two/three', maps to
//'one/two/three.js', but we want the directory, 'one/two' for
//this normalization.
normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
name = normalizedBaseParts.concat(name);
}
//start trimDots
for (i = 0; i < name.length; i++) {
part = name[i];
if (part === '.') {
name.splice(i, 1);
i -= 1;
} else if (part === '..') {
// If at the start, or previous value is still ..,
// keep them so that when converted to a path it may
// still work when converted to a path, even though
// as an ID it is less than ideal. In larger point
// releases, may be better to just kick out an error.
if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
continue;
} else if (i > 0) {
name.splice(i - 1, 2);
i -= 2;
}
}
}
2016-05-03 17:37:10 +02:00
//end trimDots
name = name.join('/');
}
//Apply map config if available.
if ((baseParts || starMap) && map) {
nameParts = name.split('/');
for (i = nameParts.length; i > 0; i -= 1) {
nameSegment = nameParts.slice(0, i).join("/");
if (baseParts) {
//Find the longest baseName segment match in the config.
//So, do joins on the biggest to smallest lengths of baseParts.
for (j = baseParts.length; j > 0; j -= 1) {
mapValue = map[baseParts.slice(0, j).join('/')];
//baseName segment has config, find if it has one for
//this name.
if (mapValue) {
mapValue = mapValue[nameSegment];
if (mapValue) {
//Match, update name to the new value.
foundMap = mapValue;
foundI = i;
break;
}
}
}
}
if (foundMap) {
break;
}
//Check for a star map match, but just hold on to it,
//if there is a shorter segment match later in a matching
//config, then favor over this star map.
if (!foundStarMap && starMap && starMap[nameSegment]) {
foundStarMap = starMap[nameSegment];
starI = i;
}
}
if (!foundMap && foundStarMap) {
foundMap = foundStarMap;
foundI = starI;
}
if (foundMap) {
nameParts.splice(0, foundI, foundMap);
name = nameParts.join('/');
}
}
return name;
}
function makeRequire(relName, forceSync) {
return function () {
//A version of a require function that passes a moduleName
//value for items that may need to
//look up paths relative to the moduleName
var args = aps.call(arguments, 0);
//If first arg is not require('string'), and there is only
//one arg, it is the array form without a callback. Insert
//a null so that the following concat is correct.
if (typeof args[0] !== 'string' && args.length === 1) {
args.push(null);
}
return req.apply(undef, args.concat([relName, forceSync]));
};
}
function makeNormalize(relName) {
return function (name) {
return normalize(name, relName);
};
}
function makeLoad(depName) {
return function (value) {
defined[depName] = value;
};
}
function callDep(name) {
if (hasProp(waiting, name)) {
var args = waiting[name];
delete waiting[name];
defining[name] = true;
main.apply(undef, args);
}
if (!hasProp(defined, name) && !hasProp(defining, name)) {
throw new Error('No ' + name);
}
return defined[name];
}
//Turns a plugin!resource to [plugin, resource]
//with the plugin being undefined if the name
//did not have a plugin prefix.
function splitPrefix(name) {
var prefix,
index = name ? name.indexOf('!') : -1;
if (index > -1) {
prefix = name.substring(0, index);
name = name.substring(index + 1, name.length);
}
return [prefix, name];
}
//Creates a parts array for a relName where first part is plugin ID,
//second part is resource ID. Assumes relName has already been normalized.
function makeRelParts(relName) {
return relName ? splitPrefix(relName) : [];
}
/**
* Makes a name map, normalizing the name, and using a plugin
* for normalization if necessary. Grabs a ref to plugin
* too, as an optimization.
*/
makeMap = function (name, relParts) {
var plugin,
parts = splitPrefix(name),
prefix = parts[0],
relResourceName = relParts[1];
name = parts[1];
if (prefix) {
prefix = normalize(prefix, relResourceName);
plugin = callDep(prefix);
}
//Normalize according
if (prefix) {
if (plugin && plugin.normalize) {
name = plugin.normalize(name, makeNormalize(relResourceName));
} else {
name = normalize(name, relResourceName);
}
} else {
name = normalize(name, relResourceName);
parts = splitPrefix(name);
prefix = parts[0];
name = parts[1];
if (prefix) {
plugin = callDep(prefix);
}
}
//Using ridiculous property names for space reasons
return {
f: prefix ? prefix + '!' + name : name, //fullName
n: name,
pr: prefix,
p: plugin
};
};
function makeConfig(name) {
return function () {
return (config && config.config && config.config[name]) || {};
};
}
handlers = {
require: function (name) {
return makeRequire(name);
},
exports: function (name) {
var e = defined[name];
if (typeof e !== 'undefined') {
return e;
} else {
return (defined[name] = {});
}
},
module: function (name) {
return {
id: name,
uri: '',
exports: defined[name],
config: makeConfig(name)
};
}
};
main = function (name, deps, callback, relName) {
var cjsModule, depName, ret, map, i, relParts,
args = [],
callbackType = typeof callback,
usingExports;
//Use name if no relName
relName = relName || name;
relParts = makeRelParts(relName);
//Call the callback to define the module, if necessary.
if (callbackType === 'undefined' || callbackType === 'function') {
//Pull out the defined dependencies and pass the ordered
//values to the callback.
//Default to [require, exports, module] if no deps
deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
for (i = 0; i < deps.length; i += 1) {
map = makeMap(deps[i], relParts);
depName = map.f;
//Fast path CommonJS standard dependencies.
if (depName === "require") {
args[i] = handlers.require(name);
} else if (depName === "exports") {
//CommonJS module spec 1.1
args[i] = handlers.exports(name);
usingExports = true;
} else if (depName === "module") {
//CommonJS module spec 1.1
cjsModule = args[i] = handlers.module(name);
} else if (hasProp(defined, depName) ||
hasProp(waiting, depName) ||
hasProp(defining, depName)) {
args[i] = callDep(depName);
} else if (map.p) {
map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
args[i] = defined[depName];
} else {
throw new Error(name + ' missing ' + depName);
}
}
ret = callback ? callback.apply(defined[name], args) : undefined;
if (name) {
//If setting exports via "module" is in play,
//favor that over return value and exports. After that,
//favor a non-undefined return value over exports use.
if (cjsModule && cjsModule.exports !== undef &&
cjsModule.exports !== defined[name]) {
defined[name] = cjsModule.exports;
} else if (ret !== undef || !usingExports) {
//Use the return value from the function.
defined[name] = ret;
}
}
} else if (name) {
//May just be an object definition for the module. Only
//worry about defining if have a module name.
defined[name] = callback;
}
};
requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
if (typeof deps === "string") {
if (handlers[deps]) {
//callback in this case is really relName
return handlers[deps](callback);
}
//Just return the module wanted. In this scenario, the
//deps arg is the module name, and second arg (if passed)
//is just the relName.
//Normalize module name, if it contains . or ..
return callDep(makeMap(deps, makeRelParts(callback)).f);
} else if (!deps.splice) {
//deps is a config object, not an array.
config = deps;
if (config.deps) {
req(config.deps, config.callback);
}
if (!callback) {
return;
}
if (callback.splice) {
//callback is an array, which means it is a dependency list.
//Adjust args if there are dependencies
deps = callback;
callback = relName;
relName = null;
} else {
deps = undef;
}
}
//Support require(['a'])
callback = callback || function () {};
//If relName is a function, it is an errback handler,
//so remove it.
if (typeof relName === 'function') {
relName = forceSync;
forceSync = alt;
}
//Simulate async callback;
if (forceSync) {
main(undef, deps, callback, relName);
} else {
//Using a non-zero value because of concern for what old browsers
//do, and latest browsers "upgrade" to 4 if lower value is used:
//http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
//If want a value immediately, use require('id') instead -- something
//that works in almond on the global level, but not guaranteed and
//unlikely to work in other AMD implementations.
setTimeout(function () {
main(undef, deps, callback, relName);
}, 4);
}
return req;
};
/**
* Just drops the config on the floor, but returns req in case
* the config return value is used.
*/
req.config = function (cfg) {
return req(cfg);
};
/**
* Expose module registry for debugging and tooling
*/
requirejs._defined = defined;
define = function (name, deps, callback) {
if (typeof name !== 'string') {
throw new Error('See almond README: incorrect module build, no module name');
}
//This module may not have dependencies
if (!deps.splice) {
//deps is not an array, so probably means
//an object literal or factory function for
//the value. Adjust args.
callback = deps;
deps = [];
}
if (!hasProp(defined, name) && !hasProp(waiting, name)) {
waiting[name] = [name, deps, callback];
}
};
define.amd = {
jQuery: true
};
}());
2016-09-16 14:35:02 +02:00
define("almond", function(){});
/*!
2018-01-04 17:58:16 +01:00
* Sizzle CSS Selector Engine v2.3.3
* https://sizzlejs.com/
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* http://jquery.org/license
*
2018-01-04 17:58:16 +01:00
* Date: 2016-08-08
*/
(function( window ) {
var i,
support,
Expr,
getText,
isXML,
tokenize,
compile,
select,
outermostContext,
sortInput,
hasDuplicate,
// Local document vars
setDocument,
document,
docElem,
documentIsHTML,
rbuggyQSA,
rbuggyMatches,
matches,
contains,
// Instance-specific data
expando = "sizzle" + 1 * new Date(),
preferredDoc = window.document,
dirruns = 0,
done = 0,
classCache = createCache(),
tokenCache = createCache(),
compilerCache = createCache(),
sortOrder = function( a, b ) {
if ( a === b ) {
hasDuplicate = true;
}
return 0;
},
// Instance methods
hasOwn = ({}).hasOwnProperty,
arr = [],
pop = arr.pop,
push_native = arr.push,
push = arr.push,
slice = arr.slice,
// Use a stripped-down indexOf as it's faster than native
2018-01-04 17:58:16 +01:00
// https://jsperf.com/thor-indexof-vs-for/5
indexOf = function( list, elem ) {
var i = 0,
len = list.length;
for ( ; i < len; i++ ) {
if ( list[i] === elem ) {
return i;
}
}
return -1;
},
booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
// Regular expressions
2016-11-07 15:43:48 +01:00
// http://www.w3.org/TR/css3-selectors/#whitespace
whitespace = "[\\x20\\t\\r\\n\\f]",
// http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
2018-01-04 17:58:16 +01:00
identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+",
2016-11-07 15:43:48 +01:00
// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
// Operator (capture 2)
"*([*^$|!~]?=)" + whitespace +
// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
"*\\]",
pseudos = ":(" + identifier + ")(?:\\((" +
// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
// 1. quoted (capture 3; capture 4 or capture 5)
"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
// 2. simple (capture 6)
"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
// 3. anything else (capture 2)
".*" +
")\\)|)",
// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
rwhitespace = new RegExp( whitespace + "+", "g" ),
rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
rpseudo = new RegExp( pseudos ),
ridentifier = new RegExp( "^" + identifier + "$" ),
matchExpr = {
"ID": new RegExp( "^#(" + identifier + ")" ),
"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
"TAG": new RegExp( "^(" + identifier + "|[*])" ),
"ATTR": new RegExp( "^" + attributes ),
"PSEUDO": new RegExp( "^" + pseudos ),
"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
// For use in libraries implementing .is()
// We use this for POS matching in `select`
"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
},
rinputs = /^(?:input|select|textarea|button)$/i,
rheader = /^h\d$/i,
rnative = /^[^{]+\{\s*\[native \w/,
// Easily-parseable/retrievable ID or TAG or CLASS selectors
rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
rsibling = /[+~]/,
2018-01-04 17:58:16 +01:00
// CSS escapes
// http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
funescape = function( _, escaped, escapedWhitespace ) {
var high = "0x" + escaped - 0x10000;
// NaN means non-codepoint
// Support: Firefox<24
// Workaround erroneous numeric interpretation of +"0x"
return high !== high || escapedWhitespace ?
escaped :
high < 0 ?
// BMP codepoint
String.fromCharCode( high + 0x10000 ) :
// Supplemental Plane codepoint (surrogate pair)
String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
},
2018-01-04 17:58:16 +01:00
// CSS string/identifier serialization
// https://drafts.csswg.org/cssom/#common-serializing-idioms
rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,
fcssescape = function( ch, asCodePoint ) {
if ( asCodePoint ) {
// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
if ( ch === "\0" ) {
return "\uFFFD";
}
// Control characters and (dependent upon position) numbers get escaped as code points
return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
}
// Other potentially-special ASCII characters get backslash-escaped
return "\\" + ch;
},
// Used for iframes
// See setDocument()
// Removing the function wrapper causes a "Permission Denied"
// error in IE
unloadHandler = function() {
setDocument();
2018-01-04 17:58:16 +01:00
},
disabledAncestor = addCombinator(
function( elem ) {
return elem.disabled === true && ("form" in elem || "label" in elem);
},
{ dir: "parentNode", next: "legend" }
);
// Optimize for push.apply( _, NodeList )
try {
push.apply(
(arr = slice.call( preferredDoc.childNodes )),
preferredDoc.childNodes
);
// Support: Android<4.0
// Detect silently failing push.apply
arr[ preferredDoc.childNodes.length ].nodeType;
} catch ( e ) {
push = { apply: arr.length ?
// Leverage slice if possible
function( target, els ) {
push_native.apply( target, slice.call(els) );
} :
// Support: IE<9
// Otherwise append directly
function( target, els ) {
var j = target.length,
i = 0;
// Can't trust NodeList.length
while ( (target[j++] = els[i++]) ) {}
target.length = j - 1;
}
};
}
function Sizzle( selector, context, results, seed ) {
2018-01-04 17:58:16 +01:00
var m, i, elem, nid, match, groups, newSelector,
newContext = context && context.ownerDocument,
// nodeType defaults to 9, since context defaults to document
nodeType = context ? context.nodeType : 9;
results = results || [];
// Return early from calls with invalid selector or context
if ( typeof selector !== "string" || !selector ||
nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
return results;
}
// Try to shortcut find operations (as opposed to filters) in HTML documents
if ( !seed ) {
if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
setDocument( context );
}
context = context || document;
if ( documentIsHTML ) {
// If the selector is sufficiently simple, try using a "get*By*" DOM method
// (excepting DocumentFragment context, where the methods don't exist)
if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
// ID selector
if ( (m = match[1]) ) {
// Document context
if ( nodeType === 9 ) {
if ( (elem = context.getElementById( m )) ) {
// Support: IE, Opera, Webkit
// TODO: identify versions
// getElementById can match elements by name instead of ID
if ( elem.id === m ) {
results.push( elem );
return results;
}
} else {
return results;
}
// Element context
} else {
// Support: IE, Opera, Webkit
// TODO: identify versions
// getElementById can match elements by name instead of ID
if ( newContext && (elem = newContext.getElementById( m )) &&
contains( context, elem ) &&
elem.id === m ) {
results.push( elem );
return results;
}
}
// Type selector
} else if ( match[2] ) {
push.apply( results, context.getElementsByTagName( selector ) );
return results;
// Class selector
} else if ( (m = match[3]) && support.getElementsByClassName &&
context.getElementsByClassName ) {
push.apply( results, context.getElementsByClassName( m ) );
return results;
}
}
// Take advantage of querySelectorAll
if ( support.qsa &&
!compilerCache[ selector + " " ] &&
(!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
if ( nodeType !== 1 ) {
newContext = context;
newSelector = selector;
// qSA looks outside Element context, which is not what we want
// Thanks to Andrew Dupont for this workaround technique
// Support: IE <=8
// Exclude object elements
} else if ( context.nodeName.toLowerCase() !== "object" ) {
// Capture the context ID, setting it first if necessary
if ( (nid = context.getAttribute( "id" )) ) {
2018-01-04 17:58:16 +01:00
nid = nid.replace( rcssescape, fcssescape );
} else {
context.setAttribute( "id", (nid = expando) );
}
// Prefix every selector in the list
groups = tokenize( selector );
i = groups.length;
while ( i-- ) {
2018-01-04 17:58:16 +01:00
groups[i] = "#" + nid + " " + toSelector( groups[i] );
}
newSelector = groups.join( "," );
// Expand context for sibling selectors
newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
context;
}
if ( newSelector ) {
try {
push.apply( results,
newContext.querySelectorAll( newSelector )
);
return results;
} catch ( qsaError ) {
} finally {
if ( nid === expando ) {
context.removeAttribute( "id" );
}
}
}
}
}
}
// All others
return select( selector.replace( rtrim, "$1" ), context, results, seed );
}
/**
* Create key-value caches of limited size
* @returns {function(string, object)} Returns the Object data after storing it on itself with
* property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
* deleting the oldest entry
*/
function createCache() {
var keys = [];
function cache( key, value ) {
// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
if ( keys.push( key + " " ) > Expr.cacheLength ) {
// Only keep the most recent entries
delete cache[ keys.shift() ];
}
return (cache[ key + " " ] = value);
}
return cache;
}
/**
* Mark a function for special use by Sizzle
* @param {Function} fn The function to mark
*/
function markFunction( fn ) {
fn[ expando ] = true;
return fn;
}
/**
* Support testing using an element
2018-01-04 17:58:16 +01:00
* @param {Function} fn Passed the created element and returns a boolean result
*/
function assert( fn ) {
2018-01-04 17:58:16 +01:00
var el = document.createElement("fieldset");
try {
2018-01-04 17:58:16 +01:00
return !!fn( el );
} catch (e) {
return false;
} finally {
// Remove from its parent by default
2018-01-04 17:58:16 +01:00
if ( el.parentNode ) {
el.parentNode.removeChild( el );
}
// release memory in IE
2018-01-04 17:58:16 +01:00
el = null;
}
}
/**
* Adds the same handler for all of the specified attrs
* @param {String} attrs Pipe-separated list of attributes
* @param {Function} handler The method that will be applied
*/
function addHandle( attrs, handler ) {
var arr = attrs.split("|"),
i = arr.length;
while ( i-- ) {
Expr.attrHandle[ arr[i] ] = handler;
}
}
/**
* Checks document order of two siblings
* @param {Element} a
* @param {Element} b
* @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
*/
function siblingCheck( a, b ) {
var cur = b && a,
diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
2018-01-04 17:58:16 +01:00
a.sourceIndex - b.sourceIndex;
// Use IE sourceIndex if available on both nodes
if ( diff ) {
return diff;
}
// Check if b follows a
if ( cur ) {
while ( (cur = cur.nextSibling) ) {
if ( cur === b ) {
return -1;
}
}
}
return a ? 1 : -1;
}
/**
* Returns a function to use in pseudos for input types
* @param {String} type
*/
function createInputPseudo( type ) {
return function( elem ) {
var name = elem.nodeName.toLowerCase();
return name === "input" && elem.type === type;
};
}
/**
* Returns a function to use in pseudos for buttons
* @param {String} type
*/
function createButtonPseudo( type ) {
return function( elem ) {
var name = elem.nodeName.toLowerCase();
return (name === "input" || name === "button") && elem.type === type;
};
}
2017-02-03 13:51:07 +01:00
2018-01-04 17:58:16 +01:00
/**
* Returns a function to use in pseudos for :enabled/:disabled
* @param {Boolean} disabled true for :disabled; false for :enabled
*/
function createDisabledPseudo( disabled ) {
// Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
return function( elem ) {
// Only certain elements can match :enabled or :disabled
// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
if ( "form" in elem ) {
// Check for inherited disabledness on relevant non-disabled elements:
// * listed form-associated elements in a disabled fieldset
// https://html.spec.whatwg.org/multipage/forms.html#category-listed
// https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
// * option elements in a disabled optgroup
// https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
// All such elements have a "form" property.
if ( elem.parentNode && elem.disabled === false ) {
// Option elements defer to a parent optgroup if present
if ( "label" in elem ) {
if ( "label" in elem.parentNode ) {
return elem.parentNode.disabled === disabled;
} else {
return elem.disabled === disabled;
}
}
// Support: IE 6 - 11
// Use the isDisabled shortcut property to check for disabled fieldset ancestors
return elem.isDisabled === disabled ||
// Where there is no isDisabled, check manually
/* jshint -W018 */
elem.isDisabled !== !disabled &&
disabledAncestor( elem ) === disabled;
}
return elem.disabled === disabled;
// Try to winnow out elements that can't be disabled before trusting the disabled property.
// Some victims get caught in our net (label, legend, menu, track), but it shouldn't
// even exist on them, let alone have a boolean value.
} else if ( "label" in elem ) {
return elem.disabled === disabled;
}
// Remaining elements are neither :enabled nor :disabled
return false;
};
}
/**
* Returns a function to use in pseudos for positionals
* @param {Function} fn
*/
function createPositionalPseudo( fn ) {
return markFunction(function( argument ) {
argument = +argument;
return markFunction(function( seed, matches ) {
var j,
matchIndexes = fn( [], seed.length, argument ),
i = matchIndexes.length;
// Match elements found at the specified indexes
while ( i-- ) {
if ( seed[ (j = matchIndexes[i]) ] ) {
seed[j] = !(matches[j] = seed[j]);
}
}
});
});
}
/**
* Checks a node for validity as a Sizzle context
* @param {Element|Object=} context
* @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
*/
function testContext( context ) {
return context && typeof context.getElementsByTagName !== "undefined" && context;
}
// Expose support vars for convenience
support = Sizzle.support = {};
2016-07-28 18:06:31 +02:00
/**
* Detects XML nodes
* @param {Element|Object} elem An element or a document
* @returns {Boolean} True iff elem is a non-HTML XML node
2017-02-03 13:51:07 +01:00
*/
isXML = Sizzle.isXML = function( elem ) {
// documentElement is verified for cases where it doesn't yet exist
// (such as loading iframes in IE - #4833)
var documentElement = elem && (elem.ownerDocument || elem).documentElement;
return documentElement ? documentElement.nodeName !== "HTML" : false;
};
2017-02-03 13:51:07 +01:00
/**
* Sets document-related variables once based on the current document
* @param {Element|Object} [doc] An element or document object to use to set the document
* @returns {Object} Returns the current document
*/
setDocument = Sizzle.setDocument = function( node ) {
2018-01-04 17:58:16 +01:00
var hasCompare, subWindow,
doc = node ? node.ownerDocument || node : preferredDoc;
2017-02-03 13:51:07 +01:00
// Return early if doc is invalid or already selected
if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
return document;
}
2017-02-03 13:51:07 +01:00
// Update global variables
document = doc;
docElem = document.documentElement;
documentIsHTML = !isXML( document );
// Support: IE 9-11, Edge
// Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
2018-01-04 17:58:16 +01:00
if ( preferredDoc !== document &&
(subWindow = document.defaultView) && subWindow.top !== subWindow ) {
// Support: IE 11, Edge
if ( subWindow.addEventListener ) {
subWindow.addEventListener( "unload", unloadHandler, false );
// Support: IE 9 - 10 only
2018-01-04 17:58:16 +01:00
} else if ( subWindow.attachEvent ) {
subWindow.attachEvent( "onunload", unloadHandler );
2017-02-03 13:51:07 +01:00
}
}
2017-02-03 13:51:07 +01:00
/* Attributes
---------------------------------------------------------------------- */
2017-02-03 13:51:07 +01:00
// Support: IE<8
// Verify that getAttribute really returns attributes and not properties
// (excepting IE8 booleans)
2018-01-04 17:58:16 +01:00
support.attributes = assert(function( el ) {
el.className = "i";
return !el.getAttribute("className");
});
2017-02-03 13:51:07 +01:00
/* getElement(s)By*
---------------------------------------------------------------------- */
2017-02-03 13:51:07 +01:00
// Check if getElementsByTagName("*") returns only elements
2018-01-04 17:58:16 +01:00
support.getElementsByTagName = assert(function( el ) {
el.appendChild( document.createComment("") );
return !el.getElementsByTagName("*").length;
});
2017-02-03 13:51:07 +01:00
// Support: IE<9
support.getElementsByClassName = rnative.test( document.getElementsByClassName );
2017-02-03 13:51:07 +01:00
// Support: IE<10
// Check if getElementById returns elements by name
2018-01-04 17:58:16 +01:00
// The broken getElementById methods don't pick up programmatically-set names,
// so use a roundabout getElementsByName test
2018-01-04 17:58:16 +01:00
support.getById = assert(function( el ) {
docElem.appendChild( el ).id = expando;
return !document.getElementsByName || !document.getElementsByName( expando ).length;
});
2017-02-03 13:51:07 +01:00
2018-01-04 17:58:16 +01:00
// ID filter and find
if ( support.getById ) {
Expr.filter["ID"] = function( id ) {
var attrId = id.replace( runescape, funescape );
return function( elem ) {
return elem.getAttribute("id") === attrId;
};
};
2018-01-04 17:58:16 +01:00
Expr.find["ID"] = function( id, context ) {
if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
var elem = context.getElementById( id );
return elem ? [ elem ] : [];
}
};
} else {
Expr.filter["ID"] = function( id ) {
var attrId = id.replace( runescape, funescape );
return function( elem ) {
var node = typeof elem.getAttributeNode !== "undefined" &&
elem.getAttributeNode("id");
return node && node.value === attrId;
};
};
2018-01-04 17:58:16 +01:00
// Support: IE 6 - 7 only
// getElementById is not reliable as a find shortcut
Expr.find["ID"] = function( id, context ) {
if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
var node, i, elems,
elem = context.getElementById( id );
if ( elem ) {
// Verify the id attribute
node = elem.getAttributeNode("id");
if ( node && node.value === id ) {
return [ elem ];
}
// Fall back on getElementsByName
elems = context.getElementsByName( id );
i = 0;
while ( (elem = elems[i++]) ) {
node = elem.getAttributeNode("id");
if ( node && node.value === id ) {
return [ elem ];
}
}
}
return [];
}
};
}
2017-02-03 13:51:07 +01:00
// Tag
Expr.find["TAG"] = support.getElementsByTagName ?
function( tag, context ) {
if ( typeof context.getElementsByTagName !== "undefined" ) {
return context.getElementsByTagName( tag );
2017-02-03 13:51:07 +01:00
// DocumentFragment nodes don't have gEBTN
} else if ( support.qsa ) {
return context.querySelectorAll( tag );
}
} :
2017-02-03 13:51:07 +01:00
function( tag, context ) {
var elem,
tmp = [],
i = 0,
// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
results = context.getElementsByTagName( tag );
2017-02-03 13:51:07 +01:00
// Filter out possible comments
if ( tag === "*" ) {
while ( (elem = results[i++]) ) {
if ( elem.nodeType === 1 ) {
tmp.push( elem );
}
}
2017-02-03 13:51:07 +01:00
return tmp;
}
return results;
};
2017-02-03 13:51:07 +01:00
// Class
Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
return context.getElementsByClassName( className );
}
};
2017-02-03 13:51:07 +01:00
/* QSA/matchesSelector
---------------------------------------------------------------------- */
2017-02-03 13:51:07 +01:00
// QSA and matchesSelector support
2017-02-03 13:51:07 +01:00
// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
rbuggyMatches = [];
2017-02-03 13:51:07 +01:00
// qSa(:focus) reports false when true (Chrome 21)
// We allow this because of a bug in IE8/9 that throws an error
// whenever `document.activeElement` is accessed on an iframe
// So, we allow :focus to pass through QSA all the time to avoid the IE error
2018-01-04 17:58:16 +01:00
// See https://bugs.jquery.com/ticket/13378
rbuggyQSA = [];
2017-02-03 13:51:07 +01:00
if ( (support.qsa = rnative.test( document.querySelectorAll )) ) {
// Build QSA regex
// Regex strategy adopted from Diego Perini
2018-01-04 17:58:16 +01:00
assert(function( el ) {
// Select is set to empty string on purpose
// This is to test IE's treatment of not explicitly
// setting a boolean content attribute,
// since its presence should be enough
2018-01-04 17:58:16 +01:00
// https://bugs.jquery.com/ticket/12359
docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" +
"<select id='" + expando + "-\r\\' msallowcapture=''>" +
"<option selected=''></option></select>";
2017-02-03 13:51:07 +01:00
// Support: IE8, Opera 11-12.16
// Nothing should be selected when empty strings follow ^= or $= or *=
// The test attribute must be unknown in Opera but "safe" for WinRT
2018-01-04 17:58:16 +01:00
// https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
if ( el.querySelectorAll("[msallowcapture^='']").length ) {
rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
}
2017-02-03 13:51:07 +01:00
// Support: IE8
// Boolean attributes and "value" are not treated correctly
2018-01-04 17:58:16 +01:00
if ( !el.querySelectorAll("[selected]").length ) {
rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
}
// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
2018-01-04 17:58:16 +01:00
if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
rbuggyQSA.push("~=");
}
// Webkit/Opera - :checked should return selected option elements
// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
// IE8 throws error here and will not see later tests
2018-01-04 17:58:16 +01:00
if ( !el.querySelectorAll(":checked").length ) {
rbuggyQSA.push(":checked");
}
// Support: Safari 8+, iOS 8+
// https://bugs.webkit.org/show_bug.cgi?id=136851
2018-01-04 17:58:16 +01:00
// In-page `selector#id sibling-combinator selector` fails
if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
rbuggyQSA.push(".#.+[+~]");
}
});
2017-02-03 13:51:07 +01:00
2018-01-04 17:58:16 +01:00
assert(function( el ) {
el.innerHTML = "<a href='' disabled='disabled'></a>" +
"<select disabled='disabled'><option/></select>";
// Support: Windows 8 Native Apps
// The type and name attributes are restricted during .innerHTML assignment
var input = document.createElement("input");
input.setAttribute( "type", "hidden" );
2018-01-04 17:58:16 +01:00
el.appendChild( input ).setAttribute( "name", "D" );
2017-02-03 13:51:07 +01:00
// Support: IE8
// Enforce case-sensitivity of name attribute
2018-01-04 17:58:16 +01:00
if ( el.querySelectorAll("[name=d]").length ) {
rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
}
2017-02-03 13:51:07 +01:00
// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
// IE8 throws error here and will not see later tests
2018-01-04 17:58:16 +01:00
if ( el.querySelectorAll(":enabled").length !== 2 ) {
rbuggyQSA.push( ":enabled", ":disabled" );
}
// Support: IE9-11+
// IE's :disabled selector does not pick up the children of disabled fieldsets
docElem.appendChild( el ).disabled = true;
if ( el.querySelectorAll(":disabled").length !== 2 ) {
rbuggyQSA.push( ":enabled", ":disabled" );
}
2017-02-03 13:51:07 +01:00
// Opera 10-11 does not throw on post-comma invalid pseudos
2018-01-04 17:58:16 +01:00
el.querySelectorAll("*,:x");
rbuggyQSA.push(",.*:");
});
2017-02-03 13:51:07 +01:00
}
if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
docElem.webkitMatchesSelector ||
docElem.mozMatchesSelector ||
docElem.oMatchesSelector ||
docElem.msMatchesSelector) )) ) {
2017-02-03 13:51:07 +01:00
2018-01-04 17:58:16 +01:00
assert(function( el ) {
// Check to see if it's possible to do matchesSelector
// on a disconnected node (IE 9)
2018-01-04 17:58:16 +01:00
support.disconnectedMatch = matches.call( el, "*" );
2017-02-03 13:51:07 +01:00
// This should fail with an exception
// Gecko does not error, returns false instead
2018-01-04 17:58:16 +01:00
matches.call( el, "[s!='']:x" );
rbuggyMatches.push( "!=", pseudos );
});
}
2017-02-03 13:51:07 +01:00
rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
2017-02-03 13:51:07 +01:00
/* Contains
---------------------------------------------------------------------- */
hasCompare = rnative.test( docElem.compareDocumentPosition );
2017-02-03 13:51:07 +01:00
// Element contains another
// Purposefully self-exclusive
// As in, an element does not contain itself
contains = hasCompare || rnative.test( docElem.contains ) ?
function( a, b ) {
var adown = a.nodeType === 9 ? a.documentElement : a,
bup = b && b.parentNode;
return a === bup || !!( bup && bup.nodeType === 1 && (
adown.contains ?
adown.contains( bup ) :
a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
));
} :
function( a, b ) {
if ( b ) {
while ( (b = b.parentNode) ) {
if ( b === a ) {
return true;
}
}
}
return false;
};
2017-02-03 13:51:07 +01:00
/* Sorting
---------------------------------------------------------------------- */
2017-02-03 13:51:07 +01:00
// Document order sorting
sortOrder = hasCompare ?
function( a, b ) {
2017-02-03 13:51:07 +01:00
// Flag for duplicate removal
if ( a === b ) {
hasDuplicate = true;
return 0;
}
2017-02-03 13:51:07 +01:00
// Sort on method existence if only one input has compareDocumentPosition
var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
if ( compare ) {
return compare;
}
2017-02-03 13:51:07 +01:00
// Calculate position if both inputs belong to the same document
compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
a.compareDocumentPosition( b ) :
2017-02-03 13:51:07 +01:00
// Otherwise we know they are disconnected
1;
2017-02-03 13:51:07 +01:00
// Disconnected nodes
if ( compare & 1 ||
(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
// Choose the first element that is related to our preferred document
if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
return -1;
}
if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
return 1;
2017-02-03 13:51:07 +01:00
}
// Maintain original order
return sortInput ?
( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
0;
}
2017-02-03 13:51:07 +01:00
return compare & 4 ? -1 : 1;
} :
function( a, b ) {
// Exit early if the nodes are identical
if ( a === b ) {
hasDuplicate = true;
return 0;
}
2017-02-03 13:51:07 +01:00
var cur,
i = 0,
aup = a.parentNode,
bup = b.parentNode,
ap = [ a ],
bp = [ b ];
2017-02-03 13:51:07 +01:00
// Parentless nodes are either documents or disconnected
if ( !aup || !bup ) {
return a === document ? -1 :
b === document ? 1 :
aup ? -1 :
bup ? 1 :
sortInput ?
( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
0;
2017-02-03 13:51:07 +01:00
// If the nodes are siblings, we can do a quick check
} else if ( aup === bup ) {
return siblingCheck( a, b );
}
2017-02-03 13:51:07 +01:00
// Otherwise we need full lists of their ancestors for comparison
cur = a;
while ( (cur = cur.parentNode) ) {
ap.unshift( cur );
}
cur = b;
while ( (cur = cur.parentNode) ) {
bp.unshift( cur );
}
2017-02-03 13:51:07 +01:00
// Walk down the tree looking for a discrepancy
while ( ap[i] === bp[i] ) {
i++;
2017-02-03 13:51:07 +01:00
}
return i ?
// Do a sibling check if the nodes have a common ancestor
siblingCheck( ap[i], bp[i] ) :
// Otherwise nodes in our document sort first
ap[i] === preferredDoc ? -1 :
bp[i] === preferredDoc ? 1 :
0;
};
return document;
};
Sizzle.matches = function( expr, elements ) {
return Sizzle( expr, null, null, elements );
};
Sizzle.matchesSelector = function( elem, expr ) {
// Set document vars if needed
if ( ( elem.ownerDocument || elem ) !== document ) {
setDocument( elem );
2017-02-03 13:51:07 +01:00
}
// Make sure that attribute selectors are quoted
expr = expr.replace( rattributeQuotes, "='$1']" );
2017-02-03 13:51:07 +01:00
if ( support.matchesSelector && documentIsHTML &&
!compilerCache[ expr + " " ] &&
( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
2017-02-03 13:51:07 +01:00
try {
var ret = matches.call( elem, expr );
// IE 9's matchesSelector returns false on disconnected nodes
if ( ret || support.disconnectedMatch ||
// As well, disconnected nodes are said to be in a document
// fragment in IE 9
elem.document && elem.document.nodeType !== 11 ) {
return ret;
}
} catch (e) {}
2017-02-03 13:51:07 +01:00
}
return Sizzle( expr, document, null, [ elem ] ).length > 0;
};
2017-02-03 13:51:07 +01:00
Sizzle.contains = function( context, elem ) {
// Set document vars if needed
if ( ( context.ownerDocument || context ) !== document ) {
setDocument( context );
}
return contains( context, elem );
};
2017-02-03 13:51:07 +01:00
Sizzle.attr = function( elem, name ) {
// Set document vars if needed
if ( ( elem.ownerDocument || elem ) !== document ) {
setDocument( elem );
2017-02-03 13:51:07 +01:00
}
var fn = Expr.attrHandle[ name.toLowerCase() ],
// Don't get fooled by Object.prototype properties (jQuery #13807)
val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
fn( elem, name, !documentIsHTML ) :
undefined;
2017-02-03 13:51:07 +01:00
return val !== undefined ?
val :
support.attributes || !documentIsHTML ?
elem.getAttribute( name ) :
(val = elem.getAttributeNode(name)) && val.specified ?
val.value :
null;
};
2018-01-04 17:58:16 +01:00
Sizzle.escape = function( sel ) {
return (sel + "").replace( rcssescape, fcssescape );
};
Sizzle.error = function( msg ) {
throw new Error( "Syntax error, unrecognized expression: " + msg );
};
2017-02-03 13:51:07 +01:00
/**
* Document sorting and removing duplicates
* @param {ArrayLike} results
2017-02-03 13:51:07 +01:00
*/
Sizzle.uniqueSort = function( results ) {
var elem,
duplicates = [],
j = 0,
i = 0;
2017-02-03 13:51:07 +01:00
// Unless we *know* we can detect duplicates, assume their presence
hasDuplicate = !support.detectDuplicates;
sortInput = !support.sortStable && results.slice( 0 );
results.sort( sortOrder );
2017-02-03 13:51:07 +01:00
if ( hasDuplicate ) {
while ( (elem = results[i++]) ) {
if ( elem === results[ i ] ) {
j = duplicates.push( i );
2017-02-03 13:51:07 +01:00
}
}
while ( j-- ) {
results.splice( duplicates[ j ], 1 );
}
2017-02-03 13:51:07 +01:00
}
// Clear input after sorting to release objects
// See https://github.com/jquery/sizzle/pull/225
sortInput = null;
2017-02-03 13:51:07 +01:00
return results;
};
2017-02-03 13:51:07 +01:00
/**
* Utility function for retrieving the text value of an array of DOM nodes
* @param {Array|Element} elem
2017-02-03 13:51:07 +01:00
*/
getText = Sizzle.getText = function( elem ) {
var node,
ret = "",
i = 0,
nodeType = elem.nodeType;
2017-02-03 13:51:07 +01:00
if ( !nodeType ) {
// If no nodeType, this is expected to be an array
while ( (node = elem[i++]) ) {
// Do not traverse comment nodes
ret += getText( node );
}
} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
// Use textContent for elements
// innerText usage removed for consistency of new lines (jQuery #11153)
if ( typeof elem.textContent === "string" ) {
return elem.textContent;
} else {
// Traverse its children
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
ret += getText( elem );
2017-02-03 13:51:07 +01:00
}
}
} else if ( nodeType === 3 || nodeType === 4 ) {
return elem.nodeValue;
}
// Do not include comment or processing instruction nodes
2017-02-03 13:51:07 +01:00
return ret;
2017-02-03 13:51:07 +01:00
};
Expr = Sizzle.selectors = {
2017-02-03 13:51:07 +01:00
// Can be adjusted by the user
cacheLength: 50,
createPseudo: markFunction,
2017-02-03 13:51:07 +01:00
match: matchExpr,
2017-02-03 13:51:07 +01:00
attrHandle: {},
2017-02-03 13:51:07 +01:00
find: {},
2017-02-03 13:51:07 +01:00
relative: {
">": { dir: "parentNode", first: true },
" ": { dir: "parentNode" },
"+": { dir: "previousSibling", first: true },
"~": { dir: "previousSibling" }
},
2017-02-03 13:51:07 +01:00
preFilter: {
"ATTR": function( match ) {
match[1] = match[1].replace( runescape, funescape );
2017-02-03 13:51:07 +01:00
// Move the given value to match[3] whether quoted or unquoted
match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
2017-02-03 13:51:07 +01:00
if ( match[2] === "~=" ) {
match[3] = " " + match[3] + " ";
}
2017-02-03 13:51:07 +01:00
return match.slice( 0, 4 );
},
2017-02-03 13:51:07 +01:00
"CHILD": function( match ) {
/* matches from matchExpr["CHILD"]
1 type (only|nth|...)
2 what (child|of-type)
3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
4 xn-component of xn+y argument ([+-]?\d*n|)
5 sign of xn-component
6 x of xn-component
7 sign of y-component
8 y of y-component
*/
match[1] = match[1].toLowerCase();
2017-02-03 13:51:07 +01:00
if ( match[1].slice( 0, 3 ) === "nth" ) {
// nth-* requires argument
if ( !match[3] ) {
Sizzle.error( match[0] );
}
// numeric x and y parameters for Expr.filter.CHILD
// remember that false/true cast respectively to 0/1
match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
// other types prohibit arguments
} else if ( match[3] ) {
Sizzle.error( match[0] );
2017-02-03 13:51:07 +01:00
}
return match;
},
2017-02-03 13:51:07 +01:00
"PSEUDO": function( match ) {
var excess,
unquoted = !match[6] && match[2];
2017-02-03 13:51:07 +01:00
if ( matchExpr["CHILD"].test( match[0] ) ) {
return null;
2017-02-03 13:51:07 +01:00
}
// Accept quoted arguments as-is
if ( match[3] ) {
match[2] = match[4] || match[5] || "";
2017-02-03 13:51:07 +01:00
// Strip excess characters from unquoted arguments
} else if ( unquoted && rpseudo.test( unquoted ) &&
// Get excess from tokenize (recursively)
(excess = tokenize( unquoted, true )) &&
// advance to the next closing parenthesis
(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
2017-02-03 13:51:07 +01:00
// excess is a negative index
match[0] = match[0].slice( 0, excess );
match[2] = unquoted.slice( 0, excess );
2017-02-03 13:51:07 +01:00
}
// Return only captures needed by the pseudo filter method (type and argument)
return match.slice( 0, 3 );
2017-02-03 13:51:07 +01:00
}
},
2017-02-03 13:51:07 +01:00
filter: {
2017-02-03 13:51:07 +01:00
"TAG": function( nodeNameSelector ) {
var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
return nodeNameSelector === "*" ?
function() { return true; } :
function( elem ) {
return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
};
},
2017-02-03 13:51:07 +01:00
"CLASS": function( className ) {
var pattern = classCache[ className + " " ];
2017-02-03 13:51:07 +01:00
return pattern ||
(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
classCache( className, function( elem ) {
return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" );
});
},
2017-02-03 13:51:07 +01:00
"ATTR": function( name, operator, check ) {
return function( elem ) {
var result = Sizzle.attr( elem, name );
2017-02-03 13:51:07 +01:00
if ( result == null ) {
return operator === "!=";
}
if ( !operator ) {
return true;
}
2017-02-03 13:51:07 +01:00
result += "";
2017-02-03 13:51:07 +01:00
return operator === "=" ? result === check :
operator === "!=" ? result !== check :
operator === "^=" ? check && result.indexOf( check ) === 0 :
operator === "*=" ? check && result.indexOf( check ) > -1 :
operator === "$=" ? check && result.slice( -check.length ) === check :
operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
false;
};
},
2017-02-03 13:51:07 +01:00
"CHILD": function( type, what, argument, first, last ) {
var simple = type.slice( 0, 3 ) !== "nth",
forward = type.slice( -4 ) !== "last",
ofType = what === "of-type";
2017-02-03 13:51:07 +01:00
return first === 1 && last === 0 ?
2017-02-03 13:51:07 +01:00
// Shortcut for :nth-*(n)
function( elem ) {
return !!elem.parentNode;
} :
2017-02-03 13:51:07 +01:00
function( elem, context, xml ) {
var cache, uniqueCache, outerCache, node, nodeIndex, start,
dir = simple !== forward ? "nextSibling" : "previousSibling",
parent = elem.parentNode,
name = ofType && elem.nodeName.toLowerCase(),
useCache = !xml && !ofType,
diff = false;
2017-02-03 13:51:07 +01:00
if ( parent ) {
2017-02-03 13:51:07 +01:00
// :(first|last|only)-(child|of-type)
if ( simple ) {
while ( dir ) {
node = elem;
while ( (node = node[ dir ]) ) {
if ( ofType ?
node.nodeName.toLowerCase() === name :
node.nodeType === 1 ) {
2017-02-03 13:51:07 +01:00
return false;
}
}
// Reverse direction for :only-* (if we haven't yet done so)
start = dir = type === "only" && !start && "nextSibling";
}
return true;
}
2017-02-03 13:51:07 +01:00
start = [ forward ? parent.firstChild : parent.lastChild ];
2017-02-03 13:51:07 +01:00
// non-xml :nth-child(...) stores cache data on `parent`
if ( forward && useCache ) {
2017-02-03 13:51:07 +01:00
// Seek `elem` from a previously-cached index
2017-02-03 13:51:07 +01:00
// ...in a gzip-friendly way
node = parent;
outerCache = node[ expando ] || (node[ expando ] = {});
2017-02-03 13:51:07 +01:00
// Support: IE <9 only
// Defend against cloned attroperties (jQuery gh-1709)
uniqueCache = outerCache[ node.uniqueID ] ||
(outerCache[ node.uniqueID ] = {});
2017-02-03 13:51:07 +01:00
cache = uniqueCache[ type ] || [];
nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
diff = nodeIndex && cache[ 2 ];
node = nodeIndex && parent.childNodes[ nodeIndex ];
2017-02-03 13:51:07 +01:00
while ( (node = ++nodeIndex && node && node[ dir ] ||
2017-02-03 13:51:07 +01:00
// Fallback to seeking `elem` from the start
(diff = nodeIndex = 0) || start.pop()) ) {
2017-02-03 13:51:07 +01:00
// When found, cache indexes on `parent` and break
if ( node.nodeType === 1 && ++diff && node === elem ) {
uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
break;
}
}
2017-02-03 13:51:07 +01:00
} else {
// Use previously-cached element index if available
if ( useCache ) {
// ...in a gzip-friendly way
node = elem;
outerCache = node[ expando ] || (node[ expando ] = {});
2017-02-03 13:51:07 +01:00
// Support: IE <9 only
// Defend against cloned attroperties (jQuery gh-1709)
uniqueCache = outerCache[ node.uniqueID ] ||
(outerCache[ node.uniqueID ] = {});
2017-02-03 13:51:07 +01:00
cache = uniqueCache[ type ] || [];
nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
diff = nodeIndex;
}
2017-02-03 13:51:07 +01:00
// xml :nth-child(...)
// or :nth-last-child(...) or :nth(-last)?-of-type(...)
if ( diff === false ) {
// Use the same loop as above to seek `elem` from the start
while ( (node = ++nodeIndex && node && node[ dir ] ||
(diff = nodeIndex = 0) || start.pop()) ) {
2017-02-03 13:51:07 +01:00
if ( ( ofType ?
node.nodeName.toLowerCase() === name :
node.nodeType === 1 ) &&
++diff ) {
2017-02-03 13:51:07 +01:00
// Cache the index of each encountered element
if ( useCache ) {
outerCache = node[ expando ] || (node[ expando ] = {});
2017-02-03 13:51:07 +01:00
// Support: IE <9 only
// Defend against cloned attroperties (jQuery gh-1709)
uniqueCache = outerCache[ node.uniqueID ] ||
(outerCache[ node.uniqueID ] = {});
2017-02-03 13:51:07 +01:00
uniqueCache[ type ] = [ dirruns, diff ];
}
2017-02-03 13:51:07 +01:00
if ( node === elem ) {
break;
}
}
}
}
}
2017-02-03 13:51:07 +01:00
// Incorporate the offset, then check against cycle size
diff -= last;
return diff === first || ( diff % first === 0 && diff / first >= 0 );
}
};
},
2017-02-03 13:51:07 +01:00
"PSEUDO": function( pseudo, argument ) {
// pseudo-class names are case-insensitive
// http://www.w3.org/TR/selectors/#pseudo-classes
// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
// Remember that setFilters inherits from pseudos
var args,
fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
Sizzle.error( "unsupported pseudo: " + pseudo );
// The user may use createPseudo to indicate that
// arguments are needed to create the filter function
// just as Sizzle does
if ( fn[ expando ] ) {
return fn( argument );
}
2017-02-03 13:51:07 +01:00
// But maintain support for old signatures
if ( fn.length > 1 ) {
args = [ pseudo, pseudo, "", argument ];
return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
markFunction(function( seed, matches ) {
var idx,
matched = fn( seed, argument ),
i = matched.length;
while ( i-- ) {
idx = indexOf( seed, matched[i] );
seed[ idx ] = !( matches[ idx ] = matched[i] );
}
}) :
function( elem ) {
return fn( elem, 0, args );
};
}
2017-02-03 13:51:07 +01:00
return fn;
}
},
2017-02-03 13:51:07 +01:00
pseudos: {
// Potentially complex pseudos
"not": markFunction(function( selector ) {
// Trim the selector passed to compile
// to avoid treating leading and trailing
// spaces as combinators
var input = [],
results = [],
matcher = compile( selector.replace( rtrim, "$1" ) );
2017-02-03 13:51:07 +01:00
return matcher[ expando ] ?
markFunction(function( seed, matches, context, xml ) {
var elem,
unmatched = matcher( seed, null, xml, [] ),
i = seed.length;
2017-02-03 13:51:07 +01:00
// Match elements unmatched by `matcher`
while ( i-- ) {
if ( (elem = unmatched[i]) ) {
seed[i] = !(matches[i] = elem);
}
}
}) :
function( elem, context, xml ) {
input[0] = elem;
matcher( input, null, xml, results );
// Don't keep the element (issue #299)
input[0] = null;
return !results.pop();
};
}),
2017-02-03 13:51:07 +01:00
"has": markFunction(function( selector ) {
return function( elem ) {
return Sizzle( selector, elem ).length > 0;
};
}),
2017-02-03 13:51:07 +01:00
"contains": markFunction(function( text ) {
text = text.replace( runescape, funescape );
return function( elem ) {
return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
};
}),
2017-02-03 13:51:07 +01:00
// "Whether an element is represented by a :lang() selector
// is based solely on the element's language value
// being equal to the identifier C,
// or beginning with the identifier C immediately followed by "-".
// The matching of C against the element's language value is performed case-insensitively.
// The identifier C does not have to be a valid language name."
// http://www.w3.org/TR/selectors/#lang-pseudo
"lang": markFunction( function( lang ) {
// lang value must be a valid identifier
if ( !ridentifier.test(lang || "") ) {
Sizzle.error( "unsupported lang: " + lang );
2017-02-03 13:51:07 +01:00
}
lang = lang.replace( runescape, funescape ).toLowerCase();
return function( elem ) {
var elemLang;
do {
if ( (elemLang = documentIsHTML ?
elem.lang :
elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
2017-02-03 13:51:07 +01:00
elemLang = elemLang.toLowerCase();
return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
}
} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
return false;
};
}),
2017-02-03 13:51:07 +01:00
// Miscellaneous
"target": function( elem ) {
var hash = window.location && window.location.hash;
return hash && hash.slice( 1 ) === elem.id;
},
2017-02-03 13:51:07 +01:00
"root": function( elem ) {
return elem === docElem;
},
2017-02-03 13:51:07 +01:00
"focus": function( elem ) {
return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
},
2017-02-03 13:51:07 +01:00
// Boolean properties
2018-01-04 17:58:16 +01:00
"enabled": createDisabledPseudo( false ),
"disabled": createDisabledPseudo( true ),
2017-02-03 13:51:07 +01:00
"checked": function( elem ) {
// In CSS3, :checked should return both checked and selected elements
// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
var nodeName = elem.nodeName.toLowerCase();
return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
},
2017-02-03 13:51:07 +01:00
"selected": function( elem ) {
// Accessing this property makes selected-by-default
// options in Safari work properly
if ( elem.parentNode ) {
elem.parentNode.selectedIndex;
2017-02-03 13:51:07 +01:00
}
return elem.selected === true;
},
2017-02-03 13:51:07 +01:00
// Contents
"empty": function( elem ) {
// http://www.w3.org/TR/selectors/#empty-pseudo
// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
// but not by others (comment: 8; processing instruction: 7; etc.)
// nodeType < 6 works because attributes (2) do not appear as children
2017-02-03 13:51:07 +01:00
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
if ( elem.nodeType < 6 ) {
return false;
}
2017-02-03 13:51:07 +01:00
}
return true;
},
"parent": function( elem ) {
return !Expr.pseudos["empty"]( elem );
},
// Element/input types
"header": function( elem ) {
return rheader.test( elem.nodeName );
},
2017-02-03 13:51:07 +01:00
"input": function( elem ) {
return rinputs.test( elem.nodeName );
},
2017-02-03 13:51:07 +01:00
"button": function( elem ) {
var name = elem.nodeName.toLowerCase();
return name === "input" && elem.type === "button" || name === "button";
},
2017-02-03 13:51:07 +01:00
"text": function( elem ) {
var attr;
return elem.nodeName.toLowerCase() === "input" &&
elem.type === "text" &&
2017-02-03 13:51:07 +01:00
// Support: IE<8
// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
},
2017-02-03 13:51:07 +01:00
// Position-in-collection
"first": createPositionalPseudo(function() {
return [ 0 ];
}),
2017-02-03 13:51:07 +01:00
"last": createPositionalPseudo(function( matchIndexes, length ) {
return [ length - 1 ];
}),
2017-02-03 13:51:07 +01:00
"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
return [ argument < 0 ? argument + length : argument ];
}),
2017-02-03 13:51:07 +01:00
"even": createPositionalPseudo(function( matchIndexes, length ) {
var i = 0;
for ( ; i < length; i += 2 ) {
matchIndexes.push( i );
}
return matchIndexes;
}),
2017-02-03 13:51:07 +01:00
"odd": createPositionalPseudo(function( matchIndexes, length ) {
var i = 1;
for ( ; i < length; i += 2 ) {
matchIndexes.push( i );
}
return matchIndexes;
}),
2017-02-03 13:51:07 +01:00
"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
var i = argument < 0 ? argument + length : argument;
for ( ; --i >= 0; ) {
matchIndexes.push( i );
}
return matchIndexes;
}),
2017-02-03 13:51:07 +01:00
"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
var i = argument < 0 ? argument + length : argument;
for ( ; ++i < length; ) {
matchIndexes.push( i );
2017-02-03 13:51:07 +01:00
}
return matchIndexes;
})
}
};
2017-02-03 13:51:07 +01:00
Expr.pseudos["nth"] = Expr.pseudos["eq"];
2017-02-03 13:51:07 +01:00
// Add button/input type pseudos
for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
Expr.pseudos[ i ] = createInputPseudo( i );
}
for ( i in { submit: true, reset: true } ) {
Expr.pseudos[ i ] = createButtonPseudo( i );
}
2017-02-03 13:51:07 +01:00
// Easy API for creating new setFilters
function setFilters() {}
setFilters.prototype = Expr.filters = Expr.pseudos;
Expr.setFilters = new setFilters();
2017-02-03 13:51:07 +01:00
tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
var matched, match, tokens, type,
soFar, groups, preFilters,
cached = tokenCache[ selector + " " ];
2017-02-03 13:51:07 +01:00
if ( cached ) {
return parseOnly ? 0 : cached.slice( 0 );
}
2017-02-03 13:51:07 +01:00
soFar = selector;
groups = [];
preFilters = Expr.preFilter;
2017-02-03 13:51:07 +01:00
while ( soFar ) {
2017-02-03 13:51:07 +01:00
// Comma and first run
if ( !matched || (match = rcomma.exec( soFar )) ) {
if ( match ) {
// Don't consume trailing commas as valid
soFar = soFar.slice( match[0].length ) || soFar;
2017-02-03 13:51:07 +01:00
}
groups.push( (tokens = []) );
}
2017-02-03 13:51:07 +01:00
matched = false;
2017-02-03 13:51:07 +01:00
// Combinators
if ( (match = rcombinators.exec( soFar )) ) {
matched = match.shift();
tokens.push({
value: matched,
// Cast descendant combinators to space
type: match[0].replace( rtrim, " " )
});
soFar = soFar.slice( matched.length );
}
2017-02-03 13:51:07 +01:00
// Filters
for ( type in Expr.filter ) {
if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
(match = preFilters[ type ]( match ))) ) {
matched = match.shift();
tokens.push({
value: matched,
type: type,
matches: match
});
soFar = soFar.slice( matched.length );
2017-02-03 13:51:07 +01:00
}
}
2017-02-03 13:51:07 +01:00
if ( !matched ) {
break;
2017-02-03 13:51:07 +01:00
}
}
2017-02-03 13:51:07 +01:00
// Return the length of the invalid excess
// if we're just parsing
// Otherwise, throw an error or return tokens
return parseOnly ?
soFar.length :
soFar ?
Sizzle.error( selector ) :
// Cache the tokens
tokenCache( selector, groups ).slice( 0 );
};
2017-02-03 13:51:07 +01:00
function toSelector( tokens ) {
var i = 0,
len = tokens.length,
selector = "";
for ( ; i < len; i++ ) {
selector += tokens[i].value;
}
return selector;
}
function addCombinator( matcher, combinator, base ) {
var dir = combinator.dir,
2018-01-04 17:58:16 +01:00
skip = combinator.next,
key = skip || dir,
checkNonElements = base && key === "parentNode",
doneName = done++;
return combinator.first ?
// Check against closest ancestor/preceding element
function( elem, context, xml ) {
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
return matcher( elem, context, xml );
}
}
2018-01-04 17:58:16 +01:00
return false;
} :
// Check against all ancestor/preceding elements
function( elem, context, xml ) {
var oldCache, uniqueCache, outerCache,
newCache = [ dirruns, doneName ];
// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
if ( xml ) {
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
if ( matcher( elem, context, xml ) ) {
return true;
}
}
}
} else {
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
outerCache = elem[ expando ] || (elem[ expando ] = {});
// Support: IE <9 only
// Defend against cloned attroperties (jQuery gh-1709)
uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {});
2018-01-04 17:58:16 +01:00
if ( skip && skip === elem.nodeName.toLowerCase() ) {
elem = elem[ dir ] || elem;
} else if ( (oldCache = uniqueCache[ key ]) &&
oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
// Assign to newCache so results back-propagate to previous elements
return (newCache[ 2 ] = oldCache[ 2 ]);
} else {
// Reuse newcache so results back-propagate to previous elements
2018-01-04 17:58:16 +01:00
uniqueCache[ key ] = newCache;
2017-02-03 13:51:07 +01:00
// A match means we're done; a fail means we have to keep checking
if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
return true;
}
}
}
}
}
2018-01-04 17:58:16 +01:00
return false;
};
}
2017-02-03 13:51:07 +01:00
function elementMatcher( matchers ) {
return matchers.length > 1 ?
function( elem, context, xml ) {
var i = matchers.length;
while ( i-- ) {
if ( !matchers[i]( elem, context, xml ) ) {
return false;
}
}
return true;
} :
matchers[0];
}
2017-02-03 13:51:07 +01:00
function multipleContexts( selector, contexts, results ) {
var i = 0,
len = contexts.length;
for ( ; i < len; i++ ) {
Sizzle( selector, contexts[i], results );
}
return results;
}
2017-02-03 13:51:07 +01:00
function condense( unmatched, map, filter, context, xml ) {
var elem,
newUnmatched = [],
i = 0,
len = unmatched.length,
mapped = map != null;
for ( ; i < len; i++ ) {
if ( (elem = unmatched[i]) ) {
if ( !filter || filter( elem, context, xml ) ) {
newUnmatched.push( elem );
if ( mapped ) {
map.push( i );
2017-02-03 13:51:07 +01:00
}
}
}
}
2017-02-03 13:51:07 +01:00
return newUnmatched;
}
2017-02-03 13:51:07 +01:00
function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
if ( postFilter && !postFilter[ expando ] ) {
postFilter = setMatcher( postFilter );
}
if ( postFinder && !postFinder[ expando ] ) {
postFinder = setMatcher( postFinder, postSelector );
}
return markFunction(function( seed, results, context, xml ) {
var temp, i, elem,
preMap = [],
postMap = [],
preexisting = results.length,
2017-02-03 13:51:07 +01:00
// Get initial elements from seed or context
elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
2017-02-03 13:51:07 +01:00
// Prefilter to get matcher input, preserving a map for seed-results synchronization
matcherIn = preFilter && ( seed || !selector ) ?
condense( elems, preMap, preFilter, context, xml ) :
elems,
2017-02-03 13:51:07 +01:00
matcherOut = matcher ?
// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
2017-02-03 13:51:07 +01:00
// ...intermediate processing is necessary
[] :
2017-02-03 13:51:07 +01:00
// ...otherwise use results directly
results :
matcherIn;
2017-02-03 13:51:07 +01:00
// Find primary matches
if ( matcher ) {
matcher( matcherIn, matcherOut, context, xml );
}
2017-02-03 13:51:07 +01:00
// Apply postFilter
if ( postFilter ) {
temp = condense( matcherOut, postMap );
postFilter( temp, [], context, xml );
2017-02-03 13:51:07 +01:00
// Un-match failing elements by moving them back to matcherIn
i = temp.length;
while ( i-- ) {
if ( (elem = temp[i]) ) {
matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
}
}
}
2017-02-03 13:51:07 +01:00
if ( seed ) {
if ( postFinder || preFilter ) {
if ( postFinder ) {
// Get the final matcherOut by condensing this intermediate into postFinder contexts
temp = [];
i = matcherOut.length;
while ( i-- ) {
if ( (elem = matcherOut[i]) ) {
// Restore matcherIn since elem is not yet a final match
temp.push( (matcherIn[i] = elem) );
}
}
postFinder( null, (matcherOut = []), temp, xml );
}
2017-02-03 13:51:07 +01:00
// Move matched elements from seed to results to keep them synchronized
i = matcherOut.length;
while ( i-- ) {
if ( (elem = matcherOut[i]) &&
(temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {
2017-02-03 13:51:07 +01:00
seed[temp] = !(results[temp] = elem);
}
}
}
2017-02-03 13:51:07 +01:00
// Add elements to results, through postFinder if defined
} else {
matcherOut = condense(
matcherOut === results ?
matcherOut.splice( preexisting, matcherOut.length ) :
matcherOut
);
if ( postFinder ) {
postFinder( null, results, matcherOut, xml );
} else {
push.apply( results, matcherOut );
}
}
});
}
2017-02-03 13:51:07 +01:00
function matcherFromTokens( tokens ) {
var checkContext, matcher, j,
len = tokens.length,
leadingRelative = Expr.relative[ tokens[0].type ],
implicitRelative = leadingRelative || Expr.relative[" "],
i = leadingRelative ? 1 : 0,
2017-02-03 13:51:07 +01:00
// The foundational matcher ensures that elements are reachable from top-level context(s)
matchContext = addCombinator( function( elem ) {
return elem === checkContext;
}, implicitRelative, true ),
matchAnyContext = addCombinator( function( elem ) {
return indexOf( checkContext, elem ) > -1;
}, implicitRelative, true ),
matchers = [ function( elem, context, xml ) {
var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
(checkContext = context).nodeType ?
matchContext( elem, context, xml ) :
matchAnyContext( elem, context, xml ) );
// Avoid hanging onto element (issue #299)
checkContext = null;
return ret;
} ];
2017-02-03 13:51:07 +01:00
for ( ; i < len; i++ ) {
if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
} else {
matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
2017-02-03 13:51:07 +01:00
// Return special upon seeing a positional matcher
if ( matcher[ expando ] ) {
// Find the next relative operator (if any) for proper handling
j = ++i;
for ( ; j < len; j++ ) {
if ( Expr.relative[ tokens[j].type ] ) {
break;
}
}
return setMatcher(
i > 1 && elementMatcher( matchers ),
i > 1 && toSelector(
// If the preceding token was a descendant combinator, insert an implicit any-element `*`
tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
).replace( rtrim, "$1" ),
matcher,
i < j && matcherFromTokens( tokens.slice( i, j ) ),
j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
j < len && toSelector( tokens )
);
}
matchers.push( matcher );
}
}
2017-02-03 13:51:07 +01:00
return elementMatcher( matchers );
}
2017-02-03 13:51:07 +01:00
function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
var bySet = setMatchers.length > 0,
byElement = elementMatchers.length > 0,
superMatcher = function( seed, context, xml, results, outermost ) {
var elem, j, matcher,
matchedCount = 0,
i = "0",
unmatched = seed && [],
setMatched = [],
contextBackup = outermostContext,
// We must always have either seed elements or outermost context
elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
// Use integer dirruns iff this is the outermost matcher
dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
len = elems.length;
2017-02-03 13:51:07 +01:00
if ( outermost ) {
outermostContext = context === document || context || outermost;
}
2017-02-03 13:51:07 +01:00
// Add elements passing elementMatchers directly to results
// Support: IE<9, Safari
// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
if ( byElement && elem ) {
j = 0;
if ( !context && elem.ownerDocument !== document ) {
setDocument( elem );
xml = !documentIsHTML;
}
while ( (matcher = elementMatchers[j++]) ) {
if ( matcher( elem, context || document, xml) ) {
results.push( elem );
break;
}
}
if ( outermost ) {
dirruns = dirrunsUnique;
}
}
2017-02-03 13:51:07 +01:00
// Track unmatched elements for set filters
if ( bySet ) {
// They will have gone through all possible matchers
if ( (elem = !matcher && elem) ) {
matchedCount--;
}
2017-02-03 13:51:07 +01:00
// Lengthen the array for every element, matched or not
if ( seed ) {
unmatched.push( elem );
}
}
}
2017-02-03 13:51:07 +01:00
// `i` is now the count of elements visited above, and adding it to `matchedCount`
// makes the latter nonnegative.
matchedCount += i;
2017-02-03 13:51:07 +01:00
// Apply set filters to unmatched elements
// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
// no element matchers and no seed.
// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
// case, which will result in a "00" `matchedCount` that differs from `i` but is also
// numerically zero.
if ( bySet && i !== matchedCount ) {
j = 0;
while ( (matcher = setMatchers[j++]) ) {
matcher( unmatched, setMatched, context, xml );
}
2017-02-03 13:51:07 +01:00
if ( seed ) {
// Reintegrate element matches to eliminate the need for sorting
if ( matchedCount > 0 ) {
while ( i-- ) {
if ( !(unmatched[i] || setMatched[i]) ) {
setMatched[i] = pop.call( results );
2017-02-03 13:51:07 +01:00
}
}
}
// Discard index placeholder values to get only actual matches
setMatched = condense( setMatched );
}
2017-02-03 13:51:07 +01:00
// Add matches to results
push.apply( results, setMatched );
2017-02-03 13:51:07 +01:00
// Seedless set matches succeeding multiple successful matchers stipulate sorting
if ( outermost && !seed && setMatched.length > 0 &&
( matchedCount + setMatchers.length ) > 1 ) {
2017-02-03 13:51:07 +01:00
Sizzle.uniqueSort( results );
}
}
2017-02-03 13:51:07 +01:00
// Override manipulation of globals by nested matchers
if ( outermost ) {
dirruns = dirrunsUnique;
outermostContext = contextBackup;
}
2017-02-03 13:51:07 +01:00
return unmatched;
};
2017-02-03 13:51:07 +01:00
return bySet ?
markFunction( superMatcher ) :
superMatcher;
}
2017-02-03 13:51:07 +01:00
compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
var i,
setMatchers = [],
elementMatchers = [],
cached = compilerCache[ selector + " " ];
2017-02-03 13:51:07 +01:00
if ( !cached ) {
// Generate a function of recursive functions that can be used to check each element
if ( !match ) {
match = tokenize( selector );
}
i = match.length;
while ( i-- ) {
cached = matcherFromTokens( match[i] );
if ( cached[ expando ] ) {
setMatchers.push( cached );
} else {
elementMatchers.push( cached );
}
}
2017-02-03 13:51:07 +01:00
// Cache the compiled function
cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
2017-02-03 13:51:07 +01:00
// Save selector and tokenization
cached.selector = selector;
}
return cached;
};
2017-02-03 13:51:07 +01:00
/**
* A low-level selection function that works with Sizzle's compiled
* selector functions
* @param {String|Function} selector A selector or a pre-compiled
* selector function built with Sizzle.compile
* @param {Element} context
* @param {Array} [results]
* @param {Array} [seed] A set of elements to match against
*/
select = Sizzle.select = function( selector, context, results, seed ) {
var i, tokens, token, type, find,
compiled = typeof selector === "function" && selector,
match = !seed && tokenize( (selector = compiled.selector || selector) );
2017-02-03 13:51:07 +01:00
results = results || [];
2017-02-03 13:51:07 +01:00
// Try to minimize operations if there is only one selector in the list and no seed
// (the latter of which guarantees us context)
if ( match.length === 1 ) {
2017-02-03 13:51:07 +01:00
// Reduce context if the leading compound selector is an ID
tokens = match[0] = match[0].slice( 0 );
if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
2018-01-04 17:58:16 +01:00
context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) {
2017-02-03 13:51:07 +01:00
context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
if ( !context ) {
return results;
2017-02-03 13:51:07 +01:00
// Precompiled matchers will still verify ancestry, so step up a level
} else if ( compiled ) {
context = context.parentNode;
2017-02-03 13:51:07 +01:00
}
selector = selector.slice( tokens.shift().value.length );
}
2017-02-03 13:51:07 +01:00
// Fetch a seed set for right-to-left matching
i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
while ( i-- ) {
token = tokens[i];
2017-02-03 13:51:07 +01:00
// Abort if we hit a combinator
if ( Expr.relative[ (type = token.type) ] ) {
break;
}
if ( (find = Expr.find[ type ]) ) {
// Search, expanding context for leading sibling combinators
if ( (seed = find(
token.matches[0].replace( runescape, funescape ),
rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
)) ) {
2017-02-03 13:51:07 +01:00
// If seed is empty or no tokens remain, we can return early
tokens.splice( i, 1 );
selector = seed.length && toSelector( tokens );
if ( !selector ) {
push.apply( results, seed );
return results;
}
2017-02-03 13:51:07 +01:00
break;
}
}
}
}
2017-02-03 13:51:07 +01:00
// Compile and execute a filtering function if one is not provided
// Provide `match` to avoid retokenization if we modified the selector above
( compiled || compile( selector, match ) )(
seed,
context,
!documentIsHTML,
results,
!context || rsibling.test( selector ) && testContext( context.parentNode ) || context
);
return results;
};
2017-02-03 13:51:07 +01:00
// One-time assignments
2017-02-03 13:51:07 +01:00
// Sort stability
support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
2017-02-03 13:51:07 +01:00
// Support: Chrome 14-35+
// Always assume duplicates if they aren't passed to the comparison function
support.detectDuplicates = !!hasDuplicate;
2017-02-03 13:51:07 +01:00
// Initialize against the default document
setDocument();
2017-02-03 13:51:07 +01:00
// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
// Detached nodes confoundingly follow *each other*
2018-01-04 17:58:16 +01:00
support.sortDetached = assert(function( el ) {
// Should return 1, but returns 4 (following)
2018-01-04 17:58:16 +01:00
return el.compareDocumentPosition( document.createElement("fieldset") ) & 1;
});
2017-02-03 13:51:07 +01:00
// Support: IE<8
// Prevent attribute/property "interpolation"
2018-01-04 17:58:16 +01:00
// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
if ( !assert(function( el ) {
el.innerHTML = "<a href='#'></a>";
return el.firstChild.getAttribute("href") === "#" ;
}) ) {
addHandle( "type|href|height|width", function( elem, name, isXML ) {
if ( !isXML ) {
return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
}
});
}
2017-02-03 13:51:07 +01:00
// Support: IE<9
// Use defaultValue in place of getAttribute("value")
2018-01-04 17:58:16 +01:00
if ( !support.attributes || !assert(function( el ) {
el.innerHTML = "<input/>";
el.firstChild.setAttribute( "value", "" );
return el.firstChild.getAttribute( "value" ) === "";
}) ) {
addHandle( "value", function( elem, name, isXML ) {
if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
return elem.defaultValue;
}
});
}
2017-02-03 13:51:07 +01:00
// Support: IE<9
// Use getAttributeNode to fetch booleans when getAttribute lies
2018-01-04 17:58:16 +01:00
if ( !assert(function( el ) {
return el.getAttribute("disabled") == null;
}) ) {
addHandle( booleans, function( elem, name, isXML ) {
var val;
if ( !isXML ) {
return elem[ name ] === true ? name.toLowerCase() :
(val = elem.getAttributeNode( name )) && val.specified ?
val.value :
null;
}
});
}
2017-02-03 13:51:07 +01:00
// EXPOSE
2018-01-04 17:58:16 +01:00
var _sizzle = window.Sizzle;
Sizzle.noConflict = function() {
if ( window.Sizzle === Sizzle ) {
window.Sizzle = _sizzle;
}
return Sizzle;
};
if ( typeof define === "function" && define.amd ) {
define('sizzle',[],function() { return Sizzle; });
// Sizzle requires that there be a global window in Common-JS like environments
} else if ( typeof module !== "undefined" && module.exports ) {
module.exports = Sizzle;
} else {
window.Sizzle = Sizzle;
}
// EXPOSE
2017-02-03 13:51:07 +01:00
})( window );
2017-02-03 13:51:07 +01:00
2018-02-14 16:53:07 +01:00
define('lodash.fp',['lodash', 'lodash.converter'], function (_, lodashConverter) {
var fp = lodashConverter(_.runInContext());
return fp;
});
2018-03-05 14:43:53 +01:00
function CustomEvent ( event, params ) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent( 'CustomEvent' );
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
return evt;
}
if ( typeof window.CustomEvent !== "function" ) {
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
}
2018-01-04 17:58:16 +01:00
if (!String.prototype.includes) {
String.prototype.includes = function(search, start) {
'use strict';
if (typeof start !== 'number') {
start = 0;
}
if (start + search.length > this.length) {
return false;
} else {
return this.indexOf(search, start) !== -1; // eslint-disable-line lodash/prefer-includes
}
2017-07-22 22:21:05 +02:00
};
}
2017-06-23 20:25:33 +02:00
2018-01-04 17:58:16 +01:00
if (!String.prototype.endsWith) {
String.prototype.endsWith = function (searchString, position) {
var subjectString = this.toString();
if (position === undefined || position > subjectString.length) {
position = subjectString.length;
}
position -= searchString.length;
var lastIndex = subjectString.indexOf(searchString, position);
return lastIndex !== -1 && lastIndex === position;
2017-07-22 22:21:05 +02:00
};
}
2017-06-23 20:25:33 +02:00
2018-01-04 17:58:16 +01:00
if (!String.prototype.startsWith) {
String.prototype.startsWith = function (searchString, position) {
position = position || 0;
return this.substr(position, searchString.length) === searchString;
2017-07-22 22:21:05 +02:00
};
}
2017-06-23 20:25:33 +02:00
2018-01-04 17:58:16 +01:00
if (!String.prototype.splitOnce) {
String.prototype.splitOnce = function (delimiter) {
var components = this.split(delimiter);
return [components.shift(), components.join(delimiter)];
};
2017-07-22 22:21:05 +02:00
}
2017-06-23 20:25:33 +02:00
2018-01-04 17:58:16 +01:00
if (!String.prototype.trim) {
String.prototype.trim = function () {
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
};
2017-07-22 22:21:05 +02:00
}
2018-01-04 17:58:16 +01:00
;
define("polyfill", function(){});
2017-06-23 20:25:33 +02:00
2018-01-17 19:45:33 +01:00
/**
* @preserve jed.js https://github.com/SlexAxton/Jed
*/
2018-01-04 17:58:16 +01:00
/*
-----------
A gettext compatible i18n library for modern JavaScript Applications
2017-06-23 20:25:33 +02:00
2018-01-04 17:58:16 +01:00
by Alex Sexton - AlexSexton [at] gmail - @SlexAxton
2018-01-17 19:45:33 +01:00
MIT License
A jQuery Foundation project - requires CLA to contribute -
https://contribute.jquery.org/CLA/
2017-06-23 20:25:33 +02:00
2018-01-04 17:58:16 +01:00
Jed offers the entire applicable GNU gettext spec'd set of
functions, but also offers some nicer wrappers around them.
The api for gettext was written for a language with no function
overloading, so Jed allows a little more of that.
2017-06-23 20:25:33 +02:00
2018-01-04 17:58:16 +01:00
Many thanks to Joshua I. Miller - unrtst@cpan.org - who wrote
gettext.js back in 2008. I was able to vet a lot of my ideas
against his. I also made sure Jed passed against his tests
in order to offer easy upgrades -- jsgettext.berlios.de
*/
(function (root, undef) {
2017-06-23 20:25:33 +02:00
2018-01-04 17:58:16 +01:00
// Set up some underscore-style functions, if you already have
// underscore, feel free to delete this section, and use it
// directly, however, the amount of functions used doesn't
// warrant having underscore as a full dependency.
// Underscore 1.3.0 was used to port and is licensed
// under the MIT License by Jeremy Ashkenas.
var ArrayProto = Array.prototype,
ObjProto = Object.prototype,
slice = ArrayProto.slice,
hasOwnProp = ObjProto.hasOwnProperty,
nativeForEach = ArrayProto.forEach,
breaker = {};
2017-06-23 20:25:33 +02:00
2018-01-04 17:58:16 +01:00
// We're not using the OOP style _ so we don't need the
// extra level of indirection. This still means that you
// sub out for real `_` though.
var _ = {
forEach : function( obj, iterator, context ) {
var i, l, key;
if ( obj === null ) {
return;
}
2017-06-23 20:25:33 +02:00
2018-01-04 17:58:16 +01:00
if ( nativeForEach && obj.forEach === nativeForEach ) {
obj.forEach( iterator, context );
}
else if ( obj.length === +obj.length ) {
for ( i = 0, l = obj.length; i < l; i++ ) {
if ( i in obj && iterator.call( context, obj[i], i, obj ) === breaker ) {
return;
}
}
}
else {
for ( key in obj) {
if ( hasOwnProp.call( obj, key ) ) {
if ( iterator.call (context, obj[key], key, obj ) === breaker ) {
return;
}
}
}
}
},
extend : function( obj ) {
this.forEach( slice.call( arguments, 1 ), function ( source ) {
for ( var prop in source ) {
obj[prop] = source[prop];
}
});
return obj;
}
};
// END Miniature underscore impl
2017-06-23 20:25:33 +02:00
2018-01-04 17:58:16 +01:00
// Jed is a constructor function
var Jed = function ( options ) {
// Some minimal defaults
this.defaults = {
"locale_data" : {
"messages" : {
"" : {
"domain" : "messages",
"lang" : "en",
"plural_forms" : "nplurals=2; plural=(n != 1);"
}
// There are no default keys, though
}
},
// The default domain if one is missing
2018-01-17 19:45:33 +01:00
"domain" : "messages",
// enable debug mode to log untranslated strings to the console
"debug" : false
2018-01-04 17:58:16 +01:00
};
2017-06-23 20:25:33 +02:00
2018-01-04 17:58:16 +01:00
// Mix in the sent options with the default options
this.options = _.extend( {}, this.defaults, options );
this.textdomain( this.options.domain );
2017-06-23 20:25:33 +02:00
2018-01-04 17:58:16 +01:00
if ( options.domain && ! this.options.locale_data[ this.options.domain ] ) {
throw new Error('Text domain set to non-existent domain: `' + options.domain + '`');
}
};
2017-06-23 20:25:33 +02:00
2018-01-04 17:58:16 +01:00
// The gettext spec sets this character as the default
// delimiter for context lookups.
// e.g.: context\u0004key
// If your translation company uses something different,
// just change this at any time and it will use that instead.
Jed.context_delimiter = String.fromCharCode( 4 );
2017-06-23 20:25:33 +02:00
2018-01-04 17:58:16 +01:00
function getPluralFormFunc ( plural_form_string ) {
return Jed.PF.compile( plural_form_string || "nplurals=2; plural=(n != 1);");
2017-07-22 22:21:05 +02:00
}
2017-06-23 20:25:33 +02:00
2018-01-04 17:58:16 +01:00
function Chain( key, i18n ){
this._key = key;
this._i18n = i18n;
}
2017-07-22 22:21:05 +02:00
// Create a chainable api for adding args prettily
_.extend( Chain.prototype, {
onDomain : function ( domain ) {
this._domain = domain;
return this;
},
withContext : function ( context ) {
this._context = context;
return this;
},
ifPlural : function ( num, pkey ) {
this._val = num;
this._pkey = pkey;
return this;
},
fetch : function ( sArr ) {
if ( {}.toString.call( sArr ) != '[object Array]' ) {
2018-01-17 19:45:33 +01:00
sArr = [].slice.call(arguments, 0);
2017-07-22 22:21:05 +02:00
}
return ( sArr && sArr.length ? Jed.sprintf : function(x){ return x; } )(
this._i18n.dcnpgettext(this._domain, this._context, this._key, this._pkey, this._val),
sArr
);
}
});
// Add functions to the Jed prototype.
// These will be the functions on the object that's returned
// from creating a `new Jed()`
// These seem redundant, but they gzip pretty well.
_.extend( Jed.prototype, {
// The sexier api start point
translate : function ( key ) {
return new Chain( key, this );
},
textdomain : function ( domain ) {
if ( ! domain ) {
return this._textdomain;
}
this._textdomain = domain;
},
gettext : function ( key ) {
return this.dcnpgettext.call( this, undef, undef, key );
},
dgettext : function ( domain, key ) {
return this.dcnpgettext.call( this, domain, undef, key );
},
dcgettext : function ( domain , key /*, category */ ) {
// Ignores the category anyways
return this.dcnpgettext.call( this, domain, undef, key );
},
ngettext : function ( skey, pkey, val ) {
return this.dcnpgettext.call( this, undef, undef, skey, pkey, val );
},
dngettext : function ( domain, skey, pkey, val ) {
return this.dcnpgettext.call( this, domain, undef, skey, pkey, val );
},
dcngettext : function ( domain, skey, pkey, val/*, category */) {
return this.dcnpgettext.call( this, domain, undef, skey, pkey, val );
},
pgettext : function ( context, key ) {
return this.dcnpgettext.call( this, undef, context, key );
},
dpgettext : function ( domain, context, key ) {
return this.dcnpgettext.call( this, domain, context, key );
},
dcpgettext : function ( domain, context, key/*, category */) {
return this.dcnpgettext.call( this, domain, context, key );
},
npgettext : function ( context, skey, pkey, val ) {
return this.dcnpgettext.call( this, undef, context, skey, pkey, val );
},
dnpgettext : function ( domain, context, skey, pkey, val ) {
return this.dcnpgettext.call( this, domain, context, skey, pkey, val );
},
// The most fully qualified gettext function. It has every option.
// Since it has every option, we can use it from every other method.
// This is the bread and butter.
// Technically there should be one more argument in this function for 'Category',
// but since we never use it, we might as well not waste the bytes to define it.
dcnpgettext : function ( domain, context, singular_key, plural_key, val ) {
// Set some defaults
plural_key = plural_key || singular_key;
// Use the global domain default if one
// isn't explicitly passed in
domain = domain || this._textdomain;
var fallback;
// Handle special cases
// No options found
if ( ! this.options ) {
// There's likely something wrong, but we'll return the correct key for english
// We do this by instantiating a brand new Jed instance with the default set
// for everything that could be broken.
fallback = new Jed();
return fallback.dcnpgettext.call( fallback, undefined, undefined, singular_key, plural_key, val );
}
// No translation data provided
if ( ! this.options.locale_data ) {
throw new Error('No locale data provided.');
}
if ( ! this.options.locale_data[ domain ] ) {
throw new Error('Domain `' + domain + '` was not found.');
}
if ( ! this.options.locale_data[ domain ][ "" ] ) {
throw new Error('No locale meta information provided.');
}
// Make sure we have a truthy key. Otherwise we might start looking
// into the empty string key, which is the options for the locale
// data.
if ( ! singular_key ) {
throw new Error('No translation key found.');
}
var key = context ? context + Jed.context_delimiter + singular_key : singular_key,
locale_data = this.options.locale_data,
dict = locale_data[ domain ],
2018-01-17 19:45:33 +01:00
defaultConf = (locale_data.messages || this.defaults.locale_data.messages)[""],
pluralForms = dict[""].plural_forms || dict[""]["Plural-Forms"] || dict[""]["plural-forms"] || defaultConf.plural_forms || defaultConf["Plural-Forms"] || defaultConf["plural-forms"],
2017-07-22 22:21:05 +02:00
val_list,
res;
2018-01-17 19:45:33 +01:00
var val_idx;
if (val === undefined) {
// No value passed in; assume singular key lookup.
val_idx = 0;
} else {
// Value has been passed in; use plural-forms calculations.
// Handle invalid numbers, but try casting strings for good measure
if ( typeof val != 'number' ) {
val = parseInt( val, 10 );
if ( isNaN( val ) ) {
throw new Error('The number that was passed in is not a number.');
}
}
val_idx = getPluralFormFunc(pluralForms)(val);
}
2017-07-22 22:21:05 +02:00
// Throw an error if a domain isn't found
if ( ! dict ) {
throw new Error('No domain named `' + domain + '` could be found.');
}
val_list = dict[ key ];
// If there is no match, then revert back to
// english style singular/plural with the keys passed in.
2018-01-17 19:45:33 +01:00
if ( ! val_list || val_idx > val_list.length ) {
2017-07-22 22:21:05 +02:00
if (this.options.missing_key_callback) {
2018-01-17 19:45:33 +01:00
this.options.missing_key_callback(key, domain);
}
res = [ singular_key, plural_key ];
// collect untranslated strings
if (this.options.debug===true) {
console.log(res[ getPluralFormFunc(pluralForms)( val ) ]);
2017-07-22 22:21:05 +02:00
}
2018-01-17 19:45:33 +01:00
return res[ getPluralFormFunc()( val ) ];
2017-07-22 22:21:05 +02:00
}
res = val_list[ val_idx ];
// This includes empty strings on purpose
if ( ! res ) {
2018-01-17 19:45:33 +01:00
res = [ singular_key, plural_key ];
return res[ getPluralFormFunc()( val ) ];
2017-07-22 22:21:05 +02:00
}
return res;
}
});
// We add in sprintf capabilities for post translation value interolation
// This is not internally used, so you can remove it if you have this
// available somewhere else, or want to use a different system.
// We _slightly_ modify the normal sprintf behavior to more gracefully handle
// undefined values.
/**
sprintf() for JavaScript 0.7-beta1
http://www.diveintojavascript.com/projects/javascript-sprintf
Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of sprintf() for JavaScript nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var sprintf = (function() {
function get_type(variable) {
return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
}
function str_repeat(input, multiplier) {
for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
return output.join('');
}
var str_format = function() {
if (!str_format.cache.hasOwnProperty(arguments[0])) {
str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
}
return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
};
str_format.format = function(parse_tree, argv) {
var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
for (i = 0; i < tree_length; i++) {
node_type = get_type(parse_tree[i]);
if (node_type === 'string') {
output.push(parse_tree[i]);
}
else if (node_type === 'array') {
match = parse_tree[i]; // convenience purposes only
if (match[2]) { // keyword argument
arg = argv[cursor];
for (k = 0; k < match[2].length; k++) {
if (!arg.hasOwnProperty(match[2][k])) {
throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
}
arg = arg[match[2][k]];
}
}
else if (match[1]) { // positional argument (explicit)
arg = argv[match[1]];
}
else { // positional argument (implicit)
arg = argv[cursor++];
}
if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
}
// Jed EDIT
if ( typeof arg == 'undefined' || arg === null ) {
arg = '';
}
// Jed EDIT
switch (match[8]) {
case 'b': arg = arg.toString(2); break;
case 'c': arg = String.fromCharCode(arg); break;
case 'd': arg = parseInt(arg, 10); break;
case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
case 'o': arg = arg.toString(8); break;
case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
case 'u': arg = Math.abs(arg); break;
case 'x': arg = arg.toString(16); break;
case 'X': arg = arg.toString(16).toUpperCase(); break;
}
arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
pad_length = match[6] - String(arg).length;
pad = match[6] ? str_repeat(pad_character, pad_length) : '';
output.push(match[5] ? arg + pad : pad + arg);
}
}
return output.join('');
};
str_format.cache = {};
str_format.parse = function(fmt) {
var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
while (_fmt) {
if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
parse_tree.push(match[0]);
}
else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
parse_tree.push('%');
}
else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
if (match[2]) {
arg_names |= 1;
var field_list = [], replacement_field = match[2], field_match = [];
if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
}
else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
field_list.push(field_match[1]);
}
else {
throw('[sprintf] huh?');
}
}
}
else {
throw('[sprintf] huh?');
}
match[2] = field_list;
}
else {
arg_names |= 2;
}
if (arg_names === 3) {
throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
}
parse_tree.push(match);
}
else {
throw('[sprintf] huh?');
}
_fmt = _fmt.substring(match[0].length);
}
return parse_tree;
};
return str_format;
})();
var vsprintf = function(fmt, argv) {
argv.unshift(fmt);
return sprintf.apply(null, argv);
};
Jed.parse_plural = function ( plural_forms, n ) {
plural_forms = plural_forms.replace(/n/g, n);
return Jed.parse_expression(plural_forms);
};
Jed.sprintf = function ( fmt, args ) {
if ( {}.toString.call( args ) == '[object Array]' ) {
return vsprintf( fmt, [].slice.call(args) );
}
return sprintf.apply(this, [].slice.call(arguments) );
};
Jed.prototype.sprintf = function () {
return Jed.sprintf.apply(this, arguments);
};
// END sprintf Implementation
// Start the Plural forms section
// This is a full plural form expression parser. It is used to avoid
// running 'eval' or 'new Function' directly against the plural
// forms.
//
// This can be important if you get translations done through a 3rd
// party vendor. I encourage you to use this instead, however, I
// also will provide a 'precompiler' that you can use at build time
// to output valid/safe function representations of the plural form
// expressions. This means you can build this code out for the most
// part.
Jed.PF = {};
Jed.PF.parse = function ( p ) {
var plural_str = Jed.PF.extractPluralExpr( p );
return Jed.PF.parser.parse.call(Jed.PF.parser, plural_str);
};
Jed.PF.compile = function ( p ) {
// Handle trues and falses as 0 and 1
function imply( val ) {
return (val === true ? 1 : val ? val : 0);
}
var ast = Jed.PF.parse( p );
return function ( n ) {
return imply( Jed.PF.interpreter( ast )( n ) );
};
};
Jed.PF.interpreter = function ( ast ) {
return function ( n ) {
var res;
switch ( ast.type ) {
case 'GROUP':
return Jed.PF.interpreter( ast.expr )( n );
case 'TERNARY':
if ( Jed.PF.interpreter( ast.expr )( n ) ) {
return Jed.PF.interpreter( ast.truthy )( n );
}
return Jed.PF.interpreter( ast.falsey )( n );
case 'OR':
return Jed.PF.interpreter( ast.left )( n ) || Jed.PF.interpreter( ast.right )( n );
case 'AND':
return Jed.PF.interpreter( ast.left )( n ) && Jed.PF.interpreter( ast.right )( n );
case 'LT':
return Jed.PF.interpreter( ast.left )( n ) < Jed.PF.interpreter( ast.right )( n );
case 'GT':
return Jed.PF.interpreter( ast.left )( n ) > Jed.PF.interpreter( ast.right )( n );
case 'LTE':
return Jed.PF.interpreter( ast.left )( n ) <= Jed.PF.interpreter( ast.right )( n );
case 'GTE':
return Jed.PF.interpreter( ast.left )( n ) >= Jed.PF.interpreter( ast.right )( n );
case 'EQ':
return Jed.PF.interpreter( ast.left )( n ) == Jed.PF.interpreter( ast.right )( n );
case 'NEQ':
return Jed.PF.interpreter( ast.left )( n ) != Jed.PF.interpreter( ast.right )( n );
case 'MOD':
return Jed.PF.interpreter( ast.left )( n ) % Jed.PF.interpreter( ast.right )( n );
case 'VAR':
return n;
case 'NUM':
return ast.val;
default:
throw new Error("Invalid Token found.");
}
};
};
Jed.PF.extractPluralExpr = function ( p ) {
// trim first
p = p.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
if (! /;\s*$/.test(p)) {
p = p.concat(';');
}
var nplurals_re = /nplurals\=(\d+);/,
plural_re = /plural\=(.*);/,
nplurals_matches = p.match( nplurals_re ),
res = {},
plural_matches;
// Find the nplurals number
if ( nplurals_matches.length > 1 ) {
res.nplurals = nplurals_matches[1];
}
else {
throw new Error('nplurals not found in plural_forms string: ' + p );
}
// remove that data to get to the formula
p = p.replace( nplurals_re, "" );
plural_matches = p.match( plural_re );
if (!( plural_matches && plural_matches.length > 1 ) ) {
throw new Error('`plural` expression not found: ' + p);
}
return plural_matches[ 1 ];
};
/* Jison generated parser */
Jed.PF.parser = (function(){
var parser = {trace: function trace() { },
yy: {},
symbols_: {"error":2,"expressions":3,"e":4,"EOF":5,"?":6,":":7,"||":8,"&&":9,"<":10,"<=":11,">":12,">=":13,"!=":14,"==":15,"%":16,"(":17,")":18,"n":19,"NUMBER":20,"$accept":0,"$end":1},
terminals_: {2:"error",5:"EOF",6:"?",7:":",8:"||",9:"&&",10:"<",11:"<=",12:">",13:">=",14:"!=",15:"==",16:"%",17:"(",18:")",19:"n",20:"NUMBER"},
productions_: [0,[3,2],[4,5],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,1],[4,1]],
performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
var $0 = $$.length - 1;
switch (yystate) {
2018-01-17 19:45:33 +01:00
case 1: return { type : 'GROUP', expr: $$[$0-1] };
2017-07-22 22:21:05 +02:00
break;
2018-01-17 19:45:33 +01:00
case 2:this.$ = { type: 'TERNARY', expr: $$[$0-4], truthy : $$[$0-2], falsey: $$[$0] };
2017-07-22 22:21:05 +02:00
break;
case 3:this.$ = { type: "OR", left: $$[$0-2], right: $$[$0] };
break;
case 4:this.$ = { type: "AND", left: $$[$0-2], right: $$[$0] };
break;
2018-01-17 19:45:33 +01:00
case 5:this.$ = { type: 'LT', left: $$[$0-2], right: $$[$0] };
2017-07-22 22:21:05 +02:00
break;
case 6:this.$ = { type: 'LTE', left: $$[$0-2], right: $$[$0] };
break;
case 7:this.$ = { type: 'GT', left: $$[$0-2], right: $$[$0] };
break;
case 8:this.$ = { type: 'GTE', left: $$[$0-2], right: $$[$0] };
break;
case 9:this.$ = { type: 'NEQ', left: $$[$0-2], right: $$[$0] };
break;
case 10:this.$ = { type: 'EQ', left: $$[$0-2], right: $$[$0] };
break;
case 11:this.$ = { type: 'MOD', left: $$[$0-2], right: $$[$0] };
break;
2018-01-17 19:45:33 +01:00
case 12:this.$ = { type: 'GROUP', expr: $$[$0-1] };
2017-07-22 22:21:05 +02:00
break;
2018-01-17 19:45:33 +01:00
case 13:this.$ = { type: 'VAR' };
2017-07-22 22:21:05 +02:00
break;
2018-01-17 19:45:33 +01:00
case 14:this.$ = { type: 'NUM', val: Number(yytext) };
2017-07-22 22:21:05 +02:00
break;
}
},
table: [{3:1,4:2,17:[1,3],19:[1,4],20:[1,5]},{1:[3]},{5:[1,6],6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{4:17,17:[1,3],19:[1,4],20:[1,5]},{5:[2,13],6:[2,13],7:[2,13],8:[2,13],9:[2,13],10:[2,13],11:[2,13],12:[2,13],13:[2,13],14:[2,13],15:[2,13],16:[2,13],18:[2,13]},{5:[2,14],6:[2,14],7:[2,14],8:[2,14],9:[2,14],10:[2,14],11:[2,14],12:[2,14],13:[2,14],14:[2,14],15:[2,14],16:[2,14],18:[2,14]},{1:[2,1]},{4:18,17:[1,3],19:[1,4],20:[1,5]},{4:19,17:[1,3],19:[1,4],20:[1,5]},{4:20,17:[1,3],19:[1,4],20:[1,5]},{4:21,17:[1,3],19:[1,4],20:[1,5]},{4:22,17:[1,3],19:[1,4],20:[1,5]},{4:23,17:[1,3],19:[1,4],20:[1,5]},{4:24,17:[1,3],19:[1,4],20:[1,5]},{4:25,17:[1,3],19:[1,4],20:[1,5]},{4:26,17:[1,3],19:[1,4],20:[1,5]},{4:27,17:[1,3],19:[1,4],20:[1,5]},{6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[1,28]},{6:[1,7],7:[1,29],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{5:[2,3],6:[2,3],7:[2,3],8:[2,3],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,3]},{5:[2,4],6:[2,4],7:[2,4],8:[2,4],9:[2,4],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,4]},{5:[2,5],6:[2,5],7:[2,5],8:[2,5],9:[2,5],10:[2,5],11:[2,5],12:[2,5],13:[2,5],14:[2,5],15:[2,5],16:[1,16],18:[2,5]},{5:[2,6],6:[2,6],7:[2,6],8:[2,6],9:[2,6],10:[2,6],11:[2,6],12:[2,6],13:[2,6],14:[2,6],15:[2,6],16:[1,16],18:[2,6]},{5:[2,7],6:[2,7],7:[2,7],8:[2,7],9:[2,7],10:[2,7],11:[2,7],12:[2,7],13:[2,7],14:[2,7],15:[2,7],16:[1,16],18:[2,7]},{5:[2,8],6:[2,8],7:[2,8],8:[2,8],9:[2,8],10:[2,8],11:[2,8],12:[2,8],13:[2,8],14:[2,8],15:[2,8],16:[1,16],18:[2,8]},{5:[2,9],6:[2,9],7:[2,9],8:[2,9],9:[2,9],10:[2,9],11:[2,9],12:[2,9],13:[2,9],14:[2,9],15:[2,9],16:[1,16],18:[2,9]},{5:[2,10],6:[2,10],7:[2,10],8:[2,10],9:[2,10],10:[2,10],11:[2,10],12:[2,10],13:[2,10],14:[2,10],15:[2,10],16:[1,16],18:[2,10]},{5:[2,11],6:[2,11],7:[2,11],8:[2,11],9:[2,11],10:[2,11],11:[2,11],12:[2,11],13:[2,11],14:[2,11],15:[2,11],16:[2,11],18:[2,11]},{5:[2,12],6:[2,12],7:[2,12],8:[2,12],9:[2,12],10:[2,12],11:[2,12],12:[2,12],13:[2,12],14:[2,12],15:[2,12],16:[2,12],18:[2,12]},{4:30,17:[1,3],19:[1,4],20:[1,5]},{5:[2,2],6:[1,7],7:[2,2],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,2]}],
defaultActions: {6:[2,1]},
parseError: function parseError(str, hash) {
throw new Error(str);
},
parse: function parse(input) {
var self = this,
stack = [0],
vstack = [null], // semantic value stack
lstack = [], // location stack
table = this.table,
yytext = '',
yylineno = 0,
yyleng = 0,
recovering = 0,
TERROR = 2,
EOF = 1;
//this.reductionCount = this.shiftCount = 0;
this.lexer.setInput(input);
this.lexer.yy = this.yy;
this.yy.lexer = this.lexer;
if (typeof this.lexer.yylloc == 'undefined')
this.lexer.yylloc = {};
var yyloc = this.lexer.yylloc;
lstack.push(yyloc);
if (typeof this.yy.parseError === 'function')
this.parseError = this.yy.parseError;
function popStack (n) {
stack.length = stack.length - 2*n;
vstack.length = vstack.length - n;
lstack.length = lstack.length - n;
}
function lex() {
var token;
token = self.lexer.lex() || 1; // $end = 1
// if token isn't its numeric value, convert
if (typeof token !== 'number') {
token = self.symbols_[token] || token;
}
return token;
}
var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected;
while (true) {
// retreive state number from top of stack
state = stack[stack.length-1];
// use default actions if available
if (this.defaultActions[state]) {
action = this.defaultActions[state];
} else {
if (symbol == null)
symbol = lex();
// read action for current state and first input
action = table[state] && table[state][symbol];
}
// handle parse error
_handle_error:
if (typeof action === 'undefined' || !action.length || !action[0]) {
if (!recovering) {
// Report error
expected = [];
for (p in table[state]) if (this.terminals_[p] && p > 2) {
expected.push("'"+this.terminals_[p]+"'");
}
var errStr = '';
if (this.lexer.showPosition) {
errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'";
} else {
errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +
(symbol == 1 /*EOF*/ ? "end of input" :
("'"+(this.terminals_[symbol] || symbol)+"'"));
}
this.parseError(errStr,
{text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
}
// just recovered from another error
if (recovering == 3) {
if (symbol == EOF) {
throw new Error(errStr || 'Parsing halted.');
}
// discard current lookahead and grab another
yyleng = this.lexer.yyleng;
yytext = this.lexer.yytext;
yylineno = this.lexer.yylineno;
yyloc = this.lexer.yylloc;
symbol = lex();
}
// try to recover from error
while (1) {
// check for error recovery rule in this state
if ((TERROR.toString()) in table[state]) {
break;
}
if (state == 0) {
throw new Error(errStr || 'Parsing halted.');
}
popStack(1);
state = stack[stack.length-1];
}
preErrorSymbol = symbol; // save the lookahead token
symbol = TERROR; // insert generic error symbol as new lookahead
state = stack[stack.length-1];
action = table[state] && table[state][TERROR];
recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
}
// this shouldn't happen, unless resolve defaults are off
if (action[0] instanceof Array && action.length > 1) {
throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol);
}
switch (action[0]) {
case 1: // shift
//this.shiftCount++;
stack.push(symbol);
vstack.push(this.lexer.yytext);
lstack.push(this.lexer.yylloc);
stack.push(action[1]); // push state
symbol = null;
if (!preErrorSymbol) { // normal execution/no error
yyleng = this.lexer.yyleng;
yytext = this.lexer.yytext;
yylineno = this.lexer.yylineno;
yyloc = this.lexer.yylloc;
if (recovering > 0)
recovering--;
} else { // error just occurred, resume old lookahead f/ before error
symbol = preErrorSymbol;
preErrorSymbol = null;
}
break;
case 2: // reduce
//this.reductionCount++;
len = this.productions_[action[1]][1];
// perform semantic action
yyval.$ = vstack[vstack.length-len]; // default to $$ = $1
// default location, uses first token for firsts, last for lasts
yyval._$ = {
first_line: lstack[lstack.length-(len||1)].first_line,
last_line: lstack[lstack.length-1].last_line,
first_column: lstack[lstack.length-(len||1)].first_column,
last_column: lstack[lstack.length-1].last_column
};
r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
if (typeof r !== 'undefined') {
return r;
}
// pop off stack
if (len) {
stack = stack.slice(0,-1*len*2);
vstack = vstack.slice(0, -1*len);
lstack = lstack.slice(0, -1*len);
}
stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce)
vstack.push(yyval.$);
lstack.push(yyval._$);
// goto new state = table[STATE][NONTERMINAL]
newState = table[stack[stack.length-2]][stack[stack.length-1]];
stack.push(newState);
break;
case 3: // accept
return true;
}
}
return true;
}};/* Jison generated lexer */
var lexer = (function(){
var lexer = ({EOF:1,
parseError:function parseError(str, hash) {
if (this.yy.parseError) {
this.yy.parseError(str, hash);
} else {
throw new Error(str);
}
},
setInput:function (input) {
this._input = input;
this._more = this._less = this.done = false;
this.yylineno = this.yyleng = 0;
this.yytext = this.matched = this.match = '';
this.conditionStack = ['INITIAL'];
this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
return this;
},
input:function () {
var ch = this._input[0];
this.yytext+=ch;
this.yyleng++;
this.match+=ch;
this.matched+=ch;
var lines = ch.match(/\n/);
if (lines) this.yylineno++;
this._input = this._input.slice(1);
return ch;
},
unput:function (ch) {
this._input = ch + this._input;
return this;
},
more:function () {
this._more = true;
return this;
},
pastInput:function () {
var past = this.matched.substr(0, this.matched.length - this.match.length);
return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
},
upcomingInput:function () {
var next = this.match;
if (next.length < 20) {
next += this._input.substr(0, 20-next.length);
}
return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
},
showPosition:function () {
var pre = this.pastInput();
var c = new Array(pre.length + 1).join("-");
return pre + this.upcomingInput() + "\n" + c+"^";
},
next:function () {
if (this.done) {
return this.EOF;
}
if (!this._input) this.done = true;
var token,
match,
col,
lines;
if (!this._more) {
this.yytext = '';
this.match = '';
}
var rules = this._currentRules();
for (var i=0;i < rules.length; i++) {
match = this._input.match(this.rules[rules[i]]);
if (match) {
lines = match[0].match(/\n.*/g);
if (lines) this.yylineno += lines.length;
this.yylloc = {first_line: this.yylloc.last_line,
last_line: this.yylineno+1,
first_column: this.yylloc.last_column,
last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length}
this.yytext += match[0];
this.match += match[0];
this.matches = match;
this.yyleng = this.yytext.length;
this._more = false;
this._input = this._input.slice(match[0].length);
this.matched += match[0];
token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]);
if (token) return token;
else return;
}
}
if (this._input === "") {
return this.EOF;
} else {
2018-01-17 19:45:33 +01:00
this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
2017-07-22 22:21:05 +02:00
{text: "", token: null, line: this.yylineno});
}
},
lex:function lex() {
var r = this.next();
if (typeof r !== 'undefined') {
return r;
} else {
return this.lex();
}
},
begin:function begin(condition) {
this.conditionStack.push(condition);
},
popState:function popState() {
return this.conditionStack.pop();
},
_currentRules:function _currentRules() {
return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
},
topState:function () {
return this.conditionStack[this.conditionStack.length-2];
},
pushState:function begin(condition) {
this.begin(condition);
}});
lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
var YYSTATE=YY_START;
switch($avoiding_name_collisions) {
case 0:/* skip whitespace */
break;
case 1:return 20
break;
case 2:return 19
break;
case 3:return 8
break;
case 4:return 9
break;
case 5:return 6
break;
case 6:return 7
break;
case 7:return 11
break;
case 8:return 13
break;
case 9:return 10
break;
case 10:return 12
break;
case 11:return 14
break;
case 12:return 15
break;
case 13:return 16
break;
case 14:return 17
break;
case 15:return 18
break;
case 16:return 5
break;
case 17:return 'INVALID'
break;
}
};
lexer.rules = [/^\s+/,/^[0-9]+(\.[0-9]+)?\b/,/^n\b/,/^\|\|/,/^&&/,/^\?/,/^:/,/^<=/,/^>=/,/^</,/^>/,/^!=/,/^==/,/^%/,/^\(/,/^\)/,/^$/,/^./];
lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"inclusive":true}};return lexer;})()
parser.lexer = lexer;
return parser;
})();
// End parser
// Handle node, amd, and global systems
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = Jed;
}
exports.Jed = Jed;
}
else {
if (typeof define === 'function' && define.amd) {
2018-01-17 19:45:33 +01:00
define('jed',[],function() {
2017-07-22 22:21:05 +02:00
return Jed;
});
}
// Leak a global regardless of module system
root['Jed'] = Jed;
}
})(this);
//! moment.js locale configuration
//! locale : Afrikaans [af]
//! author : Werner Mollentze : https://github.com/wernerm
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/af',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var af = moment.defineLocale('af', {
months : 'Januarie_Februarie_Maart_April_Mei_Junie_Julie_Augustus_September_Oktober_November_Desember'.split('_'),
monthsShort : 'Jan_Feb_Mrt_Apr_Mei_Jun_Jul_Aug_Sep_Okt_Nov_Des'.split('_'),
weekdays : 'Sondag_Maandag_Dinsdag_Woensdag_Donderdag_Vrydag_Saterdag'.split('_'),
weekdaysShort : 'Son_Maa_Din_Woe_Don_Vry_Sat'.split('_'),
weekdaysMin : 'So_Ma_Di_Wo_Do_Vr_Sa'.split('_'),
meridiemParse: /vm|nm/i,
isPM : function (input) {
return /^nm$/i.test(input);
},
meridiem : function (hours, minutes, isLower) {
if (hours < 12) {
return isLower ? 'vm' : 'VM';
} else {
return isLower ? 'nm' : 'NM';
2017-07-22 22:21:05 +02:00
}
},
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd, D MMMM YYYY HH:mm'
},
calendar : {
sameDay : '[Vandag om] LT',
nextDay : '[Môre om] LT',
nextWeek : 'dddd [om] LT',
lastDay : '[Gister om] LT',
lastWeek : '[Laas] dddd [om] LT',
sameElse : 'L'
},
relativeTime : {
future : 'oor %s',
past : '%s gelede',
s : '\'n paar sekondes',
m : '\'n minuut',
mm : '%d minute',
h : '\'n uur',
hh : '%d ure',
d : '\'n dag',
dd : '%d dae',
M : '\'n maand',
MM : '%d maande',
y : '\'n jaar',
yy : '%d jaar'
},
dayOfMonthOrdinalParse: /\d{1,2}(ste|de)/,
ordinal : function (number) {
return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de'); // Thanks to Joris Röling : https://github.com/jjupiter
},
week : {
dow : 1, // Maandag is die eerste dag van die week.
doy : 4 // Die week wat die 4de Januarie bevat is die eerste week van die jaar.
2017-07-22 22:21:05 +02:00
}
});
2017-07-22 22:21:05 +02:00
return af;
2017-07-22 22:21:05 +02:00
})));
2017-07-22 22:21:05 +02:00
//! moment.js locale configuration
//! locale : Arabic [ar]
//! author : Abdel Said: https://github.com/abdelsaid
//! author : Ahmed Elkhatib
//! author : forabi https://github.com/forabi
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/ar',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var symbolMap = {
'1': '١',
'2': '٢',
'3': '٣',
'4': '٤',
'5': '٥',
'6': '٦',
'7': '٧',
'8': '٨',
'9': '٩',
'0': '٠'
};
var numberMap = {
'١': '1',
'٢': '2',
'٣': '3',
'٤': '4',
'٥': '5',
'٦': '6',
'٧': '7',
'٨': '8',
'٩': '9',
'٠': '0'
};
var pluralForm = function (n) {
return n === 0 ? 0 : n === 1 ? 1 : n === 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 ? 4 : 5;
};
var plurals = {
s : ['أقل من ثانية', 'ثانية واحدة', ['ثانيتان', 'ثانيتين'], '%d ثوان', '%d ثانية', '%d ثانية'],
m : ['أقل من دقيقة', 'دقيقة واحدة', ['دقيقتان', 'دقيقتين'], '%d دقائق', '%d دقيقة', '%d دقيقة'],
h : ['أقل من ساعة', 'ساعة واحدة', ['ساعتان', 'ساعتين'], '%d ساعات', '%d ساعة', '%d ساعة'],
d : ['أقل من يوم', 'يوم واحد', ['يومان', 'يومين'], '%d أيام', '%d يومًا', '%d يوم'],
M : ['أقل من شهر', 'شهر واحد', ['شهران', 'شهرين'], '%d أشهر', '%d شهرا', '%d شهر'],
y : ['أقل من عام', 'عام واحد', ['عامان', 'عامين'], '%d أعوام', '%d عامًا', '%d عام']
};
var pluralize = function (u) {
return function (number, withoutSuffix, string, isFuture) {
var f = pluralForm(number),
str = plurals[u][pluralForm(number)];
if (f === 2) {
str = str[withoutSuffix ? 0 : 1];
}
return str.replace(/%d/i, number);
};
};
var months = [
'كانون الثاني يناير',
'شباط فبراير',
'آذار مارس',
'نيسان أبريل',
'أيار مايو',
'حزيران يونيو',
'تموز يوليو',
'آب أغسطس',
'أيلول سبتمبر',
'تشرين الأول أكتوبر',
'تشرين الثاني نوفمبر',
'كانون الأول ديسمبر'
];
var ar = moment.defineLocale('ar', {
months : months,
monthsShort : months,
weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'),
weekdaysShort : 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'),
weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'D/\u200FM/\u200FYYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd D MMMM YYYY HH:mm'
},
meridiemParse: /ص|م/,
isPM : function (input) {
return 'م' === input;
},
meridiem : function (hour, minute, isLower) {
if (hour < 12) {
return 'ص';
} else {
return 'م';
}
},
calendar : {
sameDay: '[اليوم عند الساعة] LT',
nextDay: '[غدًا عند الساعة] LT',
nextWeek: 'dddd [عند الساعة] LT',
lastDay: '[أمس عند الساعة] LT',
lastWeek: 'dddd [عند الساعة] LT',
sameElse: 'L'
},
relativeTime : {
future : 'بعد %s',
past : 'منذ %s',
s : pluralize('s'),
m : pluralize('m'),
mm : pluralize('m'),
h : pluralize('h'),
hh : pluralize('h'),
d : pluralize('d'),
dd : pluralize('d'),
M : pluralize('M'),
MM : pluralize('M'),
y : pluralize('y'),
yy : pluralize('y')
},
preparse: function (string) {
return string.replace(/[١٢٣٤٥٦٧٨٩٠]/g, function (match) {
return numberMap[match];
}).replace(/،/g, ',');
},
postformat: function (string) {
return string.replace(/\d/g, function (match) {
return symbolMap[match];
}).replace(/,/g, '،');
},
week : {
dow : 6, // Saturday is the first day of the week.
doy : 12 // The week that contains Jan 1st is the first week of the year.
}
});
return ar;
})));
2018-03-05 14:43:53 +01:00
//! moment.js locale configuration
//! locale : Bulgarian [bg]
//! author : Krasen Borisov : https://github.com/kraz
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/bg',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var bg = moment.defineLocale('bg', {
months : 'януари_февруари_март_април_май_юни_юли_август_септември_октомври_ноември_декември'.split('_'),
monthsShort : 'янрев_мар_апрай_юни_юли_авг_сеп_окт_ноеек'.split('_'),
weekdays : еделя_понеделник_вторник_срядаетвъртък_петък_събота'.split('_'),
weekdaysShort : ед_пон_вто_сря_чет_пет_съб'.split('_'),
weekdaysMin : 'нд_пн_вт_ср_чт_пт_сб'.split('_'),
longDateFormat : {
LT : 'H:mm',
LTS : 'H:mm:ss',
L : 'D.MM.YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY H:mm',
LLLL : 'dddd, D MMMM YYYY H:mm'
},
calendar : {
sameDay : '[Днес в] LT',
nextDay : '[Утре в] LT',
nextWeek : 'dddd [в] LT',
lastDay : '[Вчера в] LT',
lastWeek : function () {
switch (this.day()) {
case 0:
case 3:
case 6:
return '[В изминалата] dddd [в] LT';
case 1:
case 2:
case 4:
case 5:
return '[В изминалия] dddd [в] LT';
}
},
sameElse : 'L'
},
relativeTime : {
future : 'след %s',
past : 'преди %s',
s : 'няколко секунди',
m : 'минута',
mm : '%d минути',
h : 'час',
hh : '%d часа',
d : 'ден',
dd : '%d дни',
M : 'месец',
MM : '%d месеца',
y : 'година',
yy : '%d години'
},
dayOfMonthOrdinalParse: /\d{1,2}-(ев|ен|ти|ви|ри|ми)/,
ordinal : function (number) {
var lastDigit = number % 10,
last2Digits = number % 100;
if (number === 0) {
return number + '-ев';
} else if (last2Digits === 0) {
return number + '-ен';
} else if (last2Digits > 10 && last2Digits < 20) {
return number + '-ти';
} else if (lastDigit === 1) {
return number + '-ви';
} else if (lastDigit === 2) {
return number + '-ри';
} else if (lastDigit === 7 || lastDigit === 8) {
return number + '-ми';
} else {
return number + '-ти';
}
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 7 // The week that contains Jan 1st is the first week of the year.
}
});
return bg;
})));
//! moment.js locale configuration
//! locale : Catalan [ca]
//! author : Juan G. Hurtado : https://github.com/juanghurtado
2017-07-22 22:21:05 +02:00
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/ca',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
2017-07-22 22:21:05 +02:00
var ca = moment.defineLocale('ca', {
months : {
standalone: 'gener_febrer_març_abril_maig_juny_juliol_agost_setembre_octubre_novembre_desembre'.split('_'),
format: 'de gener_de febrer_de març_d\'abril_de maig_de juny_de juliol_d\'agost_de setembre_d\'octubre_de novembre_de desembre'.split('_'),
isFormat: /D[oD]?(\s)+MMMM/
},
monthsShort : 'gen._febr._març_abr._maig_juny_jul._ag._set._oct._nov._des.'.split('_'),
monthsParseExact : true,
weekdays : 'diumenge_dilluns_dimarts_dimecres_dijous_divendres_dissabte'.split('_'),
weekdaysShort : 'dg._dl._dt._dc._dj._dv._ds.'.split('_'),
2018-03-25 21:21:43 +02:00
weekdaysMin : 'dg_dl_dt_dc_dj_dv_ds'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT : 'H:mm',
LTS : 'H:mm:ss',
L : 'DD/MM/YYYY',
2018-03-25 21:21:43 +02:00
LL : 'D MMMM [de] YYYY',
ll : 'D MMM YYYY',
2018-03-25 21:21:43 +02:00
LLL : 'D MMMM [de] YYYY [a les] H:mm',
lll : 'D MMM YYYY, H:mm',
2018-03-25 21:21:43 +02:00
LLLL : 'dddd D MMMM [de] YYYY [a les] H:mm',
llll : 'ddd D MMM YYYY, H:mm'
},
calendar : {
sameDay : function () {
return '[avui a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT';
},
nextDay : function () {
return '[demà a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT';
},
nextWeek : function () {
return 'dddd [a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT';
},
lastDay : function () {
return '[ahir a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT';
2017-07-22 22:21:05 +02:00
},
lastWeek : function () {
return '[el] dddd [passat a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT';
},
sameElse : 'L'
},
relativeTime : {
future : 'd\'aquí %s',
past : 'fa %s',
s : 'uns segons',
m : 'un minut',
mm : '%d minuts',
h : 'una hora',
hh : '%d hores',
d : 'un dia',
dd : '%d dies',
M : 'un mes',
MM : '%d mesos',
y : 'un any',
yy : '%d anys'
},
dayOfMonthOrdinalParse: /\d{1,2}(r|n|t|è|a)/,
ordinal : function (number, period) {
var output = (number === 1) ? 'r' :
(number === 2) ? 'n' :
(number === 3) ? 'r' :
(number === 4) ? 't' : 'è';
if (period === 'w' || period === 'W') {
output = 'a';
}
return number + output;
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
2017-07-22 22:21:05 +02:00
return ca;
2017-07-22 22:21:05 +02:00
})));
2017-07-22 22:21:05 +02:00
//! moment.js locale configuration
//! locale : German [de]
//! author : lluchs : https://github.com/lluchs
//! author: Menelion Elensúle: https://github.com/Oire
//! author : Mikolaj Dadela : https://github.com/mik01aj
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/de',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
function processRelativeTime(number, withoutSuffix, key, isFuture) {
var format = {
'm': ['eine Minute', 'einer Minute'],
'h': ['eine Stunde', 'einer Stunde'],
'd': ['ein Tag', 'einem Tag'],
'dd': [number + ' Tage', number + ' Tagen'],
'M': ['ein Monat', 'einem Monat'],
'MM': [number + ' Monate', number + ' Monaten'],
'y': ['ein Jahr', 'einem Jahr'],
'yy': [number + ' Jahre', number + ' Jahren']
};
return withoutSuffix ? format[key][0] : format[key][1];
}
var de = moment.defineLocale('de', {
months : 'Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'),
2018-03-25 21:21:43 +02:00
monthsShort : 'Jan._Feb._März_Apr._Mai_Juni_Juli_Aug._Sep._Okt._Nov._Dez.'.split('_'),
monthsParseExact : true,
weekdays : 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'),
weekdaysShort : 'So._Mo._Di._Mi._Do._Fr._Sa.'.split('_'),
weekdaysMin : 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT: 'HH:mm',
LTS: 'HH:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D. MMMM YYYY',
LLL : 'D. MMMM YYYY HH:mm',
LLLL : 'dddd, D. MMMM YYYY HH:mm'
},
calendar : {
sameDay: '[heute um] LT [Uhr]',
sameElse: 'L',
nextDay: '[morgen um] LT [Uhr]',
nextWeek: 'dddd [um] LT [Uhr]',
lastDay: '[gestern um] LT [Uhr]',
lastWeek: '[letzten] dddd [um] LT [Uhr]'
},
relativeTime : {
future : 'in %s',
past : 'vor %s',
s : 'ein paar Sekunden',
m : processRelativeTime,
mm : '%d Minuten',
h : processRelativeTime,
hh : '%d Stunden',
d : processRelativeTime,
dd : processRelativeTime,
M : processRelativeTime,
MM : processRelativeTime,
y : processRelativeTime,
yy : processRelativeTime
},
dayOfMonthOrdinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
2017-07-22 22:21:05 +02:00
return de;
2017-07-22 22:21:05 +02:00
})));
2017-07-22 22:21:05 +02:00
//! moment.js locale configuration
//! locale : Spanish [es]
//! author : Julio Napurí : https://github.com/julionc
2017-07-22 22:21:05 +02:00
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/es',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
2017-07-22 22:21:05 +02:00
var monthsShortDot = 'ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.'.split('_');
var monthsShort = 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_');
2018-03-25 21:21:43 +02:00
var monthsParse = [/^ene/i, /^feb/i, /^mar/i, /^abr/i, /^may/i, /^jun/i, /^jul/i, /^ago/i, /^sep/i, /^oct/i, /^nov/i, /^dic/i];
var monthsRegex = /^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i;
var es = moment.defineLocale('es', {
months : 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'),
monthsShort : function (m, format) {
if (!m) {
return monthsShortDot;
} else if (/-MMM-/.test(format)) {
return monthsShort[m.month()];
} else {
return monthsShortDot[m.month()];
}
},
2018-03-25 21:21:43 +02:00
monthsRegex : monthsRegex,
monthsShortRegex : monthsRegex,
monthsStrictRegex : /^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i,
monthsShortStrictRegex : /^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,
monthsParse : monthsParse,
longMonthsParse : monthsParse,
shortMonthsParse : monthsParse,
weekdays : 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'),
weekdaysShort : 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'),
weekdaysMin : 'do_lu_ma_mi_ju_vi_sá'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT : 'H:mm',
LTS : 'H:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D [de] MMMM [de] YYYY',
LLL : 'D [de] MMMM [de] YYYY H:mm',
LLLL : 'dddd, D [de] MMMM [de] YYYY H:mm'
},
calendar : {
sameDay : function () {
return '[hoy a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
},
nextDay : function () {
return '[mañana a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
},
nextWeek : function () {
return 'dddd [a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
},
lastDay : function () {
return '[ayer a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
},
lastWeek : function () {
return '[el] dddd [pasado a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
},
sameElse : 'L'
},
relativeTime : {
future : 'en %s',
past : 'hace %s',
s : 'unos segundos',
m : 'un minuto',
mm : '%d minutos',
h : 'una hora',
hh : '%d horas',
d : 'un día',
dd : '%d días',
M : 'un mes',
MM : '%d meses',
y : 'un año',
yy : '%d años'
},
dayOfMonthOrdinalParse : /\d{1,2}º/,
ordinal : '%dº',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return es;
})));
//! moment.js locale configuration
//! locale : Basque [eu]
//! author : Eneko Illarramendi : https://github.com/eillarra
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/eu',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var eu = moment.defineLocale('eu', {
months : 'urtarrila_otsaila_martxoa_apirila_maiatza_ekaina_uztaila_abuztua_iraila_urria_azaroa_abendua'.split('_'),
monthsShort : 'urt._ots._mar._api._mai._eka._uzt._abu._ira._urr._aza._abe.'.split('_'),
monthsParseExact : true,
weekdays : 'igandea_astelehena_asteartea_asteazkena_osteguna_ostirala_larunbata'.split('_'),
weekdaysShort : 'ig._al._ar._az._og._ol._lr.'.split('_'),
weekdaysMin : 'ig_al_ar_az_og_ol_lr'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'YYYY-MM-DD',
LL : 'YYYY[ko] MMMM[ren] D[a]',
LLL : 'YYYY[ko] MMMM[ren] D[a] HH:mm',
LLLL : 'dddd, YYYY[ko] MMMM[ren] D[a] HH:mm',
l : 'YYYY-M-D',
ll : 'YYYY[ko] MMM D[a]',
lll : 'YYYY[ko] MMM D[a] HH:mm',
llll : 'ddd, YYYY[ko] MMM D[a] HH:mm'
},
calendar : {
sameDay : '[gaur] LT[etan]',
nextDay : '[bihar] LT[etan]',
nextWeek : 'dddd LT[etan]',
lastDay : '[atzo] LT[etan]',
lastWeek : '[aurreko] dddd LT[etan]',
sameElse : 'L'
},
relativeTime : {
future : '%s barru',
past : 'duela %s',
s : 'segundo batzuk',
m : 'minutu bat',
mm : '%d minutu',
h : 'ordu bat',
hh : '%d ordu',
d : 'egun bat',
dd : '%d egun',
M : 'hilabete bat',
MM : '%d hilabete',
y : 'urte bat',
yy : '%d urte'
},
dayOfMonthOrdinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 7 // The week that contains Jan 1st is the first week of the year.
}
});
return eu;
})));
//! moment.js locale configuration
//! locale : French [fr]
//! author : John Fischer : https://github.com/jfroffice
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/fr',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var fr = moment.defineLocale('fr', {
months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'),
monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'),
monthsParseExact : true,
weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'),
weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'),
weekdaysMin : 'Di_Lu_Ma_Me_Je_Ve_Sa'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd D MMMM YYYY HH:mm'
},
calendar : {
sameDay : '[Aujourdhui à] LT',
nextDay : '[Demain à] LT',
nextWeek : 'dddd [à] LT',
lastDay : '[Hier à] LT',
lastWeek : 'dddd [dernier à] LT',
sameElse : 'L'
},
relativeTime : {
future : 'dans %s',
past : 'il y a %s',
s : 'quelques secondes',
m : 'une minute',
mm : '%d minutes',
h : 'une heure',
hh : '%d heures',
d : 'un jour',
dd : '%d jours',
M : 'un mois',
MM : '%d mois',
y : 'un an',
yy : '%d ans'
},
dayOfMonthOrdinalParse: /\d{1,2}(er|)/,
ordinal : function (number, period) {
switch (period) {
// TODO: Return 'e' when day of month > 1. Move this case inside
// block for masculine words below.
// See https://github.com/moment/moment/issues/3375
case 'D':
return number + (number === 1 ? 'er' : '');
// Words with masculine grammatical gender: mois, trimestre, jour
default:
case 'M':
case 'Q':
case 'DDD':
case 'd':
return number + (number === 1 ? 'er' : 'e');
// Words with feminine grammatical gender: semaine
case 'w':
case 'W':
return number + (number === 1 ? 're' : 'e');
}
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return fr;
})));
//! moment.js locale configuration
//! locale : Hebrew [he]
//! author : Tomer Cohen : https://github.com/tomer
//! author : Moshe Simantov : https://github.com/DevelopmentIL
//! author : Tal Ater : https://github.com/TalAter
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/he',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var he = moment.defineLocale('he', {
months : 'ינואר_פברואר_מרץ_אפריל_מאי_יוני_יוליוגוסט_ספטמבר_אוקטובר_נובמבר_דצמבר'.split('_'),
monthsShort : 'ינו׳_פבר׳_מרץ_אפר׳_מאי_יוני_יוליוג׳_ספט׳וק׳וב׳_דצמ׳'.split('_'),
weekdays : 'ראשון_שני_שלישי_רביעי_חמישיישי_שבת'.split('_'),
weekdaysShort : ׳׳׳׳׳_ו׳׳'.split('_'),
weekdaysMin : 'א_ב_ג_ד_ה_ו_ש'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D [ב]MMMM YYYY',
LLL : 'D [ב]MMMM YYYY HH:mm',
LLLL : 'dddd, D [ב]MMMM YYYY HH:mm',
l : 'D/M/YYYY',
ll : 'D MMM YYYY',
lll : 'D MMM YYYY HH:mm',
llll : 'ddd, D MMM YYYY HH:mm'
},
calendar : {
sameDay : '[היום ב־]LT',
nextDay : '[מחר ב־]LT',
nextWeek : 'dddd [בשעה] LT',
lastDay : '[אתמול ב־]LT',
lastWeek : '[ביום] dddd [האחרון בשעה] LT',
sameElse : 'L'
},
relativeTime : {
future : 'בעוד %s',
past : 'לפני %s',
s : 'מספר שניות',
m : 'דקה',
mm : '%d דקות',
h : 'שעה',
hh : function (number) {
if (number === 2) {
return 'שעתיים';
}
return number + ' שעות';
},
d : 'יום',
dd : function (number) {
if (number === 2) {
return 'יומיים';
}
return number + ' ימים';
},
M : 'חודש',
MM : function (number) {
if (number === 2) {
return 'חודשיים';
}
return number + ' חודשים';
},
y : 'שנה',
yy : function (number) {
if (number === 2) {
return 'שנתיים';
} else if (number % 10 === 0 && number !== 10) {
return number + ' שנה';
}
return number + ' שנים';
}
},
meridiemParse: /אחה"צ|לפנה"צ|אחרי הצהריים|לפני הצהריים|לפנות בוקר|בבוקר|בערב/i,
isPM : function (input) {
return /^(אחה"צ|אחרי הצהריים|בערב)$/.test(input);
},
meridiem : function (hour, minute, isLower) {
if (hour < 5) {
return 'לפנות בוקר';
} else if (hour < 10) {
return 'בבוקר';
} else if (hour < 12) {
return isLower ? 'לפנה"צ' : 'לפני הצהריים';
} else if (hour < 18) {
return isLower ? 'אחה"צ' : 'אחרי הצהריים';
} else {
return 'בערב';
}
}
});
return he;
})));
//! moment.js locale configuration
//! locale : Hungarian [hu]
//! author : Adam Brunner : https://github.com/adambrunner
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/hu',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var weekEndings = 'vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton'.split(' ');
function translate(number, withoutSuffix, key, isFuture) {
2018-03-25 21:21:43 +02:00
var num = number;
switch (key) {
case 's':
return (isFuture || withoutSuffix) ? 'néhány másodperc' : 'néhány másodperce';
case 'm':
return 'egy' + (isFuture || withoutSuffix ? ' perc' : ' perce');
case 'mm':
return num + (isFuture || withoutSuffix ? ' perc' : ' perce');
case 'h':
return 'egy' + (isFuture || withoutSuffix ? ' óra' : ' órája');
case 'hh':
return num + (isFuture || withoutSuffix ? ' óra' : ' órája');
case 'd':
return 'egy' + (isFuture || withoutSuffix ? ' nap' : ' napja');
case 'dd':
return num + (isFuture || withoutSuffix ? ' nap' : ' napja');
case 'M':
return 'egy' + (isFuture || withoutSuffix ? ' hónap' : ' hónapja');
case 'MM':
return num + (isFuture || withoutSuffix ? ' hónap' : ' hónapja');
case 'y':
return 'egy' + (isFuture || withoutSuffix ? ' év' : ' éve');
case 'yy':
return num + (isFuture || withoutSuffix ? ' év' : ' éve');
}
return '';
}
function week(isFuture) {
return (isFuture ? '' : '[múlt] ') + '[' + weekEndings[this.day()] + '] LT[-kor]';
}
var hu = moment.defineLocale('hu', {
months : 'január_február_március_április_május_június_július_augusztus_szeptember_október_november_december'.split('_'),
monthsShort : 'jan_feb_márc_ápr_máj_jún_júl_aug_szept_okt_nov_dec'.split('_'),
weekdays : 'vasárnap_hétfő_kedd_szerda_csütörtök_péntek_szombat'.split('_'),
weekdaysShort : 'vas_hét_kedd_sze_csüt_pén_szo'.split('_'),
weekdaysMin : 'v_h_k_sze_cs_p_szo'.split('_'),
longDateFormat : {
LT : 'H:mm',
LTS : 'H:mm:ss',
L : 'YYYY.MM.DD.',
LL : 'YYYY. MMMM D.',
LLL : 'YYYY. MMMM D. H:mm',
LLLL : 'YYYY. MMMM D., dddd H:mm'
},
meridiemParse: /de|du/i,
isPM: function (input) {
return input.charAt(1).toLowerCase() === 'u';
},
meridiem : function (hours, minutes, isLower) {
if (hours < 12) {
return isLower === true ? 'de' : 'DE';
} else {
return isLower === true ? 'du' : 'DU';
}
},
calendar : {
sameDay : '[ma] LT[-kor]',
nextDay : '[holnap] LT[-kor]',
nextWeek : function () {
return week.call(this, true);
},
lastDay : '[tegnap] LT[-kor]',
lastWeek : function () {
return week.call(this, false);
},
sameElse : 'L'
},
relativeTime : {
future : '%s múlva',
past : '%s',
s : translate,
m : translate,
mm : translate,
h : translate,
hh : translate,
d : translate,
dd : translate,
M : translate,
MM : translate,
y : translate,
yy : translate
},
dayOfMonthOrdinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return hu;
})));
//! moment.js locale configuration
//! locale : Indonesian [id]
//! author : Mohammad Satrio Utomo : https://github.com/tyok
//! reference: http://id.wikisource.org/wiki/Pedoman_Umum_Ejaan_Bahasa_Indonesia_yang_Disempurnakan
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/id',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var id = moment.defineLocale('id', {
months : 'Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember'.split('_'),
monthsShort : 'Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nov_Des'.split('_'),
weekdays : 'Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu'.split('_'),
weekdaysShort : 'Min_Sen_Sel_Rab_Kam_Jum_Sab'.split('_'),
weekdaysMin : 'Mg_Sn_Sl_Rb_Km_Jm_Sb'.split('_'),
longDateFormat : {
LT : 'HH.mm',
LTS : 'HH.mm.ss',
L : 'DD/MM/YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY [pukul] HH.mm',
LLLL : 'dddd, D MMMM YYYY [pukul] HH.mm'
},
meridiemParse: /pagi|siang|sore|malam/,
meridiemHour : function (hour, meridiem) {
if (hour === 12) {
hour = 0;
}
if (meridiem === 'pagi') {
return hour;
} else if (meridiem === 'siang') {
return hour >= 11 ? hour : hour + 12;
} else if (meridiem === 'sore' || meridiem === 'malam') {
return hour + 12;
}
},
meridiem : function (hours, minutes, isLower) {
if (hours < 11) {
return 'pagi';
} else if (hours < 15) {
return 'siang';
} else if (hours < 19) {
return 'sore';
} else {
return 'malam';
}
},
calendar : {
sameDay : '[Hari ini pukul] LT',
nextDay : '[Besok pukul] LT',
nextWeek : 'dddd [pukul] LT',
lastDay : '[Kemarin pukul] LT',
lastWeek : 'dddd [lalu pukul] LT',
sameElse : 'L'
},
relativeTime : {
future : 'dalam %s',
past : '%s yang lalu',
s : 'beberapa detik',
m : 'semenit',
mm : '%d menit',
h : 'sejam',
hh : '%d jam',
d : 'sehari',
dd : '%d hari',
M : 'sebulan',
MM : '%d bulan',
y : 'setahun',
yy : '%d tahun'
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 7 // The week that contains Jan 1st is the first week of the year.
}
});
return id;
})));
//! moment.js locale configuration
//! locale : Italian [it]
//! author : Lorenzo : https://github.com/aliem
//! author: Mattia Larentis: https://github.com/nostalgiaz
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/it',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var it = moment.defineLocale('it', {
months : 'gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre'.split('_'),
monthsShort : 'gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic'.split('_'),
weekdays : 'domenica_lunedì_martedì_mercoledì_giovedì_venerdì_sabato'.split('_'),
weekdaysShort : 'dom_lun_mar_mer_gio_ven_sab'.split('_'),
weekdaysMin : 'do_lu_ma_me_gi_ve_sa'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd, D MMMM YYYY HH:mm'
},
calendar : {
sameDay: '[Oggi alle] LT',
nextDay: '[Domani alle] LT',
nextWeek: 'dddd [alle] LT',
lastDay: '[Ieri alle] LT',
lastWeek: function () {
switch (this.day()) {
case 0:
return '[la scorsa] dddd [alle] LT';
default:
return '[lo scorso] dddd [alle] LT';
}
},
sameElse: 'L'
},
relativeTime : {
future : function (s) {
return ((/^[0-9].+$/).test(s) ? 'tra' : 'in') + ' ' + s;
},
past : '%s fa',
s : 'alcuni secondi',
m : 'un minuto',
mm : '%d minuti',
h : 'un\'ora',
hh : '%d ore',
d : 'un giorno',
dd : '%d giorni',
M : 'un mese',
MM : '%d mesi',
y : 'un anno',
yy : '%d anni'
},
dayOfMonthOrdinalParse : /\d{1,2}º/,
ordinal: '%dº',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return it;
})));
//! moment.js locale configuration
//! locale : Japanese [ja]
//! author : LI Long : https://github.com/baryon
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/ja',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var ja = moment.defineLocale('ja', {
months : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
weekdays : '日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日'.split('_'),
weekdaysShort : '日_月_火_水_木_金_土'.split('_'),
weekdaysMin : '日_月_火_水_木_金_土'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'YYYY/MM/DD',
LL : 'YYYY年M月D日',
LLL : 'YYYY年M月D日 HH:mm',
LLLL : 'YYYY年M月D日 HH:mm dddd',
l : 'YYYY/MM/DD',
ll : 'YYYY年M月D日',
lll : 'YYYY年M月D日 HH:mm',
llll : 'YYYY年M月D日 HH:mm dddd'
},
meridiemParse: /午前|午後/i,
isPM : function (input) {
return input === '午後';
},
meridiem : function (hour, minute, isLower) {
if (hour < 12) {
return '午前';
} else {
return '午後';
}
},
calendar : {
sameDay : '[今日] LT',
nextDay : '[明日] LT',
nextWeek : '[来週]dddd LT',
lastDay : '[昨日] LT',
lastWeek : '[前週]dddd LT',
sameElse : 'L'
},
dayOfMonthOrdinalParse : /\d{1,2}日/,
ordinal : function (number, period) {
switch (period) {
case 'd':
case 'D':
case 'DDD':
return number + '日';
default:
return number;
}
},
relativeTime : {
future : '%s後',
past : '%s前',
s : '数秒',
m : '1分',
mm : '%d分',
h : '1時間',
hh : '%d時間',
d : '1日',
dd : '%d日',
M : '1ヶ月',
MM : '%dヶ月',
y : '1年',
yy : '%d年'
}
});
return ja;
})));
//! moment.js locale configuration
//! locale : Norwegian Bokmål [nb]
//! authors : Espen Hovlandsdal : https://github.com/rexxars
//! Sigurd Gartmann : https://github.com/sigurdga
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/nb',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var nb = moment.defineLocale('nb', {
months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'),
monthsShort : 'jan._feb._mars_april_mai_juni_juli_aug._sep._okt._nov._des.'.split('_'),
monthsParseExact : true,
weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'),
weekdaysShort : 'sø._ma._ti._on._to._fr._lø.'.split('_'),
weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D. MMMM YYYY',
LLL : 'D. MMMM YYYY [kl.] HH:mm',
LLLL : 'dddd D. MMMM YYYY [kl.] HH:mm'
},
calendar : {
sameDay: '[i dag kl.] LT',
nextDay: '[i morgen kl.] LT',
nextWeek: 'dddd [kl.] LT',
lastDay: '[i går kl.] LT',
lastWeek: '[forrige] dddd [kl.] LT',
sameElse: 'L'
},
relativeTime : {
future : 'om %s',
past : '%s siden',
s : 'noen sekunder',
m : 'ett minutt',
mm : '%d minutter',
h : 'en time',
hh : '%d timer',
d : 'en dag',
dd : '%d dager',
M : 'en måned',
MM : '%d måneder',
y : 'ett år',
yy : '%d år'
},
dayOfMonthOrdinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return nb;
})));
//! moment.js locale configuration
//! locale : Dutch [nl]
//! author : Joris Röling : https://github.com/jorisroling
//! author : Jacob Middag : https://github.com/middagj
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/nl',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var monthsShortWithDots = 'jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.'.split('_');
var monthsShortWithoutDots = 'jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec'.split('_');
var monthsParse = [/^jan/i, /^feb/i, /^maart|mrt.?$/i, /^apr/i, /^mei$/i, /^jun[i.]?$/i, /^jul[i.]?$/i, /^aug/i, /^sep/i, /^okt/i, /^nov/i, /^dec/i];
var monthsRegex = /^(januari|februari|maart|april|mei|april|ju[nl]i|augustus|september|oktober|november|december|jan\.?|feb\.?|mrt\.?|apr\.?|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i;
var nl = moment.defineLocale('nl', {
months : 'januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december'.split('_'),
monthsShort : function (m, format) {
if (!m) {
return monthsShortWithDots;
} else if (/-MMM-/.test(format)) {
return monthsShortWithoutDots[m.month()];
} else {
return monthsShortWithDots[m.month()];
}
},
monthsRegex: monthsRegex,
monthsShortRegex: monthsRegex,
monthsStrictRegex: /^(januari|februari|maart|mei|ju[nl]i|april|augustus|september|oktober|november|december)/i,
monthsShortStrictRegex: /^(jan\.?|feb\.?|mrt\.?|apr\.?|mei|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i,
monthsParse : monthsParse,
longMonthsParse : monthsParse,
shortMonthsParse : monthsParse,
weekdays : 'zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag'.split('_'),
weekdaysShort : 'zo._ma._di._wo._do._vr._za.'.split('_'),
2018-03-25 21:21:43 +02:00
weekdaysMin : 'zo_ma_di_wo_do_vr_za'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD-MM-YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd D MMMM YYYY HH:mm'
},
calendar : {
sameDay: '[vandaag om] LT',
nextDay: '[morgen om] LT',
nextWeek: 'dddd [om] LT',
lastDay: '[gisteren om] LT',
lastWeek: '[afgelopen] dddd [om] LT',
sameElse: 'L'
},
relativeTime : {
future : 'over %s',
past : '%s geleden',
s : 'een paar seconden',
m : 'één minuut',
mm : '%d minuten',
h : 'één uur',
hh : '%d uur',
d : 'één dag',
dd : '%d dagen',
M : 'één maand',
MM : '%d maanden',
y : 'één jaar',
yy : '%d jaar'
},
dayOfMonthOrdinalParse: /\d{1,2}(ste|de)/,
ordinal : function (number) {
return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de');
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return nl;
})));
//! moment.js locale configuration
//! locale : Polish [pl]
//! author : Rafal Hirsz : https://github.com/evoL
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/pl',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var monthsNominative = 'styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień'.split('_');
var monthsSubjective = 'stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia'.split('_');
function plural(n) {
return (n % 10 < 5) && (n % 10 > 1) && ((~~(n / 10) % 10) !== 1);
}
function translate(number, withoutSuffix, key) {
var result = number + ' ';
switch (key) {
case 'm':
return withoutSuffix ? 'minuta' : 'minutę';
case 'mm':
return result + (plural(number) ? 'minuty' : 'minut');
case 'h':
return withoutSuffix ? 'godzina' : 'godzinę';
case 'hh':
return result + (plural(number) ? 'godziny' : 'godzin');
case 'MM':
return result + (plural(number) ? 'miesiące' : 'miesięcy');
case 'yy':
return result + (plural(number) ? 'lata' : 'lat');
}
}
var pl = moment.defineLocale('pl', {
months : function (momentToFormat, format) {
if (!momentToFormat) {
return monthsNominative;
} else if (format === '') {
// Hack: if format empty we know this is used to generate
// RegExp by moment. Give then back both valid forms of months
// in RegExp ready format.
return '(' + monthsSubjective[momentToFormat.month()] + '|' + monthsNominative[momentToFormat.month()] + ')';
} else if (/D MMMM/.test(format)) {
return monthsSubjective[momentToFormat.month()];
} else {
return monthsNominative[momentToFormat.month()];
}
},
monthsShort : 'sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru'.split('_'),
weekdays : 'niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota'.split('_'),
weekdaysShort : 'ndz_pon_wt_śr_czw_pt_sob'.split('_'),
weekdaysMin : 'Nd_Pn_Wt_Śr_Cz_Pt_So'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd, D MMMM YYYY HH:mm'
},
calendar : {
sameDay: '[Dziś o] LT',
nextDay: '[Jutro o] LT',
2018-03-25 21:21:43 +02:00
nextWeek: function () {
switch (this.day()) {
case 0:
return '[W niedzielę o] LT';
case 2:
return '[We wtorek o] LT';
case 3:
return '[W środę o] LT';
case 6:
return '[W sobotę o] LT';
default:
return '[W] dddd [o] LT';
}
},
lastDay: '[Wczoraj o] LT',
lastWeek: function () {
switch (this.day()) {
case 0:
return '[W zeszłą niedzielę o] LT';
case 3:
return '[W zeszłą środę o] LT';
case 6:
return '[W zeszłą sobotę o] LT';
default:
return '[W zeszły] dddd [o] LT';
}
},
sameElse: 'L'
},
relativeTime : {
future : 'za %s',
past : '%s temu',
s : 'kilka sekund',
m : translate,
mm : translate,
h : translate,
hh : translate,
d : '1 dzień',
dd : '%d dni',
M : 'miesiąc',
MM : translate,
y : 'rok',
yy : translate
},
dayOfMonthOrdinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return pl;
})));
//! moment.js locale configuration
//! locale : Portuguese (Brazil) [pt-br]
//! author : Caio Ribeiro Pereira : https://github.com/caio-ribeiro-pereira
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/pt-br',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var ptBr = moment.defineLocale('pt-br', {
2018-03-25 21:21:43 +02:00
months : 'janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro'.split('_'),
monthsShort : 'jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez'.split('_'),
weekdays : 'Domingo_Segunda-feira_Terça-feira_Quarta-feira_Quinta-feira_Sexta-feira_Sábado'.split('_'),
weekdaysShort : 'Dom_Seg_Ter_Qua_Qui_Sex_Sáb'.split('_'),
weekdaysMin : 'Do_2ª_3ª_4ª_5ª_6ª_Sá'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D [de] MMMM [de] YYYY',
LLL : 'D [de] MMMM [de] YYYY [às] HH:mm',
LLLL : 'dddd, D [de] MMMM [de] YYYY [às] HH:mm'
},
calendar : {
sameDay: '[Hoje às] LT',
nextDay: '[Amanhã às] LT',
nextWeek: 'dddd [às] LT',
lastDay: '[Ontem às] LT',
lastWeek: function () {
return (this.day() === 0 || this.day() === 6) ?
'[Último] dddd [às] LT' : // Saturday + Sunday
'[Última] dddd [às] LT'; // Monday - Friday
},
sameElse: 'L'
},
relativeTime : {
future : 'em %s',
past : '%s atrás',
s : 'poucos segundos',
2018-03-25 21:21:43 +02:00
ss : '%d segundos',
m : 'um minuto',
mm : '%d minutos',
h : 'uma hora',
hh : '%d horas',
d : 'um dia',
dd : '%d dias',
M : 'um mês',
MM : '%d meses',
y : 'um ano',
yy : '%d anos'
},
dayOfMonthOrdinalParse: /\d{1,2}º/,
ordinal : '%dº'
});
return ptBr;
})));
//! moment.js locale configuration
//! locale : Russian [ru]
//! author : Viktorminator : https://github.com/Viktorminator
//! Author : Menelion Elensúle : https://github.com/Oire
//! author : Коренберг Марк : https://github.com/socketpair
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/ru',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
function plural(word, num) {
var forms = word.split('_');
return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]);
}
function relativeTimeWithPlural(number, withoutSuffix, key) {
var format = {
'mm': withoutSuffix ? 'минута_минуты_минут' : 'минуту_минуты_минут',
'hh': асасаасов',
'dd': ень_дня_дней',
'MM': есяц_месяцаесяцев',
'yy': 'год_годает'
};
if (key === 'm') {
return withoutSuffix ? 'минута' : 'минуту';
}
else {
return number + ' ' + plural(format[key], +number);
}
}
var monthsParse = [/^янв/i, /^фев/i, /^мар/i, /^апр/i, /^ма[йя]/i, /^июн/i, /^июл/i, /^авг/i, /^сен/i, /^окт/i, /^ноя/i, /^дек/i];
// http://new.gramota.ru/spravka/rules/139-prop : § 103
// Сокращения месяцев: http://new.gramota.ru/spravka/buro/search-answer?s=242637
// CLDR data: http://www.unicode.org/cldr/charts/28/summary/ru.html#1753
var ru = moment.defineLocale('ru', {
months : {
format: 'января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря'.split('_'),
standalone: 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_')
},
monthsShort : {
// по CLDR именно "июл." и "июн.", но какой смысл менять букву на точку ?
format: 'янв._февр._мар._апр._мая_июня_июля_авг._сент._окт._нояб._дек.'.split('_'),
standalone: 'янв._февр._март_апр._май_июнь_июль_авг._сент._окт._нояб._дек.'.split('_')
},
weekdays : {
standalone: оскресенье_понедельник_вторник_средаетверг_пятница_суббота'.split('_'),
format: оскресенье_понедельник_вторник_средуетверг_пятницу_субботу'.split('_'),
isFormat: /\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/
},
weekdaysShort : с_пн_вт_ср_чт_пт_сб'.split('_'),
weekdaysMin : с_пн_вт_ср_чт_пт_сб'.split('_'),
monthsParse : monthsParse,
longMonthsParse : monthsParse,
shortMonthsParse : monthsParse,
// полные названия с падежами, по три буквы, для некоторых, по 4 буквы, сокращения с точкой и без точки
monthsRegex: /^(январ[ья]|янв\.?|феврал[ья]|февр?\.?|марта?|мар\.?|апрел[ья]|апр\.?|ма[йя]|июн[ья]|июн\.?|июл[ья]|июл\.?|августа?|авг\.?|сентябр[ья]|сент?\.?|октябр[ья]|окт\.?|ноябр[ья]|нояб?\.?|декабр[ья]|дек\.?)/i,
// копия предыдущего
monthsShortRegex: /^(январ[ья]|янв\.?|феврал[ья]|февр?\.?|марта?|мар\.?|апрел[ья]|апр\.?|ма[йя]|июн[ья]|июн\.?|июл[ья]|июл\.?|августа?|авг\.?|сентябр[ья]|сент?\.?|октябр[ья]|окт\.?|ноябр[ья]|нояб?\.?|декабр[ья]|дек\.?)/i,
// полные названия с падежами
monthsStrictRegex: /^(январ[яь]|феврал[яь]|марта?|апрел[яь]|ма[яй]|июн[яь]|июл[яь]|августа?|сентябр[яь]|октябр[яь]|ноябр[яь]|декабр[яь])/i,
// Выражение, которое соотвествует только сокращённым формам
monthsShortStrictRegex: /^(янв\.|февр?\.|мар[т.]|апр\.|ма[яй]|июн[ья.]|июл[ья.]|авг\.|сент?\.|окт\.|нояб?\.|дек\.)/i,
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D MMMM YYYY г.',
LLL : 'D MMMM YYYY г., HH:mm',
LLLL : 'dddd, D MMMM YYYY г., HH:mm'
},
calendar : {
sameDay: '[Сегодня в] LT',
nextDay: '[Завтра в] LT',
lastDay: '[Вчера в] LT',
nextWeek: function (now) {
if (now.week() !== this.week()) {
switch (this.day()) {
case 0:
return '[В следующее] dddd [в] LT';
case 1:
case 2:
case 4:
return '[В следующий] dddd [в] LT';
case 3:
case 5:
case 6:
return '[В следующую] dddd [в] LT';
}
} else {
if (this.day() === 2) {
return '[Во] dddd [в] LT';
} else {
return '[В] dddd [в] LT';
}
2017-07-22 22:21:05 +02:00
}
},
lastWeek: function (now) {
if (now.week() !== this.week()) {
switch (this.day()) {
case 0:
return '[В прошлое] dddd [в] LT';
case 1:
case 2:
case 4:
return '[В прошлый] dddd [в] LT';
case 3:
case 5:
case 6:
return '[В прошлую] dddd [в] LT';
}
} else {
if (this.day() === 2) {
return '[Во] dddd [в] LT';
} else {
return '[В] dddd [в] LT';
}
}
},
sameElse: 'L'
},
relativeTime : {
future : 'через %s',
past : '%s назад',
s : 'несколько секунд',
m : relativeTimeWithPlural,
mm : relativeTimeWithPlural,
h : 'час',
hh : relativeTimeWithPlural,
d : 'день',
dd : relativeTimeWithPlural,
M : 'месяц',
MM : relativeTimeWithPlural,
y : 'год',
yy : relativeTimeWithPlural
},
meridiemParse: /ночи|утра|дня|вечера/i,
isPM : function (input) {
return /^(дня|вечера)$/.test(input);
},
meridiem : function (hour, minute, isLower) {
if (hour < 4) {
return 'ночи';
} else if (hour < 12) {
return 'утра';
} else if (hour < 17) {
return 'дня';
} else {
return 'вечера';
}
},
dayOfMonthOrdinalParse: /\d{1,2}-(й|го|я)/,
ordinal: function (number, period) {
switch (period) {
case 'M':
case 'd':
case 'DDD':
return number + '-й';
case 'D':
return number + '-го';
case 'w':
case 'W':
return number + '-я';
default:
return number;
}
},
week : {
dow : 1, // Monday is the first day of the week.
2018-03-25 21:21:43 +02:00
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return ru;
})));
2018-03-05 14:43:53 +01:00
//! moment.js locale configuration
//! locale : Turkish [tr]
//! authors : Erhan Gundogan : https://github.com/erhangundogan,
//! Burak Yiğit Kaya: https://github.com/BYK
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/tr',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var suffixes = {
1: '\'inci',
5: '\'inci',
8: '\'inci',
70: '\'inci',
80: '\'inci',
2: '\'nci',
7: '\'nci',
20: '\'nci',
50: '\'nci',
3: '\'üncü',
4: '\'üncü',
100: '\'üncü',
6: '\'ncı',
9: '\'uncu',
10: '\'uncu',
30: '\'uncu',
60: '\'ıncı',
90: '\'ıncı'
};
var tr = moment.defineLocale('tr', {
months : 'Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık'.split('_'),
monthsShort : 'Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara'.split('_'),
weekdays : 'Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi'.split('_'),
weekdaysShort : 'Paz_Pts_Sal_Çar_Per_Cum_Cts'.split('_'),
weekdaysMin : 'Pz_Pt_Sa_Ça_Pe_Cu_Ct'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd, D MMMM YYYY HH:mm'
},
calendar : {
sameDay : '[bugün saat] LT',
nextDay : '[yarın saat] LT',
2018-03-25 21:21:43 +02:00
nextWeek : '[gelecek] dddd [saat] LT',
2018-03-05 14:43:53 +01:00
lastDay : '[dün] LT',
2018-03-25 21:21:43 +02:00
lastWeek : '[geçen] dddd [saat] LT',
2018-03-05 14:43:53 +01:00
sameElse : 'L'
},
relativeTime : {
future : '%s sonra',
past : '%s önce',
s : 'birkaç saniye',
m : 'bir dakika',
mm : '%d dakika',
h : 'bir saat',
hh : '%d saat',
d : 'bir gün',
dd : '%d gün',
M : 'bir ay',
MM : '%d ay',
y : 'bir yıl',
yy : '%d yıl'
},
dayOfMonthOrdinalParse: /\d{1,2}'(inci|nci|üncü|ncı|uncu|ıncı)/,
ordinal : function (number) {
if (number === 0) { // special case for zero
return number + '\'ıncı';
}
var a = number % 10,
b = number % 100 - a,
c = number >= 100 ? 100 : null;
return number + (suffixes[a] || suffixes[b] || suffixes[c]);
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 7 // The week that contains Jan 1st is the first week of the year.
}
});
return tr;
})));
//! moment.js locale configuration
//! locale : Ukrainian [uk]
//! author : zemlanin : https://github.com/zemlanin
//! Author : Menelion Elensúle : https://github.com/Oire
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/uk',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
function plural(word, num) {
var forms = word.split('_');
return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]);
}
function relativeTimeWithPlural(number, withoutSuffix, key) {
var format = {
'mm': withoutSuffix ? 'хвилина_хвилини_хвилин' : 'хвилину_хвилини_хвилин',
'hh': withoutSuffix ? 'година_години_годин' : 'годину_години_годин',
'dd': ень_дні_днів',
'MM': ісяць_місяціісяців',
'yy': 'рік_роки_років'
};
if (key === 'm') {
return withoutSuffix ? 'хвилина' : 'хвилину';
}
else if (key === 'h') {
return withoutSuffix ? 'година' : 'годину';
}
else {
return number + ' ' + plural(format[key], +number);
}
}
function weekdaysCaseReplace(m, format) {
var weekdays = {
'nominative': еділя_понеділок_вівторок_середаетвер_пятниця_субота'.split('_'),
'accusative': еділю_понеділок_вівторок_середуетвер_пятницю_суботу'.split('_'),
'genitive': еділі_понеділкаівторка_середи_четверга_пятниці_суботи'.split('_')
};
if (!m) {
return weekdays['nominative'];
}
var nounCase = (/(\[[ВвУу]\]) ?dddd/).test(format) ?
'accusative' :
((/\[?(?:минулої|наступної)? ?\] ?dddd/).test(format) ?
'genitive' :
'nominative');
return weekdays[nounCase][m.day()];
}
function processHoursFunction(str) {
return function () {
return str + 'о' + (this.hours() === 11 ? 'б' : '') + '] LT';
};
}
var uk = moment.defineLocale('uk', {
months : {
'format': 'січня_лютого_березня_квітня_травня_червня_липня_серпня_вересня_жовтня_листопада_грудня'.split('_'),
'standalone': 'січень_лютий_березень_квітень_травень_червень_липень_серпень_вересень_жовтень_листопад_грудень'.split('_')
},
monthsShort : 'січ_лют_бер_квіт_трав_черв_лип_серп_веровт_лист_груд'.split('_'),
weekdays : weekdaysCaseReplace,
weekdaysShort : 'нд_пн_вт_ср_чт_пт_сб'.split('_'),
weekdaysMin : 'нд_пн_вт_ср_чт_пт_сб'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D MMMM YYYY р.',
LLL : 'D MMMM YYYY р., HH:mm',
LLLL : 'dddd, D MMMM YYYY р., HH:mm'
},
calendar : {
sameDay: processHoursFunction('[Сьогодні '),
nextDay: processHoursFunction('[Завтра '),
lastDay: processHoursFunction('[Вчора '),
nextWeek: processHoursFunction('[У] dddd ['),
lastWeek: function () {
switch (this.day()) {
case 0:
case 3:
case 5:
case 6:
return processHoursFunction('[Минулої] dddd [').call(this);
case 1:
case 2:
case 4:
return processHoursFunction('[Минулого] dddd [').call(this);
}
},
sameElse: 'L'
},
relativeTime : {
future : 'за %s',
past : '%s тому',
s : 'декілька секунд',
m : relativeTimeWithPlural,
mm : relativeTimeWithPlural,
h : 'годину',
hh : relativeTimeWithPlural,
d : 'день',
dd : relativeTimeWithPlural,
M : 'місяць',
MM : relativeTimeWithPlural,
y : 'рік',
yy : relativeTimeWithPlural
},
// M. E.: those two are virtually unused but a user might want to implement them for his/her website for some reason
meridiemParse: /ночі|ранку|дня|вечора/,
isPM: function (input) {
return /^(дня|вечора)$/.test(input);
},
meridiem : function (hour, minute, isLower) {
if (hour < 4) {
return 'ночі';
} else if (hour < 12) {
return 'ранку';
} else if (hour < 17) {
return 'дня';
} else {
return 'вечора';
}
},
dayOfMonthOrdinalParse: /\d{1,2}-(й|го)/,
ordinal: function (number, period) {
switch (period) {
case 'M':
case 'd':
case 'DDD':
case 'w':
case 'W':
return number + '-й';
case 'D':
return number + '-го';
default:
return number;
}
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 7 // The week that contains Jan 1st is the first week of the year.
}
});
return uk;
})));
2018-01-18 14:48:32 +01:00
//! moment.js locale configuration
//! locale : Chinese (China) [zh-cn]
//! author : suupic : https://github.com/suupic
//! author : Zeno Zeng : https://github.com/zenozeng
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/zh-cn',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var zhCn = moment.defineLocale('zh-cn', {
months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'),
weekdaysShort : '周日_周一_周二_周三_周四_周五_周六'.split('_'),
weekdaysMin : '日_一_二_三_四_五_六'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'YYYY年MMMD日',
LL : 'YYYY年MMMD日',
LLL : 'YYYY年MMMD日Ah点mm分',
LLLL : 'YYYY年MMMD日ddddAh点mm分',
l : 'YYYY年MMMD日',
ll : 'YYYY年MMMD日',
lll : 'YYYY年MMMD日 HH:mm',
llll : 'YYYY年MMMD日dddd HH:mm'
},
meridiemParse: /凌晨|早上|上午|中午|下午|晚上/,
meridiemHour: function (hour, meridiem) {
if (hour === 12) {
hour = 0;
}
if (meridiem === '凌晨' || meridiem === '早上' ||
meridiem === '上午') {
return hour;
} else if (meridiem === '下午' || meridiem === '晚上') {
return hour + 12;
} else {
// '中午'
return hour >= 11 ? hour : hour + 12;
}
},
meridiem : function (hour, minute, isLower) {
var hm = hour * 100 + minute;
if (hm < 600) {
return '凌晨';
} else if (hm < 900) {
return '早上';
} else if (hm < 1130) {
return '上午';
} else if (hm < 1230) {
return '中午';
} else if (hm < 1800) {
return '下午';
} else {
return '晚上';
}
},
calendar : {
sameDay : '[今天]LT',
nextDay : '[明天]LT',
nextWeek : '[下]ddddLT',
lastDay : '[昨天]LT',
lastWeek : '[上]ddddLT',
sameElse : 'L'
},
dayOfMonthOrdinalParse: /\d{1,2}(日|月|周)/,
ordinal : function (number, period) {
switch (period) {
case 'd':
case 'D':
case 'DDD':
return number + '日';
case 'M':
return number + '月';
case 'w':
case 'W':
return number + '周';
default:
return number;
}
},
relativeTime : {
future : '%s内',
past : '%s前',
s : '几秒',
m : '1 分钟',
mm : '%d 分钟',
h : '1 小时',
hh : '%d 小时',
d : '1 天',
dd : '%d 天',
M : '1 个月',
MM : '%d 个月',
y : '1 年',
yy : '%d 年'
},
week : {
// GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return zhCn;
})));
//! moment.js locale configuration
//! locale : Chinese (Taiwan) [zh-tw]
//! author : Ben : https://github.com/ben-lin
//! author : Chris Lam : https://github.com/hehachris
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define('moment/locale/zh-tw',['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var zhTw = moment.defineLocale('zh-tw', {
months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'),
weekdaysShort : '週日_週一_週二_週三_週四_週五_週六'.split('_'),
weekdaysMin : '日_一_二_三_四_五_六'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'YYYY年MMMD日',
LL : 'YYYY年MMMD日',
LLL : 'YYYY年MMMD日 HH:mm',
LLLL : 'YYYY年MMMD日dddd HH:mm',
l : 'YYYY年MMMD日',
ll : 'YYYY年MMMD日',
lll : 'YYYY年MMMD日 HH:mm',
llll : 'YYYY年MMMD日dddd HH:mm'
},
meridiemParse: /凌晨|早上|上午|中午|下午|晚上/,
meridiemHour : function (hour, meridiem) {
if (hour === 12) {
hour = 0;
}
if (meridiem === '凌晨' || meridiem === '早上' || meridiem === '上午') {
return hour;
} else if (meridiem === '中午') {
return hour >= 11 ? hour : hour + 12;
} else if (meridiem === '下午' || meridiem === '晚上') {
return hour + 12;
}
},
meridiem : function (hour, minute, isLower) {
var hm = hour * 100 + minute;
if (hm < 600) {
return '凌晨';
} else if (hm < 900) {
return '早上';
} else if (hm < 1130) {
return '上午';
} else if (hm < 1230) {
return '中午';
} else if (hm < 1800) {
return '下午';
} else {
return '晚上';
}
},
calendar : {
sameDay : '[今天]LT',
nextDay : '[明天]LT',
nextWeek : '[下]ddddLT',
lastDay : '[昨天]LT',
lastWeek : '[上]ddddLT',
sameElse : 'L'
},
dayOfMonthOrdinalParse: /\d{1,2}(日|月|週)/,
ordinal : function (number, period) {
switch (period) {
case 'd' :
case 'D' :
case 'DDD' :
return number + '日';
case 'M' :
return number + '月';
case 'w' :
case 'W' :
return number + '週';
default :
return number;
}
},
relativeTime : {
future : '%s內',
past : '%s前',
s : '幾秒',
m : '1 分鐘',
mm : '%d 分鐘',
h : '1 小時',
hh : '%d 小時',
d : '1 天',
dd : '%d 天',
M : '1 個月',
MM : '%d 個月',
y : '1 年',
yy : '%d 年'
}
});
return zhTw;
})));
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// This is the internationalization module.
//
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define */
(function (root, factory) {
define('i18n',["es6-promise", "jed", "lodash.noconflict", "moment", 'moment/locale/af', 'moment/locale/ar', 'moment/locale/bg', 'moment/locale/ca', 'moment/locale/de', 'moment/locale/es', 'moment/locale/eu', 'moment/locale/fr', 'moment/locale/he', 'moment/locale/hu', 'moment/locale/id', 'moment/locale/it', 'moment/locale/ja', 'moment/locale/nb', 'moment/locale/nl', 'moment/locale/pl', 'moment/locale/pt-br', 'moment/locale/ru', 'moment/locale/tr', 'moment/locale/uk', 'moment/locale/zh-cn', 'moment/locale/zh-tw'], factory);
})(this, function (Promise, Jed, _, moment) {
'use strict';
function detectLocale(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 = isLocaleAvailable(window.navigator.userLanguage, library_check);
}
if (window.navigator.languages && !locale) {
for (i = 0; i < window.navigator.languages.length && !locale; i++) {
locale = isLocaleAvailable(window.navigator.languages[i], library_check);
}
}
if (window.navigator.browserLanguage && !locale) {
locale = isLocaleAvailable(window.navigator.browserLanguage, library_check);
}
if (window.navigator.language && !locale) {
locale = isLocaleAvailable(window.navigator.language, library_check);
}
if (window.navigator.systemLanguage && !locale) {
locale = isLocaleAvailable(window.navigator.systemLanguage, library_check);
}
return locale || 'en';
}
function isMomentLocale(locale) {
return _.isString(locale) && moment.locale() === moment.locale(locale);
}
function isConverseLocale(locale, supported_locales) {
return _.isString(locale) && _.includes(supported_locales, locale);
}
function getLocale(preferred_locale, isSupportedByLibrary) {
if (_.isString(preferred_locale)) {
if (preferred_locale === 'en' || isSupportedByLibrary(preferred_locale)) {
return preferred_locale;
}
}
return detectLocale(isSupportedByLibrary) || 'en';
}
function isLocaleAvailable(locale, available) {
/* Check whether the locale or sub locale (e.g. en-US, en) is supported.
*
* Parameters:
* (String) locale - The locale to check for
* (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;
}
}
}
var jed_instance;
return {
setLocales: function setLocales(preferred_locale, _converse) {
_converse.locale = getLocale(preferred_locale, _.partial(isConverseLocale, _, _converse.locales));
moment.locale(getLocale(preferred_locale, isMomentLocale));
},
translate: function translate(str) {
if (_.isNil(jed_instance)) {
return Jed.sprintf.apply(Jed, arguments);
}
var t = jed_instance.translate(str);
if (arguments.length > 1) {
return t.fetch.apply(t, [].slice.call(arguments, 1));
} else {
return t.fetch();
}
},
fetchTranslations: function fetchTranslations(locale, supported_locales, locale_url) {
/* Fetch the translations for the given local at the given URL.
*
* Parameters:
* (String) locale: The given i18n locale
* (Array) supported_locales: List of locales supported
* (String) locale_url: The URL from which the translations
* should be fetched.
*/
return new Promise(function (resolve, reject) {
if (!isConverseLocale(locale, supported_locales) || locale === 'en') {
return resolve();
}
var xhr = new XMLHttpRequest();
xhr.open('GET', locale_url, true);
xhr.setRequestHeader('Accept', "application/json, text/javascript");
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 400) {
jed_instance = new Jed(window.JSON.parse(xhr.responseText));
resolve();
} else {
xhr.onerror();
2018-01-04 17:58:16 +01:00
}
};
2017-07-22 22:21:05 +02:00
2018-01-04 17:58:16 +01:00
xhr.onerror = function () {
reject(xhr.statusText);
};
2017-07-22 22:21:05 +02:00
2018-01-04 17:58:16 +01:00
xhr.send();
});
}
};
});
//# sourceMappingURL=i18n.js.map;
2018-04-30 16:05:20 +02:00
/*! https://mths.be/punycode v1.4.0 by @mathias */
;(function(root) {
/** Detect free variables */
var freeExports = typeof exports == 'object' && exports &&
!exports.nodeType && exports;
var freeModule = typeof module == 'object' && module &&
!module.nodeType && module;
var freeGlobal = typeof global == 'object' && global;
if (
freeGlobal.global === freeGlobal ||
freeGlobal.window === freeGlobal ||
freeGlobal.self === freeGlobal
) {
root = freeGlobal;
}
/**
* The `punycode` object.
* @name punycode
* @type Object
*/
var punycode,
/** Highest positive signed 32-bit float value */
maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1
/** Bootstring parameters */
base = 36,
tMin = 1,
tMax = 26,
skew = 38,
damp = 700,
initialBias = 72,
initialN = 128, // 0x80
delimiter = '-', // '\x2D'
/** Regular expressions */
regexPunycode = /^xn--/,
regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars
regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators
/** Error messages */
errors = {
'overflow': 'Overflow: input needs wider integers to process',
'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
'invalid-input': 'Invalid input'
},
/** Convenience shortcuts */
baseMinusTMin = base - tMin,
floor = Math.floor,
stringFromCharCode = String.fromCharCode,
/** Temporary variable */
key;
/*--------------------------------------------------------------------------*/
/**
* A generic error utility function.
* @private
* @param {String} type The error type.
* @returns {Error} Throws a `RangeError` with the applicable error message.
*/
function error(type) {
throw new RangeError(errors[type]);
}
/**
* A generic `Array#map` utility function.
* @private
* @param {Array} array The array to iterate over.
* @param {Function} callback The function that gets called for every array
* item.
* @returns {Array} A new array of values returned by the callback function.
*/
function map(array, fn) {
var length = array.length;
var result = [];
while (length--) {
result[length] = fn(array[length]);
}
return result;
}
/**
* A simple `Array#map`-like wrapper to work with domain name strings or email
* addresses.
* @private
* @param {String} domain The domain name or email address.
* @param {Function} callback The function that gets called for every
* character.
* @returns {Array} A new string of characters returned by the callback
* function.
*/
function mapDomain(string, fn) {
var parts = string.split('@');
var result = '';
if (parts.length > 1) {
// In email addresses, only the domain name should be punycoded. Leave
// the local part (i.e. everything up to `@`) intact.
result = parts[0] + '@';
string = parts[1];
}
// Avoid `split(regex)` for IE8 compatibility. See #17.
string = string.replace(regexSeparators, '\x2E');
var labels = string.split('.');
var encoded = map(labels, fn).join('.');
return result + encoded;
}
/**
* Creates an array containing the numeric code points of each Unicode
* character in the string. While JavaScript uses UCS-2 internally,
* this function will convert a pair of surrogate halves (each of which
* UCS-2 exposes as separate characters) into a single code point,
* matching UTF-16.
* @see `punycode.ucs2.encode`
* @see <https://mathiasbynens.be/notes/javascript-encoding>
* @memberOf punycode.ucs2
* @name decode
* @param {String} string The Unicode input string (UCS-2).
* @returns {Array} The new array of code points.
*/
function ucs2decode(string) {
var output = [],
counter = 0,
length = string.length,
value,
extra;
while (counter < length) {
value = string.charCodeAt(counter++);
if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
// high surrogate, and there is a next character
extra = string.charCodeAt(counter++);
if ((extra & 0xFC00) == 0xDC00) { // low surrogate
output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
} else {
// unmatched surrogate; only append this code unit, in case the next
// code unit is the high surrogate of a surrogate pair
output.push(value);
counter--;
}
} else {
output.push(value);
}
}
return output;
}
/**
* Creates a string based on an array of numeric code points.
* @see `punycode.ucs2.decode`
* @memberOf punycode.ucs2
* @name encode
* @param {Array} codePoints The array of numeric code points.
* @returns {String} The new Unicode string (UCS-2).
*/
function ucs2encode(array) {
return map(array, function(value) {
var output = '';
if (value > 0xFFFF) {
value -= 0x10000;
output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
value = 0xDC00 | value & 0x3FF;
}
output += stringFromCharCode(value);
return output;
}).join('');
}
/**
* Converts a basic code point into a digit/integer.
* @see `digitToBasic()`
* @private
* @param {Number} codePoint The basic numeric code point value.
* @returns {Number} The numeric value of a basic code point (for use in
* representing integers) in the range `0` to `base - 1`, or `base` if
* the code point does not represent a value.
*/
function basicToDigit(codePoint) {
if (codePoint - 48 < 10) {
return codePoint - 22;
}
if (codePoint - 65 < 26) {
return codePoint - 65;
}
if (codePoint - 97 < 26) {
return codePoint - 97;
}
return base;
}
/**
* Converts a digit/integer into a basic code point.
* @see `basicToDigit()`
* @private
* @param {Number} digit The numeric value of a basic code point.
* @returns {Number} The basic code point whose value (when used for
* representing integers) is `digit`, which needs to be in the range
* `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
* used; else, the lowercase form is used. The behavior is undefined
* if `flag` is non-zero and `digit` has no uppercase form.
*/
function digitToBasic(digit, flag) {
// 0..25 map to ASCII a..z or A..Z
// 26..35 map to ASCII 0..9
return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
}
/**
* Bias adaptation function as per section 3.4 of RFC 3492.
* https://tools.ietf.org/html/rfc3492#section-3.4
* @private
*/
function adapt(delta, numPoints, firstTime) {
var k = 0;
delta = firstTime ? floor(delta / damp) : delta >> 1;
delta += floor(delta / numPoints);
for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
delta = floor(delta / baseMinusTMin);
}
return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
}
/**
* Converts a Punycode string of ASCII-only symbols to a string of Unicode
* symbols.
* @memberOf punycode
* @param {String} input The Punycode string of ASCII-only symbols.
* @returns {String} The resulting string of Unicode symbols.
*/
function decode(input) {
// Don't use UCS-2
var output = [],
inputLength = input.length,
out,
i = 0,
n = initialN,
bias = initialBias,
basic,
j,
index,
oldi,
w,
k,
digit,
t,
/** Cached calculation results */
baseMinusT;
// Handle the basic code points: let `basic` be the number of input code
// points before the last delimiter, or `0` if there is none, then copy
// the first basic code points to the output.
basic = input.lastIndexOf(delimiter);
if (basic < 0) {
basic = 0;
}
for (j = 0; j < basic; ++j) {
// if it's not a basic code point
if (input.charCodeAt(j) >= 0x80) {
error('not-basic');
}
output.push(input.charCodeAt(j));
}
// Main decoding loop: start just after the last delimiter if any basic code
// points were copied; start at the beginning otherwise.
for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
// `index` is the index of the next character to be consumed.
// Decode a generalized variable-length integer into `delta`,
// which gets added to `i`. The overflow checking is easier
// if we increase `i` as we go, then subtract off its starting
// value at the end to obtain `delta`.
for (oldi = i, w = 1, k = base; /* no condition */; k += base) {
if (index >= inputLength) {
error('invalid-input');
}
digit = basicToDigit(input.charCodeAt(index++));
if (digit >= base || digit > floor((maxInt - i) / w)) {
error('overflow');
}
i += digit * w;
t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
if (digit < t) {
break;
}
baseMinusT = base - t;
if (w > floor(maxInt / baseMinusT)) {
error('overflow');
}
w *= baseMinusT;
}
out = output.length + 1;
bias = adapt(i - oldi, out, oldi == 0);
// `i` was supposed to wrap around from `out` to `0`,
// incrementing `n` each time, so we'll fix that now:
if (floor(i / out) > maxInt - n) {
error('overflow');
}
n += floor(i / out);
i %= out;
// Insert `n` at position `i` of the output
output.splice(i++, 0, n);
}
return ucs2encode(output);
}
/**
* Converts a string of Unicode symbols (e.g. a domain name label) to a
* Punycode string of ASCII-only symbols.
* @memberOf punycode
* @param {String} input The string of Unicode symbols.
* @returns {String} The resulting Punycode string of ASCII-only symbols.
*/
function encode(input) {
var n,
delta,
handledCPCount,
basicLength,
bias,
j,
m,
q,
k,
t,
currentValue,
output = [],
/** `inputLength` will hold the number of code points in `input`. */
inputLength,
/** Cached calculation results */
handledCPCountPlusOne,
baseMinusT,
qMinusT;
// Convert the input in UCS-2 to Unicode
input = ucs2decode(input);
// Cache the length
inputLength = input.length;
// Initialize the state
n = initialN;
delta = 0;
bias = initialBias;
// Handle the basic code points
for (j = 0; j < inputLength; ++j) {
currentValue = input[j];
if (currentValue < 0x80) {
output.push(stringFromCharCode(currentValue));
}
}
handledCPCount = basicLength = output.length;
// `handledCPCount` is the number of code points that have been handled;
// `basicLength` is the number of basic code points.
// Finish the basic string - if it is not empty - with a delimiter
if (basicLength) {
output.push(delimiter);
}
// Main encoding loop:
while (handledCPCount < inputLength) {
// All non-basic code points < n have been handled already. Find the next
// larger one:
for (m = maxInt, j = 0; j < inputLength; ++j) {
currentValue = input[j];
if (currentValue >= n && currentValue < m) {
m = currentValue;
}
}
// Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
// but guard against overflow
handledCPCountPlusOne = handledCPCount + 1;
if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
error('overflow');
}
delta += (m - n) * handledCPCountPlusOne;
n = m;
for (j = 0; j < inputLength; ++j) {
currentValue = input[j];
if (currentValue < n && ++delta > maxInt) {
error('overflow');
}
if (currentValue == n) {
// Represent delta as a generalized variable-length integer
for (q = delta, k = base; /* no condition */; k += base) {
t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
if (q < t) {
break;
}
qMinusT = q - t;
baseMinusT = base - t;
output.push(
stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
);
q = floor(qMinusT / baseMinusT);
}
output.push(stringFromCharCode(digitToBasic(q, 0)));
bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
delta = 0;
++handledCPCount;
}
}
++delta;
++n;
}
return output.join('');
}
/**
* Converts a Punycode string representing a domain name or an email address
* to Unicode. Only the Punycoded parts of the input will be converted, i.e.
* it doesn't matter if you call it on a string that has already been
* converted to Unicode.
* @memberOf punycode
* @param {String} input The Punycoded domain name or email address to
* convert to Unicode.
* @returns {String} The Unicode representation of the given Punycode
* string.
*/
function toUnicode(input) {
return mapDomain(input, function(string) {
return regexPunycode.test(string)
? decode(string.slice(4).toLowerCase())
: string;
});
}
/**
* Converts a Unicode string representing a domain name or an email address to
* Punycode. Only the non-ASCII parts of the domain name will be converted,
* i.e. it doesn't matter if you call it with a domain that's already in
* ASCII.
* @memberOf punycode
* @param {String} input The domain name or email address to convert, as a
* Unicode string.
* @returns {String} The Punycode representation of the given domain name or
* email address.
*/
function toASCII(input) {
return mapDomain(input, function(string) {
return regexNonASCII.test(string)
? 'xn--' + encode(string)
: string;
});
}
/*--------------------------------------------------------------------------*/
/** Define the public API */
punycode = {
/**
* A string representing the current Punycode.js version number.
* @memberOf punycode
* @type String
*/
'version': '1.3.2',
/**
* An object of methods to convert from JavaScript's internal character
* representation (UCS-2) to Unicode code points, and back.
* @see <https://mathiasbynens.be/notes/javascript-encoding>
* @memberOf punycode
* @type Object
*/
'ucs2': {
'decode': ucs2decode,
'encode': ucs2encode
},
'decode': decode,
'encode': encode,
'toASCII': toASCII,
'toUnicode': toUnicode
};
/** Expose `punycode` */
// Some AMD build optimizers, like r.js, check for specific condition patterns
// like the following:
if (
typeof define == 'function' &&
typeof define.amd == 'object' &&
define.amd
) {
define('punycode', [],function() {
return punycode;
});
} else if (freeExports && freeModule) {
if (module.exports == freeExports) {
// in Node.js, io.js, or RingoJS v0.8.0+
freeModule.exports = punycode;
} else {
// in Narwhal or RingoJS v0.7.0-
for (key in punycode) {
punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
}
}
} else {
// in Rhino or a web browser
root.punycode = punycode;
}
}(this));
/*!
* URI.js - Mutating URLs
* IPv6 Support
*
* Version: 1.19.1
*
* Author: Rodney Rehm
* Web: http://medialize.github.io/URI.js/
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
*
*/
(function (root, factory) {
'use strict';
// https://github.com/umdjs/umd/blob/master/returnExports.js
if (typeof module === 'object' && module.exports) {
// Node
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define('IPv6',factory);
} else {
// Browser globals (root is window)
root.IPv6 = factory(root);
}
}(this, function (root) {
'use strict';
/*
var _in = "fe80:0000:0000:0000:0204:61ff:fe9d:f156";
var _out = IPv6.best(_in);
var _expected = "fe80::204:61ff:fe9d:f156";
console.log(_in, _out, _expected, _out === _expected);
*/
// save current IPv6 variable, if any
var _IPv6 = root && root.IPv6;
function bestPresentation(address) {
// based on:
// Javascript to test an IPv6 address for proper format, and to
// present the "best text representation" according to IETF Draft RFC at
// http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04
// 8 Feb 2010 Rich Brown, Dartware, LLC
// Please feel free to use this code as long as you provide a link to
// http://www.intermapper.com
// http://intermapper.com/support/tools/IPV6-Validator.aspx
// http://download.dartware.com/thirdparty/ipv6validator.js
var _address = address.toLowerCase();
var segments = _address.split(':');
var length = segments.length;
var total = 8;
// trim colons (:: or ::a:b:c… or …a:b:c::)
if (segments[0] === '' && segments[1] === '' && segments[2] === '') {
// must have been ::
// remove first two items
segments.shift();
segments.shift();
} else if (segments[0] === '' && segments[1] === '') {
// must have been ::xxxx
// remove the first item
segments.shift();
} else if (segments[length - 1] === '' && segments[length - 2] === '') {
// must have been xxxx::
segments.pop();
}
length = segments.length;
// adjust total segments for IPv4 trailer
if (segments[length - 1].indexOf('.') !== -1) {
// found a "." which means IPv4
total = 7;
}
// fill empty segments them with "0000"
var pos;
for (pos = 0; pos < length; pos++) {
if (segments[pos] === '') {
break;
}
}
if (pos < total) {
segments.splice(pos, 1, '0000');
while (segments.length < total) {
segments.splice(pos, 0, '0000');
}
}
// strip leading zeros
var _segments;
for (var i = 0; i < total; i++) {
_segments = segments[i].split('');
for (var j = 0; j < 3 ; j++) {
if (_segments[0] === '0' && _segments.length > 1) {
_segments.splice(0,1);
} else {
break;
}
}
segments[i] = _segments.join('');
}
// find longest sequence of zeroes and coalesce them into one segment
var best = -1;
var _best = 0;
var _current = 0;
var current = -1;
var inzeroes = false;
// i; already declared
for (i = 0; i < total; i++) {
if (inzeroes) {
if (segments[i] === '0') {
_current += 1;
} else {
inzeroes = false;
if (_current > _best) {
best = current;
_best = _current;
}
}
} else {
if (segments[i] === '0') {
inzeroes = true;
current = i;
_current = 1;
}
}
}
if (_current > _best) {
best = current;
_best = _current;
}
if (_best > 1) {
segments.splice(best, _best, '');
}
length = segments.length;
// assemble remaining segments
var result = '';
if (segments[0] === '') {
result = ':';
}
for (i = 0; i < length; i++) {
result += segments[i];
if (i === length - 1) {
break;
}
result += ':';
}
if (segments[length - 1] === '') {
result += ':';
}
return result;
}
function noConflict() {
/*jshint validthis: true */
if (root.IPv6 === this) {
root.IPv6 = _IPv6;
}
return this;
}
return {
best: bestPresentation,
noConflict: noConflict
};
}));
/*!
* URI.js - Mutating URLs
* Second Level Domain (SLD) Support
*
* Version: 1.19.1
*
* Author: Rodney Rehm
* Web: http://medialize.github.io/URI.js/
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
*
*/
(function (root, factory) {
'use strict';
// https://github.com/umdjs/umd/blob/master/returnExports.js
if (typeof module === 'object' && module.exports) {
// Node
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define('SecondLevelDomains',factory);
} else {
// Browser globals (root is window)
root.SecondLevelDomains = factory(root);
}
}(this, function (root) {
'use strict';
// save current SecondLevelDomains variable, if any
var _SecondLevelDomains = root && root.SecondLevelDomains;
var SLD = {
// list of known Second Level Domains
// converted list of SLDs from https://github.com/gavingmiller/second-level-domains
// ----
// publicsuffix.org is more current and actually used by a couple of browsers internally.
// downside is it also contains domains like "dyndns.org" - which is fine for the security
// issues browser have to deal with (SOP for cookies, etc) - but is way overboard for URI.js
// ----
list: {
'ac':' com gov mil net org ',
'ae':' ac co gov mil name net org pro sch ',
'af':' com edu gov net org ',
'al':' com edu gov mil net org ',
'ao':' co ed gv it og pb ',
'ar':' com edu gob gov int mil net org tur ',
'at':' ac co gv or ',
'au':' asn com csiro edu gov id net org ',
'ba':' co com edu gov mil net org rs unbi unmo unsa untz unze ',
'bb':' biz co com edu gov info net org store tv ',
'bh':' biz cc com edu gov info net org ',
'bn':' com edu gov net org ',
'bo':' com edu gob gov int mil net org tv ',
'br':' adm adv agr am arq art ato b bio blog bmd cim cng cnt com coop ecn edu eng esp etc eti far flog fm fnd fot fst g12 ggf gov imb ind inf jor jus lel mat med mil mus net nom not ntr odo org ppg pro psc psi qsl rec slg srv tmp trd tur tv vet vlog wiki zlg ',
'bs':' com edu gov net org ',
'bz':' du et om ov rg ',
'ca':' ab bc mb nb nf nl ns nt nu on pe qc sk yk ',
'ck':' biz co edu gen gov info net org ',
'cn':' ac ah bj com cq edu fj gd gov gs gx gz ha hb he hi hl hn jl js jx ln mil net nm nx org qh sc sd sh sn sx tj tw xj xz yn zj ',
'co':' com edu gov mil net nom org ',
'cr':' ac c co ed fi go or sa ',
'cy':' ac biz com ekloges gov ltd name net org parliament press pro tm ',
'do':' art com edu gob gov mil net org sld web ',
'dz':' art asso com edu gov net org pol ',
'ec':' com edu fin gov info med mil net org pro ',
'eg':' com edu eun gov mil name net org sci ',
'er':' com edu gov ind mil net org rochest w ',
'es':' com edu gob nom org ',
'et':' biz com edu gov info name net org ',
'fj':' ac biz com info mil name net org pro ',
'fk':' ac co gov net nom org ',
'fr':' asso com f gouv nom prd presse tm ',
'gg':' co net org ',
'gh':' com edu gov mil org ',
'gn':' ac com gov net org ',
'gr':' com edu gov mil net org ',
'gt':' com edu gob ind mil net org ',
'gu':' com edu gov net org ',
'hk':' com edu gov idv net org ',
'hu':' 2000 agrar bolt casino city co erotica erotika film forum games hotel info ingatlan jogasz konyvelo lakas media news org priv reklam sex shop sport suli szex tm tozsde utazas video ',
'id':' ac co go mil net or sch web ',
'il':' ac co gov idf k12 muni net org ',
'in':' ac co edu ernet firm gen gov i ind mil net nic org res ',
'iq':' com edu gov i mil net org ',
'ir':' ac co dnssec gov i id net org sch ',
'it':' edu gov ',
'je':' co net org ',
'jo':' com edu gov mil name net org sch ',
'jp':' ac ad co ed go gr lg ne or ',
'ke':' ac co go info me mobi ne or sc ',
'kh':' com edu gov mil net org per ',
'ki':' biz com de edu gov info mob net org tel ',
'km':' asso com coop edu gouv k medecin mil nom notaires pharmaciens presse tm veterinaire ',
'kn':' edu gov net org ',
'kr':' ac busan chungbuk chungnam co daegu daejeon es gangwon go gwangju gyeongbuk gyeonggi gyeongnam hs incheon jeju jeonbuk jeonnam k kg mil ms ne or pe re sc seoul ulsan ',
'kw':' com edu gov net org ',
'ky':' com edu gov net org ',
'kz':' com edu gov mil net org ',
'lb':' com edu gov net org ',
'lk':' assn com edu gov grp hotel int ltd net ngo org sch soc web ',
'lr':' com edu gov net org ',
'lv':' asn com conf edu gov id mil net org ',
'ly':' com edu gov id med net org plc sch ',
'ma':' ac co gov m net org press ',
'mc':' asso tm ',
'me':' ac co edu gov its net org priv ',
'mg':' com edu gov mil nom org prd tm ',
'mk':' com edu gov inf name net org pro ',
'ml':' com edu gov net org presse ',
'mn':' edu gov org ',
'mo':' com edu gov net org ',
'mt':' com edu gov net org ',
'mv':' aero biz com coop edu gov info int mil museum name net org pro ',
'mw':' ac co com coop edu gov int museum net org ',
'mx':' com edu gob net org ',
'my':' com edu gov mil name net org sch ',
'nf':' arts com firm info net other per rec store web ',
'ng':' biz com edu gov mil mobi name net org sch ',
'ni':' ac co com edu gob mil net nom org ',
'np':' com edu gov mil net org ',
'nr':' biz com edu gov info net org ',
'om':' ac biz co com edu gov med mil museum net org pro sch ',
'pe':' com edu gob mil net nom org sld ',
'ph':' com edu gov i mil net ngo org ',
'pk':' biz com edu fam gob gok gon gop gos gov net org web ',
'pl':' art bialystok biz com edu gda gdansk gorzow gov info katowice krakow lodz lublin mil net ngo olsztyn org poznan pwr radom slupsk szczecin torun warszawa waw wroc wroclaw zgora ',
'pr':' ac biz com edu est gov info isla name net org pro prof ',
'ps':' com edu gov net org plo sec ',
'pw':' belau co ed go ne or ',
'ro':' arts com firm info nom nt org rec store tm www ',
'rs':' ac co edu gov in org ',
'sb':' com edu gov net org ',
'sc':' com edu gov net org ',
'sh':' co com edu gov net nom org ',
'sl':' com edu gov net org ',
'st':' co com consulado edu embaixada gov mil net org principe saotome store ',
'sv':' com edu gob org red ',
'sz':' ac co org ',
'tr':' av bbs bel biz com dr edu gen gov info k12 name net org pol tel tsk tv web ',
'tt':' aero biz cat co com coop edu gov info int jobs mil mobi museum name net org pro tel travel ',
'tw':' club com ebiz edu game gov idv mil net org ',
'mu':' ac co com gov net or org ',
'mz':' ac co edu gov org ',
'na':' co com ',
'nz':' ac co cri geek gen govt health iwi maori mil net org parliament school ',
'pa':' abo ac com edu gob ing med net nom org sld ',
'pt':' com edu gov int net nome org publ ',
'py':' com edu gov mil net org ',
'qa':' com edu gov mil net org ',
're':' asso com nom ',
'ru':' ac adygeya altai amur arkhangelsk astrakhan bashkiria belgorod bir bryansk buryatia cbg chel chelyabinsk chita chukotka chuvashia com dagestan e-burg edu gov grozny int irkutsk ivanovo izhevsk jar joshkar-ola kalmykia kaluga kamchatka karelia kazan kchr kemerovo khabarovsk khakassia khv kirov koenig komi kostroma kranoyarsk kuban kurgan kursk lipetsk magadan mari mari-el marine mil mordovia mosreg msk murmansk nalchik net nnov nov novosibirsk nsk omsk orenburg org oryol penza perm pp pskov ptz rnd ryazan sakhalin samara saratov simbirsk smolensk spb stavropol stv surgut tambov tatarstan tom tomsk tsaritsyn tsk tula tuva tver tyumen udm udmurtia ulan-ude vladikavkaz vladimir vladivostok volgograd vologda voronezh vrn vyatka yakutia yamal yekaterinburg yuzhno-sakhalinsk ',
'rw':' ac co com edu gouv gov int mil net ',
'sa':' com edu gov med net org pub sch ',
'sd':' com edu gov info med net org tv ',
'se':' a ac b bd c d e f g h i k l m n o org p parti pp press r s t tm u w x y z ',
'sg':' com edu gov idn net org per ',
'sn':' art com edu gouv org perso univ ',
'sy':' com edu gov mil net news org ',
'th':' ac co go in mi net or ',
'tj':' ac biz co com edu go gov info int mil name net nic org test web ',
'tn':' agrinet com defense edunet ens fin gov ind info intl mincom nat net org perso rnrt rns rnu tourism ',
'tz':' ac co go ne or ',
'ua':' biz cherkassy chernigov chernovtsy ck cn co com crimea cv dn dnepropetrovsk donetsk dp edu gov if in ivano-frankivsk kh kharkov kherson khmelnitskiy kiev kirovograd km kr ks kv lg lugansk lutsk lviv me mk net nikolaev od odessa org pl poltava pp rovno rv sebastopol sumy te ternopil uzhgorod vinnica vn zaporizhzhe zhitomir zp zt ',
'ug':' ac co go ne or org sc ',
'uk':' ac bl british-library co cym gov govt icnet jet lea ltd me mil mod national-library-scotland nel net nhs nic nls org orgn parliament plc police sch scot soc ',
'us':' dni fed isa kids nsn ',
'uy':' com edu gub mil net org ',
've':' co com edu gob info mil net org web ',
'vi':' co com k12 net org ',
'vn':' ac biz com edu gov health info int name net org pro ',
'ye':' co com gov ltd me net org plc ',
'yu':' ac co edu gov org ',
'za':' ac agric alt bourse city co cybernet db edu gov grondar iaccess imt inca landesign law mil net ngo nis nom olivetti org pix school tm web ',
'zm':' ac co com edu gov net org sch ',
// https://en.wikipedia.org/wiki/CentralNic#Second-level_domains
'com': 'ar br cn de eu gb gr hu jpn kr no qc ru sa se uk us uy za ',
'net': 'gb jp se uk ',
'org': 'ae',
'de': 'com '
},
// gorhill 2013-10-25: Using indexOf() instead Regexp(). Significant boost
// in both performance and memory footprint. No initialization required.
// http://jsperf.com/uri-js-sld-regex-vs-binary-search/4
// Following methods use lastIndexOf() rather than array.split() in order
// to avoid any memory allocations.
has: function(domain) {
var tldOffset = domain.lastIndexOf('.');
if (tldOffset <= 0 || tldOffset >= (domain.length-1)) {
return false;
}
var sldOffset = domain.lastIndexOf('.', tldOffset-1);
if (sldOffset <= 0 || sldOffset >= (tldOffset-1)) {
return false;
}
var sldList = SLD.list[domain.slice(tldOffset+1)];
if (!sldList) {
return false;
}
return sldList.indexOf(' ' + domain.slice(sldOffset+1, tldOffset) + ' ') >= 0;
},
is: function(domain) {
var tldOffset = domain.lastIndexOf('.');
if (tldOffset <= 0 || tldOffset >= (domain.length-1)) {
return false;
}
var sldOffset = domain.lastIndexOf('.', tldOffset-1);
if (sldOffset >= 0) {
return false;
}
var sldList = SLD.list[domain.slice(tldOffset+1)];
if (!sldList) {
return false;
}
return sldList.indexOf(' ' + domain.slice(0, tldOffset) + ' ') >= 0;
},
get: function(domain) {
var tldOffset = domain.lastIndexOf('.');
if (tldOffset <= 0 || tldOffset >= (domain.length-1)) {
return null;
}
var sldOffset = domain.lastIndexOf('.', tldOffset-1);
if (sldOffset <= 0 || sldOffset >= (tldOffset-1)) {
return null;
}
var sldList = SLD.list[domain.slice(tldOffset+1)];
if (!sldList) {
return null;
}
if (sldList.indexOf(' ' + domain.slice(sldOffset+1, tldOffset) + ' ') < 0) {
return null;
}
return domain.slice(sldOffset+1);
},
noConflict: function(){
if (root.SecondLevelDomains === this) {
root.SecondLevelDomains = _SecondLevelDomains;
}
return this;
}
};
return SLD;
}));
/*!
* URI.js - Mutating URLs
*
* Version: 1.19.1
*
* Author: Rodney Rehm
* Web: http://medialize.github.io/URI.js/
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
*
*/
(function (root, factory) {
'use strict';
// https://github.com/umdjs/umd/blob/master/returnExports.js
if (typeof module === 'object' && module.exports) {
// Node
module.exports = factory(require('./punycode'), require('./IPv6'), require('./SecondLevelDomains'));
} else if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define('uri',['./punycode', './IPv6', './SecondLevelDomains'], factory);
} else {
// Browser globals (root is window)
root.URI = factory(root.punycode, root.IPv6, root.SecondLevelDomains, root);
}
}(this, function (punycode, IPv6, SLD, root) {
'use strict';
/*global location, escape, unescape */
// FIXME: v2.0.0 renamce non-camelCase properties to uppercase
/*jshint camelcase: false */
// save current URI variable, if any
var _URI = root && root.URI;
function URI(url, base) {
var _urlSupplied = arguments.length >= 1;
var _baseSupplied = arguments.length >= 2;
// Allow instantiation without the 'new' keyword
if (!(this instanceof URI)) {
if (_urlSupplied) {
if (_baseSupplied) {
return new URI(url, base);
}
return new URI(url);
}
return new URI();
}
if (url === undefined) {
if (_urlSupplied) {
throw new TypeError('undefined is not a valid argument for URI');
}
if (typeof location !== 'undefined') {
url = location.href + '';
} else {
url = '';
}
}
if (url === null) {
if (_urlSupplied) {
throw new TypeError('null is not a valid argument for URI');
}
}
this.href(url);
// resolve to base according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor
if (base !== undefined) {
return this.absoluteTo(base);
}
return this;
}
function isInteger(value) {
return /^[0-9]+$/.test(value);
}
URI.version = '1.19.1';
var p = URI.prototype;
var hasOwn = Object.prototype.hasOwnProperty;
function escapeRegEx(string) {
// https://github.com/medialize/URI.js/commit/85ac21783c11f8ccab06106dba9735a31a86924d#commitcomment-821963
return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
}
function getType(value) {
// IE8 doesn't return [Object Undefined] but [Object Object] for undefined value
if (value === undefined) {
return 'Undefined';
}
return String(Object.prototype.toString.call(value)).slice(8, -1);
}
function isArray(obj) {
return getType(obj) === 'Array';
}
function filterArrayValues(data, value) {
var lookup = {};
var i, length;
if (getType(value) === 'RegExp') {
lookup = null;
} else if (isArray(value)) {
for (i = 0, length = value.length; i < length; i++) {
lookup[value[i]] = true;
}
} else {
lookup[value] = true;
}
for (i = 0, length = data.length; i < length; i++) {
/*jshint laxbreak: true */
var _match = lookup && lookup[data[i]] !== undefined
|| !lookup && value.test(data[i]);
/*jshint laxbreak: false */
if (_match) {
data.splice(i, 1);
length--;
i--;
}
}
return data;
}
function arrayContains(list, value) {
var i, length;
// value may be string, number, array, regexp
if (isArray(value)) {
// Note: this can be optimized to O(n) (instead of current O(m * n))
for (i = 0, length = value.length; i < length; i++) {
if (!arrayContains(list, value[i])) {
return false;
}
}
return true;
}
var _type = getType(value);
for (i = 0, length = list.length; i < length; i++) {
if (_type === 'RegExp') {
if (typeof list[i] === 'string' && list[i].match(value)) {
return true;
}
} else if (list[i] === value) {
return true;
}
}
return false;
}
function arraysEqual(one, two) {
if (!isArray(one) || !isArray(two)) {
return false;
}
// arrays can't be equal if they have different amount of content
if (one.length !== two.length) {
return false;
}
one.sort();
two.sort();
for (var i = 0, l = one.length; i < l; i++) {
if (one[i] !== two[i]) {
return false;
}
}
return true;
}
function trimSlashes(text) {
var trim_expression = /^\/+|\/+$/g;
return text.replace(trim_expression, '');
}
URI._parts = function() {
return {
protocol: null,
username: null,
password: null,
hostname: null,
urn: null,
port: null,
path: null,
query: null,
fragment: null,
// state
preventInvalidHostname: URI.preventInvalidHostname,
duplicateQueryParameters: URI.duplicateQueryParameters,
escapeQuerySpace: URI.escapeQuerySpace
};
};
// state: throw on invalid hostname
// see https://github.com/medialize/URI.js/pull/345
// and https://github.com/medialize/URI.js/issues/354
URI.preventInvalidHostname = false;
// state: allow duplicate query parameters (a=1&a=1)
URI.duplicateQueryParameters = false;
// state: replaces + with %20 (space in query strings)
URI.escapeQuerySpace = true;
// static properties
URI.protocol_expression = /^[a-z][a-z0-9.+-]*$/i;
URI.idn_expression = /[^a-z0-9\._-]/i;
URI.punycode_expression = /(xn--)/i;
// well, 333.444.555.666 matches, but it sure ain't no IPv4 - do we care?
URI.ip4_expression = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
// credits to Rich Brown
// source: http://forums.intermapper.com/viewtopic.php?p=1096#1096
// specification: http://www.ietf.org/rfc/rfc4291.txt
URI.ip6_expression = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/;
// expression used is "gruber revised" (@gruber v2) determined to be the
// best solution in a regex-golf we did a couple of ages ago at
// * http://mathiasbynens.be/demo/url-regex
// * http://rodneyrehm.de/t/url-regex.html
URI.find_uri_expression = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/ig;
URI.findUri = {
// valid "scheme://" or "www."
start: /\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi,
// everything up to the next whitespace
end: /[\s\r\n]|$/,
// trim trailing punctuation captured by end RegExp
trim: /[`!()\[\]{};:'".,<>?«»“”„‘’]+$/,
// balanced parens inclusion (), [], {}, <>
parens: /(\([^\)]*\)|\[[^\]]*\]|\{[^}]*\}|<[^>]*>)/g,
};
// http://www.iana.org/assignments/uri-schemes.html
// http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports
URI.defaultPorts = {
http: '80',
https: '443',
ftp: '21',
gopher: '70',
ws: '80',
wss: '443'
};
// list of protocols which always require a hostname
URI.hostProtocols = [
'http',
'https'
];
// allowed hostname characters according to RFC 3986
// ALPHA DIGIT "-" "." "_" "~" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" %encoded
// I've never seen a (non-IDN) hostname other than: ALPHA DIGIT . - _
URI.invalid_hostname_characters = /[^a-zA-Z0-9\.\-:_]/;
// map DOM Elements to their URI attribute
URI.domAttributes = {
'a': 'href',
'blockquote': 'cite',
'link': 'href',
'base': 'href',
'script': 'src',
'form': 'action',
'img': 'src',
'area': 'href',
'iframe': 'src',
'embed': 'src',
'source': 'src',
'track': 'src',
'input': 'src', // but only if type="image"
'audio': 'src',
'video': 'src'
};
URI.getDomAttribute = function(node) {
if (!node || !node.nodeName) {
return undefined;
}
var nodeName = node.nodeName.toLowerCase();
// <input> should only expose src for type="image"
if (nodeName === 'input' && node.type !== 'image') {
return undefined;
}
return URI.domAttributes[nodeName];
};
function escapeForDumbFirefox36(value) {
// https://github.com/medialize/URI.js/issues/91
return escape(value);
}
// encoding / decoding according to RFC3986
function strictEncodeURIComponent(string) {
// see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent
return encodeURIComponent(string)
.replace(/[!'()*]/g, escapeForDumbFirefox36)
.replace(/\*/g, '%2A');
}
URI.encode = strictEncodeURIComponent;
URI.decode = decodeURIComponent;
URI.iso8859 = function() {
URI.encode = escape;
URI.decode = unescape;
};
URI.unicode = function() {
URI.encode = strictEncodeURIComponent;
URI.decode = decodeURIComponent;
};
URI.characters = {
pathname: {
encode: {
// RFC3986 2.1: For consistency, URI producers and normalizers should
// use uppercase hexadecimal digits for all percent-encodings.
expression: /%(24|26|2B|2C|3B|3D|3A|40)/ig,
map: {
// -._~!'()*
'%24': '$',
'%26': '&',
'%2B': '+',
'%2C': ',',
'%3B': ';',
'%3D': '=',
'%3A': ':',
'%40': '@'
}
},
decode: {
expression: /[\/\?#]/g,
map: {
'/': '%2F',
'?': '%3F',
'#': '%23'
}
}
},
reserved: {
encode: {
// RFC3986 2.1: For consistency, URI producers and normalizers should
// use uppercase hexadecimal digits for all percent-encodings.
expression: /%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig,
map: {
// gen-delims
'%3A': ':',
'%2F': '/',
'%3F': '?',
'%23': '#',
'%5B': '[',
'%5D': ']',
'%40': '@',
// sub-delims
'%21': '!',
'%24': '$',
'%26': '&',
'%27': '\'',
'%28': '(',
'%29': ')',
'%2A': '*',
'%2B': '+',
'%2C': ',',
'%3B': ';',
'%3D': '='
}
}
},
urnpath: {
// The characters under `encode` are the characters called out by RFC 2141 as being acceptable
// for usage in a URN. RFC2141 also calls out "-", ".", and "_" as acceptable characters, but
// these aren't encoded by encodeURIComponent, so we don't have to call them out here. Also
// note that the colon character is not featured in the encoding map; this is because URI.js
// gives the colons in URNs semantic meaning as the delimiters of path segements, and so it
// should not appear unencoded in a segment itself.
// See also the note above about RFC3986 and capitalalized hex digits.
encode: {
expression: /%(21|24|27|28|29|2A|2B|2C|3B|3D|40)/ig,
map: {
'%21': '!',
'%24': '$',
'%27': '\'',
'%28': '(',
'%29': ')',
'%2A': '*',
'%2B': '+',
'%2C': ',',
'%3B': ';',
'%3D': '=',
'%40': '@'
}
},
// These characters are the characters called out by RFC2141 as "reserved" characters that
// should never appear in a URN, plus the colon character (see note above).
decode: {
expression: /[\/\?#:]/g,
map: {
'/': '%2F',
'?': '%3F',
'#': '%23',
':': '%3A'
}
}
}
};
URI.encodeQuery = function(string, escapeQuerySpace) {
var escaped = URI.encode(string + '');
if (escapeQuerySpace === undefined) {
escapeQuerySpace = URI.escapeQuerySpace;
}
return escapeQuerySpace ? escaped.replace(/%20/g, '+') : escaped;
};
URI.decodeQuery = function(string, escapeQuerySpace) {
string += '';
if (escapeQuerySpace === undefined) {
escapeQuerySpace = URI.escapeQuerySpace;
}
try {
return URI.decode(escapeQuerySpace ? string.replace(/\+/g, '%20') : string);
} catch(e) {
// we're not going to mess with weird encodings,
// give up and return the undecoded original string
// see https://github.com/medialize/URI.js/issues/87
// see https://github.com/medialize/URI.js/issues/92
return string;
}
};
// generate encode/decode path functions
var _parts = {'encode':'encode', 'decode':'decode'};
var _part;
var generateAccessor = function(_group, _part) {
return function(string) {
try {
return URI[_part](string + '').replace(URI.characters[_group][_part].expression, function(c) {
return URI.characters[_group][_part].map[c];
});
} catch (e) {
// we're not going to mess with weird encodings,
// give up and return the undecoded original string
// see https://github.com/medialize/URI.js/issues/87
// see https://github.com/medialize/URI.js/issues/92
return string;
}
};
};
for (_part in _parts) {
URI[_part + 'PathSegment'] = generateAccessor('pathname', _parts[_part]);
URI[_part + 'UrnPathSegment'] = generateAccessor('urnpath', _parts[_part]);
}
var generateSegmentedPathFunction = function(_sep, _codingFuncName, _innerCodingFuncName) {
return function(string) {
// Why pass in names of functions, rather than the function objects themselves? The
// definitions of some functions (but in particular, URI.decode) will occasionally change due
// to URI.js having ISO8859 and Unicode modes. Passing in the name and getting it will ensure
// that the functions we use here are "fresh".
var actualCodingFunc;
if (!_innerCodingFuncName) {
actualCodingFunc = URI[_codingFuncName];
} else {
actualCodingFunc = function(string) {
return URI[_codingFuncName](URI[_innerCodingFuncName](string));
};
}
var segments = (string + '').split(_sep);
for (var i = 0, length = segments.length; i < length; i++) {
segments[i] = actualCodingFunc(segments[i]);
}
return segments.join(_sep);
};
};
// This takes place outside the above loop because we don't want, e.g., encodeUrnPath functions.
URI.decodePath = generateSegmentedPathFunction('/', 'decodePathSegment');
URI.decodeUrnPath = generateSegmentedPathFunction(':', 'decodeUrnPathSegment');
URI.recodePath = generateSegmentedPathFunction('/', 'encodePathSegment', 'decode');
URI.recodeUrnPath = generateSegmentedPathFunction(':', 'encodeUrnPathSegment', 'decode');
URI.encodeReserved = generateAccessor('reserved', 'encode');
URI.parse = function(string, parts) {
var pos;
if (!parts) {
parts = {
preventInvalidHostname: URI.preventInvalidHostname
};
}
// [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment]
// extract fragment
pos = string.indexOf('#');
if (pos > -1) {
// escaping?
parts.fragment = string.substring(pos + 1) || null;
string = string.substring(0, pos);
}
// extract query
pos = string.indexOf('?');
if (pos > -1) {
// escaping?
parts.query = string.substring(pos + 1) || null;
string = string.substring(0, pos);
}
// extract protocol
if (string.substring(0, 2) === '//') {
// relative-scheme
parts.protocol = null;
string = string.substring(2);
// extract "user:pass@host:port"
string = URI.parseAuthority(string, parts);
} else {
pos = string.indexOf(':');
if (pos > -1) {
parts.protocol = string.substring(0, pos) || null;
if (parts.protocol && !parts.protocol.match(URI.protocol_expression)) {
// : may be within the path
parts.protocol = undefined;
} else if (string.substring(pos + 1, pos + 3) === '//') {
string = string.substring(pos + 3);
// extract "user:pass@host:port"
string = URI.parseAuthority(string, parts);
} else {
string = string.substring(pos + 1);
parts.urn = true;
}
}
}
// what's left must be the path
parts.path = string;
// and we're done
return parts;
};
URI.parseHost = function(string, parts) {
if (!string) {
string = '';
}
// Copy chrome, IE, opera backslash-handling behavior.
// Back slashes before the query string get converted to forward slashes
// See: https://github.com/joyent/node/blob/386fd24f49b0e9d1a8a076592a404168faeecc34/lib/url.js#L115-L124
// See: https://code.google.com/p/chromium/issues/detail?id=25916
// https://github.com/medialize/URI.js/pull/233
string = string.replace(/\\/g, '/');
// extract host:port
var pos = string.indexOf('/');
var bracketPos;
var t;
if (pos === -1) {
pos = string.length;
}
if (string.charAt(0) === '[') {
// IPv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6
// I claim most client software breaks on IPv6 anyways. To simplify things, URI only accepts
// IPv6+port in the format [2001:db8::1]:80 (for the time being)
bracketPos = string.indexOf(']');
parts.hostname = string.substring(1, bracketPos) || null;
parts.port = string.substring(bracketPos + 2, pos) || null;
if (parts.port === '/') {
parts.port = null;
}
} else {
var firstColon = string.indexOf(':');
var firstSlash = string.indexOf('/');
var nextColon = string.indexOf(':', firstColon + 1);
if (nextColon !== -1 && (firstSlash === -1 || nextColon < firstSlash)) {
// IPv6 host contains multiple colons - but no port
// this notation is actually not allowed by RFC 3986, but we're a liberal parser
parts.hostname = string.substring(0, pos) || null;
parts.port = null;
} else {
t = string.substring(0, pos).split(':');
parts.hostname = t[0] || null;
parts.port = t[1] || null;
}
}
if (parts.hostname && string.substring(pos).charAt(0) !== '/') {
pos++;
string = '/' + string;
}
if (parts.preventInvalidHostname) {
URI.ensureValidHostname(parts.hostname, parts.protocol);
}
if (parts.port) {
URI.ensureValidPort(parts.port);
}
return string.substring(pos) || '/';
};
URI.parseAuthority = function(string, parts) {
string = URI.parseUserinfo(string, parts);
return URI.parseHost(string, parts);
};
URI.parseUserinfo = function(string, parts) {
// extract username:password
var firstSlash = string.indexOf('/');
var pos = string.lastIndexOf('@', firstSlash > -1 ? firstSlash : string.length - 1);
var t;
// authority@ must come before /path
if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) {
t = string.substring(0, pos).split(':');
parts.username = t[0] ? URI.decode(t[0]) : null;
t.shift();
parts.password = t[0] ? URI.decode(t.join(':')) : null;
string = string.substring(pos + 1);
} else {
parts.username = null;
parts.password = null;
}
return string;
};
URI.parseQuery = function(string, escapeQuerySpace) {
if (!string) {
return {};
}
// throw out the funky business - "?"[name"="value"&"]+
string = string.replace(/&+/g, '&').replace(/^\?*&*|&+$/g, '');
if (!string) {
return {};
}
var items = {};
var splits = string.split('&');
var length = splits.length;
var v, name, value;
for (var i = 0; i < length; i++) {
v = splits[i].split('=');
name = URI.decodeQuery(v.shift(), escapeQuerySpace);
// no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters
value = v.length ? URI.decodeQuery(v.join('='), escapeQuerySpace) : null;
if (hasOwn.call(items, name)) {
if (typeof items[name] === 'string' || items[name] === null) {
items[name] = [items[name]];
}
items[name].push(value);
} else {
items[name] = value;
}
}
return items;
};
URI.build = function(parts) {
var t = '';
if (parts.protocol) {
t += parts.protocol + ':';
}
if (!parts.urn && (t || parts.hostname)) {
t += '//';
}
t += (URI.buildAuthority(parts) || '');
if (typeof parts.path === 'string') {
if (parts.path.charAt(0) !== '/' && typeof parts.hostname === 'string') {
t += '/';
}
t += parts.path;
}
if (typeof parts.query === 'string' && parts.query) {
t += '?' + parts.query;
}
if (typeof parts.fragment === 'string' && parts.fragment) {
t += '#' + parts.fragment;
}
return t;
};
URI.buildHost = function(parts) {
var t = '';
if (!parts.hostname) {
return '';
} else if (URI.ip6_expression.test(parts.hostname)) {
t += '[' + parts.hostname + ']';
} else {
t += parts.hostname;
}
if (parts.port) {
t += ':' + parts.port;
}
return t;
};
URI.buildAuthority = function(parts) {
return URI.buildUserinfo(parts) + URI.buildHost(parts);
};
URI.buildUserinfo = function(parts) {
var t = '';
if (parts.username) {
t += URI.encode(parts.username);
}
if (parts.password) {
t += ':' + URI.encode(parts.password);
}
if (t) {
t += '@';
}
return t;
};
URI.buildQuery = function(data, duplicateQueryParameters, escapeQuerySpace) {
// according to http://tools.ietf.org/html/rfc3986 or http://labs.apache.org/webarch/uri/rfc/rfc3986.html
// being »-._~!$&'()*+,;=:@/?« %HEX and alnum are allowed
// the RFC explicitly states ?/foo being a valid use case, no mention of parameter syntax!
// URI.js treats the query string as being application/x-www-form-urlencoded
// see http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type
var t = '';
var unique, key, i, length;
for (key in data) {
if (hasOwn.call(data, key) && key) {
if (isArray(data[key])) {
unique = {};
for (i = 0, length = data[key].length; i < length; i++) {
if (data[key][i] !== undefined && unique[data[key][i] + ''] === undefined) {
t += '&' + URI.buildQueryParameter(key, data[key][i], escapeQuerySpace);
if (duplicateQueryParameters !== true) {
unique[data[key][i] + ''] = true;
}
}
}
} else if (data[key] !== undefined) {
t += '&' + URI.buildQueryParameter(key, data[key], escapeQuerySpace);
}
}
}
return t.substring(1);
};
URI.buildQueryParameter = function(name, value, escapeQuerySpace) {
// http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type -- application/x-www-form-urlencoded
// don't append "=" for null values, according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#url-parameter-serialization
return URI.encodeQuery(name, escapeQuerySpace) + (value !== null ? '=' + URI.encodeQuery(value, escapeQuerySpace) : '');
};
URI.addQuery = function(data, name, value) {
if (typeof name === 'object') {
for (var key in name) {
if (hasOwn.call(name, key)) {
URI.addQuery(data, key, name[key]);
}
}
} else if (typeof name === 'string') {
if (data[name] === undefined) {
data[name] = value;
return;
} else if (typeof data[name] === 'string') {
data[name] = [data[name]];
}
if (!isArray(value)) {
value = [value];
}
data[name] = (data[name] || []).concat(value);
} else {
throw new TypeError('URI.addQuery() accepts an object, string as the name parameter');
}
};
URI.setQuery = function(data, name, value) {
if (typeof name === 'object') {
for (var key in name) {
if (hasOwn.call(name, key)) {
URI.setQuery(data, key, name[key]);
}
}
} else if (typeof name === 'string') {
data[name] = value === undefined ? null : value;
} else {
throw new TypeError('URI.setQuery() accepts an object, string as the name parameter');
}
};
URI.removeQuery = function(data, name, value) {
var i, length, key;
if (isArray(name)) {
for (i = 0, length = name.length; i < length; i++) {
data[name[i]] = undefined;
}
} else if (getType(name) === 'RegExp') {
for (key in data) {
if (name.test(key)) {
data[key] = undefined;
}
}
} else if (typeof name === 'object') {
for (key in name) {
if (hasOwn.call(name, key)) {
URI.removeQuery(data, key, name[key]);
}
}
} else if (typeof name === 'string') {
if (value !== undefined) {
if (getType(value) === 'RegExp') {
if (!isArray(data[name]) && value.test(data[name])) {
data[name] = undefined;
} else {
data[name] = filterArrayValues(data[name], value);
}
} else if (data[name] === String(value) && (!isArray(value) || value.length === 1)) {
data[name] = undefined;
} else if (isArray(data[name])) {
data[name] = filterArrayValues(data[name], value);
}
} else {
data[name] = undefined;
}
} else {
throw new TypeError('URI.removeQuery() accepts an object, string, RegExp as the first parameter');
}
};
URI.hasQuery = function(data, name, value, withinArray) {
switch (getType(name)) {
case 'String':
// Nothing to do here
break;
case 'RegExp':
for (var key in data) {
if (hasOwn.call(data, key)) {
if (name.test(key) && (value === undefined || URI.hasQuery(data, key, value))) {
return true;
}
}
}
return false;
case 'Object':
for (var _key in name) {
if (hasOwn.call(name, _key)) {
if (!URI.hasQuery(data, _key, name[_key])) {
return false;
}
}
}
return true;
default:
throw new TypeError('URI.hasQuery() accepts a string, regular expression or object as the name parameter');
}
switch (getType(value)) {
case 'Undefined':
// true if exists (but may be empty)
return name in data; // data[name] !== undefined;
case 'Boolean':
// true if exists and non-empty
var _booly = Boolean(isArray(data[name]) ? data[name].length : data[name]);
return value === _booly;
case 'Function':
// allow complex comparison
return !!value(data[name], name, data);
case 'Array':
if (!isArray(data[name])) {
return false;
}
var op = withinArray ? arrayContains : arraysEqual;
return op(data[name], value);
case 'RegExp':
if (!isArray(data[name])) {
return Boolean(data[name] && data[name].match(value));
}
if (!withinArray) {
return false;
}
return arrayContains(data[name], value);
case 'Number':
value = String(value);
/* falls through */
case 'String':
if (!isArray(data[name])) {
return data[name] === value;
}
if (!withinArray) {
return false;
}
return arrayContains(data[name], value);
default:
throw new TypeError('URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter');
}
};
URI.joinPaths = function() {
var input = [];
var segments = [];
var nonEmptySegments = 0;
for (var i = 0; i < arguments.length; i++) {
var url = new URI(arguments[i]);
input.push(url);
var _segments = url.segment();
for (var s = 0; s < _segments.length; s++) {
if (typeof _segments[s] === 'string') {
segments.push(_segments[s]);
}
if (_segments[s]) {
nonEmptySegments++;
}
}
}
if (!segments.length || !nonEmptySegments) {
return new URI('');
}
var uri = new URI('').segment(segments);
if (input[0].path() === '' || input[0].path().slice(0, 1) === '/') {
uri.path('/' + uri.path());
}
return uri.normalize();
};
URI.commonPath = function(one, two) {
var length = Math.min(one.length, two.length);
var pos;
// find first non-matching character
for (pos = 0; pos < length; pos++) {
if (one.charAt(pos) !== two.charAt(pos)) {
pos--;
break;
}
}
if (pos < 1) {
return one.charAt(0) === two.charAt(0) && one.charAt(0) === '/' ? '/' : '';
}
// revert to last /
if (one.charAt(pos) !== '/' || two.charAt(pos) !== '/') {
pos = one.substring(0, pos).lastIndexOf('/');
}
return one.substring(0, pos + 1);
};
URI.withinString = function(string, callback, options) {
options || (options = {});
var _start = options.start || URI.findUri.start;
var _end = options.end || URI.findUri.end;
var _trim = options.trim || URI.findUri.trim;
var _parens = options.parens || URI.findUri.parens;
var _attributeOpen = /[a-z0-9-]=["']?$/i;
_start.lastIndex = 0;
while (true) {
var match = _start.exec(string);
if (!match) {
break;
}
var start = match.index;
if (options.ignoreHtml) {
// attribut(e=["']?$)
var attributeOpen = string.slice(Math.max(start - 3, 0), start);
if (attributeOpen && _attributeOpen.test(attributeOpen)) {
continue;
}
}
var end = start + string.slice(start).search(_end);
var slice = string.slice(start, end);
// make sure we include well balanced parens
var parensEnd = -1;
while (true) {
var parensMatch = _parens.exec(slice);
if (!parensMatch) {
break;
}
var parensMatchEnd = parensMatch.index + parensMatch[0].length;
parensEnd = Math.max(parensEnd, parensMatchEnd);
}
if (parensEnd > -1) {
slice = slice.slice(0, parensEnd) + slice.slice(parensEnd).replace(_trim, '');
} else {
slice = slice.replace(_trim, '');
}
if (slice.length <= match[0].length) {
// the extract only contains the starting marker of a URI,
// e.g. "www" or "http://"
continue;
}
if (options.ignore && options.ignore.test(slice)) {
continue;
}
end = start + slice.length;
var result = callback(slice, start, end, string);
if (result === undefined) {
_start.lastIndex = end;
continue;
}
result = String(result);
string = string.slice(0, start) + result + string.slice(end);
_start.lastIndex = start + result.length;
}
_start.lastIndex = 0;
return string;
};
URI.ensureValidHostname = function(v, protocol) {
// Theoretically URIs allow percent-encoding in Hostnames (according to RFC 3986)
// they are not part of DNS and therefore ignored by URI.js
var hasHostname = !!v; // not null and not an empty string
var hasProtocol = !!protocol;
var rejectEmptyHostname = false;
if (hasProtocol) {
rejectEmptyHostname = arrayContains(URI.hostProtocols, protocol);
}
if (rejectEmptyHostname && !hasHostname) {
throw new TypeError('Hostname cannot be empty, if protocol is ' + protocol);
} else if (v && v.match(URI.invalid_hostname_characters)) {
// test punycode
if (!punycode) {
throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-:_] and Punycode.js is not available');
}
if (punycode.toASCII(v).match(URI.invalid_hostname_characters)) {
throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-:_]');
}
}
};
URI.ensureValidPort = function (v) {
if (!v) {
return;
}
var port = Number(v);
if (isInteger(port) && (port > 0) && (port < 65536)) {
return;
}
throw new TypeError('Port "' + v + '" is not a valid port');
};
// noConflict
URI.noConflict = function(removeAll) {
if (removeAll) {
var unconflicted = {
URI: this.noConflict()
};
if (root.URITemplate && typeof root.URITemplate.noConflict === 'function') {
unconflicted.URITemplate = root.URITemplate.noConflict();
}
if (root.IPv6 && typeof root.IPv6.noConflict === 'function') {
unconflicted.IPv6 = root.IPv6.noConflict();
}
if (root.SecondLevelDomains && typeof root.SecondLevelDomains.noConflict === 'function') {
unconflicted.SecondLevelDomains = root.SecondLevelDomains.noConflict();
}
return unconflicted;
} else if (root.URI === this) {
root.URI = _URI;
}
return this;
};
p.build = function(deferBuild) {
if (deferBuild === true) {
this._deferred_build = true;
} else if (deferBuild === undefined || this._deferred_build) {
this._string = URI.build(this._parts);
this._deferred_build = false;
}
return this;
};
p.clone = function() {
return new URI(this);
};
p.valueOf = p.toString = function() {
return this.build(false)._string;
};
function generateSimpleAccessor(_part){
return function(v, build) {
if (v === undefined) {
return this._parts[_part] || '';
} else {
this._parts[_part] = v || null;
this.build(!build);
return this;
}
};
}
function generatePrefixAccessor(_part, _key){
return function(v, build) {
if (v === undefined) {
return this._parts[_part] || '';
} else {
if (v !== null) {
v = v + '';
if (v.charAt(0) === _key) {
v = v.substring(1);
}
}
this._parts[_part] = v;
this.build(!build);
return this;
}
};
}
p.protocol = generateSimpleAccessor('protocol');
p.username = generateSimpleAccessor('username');
p.password = generateSimpleAccessor('password');
p.hostname = generateSimpleAccessor('hostname');
p.port = generateSimpleAccessor('port');
p.query = generatePrefixAccessor('query', '?');
p.fragment = generatePrefixAccessor('fragment', '#');
p.search = function(v, build) {
var t = this.query(v, build);
return typeof t === 'string' && t.length ? ('?' + t) : t;
};
p.hash = function(v, build) {
var t = this.fragment(v, build);
return typeof t === 'string' && t.length ? ('#' + t) : t;
};
p.pathname = function(v, build) {
if (v === undefined || v === true) {
var res = this._parts.path || (this._parts.hostname ? '/' : '');
return v ? (this._parts.urn ? URI.decodeUrnPath : URI.decodePath)(res) : res;
} else {
if (this._parts.urn) {
this._parts.path = v ? URI.recodeUrnPath(v) : '';
} else {
this._parts.path = v ? URI.recodePath(v) : '/';
}
this.build(!build);
return this;
}
};
p.path = p.pathname;
p.href = function(href, build) {
var key;
if (href === undefined) {
return this.toString();
}
this._string = '';
this._parts = URI._parts();
var _URI = href instanceof URI;
var _object = typeof href === 'object' && (href.hostname || href.path || href.pathname);
if (href.nodeName) {
var attribute = URI.getDomAttribute(href);
href = href[attribute] || '';
_object = false;
}
// window.location is reported to be an object, but it's not the sort
// of object we're looking for:
// * location.protocol ends with a colon
// * location.query != object.search
// * location.hash != object.fragment
// simply serializing the unknown object should do the trick
// (for location, not for everything...)
if (!_URI && _object && href.pathname !== undefined) {
href = href.toString();
}
if (typeof href === 'string' || href instanceof String) {
this._parts = URI.parse(String(href), this._parts);
} else if (_URI || _object) {
var src = _URI ? href._parts : href;
for (key in src) {
if (key === 'query') { continue; }
if (hasOwn.call(this._parts, key)) {
this._parts[key] = src[key];
}
}
if (src.query) {
this.query(src.query, false);
}
} else {
throw new TypeError('invalid input');
}
this.build(!build);
return this;
};
// identification accessors
p.is = function(what) {
var ip = false;
var ip4 = false;
var ip6 = false;
var name = false;
var sld = false;
var idn = false;
var punycode = false;
var relative = !this._parts.urn;
if (this._parts.hostname) {
relative = false;
ip4 = URI.ip4_expression.test(this._parts.hostname);
ip6 = URI.ip6_expression.test(this._parts.hostname);
ip = ip4 || ip6;
name = !ip;
sld = name && SLD && SLD.has(this._parts.hostname);
idn = name && URI.idn_expression.test(this._parts.hostname);
punycode = name && URI.punycode_expression.test(this._parts.hostname);
}
switch (what.toLowerCase()) {
case 'relative':
return relative;
case 'absolute':
return !relative;
// hostname identification
case 'domain':
case 'name':
return name;
case 'sld':
return sld;
case 'ip':
return ip;
case 'ip4':
case 'ipv4':
case 'inet4':
return ip4;
case 'ip6':
case 'ipv6':
case 'inet6':
return ip6;
case 'idn':
return idn;
case 'url':
return !this._parts.urn;
case 'urn':
return !!this._parts.urn;
case 'punycode':
return punycode;
}
return null;
};
// component specific input validation
var _protocol = p.protocol;
var _port = p.port;
var _hostname = p.hostname;
p.protocol = function(v, build) {
if (v) {
// accept trailing ://
v = v.replace(/:(\/\/)?$/, '');
if (!v.match(URI.protocol_expression)) {
throw new TypeError('Protocol "' + v + '" contains characters other than [A-Z0-9.+-] or doesn\'t start with [A-Z]');
}
}
return _protocol.call(this, v, build);
};
p.scheme = p.protocol;
p.port = function(v, build) {
if (this._parts.urn) {
return v === undefined ? '' : this;
}
if (v !== undefined) {
if (v === 0) {
v = null;
}
if (v) {
v += '';
if (v.charAt(0) === ':') {
v = v.substring(1);
}
URI.ensureValidPort(v);
}
}
return _port.call(this, v, build);
};
p.hostname = function(v, build) {
if (this._parts.urn) {
return v === undefined ? '' : this;
}
if (v !== undefined) {
var x = { preventInvalidHostname: this._parts.preventInvalidHostname };
var res = URI.parseHost(v, x);
if (res !== '/') {
throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
}
v = x.hostname;
if (this._parts.preventInvalidHostname) {
URI.ensureValidHostname(v, this._parts.protocol);
}
}
return _hostname.call(this, v, build);
};
// compound accessors
p.origin = function(v, build) {
if (this._parts.urn) {
return v === undefined ? '' : this;
}
if (v === undefined) {
var protocol = this.protocol();
var authority = this.authority();
if (!authority) {
return '';
}
return (protocol ? protocol + '://' : '') + this.authority();
} else {
var origin = URI(v);
this
.protocol(origin.protocol())
.authority(origin.authority())
.build(!build);
return this;
}
};
p.host = function(v, build) {
if (this._parts.urn) {
return v === undefined ? '' : this;
}
if (v === undefined) {
return this._parts.hostname ? URI.buildHost(this._parts) : '';
} else {
var res = URI.parseHost(v, this._parts);
if (res !== '/') {
throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
}
this.build(!build);
return this;
}
};
p.authority = function(v, build) {
if (this._parts.urn) {
return v === undefined ? '' : this;
}
if (v === undefined) {
return this._parts.hostname ? URI.buildAuthority(this._parts) : '';
} else {
var res = URI.parseAuthority(v, this._parts);
if (res !== '/') {
throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
}
this.build(!build);
return this;
}
};
p.userinfo = function(v, build) {
if (this._parts.urn) {
return v === undefined ? '' : this;
}
if (v === undefined) {
var t = URI.buildUserinfo(this._parts);
return t ? t.substring(0, t.length -1) : t;
} else {
if (v[v.length-1] !== '@') {
v += '@';
}
URI.parseUserinfo(v, this._parts);
this.build(!build);
return this;
}
};
p.resource = function(v, build) {
var parts;
if (v === undefined) {
return this.path() + this.search() + this.hash();
}
parts = URI.parse(v);
this._parts.path = parts.path;
this._parts.query = parts.query;
this._parts.fragment = parts.fragment;
this.build(!build);
return this;
};
// fraction accessors
p.subdomain = function(v, build) {
if (this._parts.urn) {
return v === undefined ? '' : this;
}
// convenience, return "www" from "www.example.org"
if (v === undefined) {
if (!this._parts.hostname || this.is('IP')) {
return '';
}
// grab domain and add another segment
var end = this._parts.hostname.length - this.domain().length - 1;
return this._parts.hostname.substring(0, end) || '';
} else {
var e = this._parts.hostname.length - this.domain().length;
var sub = this._parts.hostname.substring(0, e);
var replace = new RegExp('^' + escapeRegEx(sub));
if (v && v.charAt(v.length - 1) !== '.') {
v += '.';
}
if (v.indexOf(':') !== -1) {
throw new TypeError('Domains cannot contain colons');
}
if (v) {
URI.ensureValidHostname(v, this._parts.protocol);
}
this._parts.hostname = this._parts.hostname.replace(replace, v);
this.build(!build);
return this;
}
};
p.domain = function(v, build) {
if (this._parts.urn) {
return v === undefined ? '' : this;
}
if (typeof v === 'boolean') {
build = v;
v = undefined;
}
// convenience, return "example.org" from "www.example.org"
if (v === undefined) {
if (!this._parts.hostname || this.is('IP')) {
return '';
}
// if hostname consists of 1 or 2 segments, it must be the domain
var t = this._parts.hostname.match(/\./g);
if (t && t.length < 2) {
return this._parts.hostname;
}
// grab tld and add another segment
var end = this._parts.hostname.length - this.tld(build).length - 1;
end = this._parts.hostname.lastIndexOf('.', end -1) + 1;
return this._parts.hostname.substring(end) || '';
} else {
if (!v) {
throw new TypeError('cannot set domain empty');
}
if (v.indexOf(':') !== -1) {
throw new TypeError('Domains cannot contain colons');
}
URI.ensureValidHostname(v, this._parts.protocol);
if (!this._parts.hostname || this.is('IP')) {
this._parts.hostname = v;
} else {
var replace = new RegExp(escapeRegEx(this.domain()) + '$');
this._parts.hostname = this._parts.hostname.replace(replace, v);
}
this.build(!build);
return this;
}
};
p.tld = function(v, build) {
if (this._parts.urn) {
return v === undefined ? '' : this;
}
if (typeof v === 'boolean') {
build = v;
v = undefined;
}
// return "org" from "www.example.org"
if (v === undefined) {
if (!this._parts.hostname || this.is('IP')) {
return '';
}
var pos = this._parts.hostname.lastIndexOf('.');
var tld = this._parts.hostname.substring(pos + 1);
if (build !== true && SLD && SLD.list[tld.toLowerCase()]) {
return SLD.get(this._parts.hostname) || tld;
}
return tld;
} else {
var replace;
if (!v) {
throw new TypeError('cannot set TLD empty');
} else if (v.match(/[^a-zA-Z0-9-]/)) {
if (SLD && SLD.is(v)) {
replace = new RegExp(escapeRegEx(this.tld()) + '$');
this._parts.hostname = this._parts.hostname.replace(replace, v);
} else {
throw new TypeError('TLD "' + v + '" contains characters other than [A-Z0-9]');
}
} else if (!this._parts.hostname || this.is('IP')) {
throw new ReferenceError('cannot set TLD on non-domain host');
} else {
replace = new RegExp(escapeRegEx(this.tld()) + '$');
this._parts.hostname = this._parts.hostname.replace(replace, v);
}
this.build(!build);
return this;
}
};
p.directory = function(v, build) {
if (this._parts.urn) {
return v === undefined ? '' : this;
}
if (v === undefined || v === true) {
if (!this._parts.path && !this._parts.hostname) {
return '';
}
if (this._parts.path === '/') {
return '/';
}
var end = this._parts.path.length - this.filename().length - 1;
var res = this._parts.path.substring(0, end) || (this._parts.hostname ? '/' : '');
return v ? URI.decodePath(res) : res;
} else {
var e = this._parts.path.length - this.filename().length;
var directory = this._parts.path.substring(0, e);
var replace = new RegExp('^' + escapeRegEx(directory));
// fully qualifier directories begin with a slash
if (!this.is('relative')) {
if (!v) {
v = '/';
}
if (v.charAt(0) !== '/') {
v = '/' + v;
}
}
// directories always end with a slash
if (v && v.charAt(v.length - 1) !== '/') {
v += '/';
}
v = URI.recodePath(v);
this._parts.path = this._parts.path.replace(replace, v);
this.build(!build);
return this;
}
};
p.filename = function(v, build) {
if (this._parts.urn) {
return v === undefined ? '' : this;
}
if (typeof v !== 'string') {
if (!this._parts.path || this._parts.path === '/') {
return '';
}
var pos = this._parts.path.lastIndexOf('/');
var res = this._parts.path.substring(pos+1);
return v ? URI.decodePathSegment(res) : res;
} else {
var mutatedDirectory = false;
if (v.charAt(0) === '/') {
v = v.substring(1);
}
if (v.match(/\.?\//)) {
mutatedDirectory = true;
}
var replace = new RegExp(escapeRegEx(this.filename()) + '$');
v = URI.recodePath(v);
this._parts.path = this._parts.path.replace(replace, v);
if (mutatedDirectory) {
this.normalizePath(build);
} else {
this.build(!build);
}
return this;
}
};
p.suffix = function(v, build) {
if (this._parts.urn) {
return v === undefined ? '' : this;
}
if (v === undefined || v === true) {
if (!this._parts.path || this._parts.path === '/') {
return '';
}
var filename = this.filename();
var pos = filename.lastIndexOf('.');
var s, res;
if (pos === -1) {
return '';
}
// suffix may only contain alnum characters (yup, I made this up.)
s = filename.substring(pos+1);
res = (/^[a-z0-9%]+$/i).test(s) ? s : '';
return v ? URI.decodePathSegment(res) : res;
} else {
if (v.charAt(0) === '.') {
v = v.substring(1);
}
var suffix = this.suffix();
var replace;
if (!suffix) {
if (!v) {
return this;
}
this._parts.path += '.' + URI.recodePath(v);
} else if (!v) {
replace = new RegExp(escapeRegEx('.' + suffix) + '$');
} else {
replace = new RegExp(escapeRegEx(suffix) + '$');
}
if (replace) {
v = URI.recodePath(v);
this._parts.path = this._parts.path.replace(replace, v);
}
this.build(!build);
return this;
}
};
p.segment = function(segment, v, build) {
var separator = this._parts.urn ? ':' : '/';
var path = this.path();
var absolute = path.substring(0, 1) === '/';
var segments = path.split(separator);
if (segment !== undefined && typeof segment !== 'number') {
build = v;
v = segment;
segment = undefined;
}
if (segment !== undefined && typeof segment !== 'number') {
throw new Error('Bad segment "' + segment + '", must be 0-based integer');
}
if (absolute) {
segments.shift();
}
if (segment < 0) {
// allow negative indexes to address from the end
segment = Math.max(segments.length + segment, 0);
}
if (v === undefined) {
/*jshint laxbreak: true */
return segment === undefined
? segments
: segments[segment];
/*jshint laxbreak: false */
} else if (segment === null || segments[segment] === undefined) {
if (isArray(v)) {
segments = [];
// collapse empty elements within array
for (var i=0, l=v.length; i < l; i++) {
if (!v[i].length && (!segments.length || !segments[segments.length -1].length)) {
continue;
}
if (segments.length && !segments[segments.length -1].length) {
segments.pop();
}
segments.push(trimSlashes(v[i]));
}
} else if (v || typeof v === 'string') {
v = trimSlashes(v);
if (segments[segments.length -1] === '') {
// empty trailing elements have to be overwritten
// to prevent results such as /foo//bar
segments[segments.length -1] = v;
} else {
segments.push(v);
}
}
} else {
if (v) {
segments[segment] = trimSlashes(v);
} else {
segments.splice(segment, 1);
}
}
if (absolute) {
segments.unshift('');
}
return this.path(segments.join(separator), build);
};
p.segmentCoded = function(segment, v, build) {
var segments, i, l;
if (typeof segment !== 'number') {
build = v;
v = segment;
segment = undefined;
}
if (v === undefined) {
segments = this.segment(segment, v, build);
if (!isArray(segments)) {
segments = segments !== undefined ? URI.decode(segments) : undefined;
} else {
for (i = 0, l = segments.length; i < l; i++) {
segments[i] = URI.decode(segments[i]);
}
}
return segments;
}
if (!isArray(v)) {
v = (typeof v === 'string' || v instanceof String) ? URI.encode(v) : v;
} else {
for (i = 0, l = v.length; i < l; i++) {
v[i] = URI.encode(v[i]);
}
}
return this.segment(segment, v, build);
};
// mutating query string
var q = p.query;
p.query = function(v, build) {
if (v === true) {
return URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
} else if (typeof v === 'function') {
var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
var result = v.call(this, data);
this._parts.query = URI.buildQuery(result || data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
this.build(!build);
return this;
} else if (v !== undefined && typeof v !== 'string') {
this._parts.query = URI.buildQuery(v, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
this.build(!build);
return this;
} else {
return q.call(this, v, build);
}
};
p.setQuery = function(name, value, build) {
var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
if (typeof name === 'string' || name instanceof String) {
data[name] = value !== undefined ? value : null;
} else if (typeof name === 'object') {
for (var key in name) {
if (hasOwn.call(name, key)) {
data[key] = name[key];
}
}
} else {
throw new TypeError('URI.addQuery() accepts an object, string as the name parameter');
}
this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
if (typeof name !== 'string') {
build = value;
}
this.build(!build);
return this;
};
p.addQuery = function(name, value, build) {
var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
URI.addQuery(data, name, value === undefined ? null : value);
this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
if (typeof name !== 'string') {
build = value;
}
this.build(!build);
return this;
};
p.removeQuery = function(name, value, build) {
var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
URI.removeQuery(data, name, value);
this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
if (typeof name !== 'string') {
build = value;
}
this.build(!build);
return this;
};
p.hasQuery = function(name, value, withinArray) {
var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
return URI.hasQuery(data, name, value, withinArray);
};
p.setSearch = p.setQuery;
p.addSearch = p.addQuery;
p.removeSearch = p.removeQuery;
p.hasSearch = p.hasQuery;
// sanitizing URLs
p.normalize = function() {
if (this._parts.urn) {
return this
.normalizeProtocol(false)
.normalizePath(false)
.normalizeQuery(false)
.normalizeFragment(false)
.build();
}
return this
.normalizeProtocol(false)
.normalizeHostname(false)
.normalizePort(false)
.normalizePath(false)
.normalizeQuery(false)
.normalizeFragment(false)
.build();
};
p.normalizeProtocol = function(build) {
if (typeof this._parts.protocol === 'string') {
this._parts.protocol = this._parts.protocol.toLowerCase();
this.build(!build);
}
return this;
};
p.normalizeHostname = function(build) {
if (this._parts.hostname) {
if (this.is('IDN') && punycode) {
this._parts.hostname = punycode.toASCII(this._parts.hostname);
} else if (this.is('IPv6') && IPv6) {
this._parts.hostname = IPv6.best(this._parts.hostname);
}
this._parts.hostname = this._parts.hostname.toLowerCase();
this.build(!build);
}
return this;
};
p.normalizePort = function(build) {
// remove port of it's the protocol's default
if (typeof this._parts.protocol === 'string' && this._parts.port === URI.defaultPorts[this._parts.protocol]) {
this._parts.port = null;
this.build(!build);
}
return this;
};
p.normalizePath = function(build) {
var _path = this._parts.path;
if (!_path) {
return this;
}
if (this._parts.urn) {
this._parts.path = URI.recodeUrnPath(this._parts.path);
this.build(!build);
return this;
}
if (this._parts.path === '/') {
return this;
}
_path = URI.recodePath(_path);
var _was_relative;
var _leadingParents = '';
var _parent, _pos;
// handle relative paths
if (_path.charAt(0) !== '/') {
_was_relative = true;
_path = '/' + _path;
}
// handle relative files (as opposed to directories)
if (_path.slice(-3) === '/..' || _path.slice(-2) === '/.') {
_path += '/';
}
// resolve simples
_path = _path
.replace(/(\/(\.\/)+)|(\/\.$)/g, '/')
.replace(/\/{2,}/g, '/');
// remember leading parents
if (_was_relative) {
_leadingParents = _path.substring(1).match(/^(\.\.\/)+/) || '';
if (_leadingParents) {
_leadingParents = _leadingParents[0];
}
}
// resolve parents
while (true) {
_parent = _path.search(/\/\.\.(\/|$)/);
if (_parent === -1) {
// no more ../ to resolve
break;
} else if (_parent === 0) {
// top level cannot be relative, skip it
_path = _path.substring(3);
continue;
}
_pos = _path.substring(0, _parent).lastIndexOf('/');
if (_pos === -1) {
_pos = _parent;
}
_path = _path.substring(0, _pos) + _path.substring(_parent + 3);
}
// revert to relative
if (_was_relative && this.is('relative')) {
_path = _leadingParents + _path.substring(1);
}
this._parts.path = _path;
this.build(!build);
return this;
};
p.normalizePathname = p.normalizePath;
p.normalizeQuery = function(build) {
if (typeof this._parts.query === 'string') {
if (!this._parts.query.length) {
this._parts.query = null;
} else {
this.query(URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace));
}
this.build(!build);
}
return this;
};
p.normalizeFragment = function(build) {
if (!this._parts.fragment) {
this._parts.fragment = null;
this.build(!build);
}
return this;
};
p.normalizeSearch = p.normalizeQuery;
p.normalizeHash = p.normalizeFragment;
p.iso8859 = function() {
// expect unicode input, iso8859 output
var e = URI.encode;
var d = URI.decode;
URI.encode = escape;
URI.decode = decodeURIComponent;
try {
this.normalize();
} finally {
URI.encode = e;
URI.decode = d;
}
return this;
};
p.unicode = function() {
// expect iso8859 input, unicode output
var e = URI.encode;
var d = URI.decode;
URI.encode = strictEncodeURIComponent;
URI.decode = unescape;
try {
this.normalize();
} finally {
URI.encode = e;
URI.decode = d;
}
return this;
};
p.readable = function() {
var uri = this.clone();
// removing username, password, because they shouldn't be displayed according to RFC 3986
uri.username('').password('').normalize();
var t = '';
if (uri._parts.protocol) {
t += uri._parts.protocol + '://';
}
if (uri._parts.hostname) {
if (uri.is('punycode') && punycode) {
t += punycode.toUnicode(uri._parts.hostname);
if (uri._parts.port) {
t += ':' + uri._parts.port;
}
} else {
t += uri.host();
}
}
if (uri._parts.hostname && uri._parts.path && uri._parts.path.charAt(0) !== '/') {
t += '/';
}
t += uri.path(true);
if (uri._parts.query) {
var q = '';
for (var i = 0, qp = uri._parts.query.split('&'), l = qp.length; i < l; i++) {
var kv = (qp[i] || '').split('=');
q += '&' + URI.decodeQuery(kv[0], this._parts.escapeQuerySpace)
.replace(/&/g, '%26');
if (kv[1] !== undefined) {
q += '=' + URI.decodeQuery(kv[1], this._parts.escapeQuerySpace)
.replace(/&/g, '%26');
}
}
t += '?' + q.substring(1);
}
t += URI.decodeQuery(uri.hash(), true);
return t;
};
// resolving relative and absolute URLs
p.absoluteTo = function(base) {
var resolved = this.clone();
var properties = ['protocol', 'username', 'password', 'hostname', 'port'];
var basedir, i, p;
if (this._parts.urn) {
throw new Error('URNs do not have any generally defined hierarchical components');
}
if (!(base instanceof URI)) {
base = new URI(base);
}
if (resolved._parts.protocol) {
// Directly returns even if this._parts.hostname is empty.
return resolved;
} else {
resolved._parts.protocol = base._parts.protocol;
}
if (this._parts.hostname) {
return resolved;
}
for (i = 0; (p = properties[i]); i++) {
resolved._parts[p] = base._parts[p];
}
if (!resolved._parts.path) {
resolved._parts.path = base._parts.path;
if (!resolved._parts.query) {
resolved._parts.query = base._parts.query;
}
} else {
if (resolved._parts.path.substring(-2) === '..') {
resolved._parts.path += '/';
}
if (resolved.path().charAt(0) !== '/') {
basedir = base.directory();
basedir = basedir ? basedir : base.path().indexOf('/') === 0 ? '/' : '';
resolved._parts.path = (basedir ? (basedir + '/') : '') + resolved._parts.path;
resolved.normalizePath();
}
}
resolved.build();
return resolved;
};
p.relativeTo = function(base) {
var relative = this.clone().normalize();
var relativeParts, baseParts, common, relativePath, basePath;
if (relative._parts.urn) {
throw new Error('URNs do not have any generally defined hierarchical components');
}
base = new URI(base).normalize();
relativeParts = relative._parts;
baseParts = base._parts;
relativePath = relative.path();
basePath = base.path();
if (relativePath.charAt(0) !== '/') {
throw new Error('URI is already relative');
}
if (basePath.charAt(0) !== '/') {
throw new Error('Cannot calculate a URI relative to another relative URI');
}
if (relativeParts.protocol === baseParts.protocol) {
relativeParts.protocol = null;
}
if (relativeParts.username !== baseParts.username || relativeParts.password !== baseParts.password) {
return relative.build();
}
if (relativeParts.protocol !== null || relativeParts.username !== null || relativeParts.password !== null) {
return relative.build();
}
if (relativeParts.hostname === baseParts.hostname && relativeParts.port === baseParts.port) {
relativeParts.hostname = null;
relativeParts.port = null;
} else {
return relative.build();
}
if (relativePath === basePath) {
relativeParts.path = '';
return relative.build();
}
// determine common sub path
common = URI.commonPath(relativePath, basePath);
// If the paths have nothing in common, return a relative URL with the absolute path.
if (!common) {
return relative.build();
}
var parents = baseParts.path
.substring(common.length)
.replace(/[^\/]*$/, '')
.replace(/.*?\//g, '../');
relativeParts.path = (parents + relativeParts.path.substring(common.length)) || './';
return relative.build();
};
// comparing URIs
p.equals = function(uri) {
var one = this.clone();
var two = new URI(uri);
var one_map = {};
var two_map = {};
var checked = {};
var one_query, two_query, key;
one.normalize();
two.normalize();
// exact match
if (one.toString() === two.toString()) {
return true;
}
// extract query string
one_query = one.query();
two_query = two.query();
one.query('');
two.query('');
// definitely not equal if not even non-query parts match
if (one.toString() !== two.toString()) {
return false;
}
// query parameters have the same length, even if they're permuted
if (one_query.length !== two_query.length) {
return false;
}
one_map = URI.parseQuery(one_query, this._parts.escapeQuerySpace);
two_map = URI.parseQuery(two_query, this._parts.escapeQuerySpace);
for (key in one_map) {
if (hasOwn.call(one_map, key)) {
if (!isArray(one_map[key])) {
if (one_map[key] !== two_map[key]) {
return false;
}
} else if (!arraysEqual(one_map[key], two_map[key])) {
return false;
}
checked[key] = true;
}
}
for (key in two_map) {
if (hasOwn.call(two_map, key)) {
if (!checked[key]) {
// two contains a parameter not present in one
return false;
}
}
}
return true;
};
// state
p.preventInvalidHostname = function(v) {
this._parts.preventInvalidHostname = !!v;
return this;
};
p.duplicateQueryParameters = function(v) {
this._parts.duplicateQueryParameters = !!v;
return this;
};
p.escapeQuerySpace = function(v) {
this._parts.escapeQuerySpace = !!v;
return this;
};
return URI;
}));
/* Lo-Dash Template Loader v1.0.1
* Copyright 2015, Tim Branyen (@tbranyen).
* loader.js may be freely distributed under the MIT license.
*/
(function(global) {
"use strict";
2017-07-22 22:21:05 +02:00
// Cache used to map configuration options between load and write.
var buildMap = {};
// Alias the correct `nodeRequire` method.
var nodeRequire = typeof requirejs === "function" && requirejs.nodeRequire;
2017-07-22 22:21:05 +02:00
// Strips trailing `/` from url fragments.
var stripTrailing = function(prop) {
return prop.replace(/(\/$)/, '');
};
2017-07-22 22:21:05 +02:00
// Define the plugin using the CommonJS syntax.
define('tpl',['require','exports','module','lodash'],function(require, exports) {
var _ = require("lodash");
2017-07-22 22:21:05 +02:00
exports.version = "1.0.1";
2017-07-22 22:21:05 +02:00
// Invoked by the AMD builder, passed the path to resolve, the require
// function, done callback, and the configuration options.
exports.load = function(name, req, load, config) {
var isDojo;
// Dojo provides access to the config object through the req function.
if (!config) {
config = require.rawConfig;
isDojo = true;
}
2017-07-22 22:21:05 +02:00
var contents = "";
var settings = configure(config);
2017-07-22 22:21:05 +02:00
// If the baseUrl and root are the same, just null out the root.
if (stripTrailing(config.baseUrl) === stripTrailing(settings.root)) {
settings.root = '';
}
2017-07-22 22:21:05 +02:00
var url = require.toUrl(settings.root + name + settings.ext);
2017-07-22 22:21:05 +02:00
if (isDojo && url.indexOf(config.baseUrl) !== 0) {
url = stripTrailing(config.baseUrl) + url;
}
2017-07-22 22:21:05 +02:00
// Builds with r.js require Node.js to be installed.
if (config.isBuild) {
// If in Node, get access to the filesystem.
var fs = nodeRequire("fs");
2018-01-17 19:45:33 +01:00
try {
// First try reading the filepath as-is.
contents = String(fs.readFileSync(url));
} catch(ex) {
// If it failed, it's most likely because of a leading `/` and not an
// absolute path. Remove the leading slash and try again.
if (url.slice(0, 1) === "/") {
url = url.slice(1);
}
2018-01-17 19:45:33 +01:00
// Try reading again with the leading `/`.
contents = String(fs.readFileSync(url));
}
2018-01-17 19:45:33 +01:00
// Read in the file synchronously, as RequireJS expects, and return the
// contents. Process as a Lo-Dash template.
buildMap[name] = _.template(contents);
2018-01-17 19:45:33 +01:00
return load();
2018-01-17 19:45:33 +01:00
}
// Create a basic XHR.
var xhr = new XMLHttpRequest();
2018-01-17 19:45:33 +01:00
// Wait for it to load.
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
var templateSettings = _.clone(settings.templateSettings);
2018-01-17 19:45:33 +01:00
// Attach the sourceURL.
templateSettings.sourceURL = url;
2018-01-17 19:45:33 +01:00
// Process as a Lo-Dash template and cache.
buildMap[name] = _.template(xhr.responseText, templateSettings);
2018-01-17 19:45:33 +01:00
// Return the compiled template.
load(buildMap[name]);
}
};
2018-01-17 19:45:33 +01:00
// Initiate the fetch.
xhr.open("GET", url, true);
xhr.send(null);
2018-01-17 19:45:33 +01:00
};
// Also invoked by the AMD builder, this writes out a compatible define
// call that will work with loaders such as almond.js that cannot read
// the configuration data.
exports.write = function(pluginName, moduleName, write) {
var template = buildMap[moduleName].source;
// Write out the actual definition
write(strDefine(pluginName, moduleName, template));
2018-01-17 19:45:33 +01:00
};
// This is for curl.js/cram.js build-time support.
exports.compile = function(pluginName, moduleName, req, io, config) {
configure(config);
// Ask cram to fetch the template file (resId) and pass it to `write`.
io.read(moduleName, write, io.error);
function write(template) {
// Write-out define(id,function(){return{/* template */}});
io.write(strDefine(pluginName, moduleName, template));
}
};
// Crafts the written definition form of the module during a build.
function strDefine(pluginName, moduleName, template) {
return [
"define('", pluginName, "!", moduleName, "', ", "['lodash'], ",
[
"function(_) {",
"return ", template, ";",
"}"
].join(""),
");\n"
].join("");
}
function configure(config) {
// Default settings point to the project root and using html files.
var settings = _.extend({
ext: ".html",
root: config.baseUrl,
templateSettings: {}
}, config.lodashLoader);
// Ensure the root has been properly configured with a trailing slash,
// unless it's an empty string or undefined, in which case work off the
// baseUrl.
if (settings.root && settings.root.slice(-1) !== "/") {
settings.root += "/";
}
// Set the custom passed in template settings.
_.extend(_.templateSettings, settings.templateSettings);
return settings;
}
});
})(typeof global === "object" ? global : this);
define('tpl!audio', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<audio controls><source src="' +
__e(o.url) +
'" type="audio/mpeg"></audio>\n<a target="_blank" rel="noopener" href="' +
__e(o.url) +
'">' +
__e(o.label_download) +
'</a>\n';
return __p
};});
define('tpl!file', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<a target="_blank" rel="noopener" href="' +
__e(o.url) +
'">' +
__e(o.label_download) +
'</a>\n';
return __p
};});
define('tpl!image', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
2018-04-30 16:05:20 +02:00
__p += '<a href="' +
__e(o.url) +
'" target="_blank" rel="noopener"><img class="chat-image img-thumbnail" src="' +
__e(o.url) +
2018-04-30 16:05:20 +02:00
'"></a>\n';
return __p
};});
define('tpl!video', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<video controls><source src="' +
__e(o.url) +
'" type="video/mp4"></video>\n<a target="_blank" rel="noopener" href="' +
__e(o.url) +
'">' +
__e(o.label_download) +
'</a>\n';
return __p
};});
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// This is the utilities module.
//
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define, escape, window */
(function (root, factory) {
2018-04-30 16:05:20 +02:00
if (typeof define === 'function' && define.amd) {
2018-05-07 18:20:15 +02:00
define('utils',["sizzle", "es6-promise", "lodash.noconflict", "backbone", "strophe", "uri", "tpl!audio", "tpl!file", "tpl!image", "tpl!video"], factory);
2018-04-30 16:05:20 +02:00
} else {
// Used by the mockups
var Strophe = {
'Strophe': root.Strophe,
'$build': root.$build,
'$iq': root.$iq,
'$msg': root.$msg,
'$pres': root.$pres,
'SHA1': root.SHA1,
'MD5': root.MD5,
'b64_hmac_sha1': root.b64_hmac_sha1,
'b64_sha1': root.b64_sha1,
'str_hmac_sha1': root.str_hmac_sha1,
'str_sha1': root.str_sha1
};
2018-05-08 19:58:12 +02:00
root.converse_utils = factory(root.sizzle, root.Promise, root._, root.Backbone, Strophe);
2018-04-30 16:05:20 +02:00
}
2018-05-07 18:20:15 +02:00
})(this, function (sizzle, Promise, _, Backbone, Strophe, URI, tpl_audio, tpl_file, tpl_image, tpl_video) {
"use strict";
Strophe = Strophe.Strophe;
var URL_REGEX = /\b(https?:\/\/|www\.|https?:\/\/www\.)[^\s<>]{2,200}\b\/?/g;
var logger = _.assign({
'debug': _.get(console, 'log') ? console.log.bind(console) : _.noop,
'error': _.get(console, 'log') ? console.log.bind(console) : _.noop,
'info': _.get(console, 'log') ? console.log.bind(console) : _.noop,
'warn': _.get(console, 'log') ? console.log.bind(console) : _.noop
}, console);
var isImage = function isImage(url) {
return new Promise(function (resolve, reject) {
var img = new Image();
var timer = window.setTimeout(function () {
reject(new Error("Could not determine whether it's an image"));
img = null;
}, 3000);
img.onerror = img.onabort = function () {
clearTimeout(timer);
reject(new Error("Could not determine whether it's an image"));
};
img.onload = function () {
clearTimeout(timer);
resolve(img);
};
img.src = url;
});
};
function slideOutWrapup(el) {
/* Wrapup function for slideOut. */
el.removeAttribute('data-slider-marker');
el.classList.remove('collapsed');
el.style.overflow = "";
el.style.height = "";
}
var u = {};
u.getNextElement = function (el) {
var selector = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '*';
var next_el = el.nextElementSibling;
while (!_.isNull(next_el) && !sizzle.matchesSelector(next_el, selector)) {
next_el = next_el.nextElementSibling;
}
return next_el;
};
u.getPreviousElement = function (el) {
var selector = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '*';
var prev_el = el.previousSibling;
while (!_.isNull(prev_el) && !sizzle.matchesSelector(prev_el, selector)) {
prev_el = prev_el.previousSibling;
}
return prev_el;
};
u.getFirstChildElement = function (el) {
var selector = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '*';
var first_el = el.firstElementChild;
while (!_.isNull(first_el) && !sizzle.matchesSelector(first_el, selector)) {
first_el = first_el.nextSibling;
}
return first_el;
};
u.getLastChildElement = function (el) {
var selector = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '*';
var last_el = el.lastElementChild;
while (!_.isNull(last_el) && !sizzle.matchesSelector(last_el, selector)) {
last_el = last_el.previousSibling;
}
return last_el;
};
u.calculateElementHeight = function (el) {
/* Return the height of the passed in DOM element,
* based on the heights of its children.
*/
return _.reduce(el.children, function (result, child) {
return result + child.offsetHeight;
}, 0);
};
u.addClass = function (className, el) {
if (el instanceof Element) {
el.classList.add(className);
2018-01-04 17:58:16 +01:00
}
};
u.removeClass = function (className, el) {
if (el instanceof Element) {
el.classList.remove(className);
}
return el;
};
2017-12-20 18:08:08 +01:00
u.removeElement = function (el) {
if (!_.isNil(el) && !_.isNil(el.parentNode)) {
el.parentNode.removeChild(el);
}
};
2018-01-04 17:58:16 +01:00
u.showElement = _.flow(_.partial(u.removeClass, 'collapsed'), _.partial(u.removeClass, 'hidden'));
2017-12-20 18:08:08 +01:00
u.hideElement = function (el) {
if (!_.isNil(el)) {
el.classList.add('hidden');
}
2018-01-04 17:58:16 +01:00
return el;
2017-12-20 18:08:08 +01:00
};
2018-03-25 21:21:43 +02:00
u.ancestor = function (el, selector) {
var parent = el;
while (!_.isNil(parent) && !sizzle.matchesSelector(parent, selector)) {
parent = parent.parentElement;
}
return parent;
};
2017-12-20 18:08:08 +01:00
u.nextUntil = function (el, selector) {
var include_self = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
/* Return the element's siblings until one matches the selector. */
var matches = [];
var sibling_el = el.nextElementSibling;
while (!_.isNil(sibling_el) && !sibling_el.matches(selector)) {
matches.push(sibling_el);
sibling_el = sibling_el.nextElementSibling;
}
return matches;
};
2018-04-30 16:05:20 +02:00
u.unescapeHTML = function (htmlEscapedText) {
/* Helper method that replace HTML-escaped symbols with equivalent characters
* (e.g. transform occurrences of '&amp;' to '&')
*
* Parameters:
* (String) htmlEscapedText: a String containing the HTML-escaped symbols.
*/
var div = document.createElement('div');
div.innerHTML = htmlEscapedText;
return div.innerText;
};
u.escapeHTML = function (string) {
return string.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
};
u.addHyperlinks = function (text) {
2018-04-30 16:05:20 +02:00
return URI.withinString(text, function (url) {
var uri = new URI(url);
uri.normalize();
2017-07-22 22:21:05 +02:00
2018-04-30 16:05:20 +02:00
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = 'http://' + url;
}
2017-07-22 22:21:05 +02:00
2018-04-30 16:05:20 +02:00
url = encodeURI(decodeURI(url)).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
return "<a target=\"_blank\" rel=\"noopener\" href=\"".concat(u.escapeHTML(url), "\">").concat(u.escapeHTML(uri.readable()), "</a>");
});
};
2018-04-30 16:05:20 +02:00
u.renderImageURLs = function (_converse, obj) {
2018-01-17 19:45:33 +01:00
/* Returns a Promise which resolves once all images have been loaded.
*/
2018-04-30 16:05:20 +02:00
var __ = _converse.__;
var list = obj.textContent.match(URL_REGEX) || [];
2018-01-17 19:45:33 +01:00
return Promise.all(_.map(list, function (url) {
return new Promise(function (resolve, reject) {
return isImage(url).then(function (img) {
var i = new Image();
i.src = img.src;
i.addEventListener('load', resolve); // We also resolve for non-images, otherwise the
// Promise.all resolves prematurely.
i.addEventListener('error', resolve);
2018-04-30 16:05:20 +02:00
_.each(sizzle("a[href=\"".concat(url, "\"]"), obj), function (a) {
a.outerHTML = tpl_image({
'url': url,
'label_download': __('Download')
});
2018-01-17 19:45:33 +01:00
});
}).catch(resolve);
});
2018-01-17 19:45:33 +01:00
}));
};
2017-07-22 22:21:05 +02:00
u.renderFileURL = function (_converse, url) {
2018-04-30 16:05:20 +02:00
var uri = new URI(url),
__ = _converse.__,
filename = uri.filename();
if (!_.includes(["https", "http"], uri.protocol()) || filename.endsWith('mp3') || filename.endsWith('mp4') || filename.endsWith('jpg') || filename.endsWith('jpeg') || filename.endsWith('png') || filename.endsWith('gif') || filename.endsWith('svg')) {
return url;
}
return tpl_file({
'url': url,
2018-04-30 16:05:20 +02:00
'label_download': __('Download: "%1$s', filename)
});
};
u.renderImageURL = function (_converse, url) {
var __ = _converse.__;
if (url.endsWith('jpg') || url.endsWith('jpeg') || url.endsWith('png') || url.endsWith('gif') || url.endsWith('svg')) {
return tpl_image({
'url': url,
2018-04-30 16:05:20 +02:00
'label_download': __('Download')
});
}
return url;
};
u.renderMovieURL = function (_converse, url) {
var __ = _converse.__;
if (url.endsWith('mp4')) {
return tpl_video({
'url': url,
'label_download': __('Download video file')
});
}
return url;
};
u.renderAudioURL = function (_converse, url) {
var __ = _converse.__;
if (url.endsWith('mp3')) {
return tpl_audio({
'url': url,
'label_download': __('Download audio file')
});
}
return url;
};
u.slideInAllElements = function (elements) {
2018-01-04 17:58:16 +01:00
var duration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 300;
2017-12-20 18:08:08 +01:00
return Promise.all(_.map(elements, _.partial(u.slideIn, _, duration)));
};
2017-07-22 22:21:05 +02:00
2018-01-04 17:58:16 +01:00
u.slideToggleElement = function (el, duration) {
if (_.includes(el.classList, 'collapsed') || _.includes(el.classList, 'hidden')) {
return u.slideOut(el, duration);
} else {
2018-01-04 17:58:16 +01:00
return u.slideIn(el, duration);
}
};
2017-07-22 22:21:05 +02:00
2018-01-04 17:58:16 +01:00
u.hasClass = function (className, el) {
2017-12-20 18:08:08 +01:00
return _.includes(el.classList, className);
};
u.slideOut = function (el) {
2018-01-04 17:58:16 +01:00
var duration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 200;
2017-07-22 22:21:05 +02:00
/* Shows/expands an element by sliding it out of itself
*
* Parameters:
* (HTMLElement) el - The HTML string
* (Number) duration - The duration amount in milliseconds
*/
return new Promise(function (resolve, reject) {
if (_.isNil(el)) {
var err = "Undefined or null element passed into slideOut";
logger.warn(err);
reject(new Error(err));
return;
}
2017-07-22 22:21:05 +02:00
2017-12-20 18:08:08 +01:00
var marker = el.getAttribute('data-slider-marker');
2017-12-20 18:08:08 +01:00
if (marker) {
el.removeAttribute('data-slider-marker');
2017-12-20 18:08:08 +01:00
window.cancelAnimationFrame(marker);
}
2017-07-22 22:21:05 +02:00
2018-01-17 19:45:33 +01:00
var end_height = u.calculateElementHeight(el);
if (window.converse_disable_effects) {
// Effects are disabled (for tests)
el.style.height = end_height + 'px';
slideOutWrapup(el);
resolve();
return;
}
2018-01-04 17:58:16 +01:00
if (!u.hasClass('collapsed', el) && !u.hasClass('hidden', el)) {
2017-12-20 18:08:08 +01:00
resolve();
return;
}
var steps = duration / 17; // We assume 17ms per animation which is ~60FPS
var height = 0;
function draw() {
height += end_height / steps;
2017-12-20 18:08:08 +01:00
if (height < end_height) {
el.style.height = height + 'px';
el.setAttribute('data-slider-marker', window.requestAnimationFrame(draw));
} else {
// We recalculate the height to work around an apparent
// browser bug where browsers don't know the correct
// offsetHeight beforehand.
2017-12-20 18:08:08 +01:00
el.removeAttribute('data-slider-marker');
2018-01-17 19:45:33 +01:00
el.style.height = u.calculateElementHeight(el) + 'px';
2017-12-20 18:08:08 +01:00
el.style.overflow = "";
el.style.height = "";
resolve();
}
2017-12-20 18:08:08 +01:00
}
el.style.height = '0';
el.style.overflow = 'hidden';
el.classList.remove('hidden');
el.classList.remove('collapsed');
el.setAttribute('data-slider-marker', window.requestAnimationFrame(draw));
});
};
2017-07-22 22:21:05 +02:00
u.slideIn = function (el) {
2018-01-04 17:58:16 +01:00
var duration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 200;
/* Hides/collapses an element by sliding it into itself. */
return new Promise(function (resolve, reject) {
if (_.isNil(el)) {
var err = "Undefined or null element passed into slideIn";
logger.warn(err);
return reject(new Error(err));
} else if (_.includes(el.classList, 'collapsed')) {
2018-01-04 17:58:16 +01:00
return resolve(el);
} else if (window.converse_disable_effects) {
// Effects are disabled (for tests)
el.classList.add('collapsed');
el.style.height = "";
2018-01-04 17:58:16 +01:00
return resolve(el);
}
2017-07-22 22:21:05 +02:00
2017-12-20 18:08:08 +01:00
var marker = el.getAttribute('data-slider-marker');
2017-07-22 22:21:05 +02:00
2017-12-20 18:08:08 +01:00
if (marker) {
el.removeAttribute('data-slider-marker');
2017-12-20 18:08:08 +01:00
window.cancelAnimationFrame(marker);
}
2017-07-22 22:21:05 +02:00
2017-12-20 18:08:08 +01:00
var original_height = el.offsetHeight,
steps = duration / 17; // We assume 17ms per animation which is ~60FPS
var height = original_height;
el.style.overflow = 'hidden';
2017-07-22 22:21:05 +02:00
2017-12-20 18:08:08 +01:00
function draw() {
height -= original_height / steps;
if (height > 0) {
el.style.height = height + 'px';
el.setAttribute('data-slider-marker', window.requestAnimationFrame(draw));
} else {
el.removeAttribute('data-slider-marker');
el.classList.add('collapsed');
el.style.height = "";
2018-01-04 17:58:16 +01:00
resolve(el);
}
2017-12-20 18:08:08 +01:00
}
el.setAttribute('data-slider-marker', window.requestAnimationFrame(draw));
});
};
2017-07-22 22:21:05 +02:00
2017-12-20 18:08:08 +01:00
function afterAnimationEnds(el, callback) {
el.classList.remove('visible');
if (_.isFunction(callback)) {
callback();
}
}
u.fadeIn = function (el, callback) {
if (_.isNil(el)) {
logger.warn("Undefined or null element passed into fadeIn");
}
2017-07-22 22:21:05 +02:00
if (window.converse_disable_effects) {
el.classList.remove('hidden');
2017-12-20 18:08:08 +01:00
return afterAnimationEnds(el, callback);
}
if (_.includes(el.classList, 'hidden')) {
el.classList.add('visible');
el.classList.remove('hidden');
2017-12-20 18:08:08 +01:00
el.addEventListener("webkitAnimationEnd", _.partial(afterAnimationEnds, el, callback));
el.addEventListener("animationend", _.partial(afterAnimationEnds, el, callback));
el.addEventListener("oanimationend", _.partial(afterAnimationEnds, el, callback));
} else {
2017-12-20 18:08:08 +01:00
afterAnimationEnds(el, callback);
}
};
u.isValidJID = function (jid) {
2018-02-14 16:53:07 +01:00
return _.compact(jid.split('@')).length === 2 && !jid.startsWith('@') && !jid.endsWith('@');
};
2018-01-29 16:33:30 +01:00
u.isValidMUCJID = function (jid) {
return !jid.startsWith('@') && !jid.endsWith('@');
};
u.isSameBareJID = function (jid1, jid2) {
return Strophe.getBareJidFromJid(jid1).toLowerCase() === Strophe.getBareJidFromJid(jid2).toLowerCase();
};
u.getMostRecentMessage = function (model) {
var messages = model.messages.filter('message');
return messages[messages.length - 1];
};
u.isNewMessage = function (message) {
/* Given a stanza, determine whether it's a new
* message, i.e. not a MAM archived one.
*/
if (message instanceof Element) {
2018-01-04 17:58:16 +01:00
return !sizzle('result[xmlns="' + Strophe.NS.MAM + '"]', message).length && !sizzle('delay[xmlns="' + Strophe.NS.DELAY + '"]', message).length;
} else {
2018-02-14 16:53:07 +01:00
return !message.get('delayed');
}
};
2018-05-07 18:20:15 +02:00
u.isOnlyChatStateNotification = function (attrs) {
if (attrs instanceof Backbone.Model) {
attrs = attrs.attributes;
}
return attrs['chat_state'] && !attrs['oob_url'] && !attrs['file'] && !attrs['message'];
};
u.isOTRMessage = function (message) {
var body = message.querySelector('body'),
text = !_.isNull(body) ? body.textContent : undefined;
return text && !!text.match(/^\?OTR/);
};
2018-01-29 16:33:30 +01:00
u.isHeadlineMessage = function (_converse, message) {
var from_jid = message.getAttribute('from');
2017-07-22 22:21:05 +02:00
if (message.getAttribute('type') === 'headline') {
return true;
}
2018-01-29 16:33:30 +01:00
var chatbox = _converse.chatboxes.get(Strophe.getBareJidFromJid(from_jid));
if (chatbox && chatbox.get('type') === 'chatroom') {
return false;
}
if (message.getAttribute('type') !== 'error' && !_.isNil(from_jid) && !_.includes(from_jid, '@')) {
// 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.
return true;
}
return false;
};
u.merge = function merge(first, second) {
/* Merge the second object into the first one.
*/
for (var k in second) {
if (_.isObject(first[k])) {
merge(first[k], second[k]);
} else {
first[k] = second[k];
}
}
};
u.applyUserSettings = function applyUserSettings(context, settings, user_settings) {
/* Configuration settings might be nested objects. We only want to
* add settings which are whitelisted.
*/
for (var k in settings) {
if (_.isUndefined(user_settings[k])) {
continue;
}
if (_.isObject(settings[k]) && !_.isArray(settings[k])) {
applyUserSettings(context[k], settings[k], user_settings[k]);
} else {
context[k] = user_settings[k];
}
}
};
2018-01-04 17:58:16 +01:00
u.stringToNode = function (s) {
/* Converts an HTML string into a DOM Node.
* Expects that the HTML string has only one top-level element,
* i.e. not multiple ones.
*
* Parameters:
* (String) s - The HTML string
*/
2018-01-04 17:58:16 +01:00
var div = document.createElement('div');
div.innerHTML = s;
return div.firstChild;
};
2018-01-04 17:58:16 +01:00
u.getOuterWidth = function (el) {
var include_margin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var width = el.offsetWidth;
if (!include_margin) {
return width;
}
2018-01-04 17:58:16 +01:00
var style = window.getComputedStyle(el);
width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10);
return width;
};
2018-01-04 17:58:16 +01:00
u.stringToElement = function (s) {
/* Converts an HTML string into a DOM element.
2018-01-04 17:58:16 +01:00
* Expects that the HTML string has only one top-level element,
* i.e. not multiple ones.
*
* Parameters:
* (String) s - The HTML string
*/
var div = document.createElement('div');
div.innerHTML = s;
2018-01-04 17:58:16 +01:00
return div.firstElementChild;
};
u.matchesSelector = function (el, selector) {
/* Checks whether the DOM element matches the given selector.
*
* Parameters:
* (DOMElement) el - The DOM element
* (String) selector - The selector
*/
return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector);
};
u.queryChildren = function (el, selector) {
/* Returns a list of children of the DOM element that match the
* selector.
*
* Parameters:
* (DOMElement) el - the DOM element
* (String) selector - the selector they should be matched
* against.
*/
2018-05-15 10:32:13 +02:00
return _.filter(el.childNodes, _.partial(u.matchesSelector, _, selector));
};
u.contains = function (attr, query) {
return function (item) {
if (_typeof(attr) === 'object') {
var value = false;
_.forEach(attr, function (a) {
value = value || _.includes(item.get(a).toLowerCase(), query.toLowerCase());
});
return value;
} else if (typeof attr === 'string') {
return _.includes(item.get(attr).toLowerCase(), query.toLowerCase());
} else {
throw new TypeError('contains: wrong attribute type. Must be string or array.');
}
};
};
u.isOfType = function (type, item) {
return item.get('type') == type;
};
u.isInstance = function (type, item) {
return item instanceof type;
};
u.getAttribute = function (key, item) {
return item.get(key);
};
u.contains.not = function (attr, query) {
return function (item) {
return !u.contains(attr, query)(item);
};
};
2018-05-23 04:27:33 +02:00
u.rootContains = function (root, el) {
// The document element does not have the contains method in IE.
if (root === document && !root.contains) {
return document.head.contains(el) || document.body.contains(el);
}
return root.contains ? root.contains(el) : window.HTMLElement.prototype.contains.call(root, el);
};
u.createFragmentFromText = function (markup) {
/* Returns a DocumentFragment containing DOM nodes based on the
* passed-in markup text.
*/
// http://stackoverflow.com/questions/9334645/create-node-from-markup-string
var frag = document.createDocumentFragment(),
tmp = document.createElement('body'),
child;
tmp.innerHTML = markup; // Append elements in a loop to a DocumentFragment, so that the
// browser does not re-render the document for each node.
while (child = tmp.firstChild) {
// eslint-disable-line no-cond-assign
frag.appendChild(child);
}
return frag;
};
u.addEmoji = function (_converse, emojione, text) {
if (_converse.use_emojione) {
return emojione.toImage(text);
} else {
return emojione.shortnameToUnicode(text);
}
};
u.getEmojisByCategory = function (_converse, emojione) {
/* Return a dict of emojis with the categories as keys and
* lists of emojis in that category as values.
*/
if (_.isUndefined(_converse.emojis_by_category)) {
var emojis = _.values(_.mapValues(emojione.emojioneList, function (value, key, o) {
value._shortname = key;
return value;
}));
var tones = [':tone1:', ':tone2:', ':tone3:', ':tone4:', ':tone5:'];
var excluded = [':kiss_ww:', ':kiss_mm:', ':kiss_woman_man:'];
var excluded_substrings = [':woman', ':man', ':women_', ':men_', '_man_', '_woman_', '_woman:', '_man:'];
var excluded_categories = ['modifier', 'regional'];
var categories = _.difference(_.uniq(_.map(emojis, _.partial(_.get, _, 'category'))), excluded_categories);
var emojis_by_category = {};
_.forEach(categories, function (cat) {
var list = _.sortBy(_.filter(emojis, ['category', cat]), ['uc_base']);
list = _.filter(list, function (item) {
return !_.includes(_.concat(tones, excluded), item._shortname) && !_.some(excluded_substrings, _.partial(_.includes, item._shortname));
2017-07-22 22:21:05 +02:00
});
if (cat === 'people') {
var idx = _.findIndex(list, ['uc_base', '1f600']);
list = _.union(_.slice(list, idx), _.slice(list, 0, idx + 1));
} else if (cat === 'activity') {
list = _.union(_.slice(list, 27 - 1), _.slice(list, 0, 27));
} else if (cat === 'objects') {
list = _.union(_.slice(list, 24 - 1), _.slice(list, 0, 24));
} else if (cat === 'travel') {
list = _.union(_.slice(list, 17 - 1), _.slice(list, 0, 17));
} else if (cat === 'symbols') {
list = _.union(_.slice(list, 60 - 1), _.slice(list, 0, 60));
}
emojis_by_category[cat] = list;
});
_converse.emojis_by_category = emojis_by_category;
}
return _converse.emojis_by_category;
};
u.getTonedEmojis = function (_converse) {
_converse.toned_emojis = _.uniq(_.map(_.filter(u.getEmojisByCategory(_converse).people, function (person) {
return _.includes(person._shortname, '_tone');
}), function (person) {
return person._shortname.replace(/_tone[1-5]/, '');
}));
return _converse.toned_emojis;
};
u.isPersistableModel = function (model) {
return model.collection && model.collection.browserStorage;
};
u.getResolveablePromise = function () {
/* Returns a promise object on which `resolve` or `reject` can be
* called.
*/
var wrapper = {};
var promise = new Promise(function (resolve, reject) {
wrapper.resolve = resolve;
wrapper.reject = reject;
});
_.assign(promise, wrapper);
return promise;
};
2018-03-05 14:43:53 +01:00
u.interpolate = function (string, o) {
return string.replace(/{{{([^{}]*)}}}/g, function (a, b) {
var r = o[b];
return typeof r === 'string' || typeof r === 'number' ? r : a;
});
};
u.onMultipleEvents = function () {
var events = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var callback = arguments.length > 1 ? arguments[1] : undefined;
/* Call the callback once all the events have been triggered
*
* Parameters:
* (Array) events: An array of objects, with keys `object` and
* `event`, representing the event name and the object it's
* triggered upon.
* (Function) callback: The function to call once all events have
* been triggered.
*/
var triggered = [];
function handler(result) {
triggered.push(result);
if (events.length === triggered.length) {
callback(triggered);
triggered = [];
}
}
_.each(events, function (map) {
return map.object.on(map.event, handler);
});
};
u.safeSave = function (model, attributes) {
if (u.isPersistableModel(model)) {
model.save(attributes);
} else {
model.set(attributes);
}
};
u.isVisible = function (el) {
2018-03-05 14:43:53 +01:00
if (u.hasClass('hidden', el)) {
return false;
} // XXX: Taken from jQuery's "visible" implementation
return el.offsetWidth > 0 || el.offsetHeight > 0 || el.getClientRects().length > 0;
};
2018-01-04 17:58:16 +01:00
u.triggerEvent = function (el, name) {
var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "Event";
var bubbles = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
var cancelable = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
var evt = document.createEvent(type);
evt.initEvent(name, bubbles, cancelable);
el.dispatchEvent(evt);
};
u.geoUriToHttp = function (text, geouri_replacement) {
var regex = /geo:([\-0-9.]+),([\-0-9.]+)(?:,([\-0-9.]+))?(?:\?(.*))?/g;
return text.replace(regex, geouri_replacement);
};
u.httpToGeoUri = function (text, _converse) {
var replacement = 'geo:$1,$2';
return text.replace(_converse.geouri_regex, replacement);
};
2018-05-15 10:32:13 +02:00
u.getSelectValues = function (select) {
var result = [];
var options = select && select.options;
var opt;
for (var i = 0, iLen = options.length; i < iLen; i++) {
opt = options[i];
if (opt.selected) {
result.push(opt.value || opt.text);
}
}
return result;
};
return u;
});
//# sourceMappingURL=core.js.map;
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define('pluggable',['exports', 'lodash'], factory);
} else if (typeof exports !== "undefined") {
factory(exports, require('lodash'));
} else {
var mod = {
exports: {}
};
factory(mod.exports, global._);
global.pluggable = mod.exports;
}
})(this, function (exports, _lodash) {
'use strict';
2017-07-22 22:21:05 +02:00
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.enable = undefined;
2017-07-22 22:21:05 +02:00
var _ = _interopRequireWildcard(_lodash);
function _interopRequireWildcard(obj) {
if (obj && obj.__esModule) {
return obj;
} else {
var newObj = {};
2016-12-13 20:46:07 +01:00
if (obj != null) {
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key];
}
}
newObj.default = obj;
return newObj;
2017-07-22 22:21:05 +02:00
}
}
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
2017-07-22 22:21:05 +02:00
};
// The `PluginSocket` class contains the plugin architecture, and gets
// created whenever `pluggable.enable(obj);` is called on the object
// that you want to make pluggable.
// You can also see it as the thing into which the plugins are plugged.
// It takes two parameters, first, the object being made pluggable, and
// then the name by which the pluggable object may be referenced on the
// __super__ object (inside overrides).
function PluginSocket(plugged, name) {
this.name = name;
this.plugged = plugged;
if (typeof this.plugged.__super__ === 'undefined') {
this.plugged.__super__ = {};
} else if (typeof this.plugged.__super__ === 'string') {
this.plugged.__super__ = { '__string__': this.plugged.__super__ };
2017-07-22 22:21:05 +02:00
}
this.plugged.__super__[name] = this.plugged;
this.plugins = {};
this.initialized_plugins = [];
}
// Now we add methods to the PluginSocket by adding them to its
// prototype.
_.extend(PluginSocket.prototype, {
// `wrappedOverride` creates a partially applied wrapper function
// that makes sure to set the proper super method when the
// overriding method is called. This is done to enable
// chaining of plugin methods, all the way up to the
// original method.
wrappedOverride: function wrappedOverride(key, value, super_method, default_super) {
if (typeof super_method === "function") {
if (typeof this.__super__ === "undefined") {
/* We're not on the context of the plugged object.
* This can happen when the overridden method is called via
* an event handler or when it's a constructor.
*
* In this case, we simply tack on the __super__ obj.
*/
this.__super__ = default_super;
}
this.__super__[key] = super_method.bind(this);
2017-07-22 22:21:05 +02:00
}
return value.apply(this, _.drop(arguments, 4));
},
// `_overrideAttribute` overrides an attribute on the original object
// (the thing being plugged into).
//
// If the attribute being overridden is a function, then the original
// function will still be available via the `__super__` attribute.
//
// If the same function is being overridden multiple times, then
// the original function will be available at the end of a chain of
// functions, starting from the most recent override, all the way
// back to the original function, each being referenced by the
// previous' __super__ attribute.
//
// For example:
//
// `plugin2.MyFunc.__super__.myFunc => plugin1.MyFunc.__super__.myFunc => original.myFunc`
_overrideAttribute: function _overrideAttribute(key, plugin) {
var value = plugin.overrides[key];
if (typeof value === "function") {
var default_super = {};
default_super[this.name] = this.plugged;
var wrapped_function = _.partial(this.wrappedOverride, key, value, this.plugged[key], default_super);
this.plugged[key] = wrapped_function;
2017-07-22 22:21:05 +02:00
} else {
this.plugged[key] = value;
2017-07-22 22:21:05 +02:00
}
},
2017-07-22 22:21:05 +02:00
_extendObject: function _extendObject(obj, attributes) {
if (!obj.prototype.__super__) {
obj.prototype.__super__ = {};
obj.prototype.__super__[this.name] = this.plugged;
2017-07-22 22:21:05 +02:00
}
var that = this;
_.each(attributes, function (value, key) {
if (key === 'events') {
obj.prototype[key] = _.extend(value, obj.prototype[key]);
} else if (typeof value === 'function') {
// We create a partially applied wrapper function, that
// makes sure to set the proper super method when the
// overriding method is called. This is done to enable
// chaining of plugin methods, all the way up to the
// original method.
var default_super = {};
default_super[that.name] = that.plugged;
var wrapped_function = _.partial(that.wrappedOverride, key, value, obj.prototype[key], default_super);
obj.prototype[key] = wrapped_function;
} else {
obj.prototype[key] = value;
}
2017-07-22 22:21:05 +02:00
});
},
2018-01-17 19:45:33 +01:00
// Plugins can specify dependencies (by means of the
// `dependencies` list attribute) which refers to dependencies
// which will be initialized first, before the plugin itself gets initialized.
2018-01-17 19:45:33 +01:00
//
// If `strict_plugin_dependencies` is set to `false` (on the object being
// made pluggable), then no error will be thrown if any of these plugins aren't
// available.
loadPluginDependencies: function loadPluginDependencies(plugin) {
var _this = this;
2018-01-17 19:45:33 +01:00
_.each(plugin.dependencies, function (name) {
var dep = _this.plugins[name];
if (dep) {
2018-01-17 19:45:33 +01:00
if (_.includes(dep.dependencies, plugin.__name__)) {
/* FIXME: circular dependency checking is only one level deep. */
throw "Found a circular dependency between the plugins \"" + plugin.__name__ + "\" and \"" + name + "\"";
}
_this.initializePlugin(dep);
} else {
2018-01-17 19:45:33 +01:00
_this.throwUndefinedDependencyError("Could not find dependency \"" + name + "\" " + "for the plugin \"" + plugin.__name__ + "\". " + "If it's needed, make sure it's loaded by require.js");
}
2017-07-22 22:21:05 +02:00
});
},
throwUndefinedDependencyError: function throwUndefinedDependencyError(msg) {
if (this.plugged.strict_plugin_dependencies) {
throw msg;
} else {
console.log(msg);
return;
}
},
// `applyOverrides` is called by initializePlugin. It applies any
// and all overrides of methods or Backbone views and models that
// are defined on any of the plugins.
applyOverrides: function applyOverrides(plugin) {
var _this2 = this;
_.each(Object.keys(plugin.overrides || {}), function (key) {
var override = plugin.overrides[key];
if ((typeof override === 'undefined' ? 'undefined' : _typeof(override)) === "object") {
if (typeof _this2.plugged[key] === 'undefined') {
_this2.throwUndefinedDependencyError("Error: Plugin \"" + plugin.__name__ + "\" tried to override " + key + " but it's not found.");
} else {
_this2._extendObject(_this2.plugged[key], override);
}
} else {
_this2._overrideAttribute(key, plugin);
}
});
},
2017-07-22 22:21:05 +02:00
// `initializePlugin` applies the overrides (if any) defined on all
// the registered plugins and then calls the initialize method of the plugin
initializePlugin: function initializePlugin(plugin) {
if (!_.includes(_.keys(this.allowed_plugins), plugin.__name__)) {
/* Don't initialize disallowed plugins. */
return;
}
if (_.includes(this.initialized_plugins, plugin.__name__)) {
/* Don't initialize plugins twice, otherwise we get
* infinite recursion in overridden methods.
*/
return;
2017-07-22 22:21:05 +02:00
}
if (_.isBoolean(plugin.enabled) && plugin.enabled || _.isFunction(plugin.enabled) && plugin.enabled(this.plugged) || _.isNil(plugin.enabled)) {
_.extend(plugin, this.properties);
2018-01-17 19:45:33 +01:00
if (plugin.dependencies) {
this.loadPluginDependencies(plugin);
}
this.applyOverrides(plugin);
if (typeof plugin.initialize === "function") {
plugin.initialize.bind(plugin)(this);
}
this.initialized_plugins.push(plugin.__name__);
}
},
// `registerPlugin` registers (or inserts, if you'd like) a plugin,
// by adding it to the `plugins` map on the PluginSocket instance.
registerPlugin: function registerPlugin(name, plugin) {
if (name in this.plugins) {
throw new Error('Error: Plugin name ' + name + ' is already taken');
}
plugin.__name__ = name;
this.plugins[name] = plugin;
},
// `initializePlugins` should get called once all plugins have been
// registered. It will then iterate through all the plugins, calling
// `initializePlugin` for each.
// The passed in properties variable is an object with attributes and methods
// which will be attached to the plugins.
initializePlugins: function initializePlugins() {
var properties = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var whitelist = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
var blacklist = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
if (!_.size(this.plugins)) {
return;
}
this.properties = properties;
this.allowed_plugins = _.pickBy(this.plugins, function (plugin, key) {
return (!whitelist.length || whitelist.length && _.includes(whitelist, key)) && !_.includes(blacklist, key);
});
_.each(_.values(this.allowed_plugins), this.initializePlugin.bind(this));
2017-07-22 22:21:05 +02:00
}
});
function enable(object, name, attrname) {
// Call the `enable` method to make an object pluggable
//
// It takes three parameters:
// - `object`: The object that gets made pluggable.
// - `name`: The string name by which the now pluggable object
// may be referenced on the __super__ obj (in overrides).
// The default value is "plugged".
// - `attrname`: The string name of the attribute on the now
// pluggable object, which refers to the PluginSocket instance
// that gets created.
if (typeof attrname === "undefined") {
attrname = "pluginSocket";
2017-07-22 22:21:05 +02:00
}
if (typeof name === 'undefined') {
name = 'plugged';
2017-07-22 22:21:05 +02:00
}
var ref = {};
ref[attrname] = new PluginSocket(object, name);
return _.extend(object, ref);
}
exports.enable = enable;
exports.default = {
enable: enable
2017-07-22 22:21:05 +02:00
};
});
2018-05-15 10:32:13 +02:00
//# sourceMappingURL=pluggable.js.map
;
// Converse.js
// https://conversejs.org
//
// Copyright (c) 2012-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2)
(function (root, factory) {
2018-02-14 16:53:07 +01:00
define('converse-core',["sizzle", "es6-promise", "lodash.noconflict", "lodash.fp", "polyfill", "i18n", "utils", "moment", "strophe", "pluggable", "backbone.noconflict", "backbone.nativeview", "backbone.browserStorage"], factory);
2018-03-05 14:43:53 +01:00
})(this, function (sizzle, Promise, _, f, polyfill, i18n, u, moment, Strophe, pluggable, Backbone) {
/* Cannot use this due to Safari bug.
* See https://github.com/jcbrand/converse.js/issues/196
*/
// "use strict";
// Strophe globals
var _Strophe = Strophe,
$build = _Strophe.$build,
$iq = _Strophe.$iq,
$msg = _Strophe.$msg,
$pres = _Strophe.$pres;
var b64_sha1 = Strophe.SHA1.b64_sha1;
Strophe = Strophe.Strophe; // Add Strophe Namespaces
Strophe.addNamespace('CARBONS', 'urn:xmpp:carbons:2');
Strophe.addNamespace('CHATSTATES', 'http://jabber.org/protocol/chatstates');
Strophe.addNamespace('CSI', 'urn:xmpp:csi:0');
Strophe.addNamespace('DELAY', 'urn:xmpp:delay');
2018-01-04 17:58:16 +01:00
Strophe.addNamespace('FORWARD', 'urn:xmpp:forward:0');
Strophe.addNamespace('HINTS', 'urn:xmpp:hints');
Strophe.addNamespace('HTTPUPLOAD', 'urn:xmpp:http:upload:0');
Strophe.addNamespace('MAM', 'urn:xmpp:mam:2');
Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx');
Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
2018-01-04 17:58:16 +01:00
Strophe.addNamespace('SID', 'urn:xmpp:sid:0');
2018-02-14 16:53:07 +01:00
Strophe.addNamespace('SPOILER', 'urn:xmpp:spoiler:0');
2018-05-08 19:58:12 +02:00
Strophe.addNamespace('VCARD', 'vcard-temp');
Strophe.addNamespace('VCARDUPDATE', 'vcard-temp:x:update');
Strophe.addNamespace('XFORM', 'jabber:x:data'); // Use Mustache style syntax for variable interpolation
/* Configuration of Lodash templates (this config is distinct to the
* config of requirejs-tpl in main.js). This one is for normal inline templates.
*/
_.templateSettings = {
'escape': /\{\{\{([\s\S]+?)\}\}\}/g,
'evaluate': /\{\[([\s\S]+?)\]\}/g,
'interpolate': /\{\{([\s\S]+?)\}\}/g,
'imports': {
'_': _
}
};
var _converse = {
'templates': {},
'promises': {}
};
2018-03-25 21:21:43 +02:00
_.extend(_converse, Backbone.Events); // Core plugins are whitelisted automatically
2018-05-15 10:32:13 +02:00
_converse.core_plugins = ['converse-bookmarks', 'converse-chatboxes', 'converse-chatview', 'converse-caps', 'converse-controlbox', 'converse-core', 'converse-disco', 'converse-dragresize', 'converse-embedded', 'converse-fullscreen', 'converse-headline', 'converse-mam', 'converse-message-view', 'converse-minimize', 'converse-modal', 'converse-muc', 'converse-muc-views', 'converse-notification', 'converse-otr', 'converse-ping', 'converse-profile', 'converse-register', 'converse-roomslist', 'converse-roster', 'converse-rosterview', 'converse-singleton', 'converse-spoilers', 'converse-vcard']; // Make converse pluggable
pluggable.enable(_converse, '_converse', 'pluggable'); // Module-level constants
_converse.STATUS_WEIGHTS = {
'offline': 6,
'unavailable': 5,
'xa': 4,
'away': 3,
'dnd': 2,
'chat': 1,
// We currently don't differentiate between "chat" and "online"
'online': 1
};
_converse.PRETTY_CHAT_STATUS = {
'offline': 'Offline',
'unavailable': 'Unavailable',
'xa': 'Extended Away',
'away': 'Away',
'dnd': 'Do not disturb',
'chat': 'Chattty',
'online': 'Online'
};
_converse.ANONYMOUS = "anonymous";
_converse.CLOSED = 'closed';
_converse.EXTERNAL = "external";
_converse.LOGIN = "login";
_converse.LOGOUT = "logout";
_converse.OPENED = 'opened';
_converse.PREBIND = "prebind";
_converse.CONNECTION_STATUS = {
0: 'ERROR',
1: 'CONNECTING',
2: 'CONNFAIL',
3: 'AUTHENTICATING',
4: 'AUTHFAIL',
5: 'CONNECTED',
6: 'DISCONNECTED',
7: 'DISCONNECTING',
8: 'ATTACHED',
9: 'REDIRECT',
10: 'RECONNECTING'
};
_converse.SUCCESS = 'success';
_converse.FAILURE = 'failure';
_converse.DEFAULT_IMAGE_TYPE = 'image/png';
_converse.DEFAULT_IMAGE = "iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAIAAABt+uBvAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gwHCy455JBsggAABkJJREFUeNrtnM1PE1sUwHvvTD8otWLHST/Gimi1CEgr6M6FEWuIBo2pujDVsNDEP8GN/4MbN7oxrlipG2OCgZgYlxAbkRYw1KqkIDRCSkM7nXvvW8x7vjyNeQ9m7p1p3z1LQk/v/Dhz7vkEXL161cHl9wI5Ag6IA+KAOCAOiAPigDggLhwQB2S+iNZ+PcYY/SWEEP2HAAAIoSAIoihCCP+ngDDGtVotGAz29/cfOXJEUZSOjg6n06lp2sbGRqlUWlhYyGazS0tLbrdbEASrzgksyeYJId3d3el0uqenRxRFAAAA4KdfIIRgjD9+/Pj8+fOpqSndslofEIQwHA6Pjo4mEon//qmFhYXHjx8vLi4ihBgDEnp7e9l8E0Jo165dQ0NDd+/eDYVC2/qsJElDQ0OEkKWlpa2tLZamxAhQo9EIBoOjo6MXL17csZLe3l5FUT59+lQul5l5JRaAVFWNRqN37tw5ceKEQVWRSOTw4cOFQuHbt2+iKLYCIISQLMu3b99OJpOmKAwEAgcPHszn8+vr6wzsiG6UQQhxuVyXLl0aGBgwUW0sFstkMl6v90fo1KyAMMYDAwPnzp0zXfPg4GAqlWo0Gk0MiBAiy/L58+edTqf5Aa4onj59OhaLYYybFRCEMBaL0fNxBw4cSCQStN0QRUBut3t4eJjq6U+dOiVJElVPRBFQIBDo6+ujCqirqyscDlONGykC2lYyYSR6pBoQQapHZwAoHo/TuARYAOrs7GQASFEUqn6aIiBJkhgA6ujooFpUo6iaTa7koFwnaoWadLNe81tbWwzoaJrWrICWl5cZAFpbW6OabVAEtLi4yABQsVjUNK0pAWWzWQaAcrlcswKanZ1VVZUqHYRQEwOq1Wpv3ryhCmh6erpcLjdrNl+v1ycnJ+l5UELI27dvv3//3qxxEADgy5cvExMT9Mznw4cPtFtAdAPFarU6Pj5eKpVM17yxsfHy5cvV1VXazXu62gVBKBQKT58+rdVqJqrFGL948eLdu3dU8/g/H4FBUaJYLAqC0NPTY9brMD4+PjY25mDSracOCABACJmZmXE6nUePHjWu8NWrV48ePSKEsGlAs7Agfd5nenq6Wq0mk0kjDzY2NvbkyRMIIbP2PLvhBUEQ8vl8NpuNx+M+n29bzhVjvLKycv/+/YmJCcazQuwA6YzW1tYmJyf1SY+2trZ/rRk1Go1SqfT69esHDx4UCgVmNaa/zZ/9ABUhRFXVYDB48uTJeDweiUQkSfL7/T9MA2NcqVTK5fLy8vL8/PzU1FSxWHS5XJaM4wGr9sUwxqqqer3eUCgkSZJuUBBCfTRvc3OzXC6vrKxUKhWn02nhCJ5lM4oQQo/HgxD6+vXr58+fHf8sDOp+HQDg8XgclorFU676dKLlo6yWRdItIBwQB8QBcUCtfosRQjRNQwhhjPUC4w46WXryBSHU1zgEQWBz99EFhDGu1+t+v//48ePxeFxRlD179ng8nh0Efgiher2+vr6ur3HMzMysrq7uTJVdACGEurq6Ll++nEgkPB7Pj9jPoDHqOxyqqubz+WfPnuVyuV9XPeyeagAAAoHArVu3BgcHab8CuVzu4cOHpVKJUnfA5GweY+xyuc6cOXPv3r1IJMLAR8iyPDw8XK/Xi8Wiqqqmm5KZgBBC7e3tN27cuHbtGuPVpf7+/lAoNDs7W61WzfVKpgHSSzw3b95MpVKW3MfRaDQSiczNzVUqFRMZmQOIEOL1eq9fv3727FlL1t50URRFluX5+flqtWpWEGAOIFEUU6nUlStXLKSjy759+xwOx9zcnKZpphzGHMzhcDiTydgk9r1w4YIp7RPTAAmCkMlk2FeLf/tIEKbTab/fbwtAhJBoNGrutpNx6e7uPnTokC1eMU3T0um0DZPMkZER6wERQnw+n/FFSxpy7Nix3bt3WwwIIcRgIWnHkkwmjecfRgGx7DtuV/r6+iwGhDHev3+/bQF1dnYaH6E2CkiWZdsC2rt3r8WAHA5HW1ubbQGZcjajgOwTH/4qNko1Wlg4IA6IA+KAOKBWBUQIsfNojyliKIoRRfH9+/dut9umf3wzpoUNNQ4BAJubmwz+ic+OxefzWWlBhJD29nbug7iT5sIBcUAcEAfEAXFAHBAHxOVn+QMrmWpuPZx12gAAAABJRU5ErkJggg==";
_converse.log = function (message, level) {
var style = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
/* Logs messages to the browser's developer console.
*
* Parameters:
* (String) message - The message to be logged.
* (Integer) level - The loglevel which allows for filtering of log
* messages.
*
* Available loglevels are 0 for 'debug', 1 for 'info', 2 for 'warn',
* 3 for 'error' and 4 for 'fatal'.
*
* When using the 'error' or 'warn' loglevels, a full stacktrace will be
* logged as well.
*/
if (level === Strophe.LogLevel.ERROR || level === Strophe.LogLevel.FATAL) {
style = style || 'color: maroon';
}
if (message instanceof Error) {
message = message.stack;
}
var prefix = style ? '%c' : '';
var logger = _.assign({
'debug': _.get(console, 'log') ? console.log.bind(console) : _.noop,
'error': _.get(console, 'log') ? console.log.bind(console) : _.noop,
'info': _.get(console, 'log') ? console.log.bind(console) : _.noop,
'warn': _.get(console, 'log') ? console.log.bind(console) : _.noop
}, console);
2016-03-07 18:54:07 +01:00
if (level === Strophe.LogLevel.ERROR) {
2018-01-29 16:33:30 +01:00
logger.error("".concat(prefix, " ERROR: ").concat(message), style);
} else if (level === Strophe.LogLevel.WARN) {
if (_converse.debug) {
logger.warn("".concat(prefix, " ").concat(moment().format(), " WARNING: ").concat(message), style);
}
} else if (level === Strophe.LogLevel.FATAL) {
2018-01-29 16:33:30 +01:00
logger.error("".concat(prefix, " FATAL: ").concat(message), style);
} else if (_converse.debug) {
if (level === Strophe.LogLevel.DEBUG) {
logger.debug("".concat(prefix, " ").concat(moment().format(), " DEBUG: ").concat(message), style);
} else {
logger.info("".concat(prefix, " ").concat(moment().format(), " INFO: ").concat(message), style);
}
}
};
Strophe.log = function (level, msg) {
_converse.log(level + ' ' + msg, level);
};
2017-07-22 22:21:05 +02:00
Strophe.error = function (msg) {
_converse.log(msg, Strophe.LogLevel.ERROR);
};
2017-07-22 22:21:05 +02:00
_converse.__ = function (str) {
/* Translate the given string based on the current locale.
*
* Parameters:
* (String) str - The string to translate.
*/
if (_.isUndefined(i18n)) {
return str;
}
return i18n.translate.apply(i18n, arguments);
};
2016-03-07 18:54:07 +01:00
var __ = _converse.__;
2018-05-07 18:20:15 +02:00
var PROMISES = ['initialized', 'connectionInitialized', 'pluginsInitialized', 'statusInitialized'];
function addPromise(promise) {
/* Private function, used to add a new promise to the ones already
* available via the `waitUntil` api method.
*/
2018-03-05 14:43:53 +01:00
_converse.promises[promise] = u.getResolveablePromise();
}
_converse.emit = function (name) {
/* Event emitter and promise resolver */
_converse.trigger.apply(this, arguments);
var promise = _converse.promises[name];
2017-07-22 22:21:05 +02:00
if (!_.isUndefined(promise)) {
promise.resolve();
2017-02-13 17:16:13 +01:00
}
};
_converse.router = new Backbone.Router();
2017-02-13 17:16:13 +01:00
_converse.initialize = function (settings, callback) {
"use strict";
var _this = this;
2017-02-13 17:16:13 +01:00
settings = !_.isUndefined(settings) ? settings : {};
2018-03-05 14:43:53 +01:00
var init_promise = u.getResolveablePromise();
_.each(PROMISES, addPromise);
if (!_.isUndefined(_converse.connection)) {
// Looks like _converse.initialized was called again without logging
// out or disconnecting in the previous session.
// This happens in tests. We therefore first clean up.
Backbone.history.stop();
2017-12-20 18:08:08 +01:00
_converse.chatboxviews.closeAllChatBoxes();
2018-05-23 04:27:33 +02:00
if (_converse.bookmarks) {
_converse.bookmarks.reset();
}
delete _converse.controlboxtoggle;
2017-12-20 18:08:08 +01:00
delete _converse.chatboxviews;
_converse.connection.reset();
_converse.off();
_converse.stopListening();
_converse._tearDown();
}
var unloadevent;
if ('onpagehide' in window) {
// Pagehide gets thrown in more cases than unload. Specifically it
// gets thrown when the page is cached and not just
// closed/destroyed. It's the only viable event on mobile Safari.
// https://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/
unloadevent = 'pagehide';
} else if ('onbeforeunload' in window) {
unloadevent = 'beforeunload';
} else if ('onunload' in window) {
unloadevent = 'unload';
} // Instance level constants
this.TIMEOUTS = {
// Set as module attr so that we can override in tests.
'PAUSED': 10000,
'INACTIVE': 90000
}; // XEP-0085 Chat states
// http://xmpp.org/extensions/xep-0085.html
this.INACTIVE = 'inactive';
this.ACTIVE = 'active';
this.COMPOSING = 'composing';
this.PAUSED = 'paused';
this.GONE = 'gone'; // Default configuration values
// ----------------------------
this.default_settings = {
allow_contact_requests: true,
allow_non_roster_messaging: false,
animate: true,
authentication: 'login',
// Available values are "login", "prebind", "anonymous" and "external".
auto_away: 0,
// Seconds after which user status is set to 'away'
auto_login: false,
// Currently only used in connection with anonymous login
2018-02-14 16:53:07 +01:00
auto_reconnect: true,
auto_subscribe: false,
auto_xa: 0,
// Seconds after which user status is set to 'xa'
blacklisted_plugins: [],
bosh_service_url: undefined,
connection_options: {},
credentials_url: null,
// URL from where login credentials can be fetched
csi_waiting_time: 0,
// Support for XEP-0352. Seconds before client is considered idle and CSI is sent out.
debug: false,
default_state: 'online',
expose_rid_and_sid: false,
filter_by_resource: false,
forward_messages: false,
geouri_regex: /https:\/\/www.openstreetmap.org\/.*#map=[0-9]+\/([\-0-9.]+)\/([\-0-9.]+)\S*/g,
geouri_replacement: 'https://www.openstreetmap.org/?mlat=$1&mlon=$2#map=18/$1/$2',
hide_offline_users: false,
include_offline_state: false,
jid: undefined,
keepalive: true,
2018-02-14 16:53:07 +01:00
locales_url: 'locale/{{{locale}}}/LC_MESSAGES/converse.json',
locales: ['af', 'ar', 'bg', 'ca', 'de', 'es', 'eu', 'en', 'fr', 'he', 'hu', 'id', 'it', 'ja', 'nb', 'nl', 'pl', 'pt_BR', 'ru', 'tr', 'uk', 'zh_CN', 'zh_TW'],
message_carbons: true,
2018-03-27 18:26:56 +02:00
nickname: undefined,
password: undefined,
prebind_url: null,
priority: 0,
registration_domain: '',
rid: undefined,
2018-02-14 16:53:07 +01:00
root: window.document,
show_only_online_users: false,
show_send_button: false,
sid: undefined,
storage: 'session',
strict_plugin_dependencies: false,
synchronize_availability: true,
2018-05-23 04:27:33 +02:00
trusted: true,
view_mode: 'overlayed',
// Choices are 'overlayed', 'fullscreen', 'mobile'
websocket_url: undefined,
2018-03-25 21:21:43 +02:00
whitelisted_plugins: []
};
_.assignIn(this, this.default_settings); // Allow only whitelisted configuration attributes to be overwritten
_.assignIn(this, _.pick(settings, _.keys(this.default_settings)));
if (this.authentication === _converse.ANONYMOUS) {
if (this.auto_login && !this.jid) {
throw new Error("Config Error: you need to provide the server's " + "domain via the 'jid' option when using anonymous " + "authentication with auto_login.");
}
}
/* Localisation */
if (!_.isUndefined(i18n)) {
i18n.setLocales(settings.i18n, _converse);
} else {
_converse.locale = 'en';
} // Module-level variables
// ----------------------
this.callback = callback || _.noop;
/* When reloading the page:
* For new sessions, we need to send out a presence stanza to notify
* the server/network that we're online.
* When re-attaching to an existing session (e.g. via the keepalive
* option), we don't need to again send out a presence stanza, because
* it's as if "we never left" (see onConnectStatusChanged).
* https://github.com/jcbrand/converse.js/issues/521
*/
this.send_initial_presence = true;
this.msg_counter = 0;
this.user_settings = settings; // Save the user settings so that they can be used by plugins
// Module-level functions
// ----------------------
this.generateResource = function () {
2018-01-29 16:33:30 +01:00
return "/converse.js-".concat(Math.floor(Math.random() * 139749528).toString());
};
2016-03-07 18:54:07 +01:00
this.sendCSI = function (stat) {
/* Send out a Chat Status Notification (XEP-0352)
*
* Parameters:
* (String) stat: The user's chat status
*/
/* Send out a Chat Status Notification (XEP-0352) */
// XXX if (converse.features[Strophe.NS.CSI] || true) {
_converse.connection.send($build(stat, {
xmlns: Strophe.NS.CSI
}));
2016-03-07 18:54:07 +01:00
_converse.inactive = stat === _converse.INACTIVE ? true : false;
};
this.onUserActivity = function () {
/* Resets counters and flags relating to CSI and auto_away/auto_xa */
if (_converse.idle_seconds > 0) {
_converse.idle_seconds = 0;
}
if (!_converse.connection.authenticated) {
// We can't send out any stanzas when there's no authenticated connection.
// converse can happen when the connection reconnects.
return;
}
if (_converse.inactive) {
_converse.sendCSI(_converse.ACTIVE);
}
if (_converse.auto_changed_status === true) {
_converse.auto_changed_status = false; // XXX: we should really remember the original state here, and
// then set it back to that...
2017-02-13 17:16:13 +01:00
2018-03-25 21:21:43 +02:00
_converse.xmppstatus.set('status', _converse.default_state);
}
};
2017-02-13 17:16:13 +01:00
this.onEverySecond = function () {
/* An interval handler running every second.
* Used for CSI and the auto_away and auto_xa features.
*/
if (!_converse.connection.authenticated) {
// We can't send out any stanzas when there's no authenticated connection.
// This can happen when the connection reconnects.
return;
}
2017-02-13 17:16:13 +01:00
2018-03-25 21:21:43 +02:00
var stat = _converse.xmppstatus.get('status');
_converse.idle_seconds++;
if (_converse.csi_waiting_time > 0 && _converse.idle_seconds > _converse.csi_waiting_time && !_converse.inactive) {
_converse.sendCSI(_converse.INACTIVE);
}
if (_converse.auto_away > 0 && _converse.idle_seconds > _converse.auto_away && stat !== 'away' && stat !== 'xa' && stat !== 'dnd') {
_converse.auto_changed_status = true;
2018-03-25 21:21:43 +02:00
_converse.xmppstatus.set('status', 'away');
} else if (_converse.auto_xa > 0 && _converse.idle_seconds > _converse.auto_xa && stat !== 'xa' && stat !== 'dnd') {
_converse.auto_changed_status = true;
2018-03-25 21:21:43 +02:00
_converse.xmppstatus.set('status', 'xa');
}
};
this.registerIntervalHandler = function () {
/* Set an interval of one second and register a handler for it.
* Required for the auto_away, auto_xa and csi_waiting_time features.
*/
if (_converse.auto_away < 1 && _converse.auto_xa < 1 && _converse.csi_waiting_time < 1) {
// Waiting time of less then one second means features aren't used.
return;
}
_converse.idle_seconds = 0;
_converse.auto_changed_status = false; // Was the user's status changed by _converse.js?
2017-02-13 17:16:13 +01:00
window.addEventListener('click', _converse.onUserActivity);
window.addEventListener('focus', _converse.onUserActivity);
window.addEventListener('keypress', _converse.onUserActivity);
window.addEventListener('mousemove', _converse.onUserActivity);
window.addEventListener(unloadevent, _converse.onUserActivity);
_converse.everySecondTrigger = window.setInterval(_converse.onEverySecond, 1000);
};
2017-02-13 17:16:13 +01:00
this.setConnectionStatus = function (connection_status, message) {
_converse.connfeedback.set({
'connection_status': connection_status,
'message': message
});
};
2017-02-13 17:16:13 +01:00
this.rejectPresenceSubscription = function (jid, message) {
/* Reject or cancel another user's subscription to our presence updates.
*
* Parameters:
* (String) jid - The Jabber ID of the user whose subscription
* is being canceled.
* (String) message - An optional message to the user
*/
var pres = $pres({
to: jid,
type: "unsubscribed"
});
2017-02-13 17:16:13 +01:00
if (message && message !== "") {
pres.c("status").t(message);
}
2017-02-13 17:16:13 +01:00
_converse.connection.send(pres);
};
2017-02-13 17:16:13 +01:00
this.reconnect = _.debounce(function () {
_converse.log('RECONNECTING');
_converse.log('The connection has dropped, attempting to reconnect.');
_converse.setConnectionStatus(Strophe.Status.RECONNECTING, __('The connection has dropped, attempting to reconnect.'));
_converse.connection.reconnecting = true;
2017-04-04 17:26:06 +02:00
_converse._tearDown();
_converse.logIn(null, true);
}, 3000, {
'leading': true
});
2017-02-13 17:16:13 +01:00
this.disconnect = function () {
_converse.log('DISCONNECTED');
2017-07-22 22:21:05 +02:00
delete _converse.connection.reconnecting;
2016-03-07 18:54:07 +01:00
_converse.connection.reset();
2017-02-13 17:16:13 +01:00
_converse._tearDown();
2017-04-23 19:02:44 +02:00
_converse.emit('disconnected');
};
2017-06-23 20:25:33 +02:00
this.onDisconnected = function () {
/* Gets called once strophe's status reaches Strophe.Status.DISCONNECTED.
* Will either start a teardown process for converse.js or attempt
* to reconnect.
*/
var reason = _converse.disconnection_reason;
if (_converse.disconnection_cause === Strophe.Status.AUTHFAIL) {
if (_converse.credentials_url && _converse.auto_reconnect) {
/* In this case, we reconnect, because we might be receiving
* expirable tokens from the credentials_url.
*/
_converse.emit('will-reconnect');
return _converse.reconnect();
} else {
return _converse.disconnect();
}
} else if (_converse.disconnection_cause === _converse.LOGOUT || !_.isUndefined(reason) && reason === _.get(Strophe, 'ErrorCondition.NO_AUTH_MECH') || reason === "host-unknown" || reason === "remote-connection-failed" || !_converse.auto_reconnect) {
return _converse.disconnect();
}
2017-02-13 17:16:13 +01:00
_converse.emit('will-reconnect');
_converse.reconnect();
};
2017-02-13 17:16:13 +01:00
this.setDisconnectionCause = function (cause, reason, override) {
/* Used to keep track of why we got disconnected, so that we can
* decide on what the next appropriate action is (in onDisconnected)
*/
if (_.isUndefined(cause)) {
delete _converse.disconnection_cause;
delete _converse.disconnection_reason;
} else if (_.isUndefined(_converse.disconnection_cause) || override) {
_converse.disconnection_cause = cause;
_converse.disconnection_reason = reason;
}
};
2017-02-13 17:16:13 +01:00
this.onConnectStatusChanged = function (status, message) {
/* Callback method called by Strophe as the Strophe.Connection goes
* through various states while establishing or tearing down a
* connection.
*/
_converse.log("Status changed to: ".concat(_converse.CONNECTION_STATUS[status]));
2017-06-23 20:25:33 +02:00
if (status === Strophe.Status.CONNECTED || status === Strophe.Status.ATTACHED) {
_converse.setConnectionStatus(status); // By default we always want to send out an initial presence stanza.
2017-02-13 17:16:13 +01:00
_converse.send_initial_presence = true;
2017-02-13 17:16:13 +01:00
_converse.setDisconnectionCause();
2017-02-13 17:16:13 +01:00
if (_converse.connection.reconnecting) {
_converse.log(status === Strophe.Status.CONNECTED ? 'Reconnected' : 'Reattached');
2017-07-22 22:21:05 +02:00
_converse.onConnected(true);
} else {
_converse.log(status === Strophe.Status.CONNECTED ? 'Connected' : 'Attached');
if (_converse.connection.restored) {
// No need to send an initial presence stanza when
// we're restoring an existing session.
_converse.send_initial_presence = false;
}
_converse.onConnected();
}
} else if (status === Strophe.Status.DISCONNECTED) {
_converse.setDisconnectionCause(status, message);
_converse.onDisconnected();
} else if (status === Strophe.Status.ERROR) {
_converse.setConnectionStatus(status, __('An error occurred while connecting to the chat server.'));
} else if (status === Strophe.Status.CONNECTING) {
_converse.setConnectionStatus(status);
} else if (status === Strophe.Status.AUTHENTICATING) {
_converse.setConnectionStatus(status);
} else if (status === Strophe.Status.AUTHFAIL) {
if (!message) {
message = __('Your Jabber ID and/or password is incorrect. Please try again.');
}
_converse.setConnectionStatus(status, message);
_converse.setDisconnectionCause(status, message, true);
_converse.onDisconnected();
} else if (status === Strophe.Status.CONNFAIL) {
var feedback = message;
if (message === "host-unknown" || message == "remote-connection-failed") {
2018-01-17 19:45:33 +01:00
feedback = __("Sorry, we could not connect to the XMPP host with domain: %1$s", "\"".concat(Strophe.getDomainFromJid(_converse.connection.jid), "\""));
} else if (!_.isUndefined(message) && message === _.get(Strophe, 'ErrorCondition.NO_AUTH_MECH')) {
feedback = __("The XMPP server did not offer a supported authentication mechanism");
}
_converse.setConnectionStatus(status, feedback);
_converse.setDisconnectionCause(status, message);
} else if (status === Strophe.Status.DISCONNECTING) {
_converse.setDisconnectionCause(status, message);
}
};
this.incrementMsgCounter = function () {
this.msg_counter += 1;
var unreadMsgCount = this.msg_counter;
2018-02-14 16:53:07 +01:00
var title = document.title;
if (_.isNil(title)) {
return;
}
2016-05-03 17:37:10 +02:00
2018-02-14 16:53:07 +01:00
if (title.search(/^Messages \(\d+\) /) === -1) {
title = "Messages (".concat(unreadMsgCount, ") ").concat(title);
} else {
2018-02-14 16:53:07 +01:00
title = title.replace(/^Messages \(\d+\) /, "Messages (".concat(unreadMsgCount, ")"));
}
};
this.clearMsgCounter = function () {
this.msg_counter = 0;
2018-02-14 16:53:07 +01:00
var title = document.title;
2018-02-14 16:53:07 +01:00
if (_.isNil(title)) {
return;
}
if (title.search(/^Messages \(\d+\) /) !== -1) {
title = title.replace(/^Messages \(\d+\) /, "");
}
};
2018-03-25 21:21:43 +02:00
this.initStatus = function (reconnecting) {
// If there's no xmppstatus obj, then we were never connected to
// begin with, so we set reconnecting to false.
reconnecting = _.isUndefined(_converse.xmppstatus) ? false : reconnecting;
if (reconnecting) {
_converse.onStatusInitialized(reconnecting);
} else {
_this.xmppstatus = new _this.XMPPStatus();
var id = b64_sha1("converse.xmppstatus-".concat(_converse.bare_jid));
_this.xmppstatus.id = id; // Appears to be necessary for backbone.browserStorage
_this.xmppstatus.browserStorage = new Backbone.BrowserStorage[_converse.storage](id);
_this.xmppstatus.fetch({
2018-03-25 21:21:43 +02:00
success: _.partial(_converse.onStatusInitialized, reconnecting),
error: _.partial(_converse.onStatusInitialized, reconnecting)
});
2018-03-25 21:21:43 +02:00
}
2017-07-22 22:21:05 +02:00
};
this.initSession = function () {
_converse.session = new Backbone.Model();
var id = b64_sha1('converse.bosh-session');
_converse.session.id = id; // Appears to be necessary for backbone.browserStorage
_converse.session.browserStorage = new Backbone.BrowserStorage[_converse.storage](id);
_converse.session.fetch();
2017-07-22 22:21:05 +02:00
};
this.clearSession = function () {
2018-05-23 04:27:33 +02:00
if (!_converse.trusted) {
window.localStorage.clear();
window.sessionStorage.clear();
} else if (!_.isUndefined(this.session) && this.session.browserStorage) {
this.session.browserStorage._clear();
}
2018-05-23 04:27:33 +02:00
_converse.emit('clearSession');
};
this.logOut = function () {
_converse.clearSession();
_converse.setDisconnectionCause(_converse.LOGOUT, undefined, true);
if (!_.isUndefined(_converse.connection)) {
_converse.connection.disconnect();
} else {
_converse._tearDown();
2018-03-05 14:43:53 +01:00
} // Recreate all the promises
_.each(_.keys(_converse.promises), addPromise);
_converse.emit('logout');
};
this.saveWindowState = function (ev, hidden) {
// XXX: eventually we should be able to just use
// document.visibilityState (when we drop support for older
// browsers).
var state;
var event_map = {
'focus': "visible",
'focusin': "visible",
'pageshow': "visible",
'blur': "hidden",
'focusout': "hidden",
'pagehide': "hidden"
};
ev = ev || document.createEvent('Events');
if (ev.type in event_map) {
state = event_map[ev.type];
} else {
state = document[hidden] ? "hidden" : "visible";
}
if (state === 'visible') {
_converse.clearMsgCounter();
}
_converse.windowState = state;
_converse.emit('windowStateChanged', {
state: state
});
};
this.registerGlobalEventHandlers = function () {
// Taken from:
// http://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active
var hidden = "hidden"; // Standards:
if (hidden in document) {
document.addEventListener("visibilitychange", _.partial(_converse.saveWindowState, _, hidden));
} else if ((hidden = "mozHidden") in document) {
document.addEventListener("mozvisibilitychange", _.partial(_converse.saveWindowState, _, hidden));
} else if ((hidden = "webkitHidden") in document) {
document.addEventListener("webkitvisibilitychange", _.partial(_converse.saveWindowState, _, hidden));
} else if ((hidden = "msHidden") in document) {
document.addEventListener("msvisibilitychange", _.partial(_converse.saveWindowState, _, hidden));
} else if ("onfocusin" in document) {
// IE 9 and lower:
document.onfocusin = document.onfocusout = _.partial(_converse.saveWindowState, _, hidden);
} else {
// All others:
window.onpageshow = window.onpagehide = window.onfocus = window.onblur = _.partial(_converse.saveWindowState, _, hidden);
} // set the initial state (but only if browser supports the Page Visibility API)
if (document[hidden] !== undefined) {
_.partial(_converse.saveWindowState, _, hidden)({
type: document[hidden] ? "blur" : "focus"
});
}
};
this.enableCarbons = function () {
var _this2 = this;
/* Ask the XMPP server to enable Message Carbons
* See XEP-0280 https://xmpp.org/extensions/xep-0280.html#enabling
*/
if (!this.message_carbons || this.session.get('carbons_enabled')) {
return;
}
var carbons_iq = new Strophe.Builder('iq', {
2018-03-27 18:26:56 +02:00
'from': this.connection.jid,
'id': 'enablecarbons',
'type': 'set'
}).c('enable', {
xmlns: Strophe.NS.CARBONS
});
this.connection.addHandler(function (iq) {
if (iq.querySelectorAll('error').length > 0) {
_converse.log('An error occured while trying to enable message carbons.', Strophe.LogLevel.ERROR);
} else {
_this2.session.save({
carbons_enabled: true
});
_converse.log('Message carbons have been enabled.');
2017-07-22 22:21:05 +02:00
}
}, null, "iq", null, "enablecarbons");
this.connection.send(carbons_iq);
2017-07-22 22:21:05 +02:00
};
this.sendInitialPresence = function () {
if (_converse.send_initial_presence) {
_converse.xmppstatus.sendPresence();
}
};
2017-07-22 22:21:05 +02:00
this.onStatusInitialized = function (reconnecting) {
2018-05-07 18:20:15 +02:00
_converse.emit('statusInitialized', reconnecting);
2018-03-25 21:21:43 +02:00
if (reconnecting) {
_converse.emit('reconnected');
} else {
init_promise.resolve();
2017-07-22 22:21:05 +02:00
_converse.emit('initialized');
2018-03-25 21:21:43 +02:00
_converse.emit('connected');
}
};
this.setUserJid = function () {
_converse.jid = _converse.connection.jid;
_converse.bare_jid = Strophe.getBareJidFromJid(_converse.connection.jid);
_converse.resource = Strophe.getResourceFromJid(_converse.connection.jid);
_converse.domain = Strophe.getDomainFromJid(_converse.connection.jid);
};
this.onConnected = function (reconnecting) {
/* Called as soon as a new connection has been established, either
* by logging in or by attaching to an existing BOSH session.
*/
2018-03-25 21:21:43 +02:00
_converse.connection.flush(); // Solves problem of returned PubSub BOSH response not received by browser
2017-07-22 22:21:05 +02:00
_converse.setUserJid();
2017-07-22 22:21:05 +02:00
_converse.initSession();
2017-07-22 22:21:05 +02:00
2018-03-25 21:21:43 +02:00
_converse.enableCarbons();
2016-11-07 15:43:48 +01:00
2018-03-25 21:21:43 +02:00
_converse.initStatus(reconnecting);
};
2018-05-07 18:20:15 +02:00
this.ConnectionFeedback = Backbone.Model.extend({
defaults: {
2018-05-07 18:20:15 +02:00
'connection_status': Strophe.Status.DISCONNECTED,
'message': ''
},
2018-05-07 18:20:15 +02:00
initialize: function initialize() {
this.on('change', function () {
_converse.emit('connfeedback', _converse.connfeedback);
});
2018-05-07 18:20:15 +02:00
}
});
this.connfeedback = new this.ConnectionFeedback();
2018-05-15 10:32:13 +02:00
this.XMPPStatus = Backbone.Model.extend({
2018-05-07 18:20:15 +02:00
defaults: function defaults() {
return {
"jid": _converse.bare_jid,
2018-05-08 19:58:12 +02:00
"status": _converse.default_state
2018-05-07 18:20:15 +02:00
};
},
2018-05-07 18:20:15 +02:00
initialize: function initialize() {
var _this3 = this;
2018-03-27 18:26:56 +02:00
2018-05-08 19:58:12 +02:00
this.vcard = _converse.vcards.findWhere({
'jid': this.get('jid')
});
if (_.isNil(this.vcard)) {
this.vcard = _converse.vcards.create({
'jid': this.get('jid')
});
}
2018-05-07 18:20:15 +02:00
this.on('change:status', function (item) {
var status = _this3.get('status');
2018-05-07 18:20:15 +02:00
_this3.sendPresence(status);
2018-05-07 18:20:15 +02:00
_converse.emit('statusChanged', status);
});
2018-05-07 18:20:15 +02:00
this.on('change:status_message', function () {
var status_message = _this3.get('status_message');
2018-05-07 18:20:15 +02:00
_this3.sendPresence(_this3.get('status'), status_message);
2018-05-07 18:20:15 +02:00
_converse.emit('statusMessageChanged', status_message);
});
},
2018-05-07 18:20:15 +02:00
constructPresence: function constructPresence(type, status_message) {
var presence;
type = _.isString(type) ? type : this.get('status') || _converse.default_state;
status_message = _.isString(status_message) ? status_message : this.get('status_message'); // Most of these presence types are actually not explicitly sent,
// but I add all of them here for reference and future proofing.
2018-05-07 18:20:15 +02:00
if (type === 'unavailable' || type === 'probe' || type === 'error' || type === 'unsubscribe' || type === 'unsubscribed' || type === 'subscribe' || type === 'subscribed') {
presence = $pres({
'type': type
});
} else if (type === 'offline') {
presence = $pres({
'type': 'unavailable'
});
} else if (type === 'online') {
presence = $pres();
} else {
2018-05-07 18:20:15 +02:00
presence = $pres().c('show').t(type).up();
}
2018-05-07 18:20:15 +02:00
if (status_message) {
presence.c('status').t(status_message).up();
}
2017-07-22 22:21:05 +02:00
2018-05-07 18:20:15 +02:00
presence.c('priority').t(_.isNaN(Number(_converse.priority)) ? 0 : _converse.priority);
return presence;
},
sendPresence: function sendPresence(type, status_message) {
_converse.connection.send(this.constructPresence(type, status_message));
}
});
2017-07-22 22:21:05 +02:00
2018-05-07 18:20:15 +02:00
this.setUpXMLLogging = function () {
Strophe.log = function (level, msg) {
_converse.log(msg, level);
};
2018-05-07 18:20:15 +02:00
if (this.debug) {
this.connection.xmlInput = function (body) {
_converse.log(body.outerHTML, Strophe.LogLevel.DEBUG, 'color: darkgoldenrod');
};
2018-05-07 18:20:15 +02:00
this.connection.xmlOutput = function (body) {
_converse.log(body.outerHTML, Strophe.LogLevel.DEBUG, 'color: darkcyan');
};
}
};
2018-05-07 18:20:15 +02:00
this.fetchLoginCredentials = function () {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', _converse.credentials_url, true);
xhr.setRequestHeader('Accept', "application/json, text/javascript");
2018-05-07 18:20:15 +02:00
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 400) {
var data = JSON.parse(xhr.responseText);
resolve({
'jid': data.jid,
'password': data.password
});
} else {
xhr.onerror();
}
};
2018-05-07 18:20:15 +02:00
xhr.onerror = function () {
delete _converse.connection;
2018-05-07 18:20:15 +02:00
_converse.emit('noResumeableSession', this);
2018-05-07 18:20:15 +02:00
reject(xhr.responseText);
};
2018-05-07 18:20:15 +02:00
xhr.send();
});
};
2018-05-07 18:20:15 +02:00
this.startNewBOSHSession = function () {
var xhr = new XMLHttpRequest();
xhr.open('GET', _converse.prebind_url, true);
xhr.setRequestHeader('Accept', "application/json, text/javascript");
2018-05-07 18:20:15 +02:00
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 400) {
var data = JSON.parse(xhr.responseText);
2018-05-07 18:20:15 +02:00
_converse.connection.attach(data.jid, data.sid, data.rid, _converse.onConnectStatusChanged);
} else {
2018-05-07 18:20:15 +02:00
xhr.onerror();
}
};
2018-05-07 18:20:15 +02:00
xhr.onerror = function () {
delete _converse.connection;
2018-03-27 18:26:56 +02:00
2018-05-07 18:20:15 +02:00
_converse.emit('noResumeableSession', this);
};
2018-05-07 18:20:15 +02:00
xhr.send();
};
2017-02-13 17:16:13 +01:00
this.restoreBOSHSession = function (jid_is_required) {
/* Tries to restore a cached BOSH session. */
if (!this.jid) {
var msg = "restoreBOSHSession: tried to restore a \"keepalive\" session " + "but we don't have the JID for the user!";
2017-02-13 17:16:13 +01:00
if (jid_is_required) {
throw new Error(msg);
} else {
_converse.log(msg);
}
}
2017-02-13 17:16:13 +01:00
try {
this.connection.restore(this.jid, this.onConnectStatusChanged);
return true;
} catch (e) {
2017-12-20 18:08:08 +01:00
_converse.log("Could not restore session for jid: " + this.jid + " Error message: " + e.message, Strophe.LogLevel.WARN);
this.clearSession(); // If there's a roster, we want to clear it (see #555)
2017-07-22 22:21:05 +02:00
return false;
2017-07-22 22:21:05 +02:00
}
};
this.attemptPreboundSession = function (reconnecting) {
/* Handle session resumption or initialization when prebind is
* being used.
*/
if (!reconnecting) {
if (this.keepalive && this.restoreBOSHSession(true)) {
return;
} // No keepalive, or session resumption has failed.
if (this.jid && this.sid && this.rid) {
return this.connection.attach(this.jid, this.sid, this.rid, this.onConnectStatusChanged);
}
}
2017-07-22 22:21:05 +02:00
if (this.prebind_url) {
return this.startNewBOSHSession();
} else {
throw new Error("attemptPreboundSession: If you use prebind and not keepalive, " + "then you MUST supply JID, RID and SID values or a prebind_url.");
}
};
2017-07-22 22:21:05 +02:00
this.attemptNonPreboundSession = function (credentials, reconnecting) {
/* Handle session resumption or initialization when prebind is not being used.
*
* Two potential options exist and are handled in this method:
* 1. keepalive
* 2. auto_login
*/
if (!reconnecting && this.keepalive && this.restoreBOSHSession()) {
return;
}
2017-02-13 17:16:13 +01:00
if (credentials) {
// When credentials are passed in, they override prebinding
// or credentials fetching via HTTP
this.autoLogin(credentials);
} else if (this.auto_login) {
if (this.credentials_url) {
this.fetchLoginCredentials().then(this.autoLogin.bind(this), this.autoLogin.bind(this));
} else if (!this.jid) {
throw new Error("attemptNonPreboundSession: If you use auto_login, " + "you also need to give either a jid value (and if " + "applicable a password) or you need to pass in a URL " + "from where the username and password can be fetched " + "(via credentials_url).");
} else {
2018-05-23 04:27:33 +02:00
this.autoLogin(); // Could be ANONYMOUS or EXTERNAL
}
} else if (reconnecting) {
this.autoLogin();
}
};
2017-02-13 17:16:13 +01:00
this.autoLogin = function (credentials) {
if (credentials) {
// If passed in, the credentials come from credentials_url,
// so we set them on the converse object.
this.jid = credentials.jid;
}
2017-02-13 17:16:13 +01:00
2018-05-23 04:27:33 +02:00
if (this.authentication === _converse.ANONYMOUS || this.authentication === _converse.EXTERNAL) {
if (!this.jid) {
throw new Error("Config Error: when using anonymous login " + "you need to provide the server's domain via the 'jid' option. " + "Either when calling converse.initialize, or when calling " + "_converse.api.user.login.");
}
2017-02-13 17:16:13 +01:00
if (!this.connection.reconnecting) {
this.connection.reset();
}
2017-02-13 17:16:13 +01:00
this.connection.connect(this.jid.toLowerCase(), null, this.onConnectStatusChanged);
} else if (this.authentication === _converse.LOGIN) {
var password = _.isNil(credentials) ? _converse.connection.pass || this.password : credentials.password;
2017-06-23 20:25:33 +02:00
if (!password) {
if (this.auto_login) {
throw new Error("initConnection: If you use auto_login and " + "authentication='login' then you also need to provide a password.");
}
2017-06-23 20:25:33 +02:00
_converse.setDisconnectionCause(Strophe.Status.AUTHFAIL, undefined, true);
2017-06-23 20:25:33 +02:00
_converse.disconnect();
2017-07-22 22:21:05 +02:00
return;
}
var resource = Strophe.getResourceFromJid(this.jid);
if (!resource) {
this.jid = this.jid.toLowerCase() + _converse.generateResource();
2017-07-22 22:21:05 +02:00
} else {
this.jid = Strophe.getBareJidFromJid(this.jid).toLowerCase() + '/' + resource;
2017-07-22 22:21:05 +02:00
}
if (!this.connection.reconnecting) {
this.connection.reset();
}
this.connection.connect(this.jid, password, this.onConnectStatusChanged);
}
};
this.logIn = function (credentials, reconnecting) {
// We now try to resume or automatically set up a new session.
// Otherwise the user will be shown a login form.
if (this.authentication === _converse.PREBIND) {
this.attemptPreboundSession(reconnecting);
2017-07-22 22:21:05 +02:00
} else {
this.attemptNonPreboundSession(credentials, reconnecting);
2017-07-22 22:21:05 +02:00
}
};
2017-02-13 17:16:13 +01:00
this.initConnection = function () {
2018-05-07 18:20:15 +02:00
/* Creates a new Strophe.Connection instance if we don't already have one.
*/
if (!this.connection) {
if (!this.bosh_service_url && !this.websocket_url) {
throw new Error("initConnection: you must supply a value for either the bosh_service_url or websocket_url or both.");
}
2017-02-13 17:16:13 +01:00
if (('WebSocket' in window || 'MozWebSocket' in window) && this.websocket_url) {
this.connection = new Strophe.Connection(this.websocket_url, this.connection_options);
} else if (this.bosh_service_url) {
this.connection = new Strophe.Connection(this.bosh_service_url, _.assignIn(this.connection_options, {
'keepalive': this.keepalive
}));
} else {
throw new Error("initConnection: this browser does not support websockets and bosh_service_url wasn't specified.");
}
}
2017-02-13 17:16:13 +01:00
_converse.emit('connectionInitialized');
};
2017-02-13 17:16:13 +01:00
this._tearDown = function () {
/* Remove those views which are only allowed with a valid
* connection.
*/
_converse.emit('beforeTearDown');
2017-07-22 22:21:05 +02:00
if (!_.isUndefined(_converse.session)) {
_converse.session.destroy();
}
2017-02-13 17:16:13 +01:00
window.removeEventListener('click', _converse.onUserActivity);
window.removeEventListener('focus', _converse.onUserActivity);
window.removeEventListener('keypress', _converse.onUserActivity);
window.removeEventListener('mousemove', _converse.onUserActivity);
window.removeEventListener(unloadevent, _converse.onUserActivity);
window.clearInterval(_converse.everySecondTrigger);
2017-02-13 17:16:13 +01:00
_converse.emit('afterTearDown');
2017-02-13 17:16:13 +01:00
return _converse;
};
2017-02-13 17:16:13 +01:00
this.initPlugins = function () {
// If initialize gets called a second time (e.g. during tests), then we
// need to re-apply all plugins (for a new converse instance), and we
// therefore need to clear this array that prevents plugins from being
// initialized twice.
// If initialize is called for the first time, then this array is empty
// in any case.
_converse.pluggable.initialized_plugins = [];
2017-02-13 17:16:13 +01:00
var whitelist = _converse.core_plugins.concat(_converse.whitelisted_plugins);
2017-02-13 17:16:13 +01:00
2018-02-14 16:53:07 +01:00
if (_converse.view_mode === 'embedded') {
_.forEach([// eslint-disable-line lodash/prefer-map
2018-05-07 18:20:15 +02:00
"converse-bookmarks", "converse-controlbox", "converse-headline", "converse-register"], function (name) {
2018-02-14 16:53:07 +01:00
_converse.blacklisted_plugins.push(name);
});
}
_converse.pluggable.initializePlugins({
'updateSettings': function updateSettings() {
_converse.log("(DEPRECATION) " + "The `updateSettings` method has been deprecated. " + "Please use `_converse.api.settings.update` instead.", Strophe.LogLevel.WARN);
_converse.api.settings.update.apply(_converse, arguments);
},
'_converse': _converse
}, whitelist, _converse.blacklisted_plugins);
_converse.emit('pluginsInitialized');
}; // Initialization
// --------------
// This is the end of the initialize method.
2017-07-22 22:21:05 +02:00
if (settings.connection) {
this.connection = settings.connection;
}
2017-07-22 22:21:05 +02:00
function finishInitialization() {
_converse.initPlugins();
_converse.initConnection();
_converse.setUpXMLLogging();
_converse.logIn();
_converse.registerGlobalEventHandlers();
if (!Backbone.history.started) {
Backbone.history.start();
2017-07-22 22:21:05 +02:00
}
}
if (!_.isUndefined(_converse.connection) && _converse.connection.service === 'jasmine tests') {
finishInitialization();
return _converse;
} else if (_.isUndefined(i18n)) {
finishInitialization();
2017-07-22 22:21:05 +02:00
} else {
2018-03-05 14:43:53 +01:00
i18n.fetchTranslations(_converse.locale, _converse.locales, u.interpolate(_converse.locales_url, {
'locale': _converse.locale
2018-02-14 16:53:07 +01:00
})).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)).then(finishInitialization).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}
return init_promise;
}; // API methods only available to plugins
_converse.api = {
'connection': {
'connected': function connected() {
return _converse.connection && _converse.connection.connected || false;
},
'disconnect': function disconnect() {
_converse.connection.disconnect();
}
},
'emit': function emit() {
_converse.emit.apply(_converse, arguments);
},
'user': {
'jid': function jid() {
return _converse.connection.jid;
},
'login': function login(credentials) {
_converse.logIn(credentials);
},
'logout': function logout() {
_converse.logOut();
},
'status': {
'get': function get() {
return _converse.xmppstatus.get('status');
},
'set': function set(value, message) {
var data = {
'status': value
};
if (!_.includes(_.keys(_converse.STATUS_WEIGHTS), value)) {
throw new Error('Invalid availability value. See https://xmpp.org/rfcs/rfc3921.html#rfc.section.2.2.2.1');
}
if (_.isString(message)) {
data.status_message = message;
}
_converse.xmppstatus.sendPresence(value);
_converse.xmppstatus.save(data);
},
'message': {
'get': function get() {
return _converse.xmppstatus.get('status_message');
},
'set': function set(stat) {
_converse.xmppstatus.save({
'status_message': stat
});
}
2017-07-22 22:21:05 +02:00
}
}
},
'settings': {
'update': function update(settings) {
2018-03-05 14:43:53 +01:00
u.merge(_converse.default_settings, settings);
u.merge(_converse, settings);
u.applyUserSettings(_converse, settings, _converse.user_settings);
},
'get': function get(key) {
if (_.includes(_.keys(_converse.default_settings), key)) {
return _converse[key];
2017-07-22 22:21:05 +02:00
}
},
'set': function set(key, val) {
var o = {};
if (_.isObject(key)) {
_.assignIn(_converse, _.pick(key, _.keys(_converse.default_settings)));
} else if (_.isString("string")) {
o[key] = val;
_.assignIn(_converse, _.pick(o, _.keys(_converse.default_settings)));
2017-07-22 22:21:05 +02:00
}
}
},
'promises': {
'add': function add(promises) {
promises = _.isArray(promises) ? promises : [promises];
_.each(promises, addPromise);
}
},
'tokens': {
'get': function get(id) {
if (!_converse.expose_rid_and_sid || _.isUndefined(_converse.connection)) {
return null;
}
if (id.toLowerCase() === 'rid') {
return _converse.connection.rid || _converse.connection._proto.rid;
} else if (id.toLowerCase() === 'sid') {
return _converse.connection.sid || _converse.connection._proto.sid;
}
}
},
'listen': {
'once': _converse.once.bind(_converse),
'on': _converse.on.bind(_converse),
'not': _converse.off.bind(_converse),
'stanza': function stanza(name, options, handler) {
if (_.isFunction(options)) {
handler = options;
options = {};
} else {
options = options || {};
}
_converse.connection.addHandler(handler, options.ns, name, options.type, options.id, options.from, options);
}
},
'waitUntil': function waitUntil(name) {
var promise = _converse.promises[name];
if (_.isUndefined(promise)) {
return null;
}
return promise;
},
'send': function send(stanza) {
_converse.connection.send(stanza);
2017-07-22 22:21:05 +02:00
}
}; // The public API
2018-02-14 16:53:07 +01:00
window.converse = {
'initialize': function initialize(settings, callback) {
return _converse.initialize(settings, callback);
},
'plugins': {
'add': function add(name, plugin) {
plugin.__name__ = name;
if (!_.isUndefined(_converse.pluggable.plugins[name])) {
throw new TypeError("Error: plugin with name \"".concat(name, "\" has already been ") + 'registered!');
2017-07-22 22:21:05 +02:00
} else {
_converse.pluggable.plugins[name] = plugin;
2017-07-22 22:21:05 +02:00
}
}
},
'env': {
'$build': $build,
'$iq': $iq,
'$msg': $msg,
'$pres': $pres,
'Backbone': Backbone,
'Promise': Promise,
'Strophe': Strophe,
'_': _,
2018-02-14 16:53:07 +01:00
'f': f,
'b64_sha1': b64_sha1,
'moment': moment,
'sizzle': sizzle,
2018-03-05 14:43:53 +01:00
'utils': u
2017-07-22 22:21:05 +02:00
}
};
2018-03-05 14:43:53 +01:00
window.dispatchEvent(new CustomEvent('converse-loaded'));
2018-02-14 16:53:07 +01:00
return window.converse;
});
//# sourceMappingURL=converse-core.js.map;
define('tpl!field', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<field var="' +
__e(o.name) +
'">\n';
if (_.isArray(o.value)) { ;
__p += '\n ';
_.each(o.value,function(arrayValue) { ;
__p += '<value>' +
__e(arrayValue) +
'</value>';
}); ;
__p += '\n';
} else { ;
__p += '\n <value>' +
__e(o.value) +
'</value>\n';
} ;
__p += '</field>\n';
return __p
};});
define('tpl!select_option', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<option value="' +
__e(o.value) +
'" ';
if (o.selected) { ;
__p += ' selected="selected" ';
} ;
__p += ' >' +
__e(o.label) +
'</option>\n';
return __p
};});
define('tpl!form_select', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<label>\n ' +
__e(o.label) +
'\n <select name="' +
__e(o.name) +
'" ';
if (o.multiple) { ;
__p += ' multiple="multiple" ';
} ;
__p += '>' +
((__t = (o.options)) == null ? '' : __t) +
'</select>\n</label>\n';
return __p
};});
define('tpl!form_textarea', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<label class="label-ta">' +
__e(o.label) +
'</label>\n<textarea name="' +
__e(o.name) +
'">' +
__e(o.value) +
'</textarea>\n';
return __p
};});
define('tpl!form_checkbox', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<label class="checkbox" for="' +
__e(o.name) +
'">' +
__e(o.label) +
'<input name="' +
__e(o.name) +
'" type="' +
__e(o.type) +
'" ' +
__e(o.checked) +
'></label>\n\n';
return __p
};});
define('tpl!form_username', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
if (o.label) { ;
__p += '\n<label>\n ' +
__e(o.label) +
'\n</label>\n';
} ;
__p += '\n<div class="input-group">\n <input name="' +
__e(o.name) +
'" type="' +
__e(o.type) +
'"\n ';
if (o.value) { ;
__p += ' value="' +
__e(o.value) +
'" ';
} ;
__p += '\n ';
if (o.required) { ;
__p += ' class="required" ';
} ;
__p += ' />\n <span title="' +
__e(o.domain) +
'">' +
__e(o.domain) +
'</span>\n</div>\n';
return __p
};});
define('tpl!form_input', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<label>\n ' +
__e(o.label) +
'\n <input name="' +
__e(o.name) +
'" type="' +
__e(o.type) +
'" \n ';
if (o.placeholder) { ;
__p += ' placeholder="' +
__e(o.placeholder) +
'" ';
} ;
__p += '\n ';
if (o.value) { ;
__p += ' value="' +
__e(o.value) +
'" ';
} ;
__p += '\n ';
if (o.required) { ;
__p += ' class="required" ';
} ;
__p += ' >\n</label>\n';
return __p
};});
define('tpl!form_captcha', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
if (o.label) { ;
__p += '\n<label>\n ' +
__e(o.label) +
'\n</label>\n';
} ;
__p += '\n<img src="data:' +
__e(o.type) +
';base64,' +
__e(o.data) +
'">\n<input name="' +
__e(o.name) +
'" type="text" ';
if (o.required) { ;
__p += ' class="required" ';
} ;
__p += ' >\n\n\n';
return __p
};});
define('tpl!form_url', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<label>\n ' +
__e(o.label) +
'\n <a class="form-url" target="_blank" rel="noopener" href="' +
__e(o.value) +
'">' +
__e(o.value) +
'</a>\n</label>\n';
return __p
};});
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// This is the utilities module.
//
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define, escape, Jed */
(function (root, factory) {
define('form-utils',["sizzle", "lodash.noconflict", "utils", "tpl!field", "tpl!select_option", "tpl!form_select", "tpl!form_textarea", "tpl!form_checkbox", "tpl!form_username", "tpl!form_input", "tpl!form_captcha", "tpl!form_url"], factory);
})(this, function (sizzle, _, u, tpl_field, tpl_select_option, tpl_form_select, tpl_form_textarea, tpl_form_checkbox, tpl_form_username, tpl_form_input, tpl_form_captcha, tpl_form_url) {
"use strict";
var XFORM_TYPE_MAP = {
'text-private': 'password',
'text-single': 'text',
'fixed': 'label',
'boolean': 'checkbox',
'hidden': 'hidden',
'jid-multi': 'textarea',
'list-single': 'dropdown',
'list-multi': 'dropdown'
};
u.webForm2xForm = function (field) {
/* Takes an HTML DOM and turns it into an XForm field.
*
* Parameters:
* (DOMElement) field - the field to convert
*/
var value;
if (field.getAttribute('type') === 'checkbox') {
value = field.checked && 1 || 0;
2018-05-15 10:32:13 +02:00
} else if (field.tagName == "TEXTAREA") {
value = _.filter(field.value.split('\n'), _.trim);
2018-05-15 10:32:13 +02:00
} else if (field.tagName == "SELECT") {
value = u.getSelectValues(field);
} else {
value = field.value;
}
return u.stringToNode(tpl_field({
name: field.getAttribute('name'),
value: value
}));
};
u.xForm2webForm = function (field, stanza, domain) {
/* Takes a field in XMPP XForm (XEP-004: Data Forms) format
* and turns it into an HTML field.
*
* Returns either text or a DOM element (which is not ideal, but fine
* for now).
*
* Parameters:
* (XMLElement) field - the field to convert
*/
if (field.getAttribute('type')) {
if (field.getAttribute('type') === 'list-single' || field.getAttribute('type') === 'list-multi') {
var values = _.map(u.queryChildren(field, 'value'), _.partial(_.get, _, 'textContent'));
var options = _.map(u.queryChildren(field, 'option'), function (option) {
var value = _.get(option.querySelector('value'), 'textContent');
return tpl_select_option({
'value': value,
'label': option.getAttribute('label'),
2018-05-15 10:32:13 +02:00
'selected': _.includes(values, value),
'required': !_.isNil(field.querySelector('required'))
});
});
return tpl_form_select({
'name': field.getAttribute('var'),
'label': field.getAttribute('label'),
'options': options.join(''),
'multiple': field.getAttribute('type') === 'list-multi',
'required': !_.isNil(field.querySelector('required'))
});
} else if (field.getAttribute('type') === 'fixed') {
var text = _.get(field.querySelector('value'), 'textContent');
return '<p class="form-help">' + text + '</p>';
} else if (field.getAttribute('type') === 'jid-multi') {
return tpl_form_textarea({
'name': field.getAttribute('var'),
'label': field.getAttribute('label') || '',
'value': _.get(field.querySelector('value'), 'textContent'),
'required': !_.isNil(field.querySelector('required'))
});
} else if (field.getAttribute('type') === 'boolean') {
return tpl_form_checkbox({
'name': field.getAttribute('var'),
'type': XFORM_TYPE_MAP[field.getAttribute('type')],
'label': field.getAttribute('label') || '',
'checked': _.get(field.querySelector('value'), 'textContent') === "1" && 'checked="1"' || '',
'required': !_.isNil(field.querySelector('required'))
});
} else if (field.getAttribute('var') === 'url') {
return tpl_form_url({
'label': field.getAttribute('label') || '',
'value': _.get(field.querySelector('value'), 'textContent')
});
} else if (field.getAttribute('var') === 'username') {
return tpl_form_username({
'domain': ' @' + domain,
'name': field.getAttribute('var'),
'type': XFORM_TYPE_MAP[field.getAttribute('type')],
'label': field.getAttribute('label') || '',
'value': _.get(field.querySelector('value'), 'textContent'),
'required': !_.isNil(field.querySelector('required'))
});
} else {
return tpl_form_input({
'label': field.getAttribute('label') || '',
'name': field.getAttribute('var'),
'placeholder': null,
'required': !_.isNil(field.querySelector('required')),
'type': XFORM_TYPE_MAP[field.getAttribute('type')],
'value': _.get(field.querySelector('value'), 'textContent')
});
2018-03-25 21:21:43 +02:00
}
} else {
if (field.getAttribute('var') === 'ocr') {
// Captcha
var uri = field.querySelector('uri');
var el = sizzle('data[cid="' + uri.textContent.replace(/^cid:/, '') + '"]', stanza)[0];
return tpl_form_captcha({
'label': field.getAttribute('label'),
'name': field.getAttribute('var'),
'data': _.get(el, 'textContent'),
'type': uri.getAttribute('type'),
'required': !_.isNil(field.querySelector('required'))
});
2018-03-25 21:21:43 +02:00
}
}
};
return u;
});
//# sourceMappingURL=form.js.map;
/* jshint maxerr: 10000 */
/* jslint unused: true */
/* jshint shadow: true */
/* jshint -W075 */
(function(ns){
// this list must be ordered from largest length of the value array, index 0, to the shortest
ns.emojioneList = {":kiss_mm:":{"uc_base":"1f468-2764-1f48b-1f468","uc_output":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","uc_match":"1f468-2764-fe0f-1f48b-1f468","uc_greedy":"1f468-2764-1f48b-1f468","shortnames":[":couplekiss_mm:"],"category":"people"},":kiss_woman_man:":{"uc_base":"1f469-2764-1f48b-1f468","uc_output":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468","uc_match":"1f469-2764-fe0f-1f48b-1f468","uc_greedy":"1f469-2764-1f48b-1f468","shortnames":[],"category":"people"},":kiss_ww:":{"uc_base":"1f469-2764-1f48b-1f469","uc_output":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","uc_match":"1f469-2764-fe0f-1f48b-1f469","uc_greedy":"1f469-2764-1f48b-1f469","shortnames":[":couplekiss_ww:"],"category":"people"},":england:":{"uc_base":"1f3f4-e0067-e0062-e0065-e006e-e0067-e007f","uc_output":"1f3f4-e0067-e0062-e0065-e006e-e0067-e007f","uc_match":"1f3f4-e0067-e0062-e0065-e006e-e0067-e007f","uc_greedy":"1f3f4-e0067-e0062-e0065-e006e-e0067-e007f","shortnames":[],"category":"flags"},":scotland:":{"uc_base":"1f3f4-e0067-e0062-e0073-e0063-e0074-e007f","uc_output":"1f3f4-e0067-e0062-e0073-e0063-e0074-e007f","uc_match":"1f3f4-e0067-e0062-e0073-e0063-e0074-e007f","uc_greedy":"1f3f4-e0067-e0062-e0073-e0063-e0074-e007f","shortnames":[],"category":"flags"},":wales:":{"uc_base":"1f3f4-e0067-e0062-e0077-e006c-e0073-e007f","uc_output":"1f3f4-e0067-e0062-e0077-e006c-e0073-e007f","uc_match":"1f3f4-e0067-e0062-e0077-e006c-e0073-e007f","uc_greedy":"1f3f4-e0067-e0062-e0077-e006c-e0073-e007f","shortnames":[],"category":"flags"},":family_mmbb:":{"uc_base":"1f468-1f468-1f466-1f466","uc_output":"1f468-200d-1f468-200d-1f466-200d-1f466","uc_match":"1f468-1f468-1f466-1f466","uc_greedy":"1f468-1f468-1f466-1f466","shortnames":[],"category":"people"},":family_mmgb:":{"uc_base":"1f468-1f468-1f467-1f466","uc_output":"1f468-200d-1f468-200d-1f467-200d-1f466","uc_match":"1f468-1f468-1f467-1f466","uc_greedy":"1f468-1f468-1f467-1f466","shortnames":[],"category":"people"},":family_mmgg:":{"uc_base":"1f468-1f468-1f467-1f467","uc_output":"1f468-200d-1f468-200d-1f467-200d-1f467","uc_match":"1f468-1f468-1f467-1f467","uc_greedy":"1f468-1f468-1f467-1f467","shortnames":[],"category":"people"},":family_mwbb:":{"uc_base":"1f468-1f469-1f466-1f466","uc_output":"1f468-200d-1f469-200d-1f466-200d-1f466","uc_match":"1f468-1f469-1f466-1f466","uc_greedy":"1f468-1f469-1f466-1f466","shortnames":[],"category":"people"},":family_mwgb:":{"uc_base":"1f468-1f469-1f467-1f466","uc_output":"1f468-200d-1f469-200d-1f467-200d-1f466","uc_match":"1f468-1f469-1f467-1f466","uc_greedy":"1f468-1f469-1f467-1f466","shortnames":[],"category":"people"},":family_mwgg:":{"uc_base":"1f468-1f469-1f467-1f467","uc_output":"1f468-200d-1f469-200d-1f467-200d-1f467","uc_match":"1f468-1f469-1f467-1f467","uc_greedy":"1f468-1f469-1f467-1f467","shortnames":[],"category":"people"},":family_wwbb:":{"uc_base":"1f469-1f469-1f466-1f466","uc_output":"1f469-200d-1f469-200d-1f466-200d-1f466","uc_match":"1f469-1f469-1f466-1f466","uc_greedy":"1f469-1f469-1f466-1f466","shortnames":[],"category":"people"},":family_wwgb:":{"uc_base":"1f469-1f469-1f467-1f466","uc_output":"1f469-200d-1f469-200d-1f467-200d-1f466","uc_match":"1f469-1f469-1f467-1f466","uc_greedy":"1f469-1f469-1f467-1f466","shortnames":[],"category":"people"},":family_wwgg:":{"uc_base":"1f469-1f469-1f467-1f467","uc_output":"1f469-200d-1f469-200d-1f467-200d-1f467","uc_match":"1f469-1f469-1f467-1f467","uc_greedy":"1f469-1f469-1f467-1f467","shortnames":[],"category":"people"},":couple_mm:":{"uc_base":"1f468-2764-1f468","uc_output":"1f468-200d-2764-fe0f-200d-1f468","uc_match":"1f468-2764-fe0f-1f468","uc_greedy":"1f468-2764-1f468","shortnames":[":couple_with_heart_mm:"],"category":"people"},":couple_with_heart_woman_man:":{"uc_base":"1f469-2764-1f468","uc_output":"1f469-200d-2764-fe0f-200d-1f468","uc_match":"1f469-2764-fe0f-1f468","uc_greedy":"1f469-2764-1f468","shortnames":[],"category":"people"},":couple_ww:":{"uc_base":"1f469-2764-1f469","uc_output":"1f469-200d-2764-fe0f-200d-1f469","uc_match":"1f469-2764-fe0f-1f469","uc_greedy":"1f469-2764-1f469","shortnames":[":
var tmpShortNames = [], emoji;
for (emoji in ns.emojioneList) {
if (!ns.emojioneList.hasOwnProperty(emoji) || (emoji === '')) continue;
tmpShortNames.push(emoji.replace(/[+]/g, "\\$&"));
for (var i = 0; i < ns.emojioneList[emoji].shortnames.length; i++) {
tmpShortNames.push(ns.emojioneList[emoji].shortnames[i].replace(/[+]/g, "\\$&"));
}
2018-03-25 21:21:43 +02:00
}
ns.shortnames = tmpShortNames.join('|');
// javascript escapes here must be ordered from largest length to shortest
ns.jsEscapeMap = {"\uD83D\uDC69\u200D\u2764\uFE0F\u200D\uD83D\uDC8B\u200D\uD83D\uDC69":"1f469-2764-1f48b-1f469","\uD83D\uDC68\u200D\u2764\uFE0F\u200D\uD83D\uDC8B\u200D\uD83D\uDC68":"1f468-2764-1f48b-1f468","\uD83D\uDC69\u200D\u2764\uFE0F\u200D\uD83D\uDC8B\u200D\uD83D\uDC68":"1f469-2764-1f48b-1f468","\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67\uDB40\uDC7F":"1f3f4-e0067-e0062-e0065-e006e-e0067-e007f","\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74\uDB40\uDC7F":"1f3f4-e0067-e0062-e0073-e0063-e0074-e007f","\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73\uDB40\uDC7F":"1f3f4-e0067-e0062-e0077-e006c-e0073-e007f","\uD83D\uDC68\u200D\uD83D\uDC68\u200D\uD83D\uDC66\u200D\uD83D\uDC66":"1f468-1f468-1f466-1f466","\uD83D\uDC68\u200D\uD83D\uDC68\u200D\uD83D\uDC67\u200D\uD83D\uDC66":"1f468-1f468-1f467-1f466","\uD83D\uDC68\u200D\uD83D\uDC68\u200D\uD83D\uDC67\u200D\uD83D\uDC67":"1f468-1f468-1f467-1f467","\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66":"1f468-1f469-1f466-1f466","\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66":"1f468-1f469-1f467-1f466","\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC67":"1f468-1f469-1f467-1f467","\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66":"1f469-1f469-1f466-1f466","\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66":"1f469-1f469-1f467-1f466","\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC67":"1f469-1f469-1f467-1f467","\uD83D\uDC68\u200D\u2764\u200D\uD83D\uDC8B\u200D\uD83D\uDC68":"1f468-2764-1f48b-1f468","\uD83D\uDC69\u200D\u2764\u200D\uD83D\uDC8B\u200D\uD83D\uDC68":"1f469-2764-1f48b-1f468","\uD83D\uDC69\u200D\u2764\u200D\uD83D\uDC8B\u200D\uD83D\uDC69":"1f469-2764-1f48b-1f469","\uD83D\uDC69\u200D\u2764\uFE0F\u200D\uD83D\uDC69":"1f469-2764-1f469","\uD83D\uDC68\u200D\u2764\uFE0F\u200D\uD83D\uDC68":"1f468-2764-1f468","\uD83D\uDC69\u200D\u2764\uFE0F\u200D\uD83D\uDC68":"1f469-2764-1f468","\uD83D\uDD75\uFE0F\uD83C\uDFFB\u200D\u2640\uFE0F":"1f575-1f3fb-2640","\uD83D\uDD75\uFE0F\uD83C\uDFFB\u200D\u2642\uFE0F":"1f575-1f3fb-2642","\uD83D\uDD75\uFE0F\uD83C\uDFFC\u200D\u2640\uFE0F":"1f575-1f3fc-2640","\uD83D\uDD75\uFE0F\uD83C\uDFFC\u200D\u2642\uFE0F":"1f575-1f3fc-2642","\uD83D\uDD75\uFE0F\uD83C\uDFFD\u200D\u2640\uFE0F":"1f575-1f3fd-2640","\uD83D\uDD75\uFE0F\uD83C\uDFFD\u200D\u2642\uFE0F":"1f575-1f3fd-2642","\uD83D\uDD75\uFE0F\uD83C\uDFFE\u200D\u2640\uFE0F":"1f575-1f3fe-2640","\uD83D\uDD75\uFE0F\uD83C\uDFFE\u200D\u2642\uFE0F":"1f575-1f3fe-2642","\uD83D\uDD75\uFE0F\uD83C\uDFFF\u200D\u2640\uFE0F":"1f575-1f3ff-2640","\uD83D\uDD75\uFE0F\uD83C\uDFFF\u200D\u2642\uFE0F":"1f575-1f3ff-2642","\uD83C\uDFCB\uFE0F\uD83C\uDFFB\u200D\u2640\uFE0F":"1f3cb-1f3fb-2640","\uD83C\uDFCB\uFE0F\uD83C\uDFFB\u200D\u2642\uFE0F":"1f3cb-1f3fb-2642","\uD83C\uDFCB\uFE0F\uD83C\uDFFC\u200D\u2640\uFE0F":"1f3cb-1f3fc-2640","\uD83C\uDFCB\uFE0F\uD83C\uDFFC\u200D\u2642\uFE0F":"1f3cb-1f3fc-2642","\uD83C\uDFCB\uFE0F\uD83C\uDFFD\u200D\u2640\uFE0F":"1f3cb-1f3fd-2640","\uD83C\uDFCB\uFE0F\uD83C\uDFFD\u200D\u2642\uFE0F":"1f3cb-1f3fd-2642","\uD83C\uDFCB\uFE0F\uD83C\uDFFE\u200D\u2640\uFE0F":"1f3cb-1f3fe-2640","\uD83C\uDFCB\uFE0F\uD83C\uDFFE\u200D\u2642\uFE0F":"1f3cb-1f3fe-2642","\uD83C\uDFCB\uFE0F\uD83C\uDFFF\u200D\u2640\uFE0F":"1f3cb-1f3ff-2640","\uD83C\uDFCB\uFE0F\uD83C\uDFFF\u200D\u2642\uFE0F":"1f3cb-1f3ff-2642","\uD83C\uDFCC\uFE0F\uD83C\uDFFB\u200D\u2640\uFE0F":"1f3cc-1f3fb-2640","\uD83C\uDFCC\uFE0F\uD83C\uDFFB\u200D\u2642\uFE0F":"1f3cc-1f3fb-2642","\uD83C\uDFCC\uFE0F\uD83C\uDFFC\u200D\u2640\uFE0F":"1f3cc-1f3fc-2640","\uD83C\uDFCC\uFE0F\uD83C\uDFFC\u200D\u2642\uFE0F":"1f3cc-1f3fc-2642","\uD83C\uDFCC\uFE0F\uD83C\uDFFD\u200D\u2640\uFE0F":"1f3cc-1f3fd-2640","\uD83C\uDFCC\uFE0F\uD83C\uDFFD\u200D\u2642\uFE0F":"1f3cc-1f3fd-2642","\uD83C\uDFCC\uFE0F\uD83C\uDFFE\u200D\u2640\uFE0F":"1f3cc-1f3fe-2640","\uD83C\uDFCC\uFE0F\uD83C\uDFFE\u200D\u2642\uFE0F":"1f3cc-1f3fe-2642","\uD83C\uDFCC\uFE0F\uD83C\uDFFF\u200D\u2640\uFE0F":"1f3cc-1f3ff-2640","\uD83C\uDFCC\uFE0F\uD83C\uDFFF\u200D\u
ns.jsEscapeMapGreedy = {"\uD83D\uDC69\u2764\uD83D\uDC8B\uD83D\uDC69":"1f469-2764-1f48b-1f469","\uD83D\uDC68\u2764\uD83D\uDC8B\uD83D\uDC68":"1f468-2764-1f48b-1f468","\uD83D\uDC69\u2764\uD83D\uDC8B\uD83D\uDC68":"1f469-2764-1f48b-1f468","\uD83D\uDC69\u2764\uD83D\uDC69":"1f469-2764-1f469","\uD83D\uDC68\u2764\uD83D\uDC68":"1f468-2764-1f468","\uD83C\uDFCC\uD83C\uDFFB\u2642":"1f3cc-1f3fb-2642","\uD83C\uDFCC\uD83C\uDFFC\u2642":"1f3cc-1f3fc-2642","\uD83C\uDFCC\uD83C\uDFFD\u2642":"1f3cc-1f3fd-2642","\uD83C\uDFCC\uD83C\uDFFE\u2642":"1f3cc-1f3fe-2642","\uD83C\uDFCC\uD83C\uDFFF\u2642":"1f3cc-1f3ff-2642","\uD83C\uDFCC\uD83C\uDFFB\u2640":"1f3cc-1f3fb-2640","\uD83C\uDFCC\uD83C\uDFFC\u2640":"1f3cc-1f3fc-2640","\uD83C\uDFCC\uD83C\uDFFD\u2640":"1f3cc-1f3fd-2640","\uD83C\uDFCC\uD83C\uDFFE\u2640":"1f3cc-1f3fe-2640","\uD83C\uDFCC\uD83C\uDFFF\u2640":"1f3cc-1f3ff-2640","\uD83D\uDC68\uD83C\uDFFB\u2696":"1f468-1f3fb-2696","\uD83D\uDC68\uD83C\uDFFC\u2696":"1f468-1f3fc-2696","\uD83D\uDC68\uD83C\uDFFD\u2696":"1f468-1f3fd-2696","\uD83D\uDC68\uD83C\uDFFE\u2696":"1f468-1f3fe-2696","\uD83D\uDC68\uD83C\uDFFF\u2696":"1f468-1f3ff-2696","\uD83D\uDC69\uD83C\uDFFB\u2696":"1f469-1f3fb-2696","\uD83D\uDC69\uD83C\uDFFC\u2696":"1f469-1f3fc-2696","\uD83D\uDC69\uD83C\uDFFD\u2696":"1f469-1f3fd-2696","\uD83D\uDC69\uD83C\uDFFE\u2696":"1f469-1f3fe-2696","\uD83D\uDC69\uD83C\uDFFF\u2696":"1f469-1f3ff-2696","\uD83D\uDC68\uD83C\uDFFB\u2708":"1f468-1f3fb-2708","\uD83D\uDC68\uD83C\uDFFC\u2708":"1f468-1f3fc-2708","\uD83D\uDC68\uD83C\uDFFD\u2708":"1f468-1f3fd-2708","\uD83D\uDC68\uD83C\uDFFE\u2708":"1f468-1f3fe-2708","\uD83D\uDC68\uD83C\uDFFF\u2708":"1f468-1f3ff-2708","\uD83D\uDC69\uD83C\uDFFB\u2708":"1f469-1f3fb-2708","\uD83D\uDC69\uD83C\uDFFC\u2708":"1f469-1f3fc-2708","\uD83D\uDC69\uD83C\uDFFD\u2708":"1f469-1f3fd-2708","\uD83D\uDC69\uD83C\uDFFE\u2708":"1f469-1f3fe-2708","\uD83D\uDC69\uD83C\uDFFF\u2708":"1f469-1f3ff-2708","\uD83D\uDC69\u2764\uD83D\uDC68":"1f469-2764-1f468","\uD83D\uDC68\uD83C\uDFFB\u2695":"1f468-1f3fb-2695","\uD83D\uDC68\uD83C\uDFFC\u2695":"1f468-1f3fc-2695","\uD83D\uDC68\uD83C\uDFFD\u2695":"1f468-1f3fd-2695","\uD83D\uDC68\uD83C\uDFFE\u2695":"1f468-1f3fe-2695","\uD83D\uDC68\uD83C\uDFFF\u2695":"1f468-1f3ff-2695","\uD83D\uDC69\uD83C\uDFFB\u2695":"1f469-1f3fb-2695","\uD83D\uDC69\uD83C\uDFFC\u2695":"1f469-1f3fc-2695","\uD83D\uDC69\uD83C\uDFFD\u2695":"1f469-1f3fd-2695","\uD83D\uDC69\uD83C\uDFFE\u2695":"1f469-1f3fe-2695","\uD83D\uDC69\uD83C\uDFFF\u2695":"1f469-1f3ff-2695","\uD83D\uDC6E\uD83C\uDFFB\u2640":"1f46e-1f3fb-2640","\uD83D\uDC6E\uD83C\uDFFB\u2642":"1f46e-1f3fb-2642","\uD83D\uDC6E\uD83C\uDFFC\u2640":"1f46e-1f3fc-2640","\uD83D\uDC6E\uD83C\uDFFC\u2642":"1f46e-1f3fc-2642","\uD83D\uDC6E\uD83C\uDFFD\u2640":"1f46e-1f3fd-2640","\uD83D\uDC6E\uD83C\uDFFD\u2642":"1f46e-1f3fd-2642","\uD83D\uDC6E\uD83C\uDFFE\u2640":"1f46e-1f3fe-2640","\uD83D\uDC6E\uD83C\uDFFE\u2642":"1f46e-1f3fe-2642","\uD83D\uDC6E\uD83C\uDFFF\u2640":"1f46e-1f3ff-2640","\uD83D\uDC6E\uD83C\uDFFF\u2642":"1f46e-1f3ff-2642","\uD83D\uDC71\uD83C\uDFFB\u2640":"1f471-1f3fb-2640","\uD83D\uDC71\uD83C\uDFFB\u2642":"1f471-1f3fb-2642","\uD83D\uDC71\uD83C\uDFFC\u2640":"1f471-1f3fc-2640","\uD83D\uDC71\uD83C\uDFFC\u2642":"1f471-1f3fc-2642","\uD83D\uDC71\uD83C\uDFFD\u2640":"1f471-1f3fd-2640","\uD83D\uDC71\uD83C\uDFFD\u2642":"1f471-1f3fd-2642","\uD83D\uDC71\uD83C\uDFFE\u2640":"1f471-1f3fe-2640","\uD83D\uDC71\uD83C\uDFFE\u2642":"1f471-1f3fe-2642","\uD83D\uDC71\uD83C\uDFFF\u2640":"1f471-1f3ff-2640","\uD83D\uDC71\uD83C\uDFFF\u2642":"1f471-1f3ff-2642","\uD83D\uDC73\uD83C\uDFFB\u2640":"1f473-1f3fb-2640","\uD83D\uDC73\uD83C\uDFFB\u2642":"1f473-1f3fb-2642","\uD83D\uDC73\uD83C\uDFFC\u2640":"1f473-1f3fc-2640","\uD83D\uDC73\uD83C\uDFFC\u2642":"1f473-1f3fc-2642","\uD83D\uDC73\uD83C\uDFFD\u2640":"1f473-1f3fd-2640","\uD83D\uDC73\uD83C\uDFFD\u2642":"1f473-1f3fd-2642","\uD83D\uDC73\uD83C\uDFFE\u2640":"1f473-1f3fe-2640","\uD83D\uDC73\uD83C\uDFFE\u2642":"1f473-1f3fe-2642","\uD83D\uDC73\uD83C\uDFFF\u2640":"1f473-1f3ff-2640","\uD83D\uDC73\uD83C\uDFFF\u2642":"1f473-1f3ff-2642","\uD83D\uDC77\uD83C\uDFFB\u2640":"1f477-1f3fb-2640","\uD83D\u
ns.asciiList = {
'*\\0/*':'1f646',
'*\\O/*':'1f646',
'-___-':'1f611',
':\'-)':'1f602',
'\':-)':'1f605',
'\':-D':'1f605',
'>:-)':'1f606',
'\':-(':'1f613',
'>:-(':'1f620',
':\'-(':'1f622',
'O:-)':'1f607',
'0:-3':'1f607',
'0:-)':'1f607',
'0;^)':'1f607',
'O;-)':'1f607',
'0;-)':'1f607',
'O:-3':'1f607',
'-__-':'1f611',
':-Þ':'1f61b',
'</3':'1f494',
':\')':'1f602',
':-D':'1f603',
'\':)':'1f605',
'\'=)':'1f605',
'\':D':'1f605',
'\'=D':'1f605',
'>:)':'1f606',
'>;)':'1f606',
'>=)':'1f606',
';-)':'1f609',
'*-)':'1f609',
';-]':'1f609',
';^)':'1f609',
'\':(':'1f613',
'\'=(':'1f613',
':-*':'1f618',
':^*':'1f618',
'>:P':'1f61c',
'X-P':'1f61c',
'>:[':'1f61e',
':-(':'1f61e',
':-[':'1f61e',
'>:(':'1f620',
':\'(':'1f622',
';-(':'1f622',
'>.<':'1f623',
'#-)':'1f635',
'%-)':'1f635',
'X-)':'1f635',
'\\0/':'1f646',
'\\O/':'1f646',
'0:3':'1f607',
'0:)':'1f607',
'O:)':'1f607',
'O=)':'1f607',
'O:3':'1f607',
'B-)':'1f60e',
'8-)':'1f60e',
'B-D':'1f60e',
'8-D':'1f60e',
'-_-':'1f611',
'>:\\':'1f615',
'>:/':'1f615',
':-/':'1f615',
':-.':'1f615',
':-P':'1f61b',
':Þ':'1f61b',
':-b':'1f61b',
':-O':'1f62e',
'O_O':'1f62e',
'>:O':'1f62e',
':-X':'1f636',
':-#':'1f636',
':-)':'1f642',
'(y)':'1f44d',
'<3':'2764',
':D':'1f603',
'=D':'1f603',
';)':'1f609',
'*)':'1f609',
';]':'1f609',
';D':'1f609',
':*':'1f618',
'=*':'1f618',
':(':'1f61e',
':[':'1f61e',
'=(':'1f61e',
':@':'1f620',
';(':'1f622',
'D:':'1f628',
':$':'1f633',
'=$':'1f633',
'#)':'1f635',
'%)':'1f635',
'X)':'1f635',
'B)':'1f60e',
'8)':'1f60e',
':/':'1f615',
':\\':'1f615',
'=/':'1f615',
'=\\':'1f615',
':L':'1f615',
'=L':'1f615',
':P':'1f61b',
'=P':'1f61b',
':b':'1f61b',
':O':'1f62e',
':X':'1f636',
':#':'1f636',
'=X':'1f636',
'=#':'1f636',
':)':'1f642',
'=]':'1f642',
'=)':'1f642',
':]':'1f642'
2018-03-25 21:21:43 +02:00
};
ns.asciiRegexp = '(\\*\\\\0\\/\\*|\\*\\\\O\\/\\*|\\-___\\-|\\:\'\\-\\)|\'\\:\\-\\)|\'\\:\\-D|\\>\\:\\-\\)|>\\:\\-\\)|\'\\:\\-\\(|\\>\\:\\-\\(|>\\:\\-\\(|\\:\'\\-\\(|O\\:\\-\\)|0\\:\\-3|0\\:\\-\\)|0;\\^\\)|O;\\-\\)|0;\\-\\)|O\\:\\-3|\\-__\\-|\\:\\-Þ|\\:\\-Þ|\\<\\/3|<\\/3|\\:\'\\)|\\:\\-D|\'\\:\\)|\'\\=\\)|\'\\:D|\'\\=D|\\>\\:\\)|>\\:\\)|\\>;\\)|>;\\)|\\>\\=\\)|>\\=\\)|;\\-\\)|\\*\\-\\)|;\\-\\]|;\\^\\)|\'\\:\\(|\'\\=\\(|\\:\\-\\*|\\:\\^\\*|\\>\\:P|>\\:P|X\\-P|\\>\\:\\[|>\\:\\[|\\:\\-\\(|\\:\\-\\[|\\>\\:\\(|>\\:\\(|\\:\'\\(|;\\-\\(|\\>\\.\\<|>\\.<|#\\-\\)|%\\-\\)|X\\-\\)|\\\\0\\/|\\\\O\\/|0\\:3|0\\:\\)|O\\:\\)|O\\=\\)|O\\:3|B\\-\\)|8\\-\\)|B\\-D|8\\-D|\\-_\\-|\\>\\:\\\\|>\\:\\\\|\\>\\:\\/|>\\:\\/|\\:\\-\\/|\\:\\-\\.|\\:\\-P|\\:Þ|\\:Þ|\\:\\-b|\\:\\-O|O_O|\\>\\:O|>\\:O|\\:\\-X|\\:\\-#|\\:\\-\\)|\\(y\\)|\\<3|<3|\\:D|\\=D|;\\)|\\*\\)|;\\]|;D|\\:\\*|\\=\\*|\\:\\(|\\:\\[|\\=\\(|\\:@|;\\(|D\\:|\\:\\$|\\=\\$|#\\)|%\\)|X\\)|B\\)|8\\)|\\:\\/|\\:\\\\|\\=\\/|\\=\\\\|\\:L|\\=L|\\:P|\\=P|\\:b|\\:O|\\:X|\\:#|\\=X|\\=#|\\:\\)|\\=\\]|\\=\\)|\\:\\])';
ns.emojiVersion = '3.1'; // you can [optionally] modify this to load alternate emoji versions. see readme for backwards compatibility and version options
ns.emojiSize = '32';
ns.greedyMatch = false; // set to true for greedy unicode matching
ns.imagePathPNG = 'https://cdn.jsdelivr.net/emojione/assets/' + ns.emojiVersion + '/png/';
ns.defaultPathPNG = ns.imagePathPNG;
ns.imageTitleTag = true; // set to false to remove title attribute from img tag
ns.sprites = false; // if this is true then sprite markup will be used
ns.spriteSize = '32';
ns.unicodeAlt = true; // use the unicode char as the alt attribute (makes copy and pasting the resulting text better)
ns.ascii = false; // change to true to convert ascii smileys
ns.riskyMatchAscii = false; // set true to match ascii without leading/trailing space char
ns.regShortNames = new RegExp("<object[^>]*>.*?<\/object>|<span[^>]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|("+ns.shortnames+")", "gi");
ns.regAscii = new RegExp("<object[^>]*>.*?<\/object>|<span[^>]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|((\\s|^)"+ns.asciiRegexp+"(?=\\s|$|[!,.?]))", "gi");
ns.regAsciiRisky = new RegExp("<object[^>]*>.*?<\/object>|<span[^>]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|(()"+ns.asciiRegexp+"())", "gi");
ns.regUnicode = new RegExp("<object[^>]*>.*?<\/object>|<span[^>]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|(?:\uD83C\uDFF3)\uFE0F?\u200D?(?:\uD83C\uDF08)|(?:\uD83D\uDC41)\uFE0F?\u200D?(?:\uD83D\uDDE8)\uFE0F?|[#-9]\uFE0F?\u20E3|(?:(?:\uD83C\uDFF4)(?:\uDB40[\uDC60-\uDCFF]){1,6})|(?:\uD83C[\uDDE0-\uDDFF]){2}|(?:(?:\uD83D[\uDC68\uDC69]))\uFE0F?(?:\uD83C[\uDFFA-\uDFFF])?\u200D?(?:[\u2695\u2696\u2708]|\uD83C[\uDF3E-\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92])|(?:\uD83D[\uDC68\uDC69]|\uD83E[\uDDD0-\uDDDF])(?:\uD83C[\uDFFA-\uDFFF])?\u200D?[\u2640\u2642\u2695\u2696\u2708]?\uFE0F?|(?:(?:\u2764|\uD83D[\uDC66-\uDC69\uDC8B])[\u200D\uFE0F]{0,2}){1,3}(?:\u2764|\uD83D[\uDC66-\uDC69\uDC8B])|(?:(?:\u2764|\uD83D[\uDC66-\uDC69\uDC8B])\uFE0F?){2,4}|(?:\uD83D[\uDC68\uDC69\uDC6E\uDC71-\uDC87\uDD75\uDE45-\uDE4E]|\uD83E[\uDD26\uDD37]|\uD83C[\uDFC3-\uDFCC]|\uD83E[\uDD38-\uDD3E]|\uD83D[\uDEA3-\uDEB6]|\u26f9|\uD83D\uDC6F)\uFE0F?(?:\uD83C[\uDFFB-\uDFFF])?\u200D?[\u2640\u2642]?\uFE0F?|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85-\uDFCC]|\uD83D[\uDC42-\uDCAA\uDD74-\uDD96\uDE45-\uDE4F\uDEA3-\uDECC]|\uD83E[\uDD18-\uDD3E])\uFE0F?(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u2194-\u2199\u21a9-\u21aa]\uFE0F?|[\u0023\u002a]|[\u3030\u303d]\uFE0F?|(?:\ud83c[\udd70-\udd71]|\ud83c\udd8e|\ud83c[\udd91-\udd9a])\uFE0F?|\u24c2\uFE0F?|[\u3297\u3299]\uFE0F?|(?:\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51])\uFE0F?|[\u203c\u2049]\uFE0F?|[\u25aa-\u25ab\u25b6\u25c0\u25fb-\u25fe]\uFE0F?|[\u00a9\u00ae]\uFE0F?|[\u2122\u2139]\uFE0F?|\ud83c\udc04\uFE0F?|[\u2b05-\u2b07\u2b1b-\u2b1c\u2b50\u2b55]\uFE0F?|[\u231a-\u231b\u2328\u23cf\u23e9-\u23f3\u23f8-\u23fa]\uFE0F?|\ud83c\udccf|[\u2934\u2935]\uFE0F?)|[\u2700-\u27bf]\uFE0F?|[\ud800-\udbff][\udc00-\udfff]\uFE0F?|[\u2600-\u26FF]\uFE0F?|[\u0030-\u0039]\uFE0F", "g");
ns.toImage = function(str) {
str = ns.unicodeToImage(str);
str = ns.shortnameToImage(str);
return str;
2018-03-25 21:21:43 +02:00
};
// Uses toShort to transform all unicode into a standard shortname
// then transforms the shortname into unicode
// This is done for standardization when converting several unicode types
ns.unifyUnicode = function(str) {
str = ns.toShort(str);
str = ns.shortnameToUnicode(str);
return str;
2018-03-25 21:21:43 +02:00
};
// Replace shortnames (:wink:) with Ascii equivalents ( ;^) )
// Useful for systems that dont support unicode nor images
ns.shortnameToAscii = function(str) {
var unicode,
// something to keep in mind here is that array flip will destroy
// half of the ascii text "emojis" because the unicode numbers are duplicated
// this is ok for what it's being used for
unicodeToAscii = ns.objectFlip(ns.asciiList);
str = str.replace(ns.regShortNames, function(shortname) {
if( (typeof shortname === 'undefined') || (shortname === '') || (!(shortname in ns.emojioneList)) ) {
// if the shortname doesnt exist just return the entire match
return shortname;
}
else {
unicode = ns.emojioneList[shortname].uc_output;
if(typeof unicodeToAscii[unicode] !== 'undefined') {
return unicodeToAscii[unicode];
} else {
return shortname;
}
}
2018-03-25 21:21:43 +02:00
});
return str;
2018-03-25 21:21:43 +02:00
};
// will output unicode from shortname
// useful for sending emojis back to mobile devices
ns.shortnameToUnicode = function(str) {
// replace regular shortnames first
var unicode,fname;
str = str.replace(ns.regShortNames, function(shortname) {
if( (typeof shortname === 'undefined') || (shortname === '') || (!(shortname in ns.emojioneList)) ) {
// if the shortname doesnt exist just return the entire matchhju
return shortname;
}
unicode = ns.emojioneList[shortname].uc_output.toUpperCase();
fname = ns.emojioneList[shortname].uc_base;
return ns.convert(unicode);
});
// if ascii smileys are turned on, then we'll replace them!
if (ns.ascii) {
var asciiRX = ns.riskyMatchAscii ? ns.regAsciiRisky : ns.regAscii;
str = str.replace(asciiRX, function(entire, m1, m2, m3) {
if( (typeof m3 === 'undefined') || (m3 === '') || (!(ns.unescapeHTML(m3) in ns.asciiList)) ) {
// if the ascii doesnt exist just return the entire match
return entire;
}
m3 = ns.unescapeHTML(m3);
unicode = ns.asciiList[m3].toUpperCase();
return m2+ns.convert(unicode);
});
}
return str;
2018-03-25 21:21:43 +02:00
};
ns.shortnameToImage = function(str) {
// replace regular shortnames first
var replaceWith,shortname,unicode,fname,alt,category,title,size,ePath;
var mappedUnicode = ns.mapUnicodeToShort();
str = str.replace(ns.regShortNames, function(shortname) {
if( (typeof shortname === 'undefined') || (shortname === '') || (ns.shortnames.indexOf(shortname) === -1) ) {
// if the shortname doesnt exist just return the entire match
return shortname;
}
else {
// map shortname to parent
if (!ns.emojioneList[shortname]) {
for ( var emoji in ns.emojioneList ) {
if (!ns.emojioneList.hasOwnProperty(emoji) || (emoji === '')) continue;
if (ns.emojioneList[emoji].shortnames.indexOf(shortname) === -1) continue;
shortname = emoji;
break;
}
}
unicode = ns.emojioneList[shortname].uc_output;
fname = ns.emojioneList[shortname].uc_base;
category = (fname.includes("-1f3f")) ? 'diversity' : ns.emojioneList[shortname].category;
title = ns.imageTitleTag ? 'title="' + shortname + '"' : '';
size = (ns.spriteSize == '32' || ns.spriteSize == '64') ? ns.spriteSize : '32';
//if the image path has not changed, we'll assume the default cdn path, otherwise we'll assume the provided path
ePath = (ns.imagePathPNG != ns.defaultPathPNG) ? ns.imagePathPNG : ns.defaultPathPNG + ns.emojiSize + '/';
// depending on the settings, we'll either add the native unicode as the alt tag, otherwise the shortname
alt = (ns.unicodeAlt) ? ns.convert(unicode.toUpperCase()) : shortname;
if(ns.sprites) {
replaceWith = '<span class="emojione emojione-' + size + '-' + category + ' _' + fname + '" ' + title + '>' + alt + '</span>';
}
else {
replaceWith = '<img class="emojione" alt="' + alt + '" ' + title + ' src="' + ePath + fname + '.png"/>';
}
return replaceWith;
}
});
// if ascii smileys are turned on, then we'll replace them!
if (ns.ascii) {
var asciiRX = ns.riskyMatchAscii ? ns.regAsciiRisky : ns.regAscii;
str = str.replace(asciiRX, function(entire, m1, m2, m3) {
if( (typeof m3 === 'undefined') || (m3 === '') || (!(ns.unescapeHTML(m3) in ns.asciiList)) ) {
// if the ascii doesnt exist just return the entire match
return entire;
}
m3 = ns.unescapeHTML(m3);
unicode = ns.asciiList[m3];
shortname = mappedUnicode[unicode];
category = (unicode.includes("-1f3f")) ? 'diversity' : ns.emojioneList[shortname].category;
title = ns.imageTitleTag ? 'title="' + ns.escapeHTML(m3) + '"' : '';
size = (ns.spriteSize == '32' || ns.spriteSize == '64') ? ns.spriteSize : '32';
//if the image path has not changed, we'll assume the default cdn path, otherwise we'll assume the provided path
ePath = (ns.imagePathPNG != ns.defaultPathPNG) ? ns.imagePathPNG : ns.defaultPathPNG + ns.emojiSize + '/';
// depending on the settings, we'll either add the native unicode as the alt tag, otherwise the shortname
alt = (ns.unicodeAlt) ? ns.convert(unicode.toUpperCase()) : ns.escapeHTML(m3);
if(ns.sprites) {
replaceWith = m2+'<span class="emojione emojione-' + size + '-' + category + ' _' + unicode +'" ' + title + '>' + alt + '</span>';
}
else {
replaceWith = m2+'<img class="emojione" alt="'+alt+'" ' + title + ' src="' + ePath + unicode + '.png"/>';
}
return replaceWith;
});
2018-03-25 21:21:43 +02:00
}
return str;
2018-03-25 21:21:43 +02:00
};
ns.unicodeToImage = function(str) {
var replaceWith,unicode,short,fname,alt,category,title,size,ePath;
var mappedUnicode = ns.mapUnicodeToShort();
var eList = ns.emojioneList;
str = str.replace(ns.regUnicode, function(unicodeChar) {
if( (typeof unicodeChar === 'undefined') || (unicodeChar === '') )
{
return unicodeChar;
}
else if ( unicodeChar in ns.jsEscapeMap )
{
fname = ns.jsEscapeMap[unicodeChar];
}
else if ( ns.greedyMatch && unicodeChar in ns.jsEscapeMapGreedy )
{
fname = ns.jsEscapeMapGreedy[unicodeChar];
}
else
{
return unicodeChar;
}
// then map to shortname and locate the filename
short = mappedUnicode[fname];
// then pull the unicode output from emojioneList
fname = eList[short].uc_base;
unicode = eList[short].uc_output;
category = (fname.includes("-1f3f")) ? 'diversity' : eList[short].category;
size = (ns.spriteSize == '32' || ns.spriteSize == '64') ? ns.spriteSize : '32';
//if the image path has not changed, we'll assume the default cdn path, otherwise we'll assume the provided path
ePath = (ns.imagePathPNG != ns.defaultPathPNG) ? ns.imagePathPNG : ns.defaultPathPNG + ns.emojiSize + '/';
// depending on the settings, we'll either add the native unicode as the alt tag, otherwise the shortname
alt = (ns.unicodeAlt) ? ns.convert(unicode.toUpperCase()) : short;
title = ns.imageTitleTag ? 'title="' + short + '"' : '';
if(ns.sprites) {
replaceWith = '<span class="emojione emojione-' + size + '-' + category + ' _' + fname + '" ' + title + '>' + alt + '</span>';
}
else {
replaceWith = '<img class="emojione" alt="' + alt + '" ' + title + ' src="' + ePath + fname + '.png"/>';
}
return replaceWith;
});
// if ascii smileys are turned on, then we'll replace them!
if (ns.ascii) {
var asciiRX = ns.riskyMatchAscii ? ns.regAsciiRisky : ns.regAscii;
str = str.replace(asciiRX, function(entire, m1, m2, m3) {
if( (typeof m3 === 'undefined') || (m3 === '') || (!(ns.unescapeHTML(m3) in ns.asciiList)) ) {
// if the ascii doesnt exist just return the entire match
return entire;
}
m3 = ns.unescapeHTML(m3);
unicode = ns.asciiList[m3];
shortname = mappedUnicode[unicode];
category = (unicode.includes("-1f3f")) ? 'diversity' : ns.emojioneList[shortname].category;
title = ns.imageTitleTag ? 'title="' + ns.escapeHTML(m3) + '"' : '';
size = (ns.spriteSize == '32' || ns.spriteSize == '64') ? ns.spriteSize : '32';
//if the image path has not changed, we'll assume the default cdn path, otherwise we'll assume the provided path
ePath = (ns.imagePathPNG != ns.defaultPathPNG) ? ns.imagePathPNG : ns.defaultPathPNG + ns.emojiSize + '/';
// depending on the settings, we'll either add the native unicode as the alt tag, otherwise the shortname
alt = (ns.unicodeAlt) ? ns.convert(unicode.toUpperCase()) : ns.escapeHTML(m3);
if(ns.sprites) {
replaceWith = m2+'<span class="emojione emojione-' + size + '-' + category + ' _' + unicode +'" ' + title + '>' + alt + '</span>';
}
else {
replaceWith = m2+'<img class="emojione" alt="'+alt+'" ' + title + ' src="' + ePath + unicode + '.png"/>';
}
return replaceWith;
});
}
return str;
};
// this is really just unicodeToShortname() but I opted for the shorthand name to match toImage()
ns.toShort = function(str) {
var find = ns.unicodeCharRegex();
return ns.replaceAll(str, find);
};
// for converting unicode code points and code pairs to their respective characters
ns.convert = function(unicode) {
if(unicode.indexOf("-") > -1) {
var parts = [];
var s = unicode.split('-');
for(var i = 0; i < s.length; i++) {
var part = parseInt(s[i], 16);
if (part >= 0x10000 && part <= 0x10FFFF) {
var hi = Math.floor((part - 0x10000) / 0x400) + 0xD800;
var lo = ((part - 0x10000) % 0x400) + 0xDC00;
part = (String.fromCharCode(hi) + String.fromCharCode(lo));
}
else {
part = String.fromCharCode(part);
}
parts.push(part);
}
return parts.join('');
2018-03-25 21:21:43 +02:00
}
else {
var s = parseInt(unicode, 16);
if (s >= 0x10000 && s <= 0x10FFFF) {
var hi = Math.floor((s - 0x10000) / 0x400) + 0xD800;
var lo = ((s - 0x10000) % 0x400) + 0xDC00;
return (String.fromCharCode(hi) + String.fromCharCode(lo));
}
else {
return String.fromCharCode(s);
}
2018-03-25 21:21:43 +02:00
}
};
ns.escapeHTML = function (string) {
var escaped = {
'&' : '&amp;',
'<' : '&lt;',
'>' : '&gt;',
'"' : '&quot;',
'\'': '&#039;'
};
return string.replace(/[&<>"']/g, function (match) {
return escaped[match];
});
};
ns.unescapeHTML = function (string) {
var unescaped = {
'&amp;' : '&',
'&#38;' : '&',
'&#x26;' : '&',
'&lt;' : '<',
'&#60;' : '<',
'&#x3C;' : '<',
'&gt;' : '>',
'&#62;' : '>',
'&#x3E;' : '>',
'&quot;' : '"',
'&#34;' : '"',
'&#x22;' : '"',
'&apos;' : '\'',
'&#39;' : '\'',
'&#x27;' : '\''
};
return string.replace(/&(?:amp|#38|#x26|lt|#60|#x3C|gt|#62|#x3E|apos|#39|#x27|quot|#34|#x22);/ig, function (match) {
return unescaped[match];
});
};
ns.shortnameConversionMap = function() {
var map = [], emoji;
for (emoji in ns.emojioneList) {
if (!ns.emojioneList.hasOwnProperty(emoji) || (emoji === '')) continue;
map[ns.convert(ns.emojioneList[emoji].uc_output)] = emoji;
2018-03-25 21:21:43 +02:00
}
return map;
};
ns.unicodeCharRegex = function() {
var map = [];
for (emoji in ns.emojioneList) {
if (!ns.emojioneList.hasOwnProperty(emoji) || (emoji === '')) continue;
map.push(ns.convert(ns.emojioneList[emoji].uc_output));
2018-03-25 21:21:43 +02:00
}
return map.join('|');
2018-03-25 21:21:43 +02:00
};
ns.mapEmojioneList = function (addToMapStorage) {
for (var shortname in ns.emojioneList) {
if (!ns.emojioneList.hasOwnProperty(shortname)) { continue; }
var unicode = ns.emojioneList[shortname].uc_base;
addToMapStorage(unicode, shortname);
}
};
ns.mapUnicodeToShort = function() {
if (!ns.memMapShortToUnicode) {
ns.memMapShortToUnicode = {};
ns.mapEmojioneList(function (unicode, shortname) {
ns.memMapShortToUnicode[unicode] = shortname;
});
}
return ns.memMapShortToUnicode;
};
ns.memorizeReplacement = function() {
if (!ns.unicodeReplacementRegEx || !ns.memMapShortToUnicodeCharacters) {
var unicodeList = [];
ns.memMapShortToUnicodeCharacters = {};
ns.mapEmojioneList(function (unicode, shortname) {
var emojiCharacter = ns.convert(unicode);
ns.memMapShortToUnicodeCharacters[emojiCharacter] = shortname;
unicodeList.push(emojiCharacter);
});
ns.unicodeReplacementRegEx = unicodeList.join('|');
}
};
ns.mapUnicodeCharactersToShort = function() {
ns.memorizeReplacement();
return ns.memMapShortToUnicodeCharacters;
};
//reverse an object
ns.objectFlip = function (obj) {
var key, tmp_obj = {};
for (key in obj) {
if (obj.hasOwnProperty(key)) {
tmp_obj[obj[key]] = key;
}
}
return tmp_obj;
};
ns.escapeRegExp = function(string) {
return string.replace(/[-[\]{}()*+?.,;:&\\^$#\s]/g, "\\$&");
};
ns.replaceAll = function(string, find) {
var escapedFind = ns.escapeRegExp(find); //sorted largest output to smallest output
var search = new RegExp("<object[^>]*>.*?<\/object>|<span[^>]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|("+escapedFind+")", "gi");
// callback prevents replacing anything inside of these common html tags as well as between an <object></object> tag
var replace = function(entire, m1) {
return ((typeof m1 === 'undefined') || (m1 === '')) ? entire : ns.shortnameConversionMap()[m1];
};
return string.replace(search,replace);
};
}(this.emojione = this.emojione || {}));
if(typeof module === "object") module.exports = this.emojione;
define("emojione", (function (global) {
return function () {
var ret, fn;
return ret || global.emojione;
};
}(this)));
2018-05-15 10:32:13 +02:00
// Converse.js
2018-05-07 18:20:15 +02:00
// http://conversejs.org
//
2018-05-15 10:32:13 +02:00
// Copyright (c) 2013-2018, the Converse.js developers
2018-05-07 18:20:15 +02:00
// Licensed under the Mozilla Public License (MPLv2)
/* This is a Converse.js plugin which add support for XEP-0030: Service Discovery */
/*global Backbone, define, window */
(function (root, factory) {
2018-05-07 18:20:15 +02:00
define('converse-disco',["converse-core", "sizzle"], factory);
})(this, function (converse, sizzle) {
var _converse$env = converse.env,
Backbone = _converse$env.Backbone,
Promise = _converse$env.Promise,
Strophe = _converse$env.Strophe,
$iq = _converse$env.$iq,
b64_sha1 = _converse$env.b64_sha1,
utils = _converse$env.utils,
_ = _converse$env._,
f = _converse$env.f;
converse.plugins.add('converse-disco', {
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
var _converse = this._converse; // Promises exposed by this plugin
_converse.api.promises.add('discoInitialized');
_converse.DiscoEntity = Backbone.Model.extend({
/* A Disco Entity is a JID addressable entity that can be queried
* for features.
*
* See XEP-0030: https://xmpp.org/extensions/xep-0030.html
*/
idAttribute: 'jid',
initialize: function initialize() {
this.waitUntilFeaturesDiscovered = utils.getResolveablePromise();
this.dataforms = new Backbone.Collection();
this.dataforms.browserStorage = new Backbone.BrowserStorage[_converse.storage](b64_sha1("converse.dataforms-{this.get('jid')}"));
this.features = new Backbone.Collection();
this.features.browserStorage = new Backbone.BrowserStorage[_converse.storage](b64_sha1("converse.features-".concat(this.get('jid'))));
this.features.on('add', this.onFeatureAdded, this);
this.identities = new Backbone.Collection();
this.identities.browserStorage = new Backbone.BrowserStorage[_converse.storage](b64_sha1("converse.identities-".concat(this.get('jid'))));
this.fetchFeatures();
this.items = new _converse.DiscoEntities();
this.items.browserStorage = new Backbone.BrowserStorage[_converse.storage](b64_sha1("converse.disco-items-".concat(this.get('jid'))));
this.items.fetch();
},
getIdentity: function getIdentity(category, type) {
/* Returns a Promise which resolves with a map indicating
* whether a given identity is provided.
*
* Parameters:
* (String) category - The identity category
* (String) type - The identity type
*/
var entity = this;
return new Promise(function (resolve, reject) {
function fulfillPromise() {
var model = entity.identities.findWhere({
'category': category,
'type': type
});
resolve(model);
}
entity.waitUntilFeaturesDiscovered.then(fulfillPromise).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
});
},
hasFeature: function hasFeature(feature) {
/* Returns a Promise which resolves with a map indicating
* whether a given feature is supported.
*
* Parameters:
* (String) feature - The feature that might be supported.
*/
var entity = this;
return new Promise(function (resolve, reject) {
function fulfillPromise() {
if (entity.features.findWhere({
'var': feature
})) {
resolve(entity);
} else {
resolve();
}
}
entity.waitUntilFeaturesDiscovered.then(fulfillPromise).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
});
},
onFeatureAdded: function onFeatureAdded(feature) {
feature.entity = this;
_converse.emit('serviceDiscovered', feature);
},
fetchFeatures: function fetchFeatures() {
var _this = this;
if (this.features.browserStorage.records.length === 0) {
this.queryInfo();
} else {
this.features.fetch({
add: true,
success: function success() {
_this.waitUntilFeaturesDiscovered.resolve();
_this.trigger('featuresDiscovered');
}
});
this.identities.fetch({
add: true
});
}
},
queryInfo: function queryInfo() {
_converse.api.disco.info(this.get('jid'), null, this.onInfo.bind(this));
},
onDiscoItems: function onDiscoItems(stanza) {
var _this2 = this;
_.each(sizzle("query[xmlns=\"".concat(Strophe.NS.DISCO_ITEMS, "\"] item"), stanza), function (item) {
if (item.getAttribute("node")) {
// XXX: ignore nodes for now.
// See: https://xmpp.org/extensions/xep-0030.html#items-nodes
return;
}
var jid = item.getAttribute('jid');
if (_.isUndefined(_this2.items.get(jid))) {
_this2.items.create({
'jid': jid
});
}
});
},
queryForItems: function queryForItems() {
if (_.isEmpty(this.identities.where({
'category': 'server'
}))) {
// Don't fetch features and items if this is not a
// server or a conference component.
return;
}
_converse.api.disco.items(this.get('jid'), null, this.onDiscoItems.bind(this));
},
onInfo: function onInfo(stanza) {
var _this3 = this;
_.forEach(stanza.querySelectorAll('identity'), function (identity) {
_this3.identities.create({
'category': identity.getAttribute('category'),
'type': identity.getAttribute('type'),
'name': identity.getAttribute('name')
});
});
_.each(sizzle("x[type=\"result\"][xmlns=\"".concat(Strophe.NS.XFORM, "\"]"), stanza), function (form) {
var data = {};
_.each(form.querySelectorAll('field'), function (field) {
data[field.getAttribute('var')] = {
'value': _.get(field.querySelector('value'), 'textContent'),
'type': field.getAttribute('type')
};
});
_this3.dataforms.create(data);
});
if (stanza.querySelector("feature[var=\"".concat(Strophe.NS.DISCO_ITEMS, "\"]"))) {
this.queryForItems();
}
_.forEach(stanza.querySelectorAll('feature'), function (feature) {
_this3.features.create({
'var': feature.getAttribute('var'),
'from': stanza.getAttribute('from')
});
});
this.waitUntilFeaturesDiscovered.resolve();
this.trigger('featuresDiscovered');
}
2018-05-07 18:20:15 +02:00
});
_converse.DiscoEntities = Backbone.Collection.extend({
model: _converse.DiscoEntity,
fetchEntities: function fetchEntities() {
var _this4 = this;
return new Promise(function (resolve, reject) {
_this4.fetch({
add: true,
success: resolve,
error: function error() {
reject(new Error("Could not fetch disco entities"));
}
});
});
}
2018-05-07 18:20:15 +02:00
});
function addClientFeatures() {
// See http://xmpp.org/registrar/disco-categories.html
2018-05-15 10:32:13 +02:00
_converse.api.disco.own.identities.add('client', 'web', 'Converse.js');
2018-05-07 18:20:15 +02:00
2018-05-15 10:32:13 +02:00
_converse.api.disco.own.features.add(Strophe.NS.BOSH);
2018-05-07 18:20:15 +02:00
2018-05-15 10:32:13 +02:00
_converse.api.disco.own.features.add(Strophe.NS.CHATSTATES);
2018-05-07 18:20:15 +02:00
2018-05-15 10:32:13 +02:00
_converse.api.disco.own.features.add(Strophe.NS.DISCO_INFO);
2018-05-07 18:20:15 +02:00
2018-05-15 10:32:13 +02:00
_converse.api.disco.own.features.add(Strophe.NS.ROSTERX); // Limited support
2018-05-07 18:20:15 +02:00
if (_converse.message_carbons) {
2018-05-15 10:32:13 +02:00
_converse.api.disco.own.features.add(Strophe.NS.CARBONS);
}
2018-05-07 18:20:15 +02:00
_converse.emit('addClientFeatures');
return this;
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
function initializeDisco() {
addClientFeatures();
_converse.connection.addHandler(onDiscoInfoRequest, Strophe.NS.DISCO_INFO, 'iq', 'get', null, null);
_converse.disco_entities = new _converse.DiscoEntities();
_converse.disco_entities.browserStorage = new Backbone.BrowserStorage[_converse.storage](b64_sha1("converse.disco-entities-".concat(_converse.bare_jid)));
_converse.disco_entities.fetchEntities().then(function (collection) {
if (collection.length === 0 || !collection.get(_converse.domain)) {
// If we don't have an entity for our own XMPP server,
// create one.
_converse.disco_entities.create({
'jid': _converse.domain
});
}
_converse.emit('discoInitialized');
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
_converse.api.listen.on('reconnected', initializeDisco);
_converse.api.listen.on('connected', initializeDisco);
_converse.api.listen.on('beforeTearDown', function () {
if (_converse.disco_entities) {
_converse.disco_entities.each(function (entity) {
entity.features.reset();
entity.features.browserStorage._clear();
});
_converse.disco_entities.reset();
_converse.disco_entities.browserStorage._clear();
}
});
var plugin = this;
plugin._identities = [];
plugin._features = [];
function onDiscoInfoRequest(stanza) {
var node = stanza.getElementsByTagName('query')[0].getAttribute('node');
var attrs = {
xmlns: Strophe.NS.DISCO_INFO
};
if (node) {
attrs.node = node;
}
var iqresult = $iq({
'type': 'result',
'id': stanza.getAttribute('id')
});
var from = stanza.getAttribute('from');
if (from !== null) {
iqresult.attrs({
'to': from
});
}
_.each(plugin._identities, function (identity) {
var attrs = {
'category': identity.category,
'type': identity.type
};
if (identity.name) {
attrs.name = identity.name;
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
if (identity.lang) {
attrs['xml:lang'] = identity.lang;
}
2018-05-07 18:20:15 +02:00
iqresult.c('identity', attrs).up();
});
_.each(plugin._features, function (feature) {
iqresult.c('feature', {
'var': feature
}).up();
});
_converse.connection.send(iqresult.tree());
return true;
}
/* We extend the default converse.js API to add methods specific to service discovery */
_.extend(_converse.api, {
2018-05-15 10:32:13 +02:00
/**
* The service discovery API
* @namespace
*/
2018-05-07 18:20:15 +02:00
'disco': {
2018-05-15 10:32:13 +02:00
/**
* The "own" grouping
* @namespace
*/
'own': {
/**
* The "identities" grouping
* @namespace
*/
'identities': {
/**
* Lets you add new identities for this client (i.e. instance of Converse.js)
* @function
*
* @param {String} category - server, client, gateway, directory, etc.
* @param {String} type - phone, pc, web, etc.
* @param {String} name - "Converse.js"
* @param {String} lang - en, el, de, etc.
*
* @example
* _converse.api.disco.own.identities.clear();
*/
add: function add(category, type, name, lang) {
for (var i = 0; i < plugin._identities.length; i++) {
if (plugin._identities[i].category == category && plugin._identities[i].type == type && plugin._identities[i].name == name && plugin._identities[i].lang == lang) {
return false;
}
}
plugin._identities.push({
category: category,
type: type,
name: name,
lang: lang
});
},
/**
* Clears all previously registered identities.
* @function
*
* @example
* _converse.api.disco.own.identities.clear();
*/
clear: function clear() {
plugin._identities = [];
},
/**
* Returns all of the identities registered for this client
* (i.e. instance of Converse.js).
* @function
*
* @example
* const identities = _converse.api.disco.own.identities.get();
*/
get: function get() {
return plugin._identities;
}
},
/**
* The "features" grouping
* @namespace
*/
'features': {
/**
* Lets you register new disco features for this client (i.e. instance of Converse.js)
* @function
*
* @param {String} name - e.g. http://jabber.org/protocol/caps
*
* @example
* _converse.api.disco.own.features.add("http://jabber.org/protocol/caps");
*/
add: function add(name) {
for (var i = 0; i < plugin._features.length; i++) {
if (plugin._features[i] == name) {
return false;
}
}
plugin._features.push(name);
},
/**
* Clears all previously registered features.
* @function
*
* @example
* _converse.api.disco.own.features.clear();
*/
clear: function clear() {
plugin._features = [];
},
/**
* Returns all of the features registered for this client
* (i.e. instance of Converse.js).
* @function
*
* @example
* const features = _converse.api.disco.own.features.get();
*/
get: function get() {
return plugin._features;
}
}
},
2018-05-07 18:20:15 +02:00
'info': function info(jid, node, callback, errback, timeout) {
var attrs = {
xmlns: Strophe.NS.DISCO_INFO
};
if (node) {
attrs.node = node;
}
var info = $iq({
'from': _converse.connection.jid,
'to': jid,
'type': 'get'
}).c('query', attrs);
_converse.connection.sendIQ(info, callback, errback, timeout);
},
'items': function items(jid, node, callback, errback, timeout) {
var attrs = {
'xmlns': Strophe.NS.DISCO_ITEMS
};
if (node) {
attrs.node = node;
}
var items = $iq({
'from': _converse.connection.jid,
'to': jid,
'type': 'get'
}).c('query', attrs);
_converse.connection.sendIQ(items, callback, errback, timeout);
},
'entities': {
'get': function get(entity_jid) {
var create = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
return _converse.api.waitUntil('discoInitialized').then(function () {
if (_.isNil(entity_jid)) {
return _converse.disco_entities;
}
var entity = _converse.disco_entities.get(entity_jid);
if (entity || !create) {
return entity;
}
return _converse.disco_entities.create({
'jid': entity_jid
});
});
}
},
'supports': function supports(feature, entity_jid) {
/* Returns a Promise which resolves with a list containing
* _converse.Entity instances representing the entity
* itself or those items associated with the entity if
* they support the given feature.
*
* Parameters:
* (String) feature - The feature that might be
* supported. In the XML stanza, this is the `var`
* attribute of the `<feature>` element. For
* example: 'http://jabber.org/protocol/muc'
* (String) entity_jid - The JID of the entity
* (and its associated items) which should be queried
*/
if (_.isNil(entity_jid)) {
throw new TypeError('disco.supports: You need to provide an entity JID');
}
2018-05-23 04:27:33 +02:00
return new Promise(function (resolve, reject) {
return _converse.api.waitUntil('discoInitialized').then(function () {
2018-05-07 18:20:15 +02:00
_converse.api.disco.entities.get(entity_jid, true).then(function (entity) {
entity.waitUntilFeaturesDiscovered.then(function () {
var promises = _.concat(entity.items.map(function (item) {
return item.hasFeature(feature);
}), entity.hasFeature(feature));
Promise.all(promises).then(function (result) {
resolve(f.filter(f.isObject, result));
}).catch(reject);
}).catch(reject);
2018-05-23 04:27:33 +02:00
}).catch(reject);
}).catch(reject);
2018-05-07 18:20:15 +02:00
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
},
'getIdentity': function getIdentity(category, type, entity_jid) {
/* Returns a Promise which resolves with a map indicating
* whether an identity with a given type is provided by
* the entity.
*
* Parameters:
* (String) category - The identity category.
* In the XML stanza, this is the `category`
* attribute of the `<identity>` element.
* For example: 'pubsub'
* (String) type - The identity type.
* In the XML stanza, this is the `type`
* attribute of the `<identity>` element.
* For example: 'pep'
* (String) entity_jid - The JID of the entity which might have the identity
*/
return new Promise(function (resolve, reject) {
_converse.api.waitUntil('discoInitialized').then(function () {
_converse.api.disco.entities.get(entity_jid, true).then(function (entity) {
return resolve(entity.getIdentity(category, type));
});
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
});
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
});
});
//# sourceMappingURL=converse-disco.js.map;
/*!
* Backbone.OrderedListView
*
* Copyright (c) 2017, JC Brand <jc@opkode.com>
* Licensed under the Mozilla Public License (MPL)
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define('backbone.orderedlistview',["underscore", "backbone", "backbone.overview"], factory);
} else {
// RequireJS isn't being used.
// Assume underscore and backbone are loaded in <script> tags
factory(_ || root._, Backbone || root.Backbone);
}
})(this, function (_, Backbone) {
"use strict";
Backbone.OrderedListView = Backbone.Overview.extend({
/* An OrderedListView is a special type of Overview which adds some
* methods and conventions for rendering an ordered list of elements.
*/
// The `listItems` attribute denotes the path (from this View) to the
// list of items.
listItems: 'model',
// The `sortEvent` attribute specifies the event which should cause the
// ordered list to be sorted.
sortEvent: 'change',
// The `listSelector` is the selector used to query for the DOM list
// element which contains the ordered items.
listSelector: '.ordered-items',
// The `itemView` is constructor which should be called to create a
// View for a new item.
ItemView: undefined,
// The `subviewIndex` is the attribute of the list element model which
// acts as the index of the subview in the overview.
// An overview is a "Collection" of views, and they can be retrieved
// via an index. By default this is the 'id' attribute, but it could be
// set to something else.
subviewIndex: 'id',
initialize: function initialize() {
this.sortEventually = _.debounce(this.sortAndPositionAllItems.bind(this), 500);
this.items = _.get(this, this.listItems);
this.items.on('add', this.sortAndPositionAllItems, this);
this.items.on('remove', this.removeView, this);
if (!_.isNil(this.sortEvent)) {
this.items.on(this.sortEvent, this.sortEventually, this);
}
},
createItemView: function createItemView(item) {
var item_view = this.get(item.get(this.subviewIndex));
if (!item_view) {
item_view = new this.ItemView({
model: item
});
this.add(item.get(this.subviewIndex), item_view);
} else {
item_view.model = item;
item_view.initialize();
}
item_view.render();
return item_view;
},
removeView: function removeView(item) {
this.remove(item.get(this.subviewIndex));
},
sortAndPositionAllItems: function sortAndPositionAllItems() {
var _this = this;
if (!this.items.length) {
return;
}
this.items.sort();
var list_el = this.el.querySelector(this.listSelector);
var div = document.createElement('div');
list_el.parentNode.replaceChild(div, list_el);
this.items.each(function (item) {
var view = _this.get(item.get(_this.subviewIndex));
if (_.isUndefined(view)) {
view = _this.createItemView(item);
}
list_el.insertAdjacentElement('beforeend', view.el);
});
div.parentNode.replaceChild(list_el, div);
}
2018-05-07 18:20:15 +02:00
});
return Backbone.OrderedListView;
});
//# sourceMappingURL=backbone.orderedlistview.js.map;
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define('snabbdom',[],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.snabbdom = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var vnode_1 = require("./vnode");
var is = require("./is");
function addNS(data, children, sel) {
data.ns = 'http://www.w3.org/2000/svg';
if (sel !== 'foreignObject' && children !== undefined) {
for (var i = 0; i < children.length; ++i) {
var childData = children[i].data;
if (childData !== undefined) {
addNS(childData, children[i].children, children[i].sel);
}
}
2018-05-07 18:20:15 +02:00
}
}
function h(sel, b, c) {
var data = {}, children, text, i;
if (c !== undefined) {
data = b;
if (is.array(c)) {
children = c;
}
2018-05-07 18:20:15 +02:00
else if (is.primitive(c)) {
text = c;
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
else if (c && c.sel) {
children = [c];
2018-03-25 21:21:43 +02:00
}
}
2018-05-07 18:20:15 +02:00
else if (b !== undefined) {
if (is.array(b)) {
children = b;
}
else if (is.primitive(b)) {
text = b;
}
else if (b && b.sel) {
children = [b];
}
else {
data = b;
}
}
2018-05-07 18:20:15 +02:00
if (is.array(children)) {
for (i = 0; i < children.length; ++i) {
if (is.primitive(children[i]))
children[i] = vnode_1.vnode(undefined, undefined, undefined, children[i]);
2018-03-25 21:21:43 +02:00
}
}
2018-05-07 18:20:15 +02:00
if (sel[0] === 's' && sel[1] === 'v' && sel[2] === 'g' &&
(sel.length === 3 || sel[3] === '.' || sel[3] === '#')) {
addNS(data, children, sel);
}
return vnode_1.vnode(sel, data, children, text, undefined);
}
exports.h = h;
;
exports.default = h;
},{"./is":3,"./vnode":6}],2:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function createElement(tagName) {
return document.createElement(tagName);
}
function createElementNS(namespaceURI, qualifiedName) {
return document.createElementNS(namespaceURI, qualifiedName);
}
function createTextNode(text) {
return document.createTextNode(text);
}
function createComment(text) {
return document.createComment(text);
}
function insertBefore(parentNode, newNode, referenceNode) {
parentNode.insertBefore(newNode, referenceNode);
}
function removeChild(node, child) {
node.removeChild(child);
}
function appendChild(node, child) {
node.appendChild(child);
}
function parentNode(node) {
return node.parentNode;
}
function nextSibling(node) {
return node.nextSibling;
}
function tagName(elm) {
return elm.tagName;
}
function setTextContent(node, text) {
node.textContent = text;
}
function getTextContent(node) {
return node.textContent;
}
function isElement(node) {
return node.nodeType === 1;
}
function isText(node) {
return node.nodeType === 3;
}
function isComment(node) {
return node.nodeType === 8;
}
exports.htmlDomApi = {
createElement: createElement,
createElementNS: createElementNS,
createTextNode: createTextNode,
createComment: createComment,
insertBefore: insertBefore,
removeChild: removeChild,
appendChild: appendChild,
parentNode: parentNode,
nextSibling: nextSibling,
tagName: tagName,
setTextContent: setTextContent,
getTextContent: getTextContent,
isElement: isElement,
isText: isText,
isComment: isComment,
};
exports.default = exports.htmlDomApi;
},{}],3:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.array = Array.isArray;
function primitive(s) {
return typeof s === 'string' || typeof s === 'number';
}
exports.primitive = primitive;
},{}],4:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var vnode_1 = require("./vnode");
var is = require("./is");
var htmldomapi_1 = require("./htmldomapi");
function isUndef(s) { return s === undefined; }
function isDef(s) { return s !== undefined; }
var emptyNode = vnode_1.default('', {}, [], undefined, undefined);
function sameVnode(vnode1, vnode2) {
return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel;
}
function isVnode(vnode) {
return vnode.sel !== undefined;
}
function createKeyToOldIdx(children, beginIdx, endIdx) {
var i, map = {}, key, ch;
for (i = beginIdx; i <= endIdx; ++i) {
ch = children[i];
if (ch != null) {
key = ch.key;
if (key !== undefined)
map[key] = i;
}
2018-05-07 18:20:15 +02:00
}
return map;
}
var hooks = ['create', 'update', 'remove', 'destroy', 'pre', 'post'];
var h_1 = require("./h");
exports.h = h_1.h;
var thunk_1 = require("./thunk");
exports.thunk = thunk_1.thunk;
function init(modules, domApi) {
var i, j, cbs = {};
var api = domApi !== undefined ? domApi : htmldomapi_1.default;
for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = [];
for (j = 0; j < modules.length; ++j) {
var hook = modules[j][hooks[i]];
if (hook !== undefined) {
cbs[hooks[i]].push(hook);
}
}
2018-05-07 18:20:15 +02:00
}
function emptyNodeAt(elm) {
var id = elm.id ? '#' + elm.id : '';
var c = elm.className ? '.' + elm.className.split(' ').join('.') : '';
return vnode_1.default(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm);
}
function createRmCb(childElm, listeners) {
return function rmCb() {
if (--listeners === 0) {
var parent_1 = api.parentNode(childElm);
api.removeChild(parent_1, childElm);
}
};
}
function createElm(vnode, insertedVnodeQueue) {
var i, data = vnode.data;
if (data !== undefined) {
if (isDef(i = data.hook) && isDef(i = i.init)) {
i(vnode);
data = vnode.data;
}
}
2018-05-07 18:20:15 +02:00
var children = vnode.children, sel = vnode.sel;
if (sel === '!') {
if (isUndef(vnode.text)) {
vnode.text = '';
}
vnode.elm = api.createComment(vnode.text);
}
2018-05-07 18:20:15 +02:00
else if (sel !== undefined) {
// Parse selector
var hashIdx = sel.indexOf('#');
var dotIdx = sel.indexOf('.', hashIdx);
var hash = hashIdx > 0 ? hashIdx : sel.length;
var dot = dotIdx > 0 ? dotIdx : sel.length;
var tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel;
var elm = vnode.elm = isDef(data) && isDef(i = data.ns) ? api.createElementNS(i, tag)
: api.createElement(tag);
if (hash < dot)
elm.setAttribute('id', sel.slice(hash + 1, dot));
if (dotIdx > 0)
elm.setAttribute('class', sel.slice(dot + 1).replace(/\./g, ' '));
for (i = 0; i < cbs.create.length; ++i)
cbs.create[i](emptyNode, vnode);
if (is.array(children)) {
for (i = 0; i < children.length; ++i) {
var ch = children[i];
if (ch != null) {
api.appendChild(elm, createElm(ch, insertedVnodeQueue));
}
}
}
else if (is.primitive(vnode.text)) {
api.appendChild(elm, api.createTextNode(vnode.text));
}
i = vnode.data.hook; // Reuse variable
if (isDef(i)) {
if (i.create)
i.create(emptyNode, vnode);
if (i.insert)
insertedVnodeQueue.push(vnode);
}
}
2018-05-07 18:20:15 +02:00
else {
vnode.elm = api.createTextNode(vnode.text);
}
2018-05-07 18:20:15 +02:00
return vnode.elm;
}
function addVnodes(parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) {
for (; startIdx <= endIdx; ++startIdx) {
var ch = vnodes[startIdx];
if (ch != null) {
api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before);
}
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
}
function invokeDestroyHook(vnode) {
var i, j, data = vnode.data;
if (data !== undefined) {
if (isDef(i = data.hook) && isDef(i = i.destroy))
i(vnode);
for (i = 0; i < cbs.destroy.length; ++i)
cbs.destroy[i](vnode);
if (vnode.children !== undefined) {
for (j = 0; j < vnode.children.length; ++j) {
i = vnode.children[j];
if (i != null && typeof i !== "string") {
invokeDestroyHook(i);
}
}
}
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
}
function removeVnodes(parentElm, vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
var i_1 = void 0, listeners = void 0, rm = void 0, ch = vnodes[startIdx];
if (ch != null) {
if (isDef(ch.sel)) {
invokeDestroyHook(ch);
listeners = cbs.remove.length + 1;
rm = createRmCb(ch.elm, listeners);
for (i_1 = 0; i_1 < cbs.remove.length; ++i_1)
cbs.remove[i_1](ch, rm);
if (isDef(i_1 = ch.data) && isDef(i_1 = i_1.hook) && isDef(i_1 = i_1.remove)) {
i_1(ch, rm);
}
else {
rm();
}
}
else {
api.removeChild(parentElm, ch.elm);
}
}
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
}
function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) {
var oldStartIdx = 0, newStartIdx = 0;
var oldEndIdx = oldCh.length - 1;
var oldStartVnode = oldCh[0];
var oldEndVnode = oldCh[oldEndIdx];
var newEndIdx = newCh.length - 1;
var newStartVnode = newCh[0];
var newEndVnode = newCh[newEndIdx];
var oldKeyToIdx;
var idxInOld;
var elmToMove;
var before;
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (oldStartVnode == null) {
oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left
}
else if (oldEndVnode == null) {
oldEndVnode = oldCh[--oldEndIdx];
}
else if (newStartVnode == null) {
newStartVnode = newCh[++newStartIdx];
}
else if (newEndVnode == null) {
newEndVnode = newCh[--newEndIdx];
}
else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
}
else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
}
else if (sameVnode(oldStartVnode, newEndVnode)) {
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm));
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
}
else if (sameVnode(oldEndVnode, newStartVnode)) {
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
}
else {
if (oldKeyToIdx === undefined) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
}
idxInOld = oldKeyToIdx[newStartVnode.key];
if (isUndef(idxInOld)) {
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
newStartVnode = newCh[++newStartIdx];
}
else {
elmToMove = oldCh[idxInOld];
if (elmToMove.sel !== newStartVnode.sel) {
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
}
else {
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
oldCh[idxInOld] = undefined;
api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm);
}
newStartVnode = newCh[++newStartIdx];
}
}
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
if (oldStartIdx > oldEndIdx) {
before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
}
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
function patchVnode(oldVnode, vnode, insertedVnodeQueue) {
var i, hook;
if (isDef(i = vnode.data) && isDef(hook = i.hook) && isDef(i = hook.prepatch)) {
i(oldVnode, vnode);
}
2018-05-07 18:20:15 +02:00
var elm = vnode.elm = oldVnode.elm;
var oldCh = oldVnode.children;
var ch = vnode.children;
if (oldVnode === vnode)
return;
if (vnode.data !== undefined) {
for (i = 0; i < cbs.update.length; ++i)
cbs.update[i](oldVnode, vnode);
i = vnode.data.hook;
if (isDef(i) && isDef(i = i.update))
i(oldVnode, vnode);
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch)
updateChildren(elm, oldCh, ch, insertedVnodeQueue);
}
else if (isDef(ch)) {
if (isDef(oldVnode.text))
api.setTextContent(elm, '');
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
}
else if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
}
else if (isDef(oldVnode.text)) {
api.setTextContent(elm, '');
}
}
2018-05-07 18:20:15 +02:00
else if (oldVnode.text !== vnode.text) {
api.setTextContent(elm, vnode.text);
}
2018-05-07 18:20:15 +02:00
if (isDef(hook) && isDef(i = hook.postpatch)) {
i(oldVnode, vnode);
}
}
2018-05-07 18:20:15 +02:00
return function patch(oldVnode, vnode) {
var i, elm, parent;
var insertedVnodeQueue = [];
for (i = 0; i < cbs.pre.length; ++i)
cbs.pre[i]();
if (!isVnode(oldVnode)) {
oldVnode = emptyNodeAt(oldVnode);
}
if (sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode, insertedVnodeQueue);
}
else {
elm = oldVnode.elm;
parent = api.parentNode(elm);
createElm(vnode, insertedVnodeQueue);
if (parent !== null) {
api.insertBefore(parent, vnode.elm, api.nextSibling(elm));
removeVnodes(parent, [oldVnode], 0, 0);
}
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
for (i = 0; i < insertedVnodeQueue.length; ++i) {
insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i]);
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
for (i = 0; i < cbs.post.length; ++i)
cbs.post[i]();
return vnode;
};
}
exports.init = init;
},{"./h":1,"./htmldomapi":2,"./is":3,"./thunk":5,"./vnode":6}],5:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var h_1 = require("./h");
function copyToThunk(vnode, thunk) {
thunk.elm = vnode.elm;
vnode.data.fn = thunk.data.fn;
vnode.data.args = thunk.data.args;
thunk.data = vnode.data;
thunk.children = vnode.children;
thunk.text = vnode.text;
thunk.elm = vnode.elm;
}
function init(thunk) {
var cur = thunk.data;
var vnode = cur.fn.apply(undefined, cur.args);
copyToThunk(vnode, thunk);
}
function prepatch(oldVnode, thunk) {
var i, old = oldVnode.data, cur = thunk.data;
var oldArgs = old.args, args = cur.args;
if (old.fn !== cur.fn || oldArgs.length !== args.length) {
copyToThunk(cur.fn.apply(undefined, args), thunk);
return;
}
for (i = 0; i < args.length; ++i) {
if (oldArgs[i] !== args[i]) {
copyToThunk(cur.fn.apply(undefined, args), thunk);
return;
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
}
copyToThunk(oldVnode, thunk);
}
exports.thunk = function thunk(sel, key, fn, args) {
if (args === undefined) {
args = fn;
fn = key;
key = undefined;
}
return h_1.h(sel, {
key: key,
hook: { init: init, prepatch: prepatch },
fn: fn,
args: args
});
};
exports.default = exports.thunk;
},{"./h":1}],6:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function vnode(sel, data, children, text, elm) {
var key = data === undefined ? undefined : data.key;
return { sel: sel, data: data, children: children,
text: text, elm: elm, key: key };
}
exports.vnode = vnode;
exports.default = vnode;
},{}]},{},[4])(4)
});
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy8ucmVnaXN0cnkubnBtanMub3JnL2Jyb3dzZXItcGFjay82LjAuMi9ub2RlX21vZHVsZXMvYnJvd3Nlci1wYWNrL19wcmVsdWRlLmpzIiwiaC5qcyIsImh0bWxkb21hcGkuanMiLCJpcy5qcyIsInNuYWJiZG9tLmpzIiwidGh1bmsuanMiLCJ2bm9kZS5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDMURBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNqRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNQQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNsVEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM5Q0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzB
;
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define('snabbdom-attributes',[],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.snabbdom_attributes = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var booleanAttrs = ["allowfullscreen", "async", "autofocus", "autoplay", "checked", "compact", "controls", "declare",
"default", "defaultchecked", "defaultmuted", "defaultselected", "defer", "disabled", "draggable",
"enabled", "formnovalidate", "hidden", "indeterminate", "inert", "ismap", "itemscope", "loop", "multiple",
"muted", "nohref", "noresize", "noshade", "novalidate", "nowrap", "open", "pauseonexit", "readonly",
"required", "reversed", "scoped", "seamless", "selected", "sortable", "spellcheck", "translate",
"truespeed", "typemustmatch", "visible"];
var xlinkNS = 'http://www.w3.org/1999/xlink';
var xmlNS = 'http://www.w3.org/XML/1998/namespace';
var colonChar = 58;
var xChar = 120;
var booleanAttrsDict = Object.create(null);
for (var i = 0, len = booleanAttrs.length; i < len; i++) {
booleanAttrsDict[booleanAttrs[i]] = true;
}
function updateAttrs(oldVnode, vnode) {
var key, elm = vnode.elm, oldAttrs = oldVnode.data.attrs, attrs = vnode.data.attrs;
if (!oldAttrs && !attrs)
return;
if (oldAttrs === attrs)
return;
oldAttrs = oldAttrs || {};
attrs = attrs || {};
// update modified attributes, add new attributes
for (key in attrs) {
var cur = attrs[key];
var old = oldAttrs[key];
if (old !== cur) {
if (booleanAttrsDict[key]) {
if (cur) {
elm.setAttribute(key, "");
}
else {
elm.removeAttribute(key);
}
}
else {
if (key.charCodeAt(0) !== xChar) {
elm.setAttribute(key, cur);
}
else if (key.charCodeAt(3) === colonChar) {
// Assume xml namespace
elm.setAttributeNS(xmlNS, key, cur);
}
else if (key.charCodeAt(5) === colonChar) {
// Assume xlink namespace
elm.setAttributeNS(xlinkNS, key, cur);
}
else {
elm.setAttribute(key, cur);
}
}
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
}
// remove removed attributes
// use `in` operator since the previous `for` iteration uses it (.i.e. add even attributes with undefined value)
// the other option is to remove all attributes with value == undefined
for (key in oldAttrs) {
if (!(key in attrs)) {
elm.removeAttribute(key);
}
}
2018-05-07 18:20:15 +02:00
}
exports.attributesModule = { create: updateAttrs, update: updateAttrs };
exports.default = exports.attributesModule;
},{}]},{},[1])(1)
});
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy8ucmVnaXN0cnkubnBtanMub3JnL2Jyb3dzZXItcGFjay82LjAuMi9ub2RlX21vZHVsZXMvYnJvd3Nlci1wYWNrL19wcmVsdWRlLmpzIiwibW9kdWxlcy9hdHRyaWJ1dGVzLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FDQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzBdLmNhbGwobC5leHBvcnRzLGZ1bmN0aW9uKGUpe3ZhciBuPXRbb11bMV1bZV07cmV0dXJuIHMobj9uOmUpfSxsLGwuZXhwb3J0cyxlLHQsbixyKX1yZXR1cm4gbltvXS5leHBvcnRzfXZhciBpPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7Zm9yKHZhciBvPTA7bzxyLmxlbmd0aDtvKyspcyhyW29dKTtyZXR1cm4gc30pIiwiXCJ1c2Ugc3RyaWN0XCI7XG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgXCJfX2VzTW9kdWxlXCIsIHsgdmFsdWU6IHRydWUgfSk7XG52YXIgYm9vbGVhbkF0dHJzID0gW1wiYWxsb3dmdWxsc2NyZWVuXCIsIFwiYXN5bmNcIiwgXCJhdXRvZm9jdXNcIiwgXCJhdXRvcGxheVwiLCBcImNoZWNrZWRcIiwgXCJjb21wYWN0XCIsIFwiY29udHJvbHNcIiwgXCJkZWNsYXJlXCIsXG4gICAgXCJkZWZhdWx0XCIsIFwiZGVmYXVsdGNoZWNrZWRcIiwgXCJkZWZhdWx0bXV0ZWRcIiwgXCJkZWZhdWx0c2VsZWN0ZWRcIiwgXCJkZWZlclwiLCBcImRpc2FibGVkXCIsIFwiZHJhZ2dhYmxlXCIsXG4gICAgXCJlbmFibGVkXCIsIFwiZm9ybW5vdmFsaWRhdGVcIiwgXCJoaWRkZW5cIiwgXCJpbmRldGVybWluYXRlXCIsIFwiaW5lcnRcIiwgXCJpc21hcFwiLCBcIml0ZW1zY29wZVwiLCBcImxvb3BcIiwgXCJtdWx0aXBsZVwiLFxuICAgIFwibXV0ZWRcIiwgXCJub2hyZWZcIiwgXCJub3Jlc2l6ZVwiLCBcIm5vc2hhZGVcIiwgXCJub3ZhbGlkYXRlXCIsIFwibm93cmFwXCIsIFwib3BlblwiLCBcInBhdXNlb25leGl0XCIsIFwicmVhZG9ubHlcIixcbiAgICBcInJlcXVpcmVkXCIsIFwicmV2ZXJzZWRcIiwgXCJzY29wZWRcIiwgXCJzZWFtbGVzc1wiLCBcInNlbGVjdGVkXCIsIFwic29ydGFibGVcIiwgXCJzcGVsbGNoZWNrXCIsIFwidHJhbnNsYXRlXCIsXG4gICAgXCJ0cnVlc3BlZWRcIiwgXCJ0eXBlbXVzdG1hdGNoXCIsIFwidmlzaWJsZVwiXTtcbnZhciB4bGlua05TID0gJ2h0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsnO1xudmFyIHhtbE5TID0gJ2h0dHA6Ly93d3cudzMub3JnL1hNTC8xOTk4L25hbWVzcGFjZSc7XG52YXIgY29sb25DaGFyID0gNTg7XG52YXIgeENoYXIgPSAxMjA7XG52YXIgYm9vbGVhbkF0dHJzRGljdCA9IE9iamVjdC5jcmVhdGUobnVsbCk7XG5mb3IgKHZhciBpID0gMCwgbGVuID0gYm9vbGVhbkF0dHJzLmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgYm9vbGVhbkF0dHJzRGljdFtib29sZWFuQXR0cnNbaV1dID0gdHJ1ZTtcbn1cbmZ1bmN0aW9uIHVwZGF0ZUF0dHJzKG9sZFZub2RlLCB2bm9kZSkge1xuICAgIHZhciBrZXksIGVsbSA9IHZub2RlLmVsbSwgb2xkQXR0cnMgPSBvbGRWbm9kZS5kYXRhLmF0dHJzLCBhdHRycyA9IHZub2RlLmRhdGEuYXR0cnM7XG4gICAgaWYgKCFvbGRBdHRycyAmJiAhYXR0cnMpXG4gICAgICAgIHJldHVybjtcbiAgICBpZiAob2xkQXR0cnMgPT09IGF0dHJzKVxuICAgICAgICByZXR1cm47XG4gICAgb2xkQXR0cnMgPSBvbGRBdHRycyB8fCB7fTtcbiAgICBhdHRycyA9IGF0dHJzIHx8IHt9O1xuICAgIC8vIHVwZGF0ZSBtb2RpZmllZCBhdHRyaWJ1dGVzLCBhZGQgbmV3IGF0dHJpYnV0ZXNcbiAgICBmb3IgKGtleSBpbiBhdHRycykge1xuICAgICAgICB2YXIgY3VyID0gYXR0cnNba2V5XTtcbiAgICAgICAgdmFyIG9sZCA9IG9sZEF0dHJzW2tleV07XG4gICAgICAgIGlmIChvbGQgIT09IGN1cikge1xuICAgICAgICAgICAgaWYgKGJvb2xlYW5BdHRyc0RpY3Rba2V5XSkge1xuICAgICAgICAgICAgICAgIGlmIChjdXIpIHtcbiAgICAgICAgICAgICAgICAgICAgZWxtLnNldEF0dHJpYnV0ZShrZXksIFwiXCIpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgZWxtLnJlbW92ZUF0dHJpYnV0ZShrZXkpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIGlmIChrZXkuY2hhckNvZGVBdCgwKSAhPT0geENoYXIpIHtcbiAgICAgICAgICAgICAgICAgICAgZWxtLnNldEF0dHJpYnV0ZShrZXksIGN1cik7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2UgaWYgKGtleS5jaGFyQ29kZUF0KDMpID09PSBjb2xvbkNoYXIpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gQXNzdW1lIHhtbCBuYW1lc3BhY2VcbiAgICAgICA
;
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define('snabbdom-class',[],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.snabbdom_class = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function updateClass(oldVnode, vnode) {
var cur, name, elm = vnode.elm, oldClass = oldVnode.data.class, klass = vnode.data.class;
if (!oldClass && !klass)
return;
if (oldClass === klass)
return;
oldClass = oldClass || {};
klass = klass || {};
for (name in oldClass) {
if (!klass[name]) {
elm.classList.remove(name);
}
2018-05-07 18:20:15 +02:00
}
for (name in klass) {
cur = klass[name];
if (cur !== oldClass[name]) {
elm.classList[cur ? 'add' : 'remove'](name);
}
}
2018-05-07 18:20:15 +02:00
}
exports.classModule = { create: updateClass, update: updateClass };
exports.default = exports.classModule;
2018-05-07 18:20:15 +02:00
},{}]},{},[1])(1)
});
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy8ucmVnaXN0cnkubnBtanMub3JnL2Jyb3dzZXItcGFjay82LjAuMi9ub2RlX21vZHVsZXMvYnJvd3Nlci1wYWNrL19wcmVsdWRlLmpzIiwibW9kdWxlcy9jbGFzcy5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsIlwidXNlIHN0cmljdFwiO1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuZnVuY3Rpb24gdXBkYXRlQ2xhc3Mob2xkVm5vZGUsIHZub2RlKSB7XG4gICAgdmFyIGN1ciwgbmFtZSwgZWxtID0gdm5vZGUuZWxtLCBvbGRDbGFzcyA9IG9sZFZub2RlLmRhdGEuY2xhc3MsIGtsYXNzID0gdm5vZGUuZGF0YS5jbGFzcztcbiAgICBpZiAoIW9sZENsYXNzICYmICFrbGFzcylcbiAgICAgICAgcmV0dXJuO1xuICAgIGlmIChvbGRDbGFzcyA9PT0ga2xhc3MpXG4gICAgICAgIHJldHVybjtcbiAgICBvbGRDbGFzcyA9IG9sZENsYXNzIHx8IHt9O1xuICAgIGtsYXNzID0ga2xhc3MgfHwge307XG4gICAgZm9yIChuYW1lIGluIG9sZENsYXNzKSB7XG4gICAgICAgIGlmICgha2xhc3NbbmFtZV0pIHtcbiAgICAgICAgICAgIGVsbS5jbGFzc0xpc3QucmVtb3ZlKG5hbWUpO1xuICAgICAgICB9XG4gICAgfVxuICAgIGZvciAobmFtZSBpbiBrbGFzcykge1xuICAgICAgICBjdXIgPSBrbGFzc1tuYW1lXTtcbiAgICAgICAgaWYgKGN1ciAhPT0gb2xkQ2xhc3NbbmFtZV0pIHtcbiAgICAgICAgICAgIGVsbS5jbGFzc0xpc3RbY3VyID8gJ2FkZCcgOiAncmVtb3ZlJ10obmFtZSk7XG4gICAgICAgIH1cbiAgICB9XG59XG5leHBvcnRzLmNsYXNzTW9kdWxlID0geyBjcmVhdGU6IHVwZGF0ZUNsYXNzLCB1cGRhdGU6IHVwZGF0ZUNsYXNzIH07XG5leHBvcnRzLmRlZmF1bHQgPSBleHBvcnRzLmNsYXNzTW9kdWxlO1xuLy8jIHNvdXJjZU1hcHBpbmdVUkw9Y2xhc3MuanMubWFwIl19
;
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define('snabbdom-dataset',[],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.snabbdom_dataset = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var CAPS_REGEX = /[A-Z]/g;
function updateDataset(oldVnode, vnode) {
var elm = vnode.elm, oldDataset = oldVnode.data.dataset, dataset = vnode.data.dataset, key;
if (!oldDataset && !dataset)
return;
if (oldDataset === dataset)
return;
oldDataset = oldDataset || {};
dataset = dataset || {};
var d = elm.dataset;
for (key in oldDataset) {
if (!dataset[key]) {
if (d) {
if (key in d) {
delete d[key];
}
}
else {
elm.removeAttribute('data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase());
}
}
}
for (key in dataset) {
if (oldDataset[key] !== dataset[key]) {
if (d) {
d[key] = dataset[key];
}
else {
elm.setAttribute('data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase(), dataset[key]);
}
}
}
}
exports.datasetModule = { create: updateDataset, update: updateDataset };
exports.default = exports.datasetModule;
2018-05-07 18:20:15 +02:00
},{}]},{},[1])(1)
});
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJtb2R1bGVzL2RhdGFzZXQuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUNBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsIlwidXNlIHN0cmljdFwiO1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xudmFyIENBUFNfUkVHRVggPSAvW0EtWl0vZztcbmZ1bmN0aW9uIHVwZGF0ZURhdGFzZXQob2xkVm5vZGUsIHZub2RlKSB7XG4gICAgdmFyIGVsbSA9IHZub2RlLmVsbSwgb2xkRGF0YXNldCA9IG9sZFZub2RlLmRhdGEuZGF0YXNldCwgZGF0YXNldCA9IHZub2RlLmRhdGEuZGF0YXNldCwga2V5O1xuICAgIGlmICghb2xkRGF0YXNldCAmJiAhZGF0YXNldClcbiAgICAgICAgcmV0dXJuO1xuICAgIGlmIChvbGREYXRhc2V0ID09PSBkYXRhc2V0KVxuICAgICAgICByZXR1cm47XG4gICAgb2xkRGF0YXNldCA9IG9sZERhdGFzZXQgfHwge307XG4gICAgZGF0YXNldCA9IGRhdGFzZXQgfHwge307XG4gICAgdmFyIGQgPSBlbG0uZGF0YXNldDtcbiAgICBmb3IgKGtleSBpbiBvbGREYXRhc2V0KSB7XG4gICAgICAgIGlmICghZGF0YXNldFtrZXldKSB7XG4gICAgICAgICAgICBpZiAoZCkge1xuICAgICAgICAgICAgICAgIGlmIChrZXkgaW4gZCkge1xuICAgICAgICAgICAgICAgICAgICBkZWxldGUgZFtrZXldO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIGVsbS5yZW1vdmVBdHRyaWJ1dGUoJ2RhdGEtJyArIGtleS5yZXBsYWNlKENBUFNfUkVHRVgsICctJCYnKS50b0xvd2VyQ2FzZSgpKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbiAgICBmb3IgKGtleSBpbiBkYXRhc2V0KSB7XG4gICAgICAgIGlmIChvbGREYXRhc2V0W2tleV0gIT09IGRhdGFzZXRba2V5XSkge1xuICAgICAgICAgICAgaWYgKGQpIHtcbiAgICAgICAgICAgICAgICBkW2tleV0gPSBkYXRhc2V0W2tleV07XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICBlbG0uc2V0QXR0cmlidXRlKCdkYXRhLScgKyBrZXkucmVwbGFjZShDQVBTX1JFR0VYLCAnLSQmJykudG9Mb3dlckNhc2UoKSwgZGF0YXNldFtrZXldKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbn1cbmV4cG9ydHMuZGF0YXNldE1vZHVsZSA9IHsgY3JlYXRlOiB1cGRhdGVEYXRhc2V0LCB1cGRhdGU6IHVwZGF0ZURhdGFzZXQgfTtcbmV4cG9ydHMuZGVmYXVsdCA9IGV4cG9ydHMuZGF0YXNldE1vZHVsZTtcbi8vIyBzb3VyY2VNYXBwaW5nVVJMPWRhdGFzZXQuanMubWFwIl19
;
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define('snabbdom-props',[],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.snabbdom_props = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function updateProps(oldVnode, vnode) {
var key, cur, old, elm = vnode.elm, oldProps = oldVnode.data.props, props = vnode.data.props;
if (!oldProps && !props)
return;
if (oldProps === props)
return;
oldProps = oldProps || {};
props = props || {};
for (key in oldProps) {
if (!props[key]) {
delete elm[key];
}
}
for (key in props) {
cur = props[key];
old = oldProps[key];
if (old !== cur && (key !== 'value' || elm[key] !== cur)) {
elm[key] = cur;
}
}
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
exports.propsModule = { create: updateProps, update: updateProps };
exports.default = exports.propsModule;
2018-05-07 18:20:15 +02:00
},{}]},{},[1])(1)
});
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy8ucmVnaXN0cnkubnBtanMub3JnL2Jyb3dzZXItcGFjay82LjAuMi9ub2RlX21vZHVsZXMvYnJvd3Nlci1wYWNrL19wcmVsdWRlLmpzIiwibW9kdWxlcy9wcm9wcy5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzBdLmNhbGwobC5leHBvcnRzLGZ1bmN0aW9uKGUpe3ZhciBuPXRbb11bMV1bZV07cmV0dXJuIHMobj9uOmUpfSxsLGwuZXhwb3J0cyxlLHQsbixyKX1yZXR1cm4gbltvXS5leHBvcnRzfXZhciBpPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7Zm9yKHZhciBvPTA7bzxyLmxlbmd0aDtvKyspcyhyW29dKTtyZXR1cm4gc30pIiwiXCJ1c2Ugc3RyaWN0XCI7XG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgXCJfX2VzTW9kdWxlXCIsIHsgdmFsdWU6IHRydWUgfSk7XG5mdW5jdGlvbiB1cGRhdGVQcm9wcyhvbGRWbm9kZSwgdm5vZGUpIHtcbiAgICB2YXIga2V5LCBjdXIsIG9sZCwgZWxtID0gdm5vZGUuZWxtLCBvbGRQcm9wcyA9IG9sZFZub2RlLmRhdGEucHJvcHMsIHByb3BzID0gdm5vZGUuZGF0YS5wcm9wcztcbiAgICBpZiAoIW9sZFByb3BzICYmICFwcm9wcylcbiAgICAgICAgcmV0dXJuO1xuICAgIGlmIChvbGRQcm9wcyA9PT0gcHJvcHMpXG4gICAgICAgIHJldHVybjtcbiAgICBvbGRQcm9wcyA9IG9sZFByb3BzIHx8IHt9O1xuICAgIHByb3BzID0gcHJvcHMgfHwge307XG4gICAgZm9yIChrZXkgaW4gb2xkUHJvcHMpIHtcbiAgICAgICAgaWYgKCFwcm9wc1trZXldKSB7XG4gICAgICAgICAgICBkZWxldGUgZWxtW2tleV07XG4gICAgICAgIH1cbiAgICB9XG4gICAgZm9yIChrZXkgaW4gcHJvcHMpIHtcbiAgICAgICAgY3VyID0gcHJvcHNba2V5XTtcbiAgICAgICAgb2xkID0gb2xkUHJvcHNba2V5XTtcbiAgICAgICAgaWYgKG9sZCAhPT0gY3VyICYmIChrZXkgIT09ICd2YWx1ZScgfHwgZWxtW2tleV0gIT09IGN1cikpIHtcbiAgICAgICAgICAgIGVsbVtrZXldID0gY3VyO1xuICAgICAgICB9XG4gICAgfVxufVxuZXhwb3J0cy5wcm9wc01vZHVsZSA9IHsgY3JlYXRlOiB1cGRhdGVQcm9wcywgdXBkYXRlOiB1cGRhdGVQcm9wcyB9O1xuZXhwb3J0cy5kZWZhdWx0ID0gZXhwb3J0cy5wcm9wc01vZHVsZTtcbi8vIyBzb3VyY2VNYXBwaW5nVVJMPXByb3BzLmpzLm1hcCJdfQ==
;
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define('snabbdom-style',[],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.snabbdom_style = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var raf = (typeof window !== 'undefined' && window.requestAnimationFrame) || setTimeout;
var nextFrame = function (fn) { raf(function () { raf(fn); }); };
function setNextFrame(obj, prop, val) {
nextFrame(function () { obj[prop] = val; });
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
function updateStyle(oldVnode, vnode) {
var cur, name, elm = vnode.elm, oldStyle = oldVnode.data.style, style = vnode.data.style;
if (!oldStyle && !style)
return;
if (oldStyle === style)
return;
oldStyle = oldStyle || {};
style = style || {};
var oldHasDel = 'delayed' in oldStyle;
for (name in oldStyle) {
if (!style[name]) {
if (name[0] === '-' && name[1] === '-') {
elm.style.removeProperty(name);
}
else {
elm.style[name] = '';
}
}
}
for (name in style) {
cur = style[name];
if (name === 'delayed' && style.delayed) {
for (var name2 in style.delayed) {
cur = style.delayed[name2];
if (!oldHasDel || cur !== oldStyle.delayed[name2]) {
setNextFrame(elm.style, name2, cur);
}
}
}
else if (name !== 'remove' && cur !== oldStyle[name]) {
if (name[0] === '-' && name[1] === '-') {
elm.style.setProperty(name, cur);
}
else {
elm.style[name] = cur;
}
}
}
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
function applyDestroyStyle(vnode) {
var style, name, elm = vnode.elm, s = vnode.data.style;
if (!s || !(style = s.destroy))
return;
for (name in style) {
elm.style[name] = style[name];
}
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
function applyRemoveStyle(vnode, rm) {
var s = vnode.data.style;
if (!s || !s.remove) {
rm();
return;
}
var name, elm = vnode.elm, i = 0, compStyle, style = s.remove, amount = 0, applied = [];
for (name in style) {
applied.push(name);
elm.style[name] = style[name];
}
compStyle = getComputedStyle(elm);
var props = compStyle['transition-property'].split(', ');
for (; i < props.length; ++i) {
if (applied.indexOf(props[i]) !== -1)
amount++;
}
elm.addEventListener('transitionend', function (ev) {
if (ev.target === elm)
--amount;
if (amount === 0)
rm();
});
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
exports.styleModule = {
create: updateStyle,
update: updateStyle,
destroy: applyDestroyStyle,
remove: applyRemoveStyle
};
exports.default = exports.styleModule;
2018-05-07 18:20:15 +02:00
},{}]},{},[1])(1)
});
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy8ucmVnaXN0cnkubnBtanMub3JnL2Jyb3dzZXItcGFjay82LjAuMi9ub2RlX21vZHVsZXMvYnJvd3Nlci1wYWNrL19wcmVsdWRlLmpzIiwibW9kdWxlcy9zdHlsZS5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzBdLmNhbGwobC5leHBvcnRzLGZ1bmN0aW9uKGUpe3ZhciBuPXRbb11bMV1bZV07cmV0dXJuIHMobj9uOmUpfSxsLGwuZXhwb3J0cyxlLHQsbixyKX1yZXR1cm4gbltvXS5leHBvcnRzfXZhciBpPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7Zm9yKHZhciBvPTA7bzxyLmxlbmd0aDtvKyspcyhyW29dKTtyZXR1cm4gc30pIiwiXCJ1c2Ugc3RyaWN0XCI7XG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgXCJfX2VzTW9kdWxlXCIsIHsgdmFsdWU6IHRydWUgfSk7XG52YXIgcmFmID0gKHR5cGVvZiB3aW5kb3cgIT09ICd1bmRlZmluZWQnICYmIHdpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUpIHx8IHNldFRpbWVvdXQ7XG52YXIgbmV4dEZyYW1lID0gZnVuY3Rpb24gKGZuKSB7IHJhZihmdW5jdGlvbiAoKSB7IHJhZihmbik7IH0pOyB9O1xuZnVuY3Rpb24gc2V0TmV4dEZyYW1lKG9iaiwgcHJvcCwgdmFsKSB7XG4gICAgbmV4dEZyYW1lKGZ1bmN0aW9uICgpIHsgb2JqW3Byb3BdID0gdmFsOyB9KTtcbn1cbmZ1bmN0aW9uIHVwZGF0ZVN0eWxlKG9sZFZub2RlLCB2bm9kZSkge1xuICAgIHZhciBjdXIsIG5hbWUsIGVsbSA9IHZub2RlLmVsbSwgb2xkU3R5bGUgPSBvbGRWbm9kZS5kYXRhLnN0eWxlLCBzdHlsZSA9IHZub2RlLmRhdGEuc3R5bGU7XG4gICAgaWYgKCFvbGRTdHlsZSAmJiAhc3R5bGUpXG4gICAgICAgIHJldHVybjtcbiAgICBpZiAob2xkU3R5bGUgPT09IHN0eWxlKVxuICAgICAgICByZXR1cm47XG4gICAgb2xkU3R5bGUgPSBvbGRTdHlsZSB8fCB7fTtcbiAgICBzdHlsZSA9IHN0eWxlIHx8IHt9O1xuICAgIHZhciBvbGRIYXNEZWwgPSAnZGVsYXllZCcgaW4gb2xkU3R5bGU7XG4gICAgZm9yIChuYW1lIGluIG9sZFN0eWxlKSB7XG4gICAgICAgIGlmICghc3R5bGVbbmFtZV0pIHtcbiAgICAgICAgICAgIGlmIChuYW1lWzBdID09PSAnLScgJiYgbmFtZVsxXSA9PT0gJy0nKSB7XG4gICAgICAgICAgICAgICAgZWxtLnN0eWxlLnJlbW92ZVByb3BlcnR5KG5hbWUpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgZWxtLnN0eWxlW25hbWVdID0gJyc7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG4gICAgZm9yIChuYW1lIGluIHN0eWxlKSB7XG4gICAgICAgIGN1ciA9IHN0eWxlW25hbWVdO1xuICAgICAgICBpZiAobmFtZSA9PT0gJ2RlbGF5ZWQnICYmIHN0eWxlLmRlbGF5ZWQpIHtcbiAgICAgICAgICAgIGZvciAodmFyIG5hbWUyIGluIHN0eWxlLmRlbGF5ZWQpIHtcbiAgICAgICAgICAgICAgICBjdXIgPSBzdHlsZS5kZWxheWVkW25hbWUyXTtcbiAgICAgICAgICAgICAgICBpZiAoIW9sZEhhc0RlbCB8fCBjdXIgIT09IG9sZFN0eWxlLmRlbGF5ZWRbbmFtZTJdKSB7XG4gICAgICAgICAgICAgICAgICAgIHNldE5leHRGcmFtZShlbG0uc3R5bGUsIG5hbWUyLCBjdXIpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChuYW1lICE9PSAncmVtb3ZlJyAmJiBjdXIgIT09IG9sZFN0eWxlW25hbWVdKSB7XG4gICAgICAgICAgICBpZiAobmFtZVswXSA9PT0gJy0nICYmIG5hbWVbMV0gPT09ICctJykge1xuICAgICAgICAgICAgICAgIGVsbS5zdHlsZS5zZXRQcm9wZXJ0eShuYW1lLCBjdXIpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgZWxtLnN0eWxlW25hbWVdID0gY3VyO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxufVxuZnVuY3Rpb24gYXBwbHlEZXN0cm95U3R5bGUodm5vZGUpIHtcbiAgICB2YXIgc3R5bGUsIG5hbWUsIGVsbSA9IHZub2RlLmVsbSwgcyA9IHZub2RlLmRhdGEuc3R5bGU7XG4gICAgaWYgKCFzIHx8ICEoc3R5bGUgPSBzLmRlc3Ryb3kpKVxuICAgICAgICByZXR1cm47XG4gICAgZm9yIChuYW1lIGluIHN0eWxlKSB7XG4gICAgICAgIGVsbS5zdHlsZVtuYW1lXSA9IHN0eWxlW25hbWVdO1xuICAgIH1cbn1cbmZ1bmN0aW9uIGFwcGx5UmVtb3ZlU3R5bGUodm5vZGUsIHJtKSB7XG4gICAgdmFyIHMgPSB2bm9kZS5kYXRhLnN0eWxlO1xuICAgIGlmICghcyB8fCAhcy5yZW1vdmUpIHt
;
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define('tovnode',[],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.tovnode = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function createElement(tagName) {
return document.createElement(tagName);
}
2018-05-07 18:20:15 +02:00
function createElementNS(namespaceURI, qualifiedName) {
return document.createElementNS(namespaceURI, qualifiedName);
}
function createTextNode(text) {
return document.createTextNode(text);
}
function createComment(text) {
return document.createComment(text);
}
function insertBefore(parentNode, newNode, referenceNode) {
parentNode.insertBefore(newNode, referenceNode);
}
function removeChild(node, child) {
node.removeChild(child);
}
function appendChild(node, child) {
node.appendChild(child);
}
function parentNode(node) {
return node.parentNode;
}
function nextSibling(node) {
return node.nextSibling;
}
function tagName(elm) {
return elm.tagName;
}
function setTextContent(node, text) {
node.textContent = text;
}
function getTextContent(node) {
return node.textContent;
}
function isElement(node) {
return node.nodeType === 1;
}
function isText(node) {
return node.nodeType === 3;
}
function isComment(node) {
return node.nodeType === 8;
}
exports.htmlDomApi = {
createElement: createElement,
createElementNS: createElementNS,
createTextNode: createTextNode,
createComment: createComment,
insertBefore: insertBefore,
removeChild: removeChild,
appendChild: appendChild,
parentNode: parentNode,
nextSibling: nextSibling,
tagName: tagName,
setTextContent: setTextContent,
getTextContent: getTextContent,
isElement: isElement,
isText: isText,
isComment: isComment,
};
exports.default = exports.htmlDomApi;
},{}],2:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var vnode_1 = require("./vnode");
var htmldomapi_1 = require("./htmldomapi");
function toVNode(node, domApi) {
var api = domApi !== undefined ? domApi : htmldomapi_1.default;
var text;
if (api.isElement(node)) {
var id = node.id ? '#' + node.id : '';
var cn = node.getAttribute('class');
var c = cn ? '.' + cn.split(' ').join('.') : '';
var sel = api.tagName(node).toLowerCase() + id + c;
var attrs = {};
var children = [];
var name_1;
var i = void 0, n = void 0;
var elmAttrs = node.attributes;
var elmChildren = node.childNodes;
for (i = 0, n = elmAttrs.length; i < n; i++) {
name_1 = elmAttrs[i].nodeName;
if (name_1 !== 'id' && name_1 !== 'class') {
attrs[name_1] = elmAttrs[i].nodeValue;
}
}
for (i = 0, n = elmChildren.length; i < n; i++) {
children.push(toVNode(elmChildren[i]));
}
return vnode_1.default(sel, { attrs: attrs }, children, undefined, node);
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
else if (api.isText(node)) {
text = api.getTextContent(node);
return vnode_1.default(undefined, undefined, undefined, text, node);
}
2018-05-07 18:20:15 +02:00
else if (api.isComment(node)) {
text = api.getTextContent(node);
return vnode_1.default('!', {}, [], text, node);
}
2018-05-07 18:20:15 +02:00
else {
return vnode_1.default('', {}, [], undefined, undefined);
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
}
exports.toVNode = toVNode;
exports.default = toVNode;
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
},{"./htmldomapi":1,"./vnode":3}],3:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function vnode(sel, data, children, text, elm) {
var key = data === undefined ? undefined : data.key;
return { sel: sel, data: data, children: children,
text: text, elm: elm, key: key };
}
2018-05-07 18:20:15 +02:00
exports.vnode = vnode;
exports.default = vnode;
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
},{}]},{},[2])(2)
});
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy8ucmVnaXN0cnkubnBtanMub3JnL2Jyb3dzZXItcGFjay82LjAuMi9ub2RlX21vZHVsZXMvYnJvd3Nlci1wYWNrL19wcmVsdWRlLmpzIiwiaHRtbGRvbWFwaS5qcyIsInRvdm5vZGUuanMiLCJ2bm9kZS5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNqRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMzQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzBdLmNhbGwobC5leHBvcnRzLGZ1bmN0aW9uKGUpe3ZhciBuPXRbb11bMV1bZV07cmV0dXJuIHMobj9uOmUpfSxsLGwuZXhwb3J0cyxlLHQsbixyKX1yZXR1cm4gbltvXS5leHBvcnRzfXZhciBpPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7Zm9yKHZhciBvPTA7bzxyLmxlbmd0aDtvKyspcyhyW29dKTtyZXR1cm4gc30pIiwiXCJ1c2Ugc3RyaWN0XCI7XG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgXCJfX2VzTW9kdWxlXCIsIHsgdmFsdWU6IHRydWUgfSk7XG5mdW5jdGlvbiBjcmVhdGVFbGVtZW50KHRhZ05hbWUpIHtcbiAgICByZXR1cm4gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCh0YWdOYW1lKTtcbn1cbmZ1bmN0aW9uIGNyZWF0ZUVsZW1lbnROUyhuYW1lc3BhY2VVUkksIHF1YWxpZmllZE5hbWUpIHtcbiAgICByZXR1cm4gZG9jdW1lbnQuY3JlYXRlRWxlbWVudE5TKG5hbWVzcGFjZVVSSSwgcXVhbGlmaWVkTmFtZSk7XG59XG5mdW5jdGlvbiBjcmVhdGVUZXh0Tm9kZSh0ZXh0KSB7XG4gICAgcmV0dXJuIGRvY3VtZW50LmNyZWF0ZVRleHROb2RlKHRleHQpO1xufVxuZnVuY3Rpb24gY3JlYXRlQ29tbWVudCh0ZXh0KSB7XG4gICAgcmV0dXJuIGRvY3VtZW50LmNyZWF0ZUNvbW1lbnQodGV4dCk7XG59XG5mdW5jdGlvbiBpbnNlcnRCZWZvcmUocGFyZW50Tm9kZSwgbmV3Tm9kZSwgcmVmZXJlbmNlTm9kZSkge1xuICAgIHBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKG5ld05vZGUsIHJlZmVyZW5jZU5vZGUpO1xufVxuZnVuY3Rpb24gcmVtb3ZlQ2hpbGQobm9kZSwgY2hpbGQpIHtcbiAgICBub2RlLnJlbW92ZUNoaWxkKGNoaWxkKTtcbn1cbmZ1bmN0aW9uIGFwcGVuZENoaWxkKG5vZGUsIGNoaWxkKSB7XG4gICAgbm9kZS5hcHBlbmRDaGlsZChjaGlsZCk7XG59XG5mdW5jdGlvbiBwYXJlbnROb2RlKG5vZGUpIHtcbiAgICByZXR1cm4gbm9kZS5wYXJlbnROb2RlO1xufVxuZnVuY3Rpb24gbmV4dFNpYmxpbmcobm9kZSkge1xuICAgIHJldHVybiBub2RlLm5leHRTaWJsaW5nO1xufVxuZnVuY3Rpb24gdGFnTmFtZShlbG0pIHtcbiAgICByZXR1cm4gZWxtLnRhZ05hbWU7XG59XG5mdW5jdGlvbiBzZXRUZXh0Q29udGVudChub2RlLCB0ZXh0KSB7XG4gICAgbm9kZS50ZXh0Q29udGVudCA9IHRleHQ7XG59XG5mdW5jdGlvbiBnZXRUZXh0Q29udGVudChub2RlKSB7XG4gICAgcmV0dXJuIG5vZGUudGV4dENvbnRlbnQ7XG59XG5mdW5jdGlvbiBpc0VsZW1lbnQobm9kZSkge1xuICAgIHJldHVybiBub2RlLm5vZGVUeXBlID09PSAxO1xufVxuZnVuY3Rpb24gaXNUZXh0KG5vZGUpIHtcbiAgICByZXR1cm4gbm9kZS5ub2RlVHlwZSA9PT0gMztcbn1cbmZ1bmN0aW9uIGlzQ29tbWVudChub2RlKSB7XG4gICAgcmV0dXJuIG5vZGUubm9kZVR5cGUgPT09IDg7XG59XG5leHBvcnRzLmh0bWxEb21BcGkgPSB7XG4gICAgY3JlYXRlRWxlbWVudDogY3JlYXRlRWxlbWVudCxcbiAgICBjcmVhdGVFbGVtZW50TlM6IGNyZWF0ZUVsZW1lbnROUyxcbiAgICBjcmVhdGVUZXh0Tm9kZTogY3JlYXRlVGV4dE5vZGUsXG4gICAgY3JlYXRlQ29tbWVudDogY3JlYXRlQ29tbWVudCxcbiAgICBpbnNlcnRCZWZvcmU6IGluc2VydEJlZm9yZSxcbiAgICByZW1vdmVDaGlsZDogcmVtb3ZlQ2hpbGQsXG4gICAgYXBwZW5kQ2hpbGQ6IGFwcGVuZENoaWxkLFxuICAgIHBhcmVudE5vZGU6IHBhcmVudE5vZGUsXG4gICAgbmV4dFNpYmxpbmc6IG5leHRTaWJsaW5nLFxuICAgIHRhZ05hbWU6IHRhZ05hbWUsXG4gICAgc2V0VGV4dENvbnRlbnQ6IHNldFRleHRDb250ZW50LFxuICAgIGdldFRleHRDb250ZW50OiBnZXRUZXh0Q29udGVudCxcbiAgICBpc0VsZW1lbnQ6IGlzRWxlbWVudCxcbiAgICBpc1RleHQ6IGlzVGV4dCxcbiAgICBpc0NvbW1lbnQ6IGlzQ29tbWVudCxcbn07XG5leHBvcnRzLmR
;
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
/*!
* Backbone.VDOMView
*
2018-05-07 18:20:15 +02:00
* MIT Licensed. Copyright (c) 2017, JC Brand <jc@opkode.com>
*/
2018-05-07 18:20:15 +02:00
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define('backbone.vdomview',["snabbdom", "snabbdom-attributes", "snabbdom-class", "snabbdom-dataset", "snabbdom-props", "snabbdom-style", "tovnode", "underscore", "backbone"], factory);
} else if ((typeof module === "undefined" ? "undefined" : _typeof(module)) === 'object' && module.exports) {
// CommonJS-like environments
module.exports = factory(require('snabbdom'), require('snabbdom-attributes'), require('snabbdom-class'), require('snabbdom-dataset'), require('snabbdom-props'), require('snabbdom-style'), require('tovnode'), require('underscore'), require('backbone'));
}
})(this, function (snabbdom, snabbdom_attributes, snabbdom_class, snabbdom_dataset, snabbdom_props, snabbdom_style, tovnode, _, Backbone) {
"use strict";
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
var domParser = new DOMParser();
var patch = snabbdom.init([snabbdom_attributes.default, snabbdom_class.default, snabbdom_dataset.default, snabbdom_props.default, snabbdom_style.default]);
var View = _.isUndefined(Backbone.NativeView) ? Backbone.View : Backbone.NativeView;
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
function parseHTMLToDOM(html_str) {
/* Parses a string with HTML and returns a DOM element.
*
* Forked from vdom_parser:
* https://github.com/bitinn/vdom-parser
*/
if (typeof html_str !== 'string') {
throw new Error('Invalid parameter type in parseHTMLToDOM');
}
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
if (!('DOMParser' in window)) {
throw new Error('DOMParser is not available, ' + 'so parsing string to DOM node is not possible.');
}
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
if (!html_str) {
return document.createTextNode('');
}
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
domParser = domParser || new DOMParser();
var doc = domParser.parseFromString(html_str, 'text/html'); // most tags default to body
2018-05-07 18:20:15 +02:00
if (doc.body.firstChild) {
return doc.getElementsByTagName('body')[0].firstChild; // some tags, like script and style, default to head
} else if (doc.head.firstChild && (doc.head.firstChild.tagName !== 'TITLE' || doc.title)) {
return doc.head.firstChild; // special case for html comment, cdata, doctype
} else if (doc.firstChild && doc.firstChild.tagName !== 'HTML') {
return doc.firstChild; // other element, such as whitespace, or html/body/head tag, fallback to empty text node
} else {
return document.createTextNode('');
}
}
2018-05-07 18:20:15 +02:00
Backbone.VDOMView = View.extend({
updateEventListeners: function updateEventListeners(old_vnode, new_vnode) {
this.setElement(new_vnode.elm);
},
render: function render() {
if (_.isFunction(this.beforeRender)) {
this.beforeRender();
}
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
var new_vnode = tovnode.toVNode(parseHTMLToDOM(this.toHTML()));
new_vnode.data.hook = _.extend({
create: this.updateEventListeners.bind(this),
update: this.updateEventListeners.bind(this)
});
var el = this.vnode ? this.vnode.elm : this.el;
2018-05-07 18:20:15 +02:00
if (el.outerHTML !== new_vnode.elm.outerHTML) {
this.vnode = patch(this.vnode || this.el, new_vnode);
}
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
if (_.isFunction(this.afterRender)) {
this.afterRender();
}
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
return this;
2018-03-25 21:21:43 +02:00
}
});
2018-05-07 18:20:15 +02:00
return Backbone.VDOMView;
});
//# sourceMappingURL=backbone.vdomview.js.map;
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// This is the utilities module.
//
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
/*global define, escape, Jed */
(function (root, factory) {
define('muc-utils',["converse-core", "utils"], factory);
})(this, function (converse, u) {
"use strict";
2018-05-07 18:20:15 +02:00
var _converse$env = converse.env,
Strophe = _converse$env.Strophe,
sizzle = _converse$env.sizzle,
_ = _converse$env._;
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
u.computeAffiliationsDelta = function computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list) {
/* Given two lists of objects with 'jid', 'affiliation' and
* 'reason' properties, return a new list containing
* those objects that are new, changed or removed
* (depending on the 'remove_absentees' boolean).
*
* The affiliations for new and changed members stay the
* same, for removed members, the affiliation is set to 'none'.
*
* The 'reason' property is not taken into account when
* comparing whether affiliations have been changed.
*
* Parameters:
* (Boolean) exclude_existing: Indicates whether JIDs from
* the new list which are also in the old list
* (regardless of affiliation) should be excluded
* from the delta. One reason to do this
* would be when you want to add a JID only if it
* doesn't have *any* existing affiliation at all.
* (Boolean) remove_absentees: Indicates whether JIDs
* from the old list which are not in the new list
* should be considered removed and therefore be
* included in the delta with affiliation set
* to 'none'.
* (Array) new_list: Array containing the new affiliations
* (Array) old_list: Array containing the old affiliations
*/
var new_jids = _.map(new_list, 'jid');
2018-05-07 18:20:15 +02:00
var old_jids = _.map(old_list, 'jid'); // Get the new affiliations
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
var delta = _.map(_.difference(new_jids, old_jids), function (jid) {
return new_list[_.indexOf(new_jids, jid)];
});
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
if (!exclude_existing) {
// Get the changed affiliations
delta = delta.concat(_.filter(new_list, function (item) {
var idx = _.indexOf(old_jids, item.jid);
2018-05-07 18:20:15 +02:00
if (idx >= 0) {
return item.affiliation !== old_list[idx].affiliation;
}
2018-05-07 18:20:15 +02:00
return false;
}));
}
2018-05-07 18:20:15 +02:00
if (remove_absentees) {
// Get the removed affiliations
delta = delta.concat(_.map(_.difference(old_jids, new_jids), function (jid) {
return {
'jid': jid,
'affiliation': 'none'
};
}));
}
2018-05-07 18:20:15 +02:00
return delta;
};
2018-05-07 18:20:15 +02:00
u.parseMemberListIQ = function parseMemberListIQ(iq) {
2018-05-09 14:37:27 +02:00
/* Given an IQ stanza with a member list, create an array of member objects.
*/
2018-05-07 18:20:15 +02:00
return _.map(sizzle("query[xmlns=\"".concat(Strophe.NS.MUC_ADMIN, "\"] item"), iq), function (item) {
2018-05-09 14:37:27 +02:00
var data = {
2018-05-07 18:20:15 +02:00
'affiliation': item.getAttribute('affiliation')
};
2018-05-09 14:37:27 +02:00
var jid = item.getAttribute('jid');
if (u.isValidJID(jid)) {
data['jid'] = jid;
} else {
// XXX: Prosody sends nick for the jid attribute value
// Perhaps for anonymous room?
data['nick'] = jid;
}
var nick = item.getAttribute('nick');
if (nick) {
data['nick'] = nick;
}
var role = item.getAttribute('role');
if (role) {
data['role'] = nick;
}
return data;
2018-05-07 18:20:15 +02:00
});
};
2018-05-07 18:20:15 +02:00
u.marshallAffiliationIQs = function marshallAffiliationIQs() {
/* Marshall a list of IQ stanzas into a map of JIDs and
* affiliations.
*
* Parameters:
* Any amount of XMLElement objects, representing the IQ
* stanzas.
*/
return _.flatMap(arguments[0], u.parseMemberListIQ);
};
});
//# sourceMappingURL=muc.js.map;
// Converse.js
// http://conversejs.org
//
2018-05-15 10:32:13 +02:00
// Copyright (c) 2013-2018, the Converse.js developers
2018-05-07 18:20:15 +02:00
// Licensed under the Mozilla Public License (MPLv2)
(function (root, factory) {
define('converse-muc',["form-utils", "converse-core", "emojione", "converse-disco", "backbone.overview", "backbone.orderedlistview", "backbone.vdomview", "muc-utils"], factory);
})(this, function (u, converse, emojione) {
"use strict";
2018-05-07 18:20:15 +02:00
var MUC_ROLE_WEIGHTS = {
'moderator': 1,
'participant': 2,
'visitor': 3,
2018-05-09 14:37:27 +02:00
'none': 2
2018-05-07 18:20:15 +02:00
};
var _converse$env = converse.env,
Strophe = _converse$env.Strophe,
Backbone = _converse$env.Backbone,
Promise = _converse$env.Promise,
$iq = _converse$env.$iq,
$build = _converse$env.$build,
$msg = _converse$env.$msg,
$pres = _converse$env.$pres,
b64_sha1 = _converse$env.b64_sha1,
sizzle = _converse$env.sizzle,
_ = _converse$env._,
moment = _converse$env.moment; // Add Strophe Namespaces
2018-05-07 18:20:15 +02:00
Strophe.addNamespace('MUC_ADMIN', Strophe.NS.MUC + "#admin");
Strophe.addNamespace('MUC_OWNER', Strophe.NS.MUC + "#owner");
Strophe.addNamespace('MUC_REGISTER', "jabber:iq:register");
Strophe.addNamespace('MUC_ROOMCONF', Strophe.NS.MUC + "#roomconfig");
Strophe.addNamespace('MUC_USER', Strophe.NS.MUC + "#user");
converse.MUC_NICK_CHANGED_CODE = "303";
converse.CHATROOMS_TYPE = 'chatroom';
converse.ROOM_FEATURES = ['passwordprotected', 'unsecured', 'hidden', 'publicroom', 'membersonly', 'open', 'persistent', 'temporary', 'nonanonymous', 'semianonymous', 'moderated', 'unmoderated', 'mam_enabled'];
converse.ROOMSTATUS = {
CONNECTED: 0,
CONNECTING: 1,
NICKNAME_REQUIRED: 2,
PASSWORD_REQUIRED: 3,
DISCONNECTED: 4,
ENTERED: 5
};
converse.plugins.add('converse-muc', {
/* Optional dependencies are other plugins which might be
* overridden or relied upon, and therefore need to be loaded before
* this plugin. They are called "optional" because they might not be
* available, in which case any overrides applicable to them will be
* ignored.
*
* It's possible however to make optional dependencies non-optional.
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found.
*
* NB: These plugins need to have already been loaded via require.js.
*/
dependencies: ["converse-chatboxes", "converse-disco", "converse-controlbox"],
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
_tearDown: function _tearDown() {
var rooms = this.chatboxes.where({
'type': converse.CHATROOMS_TYPE
});
2018-05-07 18:20:15 +02:00
_.each(rooms, function (room) {
u.safeSave(room, {
'connection_status': converse.ROOMSTATUS.DISCONNECTED
});
});
2018-05-07 18:20:15 +02:00
this.__super__._tearDown.call(this, arguments);
},
ChatBoxes: {
model: function model(attrs, options) {
var _converse = this.__super__._converse;
2018-05-07 18:20:15 +02:00
if (attrs.type == converse.CHATROOMS_TYPE) {
return new _converse.ChatRoom(attrs, options);
} else {
return this.__super__.model.apply(this, arguments);
}
}
}
2018-05-07 18:20:15 +02:00
},
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
var _converse = this._converse,
__ = _converse.__; // Configuration values for this plugin
// ====================================
// Refer to docs/source/configuration.rst for explanations of these
// configuration settings.
_converse.api.settings.update({
allow_muc: true,
allow_muc_invitations: true,
auto_join_on_invite: false,
auto_join_rooms: [],
muc_domain: undefined,
muc_history_max_stanzas: undefined,
muc_instant_rooms: true,
muc_nickname_from_jid: false
});
_converse.api.promises.add(['roomsAutoJoined']);
function openRoom(jid) {
if (!u.isValidMUCJID(jid)) {
return _converse.log("Invalid JID \"".concat(jid, "\" provided in URL fragment"), Strophe.LogLevel.WARN);
}
2018-05-07 18:20:15 +02:00
var promises = [_converse.api.waitUntil('roomsAutoJoined')];
if (_converse.allow_bookmarks) {
promises.push(_converse.api.waitUntil('bookmarksInitialized'));
}
2018-05-07 18:20:15 +02:00
Promise.all(promises).then(function () {
_converse.api.rooms.open(jid);
});
}
2018-05-07 18:20:15 +02:00
_converse.router.route('converse/room?jid=:jid', openRoom);
2018-05-07 18:20:15 +02:00
_converse.openChatRoom = function (jid, settings, bring_to_foreground) {
/* Opens a chat room, making sure that certain attributes
* are correct, for example that the "type" is set to
* "chatroom".
*/
settings.type = converse.CHATROOMS_TYPE;
settings.id = jid;
settings.box_id = b64_sha1(jid);
2018-05-07 18:20:15 +02:00
var chatbox = _converse.chatboxes.getChatBox(jid, settings, true);
2018-05-07 18:20:15 +02:00
chatbox.trigger('show', true);
return chatbox;
};
2018-05-07 18:20:15 +02:00
_converse.ChatRoom = _converse.ChatBox.extend({
defaults: function defaults() {
return _.assign(_.clone(_converse.ChatBox.prototype.defaults), _.zipObject(converse.ROOM_FEATURES, _.map(converse.ROOM_FEATURES, _.stubFalse)), {
// For group chats, we distinguish between generally unread
// messages and those ones that specifically mention the
// user.
//
// To keep things simple, we reuse `num_unread` from
// _converse.ChatBox to indicate unread messages which
// mention the user and `num_unread_general` to indicate
// generally unread messages (which *includes* mentions!).
'num_unread_general': 0,
'affiliation': null,
'connection_status': converse.ROOMSTATUS.DISCONNECTED,
'name': '',
'nick': _converse.xmppstatus.get('nickname'),
'description': '',
'features_fetched': false,
'roomconfig': {},
'type': converse.CHATROOMS_TYPE,
'message_type': 'groupchat'
});
},
initialize: function initialize() {
this.constructor.__super__.initialize.apply(this, arguments);
2018-05-07 18:20:15 +02:00
this.occupants = new _converse.ChatRoomOccupants();
this.occupants.browserStorage = new Backbone.BrowserStorage.session(b64_sha1("converse.occupants-".concat(_converse.bare_jid).concat(this.get('jid'))));
2018-05-09 14:37:27 +02:00
this.occupants.chatroom = this;
2018-05-07 18:20:15 +02:00
this.registerHandlers();
this.on('change:chat_state', this.sendChatState, this);
},
registerHandlers: function registerHandlers() {
var _this = this;
2018-05-07 18:20:15 +02:00
/* Register presence and message handlers for this chat
* room
*/
var room_jid = this.get('jid');
this.removeHandlers();
this.presence_handler = _converse.connection.addHandler(function (stanza) {
_.each(_.values(_this.handlers.presence), function (callback) {
return callback(stanza);
});
2018-05-07 18:20:15 +02:00
_this.onPresence(stanza);
2018-05-07 18:20:15 +02:00
return true;
}, Strophe.NS.MUC, 'presence', null, null, room_jid, {
'ignoreNamespaceFragment': true,
'matchBareFromJid': true
});
this.message_handler = _converse.connection.addHandler(function (stanza) {
_.each(_.values(_this.handlers.message), function (callback) {
return callback(stanza);
});
2018-05-07 18:20:15 +02:00
_this.onMessage(stanza);
2018-05-07 18:20:15 +02:00
return true;
}, null, 'message', 'groupchat', null, room_jid, {
'matchBareFromJid': true
});
},
removeHandlers: function removeHandlers() {
/* Remove the presence and message handlers that were
* registered for this chat room.
*/
if (this.message_handler) {
_converse.connection.deleteHandler(this.message_handler);
2018-05-07 18:20:15 +02:00
delete this.message_handler;
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (this.presence_handler) {
_converse.connection.deleteHandler(this.presence_handler);
2018-05-07 18:20:15 +02:00
delete this.presence_handler;
}
2018-05-07 18:20:15 +02:00
return this;
},
addHandler: function addHandler(type, name, callback) {
/* Allows 'presence' and 'message' handlers to be
* registered. These will be executed once presence or
* message stanzas are received, and *before* this model's
* own handlers are executed.
*/
if (_.isNil(this.handlers)) {
this.handlers = {};
}
2018-05-07 18:20:15 +02:00
if (_.isNil(this.handlers[type])) {
this.handlers[type] = {};
}
2018-05-07 18:20:15 +02:00
this.handlers[type][name] = callback;
},
join: function join(nick, password) {
/* Join the chat room.
*
* Parameters:
* (String) nick: The user's nickname
* (String) password: Optional password, if required by
* the room.
*/
nick = nick ? nick : this.get('nick');
2018-05-07 18:20:15 +02:00
if (!nick) {
throw new TypeError('join: You need to provide a valid nickname');
}
2018-05-07 18:20:15 +02:00
if (this.get('connection_status') === converse.ROOMSTATUS.ENTERED) {
// We have restored a chat room from session storage,
// so we don't send out a presence stanza again.
return this;
}
2018-02-14 16:53:07 +01:00
2018-05-07 18:20:15 +02:00
var stanza = $pres({
'from': _converse.connection.jid,
'to': this.getRoomJIDAndNick(nick)
}).c("x", {
'xmlns': Strophe.NS.MUC
}).c("history", {
'maxstanzas': _converse.muc_history_max_stanzas
}).up();
2018-02-14 16:53:07 +01:00
2018-05-07 18:20:15 +02:00
if (password) {
stanza.cnode(Strophe.xmlElement("password", [], password));
}
2018-05-07 18:20:15 +02:00
this.save('connection_status', converse.ROOMSTATUS.CONNECTING);
2018-05-07 18:20:15 +02:00
_converse.connection.send(stanza);
2018-05-07 18:20:15 +02:00
return this;
},
leave: function leave(exit_msg) {
/* Leave the chat room.
*
* Parameters:
* (String) exit_msg: Optional message to indicate your
* reason for leaving.
*/
this.occupants.browserStorage._clear();
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
this.occupants.reset();
2018-05-07 18:20:15 +02:00
if (_converse.connection.connected) {
this.sendUnavailablePresence(exit_msg);
}
2018-05-07 18:20:15 +02:00
u.safeSave(this, {
'connection_status': converse.ROOMSTATUS.DISCONNECTED
});
this.removeHandlers();
},
sendUnavailablePresence: function sendUnavailablePresence(exit_msg) {
var presence = $pres({
type: "unavailable",
from: _converse.connection.jid,
to: this.getRoomJIDAndNick()
});
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (exit_msg !== null) {
presence.c("status", exit_msg);
}
2018-05-07 18:20:15 +02:00
_converse.connection.sendPresence(presence);
},
getOutgoingMessageAttributes: function getOutgoingMessageAttributes(text, spoiler_hint) {
var is_spoiler = this.get('composing_spoiler');
return {
'nick': this.get('nick'),
'from': "".concat(this.get('jid'), "/").concat(this.get('nick')),
'fullname': this.get('nick'),
'is_spoiler': is_spoiler,
'message': text ? u.httpToGeoUri(emojione.shortnameToUnicode(text), _converse) : undefined,
'sender': 'me',
'spoiler_hint': is_spoiler ? spoiler_hint : undefined,
'type': 'groupchat'
};
},
getRoomFeatures: function getRoomFeatures() {
var _this2 = this;
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
/* Fetch the room disco info, parse it and then save it.
*/
return new Promise(function (resolve, reject) {
_converse.api.disco.info(_this2.get('jid'), null, _.flow(_this2.parseRoomFeatures.bind(_this2), resolve), function () {
reject(new Error("Could not parse the room features"));
}, 5000);
});
},
getRoomJIDAndNick: function getRoomJIDAndNick(nick) {
/* Utility method to construct the JID for the current user
* as occupant of the room.
*
* This is the room JID, with the user's nick added at the
* end.
*
* For example: room@conference.example.org/nickname
*/
if (nick) {
this.save({
'nick': nick
});
} else {
nick = this.get('nick');
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
var room = this.get('jid');
var jid = Strophe.getBareJidFromJid(room);
return jid + (nick !== null ? "/".concat(nick) : "");
},
sendChatState: function sendChatState() {
/* Sends a message with the status of the user in this chat session
* as taken from the 'chat_state' attribute of the chat box.
* See XEP-0085 Chat State Notifications.
*/
if (this.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
return;
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
var chat_state = this.get('chat_state');
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (chat_state === _converse.GONE) {
// <gone/> is not applicable within MUC context
return;
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
_converse.connection.send($msg({
'to': this.get('jid'),
'type': 'groupchat'
}).c(chat_state, {
'xmlns': Strophe.NS.CHATSTATES
}).up().c('no-store', {
'xmlns': Strophe.NS.HINTS
}).up().c('no-permanent-store', {
'xmlns': Strophe.NS.HINTS
}));
},
directInvite: function directInvite(recipient, reason) {
/* Send a direct invitation as per XEP-0249
*
* Parameters:
* (String) recipient - JID of the person being invited
* (String) reason - Optional reason for the invitation
*/
if (this.get('membersonly')) {
// When inviting to a members-only room, we first add
// the person to the member list by giving them an
// affiliation of 'member' (if they're not affiliated
// already), otherwise they won't be able to join.
var map = {};
map[recipient] = 'member';
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
var deltaFunc = _.partial(u.computeAffiliationsDelta, true, false);
2018-05-07 18:20:15 +02:00
this.updateMemberLists([{
'jid': recipient,
'affiliation': 'member',
'reason': reason
}], ['member', 'owner', 'admin'], deltaFunc);
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
var attrs = {
'xmlns': 'jabber:x:conference',
'jid': this.get('jid')
};
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (reason !== null) {
attrs.reason = reason;
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (this.get('password')) {
attrs.password = this.get('password');
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
var invitation = $msg({
from: _converse.connection.jid,
to: recipient,
id: _converse.connection.getUniqueId()
}).c('x', attrs);
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
_converse.connection.send(invitation);
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
_converse.emit('roomInviteSent', {
'room': this,
'recipient': recipient,
'reason': reason
});
},
parseRoomFeatures: function parseRoomFeatures(iq) {
/* Parses an IQ stanza containing the room's features.
*
* See http://xmpp.org/extensions/xep-0045.html#disco-roominfo
*
* <identity
* category='conference'
* name='A Dark Cave'
* type='text'/>
* <feature var='http://jabber.org/protocol/muc'/>
* <feature var='muc_passwordprotected'/>
* <feature var='muc_hidden'/>
* <feature var='muc_temporary'/>
* <feature var='muc_open'/>
* <feature var='muc_unmoderated'/>
* <feature var='muc_nonanonymous'/>
* <feature var='urn:xmpp:mam:0'/>
*/
var features = {
'features_fetched': true,
'name': iq.querySelector('identity').getAttribute('name')
};
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
_.each(iq.querySelectorAll('feature'), function (field) {
var fieldname = field.getAttribute('var');
2018-05-07 18:20:15 +02:00
if (!fieldname.startsWith('muc_')) {
if (fieldname === Strophe.NS.MAM) {
features.mam_enabled = true;
}
2018-05-07 18:20:15 +02:00
return;
}
2018-05-07 18:20:15 +02:00
features[fieldname.replace('muc_', '')] = true;
});
2018-05-07 18:20:15 +02:00
var desc_field = iq.querySelector('field[var="muc#roominfo_description"] value');
2018-05-07 18:20:15 +02:00
if (!_.isNull(desc_field)) {
features.description = desc_field.textContent;
}
2018-05-07 18:20:15 +02:00
this.save(features);
},
2018-05-07 18:20:15 +02:00
requestMemberList: function requestMemberList(affiliation) {
var _this3 = this;
2018-01-29 16:33:30 +01:00
2018-05-07 18:20:15 +02:00
/* Send an IQ stanza to the server, asking it for the
* member-list of this room.
*
* See: http://xmpp.org/extensions/xep-0045.html#modifymember
*
* Parameters:
* (String) affiliation: The specific member list to
* fetch. 'admin', 'owner' or 'member'.
*
* Returns:
* A promise which resolves once the list has been
* retrieved.
*/
return new Promise(function (resolve, reject) {
affiliation = affiliation || 'member';
var iq = $iq({
to: _this3.get('jid'),
type: "get"
}).c("query", {
xmlns: Strophe.NS.MUC_ADMIN
}).c("item", {
'affiliation': affiliation
});
2018-05-07 18:20:15 +02:00
_converse.connection.sendIQ(iq, resolve, reject);
});
2018-03-25 21:21:43 +02:00
},
2018-05-07 18:20:15 +02:00
setAffiliation: function setAffiliation(affiliation, members) {
/* Send IQ stanzas to the server to set an affiliation for
* the provided JIDs.
*
2018-05-07 18:20:15 +02:00
* See: http://xmpp.org/extensions/xep-0045.html#modifymember
*
2018-05-07 18:20:15 +02:00
* XXX: Prosody doesn't accept multiple JIDs' affiliations
* being set in one IQ stanza, so as a workaround we send
* a separate stanza for each JID.
* Related ticket: https://prosody.im/issues/issue/795
*
* Parameters:
* (String) affiliation: The affiliation
* (Object) members: A map of jids, affiliations and
* optionally reasons. Only those entries with the
* same affiliation as being currently set will be
* considered.
*
* Returns:
* A promise which resolves and fails depending on the
* XMPP server response.
2018-03-25 21:21:43 +02:00
*/
2018-05-07 18:20:15 +02:00
members = _.filter(members, function (member) {
return (// We only want those members who have the right
// affiliation (or none, which implies the provided one).
_.isUndefined(member.affiliation) || member.affiliation === affiliation
);
});
var promises = _.map(members, _.bind(this.sendAffiliationIQ, this, affiliation));
return Promise.all(promises);
},
2018-05-07 18:20:15 +02:00
saveConfiguration: function saveConfiguration(form) {
var _this4 = this;
/* Submit the room configuration form by sending an IQ
* stanza to the server.
*
* Returns a promise which resolves once the XMPP server
* has return a response IQ.
2018-03-25 21:21:43 +02:00
*
* Parameters:
2018-05-07 18:20:15 +02:00
* (HTMLElement) form: The configuration form DOM element.
* If no form is provided, the default configuration
* values will be used.
2018-03-25 21:21:43 +02:00
*/
2018-05-07 18:20:15 +02:00
return new Promise(function (resolve, reject) {
var inputs = form ? sizzle(':input:not([type=button]):not([type=submit])', form) : [],
configArray = _.map(inputs, u.webForm2xForm);
2018-05-07 18:20:15 +02:00
_this4.sendConfiguration(configArray, resolve, reject);
});
},
autoConfigureChatRoom: function autoConfigureChatRoom() {
var _this5 = this;
2018-05-07 18:20:15 +02:00
/* Automatically configure room based on this model's
* 'roomconfig' data.
*
* Returns a promise which resolves once a response IQ has
* been received.
*/
return new Promise(function (resolve, reject) {
_this5.fetchRoomConfiguration().then(function (stanza) {
var configArray = [],
fields = stanza.querySelectorAll('field'),
config = _this5.get('roomconfig');
2018-05-07 18:20:15 +02:00
var count = fields.length;
2018-05-07 18:20:15 +02:00
_.each(fields, function (field) {
var fieldname = field.getAttribute('var').replace('muc#roomconfig_', ''),
type = field.getAttribute('type');
var value;
2018-05-07 18:20:15 +02:00
if (fieldname in config) {
switch (type) {
case 'boolean':
value = config[fieldname] ? 1 : 0;
break;
2018-05-07 18:20:15 +02:00
case 'list-multi':
// TODO: we don't yet handle "list-multi" types
value = field.innerHTML;
break;
2018-05-07 18:20:15 +02:00
default:
value = config[fieldname];
}
2018-05-07 18:20:15 +02:00
field.innerHTML = $build('value').t(value);
}
2018-05-07 18:20:15 +02:00
configArray.push(field);
if (! --count) {
_this5.sendConfiguration(configArray, resolve, reject);
}
});
});
});
2018-03-25 21:21:43 +02:00
},
2018-05-07 18:20:15 +02:00
fetchRoomConfiguration: function fetchRoomConfiguration() {
var _this6 = this;
/* Send an IQ stanza to fetch the room configuration data.
* Returns a promise which resolves once the response IQ
* has been received.
*/
2018-05-07 18:20:15 +02:00
return new Promise(function (resolve, reject) {
_converse.connection.sendIQ($iq({
'to': _this6.get('jid'),
'type': "get"
}).c("query", {
xmlns: Strophe.NS.MUC_OWNER
}), resolve, reject);
});
},
2018-05-07 18:20:15 +02:00
sendConfiguration: function sendConfiguration(config, callback, errback) {
/* Send an IQ stanza with the room configuration.
*
* Parameters:
* (Array) config: The room configuration
* (Function) callback: Callback upon succesful IQ response
* The first parameter passed in is IQ containing the
* room configuration.
* The second is the response IQ from the server.
* (Function) errback: Callback upon error IQ response
* The first parameter passed in is IQ containing the
* room configuration.
* The second is the response IQ from the server.
*/
2018-05-07 18:20:15 +02:00
var iq = $iq({
to: this.get('jid'),
type: "set"
}).c("query", {
xmlns: Strophe.NS.MUC_OWNER
}).c("x", {
xmlns: Strophe.NS.XFORM,
type: "submit"
});
_.each(config || [], function (node) {
iq.cnode(node).up();
});
callback = _.isUndefined(callback) ? _.noop : _.partial(callback, iq.nodeTree);
errback = _.isUndefined(errback) ? _.noop : _.partial(errback, iq.nodeTree);
return _converse.connection.sendIQ(iq, callback, errback);
},
2018-05-07 18:20:15 +02:00
saveAffiliationAndRole: function saveAffiliationAndRole(pres) {
/* Parse the presence stanza for the current user's
* affiliation.
*
* Parameters:
* (XMLElement) pres: A <presence> stanza.
*/
2018-05-07 18:20:15 +02:00
var item = sizzle("x[xmlns=\"".concat(Strophe.NS.MUC_USER, "\"] item"), pres).pop();
var is_self = pres.querySelector("status[code='110']");
2018-05-07 18:20:15 +02:00
if (is_self && !_.isNil(item)) {
var affiliation = item.getAttribute('affiliation');
var role = item.getAttribute('role');
2018-05-07 18:20:15 +02:00
if (affiliation) {
this.save({
'affiliation': affiliation
});
}
if (role) {
this.save({
'role': role
});
}
2018-03-25 21:21:43 +02:00
}
},
2018-05-07 18:20:15 +02:00
sendAffiliationIQ: function sendAffiliationIQ(affiliation, member) {
var _this7 = this;
2018-05-07 18:20:15 +02:00
/* Send an IQ stanza specifying an affiliation change.
*
* Paremeters:
* (String) affiliation: affiliation (could also be stored
* on the member object).
* (Object) member: Map containing the member's jid and
* optionally a reason and affiliation.
*/
return new Promise(function (resolve, reject) {
var iq = $iq({
to: _this7.get('jid'),
type: "set"
}).c("query", {
xmlns: Strophe.NS.MUC_ADMIN
}).c("item", {
'affiliation': member.affiliation || affiliation,
'jid': member.jid
});
2018-05-07 18:20:15 +02:00
if (!_.isUndefined(member.reason)) {
iq.c("reason", member.reason);
}
2018-05-07 18:20:15 +02:00
_converse.connection.sendIQ(iq, resolve, reject);
});
},
2018-05-07 18:20:15 +02:00
setAffiliations: function setAffiliations(members) {
/* Send IQ stanzas to the server to modify the
* affiliations in this room.
*
* See: http://xmpp.org/extensions/xep-0045.html#modifymember
*
* Parameters:
* (Object) members: A map of jids, affiliations and optionally reasons
* (Function) onSuccess: callback for a succesful response
* (Function) onError: callback for an error response
*/
var affiliations = _.uniq(_.map(members, 'affiliation'));
2018-05-11 00:19:49 +02:00
return Promise.all(_.map(affiliations, _.partial(this.setAffiliation.bind(this), _, members)));
2018-05-07 18:20:15 +02:00
},
getJidsWithAffiliations: function getJidsWithAffiliations(affiliations) {
var _this8 = this;
2018-05-07 18:20:15 +02:00
/* Returns a map of JIDs that have the affiliations
* as provided.
*/
if (_.isString(affiliations)) {
affiliations = [affiliations];
}
2018-05-07 18:20:15 +02:00
return new Promise(function (resolve, reject) {
var promises = _.map(affiliations, _.partial(_this8.requestMemberList.bind(_this8)));
2018-05-07 18:20:15 +02:00
Promise.all(promises).then(_.flow(u.marshallAffiliationIQs, resolve), _.flow(u.marshallAffiliationIQs, resolve));
});
},
updateMemberLists: function updateMemberLists(members, affiliations, deltaFunc) {
var _this9 = this;
2018-05-07 18:20:15 +02:00
/* Fetch the lists of users with the given affiliations.
* Then compute the delta between those users and
* the passed in members, and if it exists, send the delta
* to the XMPP server to update the member list.
*
* Parameters:
* (Object) members: Map of member jids and affiliations.
* (String|Array) affiliation: An array of affiliations or
* a string if only one affiliation.
* (Function) deltaFunc: The function to compute the delta
* between old and new member lists.
*
* Returns:
* A promise which is resolved once the list has been
* updated or once it's been established there's no need
* to update the list.
*/
this.getJidsWithAffiliations(affiliations).then(function (old_members) {
2018-05-11 00:19:49 +02:00
return _this9.setAffiliations(deltaFunc(members, old_members));
}).then(function () {
return _this9.occupants.fetchMembers();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
2018-03-25 21:21:43 +02:00
},
2018-05-07 18:20:15 +02:00
checkForReservedNick: function checkForReservedNick(callback, errback) {
/* Use service-discovery to ask the XMPP server whether
* this user has a reserved nickname for this room.
* If so, we'll use that, otherwise we render the nickname form.
*
* Parameters:
2018-05-07 18:20:15 +02:00
* (Function) callback: Callback upon succesful IQ response
* (Function) errback: Callback upon error IQ response
2018-03-25 21:21:43 +02:00
*/
2018-05-07 18:20:15 +02:00
_converse.connection.sendIQ($iq({
'to': this.get('jid'),
'from': _converse.connection.jid,
'type': "get"
}).c("query", {
'xmlns': Strophe.NS.DISCO_INFO,
'node': 'x-roomuser-item'
}), callback, errback);
2018-05-07 18:20:15 +02:00
return this;
},
updateOccupantsOnPresence: function updateOccupantsOnPresence(pres) {
/* Given a presence stanza, update the occupant model
* based on its contents.
*
* Parameters:
* (XMLElement) pres: The presence stanza
*/
var data = this.parsePresence(pres);
2018-05-07 18:20:15 +02:00
if (data.type === 'error') {
return true;
2018-03-25 21:21:43 +02:00
}
2018-05-09 14:37:27 +02:00
var occupant = this.occupants.findOccupant(data);
2018-05-07 18:20:15 +02:00
if (data.type === 'unavailable') {
if (occupant) {
// Even before destroying, we set the new data, so
// that we can for example show the
// disconnection message.
occupant.set(data);
}
2018-05-07 18:20:15 +02:00
if (!_.includes(data.states, converse.MUC_NICK_CHANGED_CODE)) {
// We only destroy the occupant if this is not a
// nickname change operation.
if (occupant) {
occupant.destroy();
}
2018-05-07 18:20:15 +02:00
return;
}
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
var jid = Strophe.getBareJidFromJid(data.jid);
2018-05-07 18:20:15 +02:00
var attributes = _.extend(data, {
'jid': jid ? jid : undefined,
'resource': data.jid ? Strophe.getResourceFromJid(data.jid) : undefined
});
2018-05-07 18:20:15 +02:00
if (occupant) {
occupant.save(attributes);
} else {
this.occupants.create(attributes);
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
},
parsePresence: function parsePresence(pres) {
var from = pres.getAttribute("from"),
data = {
'from': from,
'nick': Strophe.getResourceFromJid(from),
'type': pres.getAttribute("type"),
2018-05-09 14:37:27 +02:00
'states': [],
'show': 'online'
2018-05-07 18:20:15 +02:00
};
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
_.each(pres.childNodes, function (child) {
switch (child.nodeName) {
case "status":
data.status = child.textContent || null;
break;
2018-05-07 18:20:15 +02:00
case "show":
data.show = child.textContent || 'online';
break;
2018-05-07 18:20:15 +02:00
case "x":
if (child.getAttribute("xmlns") === Strophe.NS.MUC_USER) {
_.each(child.childNodes, function (item) {
switch (item.nodeName) {
case "item":
data.affiliation = item.getAttribute("affiliation");
data.role = item.getAttribute("role");
data.jid = item.getAttribute("jid");
data.nick = item.getAttribute("nick") || data.nick;
break;
2018-05-07 18:20:15 +02:00
case "status":
if (item.getAttribute("code")) {
data.states.push(item.getAttribute("code"));
}
2018-05-07 18:20:15 +02:00
}
});
} else if (child.getAttribute("xmlns") === Strophe.NS.VCARDUPDATE) {
data.image_hash = _.get(child.querySelector('photo'), 'textContent');
}
2017-07-22 22:21:05 +02:00
}
2018-05-07 18:20:15 +02:00
});
2018-05-07 18:20:15 +02:00
return data;
},
isDuplicate: function isDuplicate(message, original_stanza) {
var msgid = message.getAttribute('id'),
jid = message.getAttribute('from');
2018-05-07 18:20:15 +02:00
if (msgid) {
return this.messages.where({
'msgid': msgid,
'from': jid
}).length;
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
return false;
2018-03-25 21:21:43 +02:00
},
2018-05-07 18:20:15 +02:00
fetchFeaturesIfConfigurationChanged: function fetchFeaturesIfConfigurationChanged(stanza) {
var configuration_changed = stanza.querySelector("status[code='104']"),
logging_enabled = stanza.querySelector("status[code='170']"),
logging_disabled = stanza.querySelector("status[code='171']"),
room_no_longer_anon = stanza.querySelector("status[code='172']"),
room_now_semi_anon = stanza.querySelector("status[code='173']"),
room_now_fully_anon = stanza.querySelector("status[code='173']");
2017-07-22 22:21:05 +02:00
2018-05-07 18:20:15 +02:00
if (configuration_changed || logging_enabled || logging_disabled || room_no_longer_anon || room_now_semi_anon || room_now_fully_anon) {
this.getRoomFeatures();
2018-03-25 21:21:43 +02:00
}
},
2018-05-07 18:20:15 +02:00
onMessage: function onMessage(stanza) {
/* Handler for all MUC messages sent to this chat room.
*
* Parameters:
* (XMLElement) stanza: The message stanza.
*/
2018-05-07 18:20:15 +02:00
this.fetchFeaturesIfConfigurationChanged(stanza);
var original_stanza = stanza,
forwarded = stanza.querySelector('forwarded');
var delay;
2017-04-23 19:02:44 +02:00
2018-05-07 18:20:15 +02:00
if (!_.isNull(forwarded)) {
stanza = forwarded.querySelector('message');
delay = forwarded.querySelector('delay');
}
2017-04-23 19:02:44 +02:00
2018-05-07 18:20:15 +02:00
var jid = stanza.getAttribute('from'),
resource = Strophe.getResourceFromJid(jid),
sender = resource && Strophe.unescapeNode(resource) || '',
subject = _.propertyOf(stanza.querySelector('subject'))('textContent');
2018-05-07 18:20:15 +02:00
if (this.isDuplicate(stanza, original_stanza)) {
return;
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (subject) {
u.safeSave(this, {
'subject': {
'author': sender,
'text': subject
}
});
}
2018-05-07 18:20:15 +02:00
if (sender === '') {
return;
}
2018-05-07 18:20:15 +02:00
this.incrementUnreadMsgCounter(original_stanza);
this.createMessage(stanza, delay, original_stanza);
2016-03-07 18:54:07 +01:00
2018-05-07 18:20:15 +02:00
if (sender !== this.get('nick')) {
// We only emit an event if it's not our own message
_converse.emit('message', {
'stanza': original_stanza,
'chatbox': this
});
}
},
onPresence: function onPresence(pres) {
/* Handles all MUC presence stanzas.
*
* Parameters:
* (XMLElement) pres: The stanza
*/
if (pres.getAttribute('type') === 'error') {
this.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
return;
}
2016-03-07 18:54:07 +01:00
2018-05-07 18:20:15 +02:00
var is_self = pres.querySelector("status[code='110']");
2017-06-23 20:25:33 +02:00
2018-05-07 18:20:15 +02:00
if (is_self && pres.getAttribute('type') !== 'unavailable') {
this.onOwnPresence(pres);
}
2017-06-23 20:25:33 +02:00
2018-05-07 18:20:15 +02:00
this.updateOccupantsOnPresence(pres);
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (this.get('role') !== 'none' && this.get('connection_status') === converse.ROOMSTATUS.CONNECTING) {
this.save('connection_status', converse.ROOMSTATUS.CONNECTED);
}
},
onOwnPresence: function onOwnPresence(pres) {
/* Handles a received presence relating to the current
* user.
*
* For locked rooms (which are by definition "new"), the
* room will either be auto-configured or created instantly
* (with default config) or a configuration room will be
* rendered.
*
* If the room is not locked, then the room will be
* auto-configured only if applicable and if the current
* user is the room's owner.
*
* Parameters:
* (XMLElement) pres: The stanza
*/
this.saveAffiliationAndRole(pres);
var locked_room = pres.querySelector("status[code='201']");
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (locked_room) {
if (this.get('auto_configure')) {
this.autoConfigureChatRoom().then(this.getRoomFeatures.bind(this));
} else if (_converse.muc_instant_rooms) {
// Accept default configuration
this.saveConfiguration().then(this.getRoomFeatures.bind(this));
} else {
this.trigger('configurationNeeded');
return; // We haven't yet entered the room, so bail here.
}
2018-05-07 18:20:15 +02:00
} else if (!this.get('features_fetched')) {
// The features for this room weren't fetched.
// That must mean it's a new room without locking
// (in which case Prosody doesn't send a 201 status),
// otherwise the features would have been fetched in
// the "initialize" method already.
if (this.get('affiliation') === 'owner' && this.get('auto_configure')) {
this.autoConfigureChatRoom().then(this.getRoomFeatures.bind(this));
} else {
this.getRoomFeatures();
}
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
this.save('connection_status', converse.ROOMSTATUS.ENTERED);
},
isUserMentioned: function isUserMentioned(message) {
/* Returns a boolean to indicate whether the current user
* was mentioned in a message.
*
* Parameters:
* (String): The text message
*/
return new RegExp("\\b".concat(this.get('nick'), "\\b")).test(message);
},
incrementUnreadMsgCounter: function incrementUnreadMsgCounter(stanza) {
/* Given a newly received message, update the unread counter if
* necessary.
*
* Parameters:
* (XMLElement): The <messsage> stanza
*/
var body = stanza.querySelector('body');
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (_.isNull(body)) {
return; // The message has no text
}
2016-03-07 18:54:07 +01:00
2018-05-07 18:20:15 +02:00
if (u.isNewMessage(stanza) && this.newMessageWillBeHidden()) {
var settings = {
'num_unread_general': this.get('num_unread_general') + 1
};
2017-07-22 22:21:05 +02:00
2018-05-07 18:20:15 +02:00
if (this.isUserMentioned(body.textContent)) {
settings.num_unread = this.get('num_unread') + 1;
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
_converse.incrementMsgCounter();
}
2017-07-22 22:21:05 +02:00
2018-05-07 18:20:15 +02:00
this.save(settings);
}
},
clearUnreadMsgCounter: function clearUnreadMsgCounter() {
u.safeSave(this, {
'num_unread': 0,
'num_unread_general': 0
});
}
});
_converse.ChatRoomOccupant = Backbone.Model.extend({
2018-05-09 14:37:27 +02:00
defaults: {
'show': 'offline'
},
2018-05-07 18:20:15 +02:00
initialize: function initialize(attributes) {
this.set(_.extend({
'id': _converse.connection.getUniqueId()
}, attributes));
this.on('change:image_hash', this.onAvatarChanged, this);
},
onAvatarChanged: function onAvatarChanged() {
var vcard = _converse.vcards.findWhere({
'jid': this.get('from')
});
2018-01-17 19:45:33 +01:00
2018-05-07 18:20:15 +02:00
if (!vcard) {
return;
}
2018-01-17 19:45:33 +01:00
2018-05-07 18:20:15 +02:00
var hash = this.get('image_hash');
2016-03-07 18:54:07 +01:00
2018-05-07 18:20:15 +02:00
if (hash && vcard.get('image_hash') !== hash) {
_converse.api.vcard.update(vcard);
}
2018-05-11 00:19:49 +02:00
},
getDisplayName: function getDisplayName() {
return this.get('nick') || this.get('jid');
}
});
2018-05-07 18:20:15 +02:00
_converse.ChatRoomOccupants = Backbone.Collection.extend({
model: _converse.ChatRoomOccupant,
comparator: function comparator(occupant1, occupant2) {
var role1 = occupant1.get('role') || 'none';
var role2 = occupant2.get('role') || 'none';
2018-05-07 18:20:15 +02:00
if (MUC_ROLE_WEIGHTS[role1] === MUC_ROLE_WEIGHTS[role2]) {
2018-05-11 00:19:49 +02:00
var nick1 = occupant1.getDisplayName().toLowerCase();
var nick2 = occupant2.getDisplayName().toLowerCase();
2018-05-07 18:20:15 +02:00
return nick1 < nick2 ? -1 : nick1 > nick2 ? 1 : 0;
} else {
return MUC_ROLE_WEIGHTS[role1] < MUC_ROLE_WEIGHTS[role2] ? -1 : 1;
}
2018-05-09 14:37:27 +02:00
},
fetchMembers: function fetchMembers() {
var _this10 = this;
2018-05-11 00:19:49 +02:00
var old_jids = _.uniq(_.concat(_.map(this.where({
'affiliation': 'admin'
}), function (item) {
return item.get('jid');
}), _.map(this.where({
'affiliation': 'member'
}), function (item) {
return item.get('jid');
}), _.map(this.where({
'affiliation': 'owner'
}), function (item) {
return item.get('jid');
})));
2018-05-09 14:37:27 +02:00
this.chatroom.getJidsWithAffiliations(['member', 'owner', 'admin']).then(function (jids) {
2018-05-11 00:19:49 +02:00
_.each(_.difference(old_jids, jids), function (removed_jid) {
// Remove absent occupants who've been removed from
// the members lists.
var occupant = _this10.findOccupant({
'jid': removed_jid
});
if (occupant.get('show') === 'offline') {
occupant.destroy();
}
});
2018-05-09 14:37:27 +02:00
_.each(jids, function (attrs) {
var occupant = _this10.findOccupant({
'jid': attrs.jid
});
if (occupant) {
occupant.save(attrs);
} else {
_this10.create(attrs);
}
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
},
findOccupant: function findOccupant(data) {
/* Try to find an existing occupant based on the passed in
* data object.
*
* If we have a JID, we use that as lookup variable,
* otherwise we use the nick. We don't always have both,
* but should have at least one or the other.
*/
var jid = Strophe.getBareJidFromJid(data.jid);
if (jid !== null) {
return this.where({
'jid': jid
}).pop();
} else {
return this.where({
'nick': data.nick
}).pop();
}
2018-05-07 18:20:15 +02:00
}
});
_converse.RoomsPanelModel = Backbone.Model.extend({
defaults: {
'muc_domain': ''
}
});
2018-05-07 18:20:15 +02:00
_converse.onDirectMUCInvitation = function (message) {
/* A direct MUC invitation to join a room has been received
* See XEP-0249: Direct MUC invitations.
*
* Parameters:
* (XMLElement) message: The message stanza containing the
* invitation.
*/
var x_el = sizzle('x[xmlns="jabber:x:conference"]', message).pop(),
from = Strophe.getBareJidFromJid(message.getAttribute('from')),
room_jid = x_el.getAttribute('jid'),
reason = x_el.getAttribute('reason');
2018-05-07 18:20:15 +02:00
var contact = _converse.roster.get(from),
result;
2018-05-07 18:20:15 +02:00
if (_converse.auto_join_on_invite) {
result = true;
} else {
// Invite request might come from someone not your roster list
contact = contact ? contact.get('fullname') : Strophe.getNodeFromJid(from);
2017-07-22 22:21:05 +02:00
2018-05-07 18:20:15 +02:00
if (!reason) {
result = confirm(__("%1$s has invited you to join a chat room: %2$s", contact, room_jid));
} else {
result = confirm(__('%1$s has invited you to join a chat room: %2$s, and left the following reason: "%3$s"', contact, room_jid, reason));
}
}
2017-07-22 22:21:05 +02:00
2018-05-07 18:20:15 +02:00
if (result === true) {
var chatroom = _converse.openChatRoom(room_jid, {
'password': x_el.getAttribute('password')
});
2016-03-07 18:54:07 +01:00
2018-05-07 18:20:15 +02:00
if (chatroom.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED) {
_converse.chatboxviews.get(room_jid).join();
}
}
};
2018-05-07 18:20:15 +02:00
if (_converse.allow_muc_invitations) {
var registerDirectInvitationHandler = function registerDirectInvitationHandler() {
_converse.connection.addHandler(function (message) {
_converse.onDirectMUCInvitation(message);
return true;
}, 'jabber:x:conference', 'message');
};
2018-05-07 18:20:15 +02:00
_converse.on('connected', registerDirectInvitationHandler);
2018-05-07 18:20:15 +02:00
_converse.on('reconnected', registerDirectInvitationHandler);
}
2018-05-07 18:20:15 +02:00
var getChatRoom = function getChatRoom(jid, attrs, create) {
jid = jid.toLowerCase();
attrs.type = converse.CHATROOMS_TYPE;
attrs.id = jid;
attrs.box_id = b64_sha1(jid);
return _converse.chatboxes.getChatBox(jid, attrs, create);
};
2018-05-07 18:20:15 +02:00
var createChatRoom = function createChatRoom(jid, attrs) {
return getChatRoom(jid, attrs, true);
};
2018-05-07 18:20:15 +02:00
function autoJoinRooms() {
/* Automatically join chat rooms, based on the
* "auto_join_rooms" configuration setting, which is an array
* of strings (room JIDs) or objects (with room JID and other
* settings).
*/
_.each(_converse.auto_join_rooms, function (room) {
if (_converse.chatboxes.where({
'jid': room
}).length) {
return;
}
2018-05-07 18:20:15 +02:00
if (_.isString(room)) {
_converse.api.rooms.open(room);
} else if (_.isObject(room)) {
_converse.api.rooms.open(room.jid, room.nick);
} else {
_converse.log('Invalid room criteria specified for "auto_join_rooms"', Strophe.LogLevel.ERROR);
}
});
_converse.emit('roomsAutoJoined');
}
2018-05-07 18:20:15 +02:00
function disconnectChatRooms() {
/* When disconnecting, or reconnecting, mark all chat rooms as
* disconnected, so that they will be properly entered again
* when fetched from session storage.
*/
_converse.chatboxes.each(function (model) {
if (model.get('type') === converse.CHATROOMS_TYPE) {
model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
}
});
}
/************************ BEGIN Event Handlers ************************/
2018-05-07 18:20:15 +02:00
_converse.on('addClientFeatures', function () {
if (_converse.allow_muc) {
2018-05-15 10:32:13 +02:00
_converse.api.disco.own.features.add(Strophe.NS.MUC);
2018-05-07 18:20:15 +02:00
}
2018-05-07 18:20:15 +02:00
if (_converse.allow_muc_invitations) {
2018-05-15 10:32:13 +02:00
_converse.api.disco.own.features.add('jabber:x:conference'); // Invites
2018-05-07 18:20:15 +02:00
}
});
2018-05-07 18:20:15 +02:00
_converse.on('chatBoxesFetched', autoJoinRooms);
2018-05-07 18:20:15 +02:00
_converse.on('reconnecting', disconnectChatRooms);
2018-05-07 18:20:15 +02:00
_converse.on('disconnecting', disconnectChatRooms);
/************************ END Event Handlers ************************/
2018-05-07 18:20:15 +02:00
/************************ BEGIN API ************************/
// We extend the default converse.js API to add methods specific to MUC chat rooms.
2018-05-07 18:20:15 +02:00
_.extend(_converse.api, {
'rooms': {
'close': function close(jids) {
if (_.isUndefined(jids)) {
// FIXME: can't access views here
_converse.chatboxviews.each(function (view) {
if (view.is_chatroom && view.model) {
view.close();
}
});
} else if (_.isString(jids)) {
var view = _converse.chatboxviews.get(jids);
2018-05-07 18:20:15 +02:00
if (view) {
view.close();
}
} else {
_.each(jids, function (jid) {
var view = _converse.chatboxviews.get(jid);
if (view) {
view.close();
}
2018-05-07 18:20:15 +02:00
});
}
},
'create': function create(jids, attrs) {
if (_.isString(attrs)) {
attrs = {
'nick': attrs
};
} else if (_.isUndefined(attrs)) {
attrs = {};
}
2018-05-07 18:20:15 +02:00
if (_.isUndefined(attrs.maximize)) {
attrs.maximize = false;
}
2018-05-07 18:20:15 +02:00
if (!attrs.nick && _converse.muc_nickname_from_jid) {
attrs.nick = Strophe.getNodeFromJid(_converse.bare_jid);
}
2018-05-07 18:20:15 +02:00
if (_.isUndefined(jids)) {
throw new TypeError('rooms.create: You need to provide at least one JID');
} else if (_.isString(jids)) {
return createChatRoom(jids, attrs);
}
2018-05-07 18:20:15 +02:00
return _.map(jids, _.partial(createChatRoom, _, attrs));
},
'open': function open(jids, attrs) {
if (_.isUndefined(jids)) {
throw new TypeError('rooms.open: You need to provide at least one JID');
} else if (_.isString(jids)) {
return _converse.api.rooms.create(jids, attrs).trigger('show');
}
2016-03-07 18:54:07 +01:00
2018-05-07 18:20:15 +02:00
return _.map(jids, function (jid) {
return _converse.api.rooms.create(jid, attrs).trigger('show');
});
},
'get': function get(jids, attrs, create) {
if (_.isString(attrs)) {
attrs = {
'nick': attrs
};
} else if (_.isUndefined(attrs)) {
attrs = {};
}
2016-03-07 18:54:07 +01:00
2018-05-07 18:20:15 +02:00
if (_.isUndefined(jids)) {
var result = [];
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
_converse.chatboxes.each(function (chatbox) {
if (chatbox.get('type') === converse.CHATROOMS_TYPE) {
result.push(chatbox);
}
});
2018-05-07 18:20:15 +02:00
return result;
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
if (!attrs.nick) {
attrs.nick = Strophe.getNodeFromJid(_converse.bare_jid);
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
if (_.isString(jids)) {
return getChatRoom(jids, attrs);
}
2018-05-07 18:20:15 +02:00
return _.map(jids, _.partial(getChatRoom, _, attrs));
}
2018-03-25 21:21:43 +02:00
}
});
2018-05-07 18:20:15 +02:00
/************************ END API ************************/
2018-05-07 18:20:15 +02:00
}
});
});
//# sourceMappingURL=converse-muc.js.map;
2018-05-07 18:20:15 +02:00
define('tpl!chatroom_bookmark_form', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<div class="chatroom-form-container">\n <form class="converse-form chatroom-form">\n <fieldset class="form-group">\n <legend>' +
__e(o.heading) +
'</legend>\n <label>' +
__e(o.label_name) +
'</label>\n <input type="text" name="name" required="required"/>\n <label>' +
__e(o.label_autojoin) +
'</label>\n <input type="checkbox" name="autojoin"/>\n <label>' +
__e(o.label_nick) +
'</label>\n <input type="text" name="nick" value="' +
__e(o.default_nick) +
'"/>\n </fieldset>\n <fieldset class="form-group">\n <input class="btn btn-primary" type="submit" value="' +
__e(o.label_submit) +
'"/>\n <input class="btn btn-secondary button-cancel" type="button" value="' +
__e(o.label_cancel) +
'"/>\n </fieldset>\n </form>\n</div>\n';
return __p
};});
2018-05-07 18:20:15 +02:00
define('tpl!chatroom_bookmark_toggle', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<a class="chatbox-btn toggle-bookmark fa fa-bookmark\n ';
if (o.bookmarked) {;
__p += ' button-on ';
} ;
__p += '" title="' +
__e(o.info_toggle_bookmark) +
'"></a>\n';
return __p
};});
2018-05-07 18:20:15 +02:00
define('tpl!bookmark', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
2018-05-23 04:27:33 +02:00
__p += '<div class="list-item controlbox-padded room-item available-chatroom d-flex flex-row ';
2018-05-07 18:20:15 +02:00
if (o.hidden) { ;
__p += ' hidden ';
} ;
__p += '" data-room-jid="' +
__e(o.jid) +
'">\n <a class="open-room w-100" data-room-jid="' +
__e(o.jid) +
'" title="' +
__e(o.open_title) +
'" href="#">' +
__e(o.name) +
'</a>\n <a class="remove-bookmark fa fa-bookmark align-self-center ';
if (o.bookmarked) { ;
__p += ' button-on ';
} ;
__p += '"\n data-room-jid="' +
__e(o.jid) +
'" data-bookmark-name="' +
__e(o.name) +
'"\n title="' +
__e(o.info_remove_bookmark) +
'" href="#">&nbsp;</a>\n <a class="room-info fa fa-info-circle align-self-center" data-room-jid="' +
__e(o.jid) +
'"\n title="' +
__e(o.info_title) +
'" href="#">&nbsp;</a>\n</div>\n';
return __p
};});
2018-05-07 18:20:15 +02:00
define('tpl!bookmarks_list', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
2018-05-23 04:27:33 +02:00
__p += '<a href="#" class="rooms-toggle bookmarks-toggle controlbox-padded" title="' +
2018-05-07 18:20:15 +02:00
__e(o.desc_bookmarks) +
'">\n <span class="fa ';
if (o.toggle_state === o._converse.OPENED) { ;
__p += ' fa-caret-down ';
} else { ;
__p += ' fa-caret-right ';
} ;
__p += '">\n </span> ' +
__e(o.label_bookmarks) +
'</a>\n<div class="items-list bookmarks rooms-list ';
if (o.toggle_state !== o._converse.OPENED) { ;
__p += ' hidden ';
} ;
__p += '"></div>\n';
return __p
};});
2018-05-07 18:20:15 +02:00
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
2018-05-07 18:20:15 +02:00
/*global define */
2018-05-07 18:20:15 +02:00
/* This is a Converse.js plugin which add support for bookmarks specified
* in XEP-0048.
*/
(function (root, factory) {
define('converse-bookmarks',["converse-core", "converse-muc", "tpl!chatroom_bookmark_form", "tpl!chatroom_bookmark_toggle", "tpl!bookmark", "tpl!bookmarks_list"], factory);
})(this, function (converse, muc, tpl_chatroom_bookmark_form, tpl_chatroom_bookmark_toggle, tpl_bookmark, tpl_bookmarks_list) {
var _converse$env = converse.env,
Backbone = _converse$env.Backbone,
Promise = _converse$env.Promise,
Strophe = _converse$env.Strophe,
$iq = _converse$env.$iq,
b64_sha1 = _converse$env.b64_sha1,
sizzle = _converse$env.sizzle,
_ = _converse$env._;
var u = converse.env.utils;
converse.plugins.add('converse-bookmarks', {
/* Plugin dependencies are other plugins which might be
* overridden or relied upon, and therefore need to be loaded before
* this plugin.
*
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found. By default it's
* false, which means these plugins are only loaded opportunistically.
*
* NB: These plugins need to have already been loaded via require.js.
*/
dependencies: ["converse-chatboxes", "converse-muc", "converse-muc-views"],
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
ChatRoomView: {
events: {
'click .toggle-bookmark': 'toggleBookmark'
},
2018-05-07 18:20:15 +02:00
initialize: function initialize() {
this.__super__.initialize.apply(this, arguments);
2018-05-07 18:20:15 +02:00
this.model.on('change:bookmarked', this.onBookmarked, this);
this.setBookmarkState();
},
2018-05-07 18:20:15 +02:00
renderBookmarkToggle: function renderBookmarkToggle() {
var _converse = this.__super__._converse,
__ = _converse.__;
var bookmark_button = tpl_chatroom_bookmark_toggle(_.assignIn(this.model.toJSON(), {
info_toggle_bookmark: __('Bookmark this room'),
bookmarked: this.model.get('bookmarked')
}));
2018-05-07 18:20:15 +02:00
var close_button = this.el.querySelector('.close-chatbox-button');
close_button.insertAdjacentHTML('afterend', bookmark_button);
},
2018-05-07 18:20:15 +02:00
renderHeading: function renderHeading() {
var _this = this;
2018-05-07 18:20:15 +02:00
this.__super__.renderHeading.apply(this, arguments);
2018-05-07 18:20:15 +02:00
var _converse = this.__super__._converse;
2018-05-07 18:20:15 +02:00
if (_converse.allow_bookmarks) {
_converse.checkBookmarksSupport().then(function (supported) {
if (supported) {
_this.renderBookmarkToggle();
}
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}
2018-05-07 18:20:15 +02:00
},
checkForReservedNick: function checkForReservedNick() {
/* Check if the user has a bookmark with a saved nickanme
* for this room, and if so use it.
* Otherwise delegate to the super method.
*/
2018-05-07 18:20:15 +02:00
var _converse = this.__super__._converse;
2018-05-07 18:20:15 +02:00
if (_.isUndefined(_converse.bookmarks) || !_converse.allow_bookmarks) {
return this.__super__.checkForReservedNick.apply(this, arguments);
}
2018-05-07 18:20:15 +02:00
var model = _converse.bookmarks.findWhere({
'jid': this.model.get('jid')
});
2018-05-07 18:20:15 +02:00
if (!_.isUndefined(model) && model.get('nick')) {
this.join(model.get('nick'));
} else {
return this.__super__.checkForReservedNick.apply(this, arguments);
}
2018-05-07 18:20:15 +02:00
},
onBookmarked: function onBookmarked() {
var icon = this.el.querySelector('.toggle-bookmark');
2018-05-07 18:20:15 +02:00
if (_.isNull(icon)) {
return;
}
2018-05-07 18:20:15 +02:00
if (this.model.get('bookmarked')) {
icon.classList.add('button-on');
} else {
2018-05-07 18:20:15 +02:00
icon.classList.remove('button-on');
}
},
2018-05-07 18:20:15 +02:00
setBookmarkState: function setBookmarkState() {
/* Set whether the room is bookmarked or not.
*/
2018-05-07 18:20:15 +02:00
var _converse = this.__super__._converse;
2018-05-07 18:20:15 +02:00
if (!_.isUndefined(_converse.bookmarks)) {
var models = _converse.bookmarks.where({
'jid': this.model.get('jid')
});
if (!models.length) {
this.model.save('bookmarked', false);
} else {
this.model.save('bookmarked', true);
2018-03-25 21:21:43 +02:00
}
}
},
2018-05-07 18:20:15 +02:00
renderBookmarkForm: function renderBookmarkForm() {
var _converse = this.__super__._converse,
__ = _converse.__,
body = this.el.querySelector('.chatroom-body');
2018-05-07 18:20:15 +02:00
_.each(body.children, function (child) {
child.classList.add('hidden');
}); // Remove any existing forms
2018-05-07 18:20:15 +02:00
_.each(body.querySelectorAll('.chatroom-form-container'), u.removeElement);
body.insertAdjacentHTML('beforeend', tpl_chatroom_bookmark_form({
heading: __('Bookmark this room'),
label_name: __('The name for this bookmark:'),
label_autojoin: __('Would you like this room to be automatically joined upon startup?'),
label_nick: __('What should your nickname for this room be?'),
default_nick: this.model.get('nick'),
label_submit: __('Save'),
label_cancel: __('Cancel')
}));
var form = body.querySelector('form.chatroom-form');
form.addEventListener('submit', this.onBookmarkFormSubmitted.bind(this));
form.querySelector('.button-cancel').addEventListener('click', this.closeForm.bind(this));
},
2018-05-07 18:20:15 +02:00
onBookmarkFormSubmitted: function onBookmarkFormSubmitted(ev) {
ev.preventDefault();
var _converse = this.__super__._converse;
_converse.bookmarks.createBookmark({
'jid': this.model.get('jid'),
'autojoin': _.get(ev.target.querySelector('input[name="autojoin"]'), 'checked') || false,
'name': _.get(ev.target.querySelector('input[name=name]'), 'value'),
'nick': _.get(ev.target.querySelector('input[name=nick]'), 'value')
});
u.removeElement(this.el.querySelector('div.chatroom-form-container'));
this.renderAfterTransition();
},
2018-05-07 18:20:15 +02:00
toggleBookmark: function toggleBookmark(ev) {
if (ev) {
ev.preventDefault();
ev.stopPropagation();
}
2018-05-07 18:20:15 +02:00
var _converse = this.__super__._converse;
var models = _converse.bookmarks.where({
'jid': this.model.get('jid')
});
if (!models.length) {
this.renderBookmarkForm();
} else {
2018-05-07 18:20:15 +02:00
_.forEach(models, function (model) {
model.destroy();
});
this.el.querySelector('.toggle-bookmark').classList.remove('button-on');
}
2018-05-07 18:20:15 +02:00
}
}
},
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
var _converse = this._converse,
__ = _converse.__; // Configuration values for this plugin
// ====================================
// Refer to docs/source/configuration.rst for explanations of these
// configuration settings.
2018-05-07 18:20:15 +02:00
_converse.api.settings.update({
allow_bookmarks: true,
allow_public_bookmarks: false,
hide_open_bookmarks: true
}); // Promises exposed by this plugin
_converse.api.promises.add('bookmarksInitialized'); // Pure functions on the _converse object
_.extend(_converse, {
removeBookmarkViaEvent: function removeBookmarkViaEvent(ev) {
/* Remove a bookmark as determined by the passed in
* event.
*/
ev.preventDefault();
var name = ev.target.getAttribute('data-bookmark-name');
var jid = ev.target.getAttribute('data-room-jid');
if (confirm(__("Are you sure you want to remove the bookmark \"%1$s\"?", name))) {
_.invokeMap(_converse.bookmarks.where({
'jid': jid
}), Backbone.Model.prototype.destroy);
}
},
2018-05-07 18:20:15 +02:00
addBookmarkViaEvent: function addBookmarkViaEvent(ev) {
/* Add a bookmark as determined by the passed in
* event.
*/
2018-05-07 18:20:15 +02:00
ev.preventDefault();
var jid = ev.target.getAttribute('data-room-jid');
2018-05-07 18:20:15 +02:00
var chatroom = _converse.api.rooms.open(jid, {
'bring_to_foreground': true
});
_converse.chatboxviews.get(jid).renderBookmarkForm();
}
});
_converse.Bookmark = Backbone.Model;
_converse.Bookmarks = Backbone.Collection.extend({
model: _converse.Bookmark,
comparator: 'name',
initialize: function initialize() {
this.on('add', _.flow(this.openBookmarkedRoom, this.markRoomAsBookmarked));
this.on('remove', this.markRoomAsUnbookmarked, this);
this.on('remove', this.sendBookmarkStanza, this);
var cache_key = "converse.room-bookmarks".concat(_converse.bare_jid);
this.fetched_flag = b64_sha1(cache_key + 'fetched');
this.browserStorage = new Backbone.BrowserStorage[_converse.storage](b64_sha1(cache_key));
},
openBookmarkedRoom: function openBookmarkedRoom(bookmark) {
if (bookmark.get('autojoin')) {
var room = _converse.api.rooms.create(bookmark.get('jid'), bookmark.get('nick'));
if (!room.get('hidden')) {
room.trigger('show');
2018-03-25 21:21:43 +02:00
}
}
2018-05-07 18:20:15 +02:00
return bookmark;
},
fetchBookmarks: function fetchBookmarks() {
var deferred = u.getResolveablePromise();
2018-05-07 18:20:15 +02:00
if (this.browserStorage.records.length > 0) {
this.fetch({
'success': _.bind(this.onCachedBookmarksFetched, this, deferred),
'error': _.bind(this.onCachedBookmarksFetched, this, deferred)
});
} else if (!window.sessionStorage.getItem(this.fetched_flag)) {
// There aren't any cached bookmarks and the
// `fetched_flag` is off, so we query the XMPP server.
// If nothing is returned from the XMPP server, we set
// the `fetched_flag` to avoid calling the server again.
this.fetchBookmarksFromServer(deferred);
} else {
2018-05-07 18:20:15 +02:00
deferred.resolve();
}
2018-05-07 18:20:15 +02:00
return deferred;
},
onCachedBookmarksFetched: function onCachedBookmarksFetched(deferred) {
return deferred.resolve();
},
createBookmark: function createBookmark(options) {
_converse.bookmarks.create(options);
2018-04-30 16:05:20 +02:00
2018-05-07 18:20:15 +02:00
_converse.bookmarks.sendBookmarkStanza();
2018-04-30 16:05:20 +02:00
},
2018-05-07 18:20:15 +02:00
sendBookmarkStanza: function sendBookmarkStanza() {
var stanza = $iq({
'type': 'set',
'from': _converse.connection.jid
}).c('pubsub', {
'xmlns': Strophe.NS.PUBSUB
}).c('publish', {
'node': 'storage:bookmarks'
}).c('item', {
'id': 'current'
}).c('storage', {
'xmlns': 'storage:bookmarks'
});
this.each(function (model) {
stanza = stanza.c('conference', {
'name': model.get('name'),
'autojoin': model.get('autojoin'),
'jid': model.get('jid')
}).c('nick').t(model.get('nick')).up().up();
});
stanza.up().up().up();
stanza.c('publish-options').c('x', {
'xmlns': Strophe.NS.XFORM,
'type': 'submit'
}).c('field', {
'var': 'FORM_TYPE',
'type': 'hidden'
}).c('value').t('http://jabber.org/protocol/pubsub#publish-options').up().up().c('field', {
'var': 'pubsub#persist_items'
}).c('value').t('true').up().up().c('field', {
'var': 'pubsub#access_model'
}).c('value').t('whitelist');
2018-04-30 16:05:20 +02:00
2018-05-07 18:20:15 +02:00
_converse.connection.sendIQ(stanza, null, this.onBookmarkError.bind(this));
},
onBookmarkError: function onBookmarkError(iq) {
_converse.log("Error while trying to add bookmark", Strophe.LogLevel.ERROR);
2018-04-30 16:05:20 +02:00
2018-05-07 18:20:15 +02:00
_converse.log(iq); // We remove all locally cached bookmarks and fetch them
// again from the server.
2018-04-30 16:05:20 +02:00
2018-05-07 18:20:15 +02:00
this.reset();
this.fetchBookmarksFromServer(null);
window.alert(__("Sorry, something went wrong while trying to save your bookmark."));
},
2018-05-07 18:20:15 +02:00
fetchBookmarksFromServer: function fetchBookmarksFromServer(deferred) {
var stanza = $iq({
'from': _converse.connection.jid,
'type': 'get'
}).c('pubsub', {
'xmlns': Strophe.NS.PUBSUB
}).c('items', {
'node': 'storage:bookmarks'
});
2018-05-07 18:20:15 +02:00
_converse.connection.sendIQ(stanza, _.bind(this.onBookmarksReceived, this, deferred), _.bind(this.onBookmarksReceivedError, this, deferred));
},
markRoomAsBookmarked: function markRoomAsBookmarked(bookmark) {
var room = _converse.chatboxes.get(bookmark.get('jid'));
2018-05-07 18:20:15 +02:00
if (!_.isUndefined(room)) {
room.save('bookmarked', true);
}
},
2018-05-07 18:20:15 +02:00
markRoomAsUnbookmarked: function markRoomAsUnbookmarked(bookmark) {
var room = _converse.chatboxes.get(bookmark.get('jid'));
2018-05-07 18:20:15 +02:00
if (!_.isUndefined(room)) {
room.save('bookmarked', false);
}
2018-05-07 18:20:15 +02:00
},
createBookmarksFromStanza: function createBookmarksFromStanza(stanza) {
var _this2 = this;
2018-05-07 18:20:15 +02:00
var bookmarks = sizzle('items[node="storage:bookmarks"] ' + 'item#current ' + 'storage[xmlns="storage:bookmarks"] ' + 'conference', stanza);
_.forEach(bookmarks, function (bookmark) {
_this2.create({
'jid': bookmark.getAttribute('jid'),
'name': bookmark.getAttribute('name'),
'autojoin': bookmark.getAttribute('autojoin') === 'true',
'nick': _.get(bookmark.querySelector('nick'), 'textContent')
});
});
},
2018-05-07 18:20:15 +02:00
onBookmarksReceived: function onBookmarksReceived(deferred, iq) {
this.createBookmarksFromStanza(iq);
2018-05-07 18:20:15 +02:00
if (!_.isUndefined(deferred)) {
return deferred.resolve();
}
},
2018-05-07 18:20:15 +02:00
onBookmarksReceivedError: function onBookmarksReceivedError(deferred, iq) {
window.sessionStorage.setItem(this.fetched_flag, true);
2018-05-07 18:20:15 +02:00
_converse.log('Error while fetching bookmarks', Strophe.LogLevel.WARN);
2018-05-07 18:20:15 +02:00
_converse.log(iq.outerHTML, Strophe.LogLevel.DEBUG);
if (!_.isNil(deferred)) {
if (iq.querySelector('error[type="cancel"] item-not-found')) {
// Not an exception, the user simply doesn't have
// any bookmarks.
return deferred.resolve();
} else {
return deferred.reject(new Error("Could not fetch bookmarks"));
}
}
2018-05-07 18:20:15 +02:00
}
});
_converse.BookmarksList = Backbone.Model.extend({
defaults: {
"toggle-state": _converse.OPENED
}
});
_converse.BookmarkView = Backbone.VDOMView.extend({
toHTML: function toHTML() {
return tpl_bookmark({
'hidden': _converse.hide_open_bookmarks && _converse.chatboxes.where({
'jid': this.model.get('jid')
}).length,
'bookmarked': true,
'info_leave_room': __('Leave this room'),
'info_remove': __('Remove this bookmark'),
'info_remove_bookmark': __('Unbookmark this room'),
'info_title': __('Show more information on this room'),
'jid': this.model.get('jid'),
'name': Strophe.xmlunescape(this.model.get('name')),
'open_title': __('Click to open this room')
});
}
});
_converse.BookmarksView = Backbone.OrderedListView.extend({
tagName: 'div',
className: 'bookmarks-list list-container rooms-list-container',
events: {
'click .add-bookmark': 'addBookmark',
'click .bookmarks-toggle': 'toggleBookmarksList',
'click .remove-bookmark': 'removeBookmark',
'click .open-room': 'openRoom'
},
listSelector: '.rooms-list',
ItemView: _converse.BookmarkView,
subviewIndex: 'jid',
initialize: function initialize() {
Backbone.OrderedListView.prototype.initialize.apply(this, arguments);
this.model.on('add', this.showOrHide, this);
this.model.on('remove', this.showOrHide, this);
2018-05-07 18:20:15 +02:00
_converse.chatboxes.on('add', this.renderBookmarkListElement, this);
2018-05-07 18:20:15 +02:00
_converse.chatboxes.on('remove', this.renderBookmarkListElement, this);
var cachekey = "converse.room-bookmarks".concat(_converse.bare_jid, "-list-model");
this.list_model = new _converse.BookmarksList();
this.list_model.id = cachekey;
this.list_model.browserStorage = new Backbone.BrowserStorage[_converse.storage](b64_sha1(cachekey));
this.list_model.fetch();
this.render();
this.sortAndPositionAllItems();
},
render: function render() {
this.el.innerHTML = tpl_bookmarks_list({
'toggle_state': this.list_model.get('toggle-state'),
'desc_bookmarks': __('Click to toggle the bookmarks list'),
'label_bookmarks': __('Bookmarks'),
'_converse': _converse
});
this.showOrHide();
this.insertIntoControlBox();
return this;
},
2018-05-07 18:20:15 +02:00
insertIntoControlBox: function insertIntoControlBox() {
var controlboxview = _converse.chatboxviews.get('controlbox');
2018-05-23 04:27:33 +02:00
if (!_.isUndefined(controlboxview) && !u.rootContains(_converse.root, this.el)) {
2018-05-07 18:20:15 +02:00
var el = controlboxview.el.querySelector('.bookmarks-list');
2018-05-07 18:20:15 +02:00
if (!_.isNull(el)) {
el.parentNode.replaceChild(this.el, el);
}
}
},
openRoom: function openRoom(ev) {
ev.preventDefault();
var name = ev.target.textContent;
var jid = ev.target.getAttribute('data-room-jid');
var data = {
'name': name || Strophe.unescapeNode(Strophe.getNodeFromJid(jid)) || jid
};
2018-05-07 18:20:15 +02:00
_converse.api.rooms.open(jid, data);
},
removeBookmark: _converse.removeBookmarkViaEvent,
addBookmark: _converse.addBookmarkViaEvent,
renderBookmarkListElement: function renderBookmarkListElement(chatbox) {
var bookmarkview = this.get(chatbox.get('jid'));
2018-05-07 18:20:15 +02:00
if (_.isNil(bookmarkview)) {
// A chat box has been closed, but we don't have a
// bookmark for it, so nothing further to do here.
return;
}
2018-05-07 18:20:15 +02:00
bookmarkview.render();
this.showOrHide();
},
2018-05-07 18:20:15 +02:00
showOrHide: function showOrHide(item) {
if (_converse.hide_open_bookmarks) {
var bookmarks = this.model.filter(function (bookmark) {
return !_converse.chatboxes.get(bookmark.get('jid'));
});
if (!bookmarks.length) {
u.hideElement(this.el);
return;
}
}
2018-05-07 18:20:15 +02:00
if (this.model.models.length) {
u.showElement(this.el);
}
},
2018-05-07 18:20:15 +02:00
toggleBookmarksList: function toggleBookmarksList(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}
2018-05-07 18:20:15 +02:00
var icon_el = ev.target.querySelector('.fa');
2018-05-07 18:20:15 +02:00
if (u.hasClass('fa-caret-down', icon_el)) {
u.slideIn(this.el.querySelector('.bookmarks'));
this.list_model.save({
'toggle-state': _converse.CLOSED
});
icon_el.classList.remove("fa-caret-down");
icon_el.classList.add("fa-caret-right");
} else {
icon_el.classList.remove("fa-caret-right");
icon_el.classList.add("fa-caret-down");
u.slideOut(this.el.querySelector('.bookmarks'));
this.list_model.save({
'toggle-state': _converse.OPENED
});
}
2018-05-07 18:20:15 +02:00
}
});
2018-05-07 18:20:15 +02:00
_converse.checkBookmarksSupport = function () {
return new Promise(function (resolve, reject) {
Promise.all([_converse.api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid), _converse.api.disco.supports(Strophe.NS.PUBSUB + '#publish-options', _converse.bare_jid)]).then(function (args) {
resolve(args[0] && (args[1].length || _converse.allow_public_bookmarks));
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
};
2018-05-07 18:20:15 +02:00
var initBookmarks = function initBookmarks() {
if (!_converse.allow_bookmarks) {
return;
}
2018-05-07 18:20:15 +02:00
_converse.checkBookmarksSupport().then(function (supported) {
if (supported) {
_converse.bookmarks = new _converse.Bookmarks();
_converse.bookmarksview = new _converse.BookmarksView({
'model': _converse.bookmarks
});
2018-05-07 18:20:15 +02:00
_converse.bookmarks.fetchBookmarks().catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)).then(function () {
return _converse.emit('bookmarksInitialized');
});
} else {
_converse.emit('bookmarksInitialized');
}
2018-05-07 18:20:15 +02:00
});
};
2018-05-07 18:20:15 +02:00
u.onMultipleEvents([{
'object': _converse,
'event': 'chatBoxesFetched'
}, {
'object': _converse,
'event': 'roomsPanelRendered'
}], initBookmarks);
2018-05-23 04:27:33 +02:00
_converse.on('clearSession', function () {
if (!_.isUndefined(_converse.bookmarks)) {
_converse.bookmarks.browserStorage._clear();
window.sessionStorage.removeItem(_converse.bookmarks.fetched_flag);
}
});
2018-05-07 18:20:15 +02:00
_converse.on('reconnected', initBookmarks);
2018-05-07 18:20:15 +02:00
_converse.on('connected', function () {
// Add a handler for bookmarks pushed from other connected clients
// (from the same user obviously)
_converse.connection.addHandler(function (message) {
if (message.querySelector('event[xmlns="' + Strophe.NS.PUBSUB + '#event"]')) {
_converse.bookmarks.createBookmarksFromStanza(message);
}
}, null, 'message', 'headline', null, _converse.bare_jid);
});
}
});
});
//# sourceMappingURL=converse-bookmarks.js.map;
2018-05-15 10:32:13 +02:00
// Converse.js
// http://conversejs.org
//
// Copyright (c) 2013-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2)
(function (root, factory) {
define('converse-caps',["converse-core"], factory);
}(this, function (converse) {
const { Strophe, $build, _, b64_sha1 } = converse.env;
Strophe.addNamespace('CAPS', "http://jabber.org/protocol/caps");
function propertySort (array, property) {
return array.sort((a, b) => { return a[property] > b[property] ? -1 : 1 });
}
function generateVerificationString (_converse) {
const identities = _converse.api.disco.own.identities.get(),
features = _converse.api.disco.own.features.get();
if (identities.length > 1) {
propertySort(identities, "category");
propertySort(identities, "type");
propertySort(identities, "lang");
}
let S = _.reduce(
identities,
(result, id) => `${result}${id.category}/${id.type}/${_.get(id, 'lang', '')}/${id.name}<`,
"");
features.sort();
S = _.reduce(features, (result, feature) => `${result}${feature}<`, S);
return b64_sha1(S);
}
function createCapsNode (_converse) {
return $build("c", {
'xmlns': Strophe.NS.CAPS,
'hash': "sha-1",
'node': "https://conversejs.org",
'ver': generateVerificationString(_converse)
}).nodeTree;
}
converse.plugins.add('converse-caps', {
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
XMPPStatus: {
constructPresence () {
const presence = this.__super__.constructPresence.apply(this, arguments);
presence.root().cnode(createCapsNode(this.__super__._converse));
return presence;
}
}
}
});
}));
2018-05-11 00:19:49 +02:00
// Native Javascript for Bootstrap 4 v2.0.22 | © dnp_theme | MIT-License
2018-05-07 18:20:15 +02:00
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD support:
define('bootstrap',[], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS-like:
module.exports = factory();
} else {
// Browser globals (root is window)
var bsn = factory();
root.Alert = bsn.Alert;
root.Button = bsn.Button;
root.Carousel = bsn.Carousel;
root.Collapse = bsn.Collapse;
root.Dropdown = bsn.Dropdown;
root.Modal = bsn.Modal;
root.Popover = bsn.Popover;
root.ScrollSpy = bsn.ScrollSpy;
root.Tab = bsn.Tab;
root.Tooltip = bsn.Tooltip;
}
}(this, function () {
/* Native Javascript for Bootstrap 4 | Internal Utility Functions
----------------------------------------------------------------*/
"use strict";
// globals
var globalObject = typeof global !== 'undefined' ? global : this||window,
DOC = document, HTML = DOC.documentElement, body = 'body', // allow the library to be used in <head>
// Native Javascript for Bootstrap Global Object
BSN = globalObject.BSN = {},
supports = BSN.supports = [],
// function toggle attributes
dataToggle = 'data-toggle',
dataDismiss = 'data-dismiss',
dataSpy = 'data-spy',
dataRide = 'data-ride',
// components
stringAlert = 'Alert',
stringButton = 'Button',
stringCarousel = 'Carousel',
stringCollapse = 'Collapse',
stringDropdown = 'Dropdown',
stringModal = 'Modal',
stringPopover = 'Popover',
stringScrollSpy = 'ScrollSpy',
stringTab = 'Tab',
stringTooltip = 'Tooltip',
// options DATA API
databackdrop = 'data-backdrop',
dataKeyboard = 'data-keyboard',
dataTarget = 'data-target',
dataInterval = 'data-interval',
dataHeight = 'data-height',
dataPause = 'data-pause',
dataTitle = 'data-title',
dataOriginalTitle = 'data-original-title',
dataOriginalText = 'data-original-text',
dataDismissible = 'data-dismissible',
dataTrigger = 'data-trigger',
dataAnimation = 'data-animation',
dataContainer = 'data-container',
dataPlacement = 'data-placement',
dataDelay = 'data-delay',
dataOffsetTop = 'data-offset-top',
dataOffsetBottom = 'data-offset-bottom',
// option keys
backdrop = 'backdrop', keyboard = 'keyboard', delay = 'delay',
content = 'content', target = 'target',
interval = 'interval', pause = 'pause', animation = 'animation',
placement = 'placement', container = 'container',
// box model
offsetTop = 'offsetTop', offsetBottom = 'offsetBottom',
offsetLeft = 'offsetLeft',
scrollTop = 'scrollTop', scrollLeft = 'scrollLeft',
clientWidth = 'clientWidth', clientHeight = 'clientHeight',
offsetWidth = 'offsetWidth', offsetHeight = 'offsetHeight',
innerWidth = 'innerWidth', innerHeight = 'innerHeight',
scrollHeight = 'scrollHeight', height = 'height',
// aria
ariaExpanded = 'aria-expanded',
ariaHidden = 'aria-hidden',
// event names
clickEvent = 'click',
hoverEvent = 'hover',
keydownEvent = 'keydown',
keyupEvent = 'keyup',
resizeEvent = 'resize',
scrollEvent = 'scroll',
// originalEvents
showEvent = 'show',
shownEvent = 'shown',
hideEvent = 'hide',
hiddenEvent = 'hidden',
closeEvent = 'close',
closedEvent = 'closed',
slidEvent = 'slid',
slideEvent = 'slide',
changeEvent = 'change',
// other
getAttribute = 'getAttribute',
setAttribute = 'setAttribute',
hasAttribute = 'hasAttribute',
createElement = 'createElement',
appendChild = 'appendChild',
innerHTML = 'innerHTML',
getElementsByTagName = 'getElementsByTagName',
preventDefault = 'preventDefault',
getBoundingClientRect = 'getBoundingClientRect',
querySelectorAll = 'querySelectorAll',
getElementsByCLASSNAME = 'getElementsByClassName',
indexOf = 'indexOf',
parentNode = 'parentNode',
length = 'length',
toLowerCase = 'toLowerCase',
Transition = 'Transition',
Webkit = 'Webkit',
style = 'style',
push = 'push',
tabindex = 'tabindex',
contains = 'contains',
active = 'active',
showClass = 'show',
collapsing = 'collapsing',
disabled = 'disabled',
loading = 'loading',
left = 'left',
right = 'right',
top = 'top',
bottom = 'bottom',
// tooltip / popover
mouseHover = ('onmouseleave' in DOC) ? [ 'mouseenter', 'mouseleave'] : [ 'mouseover', 'mouseout' ],
tipPositions = /\b(top|bottom|left|right)+/,
// modal
modalOverlay = 0,
fixedTop = 'fixed-top',
fixedBottom = 'fixed-bottom',
// transitionEnd since 2.0.4
supportTransitions = Webkit+Transition in HTML[style] || Transition[toLowerCase]() in HTML[style],
transitionEndEvent = Webkit+Transition in HTML[style] ? Webkit[toLowerCase]()+Transition+'End' : Transition[toLowerCase]()+'end',
// set new focus element since 2.0.3
setFocus = function(element){
element.focus ? element.focus() : element.setActive();
},
// class manipulation, since 2.0.0 requires polyfill.js
addClass = function(element,classNAME) {
element.classList.add(classNAME);
},
removeClass = function(element,classNAME) {
element.classList.remove(classNAME);
},
hasClass = function(element,classNAME){ // since 2.0.0
return element.classList[contains](classNAME);
},
// selection methods
getElementsByClassName = function(element,classNAME) { // returns Array
return [].slice.call(element[getElementsByCLASSNAME]( classNAME ));
},
queryElement = function (selector, parent) {
var lookUp = parent ? parent : DOC;
return typeof selector === 'object' ? selector : lookUp.querySelector(selector);
},
getClosest = function (element, selector) { //element is the element and selector is for the closest parent element to find
// source http://gomakethings.com/climbing-up-and-down-the-dom-tree-with-vanilla-javascript/
var firstChar = selector.charAt(0), selectorSubstring = selector.substr(1);
if ( firstChar === '.' ) {// If selector is a class
for ( ; element && element !== DOC; element = element[parentNode] ) { // Get closest match
if ( queryElement(selector,element[parentNode]) !== null && hasClass(element,selectorSubstring) ) { return element; }
}
} else if ( firstChar === '#' ) { // If selector is an ID
for ( ; element && element !== DOC; element = element[parentNode] ) { // Get closest match
if ( element.id === selectorSubstring ) { return element; }
}
}
return false;
},
// event attach jQuery style / trigger since 1.2.0
on = function (element, event, handler) {
element.addEventListener(event, handler, false);
},
off = function(element, event, handler) {
element.removeEventListener(event, handler, false);
},
one = function (element, event, handler) { // one since 2.0.4
on(element, event, function handlerWrapper(e){
handler(e);
off(element, event, handlerWrapper);
});
},
emulateTransitionEnd = function(element,handler){ // emulateTransitionEnd since 2.0.4
if (supportTransitions) { one(element, transitionEndEvent, function(e){ handler(e); }); }
else { handler(); }
},
bootstrapCustomEvent = function (eventName, componentName, related) {
var OriginalCustomEvent = new CustomEvent( eventName + '.bs.' + componentName);
OriginalCustomEvent.relatedTarget = related;
this.dispatchEvent(OriginalCustomEvent);
},
// tooltip / popover stuff
getScroll = function() { // also Affix and ScrollSpy uses it
return {
y : globalObject.pageYOffset || HTML[scrollTop],
x : globalObject.pageXOffset || HTML[scrollLeft]
}
},
styleTip = function(link,element,position,parent) { // both popovers and tooltips (target,tooltip,placement,elementToAppendTo)
var elementDimensions = { w : element[offsetWidth], h: element[offsetHeight] },
windowWidth = (HTML[clientWidth] || DOC[body][clientWidth]),
windowHeight = (HTML[clientHeight] || DOC[body][clientHeight]),
rect = link[getBoundingClientRect](),
scroll = parent === DOC[body] ? getScroll() : { x: parent[offsetLeft] + parent[scrollLeft], y: parent[offsetTop] + parent[scrollTop] },
linkDimensions = { w: rect[right] - rect[left], h: rect[bottom] - rect[top] },
2018-05-11 00:19:49 +02:00
isPopover = hasClass(element,'popover'),
topPosition, leftPosition,
2018-05-07 18:20:15 +02:00
arrow = queryElement('.arrow',element),
2018-05-11 00:19:49 +02:00
arrowTop, arrowLeft, arrowWidth, arrowHeight,
2018-05-07 18:20:15 +02:00
halfTopExceed = rect[top] + linkDimensions.h/2 - elementDimensions.h/2 < 0,
halfLeftExceed = rect[left] + linkDimensions.w/2 - elementDimensions.w/2 < 0,
halfRightExceed = rect[left] + elementDimensions.w/2 + linkDimensions.w/2 >= windowWidth,
halfBottomExceed = rect[top] + elementDimensions.h/2 + linkDimensions.h/2 >= windowHeight,
topExceed = rect[top] - elementDimensions.h < 0,
leftExceed = rect[left] - elementDimensions.w < 0,
bottomExceed = rect[top] + elementDimensions.h + linkDimensions.h >= windowHeight,
rightExceed = rect[left] + elementDimensions.w + linkDimensions.w >= windowWidth;
// recompute position
position = (position === left || position === right) && leftExceed && rightExceed ? top : position; // first, when both left and right limits are exceeded, we fall back to top|bottom
position = position === top && topExceed ? bottom : position;
position = position === bottom && bottomExceed ? top : position;
position = position === left && leftExceed ? right : position;
position = position === right && rightExceed ? left : position;
2018-05-11 00:19:49 +02:00
// update tooltip/popover class
element.className[indexOf](position) === -1 && (element.className = element.className.replace(tipPositions,position));
// we check the computed width & height and update here
arrowWidth = arrow[offsetWidth]; arrowHeight = arrow[offsetHeight];
2018-05-07 18:20:15 +02:00
// apply styling to tooltip or popover
if ( position === left || position === right ) { // secondary|side positions
if ( position === left ) { // LEFT
2018-05-11 00:19:49 +02:00
leftPosition = rect[left] + scroll.x - elementDimensions.w - ( isPopover ? arrowWidth : 0 );
2018-05-07 18:20:15 +02:00
} else { // RIGHT
leftPosition = rect[left] + scroll.x + linkDimensions.w;
}
// adjust top and arrow
if (halfTopExceed) {
topPosition = rect[top] + scroll.y;
2018-05-11 00:19:49 +02:00
arrowTop = linkDimensions.h/2 - arrowWidth;
2018-05-07 18:20:15 +02:00
} else if (halfBottomExceed) {
topPosition = rect[top] + scroll.y - elementDimensions.h + linkDimensions.h;
2018-05-11 00:19:49 +02:00
arrowTop = elementDimensions.h - linkDimensions.h/2 - arrowWidth;
2018-05-07 18:20:15 +02:00
} else {
topPosition = rect[top] + scroll.y - elementDimensions.h/2 + linkDimensions.h/2;
2018-05-11 00:19:49 +02:00
arrowTop = elementDimensions.h/2 - (isPopover ? arrowHeight*0.9 : arrowHeight/2);
2018-05-07 18:20:15 +02:00
}
} else if ( position === top || position === bottom ) { // primary|vertical positions
if ( position === top) { // TOP
2018-05-11 00:19:49 +02:00
topPosition = rect[top] + scroll.y - elementDimensions.h - ( isPopover ? arrowHeight : 0 );
2018-05-07 18:20:15 +02:00
} else { // BOTTOM
topPosition = rect[top] + scroll.y + linkDimensions.h;
}
// adjust left | right and also the arrow
if (halfLeftExceed) {
leftPosition = 0;
2018-05-11 00:19:49 +02:00
arrowLeft = rect[left] + linkDimensions.w/2 - arrowWidth;
2018-05-07 18:20:15 +02:00
} else if (halfRightExceed) {
leftPosition = windowWidth - elementDimensions.w*1.01;
2018-05-11 00:19:49 +02:00
arrowLeft = elementDimensions.w - ( windowWidth - rect[left] ) + linkDimensions.w/2 - arrowWidth/2;
2018-05-07 18:20:15 +02:00
} else {
leftPosition = rect[left] + scroll.x - elementDimensions.w/2 + linkDimensions.w/2;
2018-05-11 00:19:49 +02:00
arrowLeft = elementDimensions.w/2 - arrowWidth/2;
2018-05-07 18:20:15 +02:00
}
}
2018-05-11 00:19:49 +02:00
// apply style to tooltip/popover and its arrow
2018-05-07 18:20:15 +02:00
element[style][top] = topPosition + 'px';
element[style][left] = leftPosition + 'px';
arrowTop && (arrow[style][top] = arrowTop + 'px');
arrowLeft && (arrow[style][left] = arrowLeft + 'px');
};
2018-05-11 00:19:49 +02:00
BSN.version = '2.0.22';
2018-05-07 18:20:15 +02:00
/* Native Javascript for Bootstrap 4 | Alert
-------------------------------------------*/
// ALERT DEFINITION
// ================
var Alert = function( element ) {
// initialization element
element = queryElement(element);
// bind, target alert, duration and stuff
var self = this, component = 'alert',
alert = getClosest(element,'.'+component),
triggerHandler = function(){ hasClass(alert,'fade') ? emulateTransitionEnd(alert,transitionEndHandler) : transitionEndHandler(); },
// handlers
clickHandler = function(e){
alert = getClosest(e[target],'.'+component);
element = queryElement('['+dataDismiss+'="'+component+'"]',alert);
element && alert && (element === e[target] || element[contains](e[target])) && self.close();
},
transitionEndHandler = function(){
bootstrapCustomEvent.call(alert, closedEvent, component);
off(element, clickEvent, clickHandler); // detach it's listener
alert[parentNode].removeChild(alert);
};
// public method
this.close = function() {
if ( alert && element && hasClass(alert,showClass) ) {
bootstrapCustomEvent.call(alert, closeEvent, component);
removeClass(alert,showClass);
alert && triggerHandler();
}
};
// init
if ( !(stringAlert in element ) ) { // prevent adding event handlers twice
on(element, clickEvent, clickHandler);
}
element[stringAlert] = self;
};
// ALERT DATA API
// ==============
supports[push]([stringAlert, Alert, '['+dataDismiss+'="alert"]']);
/* Native Javascript for Bootstrap 4 | Button
---------------------------------------------*/
// BUTTON DEFINITION
// ===================
var Button = function( element ) {
// initialization element
element = queryElement(element);
// constant
var toggled = false, // toggled makes sure to prevent triggering twice the change.bs.button events
// strings
component = 'button',
checked = 'checked',
reset = 'reset',
LABEL = 'LABEL',
INPUT = 'INPUT',
// private methods
keyHandler = function(e){
var key = e.which || e.keyCode;
key === 32 && e[target] === DOC.activeElement && toggle(e);
},
preventScroll = function(e){
var key = e.which || e.keyCode;
key === 32 && e[preventDefault]();
},
toggle = function(e) {
var label = e[target].tagName === LABEL ? e[target] : e[target][parentNode].tagName === LABEL ? e[target][parentNode] : null; // the .btn label
if ( !label ) return; //react if a label or its immediate child is clicked
var eventTarget = e[target], // the button itself, the target of the handler function
labels = getElementsByClassName(eventTarget[parentNode],'btn'), // all the button group buttons
input = label[getElementsByTagName](INPUT)[0];
if ( !input ) return; //return if no input found
// manage the dom manipulation
if ( input.type === 'checkbox' ) { //checkboxes
if ( !input[checked] ) {
addClass(label,active);
input[getAttribute](checked);
input[setAttribute](checked,checked);
input[checked] = true;
} else {
2018-05-07 18:20:15 +02:00
removeClass(label,active);
input[getAttribute](checked);
input.removeAttribute(checked);
input[checked] = false;
}
2018-05-07 18:20:15 +02:00
if (!toggled) { // prevent triggering the event twice
toggled = true;
bootstrapCustomEvent.call(input, changeEvent, component); //trigger the change for the input
bootstrapCustomEvent.call(element, changeEvent, component); //trigger the change for the btn-group
}
2018-05-07 18:20:15 +02:00
}
if ( input.type === 'radio' && !toggled ) { // radio buttons
if ( !input[checked] ) { // don't trigger if already active
addClass(label,active);
input[setAttribute](checked,checked);
input[checked] = true;
bootstrapCustomEvent.call(input, changeEvent, component); //trigger the change for the input
bootstrapCustomEvent.call(element, changeEvent, component); //trigger the change for the btn-group
toggled = true;
for (var i = 0, ll = labels[length]; i<ll; i++) {
var otherLabel = labels[i], otherInput = otherLabel[getElementsByTagName](INPUT)[0];
if ( otherLabel !== label && hasClass(otherLabel,active) ) {
removeClass(otherLabel,active);
otherInput.removeAttribute(checked);
otherInput[checked] = false;
bootstrapCustomEvent.call(otherInput, changeEvent, component); // trigger the change
}
}
}
2018-05-07 18:20:15 +02:00
}
setTimeout( function() { toggled = false; }, 50 );
};
// init
if ( !( stringButton in element ) ) { // prevent adding event handlers twice
on( element, clickEvent, toggle );
queryElement('['+tabindex+']',element) && on( element, keyupEvent, keyHandler ),
on( element, keydownEvent, preventScroll );
}
// activate items on load
var labelsToACtivate = getElementsByClassName(element, 'btn'), lbll = labelsToACtivate[length];
for (var i=0; i<lbll; i++) {
!hasClass(labelsToACtivate[i],active) && queryElement('input:checked',labelsToACtivate[i])
&& addClass(labelsToACtivate[i],active);
}
element[stringButton] = this;
};
// BUTTON DATA API
// =================
supports[push]( [ stringButton, Button, '['+dataToggle+'="buttons"]' ] );
/* Native Javascript for Bootstrap 4 | Carousel
----------------------------------------------*/
// CAROUSEL DEFINITION
// ===================
var Carousel = function( element, options ) {
// initialization element
element = queryElement( element );
// set options
options = options || {};
// DATA API
var intervalAttribute = element[getAttribute](dataInterval),
intervalOption = options[interval],
intervalData = intervalAttribute === 'false' ? 0 : parseInt(intervalAttribute) || 5000, // bootstrap carousel default interval
pauseData = element[getAttribute](dataPause) === hoverEvent || false,
keyboardData = element[getAttribute](dataKeyboard) === 'true' || false,
// strings
component = 'carousel',
paused = 'paused',
direction = 'direction',
carouselItem = 'carousel-item',
dataSlideTo = 'data-slide-to';
this[keyboard] = options[keyboard] === true || keyboardData;
this[pause] = (options[pause] === hoverEvent || pauseData) ? hoverEvent : false; // false / hover
this[interval] = typeof intervalOption === 'number' ? intervalOption
: intervalData === 0 ? 0
: intervalData;
// bind, event targets
var self = this, index = element.index = 0, timer = element.timer = 0,
isSliding = false, // isSliding prevents click event handlers when animation is running
slides = getElementsByClassName(element,carouselItem), total = slides[length],
slideDirection = this[direction] = left,
leftArrow = getElementsByClassName(element,component+'-control-prev')[0],
rightArrow = getElementsByClassName(element,component+'-control-next')[0],
indicator = queryElement( '.'+component+'-indicators', element ),
indicators = indicator && indicator[getElementsByTagName]( "LI" ) || [];
// handlers
var pauseHandler = function () {
if ( self[interval] !==false && !hasClass(element,paused) ) {
addClass(element,paused);
!isSliding && clearInterval( timer );
}
},
resumeHandler = function() {
if ( self[interval] !== false && hasClass(element,paused) ) {
removeClass(element,paused);
!isSliding && clearInterval( timer );
!isSliding && self.cycle();
}
},
indicatorHandler = function(e) {
e[preventDefault]();
if (isSliding) return;
var eventTarget = e[target]; // event target | the current active item
if ( eventTarget && !hasClass(eventTarget,active) && eventTarget[getAttribute](dataSlideTo) ) {
index = parseInt( eventTarget[getAttribute](dataSlideTo), 10 );
} else { return false; }
self.slideTo( index ); //Do the slide
},
controlsHandler = function (e) {
e[preventDefault]();
if (isSliding) return;
var eventTarget = e.currentTarget || e.srcElement;
if ( eventTarget === rightArrow ) {
index++;
} else if ( eventTarget === leftArrow ) {
index--;
}
self.slideTo( index ); //Do the slide
},
keyHandler = function (e) {
if (isSliding) return;
switch (e.which) {
case 39:
index++;
break;
case 37:
index--;
break;
default: return;
}
self.slideTo( index ); //Do the slide
},
// private methods
isElementInScrollRange = function () {
var rect = element[getBoundingClientRect](),
viewportHeight = globalObject[innerHeight] || HTML[clientHeight]
return rect[top] <= viewportHeight && rect[bottom] >= 0; // bottom && top
},
setActivePage = function( pageIndex ) { //indicators
for ( var i = 0, icl = indicators[length]; i < icl; i++ ) {
removeClass(indicators[i],active);
}
if (indicators[pageIndex]) addClass(indicators[pageIndex], active);
};
// public methods
this.cycle = function() {
timer = setInterval(function() {
isElementInScrollRange() && (index++, self.slideTo( index ) );
}, this[interval]);
};
this.slideTo = function( next ) {
if (isSliding) return; // when controled via methods, make sure to check again
var activeItem = this.getActiveIndex(), // the current active
orientation;
// determine slideDirection first
if ( (activeItem < next ) || (activeItem === 0 && next === total -1 ) ) {
slideDirection = self[direction] = left; // next
} else if ( (activeItem > next) || (activeItem === total - 1 && next === 0 ) ) {
slideDirection = self[direction] = right; // prev
}
// find the right next index
if ( next < 0 ) { next = total - 1; }
else if ( next === total ){ next = 0; }
// update index
index = next;
orientation = slideDirection === left ? 'next' : 'prev'; //determine type
bootstrapCustomEvent.call(element, slideEvent, component, slides[next]); // here we go with the slide
isSliding = true;
clearInterval(timer);
setActivePage( next );
if ( supportTransitions && hasClass(element,'slide') ) {
addClass(slides[next],carouselItem +'-'+ orientation);
slides[next][offsetWidth];
addClass(slides[next],carouselItem +'-'+ slideDirection);
addClass(slides[activeItem],carouselItem +'-'+ slideDirection);
one(slides[activeItem], transitionEndEvent, function(e) {
var timeout = e[target] !== slides[activeItem] ? e.elapsedTime*1000 : 0;
setTimeout(function(){
isSliding = false;
addClass(slides[next],active);
removeClass(slides[activeItem],active);
removeClass(slides[next],carouselItem +'-'+ orientation);
removeClass(slides[next],carouselItem +'-'+ slideDirection);
removeClass(slides[activeItem],carouselItem +'-'+ slideDirection);
bootstrapCustomEvent.call(element, slidEvent, component, slides[next]);
if ( !DOC.hidden && self[interval] && !hasClass(element,paused) ) {
self.cycle();
}
},timeout+100);
});
} else {
addClass(slides[next],active);
slides[next][offsetWidth];
removeClass(slides[activeItem],active);
setTimeout(function() {
isSliding = false;
if ( self[interval] && !hasClass(element,paused) ) {
self.cycle();
}
2018-05-07 18:20:15 +02:00
bootstrapCustomEvent.call(element, slidEvent, component, slides[next]);
}, 100 );
}
};
this.getActiveIndex = function () {
return slides[indexOf](getElementsByClassName(element,carouselItem+' active')[0]) || 0;
};
// init
if ( !(stringCarousel in element ) ) { // prevent adding event handlers twice
if ( self[pause] && self[interval] ) {
on( element, mouseHover[0], pauseHandler );
on( element, mouseHover[1], resumeHandler );
on( element, 'touchstart', pauseHandler );
on( element, 'touchend', resumeHandler );
}
rightArrow && on( rightArrow, clickEvent, controlsHandler );
leftArrow && on( leftArrow, clickEvent, controlsHandler );
indicator && on( indicator, clickEvent, indicatorHandler );
self[keyboard] === true && on( globalObject, keydownEvent, keyHandler );
}
if (self.getActiveIndex()<0) {
slides[length] && addClass(slides[0],active);
indicators[length] && setActivePage(0);
}
if ( self[interval] ){ self.cycle(); }
element[stringCarousel] = self;
};
// CAROUSEL DATA API
// =================
supports[push]( [ stringCarousel, Carousel, '['+dataRide+'="carousel"]' ] );
/* Native Javascript for Bootstrap 4 | Collapse
-----------------------------------------------*/
// COLLAPSE DEFINITION
// ===================
var Collapse = function( element, options ) {
// initialization element
element = queryElement(element);
// set options
options = options || {};
// event targets and constants
var accordion = null, collapse = null, self = this,
isAnimating = false, // when true it will prevent click handlers
accordionData = element[getAttribute]('data-parent'),
// component strings
component = 'collapse',
collapsed = 'collapsed',
// private methods
2018-05-11 00:19:49 +02:00
openAction = function(collapseElement,toggle) {
2018-05-07 18:20:15 +02:00
bootstrapCustomEvent.call(collapseElement, showEvent, component);
isAnimating = true;
addClass(collapseElement,collapsing);
removeClass(collapseElement,component);
collapseElement[style][height] = collapseElement[scrollHeight] + 'px';
emulateTransitionEnd(collapseElement, function() {
isAnimating = false;
collapseElement[setAttribute](ariaExpanded,'true');
2018-05-11 00:19:49 +02:00
toggle[setAttribute](ariaExpanded,'true');
2018-05-07 18:20:15 +02:00
removeClass(collapseElement,collapsing);
addClass(collapseElement, component);
addClass(collapseElement,showClass);
collapseElement[style][height] = '';
bootstrapCustomEvent.call(collapseElement, shownEvent, component);
});
},
2018-05-11 00:19:49 +02:00
closeAction = function(collapseElement,toggle) {
2018-05-07 18:20:15 +02:00
bootstrapCustomEvent.call(collapseElement, hideEvent, component);
isAnimating = true;
collapseElement[style][height] = collapseElement[scrollHeight] + 'px'; // set height first
removeClass(collapseElement,component);
removeClass(collapseElement,showClass);
addClass(collapseElement,collapsing);
collapseElement[offsetWidth]; // force reflow to enable transition
collapseElement[style][height] = '0px';
emulateTransitionEnd(collapseElement, function() {
isAnimating = false;
collapseElement[setAttribute](ariaExpanded,'false');
2018-05-11 00:19:49 +02:00
toggle[setAttribute](ariaExpanded,'false');
2018-05-07 18:20:15 +02:00
removeClass(collapseElement,collapsing);
addClass(collapseElement,component);
collapseElement[style][height] = '';
bootstrapCustomEvent.call(collapseElement, hiddenEvent, component);
});
},
getTarget = function() {
var href = element.href && element[getAttribute]('href'),
parent = element[getAttribute](dataTarget),
id = href || ( parent && parent.charAt(0) === '#' ) && parent;
return id && queryElement(id);
};
// public methods
this.toggle = function(e) {
e[preventDefault]();
if (isAnimating) return;
if (!hasClass(collapse,showClass)) { self.show(); }
else { self.hide(); }
};
this.hide = function() {
2018-05-11 00:19:49 +02:00
closeAction(collapse,element);
2018-05-07 18:20:15 +02:00
addClass(element,collapsed);
};
this.show = function() {
if ( accordion ) {
var activeCollapse = queryElement('.'+component+'.'+showClass,accordion),
toggle = activeCollapse && (queryElement('['+dataToggle+'="'+component+'"]['+dataTarget+'="#'+activeCollapse.id+'"]',accordion)
|| queryElement('['+dataToggle+'="'+component+'"][href="#'+activeCollapse.id+'"]',accordion) ),
correspondingCollapse = toggle && (toggle[getAttribute](dataTarget) || toggle.href);
if ( activeCollapse && toggle && activeCollapse !== collapse ) {
2018-05-11 00:19:49 +02:00
closeAction(activeCollapse,toggle);
2018-05-07 18:20:15 +02:00
if ( correspondingCollapse.split('#')[1] !== collapse.id ) { addClass(toggle,collapsed); }
else { removeClass(toggle,collapsed); }
}
}
2018-05-11 00:19:49 +02:00
openAction(collapse,element);
removeClass(element,collapsed);
2018-05-07 18:20:15 +02:00
};
// init
if ( !(stringCollapse in element ) ) { // prevent adding event handlers twice
on(element, clickEvent, self.toggle);
}
collapse = getTarget();
accordion = queryElement(options.parent) || accordionData && getClosest(element, accordionData);
element[stringCollapse] = self;
};
// COLLAPSE DATA API
// =================
supports[push]( [ stringCollapse, Collapse, '['+dataToggle+'="collapse"]' ] );
/* Native Javascript for Bootstrap 4 | Dropdown
----------------------------------------------*/
// DROPDOWN DEFINITION
// ===================
var Dropdown = function( element, option ) {
// initialization element
element = queryElement(element);
// set option
this.persist = option === true || element[getAttribute]('data-persist') === 'true' || false;
// constants, event targets, strings
var self = this, children = 'children',
parent = element[parentNode],
component = 'dropdown', open = 'open',
relatedTarget = null,
menu = queryElement('.dropdown-menu', parent),
menuItems = (function(){
var set = menu[children], newSet = [];
for ( var i=0; i<set[length]; i++ ){
set[i][children][length] && (set[i][children][0].tagName === 'A' && newSet[push](set[i][children][0]));
set[i].tagName === 'A' && newSet[push](set[i]);
}
return newSet;
})(),
// preventDefault on empty anchor links
preventEmptyAnchor = function(anchor){
(anchor.href && anchor.href.slice(-1) === '#' || anchor[parentNode] && anchor[parentNode].href
&& anchor[parentNode].href.slice(-1) === '#') && this[preventDefault]();
},
// toggle dismissible events
toggleDismiss = function(){
var type = element[open] ? on : off;
type(DOC, clickEvent, dismissHandler);
type(DOC, keydownEvent, preventScroll);
type(DOC, keyupEvent, keyHandler);
},
// handlers
dismissHandler = function(e) {
var eventTarget = e[target], hasData = eventTarget && (stringDropdown in eventTarget || stringDropdown in eventTarget[parentNode]);
if ( (eventTarget === menu || menu[contains](eventTarget)) && (self.persist || hasData) ) { return; }
else {
relatedTarget = eventTarget === element || element[contains](eventTarget) ? element : null;
hide();
}
preventEmptyAnchor.call(e,eventTarget);
},
clickHandler = function(e) {
relatedTarget = element;
show();
preventEmptyAnchor.call(e,e[target]);
},
preventScroll = function(e){
var key = e.which || e.keyCode;
if( key === 38 || key === 40 ) { e[preventDefault](); }
},
keyHandler = function(e){
var key = e.which || e.keyCode,
activeItem = DOC.activeElement,
idx = menuItems[indexOf](activeItem),
isSameElement = activeItem === element,
isInsideMenu = menu[contains](activeItem),
isMenuItem = activeItem[parentNode] === menu || activeItem[parentNode][parentNode] === menu;
if ( isMenuItem || isSameElement ) { // navigate up | down
idx = isSameElement ? 0
: key === 38 ? (idx>1?idx-1:0)
: key === 40 ? (idx<menuItems[length]-1?idx+1:idx) : idx;
menuItems[idx] && setFocus(menuItems[idx]);
}
if ( (menuItems[length] && isMenuItem // menu has items
|| !menuItems[length] && (isInsideMenu || isSameElement) // menu might be a form
|| !isInsideMenu ) // or the focused element is not in the menu at all
&& element[open] && key === 27 // menu must be open
) {
self.toggle();
relatedTarget = null;
}
},
// private methods
show = function() {
bootstrapCustomEvent.call(parent, showEvent, component, relatedTarget);
addClass(menu,showClass);
addClass(parent,showClass);
menu[setAttribute](ariaExpanded,true);
bootstrapCustomEvent.call(parent, shownEvent, component, relatedTarget);
element[open] = true;
off(element, clickEvent, clickHandler);
setTimeout(function(){
setFocus( menu[getElementsByTagName]('INPUT')[0] || element ); // focus the first input item | element
toggleDismiss();
},1);
},
hide = function() {
bootstrapCustomEvent.call(parent, hideEvent, component, relatedTarget);
removeClass(menu,showClass);
removeClass(parent,showClass);
menu[setAttribute](ariaExpanded,false);
bootstrapCustomEvent.call(parent, hiddenEvent, component, relatedTarget);
element[open] = false;
toggleDismiss();
setFocus(element);
setTimeout(function(){ on(element, clickEvent, clickHandler); },1);
};
// set initial state to closed
element[open] = false;
// public methods
this.toggle = function() {
if (hasClass(parent,showClass) && element[open]) { hide(); }
else { show(); }
};
// init
if ( !(stringDropdown in element) ) { // prevent adding event handlers twice
!tabindex in menu && menu[setAttribute](tabindex, '0'); // Fix onblur on Chrome | Safari
on(element, clickEvent, clickHandler);
}
element[stringDropdown] = self;
};
// DROPDOWN DATA API
// =================
supports[push]( [stringDropdown, Dropdown, '['+dataToggle+'="dropdown"]'] );
/* Native Javascript for Bootstrap 4 | Modal
-------------------------------------------*/
// MODAL DEFINITION
// ===============
var Modal = function(element, options) { // element can be the modal/triggering button
// the modal (both JavaScript / DATA API init) / triggering button element (DATA API)
element = queryElement(element);
// determine modal, triggering element
var btnCheck = element[getAttribute](dataTarget)||element[getAttribute]('href'),
checkModal = queryElement( btnCheck ),
modal = hasClass(element,'modal') ? element : checkModal,
// strings
component = 'modal',
staticString = 'static',
paddingLeft = 'paddingLeft',
paddingRight = 'paddingRight',
modalBackdropString = 'modal-backdrop';
if ( hasClass(element,'modal') ) { element = null; } // modal is now independent of it's triggering element
if ( !modal ) { return; } // invalidate
// set options
options = options || {};
this[keyboard] = options[keyboard] === false || modal[getAttribute](dataKeyboard) === 'false' ? false : true;
this[backdrop] = options[backdrop] === staticString || modal[getAttribute](databackdrop) === staticString ? staticString : true;
this[backdrop] = options[backdrop] === false || modal[getAttribute](databackdrop) === 'false' ? false : this[backdrop];
this[content] = options[content]; // JavaScript only
// bind, constants, event targets and other vars
var self = this, relatedTarget = null,
bodyIsOverflowing, modalIsOverflowing, scrollbarWidth, overlay,
// also find fixed-top / fixed-bottom items
fixedItems = getElementsByClassName(HTML,fixedTop).concat(getElementsByClassName(HTML,fixedBottom)),
// private methods
getWindowWidth = function() {
var htmlRect = HTML[getBoundingClientRect]();
return globalObject[innerWidth] || (htmlRect[right] - Math.abs(htmlRect[left]));
},
setScrollbar = function () {
var bodyStyle = globalObject.getComputedStyle(DOC[body]),
bodyPad = parseInt((bodyStyle[paddingRight]), 10), itemPad;
if (bodyIsOverflowing) {
DOC[body][style][paddingRight] = (bodyPad + scrollbarWidth) + 'px';
if (fixedItems[length]){
for (var i = 0; i < fixedItems[length]; i++) {
itemPad = globalObject.getComputedStyle(fixedItems[i])[paddingRight];
fixedItems[i][style][paddingRight] = ( parseInt(itemPad) + scrollbarWidth) + 'px';
}
}
2018-05-07 18:20:15 +02:00
}
},
resetScrollbar = function () {
DOC[body][style][paddingRight] = '';
if (fixedItems[length]){
for (var i = 0; i < fixedItems[length]; i++) {
fixedItems[i][style][paddingRight] = '';
}
2018-05-07 18:20:15 +02:00
}
},
measureScrollbar = function () { // thx walsh
var scrollDiv = DOC[createElement]('div'), scrollBarWidth;
scrollDiv.className = component+'-scrollbar-measure'; // this is here to stay
DOC[body][appendChild](scrollDiv);
scrollBarWidth = scrollDiv[offsetWidth] - scrollDiv[clientWidth];
DOC[body].removeChild(scrollDiv);
return scrollBarWidth;
},
checkScrollbar = function () {
bodyIsOverflowing = DOC[body][clientWidth] < getWindowWidth();
modalIsOverflowing = modal[scrollHeight] > HTML[clientHeight];
scrollbarWidth = measureScrollbar();
},
adjustDialog = function () {
modal[style][paddingLeft] = !bodyIsOverflowing && modalIsOverflowing ? scrollbarWidth + 'px' : '';
modal[style][paddingRight] = bodyIsOverflowing && !modalIsOverflowing ? scrollbarWidth + 'px' : '';
},
resetAdjustments = function () {
modal[style][paddingLeft] = '';
modal[style][paddingRight] = '';
},
createOverlay = function() {
modalOverlay = 1;
var newOverlay = DOC[createElement]('div');
overlay = queryElement('.'+modalBackdropString);
if ( overlay === null ) {
newOverlay[setAttribute]('class',modalBackdropString+' fade');
overlay = newOverlay;
DOC[body][appendChild](overlay);
}
},
removeOverlay = function() {
overlay = queryElement('.'+modalBackdropString);
if ( overlay && overlay !== null && typeof overlay === 'object' ) {
modalOverlay = 0;
DOC[body].removeChild(overlay); overlay = null;
}
bootstrapCustomEvent.call(modal, hiddenEvent, component);
},
keydownHandlerToggle = function() {
if (hasClass(modal,showClass)) {
on(DOC, keydownEvent, keyHandler);
} else {
off(DOC, keydownEvent, keyHandler);
}
},
resizeHandlerToggle = function() {
if (hasClass(modal,showClass)) {
on(globalObject, resizeEvent, self.update);
} else {
off(globalObject, resizeEvent, self.update);
}
},
dismissHandlerToggle = function() {
if (hasClass(modal,showClass)) {
on(modal, clickEvent, dismissHandler);
} else {
off(modal, clickEvent, dismissHandler);
}
},
// triggers
triggerShow = function() {
setFocus(modal);
bootstrapCustomEvent.call(modal, shownEvent, component, relatedTarget);
},
triggerHide = function() {
modal[style].display = '';
element && (setFocus(element));
2018-05-11 00:19:49 +02:00
(function(){
2018-05-07 18:20:15 +02:00
if (!getElementsByClassName(DOC,component+' '+showClass)[0]) {
resetAdjustments();
resetScrollbar();
removeClass(DOC[body],component+'-open');
overlay && hasClass(overlay,'fade') ? (removeClass(overlay,showClass), emulateTransitionEnd(overlay,removeOverlay))
: removeOverlay();
resizeHandlerToggle();
dismissHandlerToggle();
keydownHandlerToggle();
}
2018-05-11 00:19:49 +02:00
}());
2018-05-07 18:20:15 +02:00
},
// handlers
clickHandler = function(e) {
var clickTarget = e[target];
clickTarget = clickTarget[hasAttribute](dataTarget) || clickTarget[hasAttribute]('href') ? clickTarget : clickTarget[parentNode];
if ( clickTarget === element && !hasClass(modal,showClass) ) {
modal.modalTrigger = element;
relatedTarget = element;
self.show();
e[preventDefault]();
}
},
keyHandler = function(e) {
if (self[keyboard] && e.which == 27 && hasClass(modal,showClass)) {
self.hide();
}
},
dismissHandler = function(e) {
var clickTarget = e[target];
if ( hasClass(modal,showClass) && (clickTarget[parentNode][getAttribute](dataDismiss) === component
|| clickTarget[getAttribute](dataDismiss) === component
|| (clickTarget === modal && self[backdrop] !== staticString) ) ) {
self.hide(); relatedTarget = null;
e[preventDefault]();
}
};
// public methods
this.toggle = function() {
if ( hasClass(modal,showClass) ) {this.hide();} else {this.show();}
};
this.show = function() {
bootstrapCustomEvent.call(modal, showEvent, component, relatedTarget);
// we elegantly hide any opened modal
var currentOpen = getElementsByClassName(DOC,component+' '+showClass)[0];
currentOpen && currentOpen !== modal && currentOpen.modalTrigger[stringModal].hide();
if ( this[backdrop] ) {
!modalOverlay && createOverlay();
}
if ( overlay && modalOverlay && !hasClass(overlay,showClass)) {
overlay[offsetWidth]; // force reflow to enable trasition
addClass(overlay, showClass);
}
setTimeout( function() {
modal[style].display = 'block';
checkScrollbar();
setScrollbar();
adjustDialog();
addClass(DOC[body],component+'-open');
addClass(modal,showClass);
modal[setAttribute](ariaHidden, false);
resizeHandlerToggle();
dismissHandlerToggle();
keydownHandlerToggle();
hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerShow) : triggerShow();
}, supportTransitions ? 150 : 0);
};
this.hide = function() {
bootstrapCustomEvent.call(modal, hideEvent, component);
overlay = queryElement('.'+modalBackdropString);
removeClass(modal,showClass);
modal[setAttribute](ariaHidden, true);
2018-05-11 00:19:49 +02:00
(function(){
2018-05-07 18:20:15 +02:00
hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerHide) : triggerHide();
2018-05-11 00:19:49 +02:00
}());
2018-05-07 18:20:15 +02:00
};
this.setContent = function( content ) {
queryElement('.'+component+'-content',modal)[innerHTML] = content;
};
this.update = function() {
if (hasClass(modal,showClass)) {
checkScrollbar();
setScrollbar();
adjustDialog();
}
};
// init
// prevent adding event handlers over and over
// modal is independent of a triggering element
if ( !!element && !(stringModal in element) ) {
on(element, clickEvent, clickHandler);
}
if ( !!self[content] ) { self.setContent( self[content] ); }
!!element && (element[stringModal] = self);
};
// DATA API
supports[push]( [ stringModal, Modal, '['+dataToggle+'="modal"]' ] );
/* Native Javascript for Bootstrap 4 | Popover
----------------------------------------------*/
// POPOVER DEFINITION
// ==================
var Popover = function( element, options ) {
// initialization element
element = queryElement(element);
// set options
options = options || {};
// DATA API
var triggerData = element[getAttribute](dataTrigger), // click / hover / focus
animationData = element[getAttribute](dataAnimation), // true / false
placementData = element[getAttribute](dataPlacement),
dismissibleData = element[getAttribute](dataDismissible),
delayData = element[getAttribute](dataDelay),
containerData = element[getAttribute](dataContainer),
// internal strings
component = 'popover',
template = 'template',
trigger = 'trigger',
classString = 'class',
div = 'div',
fade = 'fade',
content = 'content',
dataContent = 'data-content',
dismissible = 'dismissible',
closeBtn = '<button type="button" class="close">×</button>',
// check container
containerElement = queryElement(options[container]),
containerDataElement = queryElement(containerData),
// maybe the element is inside a modal
modal = getClosest(element,'.modal'),
// maybe the element is inside a fixed navbar
navbarFixedTop = getClosest(element,'.'+fixedTop),
navbarFixedBottom = getClosest(element,'.'+fixedBottom);
// set instance options
this[template] = options[template] ? options[template] : null; // JavaScript only
this[trigger] = options[trigger] ? options[trigger] : triggerData || hoverEvent;
this[animation] = options[animation] && options[animation] !== fade ? options[animation] : animationData || fade;
this[placement] = options[placement] ? options[placement] : placementData || top;
this[delay] = parseInt(options[delay] || delayData) || 200;
this[dismissible] = options[dismissible] || dismissibleData === 'true' ? true : false;
this[container] = containerElement ? containerElement
: containerDataElement ? containerDataElement
: navbarFixedTop ? navbarFixedTop
: navbarFixedBottom ? navbarFixedBottom
: modal ? modal : DOC[body];
// bind, content
var self = this,
titleString = element[getAttribute](dataTitle) || null,
contentString = element[getAttribute](dataContent) || null;
if ( !contentString && !this[template] ) return; // invalidate
// constants, vars
var popover = null, timer = 0, placementSetting = this[placement],
// handlers
dismissibleHandler = function(e) {
if (popover !== null && e[target] === queryElement('.close',popover)) {
self.hide();
}
2018-05-07 18:20:15 +02:00
},
// private methods
removePopover = function() {
self[container].removeChild(popover);
timer = null; popover = null;
},
createPopover = function() {
titleString = element[getAttribute](dataTitle); // check content again
contentString = element[getAttribute](dataContent);
popover = DOC[createElement](div);
// popover arrow
var popoverArrow = DOC[createElement](div);
popoverArrow[setAttribute](classString,'arrow');
popover[appendChild](popoverArrow);
if ( contentString !== null && self[template] === null ) { //create the popover from data attributes
popover[setAttribute]('role','tooltip');
if (titleString !== null) {
var popoverTitle = DOC[createElement]('h3');
popoverTitle[setAttribute](classString,component+'-header');
popoverTitle[innerHTML] = self[dismissible] ? titleString + closeBtn : titleString;
popover[appendChild](popoverTitle);
}
2018-05-07 18:20:15 +02:00
//set popover content
var popoverContent = DOC[createElement](div);
popoverContent[setAttribute](classString,component+'-body');
popoverContent[innerHTML] = self[dismissible] && titleString === null ? contentString + closeBtn : contentString;
popover[appendChild](popoverContent);
} else { // or create the popover from template
var popoverTemplate = DOC[createElement](div);
popoverTemplate[innerHTML] = self[template];
popover[innerHTML] = popoverTemplate.firstChild[innerHTML];
}
2018-05-07 18:20:15 +02:00
//append to the container
self[container][appendChild](popover);
popover[style].display = 'block';
popover[setAttribute](classString, component+ ' bs-' + component+'-'+placementSetting + ' ' + self[animation]);
},
showPopover = function () {
!hasClass(popover,showClass) && ( addClass(popover,showClass) );
},
updatePopover = function() {
styleTip(element,popover,placementSetting,self[container]);
},
// event toggle
dismissHandlerToggle = function(type){
if (clickEvent == self[trigger] || 'focus' == self[trigger]) {
!self[dismissible] && type( element, 'blur', self.hide );
}
self[dismissible] && type( DOC, clickEvent, dismissibleHandler );
type( globalObject, resizeEvent, self.hide );
},
// triggers
showTrigger = function() {
dismissHandlerToggle(on);
bootstrapCustomEvent.call(element, shownEvent, component);
},
hideTrigger = function() {
dismissHandlerToggle(off);
removePopover();
bootstrapCustomEvent.call(element, hiddenEvent, component);
};
// public methods / handlers
this.toggle = function() {
if (popover === null) { self.show(); }
else { self.hide(); }
};
this.show = function() {
clearTimeout(timer);
timer = setTimeout( function() {
if (popover === null) {
placementSetting = self[placement]; // we reset placement in all cases
createPopover();
updatePopover();
showPopover();
bootstrapCustomEvent.call(element, showEvent, component);
!!self[animation] ? emulateTransitionEnd(popover, showTrigger) : showTrigger();
}
}, 20 );
};
this.hide = function() {
clearTimeout(timer);
timer = setTimeout( function() {
if (popover && popover !== null && hasClass(popover,showClass)) {
bootstrapCustomEvent.call(element, hideEvent, component);
removeClass(popover,showClass);
!!self[animation] ? emulateTransitionEnd(popover, hideTrigger) : hideTrigger();
}
}, self[delay] );
};
// init
if ( !(stringPopover in element) ) { // prevent adding event handlers twice
if (self[trigger] === hoverEvent) {
on( element, mouseHover[0], self.show );
if (!self[dismissible]) { on( element, mouseHover[1], self.hide ); }
} else if (clickEvent == self[trigger] || 'focus' == self[trigger]) {
on( element, self[trigger], self.toggle );
}
}
element[stringPopover] = self;
};
// POPOVER DATA API
// ================
supports[push]( [ stringPopover, Popover, '['+dataToggle+'="popover"]' ] );
/* Native Javascript for Bootstrap 4 | ScrollSpy
-----------------------------------------------*/
// SCROLLSPY DEFINITION
// ====================
var ScrollSpy = function(element, options) {
// initialization element, the element we spy on
element = queryElement(element);
// DATA API
var targetData = queryElement(element[getAttribute](dataTarget)),
offsetData = element[getAttribute]('data-offset');
// set options
options = options || {};
if ( !options[target] && !targetData ) { return; } // invalidate
// event targets, constants
var self = this, spyTarget = options[target] && queryElement(options[target]) || targetData,
links = spyTarget && spyTarget[getElementsByTagName]('A'),
offset = parseInt(offsetData || options['offset']) || 10,
items = [], targetItems = [], scrollOffset,
scrollTarget = element[offsetHeight] < element[scrollHeight] ? element : globalObject, // determine which is the real scrollTarget
isWindow = scrollTarget === globalObject;
// populate items and targets
for (var i=0, il=links[length]; i<il; i++) {
var href = links[i][getAttribute]('href'),
targetItem = href && href.charAt(0) === '#' && href.slice(-1) !== '#' && queryElement(href);
if ( !!targetItem ) {
items[push](links[i]);
targetItems[push](targetItem);
}
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
// private methods
var updateItem = function(index) {
var item = items[index],
targetItem = targetItems[index], // the menu item targets this element
dropdown = item[parentNode][parentNode],
dropdownLink = hasClass(dropdown,'dropdown') && dropdown[getElementsByTagName]('A')[0],
targetRect = isWindow && targetItem[getBoundingClientRect](),
isActive = hasClass(item,active) || false,
topEdge = (isWindow ? targetRect[top] + scrollOffset : targetItem[offsetTop]) - offset,
bottomEdge = isWindow ? targetRect[bottom] + scrollOffset - offset : targetItems[index+1] ? targetItems[index+1][offsetTop] - offset : element[scrollHeight],
inside = scrollOffset >= topEdge && bottomEdge > scrollOffset;
if ( !isActive && inside ) {
if ( !hasClass(item,active) ) {
addClass(item,active);
if (dropdownLink && !hasClass(dropdownLink,active) ) {
addClass(dropdownLink,active);
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
bootstrapCustomEvent.call(element, 'activate', 'scrollspy', items[index]);
}
2018-05-07 18:20:15 +02:00
} else if ( !inside ) {
if ( hasClass(item,active) ) {
removeClass(item,active);
if (dropdownLink && hasClass(dropdownLink,active) && !getElementsByClassName(item[parentNode],active).length ) {
removeClass(dropdownLink,active);
2018-03-25 21:21:43 +02:00
}
}
2018-05-07 18:20:15 +02:00
} else if ( !inside && !isActive || isActive && inside ) {
return;
}
},
updateItems = function(){
scrollOffset = isWindow ? getScroll().y : element[scrollTop];
for (var index=0, itl=items[length]; index<itl; index++) {
updateItem(index)
}
};
// public method
this.refresh = function () {
updateItems();
}
// init
if ( !(stringScrollSpy in element) ) { // prevent adding event handlers twice
on( scrollTarget, scrollEvent, self.refresh );
on( globalObject, resizeEvent, self.refresh );
}
self.refresh();
element[stringScrollSpy] = self;
};
// SCROLLSPY DATA API
// ==================
supports[push]( [ stringScrollSpy, ScrollSpy, '['+dataSpy+'="scroll"]' ] );
/* Native Javascript for Bootstrap 4 | Tab
-----------------------------------------*/
// TAB DEFINITION
// ==============
var Tab = function( element, options ) {
// initialization element
element = queryElement(element);
// DATA API
var heightData = element[getAttribute](dataHeight),
// strings
component = 'tab', height = 'height', float = 'float', isAnimating = 'isAnimating';
// set options
options = options || {};
this[height] = supportTransitions ? (options[height] || heightData === 'true') : false;
// bind, event targets
var self = this, next,
tabs = getClosest(element,'.nav'),
tabsContentContainer = false,
dropdown = tabs && queryElement('.dropdown-toggle',tabs),
activeTab, activeContent, nextContent, containerHeight, equalContents, nextHeight,
// trigger
triggerEnd = function(){
tabsContentContainer[style][height] = '';
removeClass(tabsContentContainer,collapsing);
tabs[isAnimating] = false;
},
triggerShow = function() {
if (tabsContentContainer) { // height animation
if ( equalContents ) {
triggerEnd();
} else {
setTimeout(function(){ // enables height animation
tabsContentContainer[style][height] = nextHeight + 'px'; // height animation
tabsContentContainer[offsetWidth];
emulateTransitionEnd(tabsContentContainer, triggerEnd);
},1);
}
2018-05-07 18:20:15 +02:00
} else {
tabs[isAnimating] = false;
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
bootstrapCustomEvent.call(next, shownEvent, component, activeTab);
},
triggerHide = function() {
if (tabsContentContainer) {
activeContent[style][float] = left;
nextContent[style][float] = left;
containerHeight = activeContent[scrollHeight];
}
addClass(nextContent,active);
bootstrapCustomEvent.call(next, showEvent, component, activeTab);
removeClass(activeContent,active);
bootstrapCustomEvent.call(activeTab, hiddenEvent, component, next);
if (tabsContentContainer) {
nextHeight = nextContent[scrollHeight];
equalContents = nextHeight === containerHeight;
addClass(tabsContentContainer,collapsing);
tabsContentContainer[style][height] = containerHeight + 'px'; // height animation
tabsContentContainer[offsetHeight];
activeContent[style][float] = '';
nextContent[style][float] = '';
}
if ( hasClass(nextContent, 'fade') ) {
setTimeout(function(){
addClass(nextContent,showClass);
emulateTransitionEnd(nextContent,triggerShow);
},20);
} else { triggerShow(); }
};
if (!tabs) return; // invalidate
// set default animation state
tabs[isAnimating] = false;
// private methods
var getActiveTab = function() {
var activeTabs = getElementsByClassName(tabs,active), activeTab;
if ( activeTabs[length] === 1 && !hasClass(activeTabs[0][parentNode],'dropdown') ) {
activeTab = activeTabs[0];
} else if ( activeTabs[length] > 1 ) {
activeTab = activeTabs[activeTabs[length]-1];
}
return activeTab;
},
getActiveContent = function() {
return queryElement(getActiveTab()[getAttribute]('href'));
},
// handler
clickHandler = function(e) {
var href = e[target][getAttribute]('href');
e[preventDefault]();
next = e[target][getAttribute](dataToggle) === component || (href && href.charAt(0) === '#')
? e[target] : e[target][parentNode]; // allow for child elements like icons to use the handler
2018-05-11 00:19:49 +02:00
!tabs[isAnimating] && !hasClass(next,active) && self.show();
2018-05-07 18:20:15 +02:00
};
// public method
this.show = function() { // the tab we clicked is now the next tab
next = next || element;
nextContent = queryElement(next[getAttribute]('href')); //this is the actual object, the next tab content to activate
activeTab = getActiveTab();
activeContent = getActiveContent();
tabs[isAnimating] = true;
removeClass(activeTab,active);
addClass(next,active);
if ( dropdown ) {
if ( !hasClass(element[parentNode],'dropdown-menu') ) {
if (hasClass(dropdown,active)) removeClass(dropdown,active);
} else {
if (!hasClass(dropdown,active)) addClass(dropdown,active);
}
}
bootstrapCustomEvent.call(activeTab, hideEvent, component, next);
if (hasClass(activeContent, 'fade')) {
removeClass(activeContent,showClass);
emulateTransitionEnd(activeContent, triggerHide);
} else { triggerHide(); }
};
// init
if ( !(stringTab in element) ) { // prevent adding event handlers twice
on(element, clickEvent, clickHandler);
}
if (self[height]) { tabsContentContainer = getActiveContent()[parentNode]; }
element[stringTab] = self;
};
// TAB DATA API
// ============
supports[push]( [ stringTab, Tab, '['+dataToggle+'="tab"]' ] );
/* Native Javascript for Bootstrap 4 | Tooltip
---------------------------------------------*/
// TOOLTIP DEFINITION
// ==================
var Tooltip = function( element,options ) {
// initialization element
element = queryElement(element);
// set options
options = options || {};
// DATA API
var animationData = element[getAttribute](dataAnimation),
placementData = element[getAttribute](dataPlacement),
delayData = element[getAttribute](dataDelay),
containerData = element[getAttribute](dataContainer),
// strings
component = 'tooltip',
classString = 'class',
title = 'title',
fade = 'fade',
div = 'div',
// check container
containerElement = queryElement(options[container]),
containerDataElement = queryElement(containerData),
// maybe the element is inside a modal
modal = getClosest(element,'.modal'),
// maybe the element is inside a fixed navbar
navbarFixedTop = getClosest(element,'.'+fixedTop),
navbarFixedBottom = getClosest(element,'.'+fixedBottom);
// set instance options
this[animation] = options[animation] && options[animation] !== fade ? options[animation] : animationData || fade;
this[placement] = options[placement] ? options[placement] : placementData || top;
this[delay] = parseInt(options[delay] || delayData) || 200;
this[container] = containerElement ? containerElement
: containerDataElement ? containerDataElement
: navbarFixedTop ? navbarFixedTop
: navbarFixedBottom ? navbarFixedBottom
: modal ? modal : DOC[body];
// bind, event targets, title and constants
var self = this, timer = 0, placementSetting = this[placement], tooltip = null,
titleString = element[getAttribute](title) || element[getAttribute](dataTitle) || element[getAttribute](dataOriginalTitle);
if ( !titleString || titleString == "" ) return; // invalidate
// private methods
var removeToolTip = function() {
self[container].removeChild(tooltip);
tooltip = null; timer = null;
},
createToolTip = function() {
titleString = element[getAttribute](title) || element[getAttribute](dataTitle) || element[getAttribute](dataOriginalTitle); // read the title again
if ( !titleString || titleString == "" ) return false; // invalidate
tooltip = DOC[createElement](div);
tooltip[setAttribute]('role',component);
// tooltip arrow
var tooltipArrow = DOC[createElement](div);
tooltipArrow[setAttribute](classString,'arrow');
tooltip[appendChild](tooltipArrow);
var tooltipInner = DOC[createElement](div);
tooltipInner[setAttribute](classString,component+'-inner');
tooltip[appendChild](tooltipInner);
tooltipInner[innerHTML] = titleString;
self[container][appendChild](tooltip);
tooltip[setAttribute](classString, component + ' bs-' + component+'-'+placementSetting + ' ' + self[animation]);
},
updateTooltip = function () {
styleTip(element,tooltip,placementSetting,self[container]);
},
showTooltip = function () {
!hasClass(tooltip,showClass) && ( addClass(tooltip,showClass) );
},
// triggers
showTrigger = function() {
on( globalObject, resizeEvent, self.hide );
bootstrapCustomEvent.call(element, shownEvent, component);
},
hideTrigger = function() {
off( globalObject, resizeEvent, self.hide );
removeToolTip();
bootstrapCustomEvent.call(element, hiddenEvent, component);
};
// public methods
this.show = function() {
clearTimeout(timer);
timer = setTimeout( function() {
if (tooltip === null) {
placementSetting = self[placement]; // we reset placement in all cases
if(createToolTip() == false) return;
updateTooltip();
showTooltip();
bootstrapCustomEvent.call(element, showEvent, component);
!!self[animation] ? emulateTransitionEnd(tooltip, showTrigger) : showTrigger();
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
}, 20 );
};
this.hide = function() {
clearTimeout(timer);
timer = setTimeout( function() {
if (tooltip && hasClass(tooltip,showClass)) {
bootstrapCustomEvent.call(element, hideEvent, component);
removeClass(tooltip,showClass);
!!self[animation] ? emulateTransitionEnd(tooltip, hideTrigger) : hideTrigger();
}
}, self[delay]);
};
this.toggle = function() {
if (!tooltip) { self.show(); }
else { self.hide(); }
};
// init
if ( !(stringTooltip in element) ) { // prevent adding event handlers twice
element[setAttribute](dataOriginalTitle,titleString);
element.removeAttribute(title);
on(element, mouseHover[0], self.show);
on(element, mouseHover[1], self.hide);
}
element[stringTooltip] = self;
};
// TOOLTIP DATA API
// =================
supports[push]( [ stringTooltip, Tooltip, '['+dataToggle+'="tooltip"]' ] );
/* Native Javascript for Bootstrap 4 | Initialize Data API
--------------------------------------------------------*/
var initializeDataAPI = function( constructor, collection ){
for (var i=0, l=collection[length]; i<l; i++) {
new constructor(collection[i]);
}
},
initCallback = BSN.initCallback = function(lookUp){
lookUp = lookUp || DOC;
for (var i=0, l=supports[length]; i<l; i++) {
initializeDataAPI( supports[i][1], lookUp[querySelectorAll] (supports[i][2]) );
}
};
// bulk initialize all components
DOC[body] ? initCallback() : on( DOC, 'DOMContentLoaded', function(){ initCallback(); } );
return {
Alert: Alert,
Button: Button,
Carousel: Carousel,
Collapse: Collapse,
Dropdown: Dropdown,
Modal: Modal,
Popover: Popover,
ScrollSpy: ScrollSpy,
Tab: Tab,
Tooltip: Tooltip
};
}));
2018-05-07 18:20:15 +02:00
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/**
* default settings
*
* @author Zongmin Lei<leizongmin@gmail.com>
*/
2018-05-07 18:20:15 +02:00
var FilterCSS = require("cssfilter").FilterCSS;
var getDefaultCSSWhiteList = require("cssfilter").getDefaultWhiteList;
var _ = require("./util");
2018-05-07 18:20:15 +02:00
function getDefaultWhiteList() {
return {
a: ["target", "href", "title"],
abbr: ["title"],
address: [],
area: ["shape", "coords", "href", "alt"],
article: [],
aside: [],
audio: ["autoplay", "controls", "loop", "preload", "src"],
b: [],
bdi: ["dir"],
bdo: ["dir"],
big: [],
blockquote: ["cite"],
br: [],
caption: [],
center: [],
cite: [],
code: [],
col: ["align", "valign", "span", "width"],
colgroup: ["align", "valign", "span", "width"],
dd: [],
del: ["datetime"],
details: ["open"],
div: [],
dl: [],
dt: [],
em: [],
font: ["color", "size", "face"],
footer: [],
h1: [],
h2: [],
h3: [],
h4: [],
h5: [],
h6: [],
header: [],
hr: [],
i: [],
img: ["src", "alt", "title", "width", "height"],
ins: ["datetime"],
li: [],
mark: [],
nav: [],
ol: [],
p: [],
pre: [],
s: [],
section: [],
small: [],
span: [],
sub: [],
sup: [],
strong: [],
table: ["width", "border", "align", "valign"],
tbody: ["align", "valign"],
td: ["width", "rowspan", "colspan", "align", "valign"],
tfoot: ["align", "valign"],
th: ["width", "rowspan", "colspan", "align", "valign"],
thead: ["align", "valign"],
tr: ["rowspan", "align", "valign"],
tt: [],
u: [],
ul: [],
video: ["autoplay", "controls", "loop", "preload", "src", "height", "width"]
};
}
2018-05-07 18:20:15 +02:00
var defaultCSSFilter = new FilterCSS();
2018-05-07 18:20:15 +02:00
/**
* default onTag function
*
* @param {String} tag
* @param {String} html
* @param {Object} options
* @return {String}
*/
function onTag(tag, html, options) {
// do nothing
}
2018-05-07 18:20:15 +02:00
/**
* default onIgnoreTag function
*
* @param {String} tag
* @param {String} html
* @param {Object} options
* @return {String}
*/
function onIgnoreTag(tag, html, options) {
// do nothing
}
2018-05-07 18:20:15 +02:00
/**
* default onTagAttr function
*
* @param {String} tag
* @param {String} name
* @param {String} value
* @return {String}
*/
function onTagAttr(tag, name, value) {
// do nothing
}
2018-05-07 18:20:15 +02:00
/**
* default onIgnoreTagAttr function
*
* @param {String} tag
* @param {String} name
* @param {String} value
* @return {String}
*/
function onIgnoreTagAttr(tag, name, value) {
// do nothing
}
2018-05-07 18:20:15 +02:00
/**
* default escapeHtml function
*
* @param {String} html
*/
function escapeHtml(html) {
return html.replace(REGEXP_LT, "&lt;").replace(REGEXP_GT, "&gt;");
}
2018-05-07 18:20:15 +02:00
/**
* default safeAttrValue function
*
* @param {String} tag
* @param {String} name
* @param {String} value
* @param {Object} cssFilter
* @return {String}
*/
function safeAttrValue(tag, name, value, cssFilter) {
// unescape attribute value firstly
value = friendlyAttrValue(value);
2018-05-07 18:20:15 +02:00
if (name === "href" || name === "src") {
// filter `href` and `src` attribute
// only allow the value that starts with `http://` | `https://` | `mailto:` | `/` | `#`
value = _.trim(value);
if (value === "#") return "#";
if (
!(
value.substr(0, 7) === "http://" ||
value.substr(0, 8) === "https://" ||
value.substr(0, 7) === "mailto:" ||
value.substr(0, 4) === "tel:" ||
value[0] === "#" ||
value[0] === "/"
)
) {
return "";
}
} else if (name === "background") {
// filter `background` attribute (maybe no use)
// `javascript:`
REGEXP_DEFAULT_ON_TAG_ATTR_4.lastIndex = 0;
if (REGEXP_DEFAULT_ON_TAG_ATTR_4.test(value)) {
return "";
}
} else if (name === "style") {
// `expression()`
REGEXP_DEFAULT_ON_TAG_ATTR_7.lastIndex = 0;
if (REGEXP_DEFAULT_ON_TAG_ATTR_7.test(value)) {
return "";
}
// `url()`
REGEXP_DEFAULT_ON_TAG_ATTR_8.lastIndex = 0;
if (REGEXP_DEFAULT_ON_TAG_ATTR_8.test(value)) {
REGEXP_DEFAULT_ON_TAG_ATTR_4.lastIndex = 0;
if (REGEXP_DEFAULT_ON_TAG_ATTR_4.test(value)) {
return "";
}
2018-05-07 18:20:15 +02:00
}
if (cssFilter !== false) {
cssFilter = cssFilter || defaultCSSFilter;
value = cssFilter.process(value);
}
}
2018-05-07 18:20:15 +02:00
// escape `<>"` before returns
value = escapeAttrValue(value);
return value;
}
2018-05-07 18:20:15 +02:00
// RegExp list
var REGEXP_LT = /</g;
var REGEXP_GT = />/g;
var REGEXP_QUOTE = /"/g;
var REGEXP_QUOTE_2 = /&quot;/g;
var REGEXP_ATTR_VALUE_1 = /&#([a-zA-Z0-9]*);?/gim;
var REGEXP_ATTR_VALUE_COLON = /&colon;?/gim;
var REGEXP_ATTR_VALUE_NEWLINE = /&newline;?/gim;
var REGEXP_DEFAULT_ON_TAG_ATTR_3 = /\/\*|\*\//gm;
var REGEXP_DEFAULT_ON_TAG_ATTR_4 = /((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a)\:/gi;
var REGEXP_DEFAULT_ON_TAG_ATTR_5 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:/gi;
var REGEXP_DEFAULT_ON_TAG_ATTR_6 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:\s*image\//gi;
var REGEXP_DEFAULT_ON_TAG_ATTR_7 = /e\s*x\s*p\s*r\s*e\s*s\s*s\s*i\s*o\s*n\s*\(.*/gi;
var REGEXP_DEFAULT_ON_TAG_ATTR_8 = /u\s*r\s*l\s*\(.*/gi;
2018-05-07 18:20:15 +02:00
/**
* escape doube quote
*
* @param {String} str
* @return {String} str
*/
function escapeQuote(str) {
return str.replace(REGEXP_QUOTE, "&quot;");
}
2018-05-07 18:20:15 +02:00
/**
* unescape double quote
*
* @param {String} str
* @return {String} str
*/
function unescapeQuote(str) {
return str.replace(REGEXP_QUOTE_2, '"');
}
2018-05-07 18:20:15 +02:00
/**
* escape html entities
*
* @param {String} str
* @return {String}
*/
function escapeHtmlEntities(str) {
return str.replace(REGEXP_ATTR_VALUE_1, function replaceUnicode(str, code) {
return code[0] === "x" || code[0] === "X"
? String.fromCharCode(parseInt(code.substr(1), 16))
: String.fromCharCode(parseInt(code, 10));
});
}
2018-05-07 18:20:15 +02:00
/**
* escape html5 new danger entities
*
* @param {String} str
* @return {String}
*/
function escapeDangerHtml5Entities(str) {
return str
.replace(REGEXP_ATTR_VALUE_COLON, ":")
.replace(REGEXP_ATTR_VALUE_NEWLINE, " ");
}
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
/**
* clear nonprintable characters
*
2018-05-07 18:20:15 +02:00
* @param {String} str
* @return {String}
*/
2018-05-07 18:20:15 +02:00
function clearNonPrintableCharacter(str) {
var str2 = "";
for (var i = 0, len = str.length; i < len; i++) {
str2 += str.charCodeAt(i) < 32 ? " " : str.charAt(i);
}
2018-05-07 18:20:15 +02:00
return _.trim(str2);
}
2018-05-07 18:20:15 +02:00
/**
* get friendly attribute value
*
* @param {String} str
* @return {String}
*/
function friendlyAttrValue(str) {
str = unescapeQuote(str);
str = escapeHtmlEntities(str);
str = escapeDangerHtml5Entities(str);
str = clearNonPrintableCharacter(str);
return str;
}
2018-05-07 18:20:15 +02:00
/**
* unescape attribute value
*
* @param {String} str
* @return {String}
*/
function escapeAttrValue(str) {
str = escapeQuote(str);
str = escapeHtml(str);
return str;
}
2018-05-07 18:20:15 +02:00
/**
* `onIgnoreTag` function for removing all the tags that are not in whitelist
*/
function onIgnoreTagStripAll() {
return "";
}
2018-05-07 18:20:15 +02:00
/**
* remove tag body
* specify a `tags` list, if the tag is not in the `tags` list then process by the specify function (optional)
*
* @param {array} tags
* @param {function} next
*/
function StripTagBody(tags, next) {
if (typeof next !== "function") {
next = function() {};
}
2018-05-07 18:20:15 +02:00
var isRemoveAllTag = !Array.isArray(tags);
function isRemoveTag(tag) {
if (isRemoveAllTag) return true;
return _.indexOf(tags, tag) !== -1;
}
2018-05-07 18:20:15 +02:00
var removeList = [];
var posStart = false;
2018-05-07 18:20:15 +02:00
return {
onIgnoreTag: function(tag, html, options) {
if (isRemoveTag(tag)) {
if (options.isClosing) {
var ret = "[/removed]";
var end = options.position + ret.length;
removeList.push([
posStart !== false ? posStart : options.position,
end
]);
posStart = false;
return ret;
} else {
if (!posStart) {
posStart = options.position;
}
return "[removed]";
2018-01-04 17:58:16 +01:00
}
2018-05-07 18:20:15 +02:00
} else {
return next(tag, html, options);
}
},
remove: function(html) {
var rethtml = "";
var lastPos = 0;
_.forEach(removeList, function(pos) {
rethtml += html.slice(lastPos, pos[0]);
lastPos = pos[1];
});
2018-05-07 18:20:15 +02:00
rethtml += html.slice(lastPos);
return rethtml;
2018-01-04 17:58:16 +01:00
}
2018-05-07 18:20:15 +02:00
};
}
2018-05-07 18:20:15 +02:00
/**
* remove html comments
*
* @param {String} html
* @return {String}
*/
function stripCommentTag(html) {
return html.replace(STRIP_COMMENT_TAG_REGEXP, "");
2017-12-20 18:08:08 +01:00
}
2018-05-07 18:20:15 +02:00
var STRIP_COMMENT_TAG_REGEXP = /<!--[\s\S]*?-->/g;
/**
* remove invisible characters
*
* @param {String} html
* @return {String}
*/
function stripBlankChar(html) {
var chars = html.split("");
chars = chars.filter(function(char) {
var c = char.charCodeAt(0);
if (c === 127) return false;
if (c <= 31) {
if (c === 10 || c === 13) return true;
return false;
}
2018-05-07 18:20:15 +02:00
return true;
});
return chars.join("");
2016-11-07 15:43:48 +01:00
}
2018-05-07 18:20:15 +02:00
exports.whiteList = getDefaultWhiteList();
exports.getDefaultWhiteList = getDefaultWhiteList;
exports.onTag = onTag;
exports.onIgnoreTag = onIgnoreTag;
exports.onTagAttr = onTagAttr;
exports.onIgnoreTagAttr = onIgnoreTagAttr;
exports.safeAttrValue = safeAttrValue;
exports.escapeHtml = escapeHtml;
exports.escapeQuote = escapeQuote;
exports.unescapeQuote = unescapeQuote;
exports.escapeHtmlEntities = escapeHtmlEntities;
exports.escapeDangerHtml5Entities = escapeDangerHtml5Entities;
exports.clearNonPrintableCharacter = clearNonPrintableCharacter;
exports.friendlyAttrValue = friendlyAttrValue;
exports.escapeAttrValue = escapeAttrValue;
exports.onIgnoreTagStripAll = onIgnoreTagStripAll;
exports.StripTagBody = StripTagBody;
exports.stripCommentTag = stripCommentTag;
exports.stripBlankChar = stripBlankChar;
exports.cssFilter = defaultCSSFilter;
exports.getDefaultCSSWhiteList = getDefaultCSSWhiteList;
},{"./util":4,"cssfilter":8}],2:[function(require,module,exports){
/**
* xss
*
* @author Zongmin Lei<leizongmin@gmail.com>
*/
var DEFAULT = require("./default");
var parser = require("./parser");
var FilterXSS = require("./xss");
/**
* filter xss function
*
* @param {String} html
* @param {Object} options { whiteList, onTag, onTagAttr, onIgnoreTag, onIgnoreTagAttr, safeAttrValue, escapeHtml }
* @return {String}
*/
function filterXSS(html, options) {
var xss = new FilterXSS(options);
return xss.process(html);
2017-12-20 18:08:08 +01:00
}
2018-05-07 18:20:15 +02:00
exports = module.exports = filterXSS;
exports.FilterXSS = FilterXSS;
for (var i in DEFAULT) exports[i] = DEFAULT[i];
for (var i in parser) exports[i] = parser[i];
// using `xss` on the browser, output `filterXSS` to the globals
if (typeof window !== "undefined") {
window.filterXSS = module.exports;
}
2018-05-07 18:20:15 +02:00
},{"./default":1,"./parser":3,"./xss":5}],3:[function(require,module,exports){
/**
* Simple HTML Parser
*
* @author Zongmin Lei<leizongmin@gmail.com>
*/
var _ = require("./util");
/**
* get tag name
*
* @param {String} html e.g. '<a hef="#">'
* @return {String}
*/
function getTagName(html) {
var i = _.spaceIndex(html);
if (i === -1) {
var tagName = html.slice(1, -1);
} else {
var tagName = html.slice(1, i + 1);
}
tagName = _.trim(tagName).toLowerCase();
if (tagName.slice(0, 1) === "/") tagName = tagName.slice(1);
if (tagName.slice(-1) === "/") tagName = tagName.slice(0, -1);
return tagName;
}
2018-05-07 18:20:15 +02:00
/**
* is close tag?
*
* @param {String} html '<a hef="#">'
* @return {Boolean}
*/
function isClosing(html) {
return html.slice(0, 2) === "</";
}
2018-05-07 18:20:15 +02:00
/**
* parse input html and returns processed html
*
* @param {String} html
* @param {Function} onTag e.g. function (sourcePosition, position, tag, html, isClosing)
* @param {Function} escapeHtml
* @return {String}
*/
function parseTag(html, onTag, escapeHtml) {
"user strict";
var rethtml = "";
var lastPos = 0;
var tagStart = false;
var quoteStart = false;
var currentPos = 0;
var len = html.length;
var currentTagName = "";
var currentHtml = "";
for (currentPos = 0; currentPos < len; currentPos++) {
var c = html.charAt(currentPos);
if (tagStart === false) {
if (c === "<") {
tagStart = currentPos;
continue;
}
} else {
if (quoteStart === false) {
if (c === "<") {
rethtml += escapeHtml(html.slice(lastPos, currentPos));
tagStart = currentPos;
lastPos = currentPos;
continue;
}
2018-05-07 18:20:15 +02:00
if (c === ">") {
rethtml += escapeHtml(html.slice(lastPos, tagStart));
currentHtml = html.slice(tagStart, currentPos + 1);
currentTagName = getTagName(currentHtml);
rethtml += onTag(
tagStart,
rethtml.length,
currentTagName,
currentHtml,
isClosing(currentHtml)
);
lastPos = currentPos + 1;
tagStart = false;
continue;
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
if ((c === '"' || c === "'") && html.charAt(currentPos - 1) === "=") {
quoteStart = c;
continue;
}
} else {
if (c === quoteStart) {
quoteStart = false;
continue;
}
}
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
}
if (lastPos < html.length) {
rethtml += escapeHtml(html.substr(lastPos));
}
return rethtml;
}
var REGEXP_ILLEGAL_ATTR_NAME = /[^a-zA-Z0-9_:\.\-]/gim;
/**
* parse input attributes and returns processed attributes
*
* @param {String} html e.g. `href="#" target="_blank"`
* @param {Function} onAttr e.g. `function (name, value)`
* @return {String}
*/
function parseAttr(html, onAttr) {
"user strict";
var lastPos = 0;
var retAttrs = [];
var tmpName = false;
var len = html.length;
function addAttr(name, value) {
name = _.trim(name);
name = name.replace(REGEXP_ILLEGAL_ATTR_NAME, "").toLowerCase();
if (name.length < 1) return;
var ret = onAttr(name, value || "");
if (ret) retAttrs.push(ret);
}
// 逐个分析字符
for (var i = 0; i < len; i++) {
var c = html.charAt(i);
var v, j;
if (tmpName === false && c === "=") {
tmpName = html.slice(lastPos, i);
lastPos = i + 1;
continue;
}
2018-05-07 18:20:15 +02:00
if (tmpName !== false) {
if (
i === lastPos &&
(c === '"' || c === "'") &&
html.charAt(i - 1) === "="
) {
j = html.indexOf(c, i + 1);
if (j === -1) {
break;
} else {
v = _.trim(html.slice(lastPos + 1, j));
addAttr(tmpName, v);
tmpName = false;
i = j;
lastPos = i + 1;
continue;
}
2018-05-07 18:20:15 +02:00
}
}
if (/\s|\n|\t/.test(c)) {
html = html.replace(/\s|\n|\t/g, " ");
if (tmpName === false) {
j = findNextEqual(html, i);
if (j === -1) {
v = _.trim(html.slice(lastPos, i));
addAttr(v);
tmpName = false;
lastPos = i + 1;
continue;
} else {
i = j - 1;
continue;
}
2018-05-07 18:20:15 +02:00
} else {
j = findBeforeEqual(html, i - 1);
if (j === -1) {
v = _.trim(html.slice(lastPos, i));
v = stripQuoteWrap(v);
addAttr(tmpName, v);
tmpName = false;
lastPos = i + 1;
continue;
} else {
continue;
}
2018-05-07 18:20:15 +02:00
}
2017-12-20 18:08:08 +01:00
}
2018-05-07 18:20:15 +02:00
}
if (lastPos < html.length) {
if (tmpName === false) {
addAttr(html.slice(lastPos));
} else {
addAttr(tmpName, stripQuoteWrap(_.trim(html.slice(lastPos))));
2017-12-20 18:08:08 +01:00
}
2018-05-07 18:20:15 +02:00
}
return _.trim(retAttrs.join(" "));
}
function findNextEqual(str, i) {
for (; i < str.length; i++) {
var c = str[i];
if (c === " ") continue;
if (c === "=") return i;
return -1;
}
}
function findBeforeEqual(str, i) {
for (; i > 0; i--) {
var c = str[i];
if (c === " ") continue;
if (c === "=") return i;
return -1;
}
}
function isQuoteWrapString(text) {
if (
(text[0] === '"' && text[text.length - 1] === '"') ||
(text[0] === "'" && text[text.length - 1] === "'")
) {
return true;
} else {
return false;
}
}
function stripQuoteWrap(text) {
if (isQuoteWrapString(text)) {
return text.substr(1, text.length - 2);
} else {
return text;
}
}
exports.parseTag = parseTag;
exports.parseAttr = parseAttr;
},{"./util":4}],4:[function(require,module,exports){
module.exports = {
indexOf: function(arr, item) {
var i, j;
if (Array.prototype.indexOf) {
return arr.indexOf(item);
2017-12-20 18:08:08 +01:00
}
2018-05-07 18:20:15 +02:00
for (i = 0, j = arr.length; i < j; i++) {
if (arr[i] === item) {
return i;
}
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
return -1;
},
forEach: function(arr, fn, scope) {
var i, j;
if (Array.prototype.forEach) {
return arr.forEach(fn, scope);
}
for (i = 0, j = arr.length; i < j; i++) {
fn.call(scope, arr[i], i, arr);
}
2018-05-07 18:20:15 +02:00
},
trim: function(str) {
if (String.prototype.trim) {
return str.trim();
}
2018-05-07 18:20:15 +02:00
return str.replace(/(^\s*)|(\s*$)/g, "");
},
spaceIndex: function(str) {
var reg = /\s|\n|\t/;
var match = reg.exec(str);
return match ? match.index : -1;
}
};
},{}],5:[function(require,module,exports){
/**
* filter xss
*
* @author Zongmin Lei<leizongmin@gmail.com>
*/
var FilterCSS = require("cssfilter").FilterCSS;
var DEFAULT = require("./default");
var parser = require("./parser");
var parseTag = parser.parseTag;
var parseAttr = parser.parseAttr;
var _ = require("./util");
/**
* returns `true` if the input value is `undefined` or `null`
*
* @param {Object} obj
* @return {Boolean}
*/
function isNull(obj) {
return obj === undefined || obj === null;
}
/**
* get attributes for a tag
*
* @param {String} html
* @return {Object}
* - {String} html
* - {Boolean} closing
*/
function getAttrs(html) {
var i = _.spaceIndex(html);
if (i === -1) {
return {
html: "",
closing: html[html.length - 2] === "/"
};
}
html = _.trim(html.slice(i + 1, -1));
var isClosing = html[html.length - 1] === "/";
if (isClosing) html = _.trim(html.slice(0, -1));
return {
html: html,
closing: isClosing
};
}
/**
* shallow copy
*
* @param {Object} obj
* @return {Object}
*/
function shallowCopyObject(obj) {
var ret = {};
for (var i in obj) {
ret[i] = obj[i];
}
return ret;
}
/**
* FilterXSS class
*
* @param {Object} options
* whiteList, onTag, onTagAttr, onIgnoreTag,
* onIgnoreTagAttr, safeAttrValue, escapeHtml
* stripIgnoreTagBody, allowCommentTag, stripBlankChar
* css{whiteList, onAttr, onIgnoreAttr} `css=false` means don't use `cssfilter`
*/
function FilterXSS(options) {
options = shallowCopyObject(options || {});
if (options.stripIgnoreTag) {
if (options.onIgnoreTag) {
console.error(
'Notes: cannot use these two options "stripIgnoreTag" and "onIgnoreTag" at the same time'
);
}
options.onIgnoreTag = DEFAULT.onIgnoreTagStripAll;
}
options.whiteList = options.whiteList || DEFAULT.whiteList;
options.onTag = options.onTag || DEFAULT.onTag;
options.onTagAttr = options.onTagAttr || DEFAULT.onTagAttr;
options.onIgnoreTag = options.onIgnoreTag || DEFAULT.onIgnoreTag;
options.onIgnoreTagAttr = options.onIgnoreTagAttr || DEFAULT.onIgnoreTagAttr;
options.safeAttrValue = options.safeAttrValue || DEFAULT.safeAttrValue;
options.escapeHtml = options.escapeHtml || DEFAULT.escapeHtml;
this.options = options;
if (options.css === false) {
this.cssFilter = false;
} else {
options.css = options.css || {};
this.cssFilter = new FilterCSS(options.css);
}
}
/**
* start process and returns result
*
* @param {String} html
* @return {String}
*/
FilterXSS.prototype.process = function(html) {
// compatible with the input
html = html || "";
html = html.toString();
if (!html) return "";
var me = this;
var options = me.options;
var whiteList = options.whiteList;
var onTag = options.onTag;
var onIgnoreTag = options.onIgnoreTag;
var onTagAttr = options.onTagAttr;
var onIgnoreTagAttr = options.onIgnoreTagAttr;
var safeAttrValue = options.safeAttrValue;
var escapeHtml = options.escapeHtml;
var cssFilter = me.cssFilter;
// remove invisible characters
if (options.stripBlankChar) {
html = DEFAULT.stripBlankChar(html);
}
// remove html comments
if (!options.allowCommentTag) {
html = DEFAULT.stripCommentTag(html);
}
// if enable stripIgnoreTagBody
var stripIgnoreTagBody = false;
if (options.stripIgnoreTagBody) {
var stripIgnoreTagBody = DEFAULT.StripTagBody(
options.stripIgnoreTagBody,
onIgnoreTag
);
onIgnoreTag = stripIgnoreTagBody.onIgnoreTag;
}
var retHtml = parseTag(
html,
function(sourcePosition, position, tag, html, isClosing) {
var info = {
sourcePosition: sourcePosition,
position: position,
isClosing: isClosing,
isWhite: whiteList.hasOwnProperty(tag)
};
// call `onTag()`
var ret = onTag(tag, html, info);
if (!isNull(ret)) return ret;
if (info.isWhite) {
if (info.isClosing) {
return "</" + tag + ">";
}
2018-05-07 18:20:15 +02:00
var attrs = getAttrs(html);
var whiteAttrList = whiteList[tag];
var attrsHtml = parseAttr(attrs.html, function(name, value) {
// call `onTagAttr()`
var isWhiteAttr = _.indexOf(whiteAttrList, name) !== -1;
var ret = onTagAttr(tag, name, value, isWhiteAttr);
if (!isNull(ret)) return ret;
if (isWhiteAttr) {
// call `safeAttrValue()`
value = safeAttrValue(tag, name, value, cssFilter);
if (value) {
return name + '="' + value + '"';
} else {
return name;
}
} else {
// call `onIgnoreTagAttr()`
var ret = onIgnoreTagAttr(tag, name, value, isWhiteAttr);
if (!isNull(ret)) return ret;
return;
}
});
// build new tag html
var html = "<" + tag;
if (attrsHtml) html += " " + attrsHtml;
if (attrs.closing) html += " /";
html += ">";
return html;
} else {
// call `onIgnoreTag()`
var ret = onIgnoreTag(tag, html, info);
if (!isNull(ret)) return ret;
return escapeHtml(html);
}
},
escapeHtml
);
// if enable stripIgnoreTagBody
if (stripIgnoreTagBody) {
retHtml = stripIgnoreTagBody.remove(retHtml);
}
return retHtml;
};
module.exports = FilterXSS;
},{"./default":1,"./parser":3,"./util":4,"cssfilter":8}],6:[function(require,module,exports){
/**
* cssfilter
*
* @author 老雷<leizongmin@gmail.com>
*/
var DEFAULT = require('./default');
var parseStyle = require('./parser');
var _ = require('./util');
/**
* 返回值是否为空
*
* @param {Object} obj
* @return {Boolean}
*/
function isNull (obj) {
return (obj === undefined || obj === null);
}
/**
* 浅拷贝对象
*
* @param {Object} obj
* @return {Object}
*/
function shallowCopyObject (obj) {
var ret = {};
for (var i in obj) {
ret[i] = obj[i];
}
return ret;
}
/**
* 创建CSS过滤器
*
* @param {Object} options
* - {Object} whiteList
* - {Function} onAttr
* - {Function} onIgnoreAttr
* - {Function} safeAttrValue
*/
function FilterCSS (options) {
options = shallowCopyObject(options || {});
options.whiteList = options.whiteList || DEFAULT.whiteList;
options.onAttr = options.onAttr || DEFAULT.onAttr;
options.onIgnoreAttr = options.onIgnoreAttr || DEFAULT.onIgnoreAttr;
options.safeAttrValue = options.safeAttrValue || DEFAULT.safeAttrValue;
this.options = options;
}
FilterCSS.prototype.process = function (css) {
// 兼容各种奇葩输入
css = css || '';
css = css.toString();
if (!css) return '';
var me = this;
var options = me.options;
var whiteList = options.whiteList;
var onAttr = options.onAttr;
var onIgnoreAttr = options.onIgnoreAttr;
var safeAttrValue = options.safeAttrValue;
var retCSS = parseStyle(css, function (sourcePosition, position, name, value, source) {
var check = whiteList[name];
var isWhite = false;
if (check === true) isWhite = check;
else if (typeof check === 'function') isWhite = check(value);
else if (check instanceof RegExp) isWhite = check.test(value);
if (isWhite !== true) isWhite = false;
// 如果过滤后 value 为空则直接忽略
value = safeAttrValue(name, value);
if (!value) return;
var opts = {
position: position,
sourcePosition: sourcePosition,
source: source,
isWhite: isWhite
};
2018-05-07 18:20:15 +02:00
if (isWhite) {
var ret = onAttr(name, value, opts);
if (isNull(ret)) {
return name + ':' + value;
} else {
return ret;
}
} else {
var ret = onIgnoreAttr(name, value, opts);
if (!isNull(ret)) {
return ret;
}
}
});
return retCSS;
};
module.exports = FilterCSS;
},{"./default":7,"./parser":9,"./util":10}],7:[function(require,module,exports){
/**
* cssfilter
*
* @author 老雷<leizongmin@gmail.com>
*/
function getDefaultWhiteList () {
// 白名单值说明:
// true: 允许该属性
// Function: function (val) { } 返回true表示允许该属性其他值均表示不允许
// RegExp: regexp.test(val) 返回true表示允许该属性其他值均表示不允许
// 除上面列出的值外均表示不允许
var whiteList = {};
whiteList['align-content'] = false; // default: auto
whiteList['align-items'] = false; // default: auto
whiteList['align-self'] = false; // default: auto
whiteList['alignment-adjust'] = false; // default: auto
whiteList['alignment-baseline'] = false; // default: baseline
whiteList['all'] = false; // default: depending on individual properties
whiteList['anchor-point'] = false; // default: none
whiteList['animation'] = false; // default: depending on individual properties
whiteList['animation-delay'] = false; // default: 0
whiteList['animation-direction'] = false; // default: normal
whiteList['animation-duration'] = false; // default: 0
whiteList['animation-fill-mode'] = false; // default: none
whiteList['animation-iteration-count'] = false; // default: 1
whiteList['animation-name'] = false; // default: none
whiteList['animation-play-state'] = false; // default: running
whiteList['animation-timing-function'] = false; // default: ease
whiteList['azimuth'] = false; // default: center
whiteList['backface-visibility'] = false; // default: visible
whiteList['background'] = true; // default: depending on individual properties
whiteList['background-attachment'] = true; // default: scroll
whiteList['background-clip'] = true; // default: border-box
whiteList['background-color'] = true; // default: transparent
whiteList['background-image'] = true; // default: none
whiteList['background-origin'] = true; // default: padding-box
whiteList['background-position'] = true; // default: 0% 0%
whiteList['background-repeat'] = true; // default: repeat
whiteList['background-size'] = true; // default: auto
whiteList['baseline-shift'] = false; // default: baseline
whiteList['binding'] = false; // default: none
whiteList['bleed'] = false; // default: 6pt
whiteList['bookmark-label'] = false; // default: content()
whiteList['bookmark-level'] = false; // default: none
whiteList['bookmark-state'] = false; // default: open
whiteList['border'] = true; // default: depending on individual properties
whiteList['border-bottom'] = true; // default: depending on individual properties
whiteList['border-bottom-color'] = true; // default: current color
whiteList['border-bottom-left-radius'] = true; // default: 0
whiteList['border-bottom-right-radius'] = true; // default: 0
whiteList['border-bottom-style'] = true; // default: none
whiteList['border-bottom-width'] = true; // default: medium
whiteList['border-collapse'] = true; // default: separate
whiteList['border-color'] = true; // default: depending on individual properties
whiteList['border-image'] = true; // default: none
whiteList['border-image-outset'] = true; // default: 0
whiteList['border-image-repeat'] = true; // default: stretch
whiteList['border-image-slice'] = true; // default: 100%
whiteList['border-image-source'] = true; // default: none
whiteList['border-image-width'] = true; // default: 1
whiteList['border-left'] = true; // default: depending on individual properties
whiteList['border-left-color'] = true; // default: current color
whiteList['border-left-style'] = true; // default: none
whiteList['border-left-width'] = true; // default: medium
whiteList['border-radius'] = true; // default: 0
whiteList['border-right'] = true; // default: depending on individual properties
whiteList['border-right-color'] = true; // default: current color
whiteList['border-right-style'] = true; // default: none
whiteList['border-right-width'] = true; // default: medium
whiteList['border-spacing'] = true; // default: 0
whiteList['border-style'] = true; // default: depending on individual properties
whiteList['border-top'] = true; // default: depending on individual properties
whiteList['border-top-color'] = true; // default: current color
whiteList['border-top-left-radius'] = true; // default: 0
whiteList['border-top-right-radius'] = true; // default: 0
whiteList['border-top-style'] = true; // default: none
whiteList['border-top-width'] = true; // default: medium
whiteList['border-width'] = true; // default: depending on individual properties
whiteList['bottom'] = false; // default: auto
whiteList['box-decoration-break'] = true; // default: slice
whiteList['box-shadow'] = true; // default: none
whiteList['box-sizing'] = true; // default: content-box
whiteList['box-snap'] = true; // default: none
whiteList['box-suppress'] = true; // default: show
whiteList['break-after'] = true; // default: auto
whiteList['break-before'] = true; // default: auto
whiteList['break-inside'] = true; // default: auto
whiteList['caption-side'] = false; // default: top
whiteList['chains'] = false; // default: none
whiteList['clear'] = true; // default: none
whiteList['clip'] = false; // default: auto
whiteList['clip-path'] = false; // default: none
whiteList['clip-rule'] = false; // default: nonzero
whiteList['color'] = true; // default: implementation dependent
whiteList['color-interpolation-filters'] = true; // default: auto
whiteList['column-count'] = false; // default: auto
whiteList['column-fill'] = false; // default: balance
whiteList['column-gap'] = false; // default: normal
whiteList['column-rule'] = false; // default: depending on individual properties
whiteList['column-rule-color'] = false; // default: current color
whiteList['column-rule-style'] = false; // default: medium
whiteList['column-rule-width'] = false; // default: medium
whiteList['column-span'] = false; // default: none
whiteList['column-width'] = false; // default: auto
whiteList['columns'] = false; // default: depending on individual properties
whiteList['contain'] = false; // default: none
whiteList['content'] = false; // default: normal
whiteList['counter-increment'] = false; // default: none
whiteList['counter-reset'] = false; // default: none
whiteList['counter-set'] = false; // default: none
whiteList['crop'] = false; // default: auto
whiteList['cue'] = false; // default: depending on individual properties
whiteList['cue-after'] = false; // default: none
whiteList['cue-before'] = false; // default: none
whiteList['cursor'] = false; // default: auto
whiteList['direction'] = false; // default: ltr
whiteList['display'] = true; // default: depending on individual properties
whiteList['display-inside'] = true; // default: auto
whiteList['display-list'] = true; // default: none
whiteList['display-outside'] = true; // default: inline-level
whiteList['dominant-baseline'] = false; // default: auto
whiteList['elevation'] = false; // default: level
whiteList['empty-cells'] = false; // default: show
whiteList['filter'] = false; // default: none
whiteList['flex'] = false; // default: depending on individual properties
whiteList['flex-basis'] = false; // default: auto
whiteList['flex-direction'] = false; // default: row
whiteList['flex-flow'] = false; // default: depending on individual properties
whiteList['flex-grow'] = false; // default: 0
whiteList['flex-shrink'] = false; // default: 1
whiteList['flex-wrap'] = false; // default: nowrap
whiteList['float'] = false; // default: none
whiteList['float-offset'] = false; // default: 0 0
whiteList['flood-color'] = false; // default: black
whiteList['flood-opacity'] = false; // default: 1
whiteList['flow-from'] = false; // default: none
whiteList['flow-into'] = false; // default: none
whiteList['font'] = true; // default: depending on individual properties
whiteList['font-family'] = true; // default: implementation dependent
whiteList['font-feature-settings'] = true; // default: normal
whiteList['font-kerning'] = true; // default: auto
whiteList['font-language-override'] = true; // default: normal
whiteList['font-size'] = true; // default: medium
whiteList['font-size-adjust'] = true; // default: none
whiteList['font-stretch'] = true; // default: normal
whiteList['font-style'] = true; // default: normal
whiteList['font-synthesis'] = true; // default: weight style
whiteList['font-variant'] = true; // default: normal
whiteList['font-variant-alternates'] = true; // default: normal
whiteList['font-variant-caps'] = true; // default: normal
whiteList['font-variant-east-asian'] = true; // default: normal
whiteList['font-variant-ligatures'] = true; // default: normal
whiteList['font-variant-numeric'] = true; // default: normal
whiteList['font-variant-position'] = true; // default: normal
whiteList['font-weight'] = true; // default: normal
whiteList['grid'] = false; // default: depending on individual properties
whiteList['grid-area'] = false; // default: depending on individual properties
whiteList['grid-auto-columns'] = false; // default: auto
whiteList['grid-auto-flow'] = false; // default: none
whiteList['grid-auto-rows'] = false; // default: auto
whiteList['grid-column'] = false; // default: depending on individual properties
whiteList['grid-column-end'] = false; // default: auto
whiteList['grid-column-start'] = false; // default: auto
whiteList['grid-row'] = false; // default: depending on individual properties
whiteList['grid-row-end'] = false; // default: auto
whiteList['grid-row-start'] = false; // default: auto
whiteList['grid-template'] = false; // default: depending on individual properties
whiteList['grid-template-areas'] = false; // default: none
whiteList['grid-template-columns'] = false; // default: none
whiteList['grid-template-rows'] = false; // default: none
whiteList['hanging-punctuation'] = false; // default: none
whiteList['height'] = true; // default: auto
whiteList['hyphens'] = false; // default: manual
whiteList['icon'] = false; // default: auto
whiteList['image-orientation'] = false; // default: auto
whiteList['image-resolution'] = false; // default: normal
whiteList['ime-mode'] = false; // default: auto
whiteList['initial-letters'] = false; // default: normal
whiteList['inline-box-align'] = false; // default: last
whiteList['justify-content'] = false; // default: auto
whiteList['justify-items'] = false; // default: auto
whiteList['justify-self'] = false; // default: auto
whiteList['left'] = false; // default: auto
whiteList['letter-spacing'] = true; // default: normal
whiteList['lighting-color'] = true; // default: white
whiteList['line-box-contain'] = false; // default: block inline replaced
whiteList['line-break'] = false; // default: auto
whiteList['line-grid'] = false; // default: match-parent
whiteList['line-height'] = false; // default: normal
whiteList['line-snap'] = false; // default: none
whiteList['line-stacking'] = false; // default: depending on individual properties
whiteList['line-stacking-ruby'] = false; // default: exclude-ruby
whiteList['line-stacking-shift'] = false; // default: consider-shifts
whiteList['line-stacking-strategy'] = false; // default: inline-line-height
whiteList['list-style'] = true; // default: depending on individual properties
whiteList['list-style-image'] = true; // default: none
whiteList['list-style-position'] = true; // default: outside
whiteList['list-style-type'] = true; // default: disc
whiteList['margin'] = true; // default: depending on individual properties
whiteList['margin-bottom'] = true; // default: 0
whiteList['margin-left'] = true; // default: 0
whiteList['margin-right'] = true; // default: 0
whiteList['margin-top'] = true; // default: 0
whiteList['marker-offset'] = false; // default: auto
whiteList['marker-side'] = false; // default: list-item
whiteList['marks'] = false; // default: none
whiteList['mask'] = false; // default: border-box
whiteList['mask-box'] = false; // default: see individual properties
whiteList['mask-box-outset'] = false; // default: 0
whiteList['mask-box-repeat'] = false; // default: stretch
whiteList['mask-box-slice'] = false; // default: 0 fill
whiteList['mask-box-source'] = false; // default: none
whiteList['mask-box-width'] = false; // default: auto
whiteList['mask-clip'] = false; // default: border-box
whiteList['mask-image'] = false; // default: none
whiteList['mask-origin'] = false; // default: border-box
whiteList['mask-position'] = false; // default: center
whiteList['mask-repeat'] = false; // default: no-repeat
whiteList['mask-size'] = false; // default: border-box
whiteList['mask-source-type'] = false; // default: auto
whiteList['mask-type'] = false; // default: luminance
whiteList['max-height'] = true; // default: none
whiteList['max-lines'] = false; // default: none
whiteList['max-width'] = true; // default: none
whiteList['min-height'] = true; // default: 0
whiteList['min-width'] = true; // default: 0
whiteList['move-to'] = false; // default: normal
whiteList['nav-down'] = false; // default: auto
whiteList['nav-index'] = false; // default: auto
whiteList['nav-left'] = false; // default: auto
whiteList['nav-right'] = false; // default: auto
whiteList['nav-up'] = false; // default: auto
whiteList['object-fit'] = false; // default: fill
whiteList['object-position'] = false; // default: 50% 50%
whiteList['opacity'] = false; // default: 1
whiteList['order'] = false; // default: 0
whiteList['orphans'] = false; // default: 2
whiteList['outline'] = false; // default: depending on individual properties
whiteList['outline-color'] = false; // default: invert
whiteList['outline-offset'] = false; // default: 0
whiteList['outline-style'] = false; // default: none
whiteList['outline-width'] = false; // default: medium
whiteList['overflow'] = false; // default: depending on individual properties
whiteList['overflow-wrap'] = false; // default: normal
whiteList['overflow-x'] = false; // default: visible
whiteList['overflow-y'] = false; // default: visible
whiteList['padding'] = true; // default: depending on individual properties
whiteList['padding-bottom'] = true; // default: 0
whiteList['padding-left'] = true; // default: 0
whiteList['padding-right'] = true; // default: 0
whiteList['padding-top'] = true; // default: 0
whiteList['page'] = false; // default: auto
whiteList['page-break-after'] = false; // default: auto
whiteList['page-break-before'] = false; // default: auto
whiteList['page-break-inside'] = false; // default: auto
whiteList['page-policy'] = false; // default: start
whiteList['pause'] = false; // default: implementation dependent
whiteList['pause-after'] = false; // default: implementation dependent
whiteList['pause-before'] = false; // default: implementation dependent
whiteList['perspective'] = false; // default: none
whiteList['perspective-origin'] = false; // default: 50% 50%
whiteList['pitch'] = false; // default: medium
whiteList['pitch-range'] = false; // default: 50
whiteList['play-during'] = false; // default: auto
whiteList['position'] = false; // default: static
whiteList['presentation-level'] = false; // default: 0
whiteList['quotes'] = false; // default: text
whiteList['region-fragment'] = false; // default: auto
whiteList['resize'] = false; // default: none
whiteList['rest'] = false; // default: depending on individual properties
whiteList['rest-after'] = false; // default: none
whiteList['rest-before'] = false; // default: none
whiteList['richness'] = false; // default: 50
whiteList['right'] = false; // default: auto
whiteList['rotation'] = false; // default: 0
whiteList['rotation-point'] = false; // default: 50% 50%
whiteList['ruby-align'] = false; // default: auto
whiteList['ruby-merge'] = false; // default: separate
whiteList['ruby-position'] = false; // default: before
whiteList['shape-image-threshold'] = false; // default: 0.0
whiteList['shape-outside'] = false; // default: none
whiteList['shape-margin'] = false; // default: 0
whiteList['size'] = false; // default: auto
whiteList['speak'] = false; // default: auto
whiteList['speak-as'] = false; // default: normal
whiteList['speak-header'] = false; // default: once
whiteList['speak-numeral'] = false; // default: continuous
whiteList['speak-punctuation'] = false; // default: none
whiteList['speech-rate'] = false; // default: medium
whiteList['stress'] = false; // default: 50
whiteList['string-set'] = false; // default: none
whiteList['tab-size'] = false; // default: 8
whiteList['table-layout'] = false; // default: auto
whiteList['text-align'] = true; // default: start
whiteList['text-align-last'] = true; // default: auto
whiteList['text-combine-upright'] = true; // default: none
whiteList['text-decoration'] = true; // default: none
whiteList['text-decoration-color'] = true; // default: currentColor
whiteList['text-decoration-line'] = true; // default: none
whiteList['text-decoration-skip'] = true; // default: objects
whiteList['text-decoration-style'] = true; // default: solid
whiteList['text-emphasis'] = true; // default: depending on individual properties
whiteList['text-emphasis-color'] = true; // default: currentColor
whiteList['text-emphasis-position'] = true; // default: over right
whiteList['text-emphasis-style'] = true; // default: none
whiteList['text-height'] = true; // default: auto
whiteList['text-indent'] = true; // default: 0
whiteList['text-justify'] = true; // default: auto
whiteList['text-orientation'] = true; // default: mixed
whiteList['text-overflow'] = true; // default: clip
whiteList['text-shadow'] = true; // default: none
whiteList['text-space-collapse'] = true; // default: collapse
whiteList['text-transform'] = true; // default: none
whiteList['text-underline-position'] = true; // default: auto
whiteList['text-wrap'] = true; // default: normal
whiteList['top'] = false; // default: auto
whiteList['transform'] = false; // default: none
whiteList['transform-origin'] = false; // default: 50% 50% 0
whiteList['transform-style'] = false; // default: flat
whiteList['transition'] = false; // default: depending on individual properties
whiteList['transition-delay'] = false; // default: 0s
whiteList['transition-duration'] = false; // default: 0s
whiteList['transition-property'] = false; // default: all
whiteList['transition-timing-function'] = false; // default: ease
whiteList['unicode-bidi'] = false; // default: normal
whiteList['vertical-align'] = false; // default: baseline
whiteList['visibility'] = false; // default: visible
whiteList['voice-balance'] = false; // default: center
whiteList['voice-duration'] = false; // default: auto
whiteList['voice-family'] = false; // default: implementation dependent
whiteList['voice-pitch'] = false; // default: medium
whiteList['voice-range'] = false; // default: medium
whiteList['voice-rate'] = false; // default: normal
whiteList['voice-stress'] = false; // default: normal
whiteList['voice-volume'] = false; // default: medium
whiteList['volume'] = false; // default: medium
whiteList['white-space'] = false; // default: normal
whiteList['widows'] = false; // default: 2
whiteList['width'] = true; // default: auto
whiteList['will-change'] = false; // default: auto
whiteList['word-break'] = true; // default: normal
whiteList['word-spacing'] = true; // default: normal
whiteList['word-wrap'] = true; // default: normal
whiteList['wrap-flow'] = false; // default: auto
whiteList['wrap-through'] = false; // default: wrap
whiteList['writing-mode'] = false; // default: horizontal-tb
whiteList['z-index'] = false; // default: auto
return whiteList;
}
/**
* 匹配到白名单上的一个属性时
*
* @param {String} name
* @param {String} value
* @param {Object} options
* @return {String}
*/
function onAttr (name, value, options) {
// do nothing
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
/**
* 匹配到不在白名单上的一个属性时
*
* @param {String} name
* @param {String} value
* @param {Object} options
* @return {String}
*/
function onIgnoreAttr (name, value, options) {
// do nothing
}
2018-05-07 18:20:15 +02:00
var REGEXP_URL_JAVASCRIPT = /javascript\s*\:/img;
/**
* 过滤属性值
*
* @param {String} name
* @param {String} value
* @return {String}
*/
function safeAttrValue(name, value) {
if (REGEXP_URL_JAVASCRIPT.test(value)) return '';
return value;
}
2018-05-07 18:20:15 +02:00
exports.whiteList = getDefaultWhiteList();
exports.getDefaultWhiteList = getDefaultWhiteList;
exports.onAttr = onAttr;
exports.onIgnoreAttr = onIgnoreAttr;
exports.safeAttrValue = safeAttrValue;
},{}],8:[function(require,module,exports){
/**
* cssfilter
*
* @author 老雷<leizongmin@gmail.com>
*/
var DEFAULT = require('./default');
var FilterCSS = require('./css');
/**
* XSS过滤
*
* @param {String} css 要过滤的CSS代码
* @param {Object} options 选项whiteList, onAttr, onIgnoreAttr
* @return {String}
*/
function filterCSS (html, options) {
var xss = new FilterCSS(options);
return xss.process(html);
}
// 输出
exports = module.exports = filterCSS;
exports.FilterCSS = FilterCSS;
for (var i in DEFAULT) exports[i] = DEFAULT[i];
// 在浏览器端使用
if (typeof window !== 'undefined') {
window.filterCSS = module.exports;
}
},{"./css":6,"./default":7}],9:[function(require,module,exports){
/**
* cssfilter
*
* @author 老雷<leizongmin@gmail.com>
*/
var _ = require('./util');
/**
* 解析style
*
* @param {String} css
* @param {Function} onAttr 处理属性的函数
* 参数格式 function (sourcePosition, position, name, value, source)
* @return {String}
*/
function parseStyle (css, onAttr) {
css = _.trimRight(css);
if (css[css.length - 1] !== ';') css += ';';
var cssLength = css.length;
var isParenthesisOpen = false;
var lastPos = 0;
var i = 0;
var retCSS = '';
function addNewAttr () {
// 如果没有正常的闭合圆括号,则直接忽略当前属性
if (!isParenthesisOpen) {
var source = _.trim(css.slice(lastPos, i));
var j = source.indexOf(':');
if (j !== -1) {
var name = _.trim(source.slice(0, j));
var value = _.trim(source.slice(j + 1));
// 必须有属性名称
if (name) {
var ret = onAttr(lastPos, retCSS.length, name, value, source);
if (ret) retCSS += ret + '; ';
2017-12-20 18:08:08 +01:00
}
2018-05-07 18:20:15 +02:00
}
}
2018-05-07 18:20:15 +02:00
lastPos = i + 1;
}
for (; i < cssLength; i++) {
var c = css[i];
if (c === '/' && css[i + 1] === '*') {
// 备注开始
var j = css.indexOf('*/', i + 2);
// 如果没有正常的备注结束,则后面的部分全部跳过
if (j === -1) break;
// 直接将当前位置调到备注结尾,并且初始化状态
i = j + 1;
lastPos = i + 1;
isParenthesisOpen = false;
} else if (c === '(') {
isParenthesisOpen = true;
} else if (c === ')') {
isParenthesisOpen = false;
} else if (c === ';') {
if (isParenthesisOpen) {
// 在圆括号里面,忽略
} else {
addNewAttr();
}
} else if (c === '\n') {
addNewAttr();
}
2018-05-07 18:20:15 +02:00
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
return _.trim(retCSS);
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
module.exports = parseStyle;
},{"./util":10}],10:[function(require,module,exports){
module.exports = {
indexOf: function (arr, item) {
var i, j;
if (Array.prototype.indexOf) {
return arr.indexOf(item);
}
for (i = 0, j = arr.length; i < j; i++) {
if (arr[i] === item) {
return i;
}
}
2018-05-07 18:20:15 +02:00
return -1;
},
forEach: function (arr, fn, scope) {
var i, j;
if (Array.prototype.forEach) {
return arr.forEach(fn, scope);
}
2018-05-07 18:20:15 +02:00
for (i = 0, j = arr.length; i < j; i++) {
fn.call(scope, arr[i], i, arr);
}
2018-05-07 18:20:15 +02:00
},
trim: function (str) {
if (String.prototype.trim) {
return str.trim();
}
2018-05-07 18:20:15 +02:00
return str.replace(/(^\s*)|(\s*$)/g, '');
},
trimRight: function (str) {
if (String.prototype.trimRight) {
return str.trimRight();
}
return str.replace(/(\s*$)/g, '');
}
};
},{}]},{},[2]);
define("xss", (function (global) {
return function () {
var ret, fn;
fn = function (xss_noconflict) {
return {
filterXSS: window.filterXSS,
filterCSS: window.filterCSS
}
};
ret = fn.apply(global, arguments);
return ret;
};
}(this)));
define('tpl!action', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<div class="message chat-msg chat-action ' +
__e(o.extra_classes) +
'" data-isodate="' +
__e(o.time) +
'" data-from="' +
__e(o.from) +
'">\n <span class="chat-msg-heading">\n <span class="chat-msg-author">**' +
__e(o.username) +
'</span>\n </span>\n <p class="chat-msg-text"><!-- message gets added here via renderMessage --></p>\n</div>\n';
return __p
};});
define('tpl!chatbox', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<div class="flyout box-flyout">\n <div class="chat-body">\n <div class="chat-content ';
if (o.show_send_button) { ;
__p += 'chat-content-sendbutton';
} ;
__p += '"></div>\n <div class="message-form-container"/>\n </div>\n</div>\n';
return __p
};});
define('tpl!chatbox_head', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<div class="chat-head chat-head-chatbox row no-gutters">\n <div class="col">\n <div class="row no-gutters">\n <canvas class="avatar" height="36" width="36"></canvas>\n <div class="col chat-title" title="' +
__e(o.jid) +
'">\n ';
if (o.url) { ;
__p += '\n <a href="' +
__e(o.url) +
'" target="_blank" rel="noopener" class="user">\n ';
} ;
__p += '\n ' +
__e( o.fullname || o.jid ) +
'\n ';
if (o.url) { ;
__p += '\n </a>\n ';
} ;
__p += '\n <p class="user-custom-message">' +
__e( o.status ) +
'</p>\n </div>\n </div>\n </div>\n <div class="chatbox-buttons row no-gutters">\n <a class="chatbox-btn close-chatbox-button fa fa-close" title=' +
__e(o.info_close) +
2018-05-11 00:19:49 +02:00
'></a>\n <a class="chatbox-btn show-user-details-modal fa fa-vcard" title="' +
__e(o.info_details) +
'"></a>\n </div>\n</div>\n';
2018-05-07 18:20:15 +02:00
return __p
};});
define('tpl!chatbox_message_form', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<div class="message-form-container">\n<div class="new-msgs-indicator hidden">▼ ' +
__e( o.unread_msgs ) +
' ▼</div>\n<form class="sendXMPPMessage">\n ';
if (o.show_toolbar) { ;
__p += '\n <ul class="chat-toolbar no-text-select"></ul>\n ';
} ;
__p += '\n <input type="text" placeholder="' +
((__t = (o.label_spoiler_hint)) == null ? '' : __t) +
'" value="' +
((__t = ( o.hint_value )) == null ? '' : __t) +
'"\n class="';
if (!o.composing_spoiler) { ;
__p += ' hidden ';
} ;
__p += ' spoiler-hint"/>\n <textarea\n type="text"\n class="chat-textarea\n ';
if (o.show_send_button) { ;
__p += ' chat-textarea-send-button ';
} ;
__p += '\n ';
if (o.composing_spoiler) { ;
__p += ' spoiler ';
} ;
__p += '"\n placeholder="' +
__e(o.label_personal_message) +
'">' +
((__t = ( o.message_value )) == null ? '' : __t) +
'</textarea>\n ';
if (o.show_send_button) { ;
__p += '\n <button type="submit" class="pure-button send-button">' +
__e( o.label_send ) +
'</button>\n ';
} ;
__p += '\n</form>\n</div>\n';
return __p
};});
define('tpl!emojis', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<div class="emoji-picker-container">\n';
_.forEach(o.emojis_by_category, function (obj, category) { ;
__p += '\n <ul class="emoji-picker emoji-picker-' +
__e(category) +
' ';
if (o.current_category !== category) { ;
__p += ' hidden ';
} ;
__p += '">\n ';
_.forEach(o.emojis_by_category[category], function (emoji) { ;
__p += '\n <li class="emoji insert-emoji ';
if (o.shouldBeHidden(emoji._shortname, o.current_skintone, o.toned_emojis)) { ;
__p += ' hidden ';
}; ;
__p += '"\n data-emoji="' +
__e(emoji._shortname) +
'">\n <a href="#" data-emoji="' +
__e(emoji._shortname) +
'"> ' +
((__t = ( o.transform(emoji._shortname) )) == null ? '' : __t) +
' </a>\n </li>\n ';
}); ;
__p += '\n </ul>\n';
}); ;
__p += '\n<ul class="emoji-toolbar">\n <li class="emoji-category-picker">\n <ul>\n ';
_.forEach(o.emojis_by_category, function (obj, category) { ;
__p += '\n <li data-category="' +
__e(category) +
'" class="emoji-category ';
if (o.current_category === category) { ;
__p += ' picked ';
} ;
__p += '">\n <a class="pick-category" href="#" data-category="' +
__e(category) +
'"> ' +
((__t = ( o.transform(o.emojis_by_category[category][0]._shortname) )) == null ? '' : __t) +
' </a>\n </li>\n ';
}); ;
__p += '\n </ul>\n </li>\n <li class="emoji-skintone-picker">\n <ul>\n ';
_.forEach(o.skintones, function (skintone) { ;
__p += '\n <li data-skintone="' +
__e(skintone) +
'" class="emoji-skintone ';
if (o.current_skintone === skintone) { ;
__p += ' picked ';
} ;
__p += '">\n <a class="pick-skintone" href="#" data-skintone="' +
__e(skintone) +
'"> ' +
((__t = ( o.transform(':'+skintone+':') )) == null ? '' : __t) +
' </a>\n </li>\n ';
}); ;
__p += '\n </ul>\n </li>\n</ul>\n</div>\n';
return __p
};});
define('tpl!error_message', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<div class="message chat-info chat-error" data-isodate="' +
__e(o.isodate) +
'">' +
__e(o.message) +
'</div>\n';
return __p
};});
define('tpl!help_message', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<div class="message chat-info ';
if (o.type !== 'info') { ;
__p += ' chat-' +
__e(o.type) +
' ';
} ;
__p += '" data-isodate="' +
__e(o.isodate) +
'">' +
((__t = (o.message)) == null ? '' : __t) +
'</div>\n';
return __p
};});
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
define('tpl!info', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<div class="message chat-info ' +
__e(o.extra_classes) +
'"\n data-isodate="' +
__e(o.isodate) +
'"\n ' +
__e(o.data) +
'>' +
__e(o.message) +
'</div>\n';
return __p
};});
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
define('tpl!new_day', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<div class="message date-separator" data-isodate="' +
__e(o.isodate) +
'">\n <hr class="separator">\n <time class="separator-text" datetime="' +
__e(o.isodate) +
'"><span>' +
__e(o.datestring) +
'</span></time>\n</div>\n';
return __p
};});
2018-03-27 18:26:56 +02:00
2016-05-03 17:37:10 +02:00
2018-05-11 00:19:49 +02:00
define('tpl!user_details_modal', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<div class="modal fade" id="user-profile-modal" tabindex="-1" role="dialog" aria-labelledby="user-profile-modal-label" aria-hidden="true">\n <div class="modal-dialog" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title" id="user-profile-modal-label">' +
__e(o.display_name) +
'</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="' +
__e(o.label_close) +
'"><span aria-hidden="true">&times;</span></button>\n </div>\n <div class="modal-body">\n ';
if (o.image) { ;
__p += '\n <img alt="' +
__e(o.alt_profile_image) +
'"\n class="img-thumbnail avatar align-self-center mb-3"\n height="100" width="100" src="data:' +
__e(o.image_type) +
';base64,' +
__e(o.image) +
'"/>\n ';
} ;
__p += '\n ';
if (o.fullname) { ;
__p += '\n <p><label>' +
__e(o.label_fullname) +
':</label>&nbsp;' +
__e(o.fullname) +
'</p>\n ';
} ;
__p += '\n <p><label>' +
__e(o.label_jid) +
':</label>&nbsp;' +
__e(o.jid) +
'</p>\n ';
if (o.nickname) { ;
__p += '\n <p><label>' +
__e(o.label_nickname) +
':</label>&nbsp;' +
__e(o.nickname) +
'</p>\n ';
} ;
__p += '\n ';
if (o.url) { ;
__p += '\n <p><label>' +
__e(o.label_url) +
':</label>&nbsp;<a target="_blank" rel="noopener" href="' +
__e(o.url) +
'">' +
__e(o.url) +
'</a></p>\n ';
} ;
__p += '\n ';
if (o.email) { ;
__p += '\n <p><label>' +
__e(o.label_email) +
':</label>&nbsp;<a href="mailto:' +
__e(o.email) +
'">' +
__e(o.email) +
'</a></p>\n ';
} ;
__p += '\n ';
if (o.role) { ;
__p += '\n <p><label>' +
__e(o.label_role) +
':</label>&nbsp;' +
__e(o.role) +
'</p>\n ';
} ;
__p += '\n </div>\n <div class="modal-footer">\n ';
if (o.allow_contact_removal && o.is_roster_contact) { ;
2018-05-15 10:32:13 +02:00
__p += '\n <button type="button" class="btn btn-danger remove-contact"><i class="fa fa-trash"> </i>' +
2018-05-11 00:19:49 +02:00
__e(o.label_remove) +
'</button>\n ';
} ;
2018-05-15 10:32:13 +02:00
__p += '\n <button type="button" class="btn btn-info refresh-contact"><i class="fa fa-refresh"> </i>' +
__e(o.label_refresh) +
'</button>\n <button type="button" class="btn btn-secondary" data-dismiss="modal">' +
2018-05-11 00:19:49 +02:00
__e(o.label_close) +
'</button>\n </div>\n </div>\n </div>\n</div>\n';
return __p
};});
2018-05-07 18:20:15 +02:00
define('tpl!toolbar_fileupload', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<li class="upload-file">\n <a class="fa fa-paperclip" title="' +
__e(o.tooltip_upload_file) +
'"></a>\n <input type="file" class="fileupload" multiple style="display:none"/>\n</li> \n';
return __p
};});
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
define('tpl!spinner', ['lodash'], function(_) {return function(o) {
var __t, __p = '';
__p += '<span class="spinner fa fa-spinner centered"/>\n';
return __p
};});
define('tpl!spoiler_button', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<li class="toggle-compose-spoiler fa ';
if (o.composing_spoiler) { ;
__p += ' fa-eye-slash ';
} ;
__p += ' ';
if (!o.composing_spoiler) { ;
__p += ' fa-eye ';
} ;
__p += '"\n title="' +
((__t = ( o.label_toggle_spoiler )) == null ? '' : __t) +
'"></a>\n</li>\n';
return __p
};});
define('tpl!status_message', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<div class="message chat-info chat-status"\n data-isodate="' +
__e(o.isodate) +
'"\n data-status="' +
__e(o.from) +
'">' +
__e(o.message) +
'</div>\n';
return __p
};});
define('tpl!toolbar', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
if (o.use_emoji) { ;
2018-05-15 10:32:13 +02:00
__p += '\n<li class="toggle-toolbar-menu toggle-smiley dropup">\n <a class="toggle-smiley fa fa-smile-o" title="' +
2018-05-07 18:20:15 +02:00
__e(o.tooltip_insert_smiley) +
'" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></a> \n <div class="emoji-picker dropdown-menu toolbar-menu"></div>\n</li>\n';
} ;
__p += '\n';
if (o.show_call_button) { ;
__p += '\n<li class="toggle-call fa fa-phone" title="' +
__e(o.label_start_call) +
'"></li>\n';
} ;
__p += '\n';
return __p
};});
2018-05-11 00:19:49 +02:00
define('tpl!alert_modal', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<div class="modal" tabindex="-1" role="dialog">\n <div class="modal-dialog" role="document">\n <div class="modal-content">\n <div class="modal-header ' +
__e(o.type) +
'">\n <h5 class="modal-title">' +
__e(o.title) +
'</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">&times;</span>\n </button>\n </div>\n <div class="modal-body">';
_.each(o.messages, function (message) { ;
__p += '\n <p>' +
__e(message) +
'</p>\n ';
}) ;
__p += '\n </div>\n </div>\n </div>\n</div>\n';
return __p
};});
// Converse.js
// http://conversejs.org
//
// Copyright (c) 2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2)
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define('converse-modal',[
"converse-core",
"tpl!alert_modal",
"bootstrap",
"backbone.vdomview"
], factory);
}
}(this, function (converse, tpl_alert_modal, bootstrap) {
"use strict";
const { Strophe, Backbone, _ } = converse.env;
converse.plugins.add('converse-modal', {
initialize () {
const { _converse } = this;
_converse.BootstrapModal = Backbone.VDOMView.extend({
initialize () {
this.render().insertIntoDOM();
this.modal = new bootstrap.Modal(this.el, {
backdrop: 'static',
keyboard: true
});
this.el.addEventListener('hide.bs.modal', (event) => {
if (!_.isNil(this.trigger_el)) {
this.trigger_el.classList.remove('selected');
}
}, false);
},
insertIntoDOM () {
const container_el = _converse.chatboxviews.el.querySelector("#converse-modals");
container_el.insertAdjacentElement('beforeEnd', this.el);
},
show (ev) {
if (ev) {
ev.preventDefault();
this.trigger_el = ev.target;
this.trigger_el.classList.add('selected');
}
this.modal.show();
}
});
_converse.Alert = _converse.BootstrapModal.extend({
initialize () {
_converse.BootstrapModal.prototype.initialize.apply(this, arguments);
this.model.on('change', this.render, this);
},
toHTML () {
return tpl_alert_modal(this.model.toJSON());
}
});
/************************ BEGIN API ************************/
// We extend the default converse.js API to add methods specific to MUC chat rooms.
let alert
_.extend(_converse.api, {
'alert': {
'show' (type, title, messages) {
if (_.isString(messages)) {
messages = [messages];
}
if (type === Strophe.LogLevel.ERROR) {
type = 'alert-danger';
} else if (type === Strophe.LogLevel.INFO) {
type = 'alert-info';
} else if (type === Strophe.LogLevel.WARN) {
type = 'alert-warning';
}
if (_.isUndefined(alert)) {
const model = new Backbone.Model({
'title': title,
'messages': messages,
'type': type
})
alert = new _converse.Alert({'model': model});
} else {
alert.model.set({
'title': title,
'messages': messages,
'type': type
});
}
alert.show();
}
}
});
}
});
}));
2018-05-07 18:20:15 +02:00
/**
* filesize
*
* @copyright 2018 Jason Mulligan <jason.mulligan@avoidwork.com>
* @license BSD-3-Clause
* @version 3.6.1
*/
2018-05-07 18:20:15 +02:00
(function (global) {
var b = /^(b|B)$/,
symbol = {
iec: {
bits: ["b", "Kib", "Mib", "Gib", "Tib", "Pib", "Eib", "Zib", "Yib"],
bytes: ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]
},
jedec: {
bits: ["b", "Kb", "Mb", "Gb", "Tb", "Pb", "Eb", "Zb", "Yb"],
bytes: ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
}
},
fullform = {
iec: ["", "kibi", "mebi", "gibi", "tebi", "pebi", "exbi", "zebi", "yobi"],
jedec: ["", "kilo", "mega", "giga", "tera", "peta", "exa", "zetta", "yotta"]
};
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
/**
* filesize
*
* @method filesize
* @param {Mixed} arg String, Int or Float to transform
* @param {Object} descriptor [Optional] Flags
* @return {String} Readable file size String
*/
function filesize(arg) {
var descriptor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
var result = [],
val = 0,
e = void 0,
base = void 0,
bits = void 0,
ceil = void 0,
full = void 0,
fullforms = void 0,
neg = void 0,
num = void 0,
output = void 0,
round = void 0,
unix = void 0,
separator = void 0,
spacer = void 0,
standard = void 0,
symbols = void 0;
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (isNaN(arg)) {
throw new Error("Invalid arguments");
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
bits = descriptor.bits === true;
unix = descriptor.unix === true;
base = descriptor.base || 2;
round = descriptor.round !== void 0 ? descriptor.round : unix ? 1 : 2;
separator = descriptor.separator !== void 0 ? descriptor.separator || "" : "";
spacer = descriptor.spacer !== void 0 ? descriptor.spacer : unix ? "" : " ";
symbols = descriptor.symbols || descriptor.suffixes || {};
standard = base === 2 ? descriptor.standard || "jedec" : "jedec";
output = descriptor.output || "string";
full = descriptor.fullform === true;
fullforms = descriptor.fullforms instanceof Array ? descriptor.fullforms : [];
e = descriptor.exponent !== void 0 ? descriptor.exponent : -1;
num = Number(arg);
neg = num < 0;
ceil = base > 2 ? 1000 : 1024;
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
// Flipping a negative number to determine the size
if (neg) {
num = -num;
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
// Determining the exponent
if (e === -1 || isNaN(e)) {
e = Math.floor(Math.log(num) / Math.log(ceil));
if (e < 0) {
e = 0;
}
}
// Exceeding supported length, time to reduce & multiply
if (e > 8) {
e = 8;
}
// Zero is now a special case because bytes divide by 1
if (num === 0) {
result[0] = 0;
result[1] = unix ? "" : symbol[standard][bits ? "bits" : "bytes"][e];
} else {
val = num / (base === 2 ? Math.pow(2, e * 10) : Math.pow(1000, e));
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (bits) {
val = val * 8;
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (val >= ceil && e < 8) {
val = val / ceil;
e++;
}
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
result[0] = Number(val.toFixed(e > 0 ? round : 0));
result[1] = base === 10 && e === 1 ? bits ? "kb" : "kB" : symbol[standard][bits ? "bits" : "bytes"][e];
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (unix) {
result[1] = standard === "jedec" ? result[1].charAt(0) : e > 0 ? result[1].replace(/B$/, "") : result[1];
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (b.test(result[1])) {
result[0] = Math.floor(result[0]);
result[1] = "";
}
}
}
2017-07-22 22:21:05 +02:00
2018-05-07 18:20:15 +02:00
// Decorating a 'diff'
if (neg) {
result[0] = -result[0];
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
// Applying custom symbol
result[1] = symbols[result[1]] || result[1];
2018-05-07 18:20:15 +02:00
// Returning Array, Object, or String (default)
if (output === "array") {
return result;
}
2018-05-07 18:20:15 +02:00
if (output === "exponent") {
return e;
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (output === "object") {
return { value: result[0], suffix: result[1], symbol: result[1] };
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (full) {
result[1] = fullforms[e] ? fullforms[e] : fullform[standard][e] + (bits ? "bit" : "byte") + (result[0] === 1 ? "" : "s");
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (separator.length > 0) {
result[0] = result[0].toString().replace(".", separator);
}
2018-05-07 18:20:15 +02:00
return result.join(spacer);
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
// Partial application for functional programming
filesize.partial = function (opt) {
return function (arg) {
return filesize(arg, opt);
};
};
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
// CommonJS, AMD, script tag
if (typeof exports !== "undefined") {
module.exports = filesize;
} else if (typeof define === "function" && define.amd) {
define('filesize',[],function () {
return filesize;
});
} else {
global.filesize = filesize;
}
})(typeof window !== "undefined" ? window : global);
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
define('tpl!chatboxes', ['lodash'], function(_) {return function(o) {
var __t, __p = '';
__p += '<div class="converse-chatboxes row no-gutters"></div>\n<div id="converse-modals" class="modals"></div>\n';
return __p
};});
2018-03-25 21:21:43 +02:00
// Converse.js
// http://conversejs.org
//
// Copyright (c) 2012-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2)
(function (root, factory) {
2018-05-07 18:20:15 +02:00
define('converse-chatboxes',["converse-core", "emojione", "filesize", "tpl!chatboxes", "backbone.overview", "form-utils"], factory);
})(this, function (converse, emojione, filesize, tpl_chatboxes) {
"use strict";
2018-03-25 21:21:43 +02:00
var _converse$env = converse.env,
2018-05-07 18:20:15 +02:00
$msg = _converse$env.$msg,
Backbone = _converse$env.Backbone,
Promise = _converse$env.Promise,
2018-05-07 18:20:15 +02:00
Strophe = _converse$env.Strophe,
b64_sha1 = _converse$env.b64_sha1,
2018-05-07 18:20:15 +02:00
moment = _converse$env.moment,
sizzle = _converse$env.sizzle,
2018-05-07 18:20:15 +02:00
utils = _converse$env.utils,
_ = _converse$env._;
var u = converse.env.utils;
converse.plugins.add('converse-chatboxes', {
2018-05-15 10:32:13 +02:00
dependencies: ["converse-roster", "converse-vcard"],
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
2018-05-07 18:20:15 +02:00
disconnect: function disconnect() {
var _converse = this.__super__._converse;
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
_converse.chatboxviews.closeAllChatBoxes();
return this.__super__.disconnect.apply(this, arguments);
},
logOut: function logOut() {
var _converse = this.__super__._converse;
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
_converse.chatboxviews.closeAllChatBoxes();
return this.__super__.logOut.apply(this, arguments);
},
2018-05-07 18:20:15 +02:00
initStatus: function initStatus(reconnecting) {
var _converse = this.__super__._converse;
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (!reconnecting) {
_converse.chatboxviews.closeAllChatBoxes();
}
2018-05-07 18:20:15 +02:00
return this.__super__.initStatus.apply(this, arguments);
}
},
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
var _converse = this._converse,
__ = _converse.__; // Configuration values for this plugin
// ====================================
// Refer to docs/source/configuration.rst for explanations of these
// configuration settings.
2018-03-25 21:21:43 +02:00
_converse.api.settings.update({
2018-05-07 18:20:15 +02:00
auto_join_private_chats: []
});
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
_converse.api.promises.add(['chatBoxesFetched', 'chatBoxesInitialized', 'privateChatsAutoJoined']);
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
function openChat(jid) {
if (!utils.isValidJID(jid)) {
return _converse.log("Invalid JID \"".concat(jid, "\" provided in URL fragment"), Strophe.LogLevel.WARN);
}
2018-05-07 18:20:15 +02:00
Promise.all([_converse.api.waitUntil('rosterContactsFetched'), _converse.api.waitUntil('chatBoxesFetched')]).then(function () {
_converse.api.chats.open(jid);
});
}
2018-05-07 18:20:15 +02:00
_converse.router.route('converse/chat?jid=:jid', openChat);
2017-12-20 18:08:08 +01:00
2018-05-07 18:20:15 +02:00
_converse.Message = Backbone.Model.extend({
defaults: function defaults() {
2018-05-07 18:20:15 +02:00
return {
'msgid': _converse.connection.getUniqueId(),
'time': moment().format()
};
},
initialize: function initialize() {
2018-05-15 10:32:13 +02:00
this.setVCard();
2018-05-07 18:20:15 +02:00
if (this.get('file')) {
this.on('change:put', this.uploadFile, this);
if (!_.includes([_converse.SUCCESS, _converse.FAILURE], this.get('upload'))) {
this.getRequestSlotURL();
}
}
if (this.isOnlyChatStateNotification()) {
window.setTimeout(this.destroy.bind(this), 20000);
}
},
2018-05-15 10:32:13 +02:00
setVCard: function setVCard() {
if (this.get('type') === 'groupchat') {
var chatbox = this.collection.chatbox,
nick = Strophe.getResourceFromJid(this.get('from'));
if (chatbox.get('nick') === nick) {
this.vcard = _converse.xmppstatus.vcard;
} else {
var occupant = chatbox.occupants.findWhere({
'nick': nick
});
var jid = occupant && occupant.get('jid') ? occupant.get('jid') : this.get('from');
this.vcard = _converse.vcards.findWhere({
'jid': jid
}) || _converse.vcards.create({
'jid': jid
});
}
} else {
var _jid = this.get('from');
this.vcard = _converse.vcards.findWhere({
'jid': _jid
}) || _converse.vcards.create({
'jid': _jid
});
}
},
2018-05-07 18:20:15 +02:00
isOnlyChatStateNotification: function isOnlyChatStateNotification() {
return u.isOnlyChatStateNotification(this);
},
getDisplayName: function getDisplayName() {
if (this.get('type') === 'groupchat') {
return this.get('nick');
} else {
return this.vcard.get('fullname') || this.get('from');
}
},
sendSlotRequestStanza: function sendSlotRequestStanza() {
var _this = this;
2017-12-20 18:08:08 +01:00
2018-05-07 18:20:15 +02:00
/* Send out an IQ stanza to request a file upload slot.
*
* https://xmpp.org/extensions/xep-0363.html#request
*/
2018-05-07 18:20:15 +02:00
var file = this.get('file');
return new Promise(function (resolve, reject) {
var iq = converse.env.$iq({
'from': _converse.jid,
'to': _this.get('slot_request_url'),
'type': 'get'
}).c('request', {
'xmlns': Strophe.NS.HTTPUPLOAD,
'filename': file.name,
'size': file.size,
'content-type': file.type
});
2017-12-20 18:08:08 +01:00
2018-05-07 18:20:15 +02:00
_converse.connection.sendIQ(iq, resolve, reject);
});
2018-05-07 18:20:15 +02:00
},
getRequestSlotURL: function getRequestSlotURL() {
var _this2 = this;
2018-05-07 18:20:15 +02:00
this.sendSlotRequestStanza().then(function (stanza) {
var slot = stanza.querySelector('slot');
2017-12-20 18:08:08 +01:00
2018-05-07 18:20:15 +02:00
if (slot) {
_this2.save({
'get': slot.querySelector('get').getAttribute('url'),
'put': slot.querySelector('put').getAttribute('url')
});
} else {
return _this2.save({
'type': 'error',
'message': __("Sorry, could not determine file upload URL.")
});
}
}).catch(function (e) {
_converse.log(e, Strophe.LogLevel.ERROR);
return _this2.save({
'type': 'error',
'message': __("Sorry, could not determine upload URL.")
});
});
},
2018-05-07 18:20:15 +02:00
uploadFile: function uploadFile() {
var _this3 = this;
2018-05-07 18:20:15 +02:00
var xhr = new XMLHttpRequest();
2018-05-07 18:20:15 +02:00
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
_converse.log("Status: " + xhr.status, Strophe.LogLevel.INFO);
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (xhr.status === 200 || xhr.status === 201) {
_this3.save({
'upload': _converse.SUCCESS,
'oob_url': _this3.get('get'),
'message': _this3.get('get')
});
} else {
xhr.onerror();
}
}
};
2018-05-07 18:20:15 +02:00
xhr.upload.addEventListener("progress", function (evt) {
if (evt.lengthComputable) {
_this3.set('progress', evt.loaded / evt.total);
}
}, false);
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
xhr.onerror = function () {
var message = __('Sorry, could not succesfully upload your file.');
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (xhr.responseText) {
message += ' ' + __('Your server\'s response: "%1$s"', xhr.responseText);
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
_this3.save({
'type': 'error',
'upload': _converse.FAILURE,
'message': message
});
};
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
xhr.open('PUT', this.get('put'), true);
xhr.setRequestHeader("Content-type", 'application/octet-stream');
xhr.send(this.get('file'));
}
});
_converse.Messages = Backbone.Collection.extend({
model: _converse.Message,
comparator: 'time'
});
2018-05-23 04:27:33 +02:00
_converse.ChatBox = _converse.ModelWithVCardAndPresence.extend({
2018-05-07 18:20:15 +02:00
defaults: {
'bookmarked': false,
'chat_state': undefined,
'num_unread': 0,
'type': 'chatbox',
'message_type': 'chat',
'url': ''
},
initialize: function initialize() {
var _this4 = this;
2018-03-25 21:21:43 +02:00
2018-05-23 04:27:33 +02:00
_converse.ModelWithVCardAndPresence.prototype.initialize.apply(this, arguments);
2018-03-25 21:21:43 +02:00
2018-05-11 00:19:49 +02:00
_converse.api.waitUntil('rosterContactsFetched').then(function () {
_this4.addRelatedContact(_converse.roster.findWhere({
'jid': _this4.get('jid')
}));
});
2018-05-07 18:20:15 +02:00
this.messages = new _converse.Messages();
2018-05-23 04:27:33 +02:00
this.messages.browserStorage = new Backbone.BrowserStorage[_converse.storage](b64_sha1("converse.messages".concat(this.get('jid')).concat(_converse.bare_jid)));
2018-05-07 18:20:15 +02:00
this.messages.chatbox = this;
this.messages.on('change:upload', function (message) {
if (message.get('upload') === _converse.SUCCESS) {
_this4.sendMessageStanza(message);
}
});
this.on('change:chat_state', this.sendChatState, this);
this.save({
// The chat_state will be set to ACTIVE once the chat box is opened
// and we listen for change:chat_state, so shouldn't set it to ACTIVE here.
'box_id': b64_sha1(this.get('jid')),
'time_opened': this.get('time_opened') || moment().valueOf(),
'user_id': Strophe.getNodeFromJid(this.get('jid'))
});
},
2018-05-11 00:19:49 +02:00
addRelatedContact: function addRelatedContact(contact) {
if (!_.isUndefined(contact)) {
this.contact = contact;
this.trigger('contactAdded', contact);
}
},
getDisplayName: function getDisplayName() {
return this.vcard.get('fullname') || this.get('jid');
},
2018-05-07 18:20:15 +02:00
createMessageStanza: function createMessageStanza(message) {
/* Given a _converse.Message Backbone.Model, return the XML
* stanza that represents it.
*
2018-05-07 18:20:15 +02:00
* Parameters:
* (Object) message - The Backbone.Model representing the message
*/
2018-05-07 18:20:15 +02:00
var stanza = $msg({
'from': _converse.connection.jid,
'to': this.get('jid'),
'type': this.get('message_type'),
'id': message.get('msgid')
}).c('body').t(message.get('message')).up().c(_converse.ACTIVE, {
'xmlns': Strophe.NS.CHATSTATES
}).up();
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (message.get('is_spoiler')) {
if (message.get('spoiler_hint')) {
stanza.c('spoiler', {
'xmlns': Strophe.NS.SPOILER
}, message.get('spoiler_hint')).up();
} else {
stanza.c('spoiler', {
'xmlns': Strophe.NS.SPOILER
}).up();
}
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (message.get('file')) {
stanza.c('x', {
'xmlns': Strophe.NS.OUTOFBAND
}).c('url').t(message.get('message')).up();
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
return stanza;
},
2018-05-07 18:20:15 +02:00
sendMessageStanza: function sendMessageStanza(message) {
var messageStanza = this.createMessageStanza(message);
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
_converse.connection.send(messageStanza);
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (_converse.forward_messages) {
// Forward the message, so that other connected resources are also aware of it.
_converse.connection.send($msg({
'to': _converse.bare_jid,
'type': this.get('message_type'),
'id': message.get('msgid')
}).c('forwarded', {
'xmlns': Strophe.NS.FORWARD
}).c('delay', {
'xmns': Strophe.NS.DELAY,
'stamp': moment().format()
}).up().cnode(messageStanza.tree()));
}
},
getOutgoingMessageAttributes: function getOutgoingMessageAttributes(text, spoiler_hint) {
2018-05-07 18:20:15 +02:00
var fullname = _converse.xmppstatus.get('fullname'),
is_spoiler = this.get('composing_spoiler');
return {
2018-05-07 18:20:15 +02:00
'fullname': fullname,
'from': _converse.bare_jid,
'sender': 'me',
2018-05-07 18:20:15 +02:00
'time': moment().format(),
'message': text ? u.httpToGeoUri(emojione.shortnameToUnicode(text), _converse) : undefined,
'is_spoiler': is_spoiler,
'spoiler_hint': is_spoiler ? spoiler_hint : undefined
};
},
2018-05-07 18:20:15 +02:00
sendMessage: function sendMessage(attrs) {
/* Responsible for sending off a text message.
*
2018-05-07 18:20:15 +02:00
* Parameters:
* (Message) message - The chat message
*/
2018-05-07 18:20:15 +02:00
this.sendMessageStanza(this.messages.create(attrs));
},
sendChatState: function sendChatState() {
/* Sends a message with the status of the user in this chat session
* as taken from the 'chat_state' attribute of the chat box.
* See XEP-0085 Chat State Notifications.
*/
_converse.connection.send($msg({
'to': this.get('jid'),
2018-05-07 18:20:15 +02:00
'type': 'chat'
}).c(this.get('chat_state'), {
'xmlns': Strophe.NS.CHATSTATES
}).up().c('no-store', {
'xmlns': Strophe.NS.HINTS
}).up().c('no-permanent-store', {
'xmlns': Strophe.NS.HINTS
}));
},
2018-05-07 18:20:15 +02:00
sendFiles: function sendFiles(files) {
var _this5 = this;
2018-05-07 18:20:15 +02:00
_converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain).then(function (result) {
var item = result.pop(),
data = item.dataforms.where({
'FORM_TYPE': {
'value': Strophe.NS.HTTPUPLOAD,
'type': "hidden"
}
}).pop(),
max_file_size = window.parseInt(_.get(data, 'attributes.max-file-size.value')),
slot_request_url = _.get(item, 'id');
2018-05-07 18:20:15 +02:00
if (!slot_request_url) {
_this5.messages.create({
'message': __("Sorry, looks like file upload is not supported by your server."),
'type': 'error'
});
2018-05-07 18:20:15 +02:00
return;
}
2018-05-07 18:20:15 +02:00
_.each(files, function (file) {
if (!window.isNaN(max_file_size) && window.parseInt(file.size) > max_file_size) {
return _this5.messages.create({
'message': __('The size of your file, %1$s, exceeds the maximum allowed by your server, which is %2$s.', file.name, filesize(max_file_size)),
'type': 'error'
});
} else {
_this5.messages.create(_.extend(_this5.getOutgoingMessageAttributes(), {
'file': file,
'progress': 0,
'slot_request_url': slot_request_url,
'type': _this5.get('message_type')
}));
}
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
},
getMessageBody: function getMessageBody(message) {
var type = message.getAttribute('type');
return type === 'error' ? _.propertyOf(message.querySelector('error text'))('textContent') : _.propertyOf(message.querySelector('body'))('textContent');
},
2018-05-07 18:20:15 +02:00
getMessageAttributesFromStanza: function getMessageAttributesFromStanza(message, delay, original_stanza) {
/* Parses a passed in message stanza and returns an object
* of attributes.
*
2018-05-07 18:20:15 +02:00
* Parameters:
* (XMLElement) message - The message stanza
* (XMLElement) delay - The <delay> node from the
* stanza, if there was one.
* (XMLElement) original_stanza - The original stanza,
* that contains the message stanza, if it was
* contained, otherwise it's the message stanza itself.
*/
2018-05-07 18:20:15 +02:00
delay = delay || message.querySelector('delay');
var _converse = this.__super__._converse,
__ = _converse.__,
spoiler = message.querySelector("spoiler[xmlns=\"".concat(Strophe.NS.SPOILER, "\"]")),
chat_state = message.getElementsByTagName(_converse.COMPOSING).length && _converse.COMPOSING || message.getElementsByTagName(_converse.PAUSED).length && _converse.PAUSED || message.getElementsByTagName(_converse.INACTIVE).length && _converse.INACTIVE || message.getElementsByTagName(_converse.ACTIVE).length && _converse.ACTIVE || message.getElementsByTagName(_converse.GONE).length && _converse.GONE;
var attrs = {
'type': message.getAttribute('type'),
'chat_state': chat_state,
'delayed': !_.isNull(delay),
'message': this.getMessageBody(message) || undefined,
'msgid': message.getAttribute('id'),
'time': delay ? delay.getAttribute('stamp') : moment().format(),
'is_spoiler': !_.isNull(spoiler)
};
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (attrs.type === 'groupchat') {
attrs.from = message.getAttribute('from');
attrs.nick = Strophe.unescapeNode(Strophe.getResourceFromJid(attrs.from));
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (attrs.from === this.get('nick')) {
attrs.sender = 'me';
} else {
attrs.sender = 'them';
}
} else {
attrs.from = Strophe.getBareJidFromJid(message.getAttribute('from'));
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (attrs.from === _converse.bare_jid) {
attrs.sender = 'me';
attrs.fullname = _converse.xmppstatus.get('fullname');
} else {
attrs.sender = 'them';
attrs.fullname = this.get('fullname');
}
2018-05-07 18:20:15 +02:00
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
_.each(sizzle("x[xmlns=\"".concat(Strophe.NS.OUTOFBAND, "\"]"), message), function (xform) {
attrs['oob_url'] = xform.querySelector('url').textContent;
attrs['oob_desc'] = xform.querySelector('url').textContent;
});
2018-03-27 18:26:56 +02:00
2018-05-07 18:20:15 +02:00
if (spoiler) {
attrs.spoiler_hint = spoiler.textContent.length > 0 ? spoiler.textContent : '';
}
2018-05-07 18:20:15 +02:00
return attrs;
},
2018-05-07 18:20:15 +02:00
createMessage: function createMessage(message, delay, original_stanza) {
/* Create a Backbone.Message object inside this chat box
* based on the identified message stanza.
*/
var attrs = this.getMessageAttributesFromStanza.apply(this, arguments);
var is_csn = u.isOnlyChatStateNotification(attrs);
2018-05-07 18:20:15 +02:00
if (is_csn && attrs.delayed) {
// No need showing old CSNs
return;
} else if (!is_csn && !attrs.file && !attrs.message && !attrs.oob_url && attrs.type !== 'error') {
// TODO: handle <subject> messages (currently being done by ChatRoom)
return;
} else {
return this.messages.create(attrs);
}
},
newMessageWillBeHidden: function newMessageWillBeHidden() {
/* Returns a boolean to indicate whether a newly received
* message will be visible to the user or not.
*/
2018-05-07 18:20:15 +02:00
return this.get('hidden') || this.get('minimized') || this.isScrolledUp() || _converse.windowState === 'hidden';
},
incrementUnreadMsgCounter: function incrementUnreadMsgCounter(stanza) {
/* Given a newly received message, update the unread counter if
* necessary.
*/
if (_.isNull(stanza.querySelector('body'))) {
return; // The message has no text
}
if (utils.isNewMessage(stanza) && this.newMessageWillBeHidden()) {
this.save({
'num_unread': this.get('num_unread') + 1
});
2018-05-07 18:20:15 +02:00
_converse.incrementMsgCounter();
}
},
2018-05-07 18:20:15 +02:00
clearUnreadMsgCounter: function clearUnreadMsgCounter() {
u.safeSave(this, {
'num_unread': 0
2018-03-25 21:21:43 +02:00
});
2018-05-07 18:20:15 +02:00
},
isScrolledUp: function isScrolledUp() {
return this.get('scrolled', true);
}
});
_converse.ChatBoxes = Backbone.Collection.extend({
comparator: 'time_opened',
model: function model(attrs, options) {
return new _converse.ChatBox(attrs, options);
},
registerMessageHandler: function registerMessageHandler() {
_converse.connection.addHandler(this.onMessage.bind(this), null, 'message', 'chat');
2018-05-07 18:20:15 +02:00
_converse.connection.addHandler(this.onErrorMessage.bind(this), null, 'message', 'error');
},
2018-05-07 18:20:15 +02:00
chatBoxMayBeShown: function chatBoxMayBeShown(chatbox) {
return true;
},
onChatBoxesFetched: function onChatBoxesFetched(collection) {
var _this6 = this;
2018-05-07 18:20:15 +02:00
/* Show chat boxes upon receiving them from sessionStorage */
collection.each(function (chatbox) {
if (_this6.chatBoxMayBeShown(chatbox)) {
chatbox.trigger('show');
}
});
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
_converse.emit('chatBoxesFetched');
},
onConnected: function onConnected() {
this.browserStorage = new Backbone.BrowserStorage[_converse.storage](b64_sha1("converse.chatboxes-".concat(_converse.bare_jid)));
this.registerMessageHandler();
this.fetch({
add: true,
success: this.onChatBoxesFetched.bind(this)
});
},
2018-05-07 18:20:15 +02:00
onErrorMessage: function onErrorMessage(message) {
/* Handler method for all incoming error message stanzas
*/
// TODO: we can likely just reuse "onMessage" below
var from_jid = Strophe.getBareJidFromJid(message.getAttribute('from'));
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (utils.isSameBareJID(from_jid, _converse.bare_jid)) {
return true;
} // Get chat box, but only create a new one when the message has a body.
var chatbox = this.getChatBox(from_jid);
if (!chatbox) {
return true;
}
chatbox.createMessage(message, null, message);
return true;
},
onMessage: function onMessage(message) {
/* Handler method for all incoming single-user chat "message"
* stanzas.
*
2018-05-07 18:20:15 +02:00
* Parameters:
* (XMLElement) message - The incoming message stanza
*/
2018-05-07 18:20:15 +02:00
var contact_jid,
delay,
resource,
from_jid = message.getAttribute('from'),
to_jid = message.getAttribute('to');
var original_stanza = message,
to_resource = Strophe.getResourceFromJid(to_jid),
is_carbon = !_.isNull(message.querySelector("received[xmlns=\"".concat(Strophe.NS.CARBONS, "\"]")));
if (_converse.filter_by_resource && to_resource && to_resource !== _converse.resource) {
_converse.log("onMessage: Ignoring incoming message intended for a different resource: ".concat(to_jid), Strophe.LogLevel.INFO);
return true;
} else if (utils.isHeadlineMessage(_converse, message)) {
// XXX: Ideally we wouldn't have to check for headline
// messages, but Prosody sends headline messages with the
// wrong type ('chat'), so we need to filter them out here.
_converse.log("onMessage: Ignoring incoming headline message sent with type 'chat' from JID: ".concat(from_jid), Strophe.LogLevel.INFO);
return true;
}
2018-05-07 18:20:15 +02:00
var forwarded = message.querySelector('forwarded');
2018-05-07 18:20:15 +02:00
if (!_.isNull(forwarded)) {
var forwarded_message = forwarded.querySelector('message');
var forwarded_from = forwarded_message.getAttribute('from');
2018-05-07 18:20:15 +02:00
if (is_carbon && Strophe.getBareJidFromJid(forwarded_from) !== from_jid) {
// Prevent message forging via carbons
//
// https://xmpp.org/extensions/xep-0280.html#security
return true;
}
2018-05-07 18:20:15 +02:00
message = forwarded_message;
delay = forwarded.querySelector('delay');
from_jid = message.getAttribute('from');
to_jid = message.getAttribute('to');
}
2018-05-07 18:20:15 +02:00
var from_bare_jid = Strophe.getBareJidFromJid(from_jid),
from_resource = Strophe.getResourceFromJid(from_jid),
is_me = from_bare_jid === _converse.bare_jid;
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (is_me) {
// I am the sender, so this must be a forwarded message...
contact_jid = Strophe.getBareJidFromJid(to_jid);
resource = Strophe.getResourceFromJid(to_jid);
} else {
contact_jid = from_bare_jid;
resource = from_resource;
} // Get chat box, but only create a new one when the message has a body.
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
var attrs = {
'fullname': _.get(_converse.api.contacts.get(contact_jid), 'attributes.fullname')
};
var chatbox = this.getChatBox(contact_jid, attrs, !_.isNull(message.querySelector('body'))),
msgid = message.getAttribute('id');
2018-05-07 18:20:15 +02:00
if (chatbox) {
var messages = msgid && chatbox.messages.findWhere({
msgid: msgid
}) || [];
if (_.isEmpty(messages)) {
// Only create the message when we're sure it's not a
// duplicate
chatbox.incrementUnreadMsgCounter(original_stanza);
chatbox.createMessage(message, delay, original_stanza);
}
}
_converse.emit('message', {
'stanza': original_stanza,
'chatbox': chatbox
});
2018-05-07 18:20:15 +02:00
return true;
},
2018-05-07 18:20:15 +02:00
getChatBox: function getChatBox(jid) {
var attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var create = arguments.length > 2 ? arguments[2] : undefined;
/* Returns a chat box or optionally return a newly
* created one if one doesn't exist.
*
* Parameters:
2018-05-07 18:20:15 +02:00
* (String) jid - The JID of the user whose chat box we want
* (Boolean) create - Should a new chat box be created if none exists?
* (Object) attrs - Optional chat box atributes.
*/
2018-05-07 18:20:15 +02:00
if (_.isObject(jid)) {
create = attrs;
attrs = jid;
jid = attrs.jid;
}
2018-05-07 18:20:15 +02:00
jid = Strophe.getBareJidFromJid(jid.toLowerCase());
var chatbox = this.get(Strophe.getBareJidFromJid(jid));
2018-05-07 18:20:15 +02:00
if (!chatbox && create) {
_.extend(attrs, {
'jid': jid,
'id': jid
});
chatbox = this.create(attrs, {
'error': function error(model, response) {
_converse.log(response.responseText);
}
});
}
return chatbox;
}
});
_converse.ChatBoxViews = Backbone.Overview.extend({
_ensureElement: function _ensureElement() {
/* Override method from backbone.js
* If the #conversejs element doesn't exist, create it.
*/
2018-05-07 18:20:15 +02:00
if (!this.el) {
var el = _converse.root.querySelector('#conversejs');
2018-05-07 18:20:15 +02:00
if (_.isNull(el)) {
el = document.createElement('div');
el.setAttribute('id', 'conversejs');
2018-05-07 18:20:15 +02:00
var body = _converse.root.querySelector('body');
if (body) {
body.appendChild(el);
} else {
// Perhaps inside a web component?
_converse.root.appendChild(el);
}
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
if (_.includes(['mobile', 'fullscreen'], _converse.view_mode)) {
el.classList.add('fullscreen');
}
2018-05-07 18:20:15 +02:00
el.innerHTML = '';
this.setElement(el, false);
} else {
this.setElement(_.result(this, 'el'), false);
2018-03-25 21:21:43 +02:00
}
},
2018-05-07 18:20:15 +02:00
initialize: function initialize() {
this.model.on("add", this.onChatBoxAdded, this);
this.model.on("destroy", this.removeChat, this);
this.el.classList.add("converse-".concat(_converse.view_mode));
this.render();
},
render: function render() {
try {
this.el.innerHTML = tpl_chatboxes();
} catch (e) {
this._ensureElement();
2018-05-07 18:20:15 +02:00
this.el.innerHTML = tpl_chatboxes();
}
this.row_el = this.el.querySelector('.row');
},
insertRowColumn: function insertRowColumn(el) {
/* Add a new DOM element (likely a chat box) into the
* the row managed by this overview.
*/
2018-05-07 18:20:15 +02:00
this.row_el.insertAdjacentElement('afterBegin', el);
},
onChatBoxAdded: function onChatBoxAdded(item) {
// Views aren't created here, since the core code doesn't
// contain any views. Instead, they're created in overrides in
// plugins, such as in converse-chatview.js and converse-muc.js
return this.get(item.get('id'));
},
removeChat: function removeChat(item) {
this.remove(item.get('id'));
},
closeAllChatBoxes: function closeAllChatBoxes() {
/* This method gets overridden in src/converse-controlbox.js if
2018-05-15 10:32:13 +02:00
* the controlbox plugin is active.
*/
2018-05-07 18:20:15 +02:00
this.each(function (view) {
view.close();
});
return this;
},
chatBoxMayBeShown: function chatBoxMayBeShown(chatbox) {
return this.model.chatBoxMayBeShown(chatbox);
}
}); // TODO: move to converse-chatboxviews.js and use there in the API
2018-05-07 18:20:15 +02:00
_converse.getViewForChatBox = function (chatbox) {
if (!chatbox) {
return;
}
2018-05-07 18:20:15 +02:00
return _converse.chatboxviews.get(chatbox.get('id'));
};
2018-03-27 18:26:56 +02:00
2018-05-07 18:20:15 +02:00
function autoJoinChats() {
/* Automatically join private chats, based on the
* "auto_join_private_chats" configuration setting.
*/
_.each(_converse.auto_join_private_chats, function (jid) {
if (_converse.chatboxes.where({
'jid': jid
}).length) {
return;
}
2018-05-07 18:20:15 +02:00
if (_.isString(jid)) {
_converse.api.chats.open(jid);
} else {
_converse.log('Invalid jid criteria specified for "auto_join_private_chats"', Strophe.LogLevel.ERROR);
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
});
2018-05-07 18:20:15 +02:00
_converse.emit('privateChatsAutoJoined');
}
/************************ BEGIN Event Handlers ************************/
2018-05-07 18:20:15 +02:00
_converse.on('chatBoxesFetched', autoJoinChats);
2018-05-11 00:19:49 +02:00
_converse.api.waitUntil('rosterContactsFetched').then(function () {
_converse.roster.on('add', function (contact) {
/* When a new contact is added, check if we already have a
* chatbox open for it, and if so attach it to the chatbox.
*/
var chatbox = _converse.chatboxes.findWhere({
'jid': contact.get('jid')
});
2018-05-15 10:32:13 +02:00
if (chatbox) {
chatbox.addRelatedContact(contact);
}
2018-05-11 00:19:49 +02:00
});
});
2018-05-07 18:20:15 +02:00
_converse.on('addClientFeatures', function () {
2018-05-15 10:32:13 +02:00
_converse.api.disco.own.features.add(Strophe.NS.HTTPUPLOAD);
2018-05-15 10:32:13 +02:00
_converse.api.disco.own.features.add(Strophe.NS.OUTOFBAND);
2018-05-07 18:20:15 +02:00
});
2018-05-07 18:20:15 +02:00
_converse.api.listen.on('pluginsInitialized', function () {
_converse.chatboxes = new _converse.ChatBoxes();
_converse.chatboxviews = new _converse.ChatBoxViews({
'model': _converse.chatboxes
});
2018-05-07 18:20:15 +02:00
_converse.emit('chatBoxesInitialized');
});
2018-05-07 18:20:15 +02:00
_converse.api.listen.on('beforeTearDown', function () {
_converse.chatboxes.remove(); // Don't call off(), events won't get re-registered upon reconnect.
2018-05-07 18:20:15 +02:00
delete _converse.chatboxes.browserStorage;
});
2018-05-07 18:20:15 +02:00
_converse.api.listen.on('statusInitialized', function () {
return _converse.chatboxes.onConnected();
});
/************************ END Event Handlers ************************/
2018-05-07 18:20:15 +02:00
/************************ BEGIN API ************************/
2018-05-07 18:20:15 +02:00
_.extend(_converse.api, {
'chats': {
'create': function create(jids, attrs) {
if (_.isUndefined(jids)) {
_converse.log("chats.create: You need to provide at least one JID", Strophe.LogLevel.ERROR);
2018-05-07 18:20:15 +02:00
return null;
}
2018-05-07 18:20:15 +02:00
if (_.isString(jids)) {
if (attrs && !_.get(attrs, 'fullname')) {
attrs.fullname = _.get(_converse.api.contacts.get(jids), 'attributes.fullname');
}
2018-05-07 18:20:15 +02:00
var chatbox = _converse.chatboxes.getChatBox(jids, attrs, true);
2018-05-07 18:20:15 +02:00
if (_.isNil(chatbox)) {
_converse.log("Could not open chatbox for JID: " + jids, Strophe.LogLevel.ERROR);
return;
}
2018-05-07 18:20:15 +02:00
return chatbox;
}
2018-05-07 18:20:15 +02:00
return _.map(jids, function (jid) {
attrs.fullname = _.get(_converse.api.contacts.get(jid), 'attributes.fullname');
return _converse.chatboxes.getChatBox(jid, attrs, true).trigger('show');
});
},
'open': function open(jids, attrs) {
if (_.isUndefined(jids)) {
_converse.log("chats.open: You need to provide at least one JID", Strophe.LogLevel.ERROR);
2018-05-07 18:20:15 +02:00
return null;
} else if (_.isString(jids)) {
var chatbox = _converse.api.chats.create(jids, attrs);
2018-05-07 18:20:15 +02:00
chatbox.trigger('show');
return chatbox;
}
return _.map(jids, function (jid) {
return _converse.api.chats.create(jid, attrs).trigger('show');
});
},
'get': function get(jids) {
if (_.isUndefined(jids)) {
var result = [];
_converse.chatboxes.each(function (chatbox) {
// FIXME: Leaky abstraction from MUC. We need to add a
// base type for chat boxes, and check for that.
if (chatbox.get('type') !== 'chatroom') {
result.push(chatbox);
}
});
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
return result;
} else if (_.isString(jids)) {
return _converse.chatboxes.getChatBox(jids);
}
2018-05-07 18:20:15 +02:00
return _.map(jids, _.partial(_converse.chatboxes.getChatBox.bind(_converse.chatboxes), _, {}, true));
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
}
});
/************************ END API ************************/
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
}
});
return converse;
});
//# sourceMappingURL=converse-chatboxes.js.map;
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
define('tpl!csn', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<div class="message chat-info chat-state-notification"\n data-isodate="' +
__e(o.isodate) +
'"\n data-csn="' +
__e(o.from) +
'">' +
__e(o.message) +
'</div>\n';
return __p
};});
2018-05-07 18:20:15 +02:00
define('tpl!file_progress', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<div class="message chat-msg" data-isodate="' +
__e(o.time) +
'" data-msgid="' +
__e(o.msgid) +
'">\n <canvas class="avatar" height="36" width="36"></canvas>\n <div class="chat-msg-content">\n <span class="chat-msg-text">Uploading file: <strong>' +
__e(o.file.name) +
'</strong>, ' +
__e(o.filesize) +
'</span>\n <progress value="' +
__e(o.progress) +
'"/>\n </div>\n</div>\n';
return __p
};});
2018-05-07 18:20:15 +02:00
define('tpl!message', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<div class="message chat-msg ' +
__e(o.type) +
' ' +
__e(o.extra_classes) +
'" data-isodate="' +
__e(o.time) +
'" data-msgid="' +
__e(o.msgid) +
'" data-from="' +
__e(o.from) +
'">\n ';
if (o.type !== 'headline') { ;
__p += '\n <canvas class="avatar" height="36" width="36"></canvas>\n ';
} ;
__p += '\n <div class="chat-msg-content">\n <span class="chat-msg-heading">\n <span class="chat-msg-author">' +
__e(o.username) +
2018-05-08 19:58:12 +02:00
'\n ';
_.each(o.roles, function (role) { ;
__p += ' <span class="badge badge-secondary">' +
__e(role) +
'</span> ';
}); ;
__p += '\n </span>\n <span class="chat-msg-time">' +
2018-05-07 18:20:15 +02:00
__e(o.pretty_time) +
'</span>\n </span>\n <span class="chat-msg-text"></span>\n <div class="chat-msg-media"></div>\n </div>\n</div>\n';
return __p
};});
2018-05-07 18:20:15 +02:00
define('tpl!spoiler_message', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<div class="message chat-msg ' +
__e(o.extra_classes) +
'" data-isodate="' +
__e(o.time) +
'" data-msgid="' +
__e(o.msgid) +
'">\n <canvas class="avatar" height="36" width="36"></canvas>\n <div class="chat-msg-content">\n <span class="chat-msg-heading">\n <span class="chat-msg-author">' +
__e(o.username) +
'</span>\n <span class="chat-msg-time">' +
__e(o.pretty_time) +
'</span>\n </span>\n <div>\n <span class="spoiler-hint">' +
__e(o.spoiler_hint) +
'</span>\n <a class="badge badge-info spoiler-toggle" data-toggle-state="closed" href="#"><i class="fa fa-eye"></i>' +
__e(o.label_show) +
'</a>\n </div>\n <div class="chat-msg-text spoiler collapsed"><!-- message gets added here via renderMessage --></div>\n </div>\n</div>\n';
return __p
};});
2018-05-07 18:20:15 +02:00
// Converse.js
// https://conversejs.org
//
// Copyright (c) 2012-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2)
2018-05-07 18:20:15 +02:00
(function (root, factory) {
define('converse-message-view',[
"converse-core",
"xss",
"emojione",
"filesize",
"tpl!action",
"tpl!csn",
"tpl!file_progress",
"tpl!info",
"tpl!message",
"tpl!spoiler_message"
], factory);
}(this, function (
converse,
xss,
emojione,
filesize,
tpl_action,
tpl_csn,
tpl_file_progress,
tpl_info,
tpl_message,
tpl_spoiler_message
) {
"use strict";
const { Backbone, _, moment } = converse.env;
const u = converse.env.utils;
2018-05-07 18:20:15 +02:00
converse.plugins.add('converse-message-view', {
2018-01-29 16:33:30 +01:00
2018-05-07 18:20:15 +02:00
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
const { _converse } = this,
{ __ } = _converse;
2018-05-07 18:20:15 +02:00
_converse.ViewWithAvatar = Backbone.NativeView.extend({
renderAvatar () {
const canvas_el = this.el.querySelector('canvas');
if (_.isNull(canvas_el)) {
return;
}
const image_type = this.model.vcard.get('image_type'),
image = this.model.vcard.get('image'),
img_src = "data:" + image_type + ";base64," + image,
img = new Image();
img.onload = () => {
const ctx = canvas_el.getContext('2d'),
ratio = img.width / img.height;
ctx.clearRect(0, 0, canvas_el.width, canvas_el.height);
if (ratio < 1) {
ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height * (1 / ratio));
} else {
ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height * ratio);
}
};
img.src = img_src;
},
});
_converse.MessageView = _converse.ViewWithAvatar.extend({
2018-05-07 18:20:15 +02:00
initialize () {
this.model.vcard.on('change', this.render, this);
this.model.on('change:progress', this.renderFileUploadProgresBar, this);
this.model.on('change:type', this.render, this);
this.model.on('change:upload', this.render, this);
this.model.on('destroy', this.remove, this);
this.render();
},
2018-05-07 18:20:15 +02:00
render () {
2018-05-15 10:32:13 +02:00
const is_followup = u.hasClass('chat-msg-followup', this.el);
let msg;
2018-05-07 18:20:15 +02:00
if (this.model.isOnlyChatStateNotification()) {
2018-05-15 10:32:13 +02:00
this.renderChatStateNotification()
2018-05-07 18:20:15 +02:00
} else if (this.model.get('file') && !this.model.get('oob_url')) {
2018-05-15 10:32:13 +02:00
this.renderFileUploadProgresBar();
2018-05-07 18:20:15 +02:00
} else if (this.model.get('type') === 'error') {
2018-05-15 10:32:13 +02:00
this.renderErrorMessage();
2018-05-07 18:20:15 +02:00
} else {
2018-05-15 10:32:13 +02:00
this.renderChatMessage();
}
if (is_followup) {
u.addClass('chat-msg-followup', this.el);
2018-05-07 18:20:15 +02:00
}
2018-05-15 10:32:13 +02:00
return this.el;
2018-05-07 18:20:15 +02:00
},
2018-05-07 18:20:15 +02:00
replaceElement (msg) {
if (!_.isNil(this.el.parentElement)) {
this.el.parentElement.replaceChild(msg, this.el);
}
this.setElement(msg);
return this.el;
},
2018-05-07 18:20:15 +02:00
renderChatMessage () {
let template, text = this.model.get('message');
if (this.isMeCommand()) {
template = tpl_action;
text = this.model.get('message').replace(/^\/me/, '');
} else {
template = this.model.get('is_spoiler') ? tpl_spoiler_message : tpl_message;
}
2018-05-08 19:58:12 +02:00
const moment_time = moment(this.model.get('time')),
role = this.model.vcard.get('role'),
roles = role ? role.split(',') : [];
2018-05-07 18:20:15 +02:00
const msg = u.stringToElement(template(
2018-05-08 19:58:12 +02:00
_.extend(
this.model.toJSON(), {
'roles': roles,
2018-05-07 18:20:15 +02:00
'pretty_time': moment_time.format(_converse.time_format),
'time': moment_time.format(),
'extra_classes': this.getExtraMessageClasses(),
'label_show': __('Show more'),
'username': this.model.getDisplayName()
})
));
2018-05-07 18:20:15 +02:00
var url = this.model.get('oob_url');
if (url) {
msg.querySelector('.chat-msg-media').innerHTML = _.flow(
_.partial(u.renderFileURL, _converse),
_.partial(u.renderMovieURL, _converse),
_.partial(u.renderAudioURL, _converse),
_.partial(u.renderImageURL, _converse))(url);
}
2018-05-07 18:20:15 +02:00
const msg_content = msg.querySelector('.chat-msg-text');
if (text !== url) {
text = xss.filterXSS(text, {'whiteList': {}});
msg_content.innerHTML = _.flow(
_.partial(u.geoUriToHttp, _, _converse.geouri_replacement),
u.addHyperlinks,
_.partial(u.addEmoji, _converse, emojione, _)
)(text);
}
u.renderImageURLs(_converse, msg_content).then(() => {
this.model.collection.trigger('rendered');
});
this.replaceElement(msg);
2018-05-07 18:20:15 +02:00
if (this.model.get('type') !== 'headline') {
this.renderAvatar();
}
},
2017-12-20 18:08:08 +01:00
2018-05-07 18:20:15 +02:00
renderErrorMessage () {
const moment_time = moment(this.model.get('time')),
msg = u.stringToElement(
tpl_info(_.extend(this.model.toJSON(), {
'extra_classes': 'chat-error',
'isodate': moment_time.format(),
'data': ''
})));
return this.replaceElement(msg);
},
2018-05-07 18:20:15 +02:00
renderChatStateNotification () {
let text;
const from = this.model.get('from'),
name = this.model.getDisplayName();
2017-12-20 18:08:08 +01:00
2018-05-07 18:20:15 +02:00
if (this.model.get('chat_state') === _converse.COMPOSING) {
if (this.model.get('sender') === 'me') {
text = __('Typing from another device');
} else {
text = name +' '+__('is typing');
}
} else if (this.model.get('chat_state') === _converse.PAUSED) {
if (this.model.get('sender') === 'me') {
text = __('Stopped typing on the other device');
} else {
text = name +' '+__('has stopped typing');
}
} else if (this.model.get('chat_state') === _converse.GONE) {
text = name +' '+__('has gone away');
} else {
return;
}
const isodate = moment().format();
this.replaceElement(
u.stringToElement(
tpl_csn({
'message': text,
'from': from,
'isodate': isodate
})));
},
2017-12-20 18:08:08 +01:00
2018-05-07 18:20:15 +02:00
renderFileUploadProgresBar () {
const msg = u.stringToElement(tpl_file_progress(
_.extend(this.model.toJSON(), {
'filesize': filesize(this.model.get('file').size),
})));
this.replaceElement(msg);
this.renderAvatar();
},
2018-05-07 18:20:15 +02:00
isMeCommand () {
const text = this.model.get('message');
if (!text) {
return false;
}
const match = text.match(/^\/(.*?)(?: (.*))?$/);
return match && match[1] === 'me';
},
2018-05-07 18:20:15 +02:00
processMessageText () {
var text = this.get('message');
text = u.geoUriToHttp(text, _converse.geouri_replacement);
},
2018-05-07 18:20:15 +02:00
getExtraMessageClasses () {
let extra_classes = this.model.get('delayed') && 'delayed' || '';
if (this.model.get('type') === 'groupchat' && this.model.get('sender') === 'them') {
if (this.model.collection.chatbox.isUserMentioned(this.model.get('message'))) {
// Add special class to mark groupchat messages
// in which we are mentioned.
extra_classes += ' mentioned';
}
}
return extra_classes;
}
});
}
});
return converse;
}));
2018-05-07 18:20:15 +02:00
// Converse.js
// http://conversejs.org
//
// Copyright (c) 2012-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2)
(function (root, factory) {
2018-05-11 00:19:49 +02:00
define('converse-chatview',["converse-core", "bootstrap", "emojione", "xss", "tpl!action", "tpl!chatbox", "tpl!chatbox_head", "tpl!chatbox_message_form", "tpl!emojis", "tpl!error_message", "tpl!help_message", "tpl!info", "tpl!new_day", "tpl!user_details_modal", "tpl!toolbar_fileupload", "tpl!spinner", "tpl!spoiler_button", "tpl!status_message", "tpl!toolbar", "converse-modal", "converse-chatboxes", "converse-message-view"], factory);
})(this, function (converse, bootstrap, emojione, xss, tpl_action, tpl_chatbox, tpl_chatbox_head, tpl_chatbox_message_form, tpl_emojis, tpl_error_message, tpl_help_message, tpl_info, tpl_new_day, tpl_user_details_modal, tpl_toolbar_fileupload, tpl_spinner, tpl_spoiler_button, tpl_status_message, tpl_toolbar) {
2018-05-07 18:20:15 +02:00
"use strict";
2018-05-07 18:20:15 +02:00
var _converse$env = converse.env,
$msg = _converse$env.$msg,
Backbone = _converse$env.Backbone,
Promise = _converse$env.Promise,
Strophe = _converse$env.Strophe,
_ = _converse$env._,
b64_sha1 = _converse$env.b64_sha1,
f = _converse$env.f,
sizzle = _converse$env.sizzle,
moment = _converse$env.moment;
var u = converse.env.utils;
var KEY = {
ENTER: 13,
FORWARD_SLASH: 47
};
converse.plugins.add('converse-chatview', {
/* Plugin dependencies are other plugins which might be
* overridden or relied upon, and therefore need to be loaded before
* this plugin.
*
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found. By default it's
* false, which means these plugins are only loaded opportunistically.
*
* NB: These plugins need to have already been loaded via require.js.
*/
2018-05-11 00:19:49 +02:00
dependencies: ["converse-chatboxes", "converse-disco", "converse-message-view", "converse-modal"],
2018-05-07 18:20:15 +02:00
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
//
ChatBoxViews: {
onChatBoxAdded: function onChatBoxAdded(item) {
var _converse = this.__super__._converse;
var view = this.get(item.get('id'));
2018-05-07 18:20:15 +02:00
if (!view) {
view = new _converse.ChatBoxView({
model: item
});
this.add(item.get('id'), view);
return view;
} else {
2018-05-07 18:20:15 +02:00
return this.__super__.onChatBoxAdded.apply(this, arguments);
}
2018-05-07 18:20:15 +02:00
}
}
2018-05-07 18:20:15 +02:00
},
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
var _converse = this._converse,
__ = _converse.__;
2018-05-07 18:20:15 +02:00
_converse.api.settings.update({
'use_emojione': false,
'emojione_image_path': emojione.imagePathPNG,
'show_toolbar': true,
'time_format': 'HH:mm',
'visible_toolbar_buttons': {
'call': false,
'clear': true,
'emoji': true,
'spoiler': true
}
});
2018-05-07 18:20:15 +02:00
emojione.imagePathPNG = _converse.emojione_image_path;
emojione.ascii = true;
function onWindowStateChanged(data) {
_converse.chatboxviews.each(function (chatboxview) {
chatboxview.onWindowStateChanged(data.state);
});
}
2018-05-07 18:20:15 +02:00
_converse.api.listen.on('windowStateChanged', onWindowStateChanged);
2018-05-07 18:20:15 +02:00
_converse.EmojiPicker = Backbone.Model.extend({
defaults: {
'current_category': 'people',
'current_skintone': '',
'scroll_position': 0
},
initialize: function initialize() {
var id = "converse.emoji-".concat(_converse.bare_jid);
this.id = id;
this.browserStorage = new Backbone.BrowserStorage[_converse.storage](id);
}
2018-05-07 18:20:15 +02:00
});
_converse.EmojiPickerView = Backbone.VDOMView.extend({
className: 'emoji-picker-container',
events: {
'click .emoji-category-picker li.emoji-category': 'chooseCategory',
'click .emoji-skintone-picker li.emoji-skintone': 'chooseSkinTone'
},
initialize: function initialize() {
this.model.on('change:current_skintone', this.render, this);
this.model.on('change:current_category', this.render, this);
},
toHTML: function toHTML() {
return tpl_emojis(_.extend(this.model.toJSON(), {
'transform': _converse.use_emojione ? emojione.shortnameToImage : emojione.shortnameToUnicode,
'emojis_by_category': u.getEmojisByCategory(_converse, emojione),
'toned_emojis': u.getTonedEmojis(_converse),
'skintones': ['tone1', 'tone2', 'tone3', 'tone4', 'tone5'],
'shouldBeHidden': this.shouldBeHidden
}));
},
shouldBeHidden: function shouldBeHidden(shortname, current_skintone, toned_emojis) {
/* Helper method for the template which decides whether an
* emoji should be hidden, based on which skin tone is
* currently being applied.
*/
if (_.includes(shortname, '_tone')) {
if (!current_skintone || !_.includes(shortname, current_skintone)) {
return true;
}
} else {
if (current_skintone && _.includes(toned_emojis, shortname)) {
return true;
}
}
2018-05-07 18:20:15 +02:00
return false;
},
chooseSkinTone: function chooseSkinTone(ev) {
ev.preventDefault();
ev.stopPropagation();
var target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target;
var skintone = target.getAttribute("data-skintone").trim();
2018-05-07 18:20:15 +02:00
if (this.model.get('current_skintone') === skintone) {
this.model.save({
'current_skintone': ''
});
} else {
this.model.save({
'current_skintone': skintone
});
}
},
chooseCategory: function chooseCategory(ev) {
ev.preventDefault();
ev.stopPropagation();
var target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target;
var category = target.getAttribute("data-category").trim();
this.model.save({
'current_category': category,
'scroll_position': 0
});
}
});
2018-05-07 18:20:15 +02:00
_converse.ChatBoxHeading = _converse.ViewWithAvatar.extend({
initialize: function initialize() {
this.model.on('change:status', this.onStatusMessageChanged, this);
this.model.vcard.on('change', this.render, this);
},
render: function render() {
this.el.innerHTML = tpl_chatbox_head(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), {
'_converse': _converse,
'info_close': __('Close this chat box')
}));
this.renderAvatar();
return this;
},
onStatusMessageChanged: function onStatusMessageChanged(item) {
this.render();
2018-05-07 18:20:15 +02:00
_converse.emit('contactStatusMessageChanged', {
'contact': item.attributes,
'message': item.get('status')
});
}
});
2018-05-11 00:19:49 +02:00
_converse.UserDetailsModal = _converse.BootstrapModal.extend({
events: {
2018-05-15 10:32:13 +02:00
'click button.remove-contact': 'removeContact',
'click button.refresh-contact': 'refreshContact'
2018-05-11 00:19:49 +02:00
},
initialize: function initialize() {
_converse.BootstrapModal.prototype.initialize.apply(this, arguments);
this.model.on('contactAdded', this.registerContactEventHandlers, this);
this.registerContactEventHandlers();
},
toHTML: function toHTML() {
return tpl_user_details_modal(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), {
'allow_contact_removal': _converse.allow_contact_removal,
'alt_profile_image': __("The User's Profile Image"),
'display_name': this.model.getDisplayName(),
'is_roster_contact': !_.isUndefined(this.model.contact),
'label_close': __('Close'),
'label_email': __('Email'),
'label_fullname': __('Full Name'),
'label_jid': __('Jabber ID'),
'label_nickname': __('Nickname'),
'label_remove': __('Remove as contact'),
2018-05-15 10:32:13 +02:00
'label_refresh': __('Refresh'),
2018-05-11 00:19:49 +02:00
'label_role': __('Role'),
'label_url': __('URL')
}));
},
registerContactEventHandlers: function registerContactEventHandlers() {
var _this = this;
if (!_.isUndefined(this.model.contact)) {
this.model.contact.on('change', this.render, this);
this.model.contact.vcard.on('change', this.render, this);
this.model.contact.on('destroy', function () {
delete _this.model.contact;
_this.render();
});
}
},
2018-05-15 10:32:13 +02:00
refreshContact: function refreshContact(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}
var refresh_icon = this.el.querySelector('.fa-refresh');
u.addClass('fa-spin', refresh_icon);
_converse.api.vcard.update(this.model.contact.vcard, true).then(function () {
return u.removeClass('fa-spin', refresh_icon);
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
},
2018-05-11 00:19:49 +02:00
removeContact: function removeContact(ev) {
var _this2 = this;
if (ev && ev.preventDefault) {
ev.preventDefault();
}
if (!_converse.allow_contact_removal) {
return;
}
var result = confirm(__("Are you sure you want to remove this contact?"));
if (result === true) {
this.modal.hide();
this.model.contact.removeFromRoster(function (iq) {
_this2.model.contact.destroy();
}, function (err) {
_converse.log(err, Strophe.LogLevel.ERROR);
_converse.api.alert.show(Strophe.LogLevel.ERROR, __('Error'), [__('Sorry, there was an error while trying to remove %1$s as a contact.', _this2.model.contact.getDisplayName())]);
});
}
}
});
2018-05-07 18:20:15 +02:00
_converse.ChatBoxView = Backbone.NativeView.extend({
length: 200,
className: 'chatbox hidden',
is_chatroom: false,
// Leaky abstraction from MUC
events: {
'change input.fileupload': 'onFileSelection',
'click .close-chatbox-button': 'close',
2018-05-11 00:19:49 +02:00
'click .show-user-details-modal': 'showUserDetailsModal',
2018-05-07 18:20:15 +02:00
'click .new-msgs-indicator': 'viewUnreadMessages',
'click .send-button': 'onFormSubmitted',
'click .toggle-call': 'toggleCall',
'click .toggle-clear': 'clearMessages',
'click .toggle-compose-spoiler': 'toggleComposeSpoilerMessage',
'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
'click .toggle-smiley': 'toggleEmojiMenu',
'click .spoiler-toggle': 'toggleSpoilerMessage',
'click .upload-file': 'toggleFileUpload',
'keypress .chat-textarea': 'keyPressed',
'input .chat-textarea': 'inputChanged'
},
initialize: function initialize() {
this.initDebounced();
this.createEmojiPicker();
this.model.messages.on('add', this.onMessageAdded, this);
this.model.messages.on('rendered', this.scrollDown, this);
this.model.on('show', this.show, this);
2018-05-23 04:27:33 +02:00
this.model.on('destroy', this.remove, this);
this.model.presence.on('change:show', this.onPresenceChanged, this);
2018-05-07 18:20:15 +02:00
this.model.on('showHelpMessages', this.showHelpMessages, this);
this.render();
this.fetchMessages();
2018-05-07 18:20:15 +02:00
_converse.emit('chatBoxOpened', this);
2018-05-07 18:20:15 +02:00
_converse.emit('chatBoxInitialized', this);
},
initDebounced: function initDebounced() {
this.scrollDown = _.debounce(this._scrollDown, 250);
this.markScrolled = _.debounce(this._markScrolled, 100);
this.show = _.debounce(this._show, 250, {
'leading': true
});
},
render: function render() {
// XXX: Is this still needed?
this.el.setAttribute('id', this.model.get('box_id'));
this.el.innerHTML = tpl_chatbox(_.extend(this.model.toJSON(), {
unread_msgs: __('You have unread messages')
}));
this.content = this.el.querySelector('.chat-content');
this.renderMessageForm();
this.insertHeading();
return this;
},
renderToolbar: function renderToolbar(toolbar, options) {
if (!_converse.show_toolbar) {
return this;
}
2018-05-07 18:20:15 +02:00
toolbar = toolbar || tpl_toolbar;
options = _.assign(this.model.toJSON(), this.getToolbarOptions(options || {}));
this.el.querySelector('.chat-toolbar').innerHTML = toolbar(options);
this.addSpoilerButton(options);
this.addFileUploadButton();
this.insertEmojiPicker();
return this;
},
renderMessageForm: function renderMessageForm() {
var placeholder;
2018-05-07 18:20:15 +02:00
if (this.model.get('composing_spoiler')) {
placeholder = __('Hidden message');
} else {
placeholder = __('Personal message');
}
2018-05-07 18:20:15 +02:00
var form_container = this.el.querySelector('.message-form-container');
form_container.innerHTML = tpl_chatbox_message_form(_.extend(this.model.toJSON(), {
'hint_value': _.get(this.el.querySelector('.spoiler-hint'), 'value'),
'label_personal_message': placeholder,
'label_send': __('Send'),
'label_spoiler_hint': __('Optional hint'),
'message_value': _.get(this.el.querySelector('.chat-textarea'), 'value'),
'show_send_button': _converse.show_send_button,
'show_toolbar': _converse.show_toolbar,
'unread_msgs': __('You have unread messages')
}));
this.renderToolbar();
},
2018-05-11 00:19:49 +02:00
showUserDetailsModal: function showUserDetailsModal(ev) {
if (_.isUndefined(this.user_details_modal)) {
this.user_details_modal = new _converse.UserDetailsModal({
model: this.model
});
}
this.user_details_modal.show(ev);
},
2018-05-07 18:20:15 +02:00
toggleFileUpload: function toggleFileUpload(ev) {
this.el.querySelector('input.fileupload').click();
},
onFileSelection: function onFileSelection(evt) {
this.model.sendFiles(evt.target.files);
},
addFileUploadButton: function addFileUploadButton(options) {
2018-05-11 00:19:49 +02:00
var _this3 = this;
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
_converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain).then(function (result) {
if (result.length) {
2018-05-11 00:19:49 +02:00
_this3.el.querySelector('.chat-toolbar').insertAdjacentHTML('beforeend', tpl_toolbar_fileupload({
2018-05-07 18:20:15 +02:00
'tooltip_upload_file': __('Choose a file to send')
}));
}
2018-05-07 18:20:15 +02:00
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
},
addSpoilerButton: function addSpoilerButton(options) {
2018-05-11 00:19:49 +02:00
var _this4 = this;
2018-05-07 18:20:15 +02:00
/* Asynchronously adds a button for writing spoiler
* messages, based on whether the contact's client supports
* it.
*/
if (!options.show_spoiler_button || this.model.get('type') === 'chatroom') {
return;
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
var contact_jid = this.model.get('jid');
2018-05-23 04:27:33 +02:00
var resources = this.model.presence.get('resources');
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (_.isEmpty(resources)) {
return;
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
Promise.all(_.map(_.keys(resources), function (resource) {
return _converse.api.disco.supports(Strophe.NS.SPOILER, "".concat(contact_jid, "/").concat(resource));
})).then(function (results) {
2018-05-23 04:27:33 +02:00
if (_.filter(results, 'length').length) {
2018-05-11 00:19:49 +02:00
var html = tpl_spoiler_button(_this4.model.toJSON());
2018-05-07 18:20:15 +02:00
if (_converse.visible_toolbar_buttons.emoji) {
2018-05-11 00:19:49 +02:00
_this4.el.querySelector('.toggle-smiley').insertAdjacentHTML('afterEnd', html);
2018-05-07 18:20:15 +02:00
} else {
2018-05-11 00:19:49 +02:00
_this4.el.querySelector('.chat-toolbar').insertAdjacentHTML('afterBegin', html);
2018-05-07 18:20:15 +02:00
}
}
2018-05-07 18:20:15 +02:00
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
},
insertHeading: function insertHeading() {
this.heading = new _converse.ChatBoxHeading({
'model': this.model
});
this.heading.render();
this.heading.chatview = this;
2018-05-11 00:19:49 +02:00
if (!_.isUndefined(this.model.contact)) {
this.model.contact.on('destroy', this.heading.render, this);
}
2018-05-07 18:20:15 +02:00
var flyout = this.el.querySelector('.flyout');
flyout.insertBefore(this.heading.el, flyout.querySelector('.chat-body'));
return this;
},
getToolbarOptions: function getToolbarOptions(options) {
var label_toggle_spoiler;
2018-05-07 18:20:15 +02:00
if (this.model.get('composing_spoiler')) {
label_toggle_spoiler = __('Click to write as a normal (non-spoiler) message');
} else {
label_toggle_spoiler = __('Click to write your message as a spoiler');
}
2018-05-07 18:20:15 +02:00
return _.extend(options || {}, {
'label_clear': __('Clear all messages'),
'tooltip_insert_smiley': __('Insert emojis'),
'tooltip_start_call': __('Start a call'),
'label_toggle_spoiler': label_toggle_spoiler,
'show_call_button': _converse.visible_toolbar_buttons.call,
'show_spoiler_button': _converse.visible_toolbar_buttons.spoiler,
'use_emoji': _converse.visible_toolbar_buttons.emoji
});
},
afterMessagesFetched: function afterMessagesFetched() {
this.insertIntoDOM();
this.scrollDown();
this.content.addEventListener('scroll', this.markScrolled.bind(this));
2018-05-07 18:20:15 +02:00
_converse.emit('afterMessagesFetched', this);
},
fetchMessages: function fetchMessages() {
this.model.messages.fetch({
'add': true,
'success': this.afterMessagesFetched.bind(this),
'error': this.afterMessagesFetched.bind(this)
});
return this;
},
insertIntoDOM: function insertIntoDOM() {
/* This method gets overridden in src/converse-controlbox.js
* as well as src/converse-muc.js (if those plugins are
* enabled).
*/
_converse.chatboxviews.insertRowColumn(this.el);
2018-05-07 18:20:15 +02:00
return this;
},
showChatEvent: function showChatEvent(message) {
var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
var isodate = moment().format();
this.content.insertAdjacentHTML('beforeend', tpl_info({
'extra_classes': 'chat-event',
'message': message,
'isodate': isodate,
'data': data
}));
this.insertDayIndicator(this.content.lastElementChild);
this.scrollDown();
return isodate;
},
showErrorMessage: function showErrorMessage(message) {
this.content.insertAdjacentHTML('beforeend', tpl_error_message({
'message': message,
'isodate': moment().format()
}));
this.scrollDown();
},
addSpinner: function addSpinner() {
var append = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
2018-05-07 18:20:15 +02:00
if (_.isNull(this.el.querySelector('.spinner'))) {
if (append) {
this.content.insertAdjacentHTML('beforeend', tpl_spinner());
this.scrollDown();
} else {
this.content.insertAdjacentHTML('afterbegin', tpl_spinner());
}
}
},
clearSpinner: function clearSpinner() {
_.each(this.content.querySelectorAll('span.spinner'), function (el) {
return el.parentNode.removeChild(el);
});
},
insertDayIndicator: function insertDayIndicator(next_msg_el) {
/* Inserts an indicator into the chat area, showing the
* day as given by the passed in date.
*
* The indicator is only inserted if necessary.
*
* Parameters:
* (HTMLElement) next_msg_el - The message element before
* which the day indicator element must be inserted.
* This element must have a "data-isodate" attribute
* which specifies its creation date.
*/
var prev_msg_el = u.getPreviousElement(next_msg_el, ".message:not(.chat-state-notification)"),
prev_msg_date = _.isNull(prev_msg_el) ? null : prev_msg_el.getAttribute('data-isodate'),
next_msg_date = next_msg_el.getAttribute('data-isodate');
2018-05-07 18:20:15 +02:00
if (_.isNull(prev_msg_date) || moment(next_msg_date).isAfter(prev_msg_date, 'day')) {
var day_date = moment(next_msg_date).startOf('day');
next_msg_el.insertAdjacentHTML('beforeBegin', tpl_new_day({
'isodate': day_date.format(),
'datestring': day_date.format("dddd MMM Do YYYY")
}));
}
},
getLastMessageDate: function getLastMessageDate(cutoff) {
/* Return the ISO8601 format date of the latest message.
*
* Parameters:
* (Object) cutoff: Moment Date cutoff date. The last
* message received cutoff this date will be returned.
*/
var first_msg = u.getFirstChildElement(this.content, '.message:not(.chat-state-notification)'),
oldest_date = first_msg ? first_msg.getAttribute('data-isodate') : null;
2018-05-07 18:20:15 +02:00
if (!_.isNull(oldest_date) && moment(oldest_date).isAfter(cutoff)) {
return null;
}
2018-05-07 18:20:15 +02:00
var last_msg = u.getLastChildElement(this.content, '.message:not(.chat-state-notification)'),
most_recent_date = last_msg ? last_msg.getAttribute('data-isodate') : null;
2018-05-07 18:20:15 +02:00
if (_.isNull(most_recent_date) || moment(most_recent_date).isBefore(cutoff)) {
return most_recent_date;
}
/* XXX: We avoid .chat-state-notification messages, since they are
* temporary and get removed once a new element is
* inserted into the chat area, so we don't query for
* them here, otherwise we get a null reference later
* upon element insertion.
*/
2018-05-07 18:20:15 +02:00
var msg_dates = _.invokeMap(sizzle('.message:not(.chat-state-notification)', this.content), Element.prototype.getAttribute, 'data-isodate');
2018-05-07 18:20:15 +02:00
if (_.isObject(cutoff)) {
cutoff = cutoff.format();
}
2018-05-07 18:20:15 +02:00
msg_dates.push(cutoff);
msg_dates.sort();
var idx = msg_dates.lastIndexOf(cutoff);
2018-05-07 18:20:15 +02:00
if (idx === 0) {
return null;
} else {
return msg_dates[idx - 1];
}
2018-03-25 21:21:43 +02:00
},
2018-05-07 18:20:15 +02:00
setScrollPosition: function setScrollPosition(message_el) {
/* Given a newly inserted message, determine whether we
* should keep the scrollbar in place (so as to not scroll
* up when using infinite scroll).
*/
if (this.model.get('scrolled')) {
var next_msg_el = u.getNextElement(message_el, ".chat-msg");
if (next_msg_el) {
// The currently received message is not new, there
// are newer messages after it. So let's see if we
// should maintain our current scroll position.
if (this.content.scrollTop === 0 || this.model.get('top_visible_message')) {
var top_visible_message = this.model.get('top_visible_message') || next_msg_el;
this.model.set('top_visible_message', top_visible_message);
this.content.scrollTop = top_visible_message.offsetTop - 30;
}
}
} else {
this.scrollDown();
}
2018-03-25 21:21:43 +02:00
},
2018-05-07 18:20:15 +02:00
showHelpMessages: function showHelpMessages(msgs, type, spinner) {
2018-05-11 00:19:49 +02:00
var _this5 = this;
2018-05-07 18:20:15 +02:00
_.each(msgs, function (msg) {
2018-05-11 00:19:49 +02:00
_this5.content.insertAdjacentHTML('beforeend', tpl_help_message({
2018-05-07 18:20:15 +02:00
'isodate': moment().format(),
'type': type,
'message': xss.filterXSS(msg, {
'whiteList': {
'strong': []
}
})
}));
});
2018-05-07 18:20:15 +02:00
if (spinner === true) {
this.addSpinner();
} else if (spinner === false) {
this.clearSpinner();
}
2018-05-07 18:20:15 +02:00
return this.scrollDown();
},
clearChatStateNotification: function clearChatStateNotification(message, isodate) {
if (isodate) {
_.each(sizzle(".chat-state-notification[data-csn=\"".concat(message.get('from'), "\"][data-isodate=\"").concat(isodate, "\"]"), this.content), u.removeElement);
} else {
_.each(sizzle(".chat-state-notification[data-csn=\"".concat(message.get('from'), "\"]"), this.content), u.removeElement);
}
},
2018-05-07 18:20:15 +02:00
shouldShowOnTextMessage: function shouldShowOnTextMessage() {
return !u.isVisible(this.el);
},
insertMessage: function insertMessage(view) {
/* Given a view representing a message, insert it into the
* content area of the chat box.
*
* Parameters:
* (Backbone.View) message: The message Backbone.View
*/
2018-05-07 18:20:15 +02:00
if (view.model.get('type') === 'error') {
var previous_msg_el = this.content.querySelector("[data-msgid=\"".concat(view.model.get('msgid'), "\"]"));
2018-05-07 18:20:15 +02:00
if (previous_msg_el) {
return previous_msg_el.insertAdjacentElement('afterend', view.el);
}
}
2018-05-07 18:20:15 +02:00
var current_msg_date = moment(view.model.get('time')) || moment,
previous_msg_date = this.getLastMessageDate(current_msg_date);
if (_.isNull(previous_msg_date)) {
this.content.insertAdjacentElement('afterbegin', view.el);
} else {
var _previous_msg_el = sizzle("[data-isodate=\"".concat(previous_msg_date, "\"]:last"), this.content).pop();
if (view.model.get('type') === 'error' && u.hasClass('chat-error', _previous_msg_el) && _previous_msg_el.textContent === view.model.get('message')) {
// We don't show a duplicate error message
return;
}
_previous_msg_el.insertAdjacentElement('afterend', view.el);
this.markFollowups(view.el);
}
},
markFollowups: function markFollowups(el) {
/* Given a message element, determine wether it should be
* marked as a followup message to the previous element.
*
* Also determine whether the element following it is a
* followup message or not.
*
* Followup messages are subsequent ones written by the same
* author with no other conversation elements inbetween and
* posted within 10 minutes of one another.
*
* Parameters:
* (HTMLElement) el - The message element.
*/
var from = el.getAttribute('data-from'),
previous_el = el.previousElementSibling,
date = moment(el.getAttribute('data-isodate')),
next_el = el.nextElementSibling;
2018-05-07 18:20:15 +02:00
if (!u.hasClass('chat-action', el) && !u.hasClass('chat-action', previous_el) && previous_el.getAttribute('data-from') === from && date.isBefore(moment(previous_el.getAttribute('data-isodate')).add(10, 'minutes'))) {
u.addClass('chat-msg-followup', el);
}
2018-05-07 18:20:15 +02:00
if (!next_el) {
return;
}
2018-05-07 18:20:15 +02:00
if (!u.hasClass('chat-action', 'el') && next_el.getAttribute('data-from') === from && moment(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes'))) {
u.addClass('chat-msg-followup', next_el);
} else {
2018-05-07 18:20:15 +02:00
u.removeClass('chat-msg-followup', next_el);
}
},
2018-05-07 18:20:15 +02:00
showMessage: function showMessage(message) {
/* Inserts a chat message into the content area of the chat box.
*
* Will also insert a new day indicator if the message is on a
* different day.
*
* Parameters:
* (Backbone.Model) message: The message object
*/
2018-05-07 18:20:15 +02:00
var view = new _converse.MessageView({
'model': message
});
this.clearChatStateNotification(message);
this.insertMessage(view);
this.insertDayIndicator(view.el);
this.setScrollPosition(view.el);
2018-05-07 18:20:15 +02:00
if (u.isNewMessage(message)) {
if (message.get('sender') === 'me') {
// We remove the "scrolled" flag so that the chat area
// gets scrolled down. We always want to scroll down
// when the user writes a message as opposed to when a
// message is received.
this.model.set('scrolled', false);
} else if (this.model.get('scrolled', true)) {
this.showNewMessagesIndicator();
}
}
2018-05-07 18:20:15 +02:00
if (this.shouldShowOnTextMessage()) {
this.show();
} else {
this.scrollDown();
}
},
2018-05-07 18:20:15 +02:00
onMessageAdded: function onMessageAdded(message) {
/* Handler that gets called when a new message object is created.
*
* Parameters:
* (Object) message - The message Backbone object that was added.
*/
this.showMessage(message);
2018-05-07 18:20:15 +02:00
_converse.emit('messageAdded', {
'message': message,
'chatbox': this.model
});
},
2018-05-07 18:20:15 +02:00
parseMessageForCommands: function parseMessageForCommands(text) {
var match = text.replace(/^\s*/, "").match(/^\/(.*)\s*$/);
2018-05-07 18:20:15 +02:00
if (match) {
if (match[1] === "clear") {
this.clearMessages();
return true;
} else if (match[1] === "help") {
var msgs = ["<strong>/clear</strong>: ".concat(__('Remove messages')), "<strong>/me</strong>: ".concat(__('Write in the third person')), "<strong>/help</strong>: ".concat(__('Show this menu'))];
this.showHelpMessages(msgs);
return true;
}
}
},
2018-05-07 18:20:15 +02:00
onMessageSubmitted: function onMessageSubmitted(text, spoiler_hint) {
/* This method gets called once the user has typed a message
* and then pressed enter in a chat box.
*
* Parameters:
* (String) text - The chat message text.
* (String) spoiler_hint - A hint in case the message
* text is a hidden/spoiler message. See XEP-0382
*/
2018-05-07 18:20:15 +02:00
if (!_converse.connection.authenticated) {
return this.showHelpMessages(['Sorry, the connection has been lost, ' + 'and your message could not be sent'], 'error');
}
2018-05-07 18:20:15 +02:00
if (this.parseMessageForCommands(text)) {
return;
}
2018-05-07 18:20:15 +02:00
var attrs = this.model.getOutgoingMessageAttributes(text, spoiler_hint);
this.model.sendMessage(attrs);
},
2018-05-07 18:20:15 +02:00
setChatState: function setChatState(state) {
/* Mutator for setting the chat state of this chat session.
* Handles clearing of any chat state notification timeouts and
* setting new ones if necessary.
* Timeouts are set when the state being set is COMPOSING or PAUSED.
* After the timeout, COMPOSING will become PAUSED and PAUSED will become INACTIVE.
* See XEP-0085 Chat State Notifications.
*
* Parameters:
* (string) state - The chat state (consts ACTIVE, COMPOSING, PAUSED, INACTIVE, GONE)
*/
if (!_.isUndefined(this.chat_state_timeout)) {
window.clearTimeout(this.chat_state_timeout);
delete this.chat_state_timeout;
}
2018-05-07 18:20:15 +02:00
if (state === _converse.COMPOSING) {
this.chat_state_timeout = window.setTimeout(this.setChatState.bind(this), _converse.TIMEOUTS.PAUSED, _converse.PAUSED);
} else if (state === _converse.PAUSED) {
this.chat_state_timeout = window.setTimeout(this.setChatState.bind(this), _converse.TIMEOUTS.INACTIVE, _converse.INACTIVE);
}
2018-05-07 18:20:15 +02:00
this.model.set('chat_state', state);
return this;
},
2018-05-07 18:20:15 +02:00
onFormSubmitted: function onFormSubmitted(ev) {
ev.preventDefault();
var textarea = this.el.querySelector('.chat-textarea'),
message = textarea.value;
var spoiler_hint;
2017-12-20 18:08:08 +01:00
2018-05-07 18:20:15 +02:00
if (this.model.get('composing_spoiler')) {
var hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint');
spoiler_hint = hint_el.value;
hint_el.value = '';
}
2018-05-07 18:20:15 +02:00
textarea.value = '';
2018-05-23 04:27:33 +02:00
textarea.focus(); // Trigger input event, so that the textarea resizes
var event = document.createEvent('Event');
event.initEvent('input', true, true);
textarea.dispatchEvent(event);
2018-05-07 18:20:15 +02:00
if (message !== '') {
this.onMessageSubmitted(message, spoiler_hint);
_converse.emit('messageSend', message);
}
this.setChatState(_converse.ACTIVE);
},
2018-05-07 18:20:15 +02:00
keyPressed: function keyPressed(ev) {
/* Event handler for when a key is pressed in a chat box textarea.
*/
if (ev.keyCode === KEY.ENTER) {
this.onFormSubmitted(ev);
} else if (ev.keyCode !== KEY.FORWARD_SLASH && this.model.get('chat_state') !== _converse.COMPOSING) {
// Set chat state to composing if keyCode is not a forward-slash
// (which would imply an internal command and not a message).
this.setChatState(_converse.COMPOSING);
}
},
2018-05-07 18:20:15 +02:00
inputChanged: function inputChanged(ev) {
ev.target.style.height = 'auto'; // Fixes weirdness
2018-05-07 18:20:15 +02:00
ev.target.style.height = ev.target.scrollHeight + 'px';
},
2018-05-07 18:20:15 +02:00
clearMessages: function clearMessages(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}
2017-12-20 18:08:08 +01:00
2018-05-07 18:20:15 +02:00
var result = confirm(__("Are you sure you want to clear the messages from this conversation?"));
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
if (result === true) {
this.content.innerHTML = '';
this.model.messages.reset();
2018-01-04 17:58:16 +01:00
2018-05-07 18:20:15 +02:00
this.model.messages.browserStorage._clear();
}
2018-05-07 18:20:15 +02:00
return this;
},
2018-05-07 18:20:15 +02:00
insertIntoTextArea: function insertIntoTextArea(value) {
var textbox_el = this.el.querySelector('.chat-textarea');
var existing = textbox_el.value;
2018-05-07 18:20:15 +02:00
if (existing && existing[existing.length - 1] !== ' ') {
existing = existing + ' ';
}
textbox_el.value = existing + value + ' ';
textbox_el.focus();
},
2018-05-07 18:20:15 +02:00
createEmojiPicker: function createEmojiPicker() {
if (_.isUndefined(_converse.emojipicker)) {
_converse.emojipicker = new _converse.EmojiPicker();
2018-05-07 18:20:15 +02:00
_converse.emojipicker.fetch();
2018-03-25 21:21:43 +02:00
}
2018-05-07 18:20:15 +02:00
this.emoji_picker_view = new _converse.EmojiPickerView({
'model': _converse.emojipicker
});
},
insertEmoji: function insertEmoji(ev) {
ev.stopPropagation();
var target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target;
this.insertIntoTextArea(target.getAttribute('data-emoji'));
},
toggleEmojiMenu: function toggleEmojiMenu(ev) {
if (_.isUndefined(this.emoji_dropdown)) {
ev.stopPropagation();
var dropdown_el = this.el.querySelector('.toggle-smiley.dropup');
this.emoji_dropdown = new bootstrap.Dropdown(dropdown_el, true);
this.emoji_dropdown.toggle();
}
},
2018-05-07 18:20:15 +02:00
toggleCall: function toggleCall(ev) {
ev.stopPropagation();
2018-05-07 18:20:15 +02:00
_converse.emit('callButtonClicked', {
connection: _converse.connection,
model: this.model
});
},
2018-05-07 18:20:15 +02:00
toggleComposeSpoilerMessage: function toggleComposeSpoilerMessage() {
this.model.set('composing_spoiler', !this.model.get('composing_spoiler'));
this.renderMessageForm();
this.focus();
},
toggleSpoilerMessage: function toggleSpoilerMessage(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}
2018-05-07 18:20:15 +02:00
var toggle_el = ev.target,
icon_el = toggle_el.firstElementChild;
u.slideToggleElement(toggle_el.parentElement.parentElement.querySelector('.spoiler'));
if (toggle_el.getAttribute("data-toggle-state") == "closed") {
toggle_el.textContent = 'Show less';
icon_el.classList.remove("fa-eye");
icon_el.classList.add("fa-eye-slash");
toggle_el.insertAdjacentElement('afterBegin', icon_el);
toggle_el.setAttribute("data-toggle-state", "open");
} else {
toggle_el.textContent = 'Show more';
icon_el.classList.remove("fa-eye-slash");
icon_el.classList.add("fa-eye");
toggle_el.insertAdjacentElement('afterBegin', icon_el);
toggle_el.setAttribute("data-toggle-state", "closed");
}
},
2018-05-23 04:27:33 +02:00
onPresenceChanged: function onPresenceChanged(item) {
var show = item.get('show'),
fullname = this.model.getDisplayName();
2018-05-07 18:20:15 +02:00
var text;
2018-05-07 18:20:15 +02:00
if (u.isVisible(this.el)) {
2018-05-23 04:27:33 +02:00
if (show === 'offline') {
2018-05-07 18:20:15 +02:00
text = fullname + ' ' + __('has gone offline');
2018-05-23 04:27:33 +02:00
} else if (show === 'away') {
2018-05-07 18:20:15 +02:00
text = fullname + ' ' + __('has gone away');
2018-05-23 04:27:33 +02:00
} else if (show === 'dnd') {
2018-05-07 18:20:15 +02:00
text = fullname + ' ' + __('is busy');
2018-05-23 04:27:33 +02:00
} else if (show === 'online') {
2018-05-07 18:20:15 +02:00
text = fullname + ' ' + __('is online');
}
2018-05-07 18:20:15 +02:00
if (text) {
this.content.insertAdjacentHTML('beforeend', tpl_status_message({
'message': text,
'isodate': moment().format()
}));
this.scrollDown();
}
}
},
2018-05-07 18:20:15 +02:00
close: function close(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}
if (Backbone.history.getFragment() === "converse/chat?jid=" + this.model.get('jid')) {
_converse.router.navigate('');
}
if (_converse.connection.connected) {
// Immediately sending the chat state, because the
// model is going to be destroyed afterwards.
this.setChatState(_converse.INACTIVE);
this.model.sendChatState();
}
2018-05-07 18:20:15 +02:00
try {
this.model.destroy();
} catch (e) {
_converse.log(e, Strophe.LogLevel.ERROR);
}
2018-05-07 18:20:15 +02:00
this.remove();
_converse.emit('chatBoxClosed', this);
return this;
},
2018-05-07 18:20:15 +02:00
renderEmojiPicker: function renderEmojiPicker() {
this.emoji_picker_view.render();
},
insertEmojiPicker: function insertEmojiPicker() {
var picker_el = this.el.querySelector('.emoji-picker');
2018-05-07 18:20:15 +02:00
if (!_.isNull(picker_el)) {
picker_el.innerHTML = '';
picker_el.appendChild(this.emoji_picker_view.el);
}
},
2018-05-07 18:20:15 +02:00
focus: function focus() {
var textarea_el = this.el.querySelector('.chat-textarea');
2018-05-07 18:20:15 +02:00
if (!_.isNull(textarea_el)) {
textarea_el.focus();
2018-05-07 18:20:15 +02:00
_converse.emit('chatBoxFocused', this);
}
2017-12-20 18:08:08 +01:00
2018-05-07 18:20:15 +02:00
return this;
2017-12-20 18:08:08 +01:00
},
2018-05-07 18:20:15 +02:00
hide: function hide() {
this.el.classList.add('hidden');
return this;
},
afterShown: function afterShown() {
if (u.isPersistableModel(this.model)) {
this.model.clearUnreadMsgCounter();
this.model.save();
}
2018-05-07 18:20:15 +02:00
this.setChatState(_converse.ACTIVE);
this.renderEmojiPicker();
this.scrollDown();
this.focus();
},
_show: function _show(f) {
/* Inner show method that gets debounced */
if (u.isVisible(this.el)) {
this.focus();
return;
}
2018-05-07 18:20:15 +02:00
u.fadeIn(this.el, _.bind(this.afterShown, this));
},
showNewMessagesIndicator: function showNewMessagesIndicator() {
u.showElement(this.el.querySelector('.new-msgs-indicator'));
},
hideNewMessagesIndicator: function hideNewMessagesIndicator() {
var new_msgs_indicator = this.el.querySelector('.new-msgs-indicator');
if (!_.isNull(new_msgs_indicator)) {
new_msgs_indicator.classList.add('hidden');
}
},
2018-05-07 18:20:15 +02:00
_markScrolled: function _markScrolled(ev) {
/* Called when the chat content is scrolled up or down.
* We want to record when the user has scrolled away from
* the bottom, so that we don't automatically scroll away
* from what the user is reading when new messages are
* received.
*/
if (ev && ev.preventDefault) {
ev.preventDefault();
}
2017-12-20 18:08:08 +01:00
2018-05-07 18:20:15 +02:00
var scrolled = true;
var is_at_bottom = this.content.scrollTop + this.content.clientHeight >= this.content.scrollHeight - 62; // sigh...
2018-05-07 18:20:15 +02:00
if (is_at_bottom) {
scrolled = false;
this.onScrolledDown();
}
2016-03-07 18:54:07 +01:00
2018-05-07 18:20:15 +02:00
u.safeSave(this.model, {
'scrolled': scrolled,
'top_visible_message': null
});
},
viewUnreadMessages: function viewUnreadMessages() {
this.model.save({
'scrolled': false,
'top_visible_message': null
});
this.scrollDown();
},
_scrollDown: function _scrollDown() {
/* Inner method that gets debounced */
if (_.isUndefined(this.content)) {
return;
}
2016-11-07 15:43:48 +01:00
2018-05-07 18:20:15 +02:00
if (u.isVisible(this.content) && !this.model.get('scrolled')) {
this.content.scrollTop = this.content.scrollHeight;
}
},
onScrolledDown: function onScrolledDown() {
this.hideNewMessagesIndicator();
2016-07-28 18:06:31 +02:00
2018-05-07 18:20:15 +02:00
if (_converse.windowState !== 'hidden') {
this.model.clearUnreadMsgCounter();
}
2016-11-07 15:43:48 +01:00
2018-05-07 18:20:15 +02:00
_converse.emit('chatBoxScrolledDown', {
'chatbox': this.model
});
},
onWindowStateChanged: function onWindowStateChanged(state) {
if (this.model.get('num_unread', 0) && !this.model.newMessageWillBeHidden()) {
this.model.clearUnreadMsgCounter();
}
2018-05-07 18:20:15 +02:00
}
});
2016-11-07 15:43:48 +01:00
2018-05-07 18:20:15 +02:00
_converse.on('connected', function () {
// Advertise that we support XEP-0382 Message Spoilers
2018-05-15 10:32:13 +02:00
_converse.api.disco.own.features.add(Strophe.NS.SPOILER);
2018-05-07 18:20:15 +02:00
});
/************************ BEGIN API ************************/
2018-05-07 18:20:15 +02:00
_.extend(_converse.api, {
'chatviews': {
'get': function get(jids) {
if (_.isUndefined(jids)) {
_converse.log("chats.create: You need to provide at least one JID", Strophe.LogLevel.ERROR);
return null;
}
if (_.isString(jids)) {
return _converse.chatboxviews.get(jids);
}
return _.map(jids, function (jid) {
return _converse.chatboxviews.get(jids);
});
}
2018-05-07 18:20:15 +02:00
}
});
2018-05-07 18:20:15 +02:00
/************************ END API ************************/
}
});
2018-05-07 18:20:15 +02:00
return converse;
});
2018-05-07 18:20:15 +02:00
//# sourceMappingURL=converse-chatview.js.map;
2016-03-07 18:54:07 +01:00
define('tpl!converse_brand_heading', ['lodash'], function(_) {return function(o) {
var __t, __p = '';
__p += '<span class="brand-heading-container">\n <div class="brand-heading">\n <a href="https://conversejs.org" target="_blank" rel="noopener">\n <i class="icon-conversejs"></i><span class="brand-name">converse</span>\n </a>\n </div>\n</span>\n';
return __p
};});
2016-03-16 12:49:35 +01:00
2016-11-07 15:43:48 +01:00
define('tpl!controlbox', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<div class="flyout box-flyout">\n <div class="chat-head controlbox-head">\n ';
if (!o.sticky_controlbox) { ;
__p += '\n <a class="chatbox-btn close-chatbox-button fa fa-close"></a>\n ';
} ;
__p += '\n </div>\n <div class="controlbox-panes"></div>\n</div>\n';
return __p
};});
define('tpl!controlbox_toggle', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<span class="toggle-feedback">' +
__e(o.label_toggle) +
'</span>\n';
return __p
};});
2016-11-07 15:43:48 +01:00
define('tpl!login_panel', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
2018-05-23 04:27:33 +02:00
__p += '<div id="converse-login-panel" class="controlbox-pane fade-in row no-gutters">\n <form id="converse-login" class="converse-form" method="post">\n <div class="conn-feedback fade-in ';
if (!o.conn_feedback_subject) { ;
__p += ' hidden ';
} ;
__p += ' ' +
__e(o.conn_feedback_class) +
'">\n <p class="feedback-subject">' +
__e( o.conn_feedback_subject ) +
'</p>\n <p class="feedback-message ';
if (!o.conn_feedback_message) { ;
__p += ' hidden ';
} ;
__p += '">' +
__e(o.conn_feedback_message) +
'</p>\n </div>\n ';
if (o.auto_login || o._converse.CONNECTION_STATUS[o.connection_status] === 'CONNECTING') { ;
__p += '\n <span class="spinner fa fa-spinner centered"/>\n ';
} else { ;
__p += '\n ';
if (o.authentication == o.LOGIN || o.authentication == o.EXTERNAL) { ;
2018-05-23 04:27:33 +02:00
__p += '\n <div class="form-group">\n <label for="converse-login-jid">' +
__e(o.__("XMPP Username:")) +
2018-05-23 04:27:33 +02:00
'</label>\n <input id="converse-login-jid" class="form-control" autofocus required="required" type="text" name="jid" placeholder="' +
__e(o.placeholder_username) +
'">\n </div>\n ';
if (o.authentication !== o.EXTERNAL) { ;
2018-05-23 04:27:33 +02:00
__p += '\n <div class="form-group">\n <label for="converse-login-password">' +
__e(o.__("Password:")) +
2018-05-23 04:27:33 +02:00
'</label>\n <input id="converse-login-password" class="form-control" required="required" type="password" name="password" placeholder="' +
__e(o.__('password')) +
'">\n </div>\n ';
} ;
2018-05-23 04:27:33 +02:00
__p += '\n <div class="form-group form-check">\n <input id="converse-login-trusted" type="checkbox" class="form-check-input" name="trusted" ';
if (o._converse.trusted) { ;
__p += ' checked="checked" ';
} ;
__p += '>\n <label for="converse-login-trusted" class="form-check-label">' +
__e(o.__('This is a trusted device')) +
'</label>\n <i class="fa fa-info-circle" data-toggle="popover"\n data-title="Trusted device?"\n data-content="' +
__e(o.__('To improve performance, we cache your data in this browser. Uncheck this box if this is a public computer or if you want your data to be deleted when you log out. It\'s important that you explicitly log out, otherwise not all cached data might be deleted.')) +
'"></i>\n </div>\n\n <fieldset class="buttons">\n <input class="btn btn-primary" type="submit" value="' +
__e(o.__('Log in')) +
'">\n </fieldset>\n ';
} ;
__p += '\n ';
if (o.authentication == o.ANONYMOUS) { ;
__p += '\n <input class="btn btn-primary login-anon" type="submit" value="' +
__e(o.__('Click here to log in anonymously')) +
'"/>\n ';
} ;
__p += '\n ';
if (o.authentication == o.PREBIND) { ;
__p += '\n <p>Disconnected.</p>\n ';
} ;
__p += '\n ';
} ;
__p += '\n </form>\n</div>\n';
return __p
};});
2017-06-23 20:25:33 +02:00
2017-07-22 22:21:05 +02:00
define('tpl!add_contact_modal', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<!-- Add contact Modal -->\n<div class="modal fade" id="add-contact-modal" tabindex="-1" role="dialog" aria-labelledby="addContactModalLabel" aria-hidden="true">\n <div class="modal-dialog" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title" id="addContactModalLabel">' +
__e(o.heading_new_contact) +
'</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>\n </div>\n <form class="converse-form add-xmpp-contact">\n <div class="modal-body">\n <div class="form-group ';
if (o._converse.xhr_user_search_url) { ;
__p += ' hidden ';
} ;
__p += '">\n <label class="clearfix" for="jid">' +
__e(o.label_xmpp_address) +
':</label>\n <input type="text" name="jid" required="required" value="' +
__e(o.jid) +
'"\n class="form-control ';
if (o.error_message) { ;
__p += ' is-invalid ';
} ;
__p += '"\n placeholder="' +
__e(o.contact_placeholder) +
'"/>\n ';
if (o.error_message) { ;
__p += '\n <div class="invalid-feedback">' +
__e(o.error_message) +
'</div>\n ';
} ;
__p += '\n </div>\n <div class="form-group">\n <label class="clearfix" for="name">' +
__e(o.label_nickname) +
':</label>\n <input type="text" name="name" value="' +
__e(o.nickname) +
'"\n class="form-control ';
if (o.error_message) { ;
__p += ' is-invalid ';
} ;
__p += '"\n placeholder="' +
__e(o.nickname_placeholder) +
'"/>\n </div>\n </div>\n <div class="modal-footer">\n <button type="submit" class="btn btn-primary">' +
__e(o.label_add) +
'</button>\n </div>\n </form>\n </div>\n </div>\n</div>\n';
return __p
};});
define('tpl!group_header', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
2018-05-23 04:27:33 +02:00
__p += '<a href="#" class="group-toggle controlbox-padded" title="' +
__e(o.desc_group_toggle) +
'">\n <span class="fa ';
if (o.toggle_state === o._converse.OPENED) { ;
__p += ' fa-caret-down ';
} else { ;
__p += ' fa-caret-right ';
} ;
__p += '">\n </span> ' +
__e(o.label_group) +
'</a>\n<ul class="roster-group-contacts ';
if (o.toggle_state === o._converse.CLOSED) { ;
__p += ' collapsed ';
} ;
__p += '"></ul>\n';
return __p
};});
2017-06-23 20:25:33 +02:00
define('tpl!pending_contact', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
2017-07-22 22:21:05 +02:00
if (o.allow_chat_pending_contacts) { ;
__p += '\n<a class="open-chat w-100" href="#">\n';
} ;
2018-05-07 18:20:15 +02:00
__p += '\n<span class="pending-contact-name w-100" title="JID: ' +
__e(o.jid) +
'">' +
2018-05-07 18:20:15 +02:00
__e(o.display_name) +
'</span> \n';
if (o.allow_chat_pending_contacts) { ;
__p += '</a>\n';
} ;
__p += '\n<a class="remove-xmpp-contact fa fa-trash" title="' +
__e(o.desc_remove) +
'" href="#"></a>\n';
return __p
};});
2017-06-23 20:25:33 +02:00
2017-07-22 22:21:05 +02:00
define('tpl!requesting_contact', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
if (o.allow_chat_pending_contacts) { ;
__p += '\n<a class="open-chat w-100"href="#">\n';
} ;
2018-05-07 18:20:15 +02:00
__p += '\n<span class="req-contact-name w-100" title="JID: ' +
__e(o.jid) +
'">' +
2018-05-07 18:20:15 +02:00
__e(o.display_name) +
'</span>\n';
if (o.allow_chat_pending_contacts) { ;
__p += '\n</a>\n';
} ;
__p += '\n<a class="accept-xmpp-request fa fa-check"\n aria-label="' +
__e(o.desc_accept) +
'" title="' +
__e(o.desc_accept) +
'" href="#"></a>\n<a class="decline-xmpp-request fa fa-times"\n aria-label="' +
__e(o.desc_decline) +
'" title="' +
__e(o.desc_decline) +
'" href="#"></a>\n';
return __p
};});
define('tpl!roster', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
2018-05-23 04:27:33 +02:00
__p += '<div class="d-flex controlbox-padded">\n <span class="w-100 controlbox-heading">' +
__e(o.heading_contacts) +
'</span>\n <a class="chatbox-btn add-contact fa fa-user-plus" title="' +
__e(o.title_add_contact) +
'"\n data-toggle="modal" data-target="#add-contact-modal"></a>\n</div>\n\n<form class="roster-filter-form"></form>\n\n<div class="roster-contacts"></div>\n';
return __p
};});
define('tpl!roster_filter', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
2018-05-23 04:27:33 +02:00
__p += '<form class="controlbox-padded roster-filter-form input-button-group ';
if (!o.visible) { ;
__p += ' hidden ';
} ;
__p += '">\n <div class="form-inline flex-nowrap">\n <div class="btn-group">\n <input ';
if (o.filter_text) { ;
__p += ' value="' +
__e(o.filter_text) +
'" ';
} ;
__p += '\n class="roster-filter form-control ';
if (o.filter_type === 'state') { ;
__p += ' hidden ';
} ;
__p += '"\n placeholder="' +
__e(o.placeholder) +
'">\n <span class="clear-input fa fa-times ';
if (!o.filter_text) { ;
__p += ' hidden ';
} ;
__p += '"></span>\n </div>\n\n <select class="form-control state-type ';
if (o.filter_type !== 'state') { ;
__p += ' hidden ';
} ;
__p += '">\n <option value="">' +
__e(o.label_any) +
'</option>\n <option ';
if (o.chat_state === 'unread_messages') { ;
__p += ' selected="selected" ';
} ;
__p += '\n value="unread_messages">' +
__e(o.label_unread_messages) +
'</option>\n <option ';
if (o.chat_state === 'online') { ;
__p += ' selected="selected" ';
} ;
__p += '\n value="online">' +
__e(o.label_online) +
'</option>\n <option ';
if (o.chat_state === 'chat') { ;
__p += ' selected="selected" ';
} ;
__p += '\n value="chat">' +
__e(o.label_chatty) +
'</option>\n <option ';
if (o.chat_state === 'dnd') { ;
__p += ' selected="selected" ';
} ;
__p += '\n value="dnd">' +
__e(o.label_busy) +
'</option>\n <option ';
if (o.chat_state === 'away') { ;
__p += ' selected="selected" ';
} ;
__p += '\n value="away">' +
__e(o.label_away) +
'</option>\n <option ';
if (o.chat_state === 'xa') { ;
__p += ' selected="selected" ';
} ;
__p += '\n value="xa">' +
__e(o.label_xa) +
'</option>\n <option ';
if (o.chat_state === 'offline') { ;
__p += ' selected="selected" ';
} ;
__p += '\n value="offline">' +
__e(o.label_offline) +
'</option>\n </select>\n\n <div class="filter-by d-flex flex-nowrap">\n <span class="fa fa-user ';
if (o.filter_type === 'contacts') { ;
__p += ' selected ';
} ;
__p += '" data-type="contacts" title="' +
__e(o.title_contact_filter) +
'"></span>\n <span class="fa fa-users ';
if (o.filter_type === 'groups') { ;
__p += ' selected ';
} ;
__p += '" data-type="groups" title="' +
__e(o.title_group_filter) +
'"></span>\n <span class="fa fa-circle ';
if (o.filter_type === 'state') { ;
__p += ' selected ';
} ;
__p += '" data-type="state" title="' +
__e(o.title_status_filter) +
'"></span>\n </div>\n </div>\n</form>\n';
return __p
};});
2017-12-20 18:08:08 +01:00
define('tpl!roster_item', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<a class="open-chat w-100 ';
if (o.num_unread) { ;
__p += ' unread-msgs ';
} ;
__p += '"\n title="' +
__e(o.desc_chat) +
'" href="#">\n <span class="fa ' +
__e(o.status_icon) +
'" title="' +
__e(o.desc_status) +
'"></span>\n ';
if (o.num_unread) { ;
__p += '\n <span class="msgs-indicator">' +
__e( o.num_unread ) +
'</span>\n ';
} ;
__p += '\n <span class="contact-name ';
if (o.num_unread) { ;
__p += ' unread-msgs ';
} ;
__p += '">' +
2018-05-07 18:20:15 +02:00
__e(o.display_name) +
'</span></a>\n';
if (o.allow_contact_removal) { ;
__p += '\n<a class="remove-xmpp-contact fa fa-trash" title="' +
__e(o.desc_remove) +
'" href="#"></a>\n';
} ;
__p += '\n';
return __p
};});
2017-12-20 18:08:08 +01:00
define('tpl!search_contact', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<li>\n <form class="search-xmpp-contact">\n <input type="text"\n name="identifier"\n class="username"\n placeholder="' +
__e(o.label_contact_name) +
'"/>\n <button type="submit">' +
__e(o.label_search) +
'</button>\n </form>\n</li>\n';
return __p
};});
2018-05-07 18:20:15 +02:00
// Converse.js
2018-03-25 21:21:43 +02:00
// http://conversejs.org
//
2018-05-07 18:20:15 +02:00
// Copyright (c) 2012-2018, the Converse.js developers
2018-03-25 21:21:43 +02:00
// Licensed under the Mozilla Public License (MPLv2)
(function (root, factory) {
define('converse-rosterview',["converse-core", "tpl!add_contact_modal", "tpl!group_header", "tpl!pending_contact", "tpl!requesting_contact", "tpl!roster", "tpl!roster_filter", "tpl!roster_item", "tpl!search_contact", "awesomplete", "converse-chatboxes", "converse-modal"], factory);
})(this, function (converse, tpl_add_contact_modal, tpl_group_header, tpl_pending_contact, tpl_requesting_contact, tpl_roster, tpl_roster_filter, tpl_roster_item, tpl_search_contact, Awesomplete) {
2018-03-25 21:21:43 +02:00
"use strict";
2017-12-20 18:08:08 +01:00
2018-03-25 21:21:43 +02:00
var _converse$env = converse.env,
Backbone = _converse$env.Backbone,
Strophe = _converse$env.Strophe,
2018-03-25 21:21:43 +02:00
$iq = _converse$env.$iq,
b64_sha1 = _converse$env.b64_sha1,
sizzle = _converse$env.sizzle,
_ = _converse$env._;
var u = converse.env.utils;
converse.plugins.add('converse-rosterview', {
2018-05-07 18:20:15 +02:00
dependencies: ["converse-roster", "converse-modal"],
2018-03-25 21:21:43 +02:00
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
afterReconnected: function afterReconnected() {
this.__super__.afterReconnected.apply(this, arguments);
},
2018-03-25 21:21:43 +02:00
_tearDown: function _tearDown() {
/* Remove the rosterview when tearing down. It gets created
* anew when reconnecting or logging in.
*/
this.__super__._tearDown.apply(this, arguments);
2017-12-20 18:08:08 +01:00
if (!_.isUndefined(this.rosterview)) {
this.rosterview.remove();
}
2018-03-25 21:21:43 +02:00
},
RosterGroups: {
comparator: function comparator() {
// RosterGroupsComparator only gets set later (once i18n is
// set up), so we need to wrap it in this nameless function.
2018-03-25 21:21:43 +02:00
var _converse = this.__super__._converse;
return _converse.RosterGroupsComparator.apply(this, arguments);
2018-03-25 21:21:43 +02:00
}
}
},
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
var _converse = this._converse,
__ = _converse.__;
_converse.api.settings.update({
'allow_chat_pending_contacts': true,
'allow_contact_removal': true,
2018-05-07 18:20:15 +02:00
'roster_groups': true,
'show_toolbar': true,
'xhr_user_search_url': null
});
_converse.api.promises.add('rosterViewInitialized');
var STATUSES = {
'dnd': __('This contact is busy'),
'online': __('This contact is online'),
'offline': __('This contact is offline'),
'unavailable': __('This contact is unavailable'),
'xa': __('This contact is away for an extended period'),
'away': __('This contact is away')
};
var LABEL_CONTACTS = __('Contacts');
var LABEL_GROUPS = __('Groups');
var HEADER_CURRENT_CONTACTS = __('My contacts');
var HEADER_PENDING_CONTACTS = __('Pending contacts');
var HEADER_REQUESTING_CONTACTS = __('Contact requests');
var HEADER_UNGROUPED = __('Ungrouped');
var HEADER_WEIGHTS = {};
HEADER_WEIGHTS[HEADER_REQUESTING_CONTACTS] = 0;
HEADER_WEIGHTS[HEADER_CURRENT_CONTACTS] = 1;
HEADER_WEIGHTS[HEADER_UNGROUPED] = 2;
HEADER_WEIGHTS[HEADER_PENDING_CONTACTS] = 3;
_converse.RosterGroupsComparator = function (a, b) {
/* Groups are sorted alphabetically, ignoring case.
* However, Ungrouped, Requesting Contacts and Pending Contacts
* appear last and in that order.
*/
a = a.get('name');
b = b.get('name');
var special_groups = _.keys(HEADER_WEIGHTS);
var a_is_special = _.includes(special_groups, a);
var b_is_special = _.includes(special_groups, b);
if (!a_is_special && !b_is_special) {
return a.toLowerCase() < b.toLowerCase() ? -1 : a.toLowerCase() > b.toLowerCase() ? 1 : 0;
} else if (a_is_special && b_is_special) {
return HEADER_WEIGHTS[a] < HEADER_WEIGHTS[b] ? -1 : HEADER_WEIGHTS[a] > HEADER_WEIGHTS[b] ? 1 : 0;
} else if (!a_is_special && b_is_special) {
return b === HEADER_REQUESTING_CONTACTS ? 1 : -1;
} else if (a_is_special && !b_is_special) {
return a === HEADER_REQUESTING_CONTACTS ? -1 : 1;
2018-03-25 21:21:43 +02:00
}
};
_converse.AddContactModal = _converse.BootstrapModal.extend({
events: {
'submit form': 'addContactFromForm'
},
initialize: function initialize() {
_converse.BootstrapModal.prototype.initialize.apply(this, arguments);
this.model.on('change', this.render, this);
},
toHTML: function toHTML() {
var label_nickname = _converse.xhr_user_search_url ? __('Contact name') : __('Optional nickname');
return tpl_add_contact_modal(_.extend(this.model.toJSON(), {
'_converse': _converse,
'heading_new_contact': __('Add a Contact'),
'label_xmpp_address': __('XMPP Address'),
'label_nickname': label_nickname,
'contact_placeholder': __('name@example.org'),
'label_add': __('Add')
}));
},
afterRender: function afterRender() {
if (_converse.xhr_user_search_url && _.isString(_converse.xhr_user_search_url)) {
this.initXHRAutoComplete();
} else {
this.initJIDAutoComplete();
}
},
initJIDAutoComplete: function initJIDAutoComplete() {
var jid_input = this.el.querySelector('input[name="jid"]');
var list = _.uniq(_converse.roster.map(function (item) {
return Strophe.getDomainFromJid(item.get('jid'));
}));
new Awesomplete(jid_input, {
'list': list,
'data': function data(text, input) {
return input.slice(0, input.indexOf("@")) + "@" + text;
},
'filter': Awesomplete.FILTER_STARTSWITH
});
this.el.addEventListener('shown.bs.modal', function () {
jid_input.focus();
}, false);
},
initXHRAutoComplete: function initXHRAutoComplete() {
var name_input = this.el.querySelector('input[name="name"]');
var jid_input = this.el.querySelector('input[name="jid"]');
var awesomplete = new Awesomplete(name_input, {
'minChars': 1,
'list': []
});
var xhr = new window.XMLHttpRequest(); // `open` must be called after `onload` for mock/testing purposes.
xhr.onload = function () {
if (xhr.responseText) {
awesomplete.list = JSON.parse(xhr.responseText).map(function (i) {
//eslint-disable-line arrow-body-style
return {
2018-05-07 18:20:15 +02:00
'label': i.fullname || i.jid,
'value': i.jid
};
});
awesomplete.evaluate();
}
};
name_input.addEventListener('input', _.debounce(function () {
2018-05-23 04:27:33 +02:00
xhr.open("GET", "".concat(_converse.xhr_user_search_url, "q=").concat(name_input.value), true);
xhr.send();
}, 300));
this.el.addEventListener('awesomplete-selectcomplete', function (ev) {
jid_input.value = ev.text.value;
name_input.value = ev.text.label;
2018-03-25 21:21:43 +02:00
});
this.el.addEventListener('shown.bs.modal', function () {
name_input.focus();
}, false);
},
addContactFromForm: function addContactFromForm(ev) {
ev.preventDefault();
var data = new FormData(ev.target),
jid = data.get('jid'),
name = data.get('name');
ev.target.reset();
2018-03-30 14:37:05 +02:00
if (!jid || _.compact(jid.split('@')).length < 2) {
this.model.set({
'error_message': __('Please enter a valid XMPP address'),
'jid': jid
});
} else {
_converse.roster.addAndSubscribe(jid, name);
2018-03-30 14:37:05 +02:00
this.model.clear();
this.modal.hide();
}
}
});
_converse.RosterFilter = Backbone.Model.extend({
initialize: function initialize() {
this.set({
'filter_text': '',
'filter_type': 'contacts',
'chat_state': ''
});
}
});
_converse.RosterFilterView = Backbone.VDOMView.extend({
tagName: 'form',
className: 'roster-filter-form',
events: {
"keydown .roster-filter": "liveFilter",
"submit form.roster-filter-form": "submitFilter",
"click .clear-input": "clearFilter",
"click .filter-by span": "changeTypeFilter",
"change .state-type": "changeChatStateFilter"
},
initialize: function initialize() {
this.model.on('change:filter_type', this.render, this);
this.model.on('change:filter_text', this.render, this);
},
toHTML: function toHTML() {
return tpl_roster_filter(_.extend(this.model.toJSON(), {
visible: this.shouldBeVisible(),
placeholder: __('Filter'),
title_contact_filter: __('Filter by contact name'),
title_group_filter: __('Filter by group name'),
title_status_filter: __('Filter by status'),
label_any: __('Any'),
label_unread_messages: __('Unread'),
label_online: __('Online'),
label_chatty: __('Chatty'),
label_busy: __('Busy'),
label_away: __('Away'),
label_xa: __('Extended Away'),
label_offline: __('Offline')
}));
},
changeChatStateFilter: function changeChatStateFilter(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}
this.model.save({
'chat_state': this.el.querySelector('.state-type').value
});
},
changeTypeFilter: function changeTypeFilter(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
2018-03-30 14:37:05 +02:00
}
var type = ev.target.dataset.type;
2018-03-30 14:37:05 +02:00
if (type === 'state') {
this.model.save({
'filter_type': type,
'chat_state': this.el.querySelector('.state-type').value
});
} else {
this.model.save({
'filter_type': type,
'filter_text': this.el.querySelector('.roster-filter').value
});
}
},
liveFilter: _.debounce(function (ev) {
this.model.save({
'filter_text': this.el.querySelector('.roster-filter').value
});
}, 250),
submitFilter: function submitFilter(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
2018-03-30 14:37:05 +02:00
}
this.liveFilter();
this.render();
},
isActive: function isActive() {
/* Returns true if the filter is enabled (i.e. if the user
* has added values to the filter).
*/
if (this.model.get('filter_type') === 'state' || this.model.get('filter_text')) {
return true;
2018-03-30 14:37:05 +02:00
}
return false;
},
shouldBeVisible: function shouldBeVisible() {
return _converse.roster.length >= 5 || this.isActive();
},
showOrHide: function showOrHide() {
if (this.shouldBeVisible()) {
this.show();
} else {
this.hide();
}
},
show: function show() {
if (u.isVisible(this.el)) {
return this;
}
2018-03-30 14:37:05 +02:00
this.el.classList.add('fade-in');
this.el.classList.remove('hidden');
return this;
},
hide: function hide() {
if (!u.isVisible(this.el)) {
return this;
}
2018-03-30 14:37:05 +02:00
this.model.save({
'filter_text': '',
'chat_state': ''
2018-03-30 14:37:05 +02:00
});
this.el.classList.add('hidden');
return this;
2018-03-30 14:37:05 +02:00
},
clearFilter: function clearFilter(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
u.hideElement(this.el.querySelector('.clear-input'));
}
2018-03-30 14:37:05 +02:00
var roster_filter = this.el.querySelector('.roster-filter');
roster_filter.value = '';
this.model.save({
'filter_text': ''
2018-03-30 14:37:05 +02:00
});
}
});
_converse.RosterContactView = Backbone.NativeView.extend({
tagName: 'li',
2018-05-23 04:27:33 +02:00
className: 'd-flex hidden controlbox-padded',
events: {
"click .accept-xmpp-request": "acceptRequest",
"click .decline-xmpp-request": "declineRequest",
"click .open-chat": "openChat",
"click .remove-xmpp-contact": "removeContact"
2018-03-30 14:37:05 +02:00
},
initialize: function initialize() {
this.model.on("change", this.render, this);
this.model.on("destroy", this.remove, this);
this.model.on("open", this.openChat, this);
2018-05-07 18:20:15 +02:00
this.model.on("remove", this.remove, this);
2018-05-23 04:27:33 +02:00
this.model.presence.on("change:show", this.render, this);
2018-05-07 18:20:15 +02:00
this.model.vcard.on('change:fullname', this.render, this);
},
render: function render() {
var that = this;
2018-03-30 14:37:05 +02:00
if (!this.mayBeShown()) {
u.hideElement(this.el);
return this;
}
2018-03-30 14:37:05 +02:00
var item = this.model,
ask = item.get('ask'),
2018-05-23 04:27:33 +02:00
show = item.presence.get('show'),
requesting = item.get('requesting'),
subscription = item.get('subscription');
var classes_to_remove = ['current-xmpp-contact', 'pending-xmpp-contact', 'requesting-xmpp-contact'].concat(_.keys(STATUSES));
2018-03-30 14:37:05 +02:00
_.each(classes_to_remove, function (cls) {
if (_.includes(that.el.className, cls)) {
that.el.classList.remove(cls);
2018-03-30 14:37:05 +02:00
}
});
2018-05-23 04:27:33 +02:00
this.el.classList.add(show);
this.el.setAttribute('data-status', show);
2018-03-30 14:37:05 +02:00
if (ask === 'subscribe' || subscription === 'from') {
/* ask === 'subscribe'
* Means we have asked to subscribe to them.
*
* subscription === 'from'
* They are subscribed to use, but not vice versa.
* We assume that there is a pending subscription
* from us to them (otherwise we're in a state not
* supported by converse.js).
*
* So in both cases the user is a "pending" contact.
*/
2018-05-07 18:20:15 +02:00
var display_name = item.getDisplayName();
this.el.classList.add('pending-xmpp-contact');
this.el.innerHTML = tpl_pending_contact(_.extend(item.toJSON(), {
2018-05-07 18:20:15 +02:00
'display_name': display_name,
'desc_remove': __('Click to remove %1$s as a contact', display_name),
'allow_chat_pending_contacts': _converse.allow_chat_pending_contacts
}));
} else if (requesting === true) {
2018-05-07 18:20:15 +02:00
var _display_name = item.getDisplayName();
this.el.classList.add('requesting-xmpp-contact');
this.el.innerHTML = tpl_requesting_contact(_.extend(item.toJSON(), {
2018-05-07 18:20:15 +02:00
'display_name': _display_name,
'desc_accept': __("Click to accept the contact request from %1$s", _display_name),
'desc_decline': __("Click to decline the contact request from %1$s", _display_name),
'allow_chat_pending_contacts': _converse.allow_chat_pending_contacts
}));
} else if (subscription === 'both' || subscription === 'to') {
this.el.classList.add('current-xmpp-contact');
this.el.classList.remove(_.without(['both', 'to'], subscription)[0]);
this.el.classList.add(subscription);
this.renderRosterItem(item);
}
return this;
},
renderRosterItem: function renderRosterItem(item) {
var status_icon = 'fa-times-circle';
2018-05-23 04:27:33 +02:00
var show = item.presence.get('show') || 'offline';
2018-05-23 04:27:33 +02:00
if (show === 'online') {
status_icon = 'fa-circle';
2018-05-23 04:27:33 +02:00
} else if (show === 'away') {
status_icon = 'fa-dot-circle-o';
2018-05-23 04:27:33 +02:00
} else if (show === 'xa') {
status_icon = 'fa-circle-o';
2018-05-23 04:27:33 +02:00
} else if (show === 'dnd') {
status_icon = 'fa-minus-circle';
}
2018-05-07 18:20:15 +02:00
var display_name = item.getDisplayName();
this.el.innerHTML = tpl_roster_item(_.extend(item.toJSON(), {
2018-05-07 18:20:15 +02:00
'display_name': display_name,
2018-05-23 04:27:33 +02:00
'desc_status': STATUSES[show],
'status_icon': status_icon,
2018-05-07 18:20:15 +02:00
'desc_chat': __('Click to chat with %1$s (JID: %2$s)', display_name, item.get('jid')),
'desc_remove': __('Click to remove %1$s as a contact', display_name),
'allow_contact_removal': _converse.allow_contact_removal,
'num_unread': item.get('num_unread') || 0
}));
return this;
},
mayBeShown: function mayBeShown() {
/* Return a boolean indicating whether this contact should
* generally be visible in the roster.
*
* It doesn't check for the more specific case of whether
* the group it's in is collapsed.
*/
2018-05-23 04:27:33 +02:00
var chatStatus = this.model.presence.get('show');
if (_converse.show_only_online_users && chatStatus !== 'online' || _converse.hide_offline_users && chatStatus === 'offline') {
// If pending or requesting, show
if (this.model.get('ask') === 'subscribe' || this.model.get('subscription') === 'from' || this.model.get('requesting') === true) {
return true;
}
return false;
2018-03-30 14:37:05 +02:00
}
return true;
2018-03-30 14:37:05 +02:00
},
openChat: function openChat(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}
2018-03-30 14:37:05 +02:00
var attrs = this.model.attributes;
2018-03-30 14:37:05 +02:00
_converse.api.chats.open(attrs.jid, attrs);
2018-03-30 14:37:05 +02:00
},
removeContact: function removeContact(ev) {
var _this = this;
2018-03-30 14:37:05 +02:00
if (ev && ev.preventDefault) {
ev.preventDefault();
}
2018-03-30 14:37:05 +02:00
if (!_converse.allow_contact_removal) {
return;
}
2018-03-30 14:37:05 +02:00
var result = confirm(__("Are you sure you want to remove this contact?"));
2018-03-30 14:37:05 +02:00
if (result === true) {
this.model.removeFromRoster(function (iq) {
_this.model.destroy();
2018-03-30 14:37:05 +02:00
_this.remove();
}, function (err) {
alert(__('Sorry, there was an error while trying to remove %1$s as a contact.', name));
_converse.log(err, Strophe.LogLevel.ERROR);
});
2018-03-30 14:37:05 +02:00
}
},
acceptRequest: function acceptRequest(ev) {
2018-03-30 14:37:05 +02:00
var _this2 = this;
if (ev && ev.preventDefault) {
ev.preventDefault();
}
2018-03-30 14:37:05 +02:00
2018-05-07 18:20:15 +02:00
_converse.roster.sendContactAddIQ(this.model.get('jid'), this.model.getFullname(), [], function () {
_this2.model.authorize().subscribe();
2018-03-30 14:37:05 +02:00
});
},
declineRequest: function declineRequest(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}
2018-03-30 14:37:05 +02:00
var result = confirm(__("Are you sure you want to decline this contact request?"));
2018-03-30 14:37:05 +02:00
if (result === true) {
this.model.unauthorize().destroy();
2018-03-30 14:37:05 +02:00
}
return this;
}
});
_converse.RosterGroupView = Backbone.OrderedListView.extend({
tagName: 'div',
className: 'roster-group hidden',
events: {
"click a.group-toggle": "toggle"
},
ItemView: _converse.RosterContactView,
listItems: 'model.contacts',
listSelector: '.roster-group-contacts',
2018-05-23 04:27:33 +02:00
sortEvent: 'presenceChanged',
initialize: function initialize() {
Backbone.OrderedListView.prototype.initialize.apply(this, arguments);
this.model.contacts.on("change:subscription", this.onContactSubscriptionChange, this);
this.model.contacts.on("change:requesting", this.onContactRequestChange, this);
this.model.contacts.on("remove", this.onRemove, this);
2018-03-30 14:37:05 +02:00
_converse.roster.on('change:groups', this.onContactGroupChange, this); // This event gets triggered once *all* contacts (i.e. not
// just this group's) have been fetched from browser
// storage or the XMPP server and once they've been
// assigned to their various groups.
_converse.rosterview.on('rosterContactsFetchedAndProcessed', this.sortAndPositionAllItems.bind(this));
},
render: function render() {
this.el.setAttribute('data-group', this.model.get('name'));
this.el.innerHTML = tpl_group_header({
'label_group': this.model.get('name'),
'desc_group_toggle': this.model.get('description'),
'toggle_state': this.model.get('state'),
'_converse': _converse
2018-03-30 14:37:05 +02:00
});
this.contacts_el = this.el.querySelector('.roster-group-contacts');
return this;
2018-03-30 14:37:05 +02:00
},
show: function show() {
var _this3 = this;
2018-03-30 14:37:05 +02:00
u.showElement(this.el);
_.each(this.getAll(), function (contact_view) {
if (contact_view.mayBeShown() && _this3.model.get('state') === _converse.OPENED) {
u.showElement(contact_view.el);
}
2018-03-30 14:37:05 +02:00
});
return this;
},
collapse: function collapse() {
return u.slideIn(this.contacts_el);
},
filterOutContacts: function filterOutContacts() {
var _this4 = this;
2017-07-22 22:21:05 +02:00
var contacts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
/* Given a list of contacts, make sure they're filtered out
* (aka hidden) and that all other contacts are visible.
*
* If all contacts are hidden, then also hide the group
* title.
*/
var shown = 0;
var all_contact_views = this.getAll();
2017-07-22 22:21:05 +02:00
_.each(this.model.contacts.models, function (contact) {
var contact_view = _this4.get(contact.get('id'));
if (_.includes(contacts, contact)) {
u.hideElement(contact_view.el);
} else if (contact_view.mayBeShown()) {
u.showElement(contact_view.el);
shown += 1;
2018-03-25 21:21:43 +02:00
}
});
2017-12-20 18:08:08 +01:00
if (shown) {
u.showElement(this.el);
2017-12-20 18:08:08 +01:00
} else {
u.hideElement(this.el);
}
},
getFilterMatches: function getFilterMatches(q, type) {
/* Given the filter query "q" and the filter type "type",
* return a list of contacts that need to be filtered out.
*/
if (q.length === 0) {
return [];
2017-12-20 18:08:08 +01:00
}
2018-03-25 21:21:43 +02:00
var matches;
q = q.toLowerCase();
2018-03-25 21:21:43 +02:00
if (type === 'state') {
if (this.model.get('name') === HEADER_REQUESTING_CONTACTS) {
// When filtering by chat state, we still want to
// show requesting contacts, even though they don't
// have the state in question.
matches = this.model.contacts.filter(function (contact) {
2018-05-23 04:27:33 +02:00
return !_.includes(contact.presence.get('show'), q) && !contact.get('requesting');
});
} else if (q === 'unread_messages') {
matches = this.model.contacts.filter({
'num_unread': 0
});
} else {
2018-05-23 04:27:33 +02:00
matches = this.model.contacts.filter(function (contact) {
return !_.includes(contact.presence.get('show'), q);
});
}
2018-03-25 21:21:43 +02:00
} else {
2018-05-07 18:20:15 +02:00
matches = this.model.contacts.filter(function (contact) {
return !_.includes(contact.getDisplayName().toLowerCase(), q.toLowerCase());
});
2018-03-25 21:21:43 +02:00
}
return matches;
},
filter: function filter(q, type) {
/* Filter the group's contacts based on the query "q".
*
* If all contacts are filtered out (i.e. hidden), then the
* group must be filtered out as well.
*/
if (_.isNil(q)) {
type = type || _converse.rosterview.filter_view.model.get('filter_type');
if (type === 'state') {
q = _converse.rosterview.filter_view.model.get('chat_state');
} else {
q = _converse.rosterview.filter_view.model.get('filter_text');
}
}
this.filterOutContacts(this.getFilterMatches(q, type));
},
toggle: function toggle(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}
2018-03-25 21:21:43 +02:00
var icon_el = ev.target.querySelector('.fa');
2018-03-25 21:21:43 +02:00
if (_.includes(icon_el.classList, "fa-caret-down")) {
this.model.save({
state: _converse.CLOSED
});
this.collapse().then(function () {
icon_el.classList.remove("fa-caret-down");
icon_el.classList.add("fa-caret-right");
});
} else {
icon_el.classList.remove("fa-caret-right");
icon_el.classList.add("fa-caret-down");
this.model.save({
state: _converse.OPENED
});
this.filter();
u.showElement(this.el);
u.slideOut(this.contacts_el);
}
},
onContactGroupChange: function onContactGroupChange(contact) {
var in_this_group = _.includes(contact.get('groups'), this.model.get('name'));
2018-03-25 21:21:43 +02:00
var cid = contact.get('id');
var in_this_overview = !this.get(cid);
2018-03-25 21:21:43 +02:00
if (in_this_group && !in_this_overview) {
this.items.trigger('add', contact);
} else if (!in_this_group) {
this.removeContact(contact);
}
},
onContactSubscriptionChange: function onContactSubscriptionChange(contact) {
if (this.model.get('name') === HEADER_PENDING_CONTACTS && contact.get('subscription') !== 'from') {
this.removeContact(contact);
}
},
onContactRequestChange: function onContactRequestChange(contact) {
if (this.model.get('name') === HEADER_REQUESTING_CONTACTS && !contact.get('requesting')) {
this.removeContact(contact);
}
},
removeContact: function removeContact(contact) {
// We suppress events, otherwise the remove event will
// also cause the contact's view to be removed from the
// "Pending Contacts" group.
this.model.contacts.remove(contact, {
'silent': true
});
this.onRemove(contact);
},
onRemove: function onRemove(contact) {
this.remove(contact.get('jid'));
2018-03-25 21:21:43 +02:00
if (this.model.contacts.length === 0) {
this.remove();
}
}
});
_converse.RosterView = Backbone.OrderedListView.extend({
tagName: 'div',
id: 'converse-roster',
2018-04-30 16:05:20 +02:00
className: 'controlbox-section',
ItemView: _converse.RosterGroupView,
listItems: 'model',
listSelector: '.roster-contacts',
sortEvent: null,
// Groups are immutable, so they don't get re-sorted
subviewIndex: 'name',
events: {
'click a.chatbox-btn.add-contact': 'showAddContactModal'
},
initialize: function initialize() {
var _this5 = this;
Backbone.OrderedListView.prototype.initialize.apply(this, arguments);
2018-03-25 21:21:43 +02:00
_converse.roster.on("add", this.onContactAdded, this);
2018-03-25 21:21:43 +02:00
_converse.roster.on('change:groups', this.onContactAdded, this);
2018-03-25 21:21:43 +02:00
_converse.roster.on('change', this.onContactChange, this);
2018-03-25 21:21:43 +02:00
_converse.roster.on("destroy", this.update, this);
2018-03-25 21:21:43 +02:00
_converse.roster.on("remove", this.update, this);
2018-03-25 21:21:43 +02:00
2018-05-23 04:27:33 +02:00
_converse.presences.on('change:show', function () {
_this5.update();
_this5.updateFilter();
});
this.model.on("reset", this.reset, this); // This event gets triggered once *all* contacts (i.e. not
// just this group's) have been fetched from browser
// storage or the XMPP server and once they've been
// assigned to their various groups.
2016-05-03 17:37:10 +02:00
_converse.on('rosterGroupsFetched', this.sortAndPositionAllItems.bind(this));
2018-03-25 21:21:43 +02:00
_converse.on('rosterContactsFetched', function () {
_converse.roster.each(function (contact) {
return _this5.addRosterContact(contact, {
'silent': true
});
});
2018-03-25 21:21:43 +02:00
_this5.update();
2018-03-25 21:21:43 +02:00
_this5.updateFilter();
2018-03-25 21:21:43 +02:00
_this5.trigger('rosterContactsFetchedAndProcessed');
});
2018-03-25 21:21:43 +02:00
this.createRosterFilter();
},
render: function render() {
this.el.innerHTML = tpl_roster({
'heading_contacts': __('Contacts'),
'title_add_contact': __('Add a contact')
});
var form = this.el.querySelector('.roster-filter-form');
this.el.replaceChild(this.filter_view.render().el, form);
this.roster_el = this.el.querySelector('.roster-contacts');
return this;
},
showAddContactModal: function showAddContactModal(ev) {
if (_.isUndefined(this.add_contact_modal)) {
this.add_contact_modal = new _converse.AddContactModal({
'model': new Backbone.Model()
});
}
2018-03-25 21:21:43 +02:00
this.add_contact_modal.show(ev);
},
createRosterFilter: function createRosterFilter() {
// Create a model on which we can store filter properties
var model = new _converse.RosterFilter();
model.id = b64_sha1("_converse.rosterfilter".concat(_converse.bare_jid));
model.browserStorage = new Backbone.BrowserStorage.local(this.filter.id);
this.filter_view = new _converse.RosterFilterView({
'model': model
});
this.filter_view.model.on('change', this.updateFilter, this);
this.filter_view.model.fetch();
},
updateFilter: _.debounce(function () {
/* Filter the roster again.
* Called whenever the filter settings have been changed or
* when contacts have been added, removed or changed.
*
* Debounced so that it doesn't get called for every
* contact fetched from browser storage.
*/
var type = this.filter_view.model.get('filter_type');
2018-03-25 21:21:43 +02:00
if (type === 'state') {
this.filter(this.filter_view.model.get('chat_state'), type);
} else {
this.filter(this.filter_view.model.get('filter_text'), type);
}
}, 100),
update: _.debounce(function () {
if (!u.isVisible(this.roster_el)) {
u.showElement(this.roster_el);
}
2018-03-25 21:21:43 +02:00
this.filter_view.showOrHide();
return this;
}, _converse.animate ? 100 : 0),
filter: function filter(query, type) {
// First we make sure the filter is restored to its
// original state
_.each(this.getAll(), function (view) {
if (view.model.contacts.length > 0) {
view.show().filter('');
}
}); // Now we can filter
query = query.toLowerCase();
if (type === 'groups') {
_.each(this.getAll(), function (view, idx) {
if (!_.includes(view.model.get('name').toLowerCase(), query.toLowerCase())) {
u.slideIn(view.el);
} else if (view.model.contacts.length > 0) {
u.slideOut(view.el);
2018-03-25 21:21:43 +02:00
}
});
} else {
_.each(this.getAll(), function (view) {
view.filter(query, type);
});
}
},
reset: function reset() {
_converse.roster.reset();
2018-03-25 21:21:43 +02:00
this.removeAll();
this.render().update();
return this;
},
onContactAdded: function onContactAdded(contact) {
2018-05-23 04:27:33 +02:00
this.addRosterContact(contact);
this.update();
this.updateFilter();
},
onContactChange: function onContactChange(contact) {
2018-05-23 04:27:33 +02:00
this.updateChatBox(contact);
this.update();
2016-05-03 17:37:10 +02:00
if (_.has(contact.changed, 'subscription')) {
if (contact.changed.subscription === 'from') {
this.addContactToGroup(contact, HEADER_PENDING_CONTACTS);
} else if (_.includes(['both', 'to'], contact.get('subscription'))) {
this.addExistingContact(contact);
}
}
2017-06-23 20:25:33 +02:00
if (_.has(contact.changed, 'ask') && contact.changed.ask === 'subscribe') {
this.addContactToGroup(contact, HEADER_PENDING_CONTACTS);
}
if (_.has(contact.changed, 'subscription') && contact.changed.requesting === 'true') {
this.addContactToGroup(contact, HEADER_REQUESTING_CONTACTS);
}
2017-06-23 20:25:33 +02:00
this.updateFilter();
},
updateChatBox: function updateChatBox(contact) {
var chatbox = _converse.chatboxes.get(contact.get('jid')),
changes = {};
if (!chatbox) {
return this;
}
if (_.has(contact.changed, 'status')) {
changes.status = contact.get('status');
}
chatbox.save(changes);
return this;
},
getGroup: function getGroup(name) {
/* Returns the group as specified by name.
* Creates the group if it doesn't exist.
*/
var view = this.get(name);
if (view) {
return view.model;
}
return this.model.create({
name: name,
id: b64_sha1(name)
});
},
addContactToGroup: function addContactToGroup(contact, name, options) {
this.getGroup(name).contacts.add(contact, options);
this.sortAndPositionAllItems();
},
addExistingContact: function addExistingContact(contact, options) {
var groups;
if (_converse.roster_groups) {
groups = contact.get('groups');
if (groups.length === 0) {
groups = [HEADER_UNGROUPED];
2018-03-25 21:21:43 +02:00
}
} else {
groups = [HEADER_CURRENT_CONTACTS];
}
_.each(groups, _.bind(this.addContactToGroup, this, contact, _, options));
},
addRosterContact: function addRosterContact(contact, options) {
if (contact.get('subscription') === 'both' || contact.get('subscription') === 'to') {
this.addExistingContact(contact, options);
} else {
if (contact.get('ask') === 'subscribe' || contact.get('subscription') === 'from') {
this.addContactToGroup(contact, HEADER_PENDING_CONTACTS, options);
} else if (contact.get('requesting') === true) {
this.addContactToGroup(contact, HEADER_REQUESTING_CONTACTS, options);
2018-03-25 21:21:43 +02:00
}
}
return this;
2018-03-25 21:21:43 +02:00
}
});
/* -------- Event Handlers ----------- */
var onChatBoxMaximized = function onChatBoxMaximized(chatboxview) {
/* When a chat box gets maximized, the num_unread counter needs
* to be cleared, but if chatbox is scrolled up, then num_unread should not be cleared.
*/
var chatbox = chatboxview.model;
if (chatbox.get('type') !== 'chatroom') {
var contact = _.head(_converse.roster.where({
'jid': chatbox.get('jid')
}));
if (!_.isUndefined(contact) && !chatbox.isScrolledUp()) {
contact.save({
'num_unread': 0
});
}
}
};
var onMessageReceived = function onMessageReceived(data) {
/* Given a newly received message, update the unread counter on
* the relevant roster contact.
*/
var chatbox = data.chatbox;
if (_.isUndefined(chatbox)) {
return;
}
if (_.isNull(data.stanza.querySelector('body'))) {
return; // The message has no text
}
if (chatbox.get('type') !== 'chatroom' && u.isNewMessage(data.stanza) && chatbox.newMessageWillBeHidden()) {
var contact = _.head(_converse.roster.where({
'jid': chatbox.get('jid')
}));
if (!_.isUndefined(contact)) {
contact.save({
'num_unread': contact.get('num_unread') + 1
});
}
}
};
var onChatBoxScrolledDown = function onChatBoxScrolledDown(data) {
var chatbox = data.chatbox;
2017-07-22 22:21:05 +02:00
if (_.isUndefined(chatbox)) {
return;
}
2018-03-25 21:21:43 +02:00
var contact = _.head(_converse.roster.where({
'jid': chatbox.get('jid')
}));
2018-03-25 21:21:43 +02:00
if (!_.isUndefined(contact)) {
contact.save({
'num_unread': 0
});
}
};
2016-11-07 15:43:48 +01:00
var initRoster = function initRoster() {
/* Create an instance of RosterView once the RosterGroups
* collection has been created (in converse-core.js)
*/
_converse.rosterview = new _converse.RosterView({
'model': _converse.rostergroups
});
2016-11-07 15:43:48 +01:00
_converse.rosterview.render();
2016-11-07 15:43:48 +01:00
_converse.emit('rosterViewInitialized');
};
_converse.api.listen.on('rosterInitialized', initRoster);
2018-03-25 21:21:43 +02:00
_converse.api.listen.on('rosterReadyAfterReconnection', initRoster);
_converse.api.listen.on('message', onMessageReceived);
2017-12-20 18:08:08 +01:00
_converse.api.listen.on('chatBoxMaximized', onChatBoxMaximized);
2018-01-29 16:33:30 +01:00
_converse.api.listen.on('chatBoxScrolledDown', onChatBoxScrolledDown);
}
});
});
//# sourceMappingURL=converse-rosterview.js.map;
2018-05-08 19:58:12 +02:00
define('tpl!alert', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<div class="alert ' +
__e(o.type) +
'" role="alert">' +
__e(o.message) +
'</div>\n';
return __p
};});
define('tpl!chat_status_modal', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<!-- Change status Modal -->\n<div class="modal fade" id="modal-status-change" tabindex="-1" role="dialog" aria-labelledby="changeStatusModalLabel" aria-hidden="true">\n <div class="modal-dialog" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title" id="changeStatusModalLabel">' +
__e(o.modal_title) +
'</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="' +
__e(o.label_close) +
'">\n <span aria-hidden="true">&times;</span>\n </button>\n </div>\n <form class="set-xmpp-status" id="set-xmpp-status">\n <div class="modal-body">\n <div class="form-group">\n <div class="custom-control custom-radio">\n <input ';
if (o.status === 'online') { ;
__p += ' checked="checked" ';
} ;
__p += '\n type="radio" id="radio-online" value="online" name="chat_status" class="custom-control-input">\n <label class="custom-control-label" for="radio-online">\n <span class="fa fa-circle"></span>&nbsp;' +
__e(o.label_online) +
'</label>\n </div>\n <div class="custom-control custom-radio">\n <input ';
if (o.status === 'busy') { ;
__p += ' checked="checked" ';
} ;
__p += '\n type="radio" id="radio-busy" value="dnd" name="chat_status" class="custom-control-input">\n <label class="custom-control-label" for="radio-busy">\n <span class="fa fa-minus-circle"></span>&nbsp;' +
__e(o.label_busy) +
'</label>\n </div>\n <div class="custom-control custom-radio">\n <input ';
if (o.status === 'away') { ;
__p += ' checked="checked" ';
} ;
__p += '\n type="radio" id="radio-away" value="away" name="chat_status" class="custom-control-input">\n <label class="custom-control-label" for="radio-away">\n <span class="fa fa-dot-circle-o"></span>&nbsp;' +
__e(o.label_away) +
'</label>\n </div>\n <div class="custom-control custom-radio">\n <input ';
if (o.status === 'xa') { ;
__p += ' checked="checked" ';
} ;
__p += '\n type="radio" id="radio-xa" value="xa" name="chat_status" class="custom-control-input">\n <label class="custom-control-label" for="radio-xa">\n <span class="fa fa-circle-o"></span>&nbsp;' +
__e(o.label_xa) +
'</label>\n </div>\n </div>\n <div class="btn-group w-100">\n <input name="status_message" type="text" class="form-control" \n value="' +
__e(o.status_message) +
'" placeholder="' +
__e(o.placeholder_status_message) +
'">\n <span class="clear-input fa fa-times ';
if (!o.status_message) { ;
__p += ' hidden ';
} ;
__p += '"></span>\n </div>\n </div>\n <div class="modal-footer">\n <button type="submit" class="btn btn-primary">' +
__e(o.label_save) +
'</button>\n </div>\n </form>\n </div>\n </div>\n</div>\n';
return __p
};});
define('tpl!profile_modal', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<div class="modal fade" id="user-profile-modal" tabindex="-1" role="dialog" aria-labelledby="user-profile-modal-label" aria-hidden="true">\n <div class="modal-dialog" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title" id="user-profile-modal-label">' +
__e(o.heading_profile) +
'</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="' +
__e(o.label_close) +
2018-05-08 19:58:12 +02:00
'"><span aria-hidden="true">&times;</span></button>\n </div>\n <form class="converse-form">\n <div class="modal-body">\n <div class="row">\n <div class="col-auto">\n <a class="change-avatar" href="#">\n ';
if (o.image) { ;
2018-05-08 19:58:12 +02:00
__p += '\n <img alt="' +
__e(o.alt_avatar) +
'" class="img-thumbnail avatar align-self-center" height="100px" width="100px" src="data:' +
__e(o.image_type) +
';base64,' +
__e(o.image) +
2018-05-08 19:58:12 +02:00
'"/>\n ';
} ;
2018-05-08 19:58:12 +02:00
__p += '\n ';
if (!o.image) { ;
__p += '\n <canvas class="avatar" height="100px" width="100px"/>\n ';
} ;
__p += '\n </a>\n <input class="hidden" name="image" type="file">\n </div>\n <div class="col">\n <div class="form-group">\n <label class="col-form-label">' +
__e(o.label_jid) +
':</label>\n <div>' +
__e(o.jid) +
2018-05-08 19:58:12 +02:00
'</div>\n </div>\n </div>\n </div>\n <div class="form-group">\n <label for="vcard-fullname" class="col-form-label">' +
__e(o.label_fullname) +
':</label>\n <input id="vcard-fullname" type="text" class="form-control" name="fn" value="' +
__e(o.fullname) +
'">\n </div>\n <div class="form-group">\n <label for="vcard-nickname" class="col-form-label">' +
__e(o.label_nickname) +
':</label>\n <input id="vcard-nickname" type="text" class="form-control" name="nickname" value="' +
__e(o.nickname) +
'">\n </div>\n <div class="form-group">\n <label for="vcard-url" class="col-form-label">' +
__e(o.label_url) +
':</label>\n <input id="vcard-url" type="url" class="form-control" name="url" value="' +
__e(o.url) +
'">\n </div>\n <div class="form-group">\n <label for="vcard-email" class="col-form-label">' +
__e(o.label_email) +
':</label>\n <input id="vcard-email" type="email" class="form-control" name="email" value="' +
__e(o.email) +
'">\n </div>\n <div class="form-group">\n <label for="vcard-role" class="col-form-label">' +
__e(o.label_role) +
':</label>\n <input id="vcard-role" type="text" class="form-control" name="role" value="' +
__e(o.role) +
'" aria-describedby="vcard-role-help">\n <small id="vcard-role-help" class="form-text text-muted">' +
__e(o.label_role_help) +
2018-05-11 00:19:49 +02:00
'</small>\n </div>\n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-secondary" data-dismiss="modal">' +
2018-05-08 19:58:12 +02:00
__e(o.label_close) +
2018-05-11 00:19:49 +02:00
'</button>\n <button type="submit" class="save-form btn btn-primary">' +
__e(o.label_save) +
2018-05-08 19:58:12 +02:00
'</button>\n </div>\n </form>\n </div>\n </div>\n</div>\n';
return __p
};});
2018-03-25 21:21:43 +02:00
define('tpl!profile_view', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
2018-05-23 04:27:33 +02:00
__p += '<div class="userinfo controlbox-padded">\n<div class="profile d-flex">\n <a class="show-profile" href="#">\n <img alt="User Avatar" class="avatar align-self-center" height="40px" width="40px" src="data:' +
__e(o.image_type) +
';base64,' +
__e(o.image) +
2018-05-08 19:58:12 +02:00
'"/>\n </a>\n <span class="username w-100 align-self-center">' +
__e(o.fullname) +
'</span>\n <!-- <a class="chatbox-btn fa fa-vcard align-self-center" title="' +
__e(o.title_your_profile) +
'" data-toggle="modal" data-target="#userProfileModal"></a> -->\n <!-- <a class="chatbox-btn fa fa-cog align-self-center" title="' +
__e(o.title_change_status) +
'" data-toggle="modal" data-target="#settingsModal"></a> -->\n ';
if (o._converse.allow_logout) { ;
__p += '\n <a class="chatbox-btn logout fa fa-sign-out align-self-center" title="' +
__e(o.title_log_out) +
'"></a>\n ';
} ;
__p += '\n</div>\n<div class="d-flex xmpp-status">\n <span class="' +
__e(o.chat_status) +
' w-100 align-self-center" data-value="' +
__e(o.chat_status) +
'">\n <span class="fa\n ';
if (o.chat_status === 'online') { ;
__p += ' fa-circle ';
} ;
__p += '\n ';
if (o.chat_status === 'dnd') { ;
__p += ' fa-minus-circle ';
} ;
__p += '\n ';
if (o.chat_status === 'away') { ;
__p += ' fa-dot-circle-o ';
} ;
__p += '\n ';
if (o.chat_status === 'xa') { ;
__p += ' fa-circle-o ';
} ;
__p += '\n ';
if (o.chat_status === 'offline') { ;
__p += ' fa-times-circle ';
} ;
__p += '"></span> ' +
__e(o.status_message) +
'</span>\n <a class="chatbox-btn change-status fa fa-pencil" title="' +
__e(o.title_change_status) +
'" data-toggle="modal" data-target="#changeStatusModal"></a>\n</div>\n</div>\n';
return __p
};});
2018-03-25 21:21:43 +02:00
define('tpl!status_option', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<li>\n <a href="#" class="' +
__e( o.value ) +
'" data-value="' +
__e( o.value ) +
'">\n <span class="icon-' +
__e( o.value ) +
'"></span>\n ' +
__e( o.text ) +
'\n </a>\n</li>\n';
return __p
};});
2018-05-08 19:58:12 +02:00
define('tpl!vcard', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<vCard xmlns="vcard-temp">\n <FN>' +
__e(o.fn) +
'</FN>\n <NICKNAME>' +
__e(o.fn) +
'</NICKNAME>\n <URL>' +
__e(o.url) +
'</URL>\n <ROLE>' +
__e(o.role) +
'</ROLE>\n <EMAIL><INTERNET/><PREF/><USERID>' +
__e(o.email) +
'</USERID></EMAIL>\n <PHOTO>\n <TYPE>' +
__e(o.image_type) +
'</TYPE>\n <BINVAL>' +
__e(o.image) +
'</BINVAL>\n </PHOTO>\n</vCard>\n';
return __p
};});
2018-05-15 10:32:13 +02:00
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
2018-05-07 18:20:15 +02:00
// Converse.js
// http://conversejs.org
//
2018-05-07 18:20:15 +02:00
// Copyright (c) 2012-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2)
(function (root, factory) {
2018-05-08 19:58:12 +02:00
define('converse-vcard',["converse-core", "crypto", "tpl!vcard"], factory);
})(this, function (converse, CryptoJS, tpl_vcard) {
"use strict";
2018-03-25 21:21:43 +02:00
var _converse$env = converse.env,
2018-05-07 18:20:15 +02:00
Backbone = _converse$env.Backbone,
Promise = _converse$env.Promise,
Strophe = _converse$env.Strophe,
2018-05-07 18:20:15 +02:00
SHA1 = _converse$env.SHA1,
_ = _converse$env._,
2018-05-08 19:58:12 +02:00
$iq = _converse$env.$iq,
$build = _converse$env.$build,
2018-05-07 18:20:15 +02:00
b64_sha1 = _converse$env.b64_sha1,
moment = _converse$env.moment,
sizzle = _converse$env.sizzle;
var u = converse.env.utils;
converse.plugins.add('converse-vcard', {
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
var _converse = this._converse;
2018-05-15 10:32:13 +02:00
_converse.VCard = Backbone.Model.extend({
defaults: {
'image': _converse.DEFAULT_IMAGE,
'image_type': _converse.DEFAULT_IMAGE_TYPE
},
set: function set(key, val, options) {
// Override Backbone.Model.prototype.set to make sure that the
// default `image` and `image_type` values are maintained.
var attrs;
if (_typeof(key) === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
if (_.has(attrs, 'image') && !attrs['image']) {
attrs['image'] = _converse.DEFAULT_IMAGE;
attrs['image_type'] = _converse.DEFAULT_IMAGE_TYPE;
return Backbone.Model.prototype.set.call(this, attrs, options);
} else {
return Backbone.Model.prototype.set.apply(this, arguments);
}
}
});
2018-05-07 18:20:15 +02:00
_converse.VCards = Backbone.Collection.extend({
2018-05-15 10:32:13 +02:00
model: _converse.VCard,
2018-05-07 18:20:15 +02:00
initialize: function initialize() {
this.on('add', function (vcard) {
return _converse.api.vcard.update(vcard);
});
}
});
2018-05-08 19:58:12 +02:00
function onVCardData(_converse, jid, iq, callback) {
var vcard = iq.querySelector('vCard');
var result = {};
if (!_.isNull(vcard)) {
result = {
'stanza': iq,
'fullname': _.get(vcard.querySelector('FN'), 'textContent'),
'nickname': _.get(vcard.querySelector('NICKNAME'), 'textContent'),
'image': _.get(vcard.querySelector('PHOTO BINVAL'), 'textContent'),
'image_type': _.get(vcard.querySelector('PHOTO TYPE'), 'textContent'),
'url': _.get(vcard.querySelector('URL'), 'textContent'),
'role': _.get(vcard.querySelector('ROLE'), 'textContent'),
'email': _.get(vcard.querySelector('EMAIL USERID'), 'textContent')
};
}
if (result.image) {
var word_array_from_b64 = CryptoJS.enc.Base64.parse(result['image']);
2018-05-23 04:27:33 +02:00
result['image_hash'] = CryptoJS.SHA1(word_array_from_b64).toString();
2018-05-08 19:58:12 +02:00
}
if (callback) {
callback(result);
}
}
function onVCardError(_converse, jid, iq, errback) {
if (errback) {
errback({
'stanza': iq,
'jid': jid
});
}
}
function createStanza(type, jid, vcard_el) {
var iq = $iq(jid ? {
'type': type,
'to': jid
} : {
'type': type
});
if (!vcard_el) {
iq.c("vCard", {
'xmlns': Strophe.NS.VCARD
});
} else {
iq.cnode(vcard_el);
}
return iq;
}
function setVCard(data) {
return new Promise(function (resolve, reject) {
var vcard_el = Strophe.xmlHtmlNode(tpl_vcard(data)).firstElementChild;
_converse.connection.sendIQ(createStanza("set", data.jid, vcard_el), resolve, reject);
});
}
function getVCard(_converse, jid) {
/* Request the VCard of another user. Returns a promise.
*
* Parameters:
* (String) jid - The Jabber ID of the user whose VCard
* is being requested.
*/
jid = Strophe.getBareJidFromJid(jid) === _converse.bare_jid ? null : jid;
return new Promise(function (resolve, reject) {
_converse.connection.sendIQ(createStanza("get", jid), _.partial(onVCardData, _converse, jid, _, resolve), _.partial(onVCardError, _converse, jid, _, resolve), 5000);
});
}
2018-05-07 18:20:15 +02:00
/* Event handlers */
2018-05-08 19:58:12 +02:00
2018-05-07 18:20:15 +02:00
_converse.initVCardCollection = function () {
_converse.vcards = new _converse.VCards();
2018-05-23 04:27:33 +02:00
_converse.vcards.browserStorage = new Backbone.BrowserStorage[_converse.storage](b64_sha1("converse.vcards"));
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
_converse.vcards.fetch();
};
2018-02-14 16:53:07 +01:00
2018-05-07 18:20:15 +02:00
_converse.api.listen.on('connectionInitialized', _converse.initVCardCollection);
_converse.on('addClientFeatures', function () {
2018-05-15 10:32:13 +02:00
_converse.api.disco.own.features.add(Strophe.NS.VCARD);
});
2018-05-07 18:20:15 +02:00
_.extend(_converse.api, {
'vcard': {
2018-05-08 19:58:12 +02:00
'set': setVCard,
2018-05-07 18:20:15 +02:00
'get': function get(model, force) {
if (_.isString(model)) {
return getVCard(_converse, model);
} else if (!model.get('vcard_updated') || force) {
var jid = model.get('jid') || model.get('muc_jid');
if (!jid) {
throw new Error("No JID to get vcard for!");
}
2018-05-07 18:20:15 +02:00
return getVCard(_converse, jid);
} else {
return Promise.resolve({});
}
2018-05-07 18:20:15 +02:00
},
'update': function update(model, force) {
var _this = this;
2018-05-07 18:20:15 +02:00
return new Promise(function (resolve, reject) {
_this.get(model, force).then(function (vcard) {
2018-05-08 19:58:12 +02:00
model.save(_.extend(_.pick(vcard, ['fullname', 'nickname', 'email', 'url', 'role', 'image_type', 'image', 'image_hash']), {
2018-05-07 18:20:15 +02:00
'vcard_updated': moment().format()
}));
resolve();
});
});
}
2018-03-25 21:21:43 +02:00
}
});
}
});
});
//# sourceMappingURL=converse-vcard.js.map;
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define */
(function (root, factory) {
2018-05-08 19:58:12 +02:00
define('converse-profile',["converse-core", "bootstrap", "tpl!alert", "tpl!chat_status_modal", "tpl!profile_modal", "tpl!profile_view", "tpl!status_option", "converse-vcard", "converse-modal"], factory);
})(this, function (converse, bootstrap, tpl_alert, tpl_chat_status_modal, tpl_profile_modal, tpl_profile_view, tpl_status_option) {
"use strict";
var _converse$env = converse.env,
Strophe = _converse$env.Strophe,
Backbone = _converse$env.Backbone,
Promise = _converse$env.Promise,
utils = _converse$env.utils,
_ = _converse$env._,
moment = _converse$env.moment;
var u = converse.env.utils;
converse.plugins.add('converse-profile', {
2018-05-08 19:58:12 +02:00
dependencies: ["converse-modal", "converse-vcard"],
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
var _converse = this._converse,
__ = _converse.__;
_converse.ProfileModal = _converse.BootstrapModal.extend({
2018-05-08 19:58:12 +02:00
events: {
'click .change-avatar': "openFileSelection",
'change input[type="file"': "updateFilePreview",
'submit form': 'onFormSubmitted'
},
initialize: function initialize() {
_converse.BootstrapModal.prototype.initialize.apply(this, arguments);
this.model.on('change', this.render, this);
},
2018-03-25 21:21:43 +02:00
toHTML: function toHTML() {
2018-05-08 19:58:12 +02:00
return tpl_profile_modal(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), {
'heading_profile': __('Your Profile'),
2018-05-08 19:58:12 +02:00
'label_close': __('Close'),
'label_email': __('Email'),
'label_fullname': __('Full Name'),
'label_nickname': __('Nickname'),
'label_jid': __('XMPP Address (JID)'),
'label_role': __('Role'),
'label_role_help': __('Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.'),
'label_save': __('Save'),
'label_url': __('URL'),
'alt_avatar': __('Your avatar image')
}));
2018-05-08 19:58:12 +02:00
},
openFileSelection: function openFileSelection(ev) {
ev.preventDefault();
this.el.querySelector('input[type="file"]').click();
},
updateFilePreview: function updateFilePreview(ev) {
var _this = this;
var file = ev.target.files[0],
reader = new FileReader();
reader.onloadend = function () {
_this.el.querySelector('.avatar').setAttribute('src', reader.result);
};
reader.readAsDataURL(file);
},
setVCard: function setVCard(body, data) {
var _this2 = this;
_converse.api.vcard.set(data).then(function () {
2018-05-09 14:37:27 +02:00
return _converse.api.vcard.update(_this2.model.vcard, true);
2018-05-08 19:58:12 +02:00
}).catch(function (err) {
_converse.log(err, Strophe.LogLevel.FATAL);
2018-05-09 14:37:27 +02:00
_converse.api.alert.show(Strophe.LogLevel.ERROR, __('Error'), [__("Sorry, an error happened while trying to save your profile data."), __("You can check your browser's developer console for any error output.")]);
2018-05-08 19:58:12 +02:00
});
2018-05-09 14:37:27 +02:00
this.modal.hide();
2018-05-08 19:58:12 +02:00
},
onFormSubmitted: function onFormSubmitted(ev) {
var _this3 = this;
ev.preventDefault();
var reader = new FileReader(),
form_data = new FormData(ev.target),
body = this.el.querySelector('.modal-body'),
image_file = form_data.get('image');
var data = {
'fn': form_data.get('fn'),
'role': form_data.get('role'),
'email': form_data.get('email'),
'url': form_data.get('url')
};
if (!image_file.size) {
_.extend(data, {
2018-05-15 10:32:13 +02:00
'image': this.model.vcard.get('image'),
'image_type': this.model.vcard.get('image_type')
2018-05-08 19:58:12 +02:00
});
this.setVCard(body, data);
} else {
reader.onloadend = function () {
_.extend(data, {
'image': btoa(reader.result),
'image_type': image_file.type
});
_this3.setVCard(body, data);
};
reader.readAsBinaryString(image_file);
}
2018-03-25 21:21:43 +02:00
}
});
_converse.ChatStatusModal = _converse.BootstrapModal.extend({
2018-03-25 21:21:43 +02:00
events: {
"submit form#set-xmpp-status": "onFormSubmitted",
"click .clear-input": "clearStatusMessage"
2018-03-25 21:21:43 +02:00
},
toHTML: function toHTML() {
2018-05-08 19:58:12 +02:00
return tpl_chat_status_modal(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), {
'label_away': __('Away'),
'label_close': __('Close'),
'label_busy': __('Busy'),
'label_cancel': __('Cancel'),
'label_custom_status': __('Custom status'),
'label_offline': __('Offline'),
'label_online': __('Online'),
'label_save': __('Save'),
'label_xa': __('Away for long'),
'modal_title': __('Change chat status'),
'placeholder_status_message': __('Personal status message')
}));
2018-03-25 21:21:43 +02:00
},
afterRender: function afterRender() {
2018-05-08 19:58:12 +02:00
var _this4 = this;
2017-03-05 10:45:18 +01:00
this.el.addEventListener('shown.bs.modal', function () {
2018-05-08 19:58:12 +02:00
_this4.el.querySelector('input[name="status_message"]').focus();
}, false);
2018-03-25 21:21:43 +02:00
},
clearStatusMessage: function clearStatusMessage(ev) {
2018-03-25 21:21:43 +02:00
if (ev && ev.preventDefault) {
ev.preventDefault();
u.hideElement(this.el.querySelector('.clear-input'));
2018-03-25 21:21:43 +02:00
}
2017-03-05 10:45:18 +01:00
var roster_filter = this.el.querySelector('input[name="status_message"]');
roster_filter.value = '';
},
onFormSubmitted: function onFormSubmitted(ev) {
ev.preventDefault();
var data = new FormData(ev.target);
this.model.save({
'status_message': data.get('status_message'),
'status': data.get('chat_status')
});
this.modal.hide();
}
2018-03-25 21:21:43 +02:00
});
_converse.XMPPStatusView = Backbone.VDOMView.extend({
tagName: "div",
events: {
"click a.show-profile": "showProfileModal",
"click a.change-status": "showStatusChangeModal",
"click .logout": "logOut"
},
initialize: function initialize() {
this.model.on("change", this.render, this);
2018-05-08 19:58:12 +02:00
this.model.vcard.on("change", this.render, this);
},
toHTML: function toHTML() {
var chat_status = this.model.get('status') || 'offline';
2018-05-08 19:58:12 +02:00
return tpl_profile_view(_.extend(this.model.toJSON(), this.model.vcard.toJSON(), {
'fullname': this.model.vcard.get('fullname') || _converse.bare_jid,
'status_message': this.model.get('status_message') || __("I am %1$s", this.getPrettyStatus(chat_status)),
'chat_status': chat_status,
'_converse': _converse,
'title_change_settings': __('Change settings'),
'title_change_status': __('Click to change your chat status'),
'title_log_out': __('Log out'),
'title_your_profile': __('Your profile')
}));
},
showProfileModal: function showProfileModal(ev) {
if (_.isUndefined(this.profile_modal)) {
this.profile_modal = new _converse.ProfileModal({
model: this.model
2018-03-25 21:21:43 +02:00
});
}
this.profile_modal.show(ev);
},
showStatusChangeModal: function showStatusChangeModal(ev) {
if (_.isUndefined(this.status_modal)) {
this.status_modal = new _converse.ChatStatusModal({
model: this.model
2018-03-25 21:21:43 +02:00
});
}
2016-03-07 18:54:07 +01:00
this.status_modal.show(ev);
},
logOut: function logOut(ev) {
ev.preventDefault();
var result = confirm(__("Are you sure you want to log out?"));
2018-03-25 21:21:43 +02:00
if (result === true) {
_converse.logOut();
2018-03-25 21:21:43 +02:00
}
},
getPrettyStatus: function getPrettyStatus(stat) {
if (stat === 'chat') {
return __('online');
} else if (stat === 'dnd') {
return __('busy');
} else if (stat === 'xa') {
return __('away for long');
} else if (stat === 'away') {
return __('away');
} else if (stat === 'offline') {
return __('offline');
} else {
return __(stat) || __('online');
}
}
2018-03-25 21:21:43 +02:00
});
}
});
});
//# sourceMappingURL=converse-profile.js.map;
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define */
(function (root, factory) {
2018-05-23 04:27:33 +02:00
define('converse-controlbox',["converse-core", "bootstrap", "lodash.fp", "tpl!converse_brand_heading", "tpl!controlbox", "tpl!controlbox_toggle", "tpl!login_panel", "converse-chatview", "converse-rosterview", "converse-profile"], factory);
})(this, function (converse, bootstrap, fp, tpl_brand_heading, tpl_controlbox, tpl_controlbox_toggle, tpl_login_panel) {
"use strict";
var CHATBOX_TYPE = 'chatbox';
var _converse$env = converse.env,
Strophe = _converse$env.Strophe,
Backbone = _converse$env.Backbone,
Promise = _converse$env.Promise,
_ = _converse$env._,
moment = _converse$env.moment;
2018-01-17 19:45:33 +01:00
var u = converse.env.utils;
var CONNECTION_STATUS_CSS_CLASS = {
'Error': 'error',
'Connecting': 'info',
'Connection failure': 'error',
'Authenticating': 'info',
'Authentication failure': 'error',
'Connected': 'info',
'Disconnected': 'error',
'Disconnecting': 'warn',
'Attached': 'info',
'Redirect': 'info',
'Reconnecting': 'warn'
};
var PRETTY_CONNECTION_STATUS = {
0: 'Error',
1: 'Connecting',
2: 'Connection failure',
3: 'Authenticating',
4: 'Authentication failure',
5: 'Connected',
6: 'Disconnected',
7: 'Disconnecting',
8: 'Attached',
9: 'Redirect',
10: 'Reconnecting'
};
var REPORTABLE_STATUSES = [0, // ERROR'
1, // CONNECTING
2, // CONNFAIL
3, // AUTHENTICATING
4, // AUTHFAIL
7, // DISCONNECTING
10 // RECONNECTING
];
converse.plugins.add('converse-controlbox', {
/* Plugin dependencies are other plugins which might be
2018-01-17 19:45:33 +01:00
* overridden or relied upon, and therefore need to be loaded before
* this plugin.
2018-01-17 19:45:33 +01:00
*
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found. By default it's
* false, which means these plugins are only loaded opportunistically.
2018-01-17 19:45:33 +01:00
*
* NB: These plugins need to have already been loaded via require.js.
*/
dependencies: ["converse-modal", "converse-chatboxes", "converse-rosterview", "converse-chatview"],
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
_tearDown: function _tearDown() {
this.__super__._tearDown.apply(this, arguments);
if (this.rosterview) {
// Removes roster groups
this.rosterview.model.off().reset();
this.rosterview.each(function (groupview) {
groupview.removeAll();
groupview.remove();
});
this.rosterview.removeAll().remove();
}
},
ChatBoxes: {
chatBoxMayBeShown: function chatBoxMayBeShown(chatbox) {
return this.__super__.chatBoxMayBeShown.apply(this, arguments) && chatbox.get('id') !== 'controlbox';
}
},
ChatBoxViews: {
onChatBoxAdded: function onChatBoxAdded(item) {
var _converse = this.__super__._converse;
2018-03-25 21:21:43 +02:00
if (item.get('box_id') === 'controlbox') {
var view = this.get(item.get('id'));
2018-03-25 21:21:43 +02:00
if (view) {
view.model = item;
view.initialize();
return view;
} else {
view = new _converse.ControlBoxView({
model: item
});
return this.add(item.get('id'), view);
}
} else {
return this.__super__.onChatBoxAdded.apply(this, arguments);
2018-03-25 21:21:43 +02:00
}
},
closeAllChatBoxes: function closeAllChatBoxes() {
var _converse = this.__super__._converse;
this.each(function (view) {
if (view.model.get('id') === 'controlbox' && (_converse.disconnection_cause !== _converse.LOGOUT || _converse.show_controlbox_by_default)) {
return;
2018-03-25 21:21:43 +02:00
}
view.close();
});
return this;
},
getChatBoxWidth: function getChatBoxWidth(view) {
var _converse = this.__super__._converse;
var controlbox = this.get('controlbox');
if (view.model.get('id') === 'controlbox') {
/* We return the width of the controlbox or its toggle,
* depending on which is visible.
*/
if (!controlbox || !u.isVisible(controlbox.el)) {
return u.getOuterWidth(_converse.controlboxtoggle.el, true);
} else {
return u.getOuterWidth(controlbox.el, true);
}
} else {
return this.__super__.getChatBoxWidth.apply(this, arguments);
2018-03-25 21:21:43 +02:00
}
}
},
ChatBox: {
initialize: function initialize() {
if (this.get('id') === 'controlbox') {
this.set({
'time_opened': moment(0).valueOf()
});
} else {
this.__super__.initialize.apply(this, arguments);
}
}
},
ChatBoxView: {
insertIntoDOM: function insertIntoDOM() {
var view = this.__super__._converse.chatboxviews.get("controlbox");
if (view) {
view.el.insertAdjacentElement('afterend', this.el);
2018-03-25 21:21:43 +02:00
} else {
this.__super__.insertIntoDOM.apply(this, arguments);
2018-03-25 21:21:43 +02:00
}
return this;
2018-03-25 21:21:43 +02:00
}
}
},
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
var _converse = this._converse,
__ = _converse.__;
_converse.api.settings.update({
allow_logout: true,
default_domain: undefined,
locked_domain: undefined,
show_controlbox_by_default: false,
sticky_controlbox: false
2018-03-25 21:21:43 +02:00
});
_converse.api.promises.add('controlboxInitialized');
var LABEL_CONTACTS = __('Contacts');
_converse.addControlBox = function () {
return _converse.chatboxes.add({
id: 'controlbox',
box_id: 'controlbox',
type: 'controlbox',
closed: !_converse.show_controlbox_by_default
});
};
_converse.ControlBoxView = _converse.ChatBoxView.extend({
tagName: 'div',
className: 'chatbox',
id: 'controlbox',
events: {
'click a.close-chatbox-button': 'close'
2018-02-14 16:53:07 +01:00
},
2018-03-25 21:21:43 +02:00
initialize: function initialize() {
if (_.isUndefined(_converse.controlboxtoggle)) {
_converse.controlboxtoggle = new _converse.ControlBoxToggle();
}
_converse.controlboxtoggle.el.insertAdjacentElement('afterend', this.el);
this.model.on('change:connected', this.onConnected, this);
this.model.on('destroy', this.hide, this);
this.model.on('hide', this.hide, this);
this.model.on('show', this.show, this);
this.model.on('change:closed', this.ensureClosedState, this);
2018-03-25 21:21:43 +02:00
this.render();
if (this.model.get('connected')) {
this.insertRoster();
2018-02-14 16:53:07 +01:00
}
2018-03-25 21:21:43 +02:00
_converse.emit('controlboxInitialized', this);
},
render: function render() {
if (this.model.get('connected')) {
if (_.isUndefined(this.model.get('closed'))) {
this.model.set('closed', !_converse.show_controlbox_by_default);
}
}
this.el.innerHTML = tpl_controlbox(_.extend(this.model.toJSON()));
2018-03-25 21:21:43 +02:00
if (!this.model.get('closed')) {
this.show();
} else {
this.hide();
}
if (!_converse.connection.connected || !_converse.connection.authenticated || _converse.connection.disconnecting) {
this.renderLoginPanel();
} else if (this.model.get('connected') && (!this.controlbox_pane || !u.isVisible(this.controlbox_pane.el))) {
this.renderControlBoxPane();
}
return this;
2018-03-25 21:21:43 +02:00
},
onConnected: function onConnected() {
if (this.model.get('connected')) {
this.render();
this.insertRoster();
}
2018-03-25 21:21:43 +02:00
},
insertRoster: function insertRoster() {
var _this = this;
/* Place the rosterview inside the "Contacts" panel. */
_converse.api.waitUntil('rosterViewInitialized').then(function () {
return _this.controlbox_pane.el.insertAdjacentElement('beforeEnd', _converse.rosterview.el);
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
2018-03-25 21:21:43 +02:00
},
createBrandHeadingHTML: function createBrandHeadingHTML() {
return tpl_brand_heading({
'sticky_controlbox': _converse.sticky_controlbox
});
},
insertBrandHeading: function insertBrandHeading() {
var heading_el = this.el.querySelector('.brand-heading-container');
if (_.isNull(heading_el)) {
var el = this.el.querySelector('.controlbox-head');
el.insertAdjacentHTML('beforeend', this.createBrandHeadingHTML());
} else {
heading_el.outerHTML = this.createBrandHeadingHTML();
2018-03-25 21:21:43 +02:00
}
},
renderLoginPanel: function renderLoginPanel() {
this.el.classList.add("logged-out");
if (_.isNil(this.loginpanel)) {
this.loginpanel = new _converse.LoginPanel({
'model': new _converse.LoginPanelModel()
});
var panes = this.el.querySelector('.controlbox-panes');
panes.innerHTML = '';
panes.appendChild(this.loginpanel.render().el);
this.insertBrandHeading();
} else {
this.loginpanel.render();
}
return this;
},
renderControlBoxPane: function renderControlBoxPane() {
/* Renders the "Contacts" panel of the controlbox.
*
* This will only be called after the user has already been
* logged in.
*/
if (this.loginpanel) {
this.loginpanel.remove();
delete this.loginpanel;
}
this.el.classList.remove("logged-out");
this.controlbox_pane = new _converse.ControlBoxPane();
this.el.querySelector('.controlbox-panes').insertAdjacentElement('afterBegin', this.controlbox_pane.el);
},
close: function close(ev) {
2018-03-25 21:21:43 +02:00
if (ev && ev.preventDefault) {
ev.preventDefault();
2018-02-14 16:53:07 +01:00
}
if (_converse.sticky_controlbox) {
return;
}
if (_converse.connection.connected && !_converse.connection.disconnecting) {
this.model.save({
'closed': true
});
} else {
this.model.trigger('hide');
}
_converse.emit('controlBoxClosed', this);
return this;
},
ensureClosedState: function ensureClosedState() {
if (this.model.get('closed')) {
this.hide();
} else {
this.show();
}
},
hide: function hide(callback) {
if (_converse.sticky_controlbox) {
return;
}
u.addClass('hidden', this.el);
_converse.emit('chatBoxClosed', this);
if (!_converse.connection.connected) {
_converse.controlboxtoggle.render();
}
_converse.controlboxtoggle.show(callback);
return this;
},
onControlBoxToggleHidden: function onControlBoxToggleHidden() {
this.model.set('closed', false);
this.el.classList.remove('hidden');
_converse.emit('controlBoxOpened', this);
},
show: function show() {
_converse.controlboxtoggle.hide(this.onControlBoxToggleHidden.bind(this));
return this;
},
showHelpMessages: function showHelpMessages() {
/* Override showHelpMessages in ChatBoxView, for now do nothing.
*
* Parameters:
* (Array) msgs: Array of messages
*/
return;
}
});
_converse.LoginPanelModel = Backbone.Model.extend({
defaults: {
// Passed-by-reference. Fine in this case because there's
// only one such model.
'errors': []
}
});
_converse.LoginPanel = Backbone.VDOMView.extend({
tagName: 'div',
id: "converse-login-panel",
className: 'controlbox-pane fade-in',
events: {
'submit form#converse-login': 'authenticate',
'change input': 'validate'
},
initialize: function initialize(cfg) {
this.model.on('change', this.render, this);
this.listenTo(_converse.connfeedback, 'change', this.render);
2018-05-23 04:27:33 +02:00
this.render();
_.forEach(this.el.querySelectorAll('[data-title]'), function (el) {
var popover = new bootstrap.Popover(el, {
'trigger': _converse.view_mode === 'mobile' && 'click' || 'hover',
'dismissible': _converse.view_mode === 'mobile' && true || false,
'container': _converse.chatboxviews.el
});
});
},
toHTML: function toHTML() {
var connection_status = _converse.connfeedback.get('connection_status');
var feedback_class, pretty_status;
if (_.includes(REPORTABLE_STATUSES, connection_status)) {
pretty_status = PRETTY_CONNECTION_STATUS[connection_status];
feedback_class = CONNECTION_STATUS_CSS_CLASS[pretty_status];
}
return tpl_login_panel(_.extend(this.model.toJSON(), {
'__': __,
'_converse': _converse,
'ANONYMOUS': _converse.ANONYMOUS,
'EXTERNAL': _converse.EXTERNAL,
'LOGIN': _converse.LOGIN,
'PREBIND': _converse.PREBIND,
'auto_login': _converse.auto_login,
'authentication': _converse.authentication,
'connection_status': connection_status,
'conn_feedback_class': feedback_class,
'conn_feedback_subject': pretty_status,
'conn_feedback_message': _converse.connfeedback.get('message'),
'placeholder_username': (_converse.locked_domain || _converse.default_domain) && __('Username') || __('user@domain')
}));
},
validate: function validate() {
var form = this.el.querySelector('form');
var jid_element = form.querySelector('input[name=jid]');
if (jid_element.value && !_converse.locked_domain && !_converse.default_domain && !u.isValidJID(jid_element.value)) {
jid_element.setCustomValidity(__('Please enter a valid XMPP address'));
return false;
}
jid_element.setCustomValidity('');
return true;
},
authenticate: function authenticate(ev) {
/* Authenticate the user based on a form submission event.
*/
if (ev && ev.preventDefault) {
ev.preventDefault();
}
if (_converse.authentication === _converse.ANONYMOUS) {
this.connect(_converse.jid, null);
return;
}
if (!this.validate()) {
return;
}
2017-07-22 22:21:05 +02:00
2018-05-23 04:27:33 +02:00
var form_data = new FormData(ev.target);
_converse.trusted = form_data.get('trusted');
_converse.storage = form_data.get('trusted') ? 'local' : 'session';
var jid = form_data.get('jid');
2017-07-22 22:21:05 +02:00
if (_converse.locked_domain) {
jid = Strophe.escapeNode(jid) + '@' + _converse.locked_domain;
} else if (_converse.default_domain && !_.includes(jid, '@')) {
jid = jid + '@' + _converse.default_domain;
}
2018-03-25 21:21:43 +02:00
2018-05-23 04:27:33 +02:00
this.connect(jid, form_data.get('password'));
},
connect: function connect(jid, password) {
if (jid) {
var resource = Strophe.getResourceFromJid(jid);
if (!resource) {
jid = jid.toLowerCase() + _converse.generateResource();
} else {
jid = Strophe.getBareJidFromJid(jid).toLowerCase() + '/' + resource;
}
}
if (_.includes(["converse/login", "converse/register"], Backbone.history.getFragment())) {
_converse.router.navigate('', {
'replace': true
});
}
_converse.connection.reset();
2018-01-04 17:58:16 +01:00
_converse.connection.connect(jid, password, _converse.onConnectStatusChanged);
}
});
_converse.ControlBoxPane = Backbone.NativeView.extend({
tagName: 'div',
className: 'controlbox-pane',
initialize: function initialize() {
_converse.xmppstatusview = new _converse.XMPPStatusView({
'model': _converse.xmppstatus
});
this.el.insertAdjacentElement('afterBegin', _converse.xmppstatusview.render().el);
}
});
_converse.ControlBoxToggle = Backbone.NativeView.extend({
tagName: 'a',
className: 'toggle-controlbox hidden',
id: 'toggle-controlbox',
events: {
'click': 'onClick'
},
attributes: {
'href': "#"
},
initialize: function initialize() {
_converse.chatboxviews.insertRowColumn(this.render().el);
_converse.api.waitUntil('initialized').then(this.render.bind(this)).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
},
render: function render() {
// We let the render method of ControlBoxView decide whether
// the ControlBox or the Toggle must be shown. This prevents
// artifacts (i.e. on page load the toggle is shown only to then
// seconds later be hidden in favor of the control box).
this.el.innerHTML = tpl_controlbox_toggle({
'label_toggle': _converse.connection.connected ? __('Chat Contacts') : __('Toggle chat')
});
return this;
},
hide: function hide(callback) {
u.hideElement(this.el);
callback();
},
show: function show(callback) {
u.fadeIn(this.el, callback);
},
showControlBox: function showControlBox() {
var controlbox = _converse.chatboxes.get('controlbox');
if (!controlbox) {
controlbox = _converse.addControlBox();
}
2017-07-22 22:21:05 +02:00
if (_converse.connection.connected) {
controlbox.save({
closed: false
});
} else {
controlbox.trigger('show');
}
},
onClick: function onClick(e) {
e.preventDefault();
if (u.isVisible(_converse.root.querySelector("#controlbox"))) {
var controlbox = _converse.chatboxes.get('controlbox');
2017-07-22 22:21:05 +02:00
if (_converse.connection.connected) {
controlbox.save({
closed: true
});
} else {
controlbox.trigger('hide');
}
} else {
this.showControlBox();
}
}
});
2018-05-23 04:27:33 +02:00
_converse.on('clearSession', function () {
if (_converse.trusted) {
var chatboxes = _.get(_converse, 'chatboxes', null);
if (!_.isNil(chatboxes)) {
var controlbox = chatboxes.get('controlbox');
if (controlbox && controlbox.collection && controlbox.collection.browserStorage) {
controlbox.save({
'connected': false
});
}
}
}
});
Promise.all([_converse.api.waitUntil('connectionInitialized'), _converse.api.waitUntil('chatBoxesInitialized')]).then(_converse.addControlBox).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
2017-07-22 22:21:05 +02:00
_converse.on('chatBoxesFetched', function () {
var controlbox = _converse.chatboxes.get('controlbox') || _converse.addControlBox();
2017-07-22 22:21:05 +02:00
controlbox.save({
connected: true
});
});
2018-01-17 19:45:33 +01:00
var disconnect = function disconnect() {
/* Upon disconnection, set connected to `false`, so that if
* we reconnect, "onConnected" will be called,
* to fetch the roster again and to send out a presence stanza.
*/
var view = _converse.chatboxviews.get('controlbox');
2018-03-25 21:21:43 +02:00
view.model.set({
'connected': false
});
view.renderLoginPanel();
};
_converse.on('disconnected', disconnect);
2017-07-22 22:21:05 +02:00
_converse.on('will-reconnect', disconnect);
}
});
});
//# sourceMappingURL=converse-controlbox.js.map;
2017-07-22 22:21:05 +02:00
define('tpl!dragresize', ['lodash'], function(_) {return function(o) {
var __t, __p = '';
__p += '<div class="dragresize dragresize-top"></div>\n<div class="dragresize dragresize-topleft"></div>\n<div class="dragresize dragresize-left"></div>\n';
return __p
};});
2016-03-16 12:49:35 +01:00
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
2017-06-23 20:25:33 +02:00
/*global define, window, document */
(function (root, factory) {
define('converse-dragresize',["converse-core", "tpl!dragresize", "converse-chatview", "converse-controlbox"], factory);
})(this, function (converse, tpl_dragresize) {
"use strict";
2018-03-05 14:43:53 +01:00
var _ = converse.env._;
2018-03-25 21:21:43 +02:00
function renderDragResizeHandles(_converse, view) {
var flyout = view.el.querySelector('.box-flyout');
var div = document.createElement('div');
div.innerHTML = tpl_dragresize();
flyout.insertBefore(div, flyout.firstChild);
}
2016-03-16 12:49:35 +01:00
converse.plugins.add('converse-dragresize', {
/* Plugin dependencies are other plugins which might be
* overridden or relied upon, and therefore need to be loaded before
* this plugin.
*
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found. By default it's
* false, which means these plugins are only loaded opportunistically.
*
* NB: These plugins need to have already been loaded via require.js.
*/
dependencies: ["converse-chatview", "converse-headline", "converse-muc-views"],
enabled: function enabled(_converse) {
return _converse.view_mode == 'overlayed';
},
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
registerGlobalEventHandlers: function registerGlobalEventHandlers() {
var that = this;
document.addEventListener('mousemove', function (ev) {
if (!that.resizing || !that.allow_dragresize) {
return true;
2018-03-25 21:21:43 +02:00
}
2018-02-14 16:53:07 +01:00
ev.preventDefault();
that.resizing.chatbox.resizeChatBox(ev);
});
document.addEventListener('mouseup', function (ev) {
if (!that.resizing || !that.allow_dragresize) {
return true;
2018-03-05 14:43:53 +01:00
}
2017-07-22 22:21:05 +02:00
ev.preventDefault();
var height = that.applyDragResistance(that.resizing.chatbox.height, that.resizing.chatbox.model.get('default_height'));
var width = that.applyDragResistance(that.resizing.chatbox.width, that.resizing.chatbox.model.get('default_width'));
2018-02-14 16:53:07 +01:00
if (that.connection.connected) {
that.resizing.chatbox.model.save({
'height': height
});
that.resizing.chatbox.model.save({
'width': width
});
} else {
that.resizing.chatbox.model.set({
'height': height
});
that.resizing.chatbox.model.set({
'width': width
});
}
2016-03-16 12:49:35 +01:00
that.resizing = null;
});
return this.__super__.registerGlobalEventHandlers.apply(this, arguments);
},
ChatBox: {
initialize: function initialize() {
2018-03-25 21:21:43 +02:00
var _converse = this.__super__._converse;
var result = this.__super__.initialize.apply(this, arguments),
height = this.get('height'),
width = this.get('width'),
save = this.get('id') === 'controlbox' ? this.set.bind(this) : this.save.bind(this);
save({
'height': _converse.applyDragResistance(height, this.get('default_height')),
'width': _converse.applyDragResistance(width, this.get('default_width'))
});
return result;
2018-03-25 21:21:43 +02:00
}
},
ChatBoxView: {
events: {
'mousedown .dragresize-top': 'onStartVerticalResize',
'mousedown .dragresize-left': 'onStartHorizontalResize',
'mousedown .dragresize-topleft': 'onStartDiagonalResize'
},
2018-03-25 21:21:43 +02:00
initialize: function initialize() {
window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100));
2018-03-25 21:21:43 +02:00
this.__super__.initialize.apply(this, arguments);
},
render: function render() {
var result = this.__super__.render.apply(this, arguments);
2018-03-25 21:21:43 +02:00
renderDragResizeHandles(this.__super__._converse, this);
this.setWidth();
2018-03-25 21:21:43 +02:00
return result;
},
setWidth: function setWidth() {
// If a custom width is applied (due to drag-resizing),
// then we need to set the width of the .chatbox element as well.
if (this.model.get('width')) {
this.el.style.width = this.model.get('width');
2018-01-17 19:45:33 +01:00
}
},
_show: function _show() {
this.initDragResize().setDimensions();
2018-03-25 21:21:43 +02:00
this.__super__._show.apply(this, arguments);
2018-03-25 21:21:43 +02:00
},
initDragResize: function initDragResize() {
/* Determine and store the default box size.
* We need this information for the drag-resizing feature.
2018-03-25 21:21:43 +02:00
*/
var _converse = this.__super__._converse,
flyout = this.el.querySelector('.box-flyout'),
style = window.getComputedStyle(flyout);
2018-01-17 19:45:33 +01:00
if (_.isUndefined(this.model.get('height'))) {
var height = parseInt(style.height.replace(/px$/, ''), 10),
width = parseInt(style.width.replace(/px$/, ''), 10);
this.model.set('height', height);
this.model.set('default_height', height);
this.model.set('width', width);
this.model.set('default_width', width);
}
2018-03-25 21:21:43 +02:00
var min_width = style['min-width'];
var min_height = style['min-height'];
this.model.set('min_width', min_width.endsWith('px') ? Number(min_width.replace(/px$/, '')) : 0);
this.model.set('min_height', min_height.endsWith('px') ? Number(min_height.replace(/px$/, '')) : 0); // Initialize last known mouse position
2018-03-25 21:21:43 +02:00
this.prev_pageY = 0;
this.prev_pageX = 0;
2018-03-25 21:21:43 +02:00
if (_converse.connection.connected) {
this.height = this.model.get('height');
this.width = this.model.get('width');
}
2018-01-17 19:45:33 +01:00
return this;
},
setDimensions: function setDimensions() {
// Make sure the chat box has the right height and width.
this.adjustToViewport();
this.setChatBoxHeight(this.model.get('height'));
this.setChatBoxWidth(this.model.get('width'));
},
setChatBoxHeight: function setChatBoxHeight(height) {
var _converse = this.__super__._converse;
2018-03-25 21:21:43 +02:00
if (height) {
height = _converse.applyDragResistance(height, this.model.get('default_height')) + 'px';
} else {
height = "";
}
2018-03-25 21:21:43 +02:00
var flyout_el = this.el.querySelector('.box-flyout');
2018-03-25 21:21:43 +02:00
if (!_.isNull(flyout_el)) {
flyout_el.style.height = height;
}
},
setChatBoxWidth: function setChatBoxWidth(width) {
var _converse = this.__super__._converse;
2018-03-25 21:21:43 +02:00
if (width) {
width = _converse.applyDragResistance(width, this.model.get('default_width')) + 'px';
} else {
width = "";
}
2018-03-25 21:21:43 +02:00
this.el.style.width = width;
var flyout_el = this.el.querySelector('.box-flyout');
if (!_.isNull(flyout_el)) {
flyout_el.style.width = width;
2018-01-17 19:45:33 +01:00
}
},
adjustToViewport: function adjustToViewport() {
/* Event handler called when viewport gets resized. We remove
* custom width/height from chat boxes.
*/
var viewport_width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
var viewport_height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
if (viewport_width <= 480) {
this.model.set('height', undefined);
this.model.set('width', undefined);
} else if (viewport_width <= this.model.get('width')) {
this.model.set('width', undefined);
} else if (viewport_height <= this.model.get('height')) {
this.model.set('height', undefined);
}
},
onStartVerticalResize: function onStartVerticalResize(ev) {
var _converse = this.__super__._converse;
if (!_converse.allow_dragresize) {
return true;
} // Record element attributes for mouseMove().
2016-12-13 20:46:07 +01:00
2018-03-25 21:21:43 +02:00
var flyout = this.el.querySelector('.box-flyout'),
style = window.getComputedStyle(flyout);
this.height = parseInt(style.height.replace(/px$/, ''), 10);
_converse.resizing = {
'chatbox': this,
'direction': 'top'
};
this.prev_pageY = ev.pageY;
},
onStartHorizontalResize: function onStartHorizontalResize(ev) {
var _converse = this.__super__._converse;
2016-12-13 20:46:07 +01:00
if (!_converse.allow_dragresize) {
return true;
}
2016-12-13 20:46:07 +01:00
var flyout = this.el.querySelector('.box-flyout'),
style = window.getComputedStyle(flyout);
this.width = parseInt(style.width.replace(/px$/, ''), 10);
_converse.resizing = {
'chatbox': this,
'direction': 'left'
};
this.prev_pageX = ev.pageX;
},
onStartDiagonalResize: function onStartDiagonalResize(ev) {
var _converse = this.__super__._converse;
this.onStartHorizontalResize(ev);
this.onStartVerticalResize(ev);
_converse.resizing.direction = 'topleft';
},
resizeChatBox: function resizeChatBox(ev) {
var diff;
var _converse = this.__super__._converse;
2016-12-13 20:46:07 +01:00
if (_converse.resizing.direction.indexOf('top') === 0) {
diff = ev.pageY - this.prev_pageY;
if (diff) {
this.height = this.height - diff > (this.model.get('min_height') || 0) ? this.height - diff : this.model.get('min_height');
this.prev_pageY = ev.pageY;
this.setChatBoxHeight(this.height);
}
}
2016-12-13 20:46:07 +01:00
if (_.includes(_converse.resizing.direction, 'left')) {
diff = this.prev_pageX - ev.pageX;
if (diff) {
this.width = this.width + diff > (this.model.get('min_width') || 0) ? this.width + diff : this.model.get('min_width');
this.prev_pageX = ev.pageX;
this.setChatBoxWidth(this.width);
}
}
}
},
HeadlinesBoxView: {
events: {
'mousedown .dragresize-top': 'onStartVerticalResize',
'mousedown .dragresize-left': 'onStartHorizontalResize',
'mousedown .dragresize-topleft': 'onStartDiagonalResize'
},
initialize: function initialize() {
window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100));
return this.__super__.initialize.apply(this, arguments);
},
render: function render() {
var result = this.__super__.render.apply(this, arguments);
renderDragResizeHandles(this.__super__._converse, this);
this.setWidth();
return result;
}
},
ControlBoxView: {
events: {
'mousedown .dragresize-top': 'onStartVerticalResize',
'mousedown .dragresize-left': 'onStartHorizontalResize',
'mousedown .dragresize-topleft': 'onStartDiagonalResize'
},
initialize: function initialize() {
window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100));
this.__super__.initialize.apply(this, arguments);
},
render: function render() {
var result = this.__super__.render.apply(this, arguments);
renderDragResizeHandles(this.__super__._converse, this);
this.setWidth();
return result;
},
renderLoginPanel: function renderLoginPanel() {
var result = this.__super__.renderLoginPanel.apply(this, arguments);
this.initDragResize().setDimensions();
return result;
},
renderControlBoxPane: function renderControlBoxPane() {
var result = this.__super__.renderControlBoxPane.apply(this, arguments);
this.initDragResize().setDimensions();
return result;
}
},
ChatRoomView: {
events: {
'mousedown .dragresize-top': 'onStartVerticalResize',
'mousedown .dragresize-left': 'onStartHorizontalResize',
'mousedown .dragresize-topleft': 'onStartDiagonalResize'
},
initialize: function initialize() {
window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100));
this.__super__.initialize.apply(this, arguments);
},
render: function render() {
var result = this.__super__.render.apply(this, arguments);
renderDragResizeHandles(this.__super__._converse, this);
this.setWidth();
return result;
}
}
},
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
var _converse = this._converse;
_converse.api.settings.update({
allow_dragresize: true
});
_converse.applyDragResistance = function (value, default_value) {
/* This method applies some resistance around the
* default_value. If value is close enough to
* default_value, then default_value is returned instead.
*/
if (_.isUndefined(value)) {
return undefined;
} else if (_.isUndefined(default_value)) {
return value;
}
var resistance = 10;
if (value !== default_value && Math.abs(value - default_value) < resistance) {
return default_value;
}
return value;
};
}
});
});
//# sourceMappingURL=converse-dragresize.js.map;
2018-05-07 18:20:15 +02:00
// Converse.js
// http://conversejs.org
//
// Copyright (c) 2012-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2)
(function (root, factory) {
define('converse-embedded',["converse-core", "converse-muc"], factory);
})(this, function (converse) {
"use strict";
var _converse$env = converse.env,
Backbone = _converse$env.Backbone,
_ = _converse$env._;
converse.plugins.add('converse-embedded', {
enabled: function enabled(_converse) {
return _converse.view_mode === 'embedded';
},
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
this._converse.api.settings.update({
'allow_logout': false,
// No point in logging out when we have auto_login as true.
'allow_muc_invitations': false,
// Doesn't make sense to allow because only
// roster contacts can be invited
'hide_muc_server': true
});
var _converse = this._converse;
if (!_.isArray(_converse.auto_join_rooms) && !_.isArray(_converse.auto_join_private_chats)) {
throw new Error("converse-embedded: auto_join_rooms must be an Array");
}
if (_converse.auto_join_rooms.length !== 1 && _converse.auto_join_private_chats.length !== 1) {
throw new Error("converse-embedded: It doesn't make " + "sense to have the auto_join_rooms setting to zero or " + "more then one, since only one chat room can be open " + "at any time.");
}
}
});
});
//# sourceMappingURL=converse-embedded.js.map;
define('tpl!inverse_brand_heading', ['lodash'], function(_) {return function(o) {
2018-03-25 21:21:43 +02:00
var __t, __p = '';
2018-05-17 11:21:29 +02:00
__p += '<div class="row">\n <div class="container brand-heading-container">\n <h1 class="brand-heading"><i class="icon-conversejs"></i>Converse</h1>\n <p class="brand-subtitle"><a target="_blank" rel="nofollow" href="https://conversejs.org">Open Source</a> XMPP chat client brought to you by <a target="_blank" rel="nofollow" href="https://opkode.com">Opkode</a> </p>\n <p class="brand-subtitle"><a target="_blank" rel="nofollow" href="https://hosted.weblate.org/projects/conversejs/#languages">Translate</a> it into your own language</p>\n <div>\n</div>\n';
2018-03-25 21:21:43 +02:00
return __p
};});
2016-11-07 15:43:48 +01:00
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2017, JC Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
2016-03-16 12:49:35 +01:00
/*global Backbone, define, window, JSON */
2016-05-03 17:37:10 +02:00
/* converse-singleton
* ******************
*
* A plugin which ensures that only one chat (private or groupchat) is
* visible at any one time. All other ongoing chats are hidden and kept in the
* background.
*
* This plugin makes sense in mobile or fullscreen chat environments (as
* configured by the `view_mode` setting).
*
*/
(function (root, factory) {
define('converse-singleton',["converse-core", "converse-chatview"], factory);
})(this, function (converse) {
"use strict";
2016-12-13 20:46:07 +01:00
var _converse$env = converse.env,
_ = _converse$env._,
Strophe = _converse$env.Strophe;
2018-03-25 21:21:43 +02:00
function hideChat(view) {
if (view.model.get('id') === 'controlbox') {
return;
}
2018-03-25 21:21:43 +02:00
view.model.save({
'hidden': true
});
view.hide();
}
converse.plugins.add('converse-singleton', {
// It's possible however to make optional dependencies non-optional.
// If the setting "strict_plugin_dependencies" is set to true,
// an error will be raised if the plugin is not found.
//
// NB: These plugins need to have already been loaded via require.js.
dependencies: ['converse-chatboxes', 'converse-muc', 'converse-controlbox', 'converse-rosterview'],
enabled: function enabled(_converse) {
return _.includes(['mobile', 'fullscreen', 'embedded'], _converse.view_mode);
},
overrides: {
// overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// new functions which don't exist yet can also be added.
ChatBoxes: {
chatBoxMayBeShown: function chatBoxMayBeShown(chatbox) {
return !chatbox.get('hidden');
},
createChatBox: function createChatBox(jid, attrs) {
/* Make sure new chat boxes are hidden by default. */
attrs = attrs || {};
attrs.hidden = true;
return this.__super__.createChatBox.call(this, jid, attrs);
}
},
ChatBoxView: {
shouldShowOnTextMessage: function shouldShowOnTextMessage() {
return false;
},
_show: function _show(focus) {
/* We only have one chat visible at any one
* time. So before opening a chat, we make sure all other
* chats are hidden.
*/
_.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat);
this.model.set('hidden', false);
return this.__super__._show.apply(this, arguments);
}
},
ChatRoomView: {
show: function show(focus) {
_.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat);
this.model.set('hidden', false);
return this.__super__.show.apply(this, arguments);
}
}
}
});
});
//# sourceMappingURL=converse-singleton.js.map;
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) JC Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define */
(function (root, factory) {
define('converse-fullscreen',["converse-core", "tpl!inverse_brand_heading", "converse-chatview", "converse-controlbox", "converse-muc", "converse-singleton"], factory);
})(this, function (converse, tpl_brand_heading) {
"use strict";
2018-01-17 19:45:33 +01:00
var _converse$env = converse.env,
Strophe = _converse$env.Strophe,
_ = _converse$env._;
converse.plugins.add('converse-fullscreen', {
enabled: function enabled(_converse) {
return _.includes(['fullscreen', 'embedded'], _converse.view_mode);
},
overrides: {
// overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// new functions which don't exist yet can also be added.
ControlBoxView: {
createBrandHeadingHTML: function createBrandHeadingHTML() {
return tpl_brand_heading();
},
insertBrandHeading: function insertBrandHeading() {
var _converse = this.__super__._converse;
2018-01-17 19:45:33 +01:00
var el = _converse.root.getElementById('converse-login-panel');
2018-01-17 19:45:33 +01:00
el.parentNode.insertAdjacentHTML('afterbegin', this.createBrandHeadingHTML());
}
}
},
initialize: function initialize() {
this._converse.api.settings.update({
chatview_avatar_height: 50,
chatview_avatar_width: 50,
hide_open_bookmarks: true,
show_controlbox_by_default: true,
sticky_controlbox: true
});
}
});
});
//# sourceMappingURL=converse-fullscreen.js.map;
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
2018-01-17 19:45:33 +01:00
/*global define */
(function (root, factory) {
define('converse-headline',["converse-core", "tpl!chatbox", "converse-chatview"], factory);
})(this, function (converse, tpl_chatbox) {
"use strict";
2018-01-17 19:45:33 +01:00
var _converse$env = converse.env,
_ = _converse$env._,
utils = _converse$env.utils;
var HEADLINES_TYPE = 'headline';
converse.plugins.add('converse-headline', {
/* Plugin dependencies are other plugins which might be
* overridden or relied upon, and therefore need to be loaded before
* this plugin.
*
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found. By default it's
* false, which means these plugins are only loaded opportunistically.
*
* NB: These plugins need to have already been loaded via require.js.
*/
dependencies: ["converse-chatview"],
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
ChatBoxes: {
model: function model(attrs, options) {
var _converse = this.__super__._converse;
2018-01-17 19:45:33 +01:00
if (attrs.type == HEADLINES_TYPE) {
return new _converse.HeadlinesBox(attrs, options);
} else {
return this.__super__.model.apply(this, arguments);
}
}
},
ChatBoxViews: {
onChatBoxAdded: function onChatBoxAdded(item) {
var _converse = this.__super__._converse;
var view = this.get(item.get('id'));
2018-01-17 19:45:33 +01:00
if (!view && item.get('type') === 'headline') {
view = new _converse.HeadlinesBoxView({
model: item
});
this.add(item.get('id'), view);
return view;
} else {
return this.__super__.onChatBoxAdded.apply(this, arguments);
}
}
}
},
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
var _converse = this._converse,
__ = _converse.__;
_converse.HeadlinesBox = _converse.ChatBox.extend({
defaults: {
'type': 'headline',
'bookmarked': false,
'chat_state': undefined,
'num_unread': 0,
'url': ''
}
});
_converse.HeadlinesBoxView = _converse.ChatBoxView.extend({
className: 'chatbox headlines',
events: {
'click .close-chatbox-button': 'close',
'click .toggle-chatbox-button': 'minimize',
'keypress textarea.chat-textarea': 'keyPressed'
},
initialize: function initialize() {
2018-04-30 16:05:20 +02:00
this.initDebounced();
this.disable_mam = true; // Don't do MAM queries for this box
2018-01-17 19:45:33 +01:00
this.model.messages.on('add', this.onMessageAdded, this);
this.model.on('show', this.show, this);
this.model.on('destroy', this.hide, this);
this.model.on('change:minimized', this.onMinimizedChanged, this);
this.render().insertHeading().fetchMessages().insertIntoDOM().hide();
2018-01-17 19:45:33 +01:00
_converse.emit('chatBoxOpened', this);
2018-01-17 19:45:33 +01:00
_converse.emit('chatBoxInitialized', this);
},
render: function render() {
this.el.setAttribute('id', this.model.get('box_id'));
this.el.innerHTML = tpl_chatbox(_.extend(this.model.toJSON(), {
info_close: '',
label_personal_message: '',
show_send_button: false,
show_toolbar: false,
unread_msgs: ''
}));
this.content = this.el.querySelector('.chat-content');
return this;
},
// Override to avoid the methods in converse-chatview.js
'renderMessageForm': _.noop,
'afterShown': _.noop
});
2018-01-17 19:45:33 +01:00
function onHeadlineMessage(message) {
/* Handler method for all incoming messages of type "headline". */
var from_jid = message.getAttribute('from');
2018-01-17 19:45:33 +01:00
if (utils.isHeadlineMessage(_converse, message)) {
if (_.includes(from_jid, '@') && !_converse.allow_non_roster_messaging) {
return;
}
2018-01-17 19:45:33 +01:00
var chatbox = _converse.chatboxes.create({
'id': from_jid,
'jid': from_jid,
2018-04-30 16:05:20 +02:00
'type': 'headline',
'from': from_jid
});
2018-01-17 19:45:33 +01:00
chatbox.createMessage(message, undefined, message);
2018-01-17 19:45:33 +01:00
_converse.emit('message', {
'chatbox': chatbox,
'stanza': message
});
}
2018-01-17 19:45:33 +01:00
return true;
}
2018-03-25 21:21:43 +02:00
function registerHeadlineHandler() {
_converse.connection.addHandler(onHeadlineMessage, null, 'message');
}
2018-01-17 19:45:33 +01:00
_converse.on('connected', registerHeadlineHandler);
2018-01-17 19:45:33 +01:00
_converse.on('reconnected', registerHeadlineHandler);
}
});
});
//# sourceMappingURL=converse-headline.js.map;
2018-03-25 21:21:43 +02:00
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
2018-03-25 21:21:43 +02:00
// Licensed under the Mozilla Public License (MPLv2)
//
2018-01-17 19:45:33 +01:00
/*global define */
// XEP-0059 Result Set Management
2018-03-25 21:21:43 +02:00
(function (root, factory) {
define('converse-mam',["sizzle", "converse-core", "utils", "converse-disco", "strophe.rsm"], factory);
})(this, function (sizzle, converse, utils) {
"use strict";
var CHATROOMS_TYPE = 'chatroom';
var _converse$env = converse.env,
Promise = _converse$env.Promise,
Strophe = _converse$env.Strophe,
$iq = _converse$env.$iq,
_ = _converse$env._,
moment = _converse$env.moment;
var RSM_ATTRIBUTES = ['max', 'first', 'last', 'after', 'before', 'index', 'count']; // XEP-0313 Message Archive Management
var MAM_ATTRIBUTES = ['with', 'start', 'end'];
function getMessageArchiveID(stanza) {
var result = sizzle("result[xmlns=\"".concat(Strophe.NS.MAM, "\"]"), stanza).pop();
if (!_.isUndefined(result)) {
return result.getAttribute('id');
}
var stanza_id = sizzle("stanza-id[xmlns=\"".concat(Strophe.NS.SID, "\"]"), stanza).pop();
if (!_.isUndefined(stanza_id)) {
return stanza_id.getAttribute('id');
}
}
function queryForArchivedMessages(_converse, options, callback, errback) {
/* Internal function, called by the "archive.query" API method.
*/
var date;
if (_.isFunction(options)) {
callback = options;
errback = callback;
}
2018-01-29 16:33:30 +01:00
var queryid = _converse.connection.getUniqueId();
2018-03-25 21:21:43 +02:00
var attrs = {
'type': 'set'
2018-03-25 21:21:43 +02:00
};
2018-01-17 19:45:33 +01:00
if (!_.isUndefined(options) && options.groupchat) {
if (!options['with']) {
// eslint-disable-line dot-notation
throw new Error('You need to specify a "with" value containing ' + 'the chat room JID, when querying groupchat messages.');
}
2018-03-25 21:21:43 +02:00
attrs.to = options['with']; // eslint-disable-line dot-notation
}
2018-03-25 21:21:43 +02:00
var stanza = $iq(attrs).c('query', {
'xmlns': Strophe.NS.MAM,
'queryid': queryid
});
2018-03-25 21:21:43 +02:00
if (!_.isUndefined(options)) {
stanza.c('x', {
'xmlns': Strophe.NS.XFORM,
'type': 'submit'
}).c('field', {
'var': 'FORM_TYPE',
'type': 'hidden'
}).c('value').t(Strophe.NS.MAM).up().up();
2018-03-25 21:21:43 +02:00
if (options['with'] && !options.groupchat) {
// eslint-disable-line dot-notation
stanza.c('field', {
'var': 'with'
}).c('value').t(options['with']).up().up(); // eslint-disable-line dot-notation
}
2018-03-25 21:21:43 +02:00
_.each(['start', 'end'], function (t) {
if (options[t]) {
date = moment(options[t]);
2018-03-25 21:21:43 +02:00
if (date.isValid()) {
stanza.c('field', {
'var': t
}).c('value').t(date.format()).up().up();
} else {
throw new TypeError("archive.query: invalid date provided for: ".concat(t));
}
}
});
2018-01-17 19:45:33 +01:00
stanza.up();
2018-03-25 21:21:43 +02:00
if (options instanceof Strophe.RSM) {
stanza.cnode(options.toXML());
} else if (_.intersection(RSM_ATTRIBUTES, _.keys(options)).length) {
stanza.cnode(new Strophe.RSM(options).toXML());
}
}
2018-03-25 21:21:43 +02:00
var messages = [];
2018-03-25 21:21:43 +02:00
var message_handler = _converse.connection.addHandler(function (message) {
if (options.groupchat && message.getAttribute('from') !== options['with']) {
// eslint-disable-line dot-notation
return true;
}
2018-01-17 19:45:33 +01:00
var result = message.querySelector('result');
2018-01-17 19:45:33 +01:00
if (!_.isNull(result) && result.getAttribute('queryid') === queryid) {
messages.push(message);
}
2018-03-25 21:21:43 +02:00
return true;
}, Strophe.NS.MAM);
2018-03-25 21:21:43 +02:00
_converse.connection.sendIQ(stanza, function (iq) {
_converse.connection.deleteHandler(message_handler);
2018-03-25 21:21:43 +02:00
if (_.isFunction(callback)) {
var set = iq.querySelector('set');
var rsm;
if (!_.isUndefined(set)) {
rsm = new Strophe.RSM({
xml: set
});
_.extend(rsm, _.pick(options, _.concat(MAM_ATTRIBUTES, ['max'])));
}
callback(messages, rsm);
}
}, function () {
_converse.connection.deleteHandler(message_handler);
if (_.isFunction(errback)) {
errback.apply(this, arguments);
}
}, _converse.message_archiving_timeout);
}
converse.plugins.add('converse-mam', {
dependencies: ['converse-chatview', 'converse-muc', 'converse-muc-views'],
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
ChatBox: {
getMessageAttributesFromStanza: function getMessageAttributesFromStanza(message, delay, original_stanza) {
var attrs = this.__super__.getMessageAttributesFromStanza.apply(this, arguments);
var archive_id = getMessageArchiveID(original_stanza);
if (archive_id) {
attrs.archive_id = archive_id;
}
2018-03-25 21:21:43 +02:00
return attrs;
}
},
ChatBoxView: {
render: function render() {
var result = this.__super__.render.apply(this, arguments);
2018-03-27 18:26:56 +02:00
if (!this.disable_mam) {
this.content.addEventListener('scroll', _.debounce(this.onScroll.bind(this), 100));
}
2018-03-25 21:21:43 +02:00
return result;
},
fetchNewestMessages: function fetchNewestMessages() {
/* Fetches messages that might have been archived *after*
* the last archived message in our local cache.
*/
if (this.disable_mam) {
return;
}
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
var _converse = this.__super__._converse,
most_recent_msg = utils.getMostRecentMessage(this.model);
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (_.isNil(most_recent_msg)) {
this.fetchArchivedMessages();
} else {
var archive_id = most_recent_msg.get('archive_id');
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (archive_id) {
this.fetchArchivedMessages({
'after': most_recent_msg.get('archive_id')
});
} else {
this.fetchArchivedMessages({
'start': most_recent_msg.get('time')
});
}
2018-05-07 18:20:15 +02:00
}
},
fetchArchivedMessagesIfNecessary: function fetchArchivedMessagesIfNecessary() {
2018-05-07 18:20:15 +02:00
var _this = this;
2018-03-25 21:21:43 +02:00
/* Check if archived messages should be fetched, and if so, do so. */
if (this.disable_mam || this.model.get('mam_initialized')) {
return;
}
2018-03-25 21:21:43 +02:00
var _converse = this.__super__._converse;
2018-03-25 21:21:43 +02:00
_converse.api.disco.supports(Strophe.NS.MAM, _converse.bare_jid).then(function (result) {
// Success
if (result.length) {
2018-05-07 18:20:15 +02:00
_this.fetchArchivedMessages();
}
2018-05-07 18:20:15 +02:00
_this.model.save({
'mam_initialized': true
2018-03-25 21:21:43 +02:00
});
}, function () {
// Error
_converse.log("Error or timeout while checking for MAM support", Strophe.LogLevel.ERROR);
}).catch(function (msg) {
2018-05-07 18:20:15 +02:00
_this.clearSpinner();
2018-01-17 19:45:33 +01:00
_converse.log(msg, Strophe.LogLevel.FATAL);
});
},
fetchArchivedMessages: function fetchArchivedMessages(options) {
2018-05-07 18:20:15 +02:00
var _this2 = this;
2018-01-17 19:45:33 +01:00
var _converse = this.__super__._converse;
2018-03-25 21:21:43 +02:00
if (this.disable_mam) {
return;
}
2018-03-27 18:26:56 +02:00
2018-05-07 18:20:15 +02:00
var is_groupchat = this.model.get('type') === CHATROOMS_TYPE;
var mam_jid, message_handler;
2018-03-25 21:21:43 +02:00
2018-05-07 18:20:15 +02:00
if (is_groupchat) {
mam_jid = this.model.get('jid');
message_handler = this.model.onMessage.bind(this.model);
} else {
mam_jid = _converse.bare_jid;
message_handler = _converse.chatboxes.onMessage.bind(_converse.chatboxes);
}
2018-01-17 19:45:33 +01:00
2018-05-07 18:20:15 +02:00
_converse.api.disco.supports(Strophe.NS.MAM, mam_jid).then(function (results) {
// Success
if (!results.length) {
return;
}
2018-05-07 18:20:15 +02:00
_this2.addSpinner();
_converse.api.archive.query(_.extend({
'groupchat': is_groupchat,
'before': '',
// Page backwards from the most recent message
'max': _converse.archived_messages_page_size,
'with': _this2.model.get('jid')
}, options), function (messages) {
// Success
_this2.clearSpinner();
_.each(messages, message_handler);
}, function () {
// Error
_this2.clearSpinner();
_converse.log("Error or timeout while trying to fetch " + "archived messages", Strophe.LogLevel.ERROR);
});
}, function () {
// Error
2018-05-07 18:20:15 +02:00
_converse.log("Error or timeout while checking for MAM support", Strophe.LogLevel.ERROR);
}).catch(function (msg) {
_this2.clearSpinner();
2018-01-17 19:45:33 +01:00
2018-05-07 18:20:15 +02:00
_converse.log(msg, Strophe.LogLevel.FATAL);
});
},
onScroll: function onScroll(ev) {
var _converse = this.__super__._converse;
2018-03-25 21:21:43 +02:00
if (this.content.scrollTop === 0 && this.model.messages.length) {
var oldest_message = this.model.messages.at(0);
var archive_id = oldest_message.get('archive_id');
2018-03-25 21:21:43 +02:00
if (archive_id) {
this.fetchArchivedMessages({
'before': archive_id
});
} else {
this.fetchArchivedMessages({
'end': oldest_message.get('time')
});
}
}
}
},
ChatRoom: {
isDuplicate: function isDuplicate(message, original_stanza) {
var result = this.__super__.isDuplicate.apply(this, arguments);
2018-03-25 21:21:43 +02:00
if (result) {
return result;
}
2018-03-25 21:21:43 +02:00
var archive_id = getMessageArchiveID(original_stanza);
if (archive_id) {
return this.messages.filter({
'archive_id': archive_id
}).length > 0;
}
}
},
ChatRoomView: {
initialize: function initialize() {
var _converse = this.__super__._converse;
this.__super__.initialize.apply(this, arguments);
2016-11-07 15:43:48 +01:00
this.model.on('change:mam_enabled', this.fetchArchivedMessagesIfNecessary, this);
this.model.on('change:connection_status', this.fetchArchivedMessagesIfNecessary, this);
},
renderChatArea: function renderChatArea() {
var result = this.__super__.renderChatArea.apply(this, arguments);
2018-03-25 21:21:43 +02:00
if (!this.disable_mam) {
this.content.addEventListener('scroll', _.debounce(this.onScroll.bind(this), 100));
}
return result;
},
fetchArchivedMessagesIfNecessary: function fetchArchivedMessagesIfNecessary() {
if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED || !this.model.get('mam_enabled') || this.model.get('mam_initialized')) {
return;
}
this.fetchArchivedMessages();
this.model.save({
'mam_initialized': true
});
}
}
},
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by Converse.js's plugin machinery.
*/
var _converse = this._converse;
_converse.api.settings.update({
archived_messages_page_size: '50',
message_archiving: undefined,
// Supported values are 'always', 'never', 'roster' (https://xmpp.org/extensions/xep-0313.html#prefs)
message_archiving_timeout: 8000 // Time (in milliseconds) to wait before aborting MAM request
});
2018-03-25 21:21:43 +02:00
_converse.onMAMError = function (iq) {
if (iq.querySelectorAll('feature-not-implemented').length) {
_converse.log("Message Archive Management (XEP-0313) not supported by this server", Strophe.LogLevel.WARN);
} else {
_converse.log("An error occured while trying to set archiving preferences.", Strophe.LogLevel.ERROR);
_converse.log(iq);
}
};
2018-03-25 21:21:43 +02:00
_converse.onMAMPreferences = function (feature, iq) {
/* Handle returned IQ stanza containing Message Archive
* Management (XEP-0313) preferences.
*
* XXX: For now we only handle the global default preference.
* The XEP also provides for per-JID preferences, which is
* currently not supported in converse.js.
*
* Per JID preferences will be set in chat boxes, so it'll
* probbaly be handled elsewhere in any case.
*/
var preference = sizzle("prefs[xmlns=\"".concat(Strophe.NS.MAM, "\"]"), iq).pop();
var default_pref = preference.getAttribute('default');
if (default_pref !== _converse.message_archiving) {
var stanza = $iq({
'type': 'set'
}).c('prefs', {
'xmlns': Strophe.NS.MAM,
'default': _converse.message_archiving
});
2018-01-17 19:45:33 +01:00
_.each(preference.children, function (child) {
stanza.cnode(child).up();
});
_converse.connection.sendIQ(stanza, _.partial(function (feature, iq) {
// XXX: Strictly speaking, the server should respond with the updated prefs
// (see example 18: https://xmpp.org/extensions/xep-0313.html#config)
// but Prosody doesn't do this, so we don't rely on it.
feature.save({
'preferences': {
'default': _converse.message_archiving
}
});
}, feature), _converse.onMAMError);
} else {
feature.save({
'preferences': {
'default': _converse.message_archiving
}
});
}
};
/* Event handlers */
2018-03-25 21:21:43 +02:00
_converse.on('serviceDiscovered', function (feature) {
var prefs = feature.get('preferences') || {};
2018-03-25 21:21:43 +02:00
if (feature.get('var') === Strophe.NS.MAM && prefs['default'] !== _converse.message_archiving && // eslint-disable-line dot-notation
!_.isUndefined(_converse.message_archiving)) {
// Ask the server for archiving preferences
_converse.connection.sendIQ($iq({
'type': 'get'
}).c('prefs', {
'xmlns': Strophe.NS.MAM
}), _.partial(_converse.onMAMPreferences, feature), _.partial(_converse.onMAMError, feature));
}
});
2018-03-25 21:21:43 +02:00
_converse.on('addClientFeatures', function () {
2018-05-15 10:32:13 +02:00
_converse.api.disco.own.features.add(Strophe.NS.MAM);
});
_converse.on('afterMessagesFetched', function (chatboxview) {
chatboxview.fetchNewestMessages();
});
2018-03-25 21:21:43 +02:00
_converse.on('reconnected', function () {
var private_chats = _converse.chatboxviews.filter(function (view) {
return _.at(view, 'model.attributes.type')[0] === 'chatbox';
});
2018-03-25 21:21:43 +02:00
_.each(private_chats, function (view) {
return view.fetchNewestMessages();
});
});
_.extend(_converse.api, {
/* Extend default converse.js API to add methods specific to MAM
*/
'archive': {
'query': function query(options, callback, errback) {
/* Do a MAM (XEP-0313) query for archived messages.
*
* Parameters:
* (Object) options - Query parameters, either
* MAM-specific or also for Result Set Management.
* (Function) callback - A function to call whenever
* we receive query-relevant stanza.
* (Function) errback - A function to call when an
* error stanza is received.
*
* The options parameter can also be an instance of
* Strophe.RSM to enable easy querying between results pages.
*
* When the the callback is called, a Strophe.RSM object is
* returned on which "next" or "previous" can be called
* before passing it in again to this method, to
* get the next or previous page in the result set.
*/
if (!_converse.api.connection.connected()) {
throw new Error('Can\'t call `api.archive.query` before having established an XMPP session');
}
return queryForArchivedMessages(_converse, options, callback, errback);
}
}
});
}
});
});
//# sourceMappingURL=converse-mam.js.map;
define('tpl!chatbox_minimize', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<a class="chatbox-btn toggle-chatbox-button fa fa-minus" title="' +
__e(o.info_minimize) +
'"></a>\n';
return __p
};});
2018-03-25 21:21:43 +02:00
define('tpl!toggle_chats', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p +=
__e(o.num_minimized) +
' ' +
__e(o.Minimized) +
'\n<span class="unread-message-count ';
if (!o.num_unread) { ;
__p += ' unread-message-count-hidden ';
} ;
__p += '" href="#">' +
__e(o.num_unread) +
'</span>\n';
return __p
};});
define('tpl!trimmed_chat', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<a href="#" class="restore-chat w-100 align-self-center" title="' +
__e(o.tooltip) +
'">\n ';
if (o.num_unread) { ;
__p += ' \n <span class="message-count badge badge-light">' +
__e(o.num_unread) +
'</span>\n ';
} ;
__p += '\n ' +
__e(o.title || o.jid ) +
'\n</a>\n<a class="chatbox-btn close-chatbox-button fa fa-times"></a>\n';
return __p
};});
2018-03-25 21:21:43 +02:00
define('tpl!chats_panel', ['lodash'], function(_) {return function(o) {
var __t, __p = '';
__p += '<a id="toggle-minimized-chats" href="#" class="row no-gutters"></a>\n<div class="flyout minimized-chats-flyout row no-gutters"></div>\n';
return __p
};});
2018-03-25 21:21:43 +02:00
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
2018-03-25 21:21:43 +02:00
/*global define, window, document */
(function (root, factory) {
define('converse-minimize',["converse-core", "tpl!chatbox_minimize", "tpl!toggle_chats", "tpl!trimmed_chat", "tpl!chats_panel", "converse-chatview"], factory);
})(this, function (converse, tpl_chatbox_minimize, tpl_toggle_chats, tpl_trimmed_chat, tpl_chats_panel) {
"use strict";
2018-03-25 21:21:43 +02:00
var _converse$env = converse.env,
_ = _converse$env._,
Backbone = _converse$env.Backbone,
Promise = _converse$env.Promise,
Strophe = _converse$env.Strophe,
b64_sha1 = _converse$env.b64_sha1,
moment = _converse$env.moment;
var u = converse.env.utils;
converse.plugins.add('converse-minimize', {
/* Optional dependencies are other plugins which might be
* overridden or relied upon, and therefore need to be loaded before
* this plugin. They are called "optional" because they might not be
* available, in which case any overrides applicable to them will be
* ignored.
*
* It's possible however to make optional dependencies non-optional.
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found.
*
* NB: These plugins need to have already been loaded via require.js.
*/
dependencies: ["converse-chatview", "converse-controlbox", "converse-muc", "converse-muc-views", "converse-headline"],
enabled: function enabled(_converse) {
return _converse.view_mode == 'overlayed';
},
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
registerGlobalEventHandlers: function registerGlobalEventHandlers() {
var _converse = this.__super__._converse;
window.addEventListener("resize", _.debounce(function (ev) {
if (_converse.connection.connected) {
_converse.chatboxviews.trimChats();
}
}, 200));
return this.__super__.registerGlobalEventHandlers.apply(this, arguments);
},
ChatBox: {
initialize: function initialize() {
this.__super__.initialize.apply(this, arguments);
2018-03-25 21:21:43 +02:00
this.on('show', this.maximize, this);
2018-03-25 21:21:43 +02:00
if (this.get('id') === 'controlbox') {
return;
}
this.save({
'minimized': this.get('minimized') || false,
'time_minimized': this.get('time_minimized') || moment()
});
},
maximize: function maximize() {
u.safeSave(this, {
'minimized': false,
'time_opened': moment().valueOf()
});
},
minimize: function minimize() {
u.safeSave(this, {
'minimized': true,
'time_minimized': moment().format()
});
}
},
ChatBoxView: {
events: {
'click .toggle-chatbox-button': 'minimize'
},
initialize: function initialize() {
this.model.on('change:minimized', this.onMinimizedChanged, this);
return this.__super__.initialize.apply(this, arguments);
},
_show: function _show() {
var _converse = this.__super__._converse;
if (!this.model.get('minimized')) {
this.__super__._show.apply(this, arguments);
_converse.chatboxviews.trimChats(this);
} else {
this.minimize();
}
},
isNewMessageHidden: function isNewMessageHidden() {
return this.model.get('minimized') || this.__super__.isNewMessageHidden.apply(this, arguments);
},
shouldShowOnTextMessage: function shouldShowOnTextMessage() {
return !this.model.get('minimized') && this.__super__.shouldShowOnTextMessage.apply(this, arguments);
},
setChatBoxHeight: function setChatBoxHeight(height) {
if (!this.model.get('minimized')) {
return this.__super__.setChatBoxHeight.apply(this, arguments);
}
},
setChatBoxWidth: function setChatBoxWidth(width) {
if (!this.model.get('minimized')) {
return this.__super__.setChatBoxWidth.apply(this, arguments);
}
},
onMinimizedChanged: function onMinimizedChanged(item) {
if (item.get('minimized')) {
this.minimize();
} else {
this.maximize();
}
},
maximize: function maximize() {
// Restores a minimized chat box
var _converse = this.__super__._converse;
this.insertIntoDOM();
2018-03-25 21:21:43 +02:00
if (!this.model.isScrolledUp()) {
this.model.clearUnreadMsgCounter();
}
2018-03-25 21:21:43 +02:00
this.show();
2018-03-25 21:21:43 +02:00
this.__super__._converse.emit('chatBoxMaximized', this);
2018-03-25 21:21:43 +02:00
return this;
},
minimize: function minimize(ev) {
var _converse = this.__super__._converse;
2018-03-25 21:21:43 +02:00
if (ev && ev.preventDefault) {
ev.preventDefault();
} // save the scroll position to restore it on maximize
2018-03-25 21:21:43 +02:00
if (this.model.collection && this.model.collection.browserStorage) {
this.model.save({
'scroll': this.content.scrollTop
});
} else {
this.model.set({
'scroll': this.content.scrollTop
});
}
this.setChatState(_converse.INACTIVE).model.minimize();
this.hide();
2018-03-25 21:21:43 +02:00
_converse.emit('chatBoxMinimized', this);
}
},
2018-05-07 18:20:15 +02:00
ChatBoxHeading: {
render: function render() {
var _converse = this.__super__._converse,
__ = _converse.__;
var result = this.__super__.render.apply(this, arguments);
var new_html = tpl_chatbox_minimize({
info_minimize: __('Minimize this chat box')
});
var el = this.el.querySelector('.toggle-chatbox-button');
if (el) {
el.outerHTML = new_html;
} else {
var button = this.el.querySelector('.close-chatbox-button');
button.insertAdjacentHTML('afterEnd', new_html);
}
}
},
ChatRoomView: {
events: {
'click .toggle-chatbox-button': 'minimize'
},
initialize: function initialize() {
this.model.on('change:minimized', function (item) {
if (item.get('minimized')) {
this.hide();
} else {
this.maximize();
}
}, this);
2018-03-25 21:21:43 +02:00
var result = this.__super__.initialize.apply(this, arguments);
2018-03-25 21:21:43 +02:00
if (this.model.get('minimized')) {
this.hide();
}
2018-03-25 21:21:43 +02:00
return result;
},
generateHeadingHTML: function generateHeadingHTML() {
var _converse = this.__super__._converse,
__ = _converse.__;
var html = this.__super__.generateHeadingHTML.apply(this, arguments);
var div = document.createElement('div');
div.innerHTML = html;
var button = div.querySelector('.close-chatbox-button');
button.insertAdjacentHTML('afterend', tpl_chatbox_minimize({
'info_minimize': __('Minimize this chat box')
}));
return div.innerHTML;
}
},
ChatBoxes: {
chatBoxMayBeShown: function chatBoxMayBeShown(chatbox) {
return this.__super__.chatBoxMayBeShown.apply(this, arguments) && !chatbox.get('minimized');
}
},
ChatBoxViews: {
getChatBoxWidth: function getChatBoxWidth(view) {
if (!view.model.get('minimized') && u.isVisible(view.el)) {
return u.getOuterWidth(view.el, true);
}
2018-03-25 21:21:43 +02:00
return 0;
},
getShownChats: function getShownChats() {
return this.filter(function (view) {
return (// The controlbox can take a while to close,
// so we need to check its state. That's why we checked
// the 'closed' state.
!view.model.get('minimized') && !view.model.get('closed') && u.isVisible(view.el)
);
});
},
trimChats: function trimChats(newchat) {
var _this = this;
/* This method is called when a newly created chat box will
* be shown.
*
* It checks whether there is enough space on the page to show
* another chat box. Otherwise it minimizes the oldest chat box
* to create space.
*/
var _converse = this.__super__._converse,
shown_chats = this.getShownChats(),
body_width = u.getOuterWidth(document.querySelector('body'), true);
if (_converse.no_trimming || shown_chats.length <= 1) {
return;
}
2018-03-25 21:21:43 +02:00
if (this.getChatBoxWidth(shown_chats[0]) === body_width) {
// If the chats shown are the same width as the body,
// then we're in responsive mode and the chats are
// fullscreen. In this case we don't trim.
return;
}
2018-03-25 21:21:43 +02:00
_converse.api.waitUntil('minimizedChatsInitialized').then(function () {
var minimized_el = _.get(_converse.minimized_chats, 'el'),
new_id = newchat ? newchat.model.get('id') : null;
2018-03-25 21:21:43 +02:00
if (minimized_el) {
var minimized_width = _.includes(_this.model.pluck('minimized'), true) ? u.getOuterWidth(minimized_el, true) : 0;
2018-03-25 21:21:43 +02:00
var boxes_width = _.reduce(_this.xget(new_id), function (memo, view) {
return memo + _this.getChatBoxWidth(view);
}, newchat ? u.getOuterWidth(newchat.el, true) : 0);
2018-03-25 21:21:43 +02:00
if (minimized_width + boxes_width > body_width) {
var oldest_chat = _this.getOldestMaximizedChat([new_id]);
2018-03-25 21:21:43 +02:00
if (oldest_chat) {
// We hide the chat immediately, because waiting
// for the event to fire (and letting the
// ChatBoxView hide it then) causes race
// conditions.
var view = _this.get(oldest_chat.get('id'));
2018-03-25 21:21:43 +02:00
if (view) {
view.hide();
}
2018-01-17 19:45:33 +01:00
oldest_chat.minimize();
}
}
}
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
},
getOldestMaximizedChat: function getOldestMaximizedChat(exclude_ids) {
// Get oldest view (if its id is not excluded)
exclude_ids.push('controlbox');
var i = 0;
var model = this.model.sort().at(i);
2018-01-17 19:45:33 +01:00
while (_.includes(exclude_ids, model.get('id')) || model.get('minimized') === true) {
i++;
model = this.model.at(i);
2018-03-25 21:21:43 +02:00
if (!model) {
return null;
}
}
2018-03-25 21:21:43 +02:00
return model;
}
}
},
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by Converse.js's plugin machinery.
*/
var _converse = this._converse,
__ = _converse.__; // Add new HTML templates.
_converse.templates.chatbox_minimize = tpl_chatbox_minimize;
_converse.templates.toggle_chats = tpl_toggle_chats;
_converse.templates.trimmed_chat = tpl_trimmed_chat;
_converse.templates.chats_panel = tpl_chats_panel;
2018-03-25 21:21:43 +02:00
_converse.api.settings.update({
no_trimming: false // Set to true for phantomjs tests (where browser apparently has no width)
2018-03-25 21:21:43 +02:00
});
2018-03-25 21:21:43 +02:00
_converse.api.promises.add('minimizedChatsInitialized');
2018-03-25 21:21:43 +02:00
_converse.MinimizedChatBoxView = Backbone.NativeView.extend({
tagName: 'div',
className: 'chat-head row no-gutters',
events: {
'click .close-chatbox-button': 'close',
'click .restore-chat': 'restore'
},
initialize: function initialize() {
this.model.on('change:num_unread', this.render, this);
},
render: function render() {
var data = _.extend(this.model.toJSON(), {
'tooltip': __('Click to restore this chat')
});
if (this.model.get('type') === 'chatroom') {
data.title = this.model.get('name');
u.addClass('chat-head-chatroom', this.el);
} else {
data.title = this.model.get('fullname');
u.addClass('chat-head-chatbox', this.el);
}
this.el.innerHTML = tpl_trimmed_chat(data);
return this.el;
},
close: function close(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}
2018-03-25 21:21:43 +02:00
this.remove();
var view = _converse.chatboxviews.get(this.model.get('id'));
if (view) {
// This will call model.destroy(), removing it from the
// collection and will also emit 'chatBoxClosed'
view.close();
} else {
this.model.destroy();
_converse.emit('chatBoxClosed', this);
}
return this;
},
restore: _.debounce(function (ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}
this.model.off('change:num_unread', null, this);
this.remove();
this.model.maximize();
}, 200, {
'leading': true
})
});
_converse.MinimizedChats = Backbone.Overview.extend({
tagName: 'div',
id: "minimized-chats",
className: 'hidden',
events: {
"click #toggle-minimized-chats": "toggle"
},
initialize: function initialize() {
this.render();
this.initToggle();
this.addMultipleChats(this.model.where({
'minimized': true
}));
this.model.on("add", this.onChanged, this);
this.model.on("destroy", this.removeChat, this);
this.model.on("change:minimized", this.onChanged, this);
this.model.on('change:num_unread', this.updateUnreadMessagesCounter, this);
},
render: function render() {
if (!this.el.parentElement) {
this.el.innerHTML = tpl_chats_panel();
_converse.chatboxviews.insertRowColumn(this.el);
}
2017-07-22 22:21:05 +02:00
if (this.keys().length === 0) {
this.el.classList.add('hidden');
} else if (this.keys().length > 0 && !u.isVisible(this.el)) {
this.el.classList.remove('hidden');
2018-03-25 21:21:43 +02:00
_converse.chatboxviews.trimChats();
}
return this.el;
},
tearDown: function tearDown() {
this.model.off("add", this.onChanged);
this.model.off("destroy", this.removeChat);
this.model.off("change:minimized", this.onChanged);
this.model.off('change:num_unread', this.updateUnreadMessagesCounter);
return this;
},
initToggle: function initToggle() {
this.toggleview = new _converse.MinimizedChatsToggleView({
model: new _converse.MinimizedChatsToggle()
});
var id = b64_sha1("converse.minchatstoggle".concat(_converse.bare_jid));
this.toggleview.model.id = id; // Appears to be necessary for backbone.browserStorage
2018-03-25 21:21:43 +02:00
this.toggleview.model.browserStorage = new Backbone.BrowserStorage[_converse.storage](id);
this.toggleview.model.fetch();
},
toggle: function toggle(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}
2018-03-25 21:21:43 +02:00
this.toggleview.model.save({
'collapsed': !this.toggleview.model.get('collapsed')
});
u.slideToggleElement(this.el.querySelector('.minimized-chats-flyout'), 200);
},
onChanged: function onChanged(item) {
if (item.get('id') === 'controlbox') {
// The ControlBox has it's own minimize toggle
return;
}
2018-03-25 21:21:43 +02:00
if (item.get('minimized')) {
this.addChat(item);
} else if (this.get(item.get('id'))) {
this.removeChat(item);
}
},
addChatView: function addChatView(item) {
var existing = this.get(item.get('id'));
if (existing && existing.el.parentNode) {
return;
}
var view = new _converse.MinimizedChatBoxView({
model: item
});
this.el.querySelector('.minimized-chats-flyout').insertAdjacentElement('beforeEnd', view.render());
this.add(item.get('id'), view);
},
addMultipleChats: function addMultipleChats(items) {
_.each(items, this.addChatView.bind(this));
this.toggleview.model.set({
'num_minimized': this.keys().length
});
this.render();
},
addChat: function addChat(item) {
this.addChatView(item);
this.toggleview.model.set({
'num_minimized': this.keys().length
});
this.render();
},
removeChat: function removeChat(item) {
this.remove(item.get('id'));
this.toggleview.model.set({
'num_minimized': this.keys().length
});
this.render();
},
updateUnreadMessagesCounter: function updateUnreadMessagesCounter() {
var ls = this.model.pluck('num_unread');
var count = 0,
i;
2018-03-25 21:21:43 +02:00
for (i = 0; i < ls.length; i++) {
count += ls[i];
}
2018-03-25 21:21:43 +02:00
this.toggleview.model.save({
'num_unread': count
});
this.render();
}
});
_converse.MinimizedChatsToggle = Backbone.Model.extend({
defaults: {
'collapsed': false,
'num_minimized': 0,
'num_unread': 0
}
});
_converse.MinimizedChatsToggleView = Backbone.NativeView.extend({
el: '#toggle-minimized-chats',
initialize: function initialize() {
this.model.on('change:num_minimized', this.render, this);
this.model.on('change:num_unread', this.render, this);
this.flyout = this.el.parentElement.querySelector('.minimized-chats-flyout');
},
render: function render() {
this.el.innerHTML = tpl_toggle_chats(_.extend(this.model.toJSON(), {
'Minimized': __('Minimized')
}));
2018-03-25 21:21:43 +02:00
if (this.model.get('collapsed')) {
u.hideElement(this.flyout);
} else {
u.showElement(this.flyout);
}
return this.el;
}
});
Promise.all([_converse.api.waitUntil('connectionInitialized'), _converse.api.waitUntil('chatBoxesInitialized')]).then(function () {
_converse.minimized_chats = new _converse.MinimizedChats({
model: _converse.chatboxes
});
_converse.emit('minimizedChatsInitialized');
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
_converse.on('controlBoxOpened', function (chatbox) {
// Wrapped in anon method because at scan time, chatboxviews
// attr not set yet.
2018-05-07 18:20:15 +02:00
if (_converse.connection.connected) {
_converse.chatboxviews.trimChats(chatbox);
}
});
}
});
});
2018-05-07 18:20:15 +02:00
//# sourceMappingURL=converse-minimize.js.map;
2018-03-25 21:21:43 +02:00
define('tpl!add_chatroom_modal', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<div class="modal fade" id="add-chatroom-modal" tabindex="-1" role="dialog" aria-labelledby="add-chatroom-modal-label" aria-hidden="true">\n <div class="modal-dialog" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title"\n id="add-chatroom-modal-label">' +
__e(o.heading_new_chatroom) +
'</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">&times;</span>\n </button>\n </div>\n <div class="modal-body">\n <form class="converse-form add-chatroom">\n <div class="form-group">\n <label for="chatroom">' +
__e(o.label_room_address) +
':</label>\n <input type="text" required="required" name="chatroom" class="form-control" placeholder="' +
__e(o.chatroom_placeholder) +
'">\n </div>\n <div class="form-group">\n <label for="nickname">' +
__e(o.label_nickname) +
':</label>\n <input type="text" name="nickname" value="' +
__e(o.nick) +
'" class="form-control">\n </div>\n <input type="submit" class="btn btn-primary" name="join" value="' +
__e(o.label_join) +
'">\n </form>\n </div>\n </div>\n </div>\n</div>\n';
return __p
};});
2018-03-25 21:21:43 +02:00
define('tpl!chatarea', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<div class="chat-area col">\n <div class="chat-content ';
if (o.show_send_button) { ;
__p += 'chat-content-sendbutton';
} ;
__p += '"></div>\n <div class="new-msgs-indicator hidden">▼ ' +
__e( o.unread_msgs ) +
' ▼</div>\n <form class="sendXMPPMessage">\n ';
if (o.show_toolbar) { ;
__p += '\n <ul class="chat-toolbar no-text-select"></ul>\n ';
} ;
__p += '\n <textarea type="text" class="chat-textarea ';
if (o.show_send_button) { ;
__p += 'chat-textarea-send-button';
} ;
__p += '"\n placeholder="' +
__e(o.label_message) +
'"></textarea>\n ';
if (o.show_send_button) { ;
__p += '\n <button type="submit" class="pure-button send-button">' +
__e( o.label_send ) +
'</button>\n ';
} ;
__p += '\n </form>\n</div>\n';
return __p
};});
2018-03-25 21:21:43 +02:00
define('tpl!chatroom', ['lodash'], function(_) {return function(o) {
var __t, __p = '';
__p += '<div class="flyout box-flyout">\n <div class="chat-head chat-head-chatroom row no-gutters"></div>\n <div class="chat-body chatroom-body row no-gutters"></div>\n</div>\n';
return __p
};});
2018-03-25 21:21:43 +02:00
define('tpl!chatroom_disconnect', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<p class="disconnect-msg">' +
__e(o.disconnect_message) +
'</p>\n';
return __p
};});
2018-03-25 21:21:43 +02:00
define('tpl!chatroom_features', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
if (o.has_features) { ;
__p += '\n<p class="occupants-heading">' +
__e(o.label_features) +
'</p>\n';
} ;
__p += '\n<ul class="features-list">\n';
if (o.passwordprotected) { ;
__p += '\n<li class="feature" title="' +
__e( o.tt_passwordprotected ) +
'"><span class="fa fa-lock"></span>' +
__e( o.label_passwordprotected ) +
'</li>\n';
} ;
__p += '\n';
if (o.unsecured) { ;
__p += '\n<li class="feature" title="' +
__e( o.tt_unsecured ) +
'"><span class="fa fa-unlock"></span>' +
__e( o.label_unsecured ) +
'</li>\n';
} ;
__p += '\n';
if (o.hidden) { ;
__p += '\n<li class="feature" title="' +
__e( o.tt_hidden ) +
'"><span class="fa fa-eye-slash"></span>' +
__e( o.label_hidden ) +
'</li>\n';
} ;
__p += '\n';
if (o.public_room) { ;
__p += '\n<li class="feature" title="' +
__e( o.tt_public ) +
'"><span class="fa fa-eye"></span>' +
__e( o.label_public ) +
'</li>\n';
} ;
__p += '\n';
if (o.membersonly) { ;
__p += '\n<li class="feature" title="' +
__e( o.tt_membersonly ) +
'"><span class="fa fa-address-book"></span>' +
__e( o.label_membersonly ) +
'</li>\n';
} ;
__p += '\n';
if (o.open) { ;
__p += '\n<li class="feature" title="' +
__e( o.tt_open ) +
'"><span class="fa fa-globe"></span>' +
__e( o.label_open ) +
'</li>\n';
} ;
__p += '\n';
if (o.persistent) { ;
__p += '\n<li class="feature" title="' +
__e( o.tt_persistent ) +
'"><span class="fa fa-save"></span>' +
__e( o.label_persistent ) +
'</li>\n';
} ;
__p += '\n';
if (o.temporary) { ;
__p += '\n<li class="feature" title="' +
__e( o.tt_temporary ) +
'"><span class="fa fa-snowflake-o"></span>' +
__e( o.label_temporary ) +
'</li>\n';
} ;
__p += '\n';
if (o.nonanonymous) { ;
__p += '\n<li class="feature" title="' +
__e( o.tt_nonanonymous ) +
2018-05-09 14:37:27 +02:00
'"><span class="fa fa-id-card"></span>' +
__e( o.label_nonanonymous ) +
'</li>\n';
} ;
__p += '\n';
if (o.semianonymous) { ;
__p += '\n<li class="feature" title="' +
__e( o.tt_semianonymous ) +
'"><span class="fa fa-user-secret"></span>' +
__e( o.label_semianonymous ) +
'</li>\n';
} ;
__p += '\n';
if (o.moderated) { ;
__p += '\n<li class="feature" title="' +
__e( o.tt_moderated ) +
'"><span class="fa fa-gavel"></span>' +
__e( o.label_moderated ) +
'</li>\n';
} ;
__p += '\n';
if (o.unmoderated) { ;
__p += '\n<li class="feature" title="' +
__e( o.tt_unmoderated ) +
'"><span class="fa fa-info-circle"></span>' +
__e( o.label_unmoderated ) +
'</li>\n';
} ;
__p += '\n';
if (o.mam_enabled) { ;
__p += '\n<li class="feature" title="' +
__e( o.tt_mam_enabled ) +
'"><span class="fa fa-database"></span>' +
__e( o.label_mam_enabled ) +
'</li>\n';
} ;
__p += '\n</ul>\n';
return __p
};});
2018-03-25 21:21:43 +02:00
define('tpl!chatroom_form', ['lodash'], function(_) {return function(o) {
var __t, __p = '';
__p += '<div class="chatroom-form-container">\n <form class="converse-form chatroom-form">\n <fieldset class="form-group">\n <span class="spinner fa fa-spinner centered"/>\n </fieldset>\n </form>\n</div>\n';
return __p
};});
2018-03-25 21:21:43 +02:00
define('tpl!chatroom_head', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
2018-05-23 04:27:33 +02:00
__p += '<div class="col col-8">\n <div class="chat-title" title="' +
__e(o.jid) +
'">\n ';
if (o.name && o.name !== o.Strophe.getNodeFromJid(o.jid)) { ;
2018-04-30 16:05:20 +02:00
__p += '\n ' +
__e( o.name ) +
2018-04-30 16:05:20 +02:00
'\n ';
} else { ;
2018-04-30 16:05:20 +02:00
__p += '\n ' +
__e( o.Strophe.getNodeFromJid(o.jid) ) +
2018-04-30 16:05:20 +02:00
'@' +
__e( o.Strophe.getDomainFromJid(o.jid) ) +
'\n ';
} ;
2018-04-30 16:05:20 +02:00
__p += '\n </div>\n <p class="chatroom-description">' +
__e( o.description ) +
2018-04-30 16:05:20 +02:00
'<p/>\n</div>\n<div class="chatbox-buttons row no-gutters">\n <a class="chatbox-btn close-chatbox-button fa fa-sign-out" title="' +
__e(o.info_close) +
'"></a>\n ';
if (o.affiliation == 'owner') { ;
__p += '\n <a class="chatbox-btn configure-chatroom-button fa fa-wrench" title="' +
__e(o.info_configure) +
' "></a>\n ';
} ;
__p += '\n</div>\n';
return __p
};});
define('tpl!chatroom_invite', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<form class="room-invite">\n ';
if (o.error_message) { ;
__p += '\n <span class="error">' +
__e(o.error_message) +
'</span>\n ';
} ;
__p += '\n <input class="form-control invited-contact" placeholder="' +
__e(o.label_invitation) +
'" type="text"/>\n</form>\n';
return __p
};});
define('tpl!chatroom_nickname_form', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<div class="chatroom-form-container">\n <form class="converse-form chatroom-form converse-centered-form">\n <fieldset class="form-group">\n <label>' +
__e(o.heading) +
'</label>\n <p class="validation-message">' +
__e(o.validation_message) +
'</p>\n <input type="text" required="required" name="nick" class="form-control" placeholder="' +
__e(o.label_nickname) +
'"/>\n </fieldset>\n <input type="submit" class="btn btn-primary" name="join" value="' +
__e(o.label_join) +
'"/>\n </form>\n</div>\n';
return __p
};});
2018-03-25 21:21:43 +02:00
define('tpl!chatroom_password_form', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<div class="chatroom-form-container">\n <form class="pure-form converse-form chatroom-form">\n <fieldset class="form-group">\n <legend>' +
__e(o.heading) +
'</legend>\n <label>' +
__e(o.label_password) +
'</label>\n <input type="password" name="password"/>\n </fieldset>\n <input class="btn btn-primary" type="submit" value="' +
__e(o.label_submit) +
'"/>\n </form>\n</div>\n';
return __p
};});
2018-03-25 21:21:43 +02:00
2018-02-14 16:53:07 +01:00
define('tpl!chatroom_sidebar', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<!-- <div class="occupants"> -->\n<p class="occupants-heading">' +
__e(o.label_occupants) +
'</p>\n<ul class="occupant-list"></ul>\n<div class="chatroom-features"></div>\n<!-- </div> -->\n';
return __p
};});
2018-02-14 16:53:07 +01:00
define('tpl!chatroom_toolbar', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
2018-02-14 16:53:07 +01:00
if (o.use_emoji) { ;
2018-05-15 10:32:13 +02:00
__p += '\n<li class="toggle-toolbar-menu toggle-smiley dropup">\n <a class="toggle-smiley fa fa-smile-o" title="' +
__e(o.label_insert_smiley) +
'" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></a> \n <div class="emoji-picker dropdown-menu toolbar-menu"></div>\n</li>\n';
} ;
__p += '\n';
if (o.show_call_button) { ;
__p += '\n<li class="toggle-call fa fa-phone" title="' +
__e(o.label_start_call) +
'"></li>\n';
} ;
__p += '\n';
if (o.show_occupants_toggle) { ;
__p += '\n<li class="toggle-occupants fa fa-users" title="' +
__e(o.label_hide_occupants) +
'"></li>\n';
} ;
__p += '\n';
return __p
};});
2018-02-14 16:53:07 +01:00
define('tpl!list_chatrooms_modal', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<div class="modal fade" id="list-chatrooms-modal" tabindex="-1" role="dialog" aria-labelledby="list-chatrooms-modal-label" aria-hidden="true">\n <div class="modal-dialog" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title"\n id="list-chatrooms-modal-label">' +
__e(o.heading_list_chatrooms) +
'</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">&times;</span>\n </button>\n </div>\n <div class="modal-body">\n <form class="converse-form list-chatrooms">\n <div class="form-group">\n <label for="chatroom">' +
__e(o.label_server_address) +
':</label>\n <input type="text" value="' +
__e(o.muc_domain) +
'" required="required" name="server" class="form-control" placeholder="' +
__e(o.server_placeholder) +
'">\n </div>\n <input type="submit" class="btn btn-primary" name="join" value="' +
__e(o.label_query) +
'">\n </form>\n <ul class="available-chatrooms list-group"></ul>\n </div>\n </div>\n </div>\n</div>\n';
return __p
};});
define('tpl!occupant', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
2018-05-09 14:37:27 +02:00
__p += '<li class="occupant" id="' +
__e( o.id ) +
'"\n ';
if (o.role === "moderator") { ;
__p += '\n title="' +
__e( o.jid ) +
' ' +
__e( o.desc_moderator ) +
' ' +
__e( o.hint_occupant ) +
'"\n ';
} ;
__p += '\n ';
2018-05-09 14:37:27 +02:00
if (o.role === "participant") { ;
__p += '\n title="' +
__e( o.jid ) +
' ' +
2018-05-09 14:37:27 +02:00
__e( o.desc_participant ) +
' ' +
__e( o.hint_occupant ) +
'"\n ';
} ;
__p += '\n ';
if (o.role === "visitor") { ;
__p += '\n title="' +
__e( o.jid ) +
' ' +
__e( o.desc_visitor ) +
' ' +
__e( o.hint_occupant ) +
'"\n ';
} ;
__p += '\n ';
2018-05-09 14:37:27 +02:00
if (!_.includes(["visitor", "participant", "moderator"], o.role)) { ;
__p += '\n title="' +
__e( o.jid ) +
' ' +
__e( o.hint_occupant ) +
2018-05-09 14:37:27 +02:00
'"\n ';
} ;
2018-05-09 14:37:27 +02:00
__p += '>\n <div class="row no-gutters">\n <div class="col-auto">\n <div class="occupant-status occupant-' +
__e(o.show) +
' circle" title="' +
__e(o.hint_show) +
2018-05-09 14:37:27 +02:00
'"></div>\n </div>\n <div class="col">\n <span class="occupant-nick">' +
__e(o.nick || o.jid) +
'</span>\n ';
if (o.affiliation === "owner") { ;
__p += '\n <span class="badge badge-danger">' +
__e(o.label_owner) +
'</span>\n ';
} ;
__p += '\n ';
if (o.affiliation === "admin") { ;
__p += '\n <span class="badge badge-info">' +
__e(o.label_admin) +
'</span>\n ';
} ;
__p += '\n ';
if (o.affiliation === "member") { ;
__p += '\n <span class="badge badge-info">' +
__e(o.label_member) +
'</span>\n ';
} ;
__p += '\n\n ';
if (o.role === "moderator") { ;
__p += '\n <span class="badge badge-info">' +
__e(o.label_moderator) +
'</span>\n ';
} ;
__p += '\n ';
if (o.role === "visitor") { ;
__p += '\n <span class="badge badge-secondary">' +
__e(o.label_visitor) +
'</span>\n ';
} ;
__p += '\n </div>\n </div>\n</li>\n';
return __p
};});
define('tpl!room_description', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<!-- FIXME: check markup in mockup -->\n<div class="room-info">\n<p class="room-info"><strong>' +
__e(o.label_jid) +
'</strong> ' +
__e(o.jid) +
'</p>\n<p class="room-info"><strong>' +
__e(o.label_desc) +
'</strong> ' +
__e(o.desc) +
'</p>\n<p class="room-info"><strong>' +
__e(o.label_occ) +
'</strong> ' +
__e(o.occ) +
'</p>\n<p class="room-info"><strong>' +
__e(o.label_features) +
'</strong>\n <ul>\n ';
if (o.passwordprotected) { ;
__p += '\n <li class="room-info locked">' +
__e(o.label_requires_auth) +
'</li>\n ';
} ;
__p += '\n ';
if (o.hidden) { ;
__p += '\n <li class="room-info">' +
__e(o.label_hidden) +
'</li>\n ';
} ;
__p += '\n ';
if (o.membersonly) { ;
__p += '\n <li class="room-info">' +
__e(o.label_requires_invite) +
'</li>\n ';
} ;
__p += '\n ';
if (o.moderated) { ;
__p += '\n <li class="room-info">' +
__e(o.label_moderated) +
'</li>\n ';
} ;
__p += '\n ';
if (o.nonanonymous) { ;
__p += '\n <li class="room-info">' +
__e(o.label_non_anon) +
'</li>\n ';
} ;
__p += '\n ';
if (o.open) { ;
__p += '\n <li class="room-info">' +
__e(o.label_open_room) +
'</li>\n ';
} ;
__p += '\n ';
if (o.persistent) { ;
__p += '\n <li class="room-info">' +
__e(o.label_permanent_room) +
'</li>\n ';
} ;
__p += '\n ';
if (o.publicroom) { ;
__p += '\n <li class="room-info">' +
__e(o.label_public) +
'</li>\n ';
} ;
__p += '\n ';
if (o.semianonymous) { ;
__p += '\n <li class="room-info">' +
__e(o.label_semi_anon) +
'</li>\n ';
} ;
__p += '\n ';
if (o.temporary) { ;
__p += '\n <li class="room-info">' +
__e(o.label_temp_room) +
'</li>\n ';
} ;
__p += '\n ';
if (o.unmoderated) { ;
__p += '\n <li class="room-info">' +
__e(o.label_unmoderated) +
'</li>\n ';
} ;
__p += '\n </ul>\n</p>\n</div>\n';
return __p
};});
2016-03-16 12:49:35 +01:00
define('tpl!room_item', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<li class="room-item list-group-item">\n <div class="available-chatroom d-flex flex-row">\n <a class="open-room available-room w-100"\n data-room-jid="' +
__e(o.jid) +
'"\n data-room-name="' +
__e(o.name) +
'"\n title="' +
__e(o.open_title) +
'"\n href="#">' +
__e(o.name) +
'</a>\n <a class="right room-info icon-room-info"\n data-room-jid="' +
__e(o.jid) +
'"\n title="' +
__e(o.info_title) +
'" href="#">&nbsp;</a>\n </div>\n</li>\n';
return __p
};});
define('tpl!room_panel', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
2018-05-23 04:27:33 +02:00
__p += '<!-- <div id="chatrooms"> -->\n<div class="d-flex controlbox-padded">\n <span class="w-100 controlbox-heading">' +
__e(o.heading_chatrooms) +
'</span>\n <a class="chatbox-btn trigger-list-chatrooms-modal fa fa-list-ul" title="' +
__e(o.title_list_rooms) +
'" data-toggle="modal" data-target="#list-chatrooms-modal"></a>\n <a class="chatbox-btn trigger-add-chatrooms-modal fa fa-users" title="' +
__e(o.title_new_room) +
'" data-toggle="modal" data-target="#add-chatrooms-modal"></a>\n</div>\n<div class="list-container open-rooms-list rooms-list-container"></div>\n<div class="list-container bookmarks-list rooms-list-container"></div>\n<!-- </div> -->\n';
return __p
};});
define('tpl!rooms_results', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape;
__p += '<li class="list-group-item active">' +
__e( o.feedback_text ) +
'</dt>\n';
return __p
};});
// Converse.js
2016-11-07 15:43:48 +01:00
// http://conversejs.org
//
// Copyright (c) 2012-2018, the Converse.js developers
2016-11-07 15:43:48 +01:00
// Licensed under the Mozilla Public License (MPLv2)
2016-11-07 15:43:48 +01:00
(function (root, factory) {
define('converse-muc-views',[
"converse-core",
"muc-utils",
"tpl!add_chatroom_modal",
"tpl!chatarea",
"tpl!chatroom",
"tpl!chatroom_disconnect",
"tpl!chatroom_features",
"tpl!chatroom_form",
"tpl!chatroom_head",
"tpl!chatroom_invite",
"tpl!chatroom_nickname_form",
"tpl!chatroom_password_form",
"tpl!chatroom_sidebar",
"tpl!chatroom_toolbar",
"tpl!info",
"tpl!list_chatrooms_modal",
"tpl!occupant",
"tpl!room_description",
"tpl!room_item",
"tpl!room_panel",
"tpl!rooms_results",
"tpl!spinner",
"awesomplete",
"converse-modal"
], factory);
}(this, function (
converse,
muc_utils,
tpl_add_chatroom_modal,
tpl_chatarea,
tpl_chatroom,
tpl_chatroom_disconnect,
tpl_chatroom_features,
tpl_chatroom_form,
tpl_chatroom_head,
tpl_chatroom_invite,
tpl_chatroom_nickname_form,
tpl_chatroom_password_form,
tpl_chatroom_sidebar,
tpl_chatroom_toolbar,
tpl_info,
tpl_list_chatrooms_modal,
tpl_occupant,
tpl_room_description,
tpl_room_item,
tpl_room_panel,
tpl_rooms_results,
tpl_spinner,
Awesomplete
) {
"use strict";
2017-07-22 22:21:05 +02:00
const { Backbone, Promise, Strophe, b64_sha1, moment, f, sizzle, _, $build, $iq, $msg, $pres } = converse.env;
const u = converse.env.utils;
const ROOM_FEATURES_MAP = {
'passwordprotected': 'unsecured',
'unsecured': 'passwordprotected',
'hidden': 'publicroom',
'publicroom': 'hidden',
'membersonly': 'open',
'open': 'membersonly',
'persistent': 'temporary',
'temporary': 'persistent',
'nonanonymous': 'semianonymous',
'semianonymous': 'nonanonymous',
'moderated': 'unmoderated',
'unmoderated': 'moderated'
};
converse.plugins.add('converse-muc-views', {
/* Dependencies are other plugins which might be
* overridden or relied upon, and therefore need to be loaded before
* this plugin. They are "optional" because they might not be
* available, in which case any overrides applicable to them will be
* ignored.
*
* NB: These plugins need to have already been loaded via require.js.
*
* It's possible to make these dependencies "non-optional".
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found.
*/
dependencies: ["converse-modal", "converse-controlbox", "converse-chatview"],
overrides: {
ControlBoxView: {
renderRoomsPanel () {
const { _converse } = this.__super__;
this.roomspanel = new _converse.RoomsPanel({
'model': new (_converse.RoomsPanelModel.extend({
2018-05-07 18:20:15 +02:00
'id': b64_sha1(`converse.roomspanel${_converse.bare_jid}`), // Required by sessionStorage
'browserStorage': new Backbone.BrowserStorage[_converse.storage](
b64_sha1(`converse.roomspanel${_converse.bare_jid}`))
}))()
});
this.roomspanel.model.fetch();
this.el.querySelector('.controlbox-pane').insertAdjacentElement(
'beforeEnd', this.roomspanel.render().el);
if (!this.roomspanel.model.get('nick')) {
this.roomspanel.model.save({
nick: _converse.xmppstatus.get('nickname') || Strophe.getNodeFromJid(_converse.bare_jid)
});
}
_converse.emit('roomsPanelRendered');
},
renderControlBoxPane () {
const { _converse } = this.__super__;
this.__super__.renderControlBoxPane.apply(this, arguments);
if (_converse.allow_muc) {
this.renderRoomsPanel();
}
},
},
ChatBoxViews: {
onChatBoxAdded (item) {
const { _converse } = this.__super__;
let view = this.get(item.get('id'));
if (!view && item.get('type') === converse.CHATROOMS_TYPE) {
view = new _converse.ChatRoomView({'model': item});
return this.add(item.get('id'), view);
} else {
return this.__super__.onChatBoxAdded.apply(this, arguments);
}
}
}
},
2017-07-22 22:21:05 +02:00
initialize () {
const { _converse } = this,
{ __ } = _converse;
_converse.api.promises.add(['roomsPanelRendered']);
// Configuration values for this plugin
// ====================================
// Refer to docs/source/configuration.rst for explanations of these
// configuration settings.
_converse.api.settings.update({
auto_list_rooms: false,
hide_muc_server: false, // TODO: no longer implemented...
muc_disable_moderator_commands: false,
visible_toolbar_buttons: {
'toggle_occupants': true
}
});
2017-07-22 22:21:05 +02:00
function ___ (str) {
/* This is part of a hack to get gettext to scan strings to be
* 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 further below.
*/
return str;
}
/* http://xmpp.org/extensions/xep-0045.html
* ----------------------------------------
* 100 message Entering a room Inform user that any occupant is allowed to see the user's full JID
* 101 message (out of band) Affiliation change Inform user that his or her affiliation changed while not in the room
* 102 message Configuration change Inform occupants that room now shows unavailable members
* 103 message Configuration change Inform occupants that room now does not show unavailable members
* 104 message Configuration change Inform occupants that a non-privacy-related room configuration change has occurred
* 110 presence Any room presence Inform user that presence refers to one of its own room occupants
* 170 message or initial presence Configuration change Inform occupants that room logging is now enabled
* 171 message Configuration change Inform occupants that room logging is now disabled
* 172 message Configuration change Inform occupants that the room is now non-anonymous
* 173 message Configuration change Inform occupants that the room is now semi-anonymous
* 174 message Configuration change Inform occupants that the room is now fully-anonymous
* 201 presence Entering a room Inform user that a new room has been created
* 210 presence Entering a room Inform user that the service has assigned or modified the occupant's roomnick
* 301 presence Removal from room Inform user that he or she has been banned from the room
* 303 presence Exiting a room Inform all occupants of new room nickname
* 307 presence Removal from room Inform user that he or she has been kicked from the room
* 321 presence Removal from room Inform user that he or she is being removed from the room because of an affiliation change
* 322 presence Removal from room Inform user that he or she is being removed from the room because the room has been changed to members-only and the user is not a member
* 332 presence Removal from room Inform user that he or she is being removed from the room because of a system shutdown
*/
_converse.muc = {
info_messages: {
100: __('This room is not anonymous'),
102: __('This room now shows unavailable members'),
103: __('This room does not show unavailable members'),
104: __('The room configuration has changed'),
170: __('Room logging is now enabled'),
171: __('Room logging is now disabled'),
172: __('This room is now no longer anonymous'),
173: __('This room is now semi-anonymous'),
174: __('This room is now fully-anonymous'),
201: __('A new room has been created')
},
2017-07-22 22:21:05 +02:00
disconnect_messages: {
301: __('You have been banned from this room'),
307: __('You have been kicked from this room'),
321: __("You have been removed from this room because of an affiliation change"),
322: __("You have been removed from this room because the room has changed to members-only and you're not a member"),
332: __("You have been removed from this room because the MUC (Multi-user chat) service is being shut down")
},
action_info_messages: {
/* XXX: Note the triple underscore function and not double
* underscore.
*
* This is a hack. We can't pass the strings to __ because we
* don't yet know what the variable to interpolate is.
*
* Triple underscore will just return the string again, but we
* can then at least tell gettext to scan for it so that these
* strings are picked up by the translation machinery.
*/
301: ___("%1$s has been banned"),
303: ___("%1$s's nickname has changed"),
307: ___("%1$s has been kicked out"),
321: ___("%1$s has been removed because of an affiliation change"),
322: ___("%1$s has been removed for not being a member")
},
new_nickname_messages: {
210: ___('Your nickname has been automatically set to %1$s'),
303: ___('Your nickname has been changed to %1$s')
}
};
function insertRoomInfo (el, stanza) {
/* Insert room info (based on returned #disco IQ stanza)
*
* Parameters:
* (HTMLElement) el: The HTML DOM element that should
* contain the info.
* (XMLElement) stanza: The IQ stanza containing the room
* info.
*/
// All MUC features found here: http://xmpp.org/registrar/disco-features.html
el.querySelector('span.spinner').remove();
el.querySelector('a.room-info').classList.add('selected');
el.insertAdjacentHTML(
'beforeEnd',
tpl_room_description({
'jid': stanza.getAttribute('from'),
'desc': _.get(_.head(sizzle('field[var="muc#roominfo_description"] value', stanza)), 'textContent'),
'occ': _.get(_.head(sizzle('field[var="muc#roominfo_occupants"] value', stanza)), 'textContent'),
'hidden': sizzle('feature[var="muc_hidden"]', stanza).length,
'membersonly': sizzle('feature[var="muc_membersonly"]', stanza).length,
'moderated': sizzle('feature[var="muc_moderated"]', stanza).length,
'nonanonymous': sizzle('feature[var="muc_nonanonymous"]', stanza).length,
'open': sizzle('feature[var="muc_open"]', stanza).length,
'passwordprotected': sizzle('feature[var="muc_passwordprotected"]', stanza).length,
'persistent': sizzle('feature[var="muc_persistent"]', stanza).length,
'publicroom': sizzle('feature[var="muc_publicroom"]', stanza).length,
'semianonymous': sizzle('feature[var="muc_semianonymous"]', stanza).length,
'temporary': sizzle('feature[var="muc_temporary"]', stanza).length,
'unmoderated': sizzle('feature[var="muc_unmoderated"]', stanza).length,
'label_desc': __('Description:'),
'label_jid': __('Room Address (JID):'),
'label_occ': __('Occupants:'),
'label_features': __('Features:'),
'label_requires_auth': __('Requires authentication'),
'label_hidden': __('Hidden'),
'label_requires_invite': __('Requires an invitation'),
'label_moderated': __('Moderated'),
'label_non_anon': __('Non-anonymous'),
'label_open_room': __('Open room'),
'label_permanent_room': __('Permanent room'),
'label_public': __('Public'),
'label_semi_anon': __('Semi-anonymous'),
'label_temp_room': __('Temporary room'),
'label_unmoderated': __('Unmoderated')
}));
}
function toggleRoomInfo (ev) {
/* Show/hide extra information about a room in a listing. */
const parent_el = u.ancestor(ev.target, '.room-item'),
div_el = parent_el.querySelector('div.room-info');
if (div_el) {
u.slideIn(div_el).then(u.removeElement)
parent_el.querySelector('a.room-info').classList.remove('selected');
} else {
parent_el.insertAdjacentHTML('beforeend', tpl_spinner());
2018-05-07 18:20:15 +02:00
_converse.api.disco.info(
ev.target.getAttribute('data-room-jid'),
null,
_.partial(insertRoomInfo, parent_el)
);
}
}
_converse.ListChatRoomsModal = _converse.BootstrapModal.extend({
2016-11-07 15:43:48 +01:00
events: {
'submit form': 'showRooms',
'click a.room-info': 'toggleRoomInfo',
'change input[name=nick]': 'setNick',
'change input[name=server]': 'setDomain',
'click .open-room': 'openRoom'
},
initialize () {
_converse.BootstrapModal.prototype.initialize.apply(this, arguments);
this.model.on('change:muc_domain', this.onDomainChange, this);
},
toHTML () {
return tpl_list_chatrooms_modal(_.extend(this.model.toJSON(), {
'heading_list_chatrooms': __('Query for Chatrooms'),
'label_server_address': __('Server address'),
'label_query': __('Show rooms'),
'server_placeholder': __('conference.example.org')
}));
},
afterRender () {
this.el.addEventListener('shown.bs.modal', () => {
this.el.querySelector('input[name="server"]').focus();
}, false);
},
openRoom (ev) {
ev.preventDefault();
const jid = ev.target.getAttribute('data-room-jid');
const name = ev.target.getAttribute('data-room-name');
this.modal.hide();
_converse.api.rooms.open(jid, {'name': name});
},
toggleRoomInfo (ev) {
ev.preventDefault();
toggleRoomInfo(ev);
},
onDomainChange (model) {
if (_converse.auto_list_rooms) {
this.updateRoomsList();
}
},
roomStanzaItemToHTMLElement (room) {
const name = Strophe.unescapeNode(
room.getAttribute('name') ||
room.getAttribute('jid')
);
const div = document.createElement('div');
div.innerHTML = tpl_room_item({
'name': Strophe.xmlunescape(name),
'jid': room.getAttribute('jid'),
'open_title': __('Click to open this room'),
'info_title': __('Show more information on this room')
});
return div.firstChild;
},
removeSpinner () {
_.each(this.el.querySelectorAll('span.spinner'),
(el) => el.parentNode.removeChild(el)
);
},
informNoRoomsFound () {
const chatrooms_el = this.el.querySelector('.available-chatrooms');
chatrooms_el.innerHTML = tpl_rooms_results({
'feedback_text': __('No rooms found')
});
const input_el = this.el.querySelector('input[name="server"]');
input_el.classList.remove('hidden')
this.removeSpinner();
},
onRoomsFound (iq) {
/* Handle the IQ stanza returned from the server, containing
* all its public rooms.
*/
const available_chatrooms = this.el.querySelector('.available-chatrooms');
this.rooms = iq.querySelectorAll('query item');
if (this.rooms.length) {
// For translators: %1$s is a variable and will be
// replaced with the XMPP server name
available_chatrooms.innerHTML = tpl_rooms_results({
'feedback_text': __('Rooms found:')
});
const fragment = document.createDocumentFragment();
const children = _.reject(_.map(this.rooms, this.roomStanzaItemToHTMLElement), _.isNil)
_.each(children, (child) => fragment.appendChild(child));
available_chatrooms.appendChild(fragment);
this.removeSpinner();
} else {
this.informNoRoomsFound();
}
return true;
},
updateRoomsList () {
/* Send an IQ stanza to the server asking for all rooms
*/
_converse.connection.sendIQ(
$iq({
to: this.model.get('muc_domain'),
from: _converse.connection.jid,
type: "get"
}).c("query", {xmlns: Strophe.NS.DISCO_ITEMS}),
this.onRoomsFound.bind(this),
this.informNoRoomsFound.bind(this),
5000
);
},
2016-11-07 15:43:48 +01:00
showRooms (ev) {
ev.preventDefault();
const data = new FormData(ev.target);
this.model.save('muc_domain', data.get('server'));
this.updateRoomsList();
},
setDomain (ev) {
this.model.save({muc_domain: ev.target.value});
},
setNick (ev) {
this.model.save({nick: ev.target.value});
}
});
_converse.AddChatRoomModal = _converse.BootstrapModal.extend({
events: {
'submit form.add-chatroom': 'openChatRoom'
},
toHTML () {
return tpl_add_chatroom_modal(_.extend(this.model.toJSON(), {
'heading_new_chatroom': __('Enter a new Chatroom'),
'label_room_address': __('Room address'),
'label_nickname': __('Optional nickname'),
'chatroom_placeholder': __('name@conference.example.org'),
'label_join': __('Join'),
}));
},
afterRender () {
this.el.addEventListener('shown.bs.modal', () => {
this.el.querySelector('input[name="chatroom"]').focus();
}, false);
},
parseRoomDataFromEvent (form) {
const data = new FormData(form);
const jid = data.get('chatroom');
const server = Strophe.getDomainFromJid(jid);
this.model.save('muc_domain', server);
return {
'jid': jid,
'nick': data.get('nickname')
}
},
2017-06-23 20:25:33 +02:00
openChatRoom (ev) {
ev.preventDefault();
const data = this.parseRoomDataFromEvent(ev.target);
_converse.api.rooms.open(data.jid, data);
this.modal.hide();
ev.target.reset();
}
});
2017-06-23 20:25:33 +02:00
2016-11-07 15:43:48 +01:00
_converse.ChatRoomView = _converse.ChatBoxView.extend({
/* Backbone.NativeView which renders a chat room, based upon the view
* for normal one-on-one chat boxes.
*/
length: 300,
tagName: 'div',
className: 'chatbox chatroom hidden',
is_chatroom: true,
events: {
'change input.fileupload': 'onFileSelection',
'click .close-chatbox-button': 'close',
'click .configure-chatroom-button': 'getAndRenderConfigurationForm',
'click .new-msgs-indicator': 'viewUnreadMessages',
2018-05-11 00:19:49 +02:00
'click .occupant-nick': 'onOccupantClicked',
'click .send-button': 'onFormSubmitted',
'click .toggle-call': 'toggleCall',
'click .toggle-occupants': 'toggleOccupants',
'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
'click .toggle-smiley': 'toggleEmojiMenu',
'click .upload-file': 'toggleFileUpload',
'keypress .chat-textarea': 'keyPressed',
'input .chat-textarea': 'inputChanged'
},
2016-11-07 15:43:48 +01:00
initialize () {
2018-04-30 16:05:20 +02:00
this.initDebounced();
2017-07-22 22:21:05 +02:00
this.model.messages.on('add', this.onMessageAdded, this);
this.model.messages.on('rendered', this.scrollDown, this);
2017-06-23 20:25:33 +02:00
this.model.on('change:affiliation', this.renderHeading, this);
this.model.on('change:connection_status', this.afterConnected, this);
this.model.on('change:description', this.renderHeading, this);
this.model.on('change:name', this.renderHeading, this);
this.model.on('change:subject', this.setChatRoomSubject, this);
this.model.on('configurationNeeded', this.getAndRenderConfigurationForm, this);
this.model.on('destroy', this.hide, this);
this.model.on('show', this.show, this);
2017-06-23 20:25:33 +02:00
this.model.occupants.on('add', this.showJoinNotification, this);
this.model.occupants.on('remove', this.showLeaveNotification, this);
2017-06-23 20:25:33 +02:00
this.createEmojiPicker();
this.createOccupantsView();
this.render().insertIntoDOM();
this.registerHandlers();
if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
const handler = () => {
2018-04-30 16:05:20 +02:00
if (!u.isPersistableModel(this.model)) {
// Happens during tests, nothing to do if this
// is a hanging chatbox (i.e. not in the
// collection anymore).
return;
}
2018-05-09 14:37:27 +02:00
this.populateAndJoin();
_converse.emit('chatRoomOpened', this);
}
this.model.getRoomFeatures().then(handler, handler);
} else {
this.fetchMessages();
_converse.emit('chatRoomOpened', this);
}
},
2017-06-23 20:25:33 +02:00
render () {
this.el.setAttribute('id', this.model.get('box_id'));
this.el.innerHTML = tpl_chatroom();
this.renderHeading();
this.renderChatArea();
if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
this.showSpinner();
}
return this;
},
2017-06-23 20:25:33 +02:00
renderHeading () {
/* Render the heading UI of the chat room. */
this.el.querySelector('.chat-head-chatroom').innerHTML = this.generateHeadingHTML();
},
renderChatArea () {
/* Render the UI container in which chat room messages will appear.
*/
if (_.isNull(this.el.querySelector('.chat-area'))) {
const container_el = this.el.querySelector('.chatroom-body');
container_el.innerHTML = tpl_chatarea({
'label_message': __('Message'),
'label_send': __('Send'),
'show_send_button': _converse.show_send_button,
'show_toolbar': _converse.show_toolbar,
'unread_msgs': __('You have unread messages')
});
container_el.insertAdjacentElement('beforeend', this.occupantsview.el);
this.renderToolbar(tpl_chatroom_toolbar);
this.content = this.el.querySelector('.chat-content');
this.toggleOccupants(null, true);
}
return this;
},
2018-02-14 16:53:07 +01:00
showChatStateNotification (message) {
if (message.get('sender') === 'me') {
return;
}
return _converse.ChatBoxView.prototype.showChatStateNotification.apply(this, arguments);
},
2018-02-14 16:53:07 +01:00
createOccupantsView () {
/* Create the ChatRoomOccupantsView Backbone.NativeView
*/
this.model.occupants.chatroomview = this;
this.occupantsview = new _converse.ChatRoomOccupantsView({'model': this.model.occupants});
this.occupantsview.model.on('change:role', this.informOfOccupantsRoleChange, this);
return this;
},
2018-02-14 16:53:07 +01:00
informOfOccupantsRoleChange (occupant, changed) {
const previous_role = occupant._previousAttributes.role;
if (previous_role === 'moderator') {
this.showChatEvent(__("%1$s is no longer a moderator", occupant.get('nick')))
}
if (previous_role === 'visitor') {
this.showChatEvent(__("%1$s has been given a voice again", occupant.get('nick')))
}
if (occupant.get('role') === 'visitor') {
this.showChatEvent(__("%1$s has been muted", occupant.get('nick')))
}
if (occupant.get('role') === 'moderator') {
this.showChatEvent(__("%1$s is now a moderator", occupant.get('nick')))
}
},
generateHeadingHTML () {
/* Returns the heading HTML to be rendered.
*/
return tpl_chatroom_head(
_.extend(this.model.toJSON(), {
Strophe: Strophe,
info_close: __('Close and leave this room'),
info_configure: __('Configure this room'),
description: this.model.get('description') || ''
}));
},
2017-06-23 20:25:33 +02:00
afterShown () {
/* Override from converse-chatview, specifically to avoid
* the 'active' chat state from being sent out prematurely.
*
* This is instead done in `afterConnected` below.
*/
if (u.isPersistableModel(this.model)) {
this.model.clearUnreadMsgCounter();
this.model.save();
}
this.occupantsview.setOccupantsHeight();
this.scrollDown();
this.renderEmojiPicker();
},
show () {
if (u.isVisible(this.el)) {
this.focus();
return;
}
// Override from converse-chatview in order to not use
// "fadeIn", which causes flashing.
u.showElement(this.el);
this.afterShown();
},
2017-07-22 22:21:05 +02:00
afterConnected () {
if (this.model.get('connection_status') === converse.ROOMSTATUS.ENTERED) {
this.hideSpinner();
this.setChatState(_converse.ACTIVE);
this.scrollDown();
this.focus();
}
},
2017-06-23 20:25:33 +02:00
getToolbarOptions () {
return _.extend(
_converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments),
{
label_hide_occupants: __('Hide the list of occupants'),
show_occupants_toggle: this.is_chatroom && _converse.visible_toolbar_buttons.toggle_occupants
}
);
},
2017-06-23 20:25:33 +02:00
close (ev) {
/* Close this chat box, which implies leaving the room as
* well.
*/
this.hide();
if (Backbone.history.getFragment() === "converse/room?jid="+this.model.get('jid')) {
_converse.router.navigate('');
}
this.model.leave();
_converse.ChatBoxView.prototype.close.apply(this, arguments);
},
2017-06-23 20:25:33 +02:00
setOccupantsVisibility () {
const icon_el = this.el.querySelector('.toggle-occupants');
if (this.model.get('hidden_occupants')) {
this.el.querySelector('.chat-area').classList.add('full');
u.hideElement(this.el.querySelector('.occupants'));
} else {
this.el.querySelector('.chat-area').classList.remove('full');
this.el.querySelector('.occupants').classList.remove('hidden');
}
this.occupantsview.setOccupantsHeight();
},
toggleOccupants (ev, preserve_state) {
/* Show or hide the right sidebar containing the chat
* occupants (and the invite widget).
*/
if (ev) {
ev.preventDefault();
ev.stopPropagation();
}
if (!preserve_state) {
this.model.set({'hidden_occupants': !this.model.get('hidden_occupants')});
}
this.setOccupantsVisibility();
this.scrollDown();
},
onOccupantClicked (ev) {
/* When an occupant is clicked, insert their nickname into
* the chat textarea input.
*/
this.insertIntoTextArea(ev.target.textContent);
},
2017-07-05 11:59:55 +02:00
handleChatStateNotification (message) {
/* Override the method on the ChatBoxView base class to
* ignore <gone/> notifications in groupchats.
*
* As laid out in the business rules in XEP-0085
* http://xmpp.org/extensions/xep-0085.html#bizrules-groupchat
*/
if (message.get('fullname') === this.model.get('nick')) {
// Don't know about other servers, but OpenFire sends
// back to you your own chat state notifications.
// We ignore them here...
return;
}
if (message.get('chat_state') !== _converse.GONE) {
_converse.ChatBoxView.prototype.handleChatStateNotification.apply(this, arguments);
}
},
2017-06-23 20:25:33 +02:00
modifyRole(room, nick, role, reason, onSuccess, onError) {
const item = $build("item", {nick, role});
const iq = $iq({to: room, type: "set"}).c("query", {xmlns: Strophe.NS.MUC_ADMIN}).cnode(item.node);
if (reason !== null) { iq.c("reason", reason); }
return _converse.connection.sendIQ(iq, onSuccess, onError);
},
2018-02-14 16:53:07 +01:00
validateRoleChangeCommand (command, args) {
/* Check that a command to change a chat room user's role or
* affiliation has anough arguments.
*/
// TODO check if first argument is valid
if (args.length < 1 || args.length > 2) {
this.showErrorMessage(
__('Error: the "%1$s" command takes two arguments, the user\'s nickname and optionally a reason.',
command),
true
);
return false;
}
return true;
},
2018-05-11 00:19:49 +02:00
onCommandError (err) {
_converse.log(err, Strophe.LogLevel.FATAL);
this.showErrorMessage(
__("Sorry, an error happened while running the command. Check your browser's developer console for details."),
true
);
},
parseMessageForCommands (text) {
const _super_ = _converse.ChatBoxView.prototype;
if (_super_.parseMessageForCommands.apply(this, arguments)) {
return true;
}
if (_converse.muc_disable_moderator_commands) {
return false;
}
const match = text.replace(/^\s*/, "").match(/^\/(.*?)(?: (.*))?$/) || [false, '', ''],
args = match[2] && match[2].splitOnce(' ') || [],
command = match[1].toLowerCase();
switch (command) {
case 'admin':
if (!this.validateRoleChangeCommand(command, args)) { break; }
this.model.setAffiliation('admin',
[{ 'jid': args[0],
'reason': args[1]
2018-05-11 00:19:49 +02:00
}]).then(
() => this.model.occupants.fetchMembers(),
(err) => this.onCommandError(err)
);
break;
case 'ban':
if (!this.validateRoleChangeCommand(command, args)) { break; }
this.model.setAffiliation('outcast',
[{ 'jid': args[0],
'reason': args[1]
2018-05-11 00:19:49 +02:00
}]).then(
() => this.model.occupants.fetchMembers(),
(err) => this.onCommandError(err)
);
break;
case 'deop':
if (!this.validateRoleChangeCommand(command, args)) { break; }
this.modifyRole(
this.model.get('jid'), args[0], 'participant', args[1],
undefined, this.onCommandError.bind(this));
break;
case 'help':
this.showHelpMessages([
`<strong>/admin</strong>: ${__("Change user's affiliation to admin")}`,
`<strong>/ban</strong>: ${__('Ban user from room')}`,
`<strong>/clear</strong>: ${__('Remove messages')}`,
`<strong>/deop</strong>: ${__('Change user role to participant')}`,
`<strong>/help</strong>: ${__('Show this menu')}`,
`<strong>/kick</strong>: ${__('Kick user from room')}`,
`<strong>/me</strong>: ${__('Write in 3rd person')}`,
`<strong>/member</strong>: ${__('Grant membership to a user')}`,
`<strong>/mute</strong>: ${__("Remove user's ability to post messages")}`,
`<strong>/nick</strong>: ${__('Change your nickname')}`,
`<strong>/op</strong>: ${__('Grant moderator role to user')}`,
`<strong>/owner</strong>: ${__('Grant ownership of this room')}`,
`<strong>/revoke</strong>: ${__("Revoke user's membership")}`,
`<strong>/subject</strong>: ${__('Set room subject')}`,
`<strong>/topic</strong>: ${__('Set room subject (alias for /subject)')}`,
`<strong>/voice</strong>: ${__('Allow muted user to post messages')}`
]);
break;
case 'kick':
if (!this.validateRoleChangeCommand(command, args)) { break; }
this.modifyRole(
this.model.get('jid'), args[0], 'none', args[1],
undefined, this.onCommandError.bind(this));
break;
case 'mute':
if (!this.validateRoleChangeCommand(command, args)) { break; }
this.modifyRole(
this.model.get('jid'), args[0], 'visitor', args[1],
undefined, this.onCommandError.bind(this));
break;
case 'member':
if (!this.validateRoleChangeCommand(command, args)) { break; }
this.model.setAffiliation('member',
[{ 'jid': args[0],
'reason': args[1]
2018-05-11 00:19:49 +02:00
}]).then(
() => this.model.occupants.fetchMembers(),
(err) => this.onCommandError(err)
);
break;
case 'nick':
_converse.connection.send($pres({
from: _converse.connection.jid,
to: this.model.getRoomJIDAndNick(match[2]),
id: _converse.connection.getUniqueId()
}).tree());
break;
case 'owner':
if (!this.validateRoleChangeCommand(command, args)) { break; }
this.model.setAffiliation('owner',
[{ 'jid': args[0],
'reason': args[1]
2018-05-11 00:19:49 +02:00
}]).then(
() => this.model.occupants.fetchMembers(),
(err) => this.onCommandError(err)
);
break;
case 'op':
if (!this.validateRoleChangeCommand(command, args)) { break; }
this.modifyRole(
this.model.get('jid'), args[0], 'moderator', args[1],
undefined, this.onCommandError.bind(this));
break;
case 'revoke':
if (!this.validateRoleChangeCommand(command, args)) { break; }
this.model.setAffiliation('none',
[{ 'jid': args[0],
'reason': args[1]
2018-05-11 00:19:49 +02:00
}]).then(
() => this.model.occupants.fetchMembers(),
(err) => this.onCommandError(err)
);
break;
case 'topic':
case 'subject':
_converse.connection.send(
$msg({
to: this.model.get('jid'),
from: _converse.connection.jid,
type: "groupchat"
}).c("subject", {xmlns: "jabber:client"}).t(match[2]).tree()
);
break;
case 'voice':
if (!this.validateRoleChangeCommand(command, args)) { break; }
this.modifyRole(
this.model.get('jid'), args[0], 'participant', args[1],
undefined, this.onCommandError.bind(this));
break;
default:
return false;
}
return true;
},
registerHandlers () {
/* Register presence and message handlers for this chat
* room
*/
// XXX: Ideally this can be refactored out so that we don't
// need to do stanza processing inside the views in this
// module. See the comment in "onPresence" for more info.
this.model.addHandler('presence', 'ChatRoomView.onPresence', this.onPresence.bind(this));
// XXX instead of having a method showStatusMessages, we could instead
// create message models in converse-muc.js and then give them views in this module.
this.model.addHandler('message', 'ChatRoomView.showStatusMessages', this.showStatusMessages.bind(this));
},
onPresence (pres) {
/* Handles all MUC presence stanzas.
*
* Parameters:
* (XMLElement) pres: The stanza
*/
// XXX: Current thinking is that excessive stanza
// processing inside a view is a "code smell".
// Instead stanza processing should happen inside the
// models/collections.
if (pres.getAttribute('type') === 'error') {
this.showErrorMessageFromPresence(pres);
} else {
// Instead of doing it this way, we could perhaps rather
// create StatusMessage objects inside the messages
// Collection and then simply render those. Then stanza
// processing is done on the model and rendering in the
// view(s).
this.showStatusMessages(pres);
}
},
2018-05-09 14:37:27 +02:00
populateAndJoin () {
this.model.occupants.fetchMembers();
this.join();
this.fetchMessages();
},
join (nick, password) {
/* Join the chat room.
*
* Parameters:
* (String) nick: The user's nickname
* (String) password: Optional password, if required by
* the room.
*/
if (!nick && !this.model.get('nick')) {
this.checkForReservedNick();
return this;
}
this.model.join(nick, password);
return this;
},
renderConfigurationForm (stanza) {
/* Renders a form given an IQ stanza containing the current
* room configuration.
*
* Returns a promise which resolves once the user has
* either submitted the form, or canceled it.
*
* Parameters:
* (XMLElement) stanza: The IQ stanza containing the room
* config.
*/
const container_el = this.el.querySelector('.chatroom-body');
_.each(container_el.querySelectorAll('.chatroom-form-container'), u.removeElement);
_.each(container_el.children, u.hideElement);
container_el.insertAdjacentHTML('beforeend', tpl_chatroom_form());
const form_el = container_el.querySelector('form.chatroom-form'),
fieldset_el = form_el.querySelector('fieldset'),
fields = stanza.querySelectorAll('field'),
title = _.get(stanza.querySelector('title'), 'textContent'),
instructions = _.get(stanza.querySelector('instructions'), 'textContent');
u.removeElement(fieldset_el.querySelector('span.spinner'));
fieldset_el.insertAdjacentHTML('beforeend', `<legend>${title}</legend>`);
if (instructions && instructions !== title) {
fieldset_el.insertAdjacentHTML('beforeend', `<p class="instructions">${instructions}</p>`);
}
_.each(fields, function (field) {
fieldset_el.insertAdjacentHTML('beforeend', u.xForm2webForm(field, stanza));
});
// Render save/cancel buttons
const last_fieldset_el = document.createElement('fieldset');
last_fieldset_el.insertAdjacentHTML(
'beforeend',
2018-05-15 10:32:13 +02:00
`<input type="submit" class="btn btn-primary" value="${__('Save')}"/>`);
last_fieldset_el.insertAdjacentHTML(
'beforeend',
2018-05-15 10:32:13 +02:00
`<input type="button" class="btn btn-secondary" value="${__('Cancel')}"/>`);
form_el.insertAdjacentElement('beforeend', last_fieldset_el);
last_fieldset_el.querySelector('input[type=button]').addEventListener('click', (ev) => {
ev.preventDefault();
this.closeForm();
});
form_el.addEventListener('submit', (ev) => {
ev.preventDefault();
this.model.saveConfiguration(ev.target).then(
this.model.getRoomFeatures.bind(this.model)
);
this.closeForm();
},
false
);
},
closeForm () {
/* Remove the configuration form without submitting and
* return to the chat view.
*/
u.removeElement(this.el.querySelector('.chatroom-form-container'));
this.renderAfterTransition();
},
2017-07-22 22:21:05 +02:00
getAndRenderConfigurationForm (ev) {
/* Start the process of configuring a chat room, either by
* rendering a configuration form, or by auto-configuring
* based on the "roomconfig" data stored on the
* Backbone.Model.
*
* Stores the new configuration on the Backbone.Model once
* completed.
*
* Paremeters:
* (Event) ev: DOM event that might be passed in if this
* method is called due to a user action. In this
* case, auto-configure won't happen, regardless of
* the settings.
*/
this.showSpinner();
this.model.fetchRoomConfiguration()
.then(this.renderConfigurationForm.bind(this))
.catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
},
2017-06-23 20:25:33 +02:00
submitNickname (ev) {
/* Get the nickname value from the form and then join the
* chat room with it.
*/
ev.preventDefault();
const nick_el = ev.target.nick;
const nick = nick_el.value;
if (!nick) {
nick_el.classList.add('error');
return;
}
else {
nick_el.classList.remove('error');
}
this.el.querySelector('.chatroom-form-container').outerHTML = tpl_spinner();
this.join(nick);
},
2016-03-16 12:49:35 +01:00
checkForReservedNick () {
/* User service-discovery to ask the XMPP server whether
* this user has a reserved nickname for this room.
* If so, we'll use that, otherwise we render the nickname form.
*/
this.showSpinner();
this.model.checkForReservedNick(
this.onNickNameFound.bind(this),
this.onNickNameNotFound.bind(this)
)
},
onNickNameFound (iq) {
/* We've received an IQ response from the server which
* might contain the user's reserved nickname.
* If no nickname is found we either render a form for
* them to specify one, or we try to join the room with the
* node of the user's JID.
*
* Parameters:
* (XMLElement) iq: The received IQ stanza
*/
const identity_el = iq.querySelector('query[node="x-roomuser-item"] identity'),
nick = identity_el ? identity_el.getAttribute('name') : null;
if (!nick) {
this.onNickNameNotFound();
} else {
this.join(nick);
}
},
onNickNameNotFound (message) {
if (_converse.muc_nickname_from_jid) {
// We try to enter the room with the node part of
// the user's JID.
this.join(this.getDefaultNickName());
} else {
this.renderNicknameForm(message);
}
},
2016-12-13 20:46:07 +01:00
getDefaultNickName () {
/* The default nickname (used when muc_nickname_from_jid is true)
* is the node part of the user's JID.
* We put this in a separate method so that it can be
* overridden by plugins.
*/
return Strophe.unescapeNode(Strophe.getNodeFromJid(_converse.bare_jid));
},
2017-08-08 22:11:13 +02:00
onNicknameClash (presence) {
/* When the nickname is already taken, we either render a
* form for the user to choose a new nickname, or we
* try to make the nickname unique by adding an integer to
* it. So john will become john-2, and then john-3 and so on.
*
* Which option is take depends on the value of
* muc_nickname_from_jid.
*/
if (_converse.muc_nickname_from_jid) {
const nick = presence.getAttribute('from').split('/')[1];
if (nick === this.getDefaultNickName()) {
this.join(nick + '-2');
} else {
const del= nick.lastIndexOf("-");
const num = nick.substring(del+1, nick.length);
this.join(nick.substring(0, del+1) + String(Number(num)+1));
}
} else {
this.renderNicknameForm(
__("The nickname you chose is reserved or "+
"currently in use, please choose a different one.")
);
}
},
2017-08-08 22:11:13 +02:00
hideChatRoomContents () {
const container_el = this.el.querySelector('.chatroom-body');
if (!_.isNull(container_el)) {
_.each(container_el.children, (child) => { child.classList.add('hidden'); });
}
},
renderNicknameForm (message) {
/* Render a form which allows the user to choose their
* nickname.
*/
this.hideChatRoomContents();
_.each(this.el.querySelectorAll('span.centered.spinner'), u.removeElement);
if (!_.isString(message)) {
message = '';
}
const container_el = this.el.querySelector('.chatroom-body');
container_el.insertAdjacentHTML(
'beforeend',
tpl_chatroom_nickname_form({
heading: __('Please choose your nickname'),
label_nickname: __('Nickname'),
label_join: __('Enter room'),
validation_message: message
}));
this.model.save('connection_status', converse.ROOMSTATUS.NICKNAME_REQUIRED);
const form_el = this.el.querySelector('.chatroom-form');
form_el.addEventListener('submit', this.submitNickname.bind(this), false);
},
submitPassword (ev) {
ev.preventDefault();
const password = this.el.querySelector('.chatroom-form input[type=password]').value;
this.showSpinner();
this.join(this.model.get('nick'), password);
},
2017-12-20 18:08:08 +01:00
renderPasswordForm () {
const container_el = this.el.querySelector('.chatroom-body');
_.each(container_el.children, u.hideElement);
_.each(this.el.querySelectorAll('.spinner'), u.removeElement);
2017-07-22 22:21:05 +02:00
container_el.insertAdjacentHTML('beforeend',
tpl_chatroom_password_form({
heading: __('This chatroom requires a password'),
label_password: __('Password: '),
label_submit: __('Submit')
}));
2017-07-22 22:21:05 +02:00
this.model.save('connection_status', converse.ROOMSTATUS.PASSWORD_REQUIRED);
this.el.querySelector('.chatroom-form').addEventListener(
'submit', this.submitPassword.bind(this), false);
},
2017-07-22 22:21:05 +02:00
showDisconnectMessage (msg) {
u.hideElement(this.el.querySelector('.chat-area'));
u.hideElement(this.el.querySelector('.occupants'));
_.each(this.el.querySelectorAll('.spinner'), u.removeElement);
this.el.querySelector('.chatroom-body').insertAdjacentHTML(
'beforeend',
tpl_chatroom_disconnect({
'disconnect_message': msg
})
);
},
getMessageFromStatus (stat, stanza, is_self) {
/* Parameters:
* (XMLElement) stat: A <status> element.
* (Boolean) is_self: Whether the element refers to the
* current user.
* (XMLElement) stanza: The original stanza received.
*/
const code = stat.getAttribute('code');
2018-05-15 10:32:13 +02:00
if (code === '110' || (code === '100' && !is_self)) { return; }
if (code in _converse.muc.info_messages) {
return _converse.muc.info_messages[code];
}
let nick;
if (!is_self) {
if (code in _converse.muc.action_info_messages) {
nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
return __(_converse.muc.action_info_messages[code], nick);
}
} else if (code in _converse.muc.new_nickname_messages) {
if (is_self && code === "210") {
nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
} else if (is_self && code === "303") {
nick = stanza.querySelector('x item').getAttribute('nick');
}
return __(_converse.muc.new_nickname_messages[code], nick);
}
return;
},
2017-07-22 22:21:05 +02:00
parseXUserElement (x, stanza, is_self) {
/* Parse the passed-in <x xmlns='http://jabber.org/protocol/muc#user'>
* element and construct a map containing relevant
* information.
*/
// 1. Get notification messages based on the <status> elements.
const statuses = x.querySelectorAll('status');
const mapper = _.partial(this.getMessageFromStatus, _, stanza, is_self);
const notification = {};
const messages = _.reject(_.map(statuses, mapper), _.isUndefined);
if (messages.length) {
notification.messages = messages;
}
// 2. Get disconnection messages based on the <status> elements
const codes = _.invokeMap(statuses, Element.prototype.getAttribute, 'code');
const disconnection_codes = _.intersection(codes, _.keys(_converse.muc.disconnect_messages));
const disconnected = is_self && disconnection_codes.length > 0;
if (disconnected) {
notification.disconnected = true;
notification.disconnection_message = _converse.muc.disconnect_messages[disconnection_codes[0]];
}
// 3. Find the reason and actor from the <item> element
const item = x.querySelector('item');
// By using querySelector above, we assume here there is
// one <item> per <x xmlns='http://jabber.org/protocol/muc#user'>
// element. This appears to be a safe assumption, since
// each <x/> element pertains to a single user.
if (!_.isNull(item)) {
const reason = item.querySelector('reason');
if (reason) {
notification.reason = reason ? reason.textContent : undefined;
}
const actor = item.querySelector('actor');
if (actor) {
notification.actor = actor ? actor.getAttribute('nick') : undefined;
}
}
return notification;
},
2017-03-05 10:45:18 +01:00
showNotificationsforUser (notification) {
/* Given the notification object generated by
* parseXUserElement, display any relevant messages and
* information to the user.
*/
if (notification.disconnected) {
this.showDisconnectMessage(notification.disconnection_message);
if (notification.actor) {
this.showDisconnectMessage(__('This action was done by %1$s.', notification.actor));
}
if (notification.reason) {
this.showDisconnectMessage(__('The reason given is: "%1$s".', notification.reason));
}
this.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
return;
}
_.each(notification.messages, (message) => {
this.content.insertAdjacentHTML(
'beforeend',
tpl_info({
'data': '',
'isodate': moment().format(),
'extra_classes': 'chat-event',
'message': message
}));
});
if (notification.reason) {
this.showChatEvent(__('The reason given is: "%1$s".', notification.reason));
}
if (_.get(notification.messages, 'length')) {
this.scrollDown();
}
},
showJoinNotification (occupant) {
if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
return;
}
const nick = occupant.get('nick');
const stat = occupant.get('status');
const last_el = this.content.lastElementChild;
if (_.includes(_.get(last_el, 'classList', []), 'chat-info') &&
_.get(last_el, 'dataset', {}).leave === `"${nick}"`) {
last_el.outerHTML =
tpl_info({
'data': `data-leavejoin="${nick}"`,
'isodate': moment().format(),
'extra_classes': 'chat-event',
'message': __('%1$s has left and re-entered the room', nick)
});
} else {
let message;
if (_.isNil(stat)) {
message = __('%1$s has entered the room', nick);
} else {
message = __('%1$s has entered the room. "%2$s"', nick, stat);
}
const data = {
'data': `data-join="${nick}"`,
'isodate': moment().format(),
'extra_classes': 'chat-event',
'message': message
};
if (_.includes(_.get(last_el, 'classList', []), 'chat-info') &&
_.get(last_el, 'dataset', {}).joinleave === `"${nick}"`) {
2016-03-16 12:49:35 +01:00
last_el.outerHTML = tpl_info(data);
} else {
const el = u.stringToElement(tpl_info(data));
this.content.insertAdjacentElement('beforeend', el);
this.insertDayIndicator(el);
}
}
this.scrollDown();
},
2017-07-22 22:21:05 +02:00
showLeaveNotification (occupant) {
const nick = occupant.get('nick');
const stat = occupant.get('status');
const last_el = this.content.lastElementChild;
if (_.includes(_.get(last_el, 'classList', []), 'chat-info') &&
_.get(last_el, 'dataset', {}).join === `"${nick}"`) {
2016-03-16 12:49:35 +01:00
let message;
if (_.isNil(stat)) {
message = __('%1$s has entered and left the room', nick);
} else {
message = __('%1$s has entered and left the room. "%2$s"', nick, stat);
}
last_el.outerHTML =
tpl_info({
'data': `data-joinleave="${nick}"`,
'isodate': moment().format(),
'extra_classes': 'chat-event',
'message': message
});
} else {
let message;
if (_.isNil(stat)) {
message = __('%1$s has left the room', nick);
} else {
message = __('%1$s has left the room. "%2$s"', nick, stat);
}
const data = {
'message': message,
'isodate': moment().format(),
'extra_classes': 'chat-event',
'data': `data-leave="${nick}"`
}
if (_.includes(_.get(last_el, 'classList', []), 'chat-info') &&
_.get(last_el, 'dataset', {}).leavejoin === `"${nick}"`) {
last_el.outerHTML = tpl_info(data);
} else {
const el = u.stringToElement(tpl_info(data));
this.content.insertAdjacentElement('beforeend', el);
this.insertDayIndicator(el);
}
}
this.scrollDown();
},
2016-07-28 18:06:31 +02:00
showStatusMessages (stanza) {
/* Check for status codes and communicate their purpose to the user.
* See: http://xmpp.org/registrar/mucstatus.html
*
* Parameters:
* (XMLElement) stanza: The message or presence stanza
* containing the status codes.
*/
const elements = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"]`, stanza);
const is_self = stanza.querySelectorAll("status[code='110']").length;
const iteratee = _.partial(this.parseXUserElement.bind(this), _, stanza, is_self);
const notifications = _.reject(_.map(elements, iteratee), _.isEmpty);
_.each(notifications, this.showNotificationsforUser.bind(this));
},
2016-03-16 12:49:35 +01:00
showErrorMessageFromPresence (presence) {
// We didn't enter the room, so we must remove it from the MUC add-on
const error = presence.querySelector('error');
if (error.getAttribute('type') === 'auth') {
if (!_.isNull(error.querySelector('not-authorized'))) {
this.renderPasswordForm();
} else if (!_.isNull(error.querySelector('registration-required'))) {
this.showDisconnectMessage(__('You are not on the member list of this room.'));
} else if (!_.isNull(error.querySelector('forbidden'))) {
this.showDisconnectMessage(__('You have been banned from this room.'));
}
} else if (error.getAttribute('type') === 'modify') {
if (!_.isNull(error.querySelector('jid-malformed'))) {
this.showDisconnectMessage(__('No nickname was specified.'));
}
} else if (error.getAttribute('type') === 'cancel') {
if (!_.isNull(error.querySelector('not-allowed'))) {
this.showDisconnectMessage(__('You are not allowed to create new rooms.'));
} else if (!_.isNull(error.querySelector('not-acceptable'))) {
this.showDisconnectMessage(__("Your nickname doesn't conform to this room's policies."));
} else if (!_.isNull(error.querySelector('conflict'))) {
this.onNicknameClash(presence);
} else if (!_.isNull(error.querySelector('item-not-found'))) {
this.showDisconnectMessage(__("This room does not (yet) exist."));
} else if (!_.isNull(error.querySelector('service-unavailable'))) {
this.showDisconnectMessage(__("This room has reached its maximum number of occupants."));
}
}
},
renderAfterTransition () {
/* Rerender the room after some kind of transition. For
* example after the spinner has been removed or after a
* form has been submitted and removed.
*/
if (this.model.get('connection_status') == converse.ROOMSTATUS.NICKNAME_REQUIRED) {
this.renderNicknameForm();
} else if (this.model.get('connection_status') == converse.ROOMSTATUS.PASSWORD_REQUIRED) {
this.renderPasswordForm();
} else {
this.el.querySelector('.chat-area').classList.remove('hidden');
this.setOccupantsVisibility();
this.scrollDown();
}
},
showSpinner () {
u.removeElement(this.el.querySelector('.spinner'));
2017-07-22 22:21:05 +02:00
const container_el = this.el.querySelector('.chatroom-body');
const children = Array.prototype.slice.call(container_el.children, 0);
container_el.insertAdjacentHTML('afterbegin', tpl_spinner());
_.each(children, u.hideElement);
2017-07-22 22:21:05 +02:00
},
hideSpinner () {
/* Check if the spinner is being shown and if so, hide it.
* Also make sure then that the chat area and occupants
* list are both visible.
*/
const spinner = this.el.querySelector('.spinner');
if (!_.isNull(spinner)) {
u.removeElement(spinner);
this.renderAfterTransition();
}
return this;
},
setChatRoomSubject () {
// For translators: the %1$s and %2$s parts will get
// replaced by the user and topic text respectively
// Example: Topic set by JC Brand to: Hello World!
const subject = this.model.get('subject');
this.content.insertAdjacentHTML(
'beforeend',
tpl_info({
'data': '',
'isodate': moment().format(),
'extra_classes': 'chat-event',
'message': __('Topic set by %1$s', subject.author)
}));
this.content.insertAdjacentHTML(
'beforeend',
tpl_info({
'data': '',
'isodate': moment().format(),
'extra_classes': 'chat-topic',
'message': subject.text
}));
this.scrollDown();
}
});
2017-07-22 22:21:05 +02:00
_converse.RoomsPanel = Backbone.NativeView.extend({
/* Backbone.NativeView which renders MUC section of the control box.
*
* Chat rooms can be listed, joined and new rooms can be created.
*/
tagName: 'div',
2018-04-30 16:05:20 +02:00
className: 'controlbox-section',
id: 'chatrooms',
events: {
'click a.chatbox-btn.fa-users': 'showAddRoomModal',
'click a.chatbox-btn.fa-list-ul': 'showListRoomsModal',
'click a.room-info': 'toggleRoomInfo'
},
render () {
this.el.innerHTML = tpl_room_panel({
'heading_chatrooms': __('Chatrooms'),
'title_new_room': __('Add a new room'),
'title_list_rooms': __('Query for rooms')
});
return this;
},
toggleRoomInfo (ev) {
ev.preventDefault();
toggleRoomInfo(ev);
},
2017-07-22 22:21:05 +02:00
showAddRoomModal (ev) {
if (_.isUndefined(this.add_room_modal)) {
this.add_room_modal = new _converse.AddChatRoomModal({'model': this.model});
}
this.add_room_modal.show(ev);
},
2017-07-22 22:21:05 +02:00
showListRoomsModal(ev) {
if (_.isUndefined(this.list_rooms_modal)) {
this.list_rooms_modal = new _converse.ListChatRoomsModal({'model': this.model});
}
this.list_rooms_modal.show(ev);
}
});
_converse.ChatRoomOccupantView = Backbone.VDOMView.extend({
tagName: 'li',
initialize () {
this.model.on('change', this.render, this);
},
toHTML () {
2018-05-09 14:37:27 +02:00
const show = this.model.get('show');
return tpl_occupant(
_.extend(
{ 'jid': '',
'show': show,
'hint_show': _converse.PRETTY_CHAT_STATUS[show],
'hint_occupant': __('Click to mention %1$s in your message.', this.model.get('nick')),
'desc_moderator': __('This user is a moderator.'),
2018-05-09 14:37:27 +02:00
'desc_participant': __('This user can send messages in this room.'),
'desc_visitor': __('This user can NOT send messages in this room.'),
'label_moderator': __('Moderator'),
'label_visitor': __('Visitor'),
'label_owner': __('Owner'),
'label_member': __('Member'),
'label_admin': __('Admin')
}, this.model.toJSON())
);
},
2017-07-22 22:21:05 +02:00
destroy () {
this.el.parentElement.removeChild(this.el);
}
});
_converse.ChatRoomOccupantsView = Backbone.OrderedListView.extend({
tagName: 'div',
className: 'occupants col-md-3 col-4',
listItems: 'model',
sortEvent: 'change:role',
listSelector: '.occupant-list',
ItemView: _converse.ChatRoomOccupantView,
initialize () {
Backbone.OrderedListView.prototype.initialize.apply(this, arguments);
this.chatroomview = this.model.chatroomview;
this.chatroomview.model.on('change:open', this.renderInviteWidget, this);
this.chatroomview.model.on('change:affiliation', this.renderInviteWidget, this);
this.chatroomview.model.on('change:hidden', this.onFeatureChanged, this);
this.chatroomview.model.on('change:mam_enabled', this.onFeatureChanged, this);
this.chatroomview.model.on('change:membersonly', this.onFeatureChanged, this);
this.chatroomview.model.on('change:moderated', this.onFeatureChanged, this);
this.chatroomview.model.on('change:nonanonymous', this.onFeatureChanged, this);
this.chatroomview.model.on('change:open', this.onFeatureChanged, this);
this.chatroomview.model.on('change:passwordprotected', this.onFeatureChanged, this);
this.chatroomview.model.on('change:persistent', this.onFeatureChanged, this);
this.chatroomview.model.on('change:publicroom', this.onFeatureChanged, this);
this.chatroomview.model.on('change:semianonymous', this.onFeatureChanged, this);
this.chatroomview.model.on('change:temporary', this.onFeatureChanged, this);
this.chatroomview.model.on('change:unmoderated', this.onFeatureChanged, this);
this.chatroomview.model.on('change:unsecured', this.onFeatureChanged, this);
this.render();
this.model.fetch({
'add': true,
'silent': true,
'success': this.sortAndPositionAllItems.bind(this)
});
},
render () {
this.el.innerHTML = tpl_chatroom_sidebar(
_.extend(this.chatroomview.model.toJSON(), {
'allow_muc_invitations': _converse.allow_muc_invitations,
'label_occupants': __('Occupants')
})
);
if (_converse.allow_muc_invitations) {
_converse.api.waitUntil('rosterContactsFetched').then(
this.renderInviteWidget.bind(this)
);
}
return this.renderRoomFeatures();
},
renderInviteWidget () {
const form = this.el.querySelector('form.room-invite');
if (this.shouldInviteWidgetBeShown()) {
if (_.isNull(form)) {
const heading = this.el.querySelector('.occupants-heading');
heading.insertAdjacentHTML(
'afterend',
tpl_chatroom_invite({
'error_message': null,
'label_invitation': __('Invite'),
})
);
this.initInviteWidget();
}
} else if (!_.isNull(form)) {
form.remove();
}
return this;
},
2017-07-22 22:21:05 +02:00
renderRoomFeatures () {
const picks = _.pick(this.chatroomview.model.attributes, converse.ROOM_FEATURES),
iteratee = (a, v) => a || v,
el = this.el.querySelector('.chatroom-features');
el.innerHTML = tpl_chatroom_features(
_.extend(this.chatroomview.model.toJSON(), {
'has_features': _.reduce(_.values(picks), iteratee),
'label_features': __('Features'),
'label_hidden': __('Hidden'),
'label_mam_enabled': __('Message archiving'),
'label_membersonly': __('Members only'),
'label_moderated': __('Moderated'),
'label_nonanonymous': __('Non-anonymous'),
'label_open': __('Open'),
'label_passwordprotected': __('Password protected'),
'label_persistent': __('Persistent'),
'label_public': __('Public'),
'label_semianonymous': __('Semi-anonymous'),
'label_temporary': __('Temporary'),
'label_unmoderated': __('Unmoderated'),
'label_unsecured': __('No password'),
'tt_hidden': __('This room is not publicly searchable'),
'tt_mam_enabled': __('Messages are archived on the server'),
'tt_membersonly': __('This room is restricted to members only'),
'tt_moderated': __('This room is being moderated'),
'tt_nonanonymous': __('All other room occupants can see your XMPP username'),
'tt_open': __('Anyone can join this room'),
'tt_passwordprotected': __('This room requires a password before entry'),
'tt_persistent': __('This room persists even if it\'s unoccupied'),
'tt_public': __('This room is publicly searchable'),
'tt_semianonymous': __('Only moderators can see your XMPP username'),
'tt_temporary': __('This room will disappear once the last person leaves'),
'tt_unmoderated': __('This room is not being moderated'),
'tt_unsecured': __('This room does not require a password upon entry')
}));
this.setOccupantsHeight();
return this;
},
2016-03-16 12:49:35 +01:00
onFeatureChanged (model) {
/* When a feature has been changed, it's logical opposite
* must be set to the opposite value.
*
* So for example, if "temporary" was set to "false", then
* "persistent" will be set to "true" in this method.
*
* Additionally a debounced render method is called to make
* sure the features widget gets updated.
*/
if (_.isUndefined(this.debouncedRenderRoomFeatures)) {
this.debouncedRenderRoomFeatures = _.debounce(
this.renderRoomFeatures, 100, {'leading': false}
);
}
const changed_features = {};
_.each(_.keys(model.changed), function (k) {
if (!_.isNil(ROOM_FEATURES_MAP[k])) {
changed_features[ROOM_FEATURES_MAP[k]] = !model.changed[k];
}
});
this.chatroomview.model.save(changed_features, {'silent': true});
this.debouncedRenderRoomFeatures();
},
setOccupantsHeight () {
const el = this.el.querySelector('.chatroom-features');
this.el.querySelector('.occupant-list').style.cssText =
`height: calc(100% - ${el.offsetHeight}px - 5em);`;
},
2016-11-07 15:43:48 +01:00
promptForInvite (suggestion) {
const reason = prompt(
__('You are about to invite %1$s to the chat room "%2$s". '+
'You may optionally include a message, explaining the reason for the invitation.',
suggestion.text.label, this.model.get('id'))
);
if (reason !== null) {
this.chatroomview.model.directInvite(suggestion.text.value, reason);
}
const form = suggestion.target.form,
error = form.querySelector('.pure-form-message.error');
if (!_.isNull(error)) {
error.parentNode.removeChild(error);
}
suggestion.target.value = '';
},
inviteFormSubmitted (evt) {
evt.preventDefault();
const el = evt.target.querySelector('input.invited-contact'),
jid = el.value;
if (!jid || _.compact(jid.split('@')).length < 2) {
evt.target.outerHTML = tpl_chatroom_invite({
'error_message': __('Please enter a valid XMPP username'),
'label_invitation': __('Invite'),
});
this.initInviteWidget();
return;
}
this.promptForInvite({
'target': el,
'text': {
'label': jid,
'value': jid
}});
},
2016-11-07 15:43:48 +01:00
shouldInviteWidgetBeShown () {
return _converse.allow_muc_invitations &&
(this.chatroomview.model.get('open') ||
this.chatroomview.model.get('affiliation') === "owner"
);
},
2017-07-22 22:21:05 +02:00
initInviteWidget () {
const form = this.el.querySelector('form.room-invite');
if (_.isNull(form)) {
return;
}
form.addEventListener('submit', this.inviteFormSubmitted.bind(this), false);
const el = this.el.querySelector('input.invited-contact');
const list = _converse.roster.map(function (item) {
const label = item.get('fullname') || item.get('jid');
return {'label': label, 'value':item.get('jid')};
});
const awesomplete = new Awesomplete(el, {
'minChars': 1,
'list': list
});
el.addEventListener('awesomplete-selectcomplete',
this.promptForInvite.bind(this));
}
});
function setMUCDomain (domain, controlboxview) {
_converse.muc_domain = domain;
controlboxview.roomspanel.model.save({'muc_domain': domain});
}
2017-07-22 22:21:05 +02:00
function setMUCDomainFromDisco (controlboxview) {
/* Check whether service discovery for the user's domain
* returned MUC information and use that to automatically
* set the MUC domain for the "Rooms" panel of the controlbox.
*/
function featureAdded (feature) {
if (feature.get('var') === Strophe.NS.MUC &&
f.includes('conference', feature.entity.identities.pluck('category'))) {
setMUCDomain(feature.get('from'), controlboxview);
}
}
_converse.api.waitUntil('discoInitialized').then(() => {
_converse.api.listen.on('serviceDiscovered', featureAdded);
// Features could have been added before the controlbox was
// initialized. We're only interested in MUC
_converse.disco_entities.each((entity) => {
const feature = entity.features.findWhere({'var': Strophe.NS.MUC });
if (feature) {
featureAdded(feature)
}
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
}
function fetchAndSetMUCDomain (controlboxview) {
if (controlboxview.model.get('connected')) {
if (!controlboxview.roomspanel.model.get('muc_domain')) {
if (_.isUndefined(_converse.muc_domain)) {
setMUCDomainFromDisco(controlboxview);
} else {
setMUCDomain(_converse.muc_domain, controlboxview);
}
}
}
}
/************************ BEGIN Event Handlers ************************/
_converse.on('controlboxInitialized', (view) => {
if (!_converse.allow_muc) {
return;
}
fetchAndSetMUCDomain(view);
view.model.on('change:connected', _.partial(fetchAndSetMUCDomain, view));
});
2018-05-07 18:20:15 +02:00
function reconnectToChatRooms () {
/* Upon a reconnection event from converse, join again
* all the open chat rooms.
*/
_converse.chatboxviews.each(function (view) {
if (view.model.get('type') === converse.CHATROOMS_TYPE) {
view.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
view.model.registerHandlers();
2018-05-09 14:37:27 +02:00
view.populateAndJoin();
2018-05-07 18:20:15 +02:00
}
});
}
_converse.on('reconnected', reconnectToChatRooms);
/************************ END Event Handlers ************************/
}
});
}));
2017-07-22 22:21:05 +02:00
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
2017-07-22 22:21:05 +02:00
/*global define */
(function (root, factory) {
define('converse-notification',["converse-core"], factory);
})(this, function (converse) {
"use strict";
2017-07-22 22:21:05 +02:00
var _converse$env = converse.env,
utils = _converse$env.utils,
Strophe = _converse$env.Strophe,
_ = _converse$env._;
converse.plugins.add('converse-notification', {
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
var _converse = this._converse;
var __ = _converse.__;
_converse.supports_html5_notification = "Notification" in window;
_converse.api.settings.update({
notify_all_room_messages: false,
show_desktop_notifications: true,
show_chatstate_notifications: false,
chatstate_notification_blacklist: [],
// ^ a list of JIDs to ignore concerning chat state notifications
play_sounds: true,
sounds_path: '/sounds/',
notification_icon: '/logo/conversejs128.png'
});
_converse.isOnlyChatStateNotification = function (msg) {
return (// See XEP-0085 Chat State Notification
_.isNull(msg.querySelector('body')) && (_.isNull(msg.querySelector(_converse.ACTIVE)) || _.isNull(msg.querySelector(_converse.COMPOSING)) || _.isNull(msg.querySelector(_converse.INACTIVE)) || _.isNull(msg.querySelector(_converse.PAUSED)) || _.isNull(msg.querySelector(_converse.GONE)))
);
};
_converse.shouldNotifyOfGroupMessage = function (message) {
/* Is this a group message worthy of notification?
*/
var notify_all = _converse.notify_all_room_messages;
var jid = message.getAttribute('from'),
resource = Strophe.getResourceFromJid(jid),
room_jid = Strophe.getBareJidFromJid(jid),
sender = resource && Strophe.unescapeNode(resource) || '';
if (sender === '' || message.querySelectorAll('delay').length > 0) {
return false;
}
var room = _converse.chatboxes.get(room_jid);
var body = message.querySelector('body');
if (_.isNull(body)) {
return false;
}
var mentioned = new RegExp("\\b".concat(room.get('nick'), "\\b")).test(body.textContent);
notify_all = notify_all === true || _.isArray(notify_all) && _.includes(notify_all, room_jid);
if (sender === room.get('nick') || !notify_all && !mentioned) {
return false;
}
2017-07-22 22:21:05 +02:00
return true;
};
2017-06-23 20:25:33 +02:00
_converse.isMessageToHiddenChat = function (message) {
if (_.includes(['mobile', 'fullscreen', 'embedded'], _converse.view_mode)) {
var jid = Strophe.getBareJidFromJid(message.getAttribute('from'));
var model = _converse.chatboxes.get(jid);
if (!_.isNil(model)) {
return model.get('hidden') || _converse.windowState === 'hidden';
}
2017-07-22 22:21:05 +02:00
return true;
}
return _converse.windowState === 'hidden';
};
_converse.shouldNotifyOfMessage = function (message) {
/* Is this a message worthy of notification?
*/
if (utils.isOTRMessage(message)) {
return false;
}
2017-07-22 22:21:05 +02:00
var forwarded = message.querySelector('forwarded');
2017-07-22 22:21:05 +02:00
if (!_.isNull(forwarded)) {
return false;
} else if (message.getAttribute('type') === 'groupchat') {
return _converse.shouldNotifyOfGroupMessage(message);
} else if (utils.isHeadlineMessage(_converse, message)) {
// We want to show notifications for headline messages.
return _converse.isMessageToHiddenChat(message);
}
2017-07-22 22:21:05 +02:00
var is_me = Strophe.getBareJidFromJid(message.getAttribute('from')) === _converse.bare_jid;
return !_converse.isOnlyChatStateNotification(message) && !is_me && _converse.isMessageToHiddenChat(message);
};
_converse.playSoundNotification = function () {
/* Plays a sound to notify that a new message was recieved.
*/
// XXX Eventually this can be refactored to use Notification's sound
// feature, but no browser currently supports it.
// https://developer.mozilla.org/en-US/docs/Web/API/notification/sound
var audio;
2017-07-22 22:21:05 +02:00
if (_converse.play_sounds && !_.isUndefined(window.Audio)) {
audio = new Audio(_converse.sounds_path + "msg_received.ogg");
2017-07-22 22:21:05 +02:00
if (audio.canPlayType('audio/ogg')) {
audio.play();
} else {
audio = new Audio(_converse.sounds_path + "msg_received.mp3");
if (audio.canPlayType('audio/mp3')) {
audio.play();
}
}
}
};
_converse.areDesktopNotificationsEnabled = function () {
return _converse.supports_html5_notification && _converse.show_desktop_notifications && Notification.permission === "granted";
};
2017-02-03 13:51:07 +01:00
_converse.showMessageNotification = function (message) {
/* Shows an HTML5 Notification to indicate that a new chat
* message was received.
*/
var title, roster_item;
var full_from_jid = message.getAttribute('from'),
from_jid = Strophe.getBareJidFromJid(full_from_jid);
if (message.getAttribute('type') === 'headline') {
if (!_.includes(from_jid, '@') || _converse.allow_non_roster_messaging) {
title = __("Notification from %1$s", from_jid);
} else {
return;
}
} else if (!_.includes(from_jid, '@')) {
// workaround for Prosody which doesn't give type "headline"
title = __("Notification from %1$s", from_jid);
} else if (message.getAttribute('type') === 'groupchat') {
title = __("%1$s says", Strophe.getResourceFromJid(full_from_jid));
} else {
if (_.isUndefined(_converse.roster)) {
_converse.log("Could not send notification, because roster is undefined", Strophe.LogLevel.ERROR);
return;
}
2017-07-22 22:21:05 +02:00
roster_item = _converse.roster.get(from_jid);
2017-02-03 13:51:07 +01:00
if (!_.isUndefined(roster_item)) {
2018-05-07 18:20:15 +02:00
title = __("%1$s says", roster_item.getDisplayName());
} else {
if (_converse.allow_non_roster_messaging) {
title = __("%1$s says", from_jid);
} else {
return;
}
}
}
2016-11-07 15:43:48 +01:00
var n = new Notification(title, {
body: message.querySelector('body').textContent,
lang: _converse.locale,
icon: _converse.notification_icon
});
setTimeout(n.close.bind(n), 5000);
};
_converse.showChatStateNotification = function (contact) {
/* Creates an HTML5 Notification to inform of a change in a
* contact's chat state.
*/
if (_.includes(_converse.chatstate_notification_blacklist, contact.jid)) {
// Don't notify if the user is being ignored.
return;
}
var chat_state = contact.chat_status;
var message = null;
if (chat_state === 'offline') {
message = __('has gone offline');
} else if (chat_state === 'away') {
message = __('has gone away');
} else if (chat_state === 'dnd') {
message = __('is busy');
} else if (chat_state === 'online') {
message = __('has come online');
}
if (message === null) {
return;
}
2018-05-07 18:20:15 +02:00
var n = new Notification(contact.getDisplayName(), {
body: message,
lang: _converse.locale,
icon: _converse.notification_icon
});
setTimeout(n.close.bind(n), 5000);
};
_converse.showContactRequestNotification = function (contact) {
2018-05-07 18:20:15 +02:00
var n = new Notification(contact.getDisplayName(), {
body: __('wants to be your contact'),
lang: _converse.locale,
icon: _converse.notification_icon
});
setTimeout(n.close.bind(n), 5000);
};
_converse.showFeedbackNotification = function (data) {
if (data.klass === 'error' || data.klass === 'warn') {
var n = new Notification(data.subject, {
body: data.message,
lang: _converse.locale,
icon: _converse.notification_icon
});
setTimeout(n.close.bind(n), 5000);
}
};
_converse.handleChatStateNotification = function (contact) {
2018-05-23 04:27:33 +02:00
/* Event handler for on('contactPresenceChanged').
* Will show an HTML5 notification to indicate that the chat
* status has changed.
*/
if (_converse.areDesktopNotificationsEnabled() && _converse.show_chatstate_notifications) {
_converse.showChatStateNotification(contact);
}
};
2016-11-07 15:43:48 +01:00
_converse.handleMessageNotification = function (data) {
/* Event handler for the on('message') event. Will call methods
* to play sounds and show HTML5 notifications.
*/
var message = data.stanza;
if (!_converse.shouldNotifyOfMessage(message)) {
return false;
}
_converse.playSoundNotification();
if (_converse.areDesktopNotificationsEnabled()) {
_converse.showMessageNotification(message);
}
};
_converse.handleContactRequestNotification = function (contact) {
if (_converse.areDesktopNotificationsEnabled(true)) {
_converse.showContactRequestNotification(contact);
}
};
_converse.handleFeedback = function (data) {
if (_converse.areDesktopNotificationsEnabled(true)) {
_converse.showFeedbackNotification(data);
}
};
_converse.requestPermission = function () {
if (_converse.supports_html5_notification && !_.includes(['denied', 'granted'], Notification.permission)) {
// Ask user to enable HTML5 notifications
Notification.requestPermission();
}
};
2016-11-07 15:43:48 +01:00
_converse.on('pluginsInitialized', function () {
// We only register event handlers after all plugins are
// registered, because other plugins might override some of our
// handlers.
_converse.on('contactRequest', _converse.handleContactRequestNotification);
2017-07-22 22:21:05 +02:00
2018-05-23 04:27:33 +02:00
_converse.on('contactPresenceChanged', _converse.handleChatStateNotification);
2017-07-22 22:21:05 +02:00
_converse.on('message', _converse.handleMessageNotification);
_converse.on('feedback', _converse.handleFeedback);
_converse.on('connected', _converse.requestPermission);
});
}
});
});
//# sourceMappingURL=converse-notification.js.map;
define('tpl!toolbar_otr', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
if (o.allow_otr) { ;
__p += '\n <li class="toggle-toolbar-menu dropup right" title="' +
__e(o.otr_tooltip) +
'">\n <a class="toggle-otr ' +
__e(o.otr_status_class) +
'" title="' +
__e(o.label_insert_smiley) +
'" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">\n\n <span class="chat-toolbar-text">' +
__e(o.otr_translated_status) +
'</span>\n ';
if (o.otr_status == o.UNENCRYPTED) { ;
__p += '\n <span class="fa fa-unlock"></span>\n ';
} ;
__p += '\n ';
if (o.otr_status == o.UNVERIFIED) { ;
__p += '\n <span class="fa fa-lock"></span>\n ';
} ;
__p += '\n ';
if (o.otr_status == o.VERIFIED) { ;
__p += '\n <span class="fa fa-lock"></span>\n ';
} ;
__p += ' ';
if (o.otr_status == o.FINISHED) { ;
__p += '\n <span class="fa fa-unlock"></span>\n ';
} ;
__p += '\n </a> \n\n <ul class="otr-menu toolbar-menu dropdown-menu">\n ';
if (o.otr_status == o.UNENCRYPTED) { ;
__p += '\n <li class="dropdown-item"><a class="start-otr" href="#">' +
__e(o.label_start_encrypted_conversation) +
'</a></li>\n ';
} ;
__p += '\n ';
if (o.otr_status != o.UNENCRYPTED) { ;
__p += '\n <li class="dropdown-item"><a class="start-otr" href="#">' +
__e(o.label_refresh_encrypted_conversation) +
'</a></li>\n <li class="dropdown-item"><a class="end-otr" href="#">' +
__e(o.label_end_encrypted_conversation) +
'</a></li>\n <li class="dropdown-item"><a class="auth-otr" data-scheme="smp" href="#">' +
__e(o.label_verify_with_smp) +
'</a></li>\n ';
} ;
__p += '\n ';
if (o.otr_status == o.UNVERIFIED) { ;
__p += '\n <li class="dropdown-item"><a class="auth-otr" data-scheme="fingerprint" href="#">' +
__e(o.label_verify_with_fingerprints) +
'</a></li>\n ';
} ;
__p += '\n <li class="dropdown-item"><a href="http://www.cypherpunks.ca/otr/help/3.2.0/levels.php" target="_blank" rel="noopener">' +
__e(o.label_whats_this) +
'</a></li>\n </ul>\n </li>\n';
} ;
__p += '\n';
return __p
};});
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
2017-02-13 17:16:13 +01:00
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define, window, crypto, CryptoJS */
/* This is a Converse.js plugin which add support Off-the-record (OTR)
* encryption of one-on-one chat messages.
*/
(function (root, factory) {
define('converse-otr',["converse-chatview", "bootstrap", "tpl!toolbar_otr", 'otr'], factory);
})(this, function (converse, bootstrap, tpl_toolbar_otr, otr) {
"use strict";
var _converse$env = converse.env,
Strophe = _converse$env.Strophe,
utils = _converse$env.utils,
_ = _converse$env._;
var HAS_CSPRNG = _.isUndefined(window.crypto) ? false : _.isFunction(window.crypto.randomBytes) || _.isFunction(window.crypto.getRandomValues);
var HAS_CRYPTO = HAS_CSPRNG && !_.isUndefined(otr.OTR) && !_.isUndefined(otr.DSA);
var UNENCRYPTED = 0;
var UNVERIFIED = 1;
var VERIFIED = 2;
var FINISHED = 3;
var OTR_TRANSLATED_MAPPING = {}; // Populated in initialize
var OTR_CLASS_MAPPING = {};
OTR_CLASS_MAPPING[UNENCRYPTED] = 'unencrypted';
OTR_CLASS_MAPPING[UNVERIFIED] = 'unverified';
OTR_CLASS_MAPPING[VERIFIED] = 'verified';
OTR_CLASS_MAPPING[FINISHED] = 'finished';
converse.plugins.add('converse-otr', {
/* Plugin dependencies are other plugins which might be
* overridden or relied upon, and therefore need to be loaded before
* this plugin.
*
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found. By default it's
* false, which means these plugins are only loaded opportunistically.
*
* NB: These plugins need to have already been loaded via require.js.
*/
dependencies: ["converse-chatview"],
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
ChatBox: {
initialize: function initialize() {
this.__super__.initialize.apply(this, arguments);
if (this.get('box_id') !== 'controlbox') {
this.save({
'otr_status': this.get('otr_status') || UNENCRYPTED
});
}
},
createMessageStanza: function createMessageStanza() {
var stanza = this.__super__.createMessageStanza.apply(this, arguments);
if (this.get('otr_status') !== UNENCRYPTED || utils.isOTRMessage(stanza.nodeTree)) {
// OTR messages aren't carbon copied
stanza.c('private', {
'xmlns': Strophe.NS.CARBONS
}).up().c('no-store', {
'xmlns': Strophe.NS.HINTS
}).up().c('no-permanent-store', {
'xmlns': Strophe.NS.HINTS
}).up().c('no-copy', {
'xmlns': Strophe.NS.HINTS
});
}
2016-11-07 15:43:48 +01:00
return stanza;
},
shouldPlayNotification: function shouldPlayNotification($message) {
/* Don't play a notification if this is an OTR message but
* encryption is not yet set up. That would mean that the
* OTR session is still being established, so there are no
* "visible" OTR messages being exchanged.
*/
return this.__super__.shouldPlayNotification.apply(this, arguments) && !(utils.isOTRMessage($message[0]) && !_.includes([UNVERIFIED, VERIFIED], this.get('otr_status')));
},
createMessage: function createMessage(message, delay, original_stanza) {
var _converse = this.__super__._converse,
text = _.propertyOf(message.querySelector('body'))('textContent');
2017-07-22 22:21:05 +02:00
if (!text || !_converse.allow_otr) {
return this.__super__.createMessage.apply(this, arguments);
}
2017-04-04 17:26:06 +02:00
if (utils.isNewMessage(original_stanza)) {
if (text.match(/^\?OTRv23?/)) {
return this.initiateOTR(text);
} else if (_.includes([UNVERIFIED, VERIFIED], this.get('otr_status'))) {
return this.otr.receiveMsg(text);
} else if (text.match(/^\?OTR/)) {
if (!this.otr) {
return this.initiateOTR(text);
} else {
return this.otr.receiveMsg(text);
}
}
} // Normal unencrypted message (or archived message)
2017-07-22 22:21:05 +02:00
return this.__super__.createMessage.apply(this, arguments);
},
generatePrivateKey: function generatePrivateKey(instance_tag) {
var _converse = this.__super__._converse;
var key = new otr.DSA();
var jid = _converse.connection.jid;
if (_converse.cache_otr_key) {
this.save({
'otr_priv_key': key.packPrivate(),
'otr_instance_tag': instance_tag
});
}
return key;
},
getSession: function getSession(callback) {
var _converse = this.__super__._converse,
__ = _converse.__;
var instance_tag, saved_key, encrypted_key;
if (_converse.cache_otr_key) {
encrypted_key = this.get('otr_priv_key');
if (_.isString(encrypted_key)) {
instance_tag = this.get('otr_instance_tag');
saved_key = otr.DSA.parsePrivate(encrypted_key);
if (saved_key && instance_tag) {
this.trigger('showHelpMessages', [__('Re-establishing encrypted session')]);
callback({
'key': saved_key,
'instance_tag': instance_tag
});
return; // Our work is done here
}
}
} // We need to generate a new key and instance tag
2017-07-22 22:21:05 +02:00
this.trigger('showHelpMessages', [__('Generating private key.'), __('Your browser might become unresponsive.')], null, true // show spinner
);
var that = this;
window.setTimeout(function () {
callback({
'key': that.generatePrivateKey(instance_tag),
'instance_tag': otr.OTR.makeInstanceTag()
});
}, 500);
},
updateOTRStatus: function updateOTRStatus(state) {
switch (state) {
case otr.OTR.CONST.STATUS_AKE_SUCCESS:
if (this.otr.msgstate === otr.OTR.CONST.MSGSTATE_ENCRYPTED) {
this.save({
'otr_status': UNVERIFIED
});
}
2017-07-22 22:21:05 +02:00
break;
case otr.OTR.CONST.STATUS_END_OTR:
if (this.otr.msgstate === otr.OTR.CONST.MSGSTATE_FINISHED) {
this.save({
'otr_status': FINISHED
});
} else if (this.otr.msgstate === otr.OTR.CONST.MSGSTATE_PLAINTEXT) {
this.save({
'otr_status': UNENCRYPTED
});
}
2017-02-03 13:51:07 +01:00
break;
}
},
onSMP: function onSMP(type, data) {
// Event handler for SMP (Socialist's Millionaire Protocol)
// used by OTR (off-the-record).
var _converse = this.__super__._converse,
__ = _converse.__;
switch (type) {
case 'question':
this.otr.smpSecret(prompt(__('Authentication request from %1$s\n\nYour chat contact is attempting to verify your identity, by asking you the question below.\n\n%2$s', [this.get('fullname'), data])));
break;
case 'trust':
if (data === true) {
this.save({
'otr_status': VERIFIED
});
} else {
this.trigger('showHelpMessages', [__("Could not verify this user's identify.")], 'error');
this.save({
'otr_status': UNVERIFIED
});
}
break;
default:
throw new TypeError('ChatBox.onSMP: Unknown type for SMP');
}
},
initiateOTR: function initiateOTR(query_msg) {
var _this = this;
// Sets up an OTR object through which we can send and receive
// encrypted messages.
//
// If 'query_msg' is passed in, it means there is an alread incoming
// query message from our contact. Otherwise, it is us who will
// send the query message to them.
var _converse = this.__super__._converse,
__ = _converse.__;
this.save({
'otr_status': UNENCRYPTED
});
this.getSession(function (session) {
var _converse = _this.__super__._converse;
_this.otr = new otr.OTR({
fragment_size: 140,
send_interval: 200,
priv: session.key,
instance_tag: session.instance_tag,
debug: _this.debug
});
_this.otr.on('status', _this.updateOTRStatus.bind(_this));
_this.otr.on('smp', _this.onSMP.bind(_this));
_this.otr.on('ui', function (msg) {
_this.trigger('showReceivedOTRMessage', msg);
});
_this.otr.on('io', function (msg) {
_this.sendMessage(new _converse.Message({
'message': msg
}));
});
_this.otr.on('error', function (msg) {
_this.trigger('showOTRError', msg);
});
_this.trigger('showHelpMessages', [__('Exchanging private key with contact.')]);
if (query_msg) {
_this.otr.receiveMsg(query_msg);
} else {
_this.otr.sendQueryMsg();
}
});
},
endOTR: function endOTR() {
if (this.otr) {
this.otr.endOtr();
}
this.save({
'otr_status': UNENCRYPTED
});
}
},
ChatBoxView: {
events: {
'click .toggle-otr': 'toggleOTRMenu',
'click .start-otr': 'startOTRFromToolbar',
'click .end-otr': 'endOTR',
'click .auth-otr': 'authOTR'
},
initialize: function initialize() {
var _converse = this.__super__._converse;
2017-07-22 22:21:05 +02:00
this.__super__.initialize.apply(this, arguments);
this.model.on('change:otr_status', this.onOTRStatusChanged, this);
this.model.on('showOTRError', this.showOTRError, this);
this.model.on('showSentOTRMessage', function (text) {
this.showMessage({
'message': text,
'sender': 'me'
});
}, this);
this.model.on('showReceivedOTRMessage', function (text) {
this.showMessage({
'message': text,
'sender': 'them'
});
}, this);
if (_.includes([UNVERIFIED, VERIFIED], this.model.get('otr_status')) || _converse.use_otr_by_default) {
this.model.initiateOTR();
}
},
parseMessageForCommands: function parseMessageForCommands(text) {
var _converse = this.__super__._converse;
var match = text.replace(/^\s*/, "").match(/^\/(.*)\s*$/);
if (match) {
if (_converse.allow_otr && match[1] === "endotr") {
this.endOTR();
return true;
} else if (_converse.allow_otr && match[1] === "otr") {
this.model.initiateOTR();
return true;
}
}
2017-07-22 22:21:05 +02:00
return this.__super__.parseMessageForCommands.apply(this, arguments);
},
isOTREncryptedSession: function isOTREncryptedSession() {
return _.includes([UNVERIFIED, VERIFIED], this.model.get('otr_status'));
},
onMessageSubmitted: function onMessageSubmitted(text, spoiler_hint) {
var _converse = this.__super__._converse;
2017-07-22 22:21:05 +02:00
if (!_converse.connection.authenticated) {
this.__super__.onMessageSubmitted.apply(this, arguments);
}
if (this.parseMessageForCommands(text)) {
return;
}
2017-07-22 22:21:05 +02:00
if (this.isOTREncryptedSession()) {
this.model.otr.sendMsg(text);
this.model.trigger('showSentOTRMessage', text);
} else {
this.__super__.onMessageSubmitted.apply(this, arguments);
}
},
onOTRStatusChanged: function onOTRStatusChanged() {
this.renderToolbar().informOTRChange();
},
informOTRChange: function informOTRChange() {
var _converse = this.__super__._converse,
__ = _converse.__,
data = this.model.toJSON(),
msgs = [];
if (data.otr_status === UNENCRYPTED) {
msgs.push(__("Your messages are not encrypted anymore"));
} else if (data.otr_status === UNVERIFIED) {
msgs.push(__("Your messages are now encrypted but your contact's identity has not been verified."));
} else if (data.otr_status === VERIFIED) {
msgs.push(__("Your contact's identify has been verified."));
} else if (data.otr_status === FINISHED) {
msgs.push(__("Your contact has ended encryption on their end, you should do the same."));
}
return this.showHelpMessages(msgs, 'info', false);
},
showOTRError: function showOTRError(msg) {
var _converse = this.__super__._converse,
__ = _converse.__;
if (msg === 'Message cannot be sent at this time.') {
this.showHelpMessages([__('Your message could not be sent')], 'error');
} else if (msg === 'Received an unencrypted message.') {
this.showHelpMessages([__('We received an unencrypted message')], 'error');
} else if (msg === 'Received an unreadable encrypted message.') {
this.showHelpMessages([__('We received an unreadable encrypted message')], 'error');
} else {
this.showHelpMessages(["Encryption error occured: ".concat(msg)], 'error');
}
_converse.log("OTR ERROR:".concat(msg), Strophe.LogLevel.ERROR);
},
startOTRFromToolbar: function startOTRFromToolbar(ev) {
ev.stopPropagation();
this.model.initiateOTR();
},
endOTR: function endOTR(ev) {
if (!_.isUndefined(ev)) {
ev.preventDefault();
ev.stopPropagation();
}
this.model.endOTR();
},
authOTR: function authOTR(ev) {
var _converse = this.__super__._converse,
__ = _converse.__,
scheme = ev.target.getAttribute('data-scheme');
var result, question, answer;
if (scheme === 'fingerprint') {
result = confirm(__('Here are the fingerprints, please confirm them with %1$s, outside of this chat.\n\nFingerprint for you, %2$s: %3$s\n\nFingerprint for %1$s: %4$s\n\nIf you have confirmed that the fingerprints match, click OK, otherwise click Cancel.', [this.model.get('fullname'), _converse.xmppstatus.get('fullname') || _converse.bare_jid, this.model.otr.priv.fingerprint(), this.model.otr.their_priv_pk.fingerprint()]));
if (result === true) {
this.model.save({
'otr_status': VERIFIED
});
} else {
this.model.save({
'otr_status': UNVERIFIED
});
}
} else if (scheme === 'smp') {
alert(__('You will be prompted to provide a security question and then an answer to that question.\n\nYour contact will then be prompted the same question and if they type the exact same answer (case sensitive), their identity will be verified.'));
question = prompt(__('What is your security question?'));
if (question) {
answer = prompt(__('What is the answer to the security question?'));
this.model.otr.smpSecret(answer, question);
}
} else {
this.showHelpMessages([__('Invalid authentication scheme provided')], 'error');
}
},
toggleOTRMenu: function toggleOTRMenu(ev) {
if (_.isUndefined(this.otr_dropdown)) {
ev.stopPropagation();
var dropdown_el = this.el.querySelector('.toggle-otr');
this.otr_dropdown = new bootstrap.Dropdown(dropdown_el, true);
this.otr_dropdown.toggle();
}
},
getOTRTooltip: function getOTRTooltip() {
var _converse = this.__super__._converse,
__ = _converse.__,
data = this.model.toJSON();
if (data.otr_status === UNENCRYPTED) {
return __('Your messages are not encrypted. Click here to enable OTR encryption.');
} else if (data.otr_status === UNVERIFIED) {
return __('Your messages are encrypted, but your contact has not been verified.');
} else if (data.otr_status === VERIFIED) {
return __('Your messages are encrypted and your contact verified.');
} else if (data.otr_status === FINISHED) {
return __('Your contact has closed their end of the private session, you should do the same');
}
},
addOTRToolbarButton: function addOTRToolbarButton(options) {
var _converse = this.__super__._converse,
__ = _converse.__,
data = this.model.toJSON();
options = _.extend(options || {}, {
FINISHED: FINISHED,
UNENCRYPTED: UNENCRYPTED,
UNVERIFIED: UNVERIFIED,
VERIFIED: VERIFIED,
// FIXME: Leaky abstraction MUC
allow_otr: _converse.allow_otr && !this.is_chatroom,
label_end_encrypted_conversation: __('End encrypted conversation'),
label_refresh_encrypted_conversation: __('Refresh encrypted conversation'),
label_start_encrypted_conversation: __('Start encrypted conversation'),
label_verify_with_fingerprints: __('Verify with fingerprints'),
label_verify_with_smp: __('Verify with SMP'),
label_whats_this: __("What\'s this?"),
otr_status_class: OTR_CLASS_MAPPING[data.otr_status],
otr_tooltip: this.getOTRTooltip(),
otr_translated_status: OTR_TRANSLATED_MAPPING[data.otr_status]
});
this.el.querySelector('.chat-toolbar').insertAdjacentHTML('beforeend', tpl_toolbar_otr(_.extend(data, options || {})));
},
getToolbarOptions: function getToolbarOptions(options) {
options = this.__super__.getToolbarOptions();
if (this.isOTREncryptedSession()) {
options.show_spoiler_button = false;
}
return options;
},
renderToolbar: function renderToolbar(toolbar, options) {
var result = this.__super__.renderToolbar.apply(this, arguments);
this.addOTRToolbarButton(options);
return result;
}
}
},
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
var _converse = this._converse,
__ = _converse.__;
2017-07-22 22:21:05 +02:00
_converse.api.settings.update({
allow_otr: true,
cache_otr_key: false,
use_otr_by_default: false
}); // Translation aware constants
// ---------------------------
// We can only call the __ translation method *after* converse.js
// has been initialized and with it the i18n machinery. That's why
// we do it here in the "initialize" method and not at the top of
// the module.
OTR_TRANSLATED_MAPPING[UNENCRYPTED] = __('unencrypted');
OTR_TRANSLATED_MAPPING[UNVERIFIED] = __('unverified');
OTR_TRANSLATED_MAPPING[VERIFIED] = __('verified');
OTR_TRANSLATED_MAPPING[FINISHED] = __('finished'); // Only allow OTR if we have the capability
_converse.allow_otr = _converse.allow_otr && HAS_CRYPTO; // Only use OTR by default if allow OTR is enabled to begin with
_converse.use_otr_by_default = _converse.use_otr_by_default && _converse.allow_otr;
}
});
});
//# sourceMappingURL=converse-otr.js.map;
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
2018-01-04 17:58:16 +01:00
/*global define */
2017-07-22 22:21:05 +02:00
/* This is a Converse.js plugin which add support for application-level pings
* as specified in XEP-0199 XMPP Ping.
*/
(function (root, factory) {
define('converse-ping',["converse-core", "strophe.ping"], factory);
})(this, function (converse) {
"use strict"; // Strophe methods for building stanzas
2017-02-03 13:51:07 +01:00
var _converse$env = converse.env,
Strophe = _converse$env.Strophe,
_ = _converse$env._;
converse.plugins.add('converse-ping', {
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
var _converse = this._converse;
2017-07-22 22:21:05 +02:00
_converse.api.settings.update({
ping_interval: 180 //in seconds
});
_converse.ping = function (jid, success, error, timeout) {
// XXX: We could first check here if the server advertised that
// it supports PING.
// However, some servers don't advertise while still keeping the
// connection option due to pings.
//
// var feature = _converse.disco_entities[_converse.domain].features.findWhere({'var': Strophe.NS.PING});
_converse.lastStanzaDate = new Date();
if (_.isNil(jid)) {
jid = Strophe.getDomainFromJid(_converse.bare_jid);
}
if (_.isUndefined(timeout)) {
timeout = null;
}
if (_.isUndefined(success)) {
success = null;
}
if (_.isUndefined(error)) {
error = null;
}
if (_converse.connection) {
_converse.connection.ping.ping(jid, success, error, timeout);
return true;
}
return false;
};
_converse.pong = function (ping) {
_converse.lastStanzaDate = new Date();
_converse.connection.ping.pong(ping);
return true;
};
_converse.registerPongHandler = function () {
if (!_.isUndefined(_converse.connection.disco)) {
2018-05-15 10:32:13 +02:00
_converse.api.disco.own.features.add(Strophe.NS.PING);
}
_converse.connection.ping.addPingHandler(_converse.pong);
};
2016-03-16 12:49:35 +01:00
_converse.registerPingHandler = function () {
_converse.registerPongHandler();
if (_converse.ping_interval > 0) {
_converse.connection.addHandler(function () {
/* Handler on each stanza, saves the received date
* in order to ping only when needed.
*/
_converse.lastStanzaDate = new Date();
return true;
});
_converse.connection.addTimedHandler(1000, function () {
var now = new Date();
if (!_converse.lastStanzaDate) {
_converse.lastStanzaDate = now;
}
if ((now - _converse.lastStanzaDate) / 1000 > _converse.ping_interval) {
return _converse.ping();
}
return true;
});
}
};
var onConnected = function onConnected() {
// Wrapper so that we can spy on registerPingHandler in tests
_converse.registerPingHandler();
};
_converse.on('connected', onConnected);
_converse.on('reconnected', onConnected);
}
});
2017-07-22 22:21:05 +02:00
});
//# sourceMappingURL=converse-ping.js.map;
2018-05-07 18:20:15 +02:00
// Converse.js
// http://conversejs.org
//
// Copyright (c) 2012-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2)
(function (root, factory) {
define('converse-roster',["converse-core"], factory);
}(this, function (converse) {
"use strict";
const { Backbone, Promise, Strophe, $iq, $pres, b64_sha1, moment, sizzle, _ } = converse.env;
const u = converse.env.utils;
converse.plugins.add('converse-roster', {
dependencies: ["converse-vcard"],
overrides: {
_tearDown () {
this.__super__._tearDown.apply(this, arguments);
}
},
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
const { _converse } = this,
{ __ } = _converse;
_converse.api.promises.add([
'cachedRoster',
'roster',
'rosterContactsFetched',
'rosterGroupsFetched',
'rosterInitialized',
]);
_converse.registerPresenceHandler = function () {
_converse.unregisterPresenceHandler();
_converse.presence_ref = _converse.connection.addHandler(
function (presence) {
_converse.roster.presenceHandler(presence);
return true;
}, null, 'presence', null);
};
_converse.initRoster = function () {
/* Initialize the Bakcbone collections that represent the contats
* roster and the roster groups.
*/
_converse.roster = new _converse.RosterContacts();
2018-05-23 04:27:33 +02:00
_converse.roster.browserStorage = new Backbone.BrowserStorage[_converse.storage](
2018-05-07 18:20:15 +02:00
b64_sha1(`converse.contacts-${_converse.bare_jid}`));
_converse.rostergroups = new _converse.RosterGroups();
2018-05-23 04:27:33 +02:00
_converse.rostergroups.browserStorage = new Backbone.BrowserStorage[_converse.storage](
2018-05-07 18:20:15 +02:00
b64_sha1(`converse.roster.groups${_converse.bare_jid}`));
_converse.emit('rosterInitialized');
};
_converse.populateRoster = function (ignore_cache=false) {
/* Fetch all the roster groups, and then the roster contacts.
* Emit an event after fetching is done in each case.
*
* Parameters:
* (Bool) ignore_cache - If set to to true, the local cache
* will be ignored it's guaranteed that the XMPP server
* will be queried for the roster.
*/
if (ignore_cache) {
_converse.send_initial_presence = true;
_converse.roster.fetchFromServer()
.then(() => {
_converse.emit('rosterContactsFetched');
_converse.sendInitialPresence();
}).catch((reason) => {
_converse.log(reason, Strophe.LogLevel.ERROR);
_converse.sendInitialPresence();
});
} else {
_converse.rostergroups.fetchRosterGroups().then(() => {
_converse.emit('rosterGroupsFetched');
return _converse.roster.fetchRosterContacts();
}).then(() => {
_converse.emit('rosterContactsFetched');
_converse.sendInitialPresence();
}).catch((reason) => {
_converse.log(reason, Strophe.LogLevel.ERROR);
_converse.sendInitialPresence();
});
}
};
2018-05-23 04:27:33 +02:00
_converse.Presence = Backbone.Model.extend({
defaults: {
'show': 'offline',
'resources': {}
},
getHighestPriorityResource () {
/* Return the resource with the highest priority.
*
* If multiple resources have the same priority, take the
* latest one.
*/
const resources = this.get('resources');
if (_.isObject(resources) && _.size(resources)) {
const val = _.flow(
_.values,
_.partial(_.sortBy, _, ['priority', 'timestamp']),
_.reverse
)(resources)[0];
if (!_.isUndefined(val)) {
return val;
}
}
},
addResource (presence) {
/* Adds a new resource and it's associated attributes as taken
* from the passed in presence stanza.
*
* Also updates the presence if the resource has higher priority (and is newer).
*/
const jid = presence.getAttribute('from'),
show = _.propertyOf(presence.querySelector('show'))('textContent') || 'online',
resource = Strophe.getResourceFromJid(jid),
delay = presence.querySelector(
`delay[xmlns="${Strophe.NS.DELAY}"]`
),
timestamp = _.isNull(delay) ? moment().format() : moment(delay.getAttribute('stamp')).format();
let priority = _.propertyOf(presence.querySelector('priority'))('textContent') || 0;
priority = _.isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10);
const resources = _.isObject(this.get('resources')) ? this.get('resources') : {};
resources[resource] = {
'name': resource,
'priority': priority,
'show': show,
'timestamp': timestamp
};
const changed = {'resources': resources};
const hpr = this.getHighestPriorityResource();
if (priority == hpr.priority && timestamp == hpr.timestamp) {
// Only set the "global" presence if this is the newest resource
// with the highest priority
changed.show = show;
}
this.save(changed);
return resources;
},
removeResource (resource) {
/* Remove the passed in resource from the resources map.
*
* Also redetermines the presence given that there's one less
* resource.
*/
let resources = this.get('resources');
if (!_.isObject(resources)) {
resources = {};
} else {
delete resources[resource];
}
this.save({
'resources': resources,
'show': _.propertyOf(
this.getHighestPriorityResource())('show') || 'offline'
});
},
});
_converse.Presences = Backbone.Collection.extend({
model: _converse.Presence,
});
_converse.ModelWithVCardAndPresence = Backbone.Model.extend({
initialize () {
this.setVCard();
this.setPresence();
},
setVCard () {
const jid = this.get('jid');
this.vcard = _converse.vcards.findWhere({'jid': jid}) || _converse.vcards.create({'jid': jid});
},
2018-05-07 18:20:15 +02:00
2018-05-23 04:27:33 +02:00
setPresence () {
const jid = this.get('jid');
this.presence = _converse.presences.findWhere({'jid': jid}) || _converse.presences.create({'jid': jid});
}
});
_converse.RosterContact = _converse.ModelWithVCardAndPresence.extend({
2018-05-07 18:20:15 +02:00
defaults: {
'chat_state': undefined,
'image': _converse.DEFAULT_IMAGE,
'image_type': _converse.DEFAULT_IMAGE_TYPE,
'num_unread': 0,
'status': '',
},
initialize (attributes) {
2018-05-23 04:27:33 +02:00
_converse.ModelWithVCardAndPresence.prototype.initialize.apply(this, arguments);
2018-05-07 18:20:15 +02:00
const { jid } = attributes,
bare_jid = Strophe.getBareJidFromJid(jid).toLowerCase(),
resource = Strophe.getResourceFromJid(jid);
attributes.jid = bare_jid;
this.set(_.assignIn({
'groups': [],
'id': bare_jid,
'jid': bare_jid,
'user_id': Strophe.getNodeFromJid(jid)
}, attributes));
2018-05-23 04:27:33 +02:00
this.presence.on('change:show', () => _converse.emit('contactPresenceChanged', this));
this.presence.on('change:show', () => this.trigger('presenceChanged'));
2018-05-07 18:20:15 +02:00
},
getDisplayName () {
return this.vcard.get('fullname') || this.get('jid');
},
getFullname () {
return this.vcard.get('fullname');
},
subscribe (message) {
/* Send a presence subscription request to this roster contact
*
* Parameters:
* (String) message - An optional message to explain the
* reason for the subscription request.
*/
const pres = $pres({to: this.get('jid'), type: "subscribe"});
if (message && message !== "") {
pres.c("status").t(message).up();
}
2018-05-08 19:58:12 +02:00
const nick = _converse.xmppstatus.vcard.get('nickname') || _converse.xmppstatus.vcard.get('fullname');
2018-05-07 18:20:15 +02:00
if (nick) {
pres.c('nick', {'xmlns': Strophe.NS.NICK}).t(nick).up();
}
_converse.connection.send(pres);
this.save('ask', "subscribe"); // ask === 'subscribe' Means we have asked to subscribe to them.
return this;
},
ackSubscribe () {
/* Upon receiving the presence stanza of type "subscribed",
* the user SHOULD acknowledge receipt of that subscription
* state notification by sending a presence stanza of type
* "subscribe" to the contact
*/
_converse.connection.send($pres({
'type': 'subscribe',
'to': this.get('jid')
}));
},
ackUnsubscribe () {
/* Upon receiving the presence stanza of type "unsubscribed",
* the user SHOULD acknowledge receipt of that subscription state
* notification by sending a presence stanza of type "unsubscribe"
* this step lets the user's server know that it MUST no longer
* send notification of the subscription state change to the user.
* Parameters:
* (String) jid - The Jabber ID of the user who is unsubscribing
*/
_converse.connection.send($pres({'type': 'unsubscribe', 'to': this.get('jid')}));
this.removeFromRoster();
this.destroy();
},
unauthorize (message) {
/* Unauthorize this contact's presence subscription
* Parameters:
* (String) message - Optional message to send to the person being unauthorized
*/
_converse.rejectPresenceSubscription(this.get('jid'), message);
return this;
},
authorize (message) {
/* Authorize presence subscription
* Parameters:
* (String) message - Optional message to send to the person being authorized
*/
const pres = $pres({'to': this.get('jid'), 'type': "subscribed"});
if (message && message !== "") {
pres.c("status").t(message);
}
_converse.connection.send(pres);
return this;
},
removeFromRoster (callback, errback) {
/* Instruct the XMPP server to remove this contact from our roster
* Parameters:
* (Function) callback
*/
const iq = $iq({type: 'set'})
.c('query', {xmlns: Strophe.NS.ROSTER})
.c('item', {jid: this.get('jid'), subscription: "remove"});
_converse.connection.sendIQ(iq, callback, errback);
return this;
}
});
_converse.RosterContacts = Backbone.Collection.extend({
model: _converse.RosterContact,
comparator (contact1, contact2) {
2018-05-23 04:27:33 +02:00
const status1 = contact1.presence.get('show') || 'offline';
const status2 = contact2.presence.get('show') || 'offline';
2018-05-07 18:20:15 +02:00
if (_converse.STATUS_WEIGHTS[status1] === _converse.STATUS_WEIGHTS[status2]) {
const name1 = (contact1.getDisplayName()).toLowerCase();
const name2 = (contact2.getDisplayName()).toLowerCase();
return name1 < name2 ? -1 : (name1 > name2? 1 : 0);
} else {
return _converse.STATUS_WEIGHTS[status1] < _converse.STATUS_WEIGHTS[status2] ? -1 : 1;
}
},
onConnected () {
/* Called as soon as the connection has been established
* (either after initial login, or after reconnection).
*
* Use the opportunity to register stanza handlers.
*/
this.registerRosterHandler();
this.registerRosterXHandler();
},
registerRosterHandler () {
/* Register a handler for roster IQ "set" stanzas, which update
* roster contacts.
*/
_converse.connection.addHandler(
_converse.roster.onRosterPush.bind(_converse.roster),
Strophe.NS.ROSTER, 'iq', "set"
);
},
registerRosterXHandler () {
/* Register a handler for RosterX message stanzas, which are
* used to suggest roster contacts to a user.
*/
let t = 0;
_converse.connection.addHandler(
function (msg) {
window.setTimeout(
function () {
_converse.connection.flush();
_converse.roster.subscribeToSuggestedItems.bind(_converse.roster)(msg);
}, t);
t += msg.querySelectorAll('item').length*250;
return true;
},
Strophe.NS.ROSTERX, 'message', null
);
},
fetchRosterContacts () {
/* Fetches the roster contacts, first by trying the
2018-05-23 04:27:33 +02:00
* sessionStorage cache, and if that's empty, then by querying
* the XMPP server.
*
* Returns a promise which resolves once the contacts have been
* fetched.
*/
2018-05-07 18:20:15 +02:00
return new Promise((resolve, reject) => {
this.fetch({
'add': true,
'silent': true,
success (collection) {
if (collection.length === 0) {
_converse.send_initial_presence = true;
_converse.roster.fetchFromServer().then(resolve).catch(reject);
} else {
_converse.emit('cachedRoster', collection);
resolve();
}
}
});
});
},
subscribeToSuggestedItems (msg) {
_.each(msg.querySelectorAll('item'), function (item) {
if (item.getAttribute('action') === 'add') {
_converse.roster.addAndSubscribe(
item.getAttribute('jid'),
2018-05-08 19:58:12 +02:00
_converse.xmppstatus.vcard.get('nickname') || _converse.xmppstatus.vcard.get('fullname')
2018-05-07 18:20:15 +02:00
);
}
});
return true;
},
isSelf (jid) {
return u.isSameBareJID(jid, _converse.connection.jid);
},
addAndSubscribe (jid, name, groups, message, attributes) {
/* Add a roster contact and then once we have confirmation from
2018-05-11 00:19:49 +02:00
* the XMPP server we subscribe to that contact's presence updates.
* Parameters:
* (String) jid - The Jabber ID of the user being added and subscribed to.
* (String) name - The name of that user
* (Array of Strings) groups - Any roster groups the user might belong to
* (String) message - An optional message to explain the
* reason for the subscription request.
* (Object) attributes - Any additional attributes to be stored on the user's model.
*/
2018-05-07 18:20:15 +02:00
const handler = (contact) => {
if (contact instanceof _converse.RosterContact) {
contact.subscribe(message);
}
}
this.addContactToRoster(jid, name, groups, attributes).then(handler, handler);
},
sendContactAddIQ (jid, name, groups, callback, errback) {
/* Send an IQ stanza to the XMPP server to add a new roster contact.
2018-05-11 00:19:49 +02:00
*
* Parameters:
* (String) jid - The Jabber ID of the user being added
* (String) name - The name of that user
* (Array of Strings) groups - Any roster groups the user might belong to
* (Function) callback - A function to call once the IQ is returned
* (Function) errback - A function to call if an error occured
*/
2018-05-07 18:20:15 +02:00
name = _.isEmpty(name)? jid: name;
const iq = $iq({type: 'set'})
.c('query', {xmlns: Strophe.NS.ROSTER})
.c('item', { jid, name });
_.each(groups, function (group) { iq.c('group').t(group).up(); });
_converse.connection.sendIQ(iq, callback, errback);
},
addContactToRoster (jid, name, groups, attributes) {
/* Adds a RosterContact instance to _converse.roster and
2018-05-11 00:19:49 +02:00
* registers the contact on the XMPP server.
* Returns a promise which is resolved once the XMPP server has
* responded.
*
* Parameters:
* (String) jid - The Jabber ID of the user being added and subscribed to.
* (String) name - The name of that user
* (Array of Strings) groups - Any roster groups the user might belong to
* (Object) attributes - Any additional attributes to be stored on the user's model.
*/
2018-05-07 18:20:15 +02:00
return new Promise((resolve, reject) => {
groups = groups || [];
this.sendContactAddIQ(jid, name, groups,
() => {
const contact = this.create(_.assignIn({
'ask': undefined,
'nickname': name,
groups,
jid,
'requesting': false,
'subscription': 'none'
}, attributes), {sort: false});
resolve(contact);
},
function (err) {
alert(__('Sorry, there was an error while trying to add %1$s as a contact.', name));
_converse.log(err, Strophe.LogLevel.ERROR);
resolve(err);
}
);
});
},
subscribeBack (bare_jid, presence) {
const contact = this.get(bare_jid);
if (contact instanceof _converse.RosterContact) {
contact.authorize().subscribe();
} else {
// Can happen when a subscription is retried or roster was deleted
const handler = (contact) => {
if (contact instanceof _converse.RosterContact) {
contact.authorize().subscribe();
}
}
const nickname = _.get(sizzle(`nick[xmlns="${Strophe.NS.NICK}"]`, presence).pop(), 'textContent', null);
this.addContactToRoster(bare_jid, nickname, [], {'subscription': 'from'}).then(handler, handler);
}
},
getNumOnlineContacts () {
let ignored = ['offline', 'unavailable'];
if (_converse.show_only_online_users) {
ignored = _.union(ignored, ['dnd', 'xa', 'away']);
}
2018-05-23 04:27:33 +02:00
return _.sum(this.models.filter((model) => !_.includes(ignored, model.presence.get('show'))));
2018-05-07 18:20:15 +02:00
},
onRosterPush (iq) {
/* Handle roster updates from the XMPP server.
* See: https://xmpp.org/rfcs/rfc6121.html#roster-syntax-actions-push
*
* Parameters:
* (XMLElement) IQ - The IQ stanza received from the XMPP server.
*/
const id = iq.getAttribute('id');
const from = iq.getAttribute('from');
if (from && from !== "" && Strophe.getBareJidFromJid(from) !== _converse.bare_jid) {
// Receiving client MUST ignore stanza unless it has no from or from = user's bare JID.
// XXX: Some naughty servers apparently send from a full
// JID so we need to explicitly compare bare jids here.
// https://github.com/jcbrand/converse.js/issues/493
_converse.connection.send(
$iq({type: 'error', id, from: _converse.connection.jid})
.c('error', {'type': 'cancel'})
.c('service-unavailable', {'xmlns': Strophe.NS.ROSTER })
);
return true;
}
_converse.connection.send($iq({type: 'result', id, from: _converse.connection.jid}));
const items = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"] item`, iq);
_.each(items, this.updateContact.bind(this));
_converse.emit('rosterPush', iq);
return true;
},
fetchFromServer () {
/* Fetch the roster from the XMPP server */
return new Promise((resolve, reject) => {
const iq = $iq({
'type': 'get',
'id': _converse.connection.getUniqueId('roster')
}).c('query', {xmlns: Strophe.NS.ROSTER});
const callback = _.flow(this.onReceivedFromServer.bind(this), resolve);
const errback = function (iq) {
const errmsg = "Error while trying to fetch roster from the server";
_converse.log(errmsg, Strophe.LogLevel.ERROR);
reject(new Error(errmsg));
}
return _converse.connection.sendIQ(iq, callback, errback);
});
},
onReceivedFromServer (iq) {
/* An IQ stanza containing the roster has been received from
* the XMPP server.
*/
const items = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"] item`, iq);
_.each(items, this.updateContact.bind(this));
_converse.emit('roster', iq);
},
updateContact (item) {
/* Update or create RosterContact models based on items
* received in the IQ from the server.
*/
const jid = item.getAttribute('jid');
if (this.isSelf(jid)) { return; }
const contact = this.get(jid),
subscription = item.getAttribute("subscription"),
ask = item.getAttribute("ask"),
groups = _.map(item.getElementsByTagName('group'), Strophe.getText);
if (!contact) {
if ((subscription === "none" && ask === null) || (subscription === "remove")) {
return; // We're lazy when adding contacts.
}
this.create({
'ask': ask,
'nickname': item.getAttribute("name"),
'groups': groups,
'jid': jid,
'subscription': subscription
}, {sort: false});
} else {
if (subscription === "remove") {
return contact.destroy();
}
// We only find out about requesting contacts via the
// presence handler, so if we receive a contact
// here, we know they aren't requesting anymore.
// see docs/DEVELOPER.rst
contact.save({
'subscription': subscription,
'ask': ask,
'requesting': null,
'groups': groups
});
}
},
createRequestingContact (presence) {
const bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from')),
nickname = _.get(sizzle(`nick[xmlns="${Strophe.NS.NICK}"]`, presence).pop(), 'textContent', null);
const user_data = {
'jid': bare_jid,
'subscription': 'none',
'ask': null,
'requesting': true,
'nickname': nickname
};
_converse.emit('contactRequest', this.create(user_data));
},
handleIncomingSubscription (presence) {
const jid = presence.getAttribute('from'),
bare_jid = Strophe.getBareJidFromJid(jid),
contact = this.get(bare_jid);
if (!_converse.allow_contact_requests) {
_converse.rejectPresenceSubscription(
jid,
__("This client does not allow presence subscriptions")
);
}
if (_converse.auto_subscribe) {
if ((!contact) || (contact.get('subscription') !== 'to')) {
this.subscribeBack(bare_jid, presence);
} else {
contact.authorize();
}
} else {
if (contact) {
if (contact.get('subscription') !== 'none') {
contact.authorize();
} else if (contact.get('ask') === "subscribe") {
contact.authorize();
}
} else {
this.createRequestingContact(presence);
}
}
},
presenceHandler (presence) {
const presence_type = presence.getAttribute('type');
if (presence_type === 'error') { return true; }
const jid = presence.getAttribute('from'),
bare_jid = Strophe.getBareJidFromJid(jid),
resource = Strophe.getResourceFromJid(jid),
status_message = _.propertyOf(presence.querySelector('status'))('textContent'),
contact = this.get(bare_jid);
if (this.isSelf(bare_jid)) {
if ((_converse.connection.jid !== jid) &&
(presence_type !== 'unavailable') &&
(_converse.synchronize_availability === true ||
_converse.synchronize_availability === resource)) {
// Another resource has changed its status and
// synchronize_availability option set to update,
// we'll update ours as well.
2018-05-23 04:27:33 +02:00
const show = _.propertyOf(presence.querySelector('show'))('textContent') || 'online';
_converse.xmppstatus.save({'status': show});
2018-05-07 18:20:15 +02:00
if (status_message) {
_converse.xmppstatus.save({'status_message': status_message});
}
}
if (_converse.jid === jid && presence_type === 'unavailable') {
// XXX: We've received an "unavailable" presence from our
// own resource. Apparently this happens due to a
// Prosody bug, whereby we send an IQ stanza to remove
// a roster contact, and Prosody then sends
// "unavailable" globally, instead of directed to the
// particular user that's removed.
//
// Here is the bug report: https://prosody.im/issues/1121
//
// I'm not sure whether this might legitimately happen
// in other cases.
//
// As a workaround for now we simply send our presence again,
// otherwise we're treated as offline.
_converse.xmppstatus.sendPresence();
}
return;
} else if (sizzle(`query[xmlns="${Strophe.NS.MUC}"]`, presence).length) {
return; // Ignore MUC
}
if (contact && (status_message !== contact.get('status'))) {
contact.save({'status': status_message});
}
if (presence_type === 'subscribed' && contact) {
contact.ackSubscribe();
} else if (presence_type === 'unsubscribed' && contact) {
contact.ackUnsubscribe();
} else if (presence_type === 'unsubscribe') {
return;
} else if (presence_type === 'subscribe') {
this.handleIncomingSubscription(presence);
} else if (presence_type === 'unavailable' && contact) {
2018-05-23 04:27:33 +02:00
contact.presence.removeResource(resource);
2018-05-07 18:20:15 +02:00
} else if (contact) {
// presence_type is undefined
2018-05-23 04:27:33 +02:00
contact.presence.addResource(presence);
2018-05-07 18:20:15 +02:00
}
}
});
_converse.RosterGroup = Backbone.Model.extend({
initialize (attributes) {
this.set(_.assignIn({
description: __('Click to hide these contacts'),
state: _converse.OPENED
}, attributes));
// Collection of contacts belonging to this group.
this.contacts = new _converse.RosterContacts();
}
});
_converse.RosterGroups = Backbone.Collection.extend({
model: _converse.RosterGroup,
fetchRosterGroups () {
/* Fetches all the roster groups from sessionStorage.
*
* Returns a promise which resolves once the groups have been
* returned.
*/
return new Promise((resolve, reject) => {
this.fetch({
silent: true, // We need to first have all groups before
// we can start positioning them, so we set
// 'silent' to true.
success: resolve
});
});
}
});
2018-05-23 04:27:33 +02:00
_converse.unregisterPresenceHandler = function () {
if (!_.isUndefined(_converse.presence_ref)) {
_converse.connection.deleteHandler(_converse.presence_ref);
delete _converse.presence_ref;
}
};
2018-05-07 18:20:15 +02:00
/********** Event Handlers *************/
2018-05-23 04:27:33 +02:00
_converse.api.listen.on('beforeTearDown', _converse.unregisterPresenceHandler());
_converse.api.listen.on('afterTearDown', () => {
if (_converse.presence) {
_converse.presences.off().reset(); // Remove presences
}
});
_converse.api.listen.on('clearSession', () => {
if (!_.isUndefined(this.roster)) {
this.roster.browserStorage._clear();
}
});
_converse.api.listen.on('connectionInitialized', () => {
_converse.presences = new _converse.Presences();
_converse.presences.browserStorage =
new Backbone.BrowserStorage.session(b64_sha1(`converse.presences-${_converse.bare_jid}`));
_converse.presences.fetch();
});
2018-05-07 18:20:15 +02:00
_converse.api.listen.on('statusInitialized', (reconnecting) => {
if (reconnecting) {
// No need to recreate the roster, otherwise we lose our
// cached data. However we still emit an event, to give
// event handlers a chance to register views for the
// roster and its groups, before we start populating.
_converse.emit('rosterReadyAfterReconnection');
} else {
_converse.registerIntervalHandler();
_converse.initRoster();
}
_converse.roster.onConnected();
_converse.populateRoster(reconnecting);
_converse.registerPresenceHandler();
});
/************************ API ************************/
// API methods only available to plugins
_.extend(_converse.api, {
'contacts': {
'get' (jids) {
const _getter = function (jid) {
return _converse.roster.get(Strophe.getBareJidFromJid(jid)) || null;
};
if (_.isUndefined(jids)) {
jids = _converse.roster.pluck('jid');
} else if (_.isString(jids)) {
return _getter(jids);
}
return _.map(jids, _getter);
},
'add' (jid, name) {
if (!_.isString(jid) || !_.includes(jid, '@')) {
throw new TypeError('contacts.add: invalid jid');
}
_converse.roster.addAndSubscribe(jid, _.isEmpty(name)? jid: name);
}
}
});
}
});
}));
define('tpl!register_link', ['lodash'], function(_) {return function(o) {
2017-02-13 17:16:13 +01:00
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<div class="switch-form">\n ';
if (!o._converse.auto_login && o._converse.CONNECTION_STATUS[o.connection_status] !== 'CONNECTING') { ;
__p += '\n <p>' +
__e( o.__("Don't have a chat account?") ) +
'</p>\n <p><a class="register-account toggle-register-login" href="#converse/register">' +
__e(o.__("Create an account")) +
'</a></p>\n ';
} ;
__p += '\n</div>\n';
return __p
};});
define('tpl!register_panel', ['lodash'], function(_) {return function(o) {
2017-02-13 17:16:13 +01:00
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<div class="row">\n <form id="converse-register" class="converse-form">\n <legend>' +
__e(o.__("Create your account")) +
'</legend>\n\n <div class="form-group">\n <label>' +
__e(o.__("Please enter the XMPP provider to register with:")) +
'</label>\n <div class="form-errors hidden"></div>\n\n ';
if (o.default_domain) { ;
__p += '\n ' +
__e(o.default_domain) +
'\n </div>\n ';
2018-03-25 21:21:43 +02:00
} ;
__p += '\n ';
if (!o.default_domain) { ;
__p += '\n <input class="form-control" autofocus="autofocus" required="required" type="text" name="domain" placeholder="' +
__e(o.domain_placeholder) +
'">\n <p class="form-text text-muted">' +
__e(o.help_providers) +
' <a href="' +
__e(o.href_providers) +
'" class="url" target="_blank" rel="noopener">' +
__e(o.help_providers_link) +
'</a>.</p>\n </div>\n <fieldset class="buttons">\n <input class="btn btn-primary" type="submit" value="' +
__e(o.label_register) +
'">\n <div class="switch-form">\n <p>' +
__e( o.__("Already have a chat account?") ) +
'</p>\n <p><a class="login-here toggle-register-login" href="#converse/login">' +
__e(o.__("Log in here")) +
'</a></p>\n </div>\n </fieldset>\n ';
} ;
__p += '\n </div>\n </form>\n</div>\n';
return __p
};});
define('tpl!registration_form', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<legend>' +
__e(o.__("Account Registration:")) +
' ' +
__e(o.domain) +
'</legend>\n<p class="title">' +
__e(o.title) +
'</p>\n<p class="instructions">' +
__e(o.instructions) +
'</p>\n<div class="form-errors hidden"></div>\n\n<fieldset class="buttons">\n <input type="submit" class="btn btn-primary" value="' +
__e(o.__('Register')) +
'"/>\n ';
if (!o.registration_domain) { ;
__p += '\n <input type="button" class="btn btn-secondary button-cancel" value="' +
__e(o.__('Choose a different provider')) +
'"/>\n ';
} ;
__p += '\n</fieldset>\n';
return __p
};});
define('tpl!registration_request', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
__p += '<span class="spinner login-submit fa fa-spinner"></span>\n<p class="info">' +
__e(o.__("Hold tight, we're fetching the registration form…")) +
'</p>\n';
if (o.cancel) { ;
__p += '\n <button class="btn btn-secondary button-cancel hor_centered">' +
__e(o.__('Cancel')) +
'</button>\n';
} ;
__p += '\n';
return __p
};});
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define */
/* This is a Converse.js plugin which add support for in-band registration
* as specified in XEP-0077.
*/
(function (root, factory) {
define('converse-register',["form-utils", "converse-core", "tpl!form_username", "tpl!register_link", "tpl!register_panel", "tpl!registration_form", "tpl!registration_request", "tpl!form_input", "tpl!spinner", "converse-controlbox"], factory);
})(this, function (utils, converse, tpl_form_username, tpl_register_link, tpl_register_panel, tpl_registration_form, tpl_registration_request, tpl_form_input, tpl_spinner) {
"use strict"; // Strophe methods for building stanzas
var _converse$env = converse.env,
Strophe = _converse$env.Strophe,
Backbone = _converse$env.Backbone,
sizzle = _converse$env.sizzle,
$iq = _converse$env.$iq,
_ = _converse$env._; // Add Strophe Namespaces
Strophe.addNamespace('REGISTER', 'jabber:iq:register'); // Add Strophe Statuses
var i = 0;
_.each(_.keys(Strophe.Status), function (key) {
i = Math.max(i, Strophe.Status[key]);
});
Strophe.Status.REGIFAIL = i + 1;
Strophe.Status.REGISTERED = i + 2;
Strophe.Status.CONFLICT = i + 3;
Strophe.Status.NOTACCEPTABLE = i + 5;
converse.plugins.add('converse-register', {
'overrides': {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
LoginPanel: {
render: function render(cfg) {
var _converse = this.__super__._converse;
this.__super__.render.apply(this, arguments);
if (_converse.allow_registration) {
if (_.isUndefined(this.registerlinkview)) {
this.registerlinkview = new _converse.RegisterLinkView({
'model': this.model
});
this.registerlinkview.render();
this.el.querySelector('.buttons').insertAdjacentElement('beforeend', this.registerlinkview.el);
}
this.registerlinkview.render();
}
return this;
}
},
ControlBoxView: {
initialize: function initialize() {
this.__super__.initialize.apply(this, arguments);
2016-11-07 15:43:48 +01:00
this.model.on('change:active-form', this.showLoginOrRegisterForm.bind(this));
},
showLoginOrRegisterForm: function showLoginOrRegisterForm() {
var _converse = this.__super__._converse;
if (_.isNil(this.registerpanel)) {
return;
}
2016-11-30 17:27:20 +01:00
if (this.model.get('active-form') == "register") {
this.loginpanel.el.classList.add('hidden');
this.registerpanel.el.classList.remove('hidden');
} else {
this.loginpanel.el.classList.remove('hidden');
this.registerpanel.el.classList.add('hidden');
}
},
renderRegistrationPanel: function renderRegistrationPanel() {
var _converse = this.__super__._converse;
if (_converse.allow_registration) {
this.registerpanel = new _converse.RegisterPanel({
'model': this.model
});
this.registerpanel.render();
this.registerpanel.el.classList.add('hidden');
this.el.querySelector('#converse-login-panel').insertAdjacentElement('afterend', this.registerpanel.el);
this.showLoginOrRegisterForm();
}
return this;
},
renderLoginPanel: function renderLoginPanel() {
/* Also render a registration panel, when rendering the
* login panel.
*/
this.__super__.renderLoginPanel.apply(this, arguments);
this.renderRegistrationPanel();
return this;
}
}
},
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
var _converse = this._converse,
__ = _converse.__;
_converse.CONNECTION_STATUS[Strophe.Status.REGIFAIL] = 'REGIFAIL';
_converse.CONNECTION_STATUS[Strophe.Status.REGISTERED] = 'REGISTERED';
_converse.CONNECTION_STATUS[Strophe.Status.CONFLICT] = 'CONFLICT';
_converse.CONNECTION_STATUS[Strophe.Status.NOTACCEPTABLE] = 'NOTACCEPTABLE';
_converse.api.settings.update({
allow_registration: true,
domain_placeholder: __(" e.g. conversejs.org"),
// Placeholder text shown in the domain input on the registration form
providers_link: 'https://xmpp.net/directory.php' // Link to XMPP providers shown on registration page
});
function setActiveForm(value) {
_converse.api.waitUntil('controlboxInitialized').then(function () {
var controlbox = _converse.chatboxes.get('controlbox');
2016-03-16 12:49:35 +01:00
controlbox.set({
'active-form': value
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}
2017-07-22 22:21:05 +02:00
_converse.router.route('converse/login', _.partial(setActiveForm, 'login'));
2016-03-07 18:54:07 +01:00
_converse.router.route('converse/register', _.partial(setActiveForm, 'register'));
2017-07-22 22:21:05 +02:00
_converse.RegisterLinkView = Backbone.VDOMView.extend({
toHTML: function toHTML() {
return tpl_register_link(_.extend(this.model.toJSON(), {
'__': _converse.__,
'_converse': _converse,
'connection_status': _converse.connfeedback.get('connection_status')
}));
}
});
_converse.RegisterPanel = Backbone.NativeView.extend({
tagName: 'div',
id: "converse-register-panel",
className: 'controlbox-pane fade-in',
events: {
'submit form#converse-register': 'onFormSubmission',
'click .button-cancel': 'renderProviderChoiceForm'
},
initialize: function initialize(cfg) {
this.reset();
this.registerHooks();
},
render: function render() {
this.model.set('registration_form_rendered', false);
this.el.innerHTML = tpl_register_panel({
'__': __,
'default_domain': _converse.registration_domain,
'label_register': __('Fetch registration form'),
'help_providers': __('Tip: A list of public XMPP providers is available'),
'help_providers_link': __('here'),
'href_providers': _converse.providers_link,
'domain_placeholder': _converse.domain_placeholder
});
2016-03-07 18:54:07 +01:00
if (_converse.registration_domain) {
this.fetchRegistrationForm(_converse.registration_domain);
}
2016-03-07 18:54:07 +01:00
return this;
},
registerHooks: function registerHooks() {
var _this = this;
/* Hook into Strophe's _connect_cb, so that we can send an IQ
* requesting the registration fields.
*/
var conn = _converse.connection;
var connect_cb = conn._connect_cb.bind(conn);
conn._connect_cb = function (req, callback, raw) {
if (!_this._registering) {
connect_cb(req, callback, raw);
} else {
if (_this.getRegistrationFields(req, callback, raw)) {
_this._registering = false;
}
}
};
},
getRegistrationFields: function getRegistrationFields(req, _callback, raw) {
/* Send an IQ stanza to the XMPP server asking for the
* registration fields.
* Parameters:
* (Strophe.Request) req - The current request
* (Function) callback
*/
var conn = _converse.connection;
conn.connected = true;
var body = conn._proto._reqToData(req);
if (!body) {
return;
}
2017-07-22 22:21:05 +02:00
if (conn._proto._connect_cb(body) === Strophe.Status.CONNFAIL) {
this.showValidationError(__("Sorry, we're unable to connect to your chosen provider."));
return false;
}
2017-07-22 22:21:05 +02:00
var register = body.getElementsByTagName("register");
var mechanisms = body.getElementsByTagName("mechanism");
if (register.length === 0 && mechanisms.length === 0) {
conn._proto._no_auth_received(_callback);
2017-07-22 22:21:05 +02:00
return false;
}
if (register.length === 0) {
conn._changeConnectStatus(Strophe.Status.REGIFAIL);
this.showValidationError(__("Sorry, the given provider does not support in " + "band account registration. Please try with a " + "different provider."));
return true;
} // Send an IQ stanza to get all required data fields
conn._addSysHandler(this.onRegistrationFields.bind(this), null, "iq", null, null);
var stanza = $iq({
type: "get"
}).c("query", {
xmlns: Strophe.NS.REGISTER
}).tree();
stanza.setAttribute("id", conn.getUniqueId("sendIQ"));
conn.send(stanza);
conn.connected = false;
return true;
},
onRegistrationFields: function onRegistrationFields(stanza) {
/* Handler for Registration Fields Request.
*
* Parameters:
* (XMLElement) elem - The query stanza.
*/
if (stanza.getAttribute("type") === "error") {
_converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, __('Something went wrong while establishing a connection with "%1$s". ' + 'Are you sure it exists?', this.domain));
return false;
}
if (stanza.getElementsByTagName("query").length !== 1) {
_converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, "unknown");
return false;
}
2016-03-16 12:49:35 +01:00
this.setFields(stanza);
if (!this.model.get('registration_form_rendered')) {
this.renderRegistrationForm(stanza);
}
2017-07-22 22:21:05 +02:00
return false;
},
reset: function reset(settings) {
var defaults = {
fields: {},
urls: [],
title: "",
instructions: "",
registered: false,
_registering: false,
domain: null,
form_type: null
};
_.extend(this, defaults);
if (settings) {
_.extend(this, _.pick(settings, _.keys(defaults)));
}
},
onFormSubmission: function onFormSubmission(ev) {
/* Event handler when the #converse-register form is
* submitted.
*
* Depending on the available input fields, we delegate to
* other methods.
*/
if (ev && ev.preventDefault) {
ev.preventDefault();
}
2017-02-03 13:51:07 +01:00
if (_.isNull(ev.target.querySelector('input[name=domain]'))) {
this.submitRegistrationForm(ev.target);
} else {
this.onProviderChosen(ev.target);
}
},
onProviderChosen: function onProviderChosen(form) {
/* Callback method that gets called when the user has chosen an
* XMPP provider.
*
* Parameters:
* (HTMLElement) form - The form that was submitted
*/
var domain_input = form.querySelector('input[name=domain]'),
domain = _.get(domain_input, 'value');
if (!domain) {
// TODO: add validation message
domain_input.classList.add('error');
2018-01-04 17:58:16 +01:00
return;
}
2017-07-22 22:21:05 +02:00
form.querySelector('input[type=submit]').classList.add('hidden');
this.fetchRegistrationForm(domain.trim());
2018-01-04 17:58:16 +01:00
},
fetchRegistrationForm: function fetchRegistrationForm(domain_name) {
/* This is called with a domain name based on which, it fetches a
* registration form from the requested domain.
*
* Parameters:
* (String) domain_name - XMPP server domain
*/
if (!this.model.get('registration_form_rendered')) {
this.renderRegistrationRequest();
}
this.reset({
domain: Strophe.getDomainFromJid(domain_name),
_registering: true
});
_converse.connection.connect(this.domain, "", this.onConnectStatusChanged.bind(this));
return false;
},
renderRegistrationRequest: function renderRegistrationRequest() {
/* Clear the form and inform the user that the registration
* form is being fetched.
*/
this.clearRegistrationForm().insertAdjacentHTML('beforeend', tpl_registration_request({
'__': _converse.__,
'cancel': _converse.registration_domain
2018-01-04 17:58:16 +01:00
}));
},
giveFeedback: function giveFeedback(message, klass) {
var feedback = this.el.querySelector('.reg-feedback');
if (!_.isNull(feedback)) {
feedback.parentNode.removeChild(feedback);
}
2016-03-16 12:49:35 +01:00
var form = this.el.querySelector('form');
form.insertAdjacentHTML('afterbegin', '<span class="reg-feedback"></span>');
feedback = form.querySelector('.reg-feedback');
feedback.textContent = message;
if (klass) {
feedback.classList.add(klass);
}
},
clearRegistrationForm: function clearRegistrationForm() {
var form = this.el.querySelector('form');
form.innerHTML = '';
this.model.set('registration_form_rendered', false);
return form;
},
showSpinner: function showSpinner() {
var form = this.el.querySelector('form');
form.innerHTML = tpl_spinner();
this.model.set('registration_form_rendered', false);
return this;
},
onConnectStatusChanged: function onConnectStatusChanged(status_code) {
/* Callback function called by Strophe whenever the
* connection status changes.
*
* Passed to Strophe specifically during a registration
* attempt.
*
* Parameters:
* (Integer) status_code - The Stroph.Status status code
*/
_converse.log('converse-register: onConnectStatusChanged');
2017-02-01 12:05:32 +01:00
if (_.includes([Strophe.Status.DISCONNECTED, Strophe.Status.CONNFAIL, Strophe.Status.REGIFAIL, Strophe.Status.NOTACCEPTABLE, Strophe.Status.CONFLICT], status_code)) {
_converse.log("Problem during registration: Strophe.Status is ".concat(_converse.CONNECTION_STATUS[status_code]), Strophe.LogLevel.ERROR);
this.abortRegistration();
} else if (status_code === Strophe.Status.REGISTERED) {
_converse.log("Registered successfully.");
_converse.connection.reset();
2016-12-13 20:46:07 +01:00
this.showSpinner();
2016-12-13 20:46:07 +01:00
if (_.includes(["converse/login", "converse/register"], Backbone.history.getFragment())) {
_converse.router.navigate('', {
'replace': true
});
}
2016-03-16 12:49:35 +01:00
if (this.fields.password && this.fields.username) {
// automatically log the user in
_converse.connection.connect(this.fields.username.toLowerCase() + '@' + this.domain.toLowerCase(), this.fields.password, _converse.onConnectStatusChanged);
2016-03-16 12:49:35 +01:00
this.giveFeedback(__('Now logging you in'), 'info');
} else {
_converse.chatboxviews.get('controlbox').renderLoginPanel();
2017-07-22 22:21:05 +02:00
_converse.giveFeedback(__('Registered successfully'));
}
2017-07-22 22:21:05 +02:00
this.reset();
}
},
renderLegacyRegistrationForm: function renderLegacyRegistrationForm(form) {
var _this2 = this;
2017-06-23 20:25:33 +02:00
_.each(_.keys(this.fields), function (key) {
if (key === "username") {
form.insertAdjacentHTML('beforeend', tpl_form_username({
'domain': " @".concat(_this2.domain),
'name': key,
'type': "text",
'label': key,
'value': '',
'required': true
}));
} else {
form.insertAdjacentHTML('beforeend', tpl_form_input({
'label': key,
'name': key,
'placeholder': key,
'required': true,
'type': key === 'password' || key === 'email' ? key : "text",
'value': ''
}));
}
}); // Show urls
_.each(this.urls, function (url) {
form.insertAdjacentHTML('afterend', '<a target="blank" rel="noopener" href="' + url + '">' + url + '</a>');
});
},
renderRegistrationForm: function renderRegistrationForm(stanza) {
var _this3 = this;
/* Renders the registration form based on the XForm fields
* received from the XMPP server.
*
* Parameters:
* (XMLElement) stanza - The IQ stanza received from the XMPP server.
*/
var form = this.el.querySelector('form');
form.innerHTML = tpl_registration_form({
'__': _converse.__,
'domain': this.domain,
'title': this.title,
'instructions': this.instructions,
'registration_domain': _converse.registration_domain
});
var buttons = form.querySelector('fieldset.buttons');
if (this.form_type === 'xform') {
_.each(stanza.querySelectorAll('field'), function (field) {
buttons.insertAdjacentHTML('beforebegin', utils.xForm2webForm(field, stanza, _this3.domain));
});
} else {
this.renderLegacyRegistrationForm(form);
}
if (!this.fields) {
form.querySelector('.button-primary').classList.add('hidden');
}
form.classList.remove('hidden');
this.model.set('registration_form_rendered', true);
},
showValidationError: function showValidationError(message) {
var form = this.el.querySelector('form');
var flash = form.querySelector('.form-errors');
if (_.isNull(flash)) {
flash = '<div class="form-errors hidden"></div>';
var instructions = form.querySelector('p.instructions');
2016-03-16 12:49:35 +01:00
if (_.isNull(instructions)) {
form.insertAdjacentHTML('afterbegin', flash);
} else {
instructions.insertAdjacentHTML('afterend', flash);
}
flash = form.querySelector('.form-errors');
} else {
flash.innerHTML = '';
}
flash.insertAdjacentHTML('beforeend', '<p class="form-help error">' + message + '</p>');
flash.classList.remove('hidden');
},
reportErrors: function reportErrors(stanza) {
var _this4 = this;
/* Report back to the user any error messages received from the
* XMPP server after attempted registration.
*
* Parameters:
* (XMLElement) stanza - The IQ stanza received from the
* XMPP server.
*/
var errors = stanza.querySelectorAll('error');
_.each(errors, function (error) {
_this4.showValidationError(error.textContent);
});
2016-06-20 21:11:43 +02:00
if (!errors.length) {
var message = __('The provider rejected your registration attempt. ' + 'Please check the values you entered for correctness.');
2017-07-22 22:21:05 +02:00
this.showValidationError(message);
}
},
renderProviderChoiceForm: function renderProviderChoiceForm(ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}
_converse.connection._proto._abortAllRequests();
_converse.connection.reset();
this.render();
},
abortRegistration: function abortRegistration() {
_converse.connection._proto._abortAllRequests();
2016-03-16 12:49:35 +01:00
_converse.connection.reset();
if (this.model.get('registration_form_rendered')) {
if (_converse.registration_domain && this.model.get('registration_form_rendered')) {
this.fetchRegistrationForm(_converse.registration_domain);
}
} else {
this.render();
}
},
submitRegistrationForm: function submitRegistrationForm(form) {
/* Handler, when the user submits the registration form.
* Provides form error feedback or starts the registration
* process.
*
* Parameters:
* (HTMLElement) form - The HTML form that was submitted
*/
var has_empty_inputs = _.reduce(this.el.querySelectorAll('input.required'), function (result, input) {
if (input.value === '') {
input.classList.add('error');
return result + 1;
}
2016-03-16 12:49:35 +01:00
return result;
}, 0);
2018-01-29 16:33:30 +01:00
if (has_empty_inputs) {
return;
2018-01-29 16:33:30 +01:00
}
2017-07-22 22:21:05 +02:00
var inputs = sizzle(':input:not([type=button]):not([type=submit])', form),
iq = $iq({
'type': 'set',
'id': _converse.connection.getUniqueId()
}).c("query", {
xmlns: Strophe.NS.REGISTER
});
if (this.form_type === 'xform') {
iq.c("x", {
xmlns: Strophe.NS.XFORM,
type: 'submit'
});
_.each(inputs, function (input) {
iq.cnode(utils.webForm2xForm(input)).up();
});
} else {
_.each(inputs, function (input) {
iq.c(input.getAttribute('name'), {}, input.value);
});
}
2017-07-22 22:21:05 +02:00
_converse.connection._addSysHandler(this._onRegisterIQ.bind(this), null, "iq", null, null);
2018-01-29 16:33:30 +01:00
_converse.connection.send(iq);
this.setFields(iq.tree());
},
setFields: function setFields(stanza) {
/* Stores the values that will be sent to the XMPP server
* during attempted registration.
*
* Parameters:
* (XMLElement) stanza - the IQ stanza that will be sent to the XMPP server.
*/
var query = stanza.querySelector('query');
var xform = sizzle("x[xmlns=\"".concat(Strophe.NS.XFORM, "\"]"), query);
if (xform.length > 0) {
this._setFieldsFromXForm(xform.pop());
} else {
this._setFieldsFromLegacy(query);
}
},
_setFieldsFromLegacy: function _setFieldsFromLegacy(query) {
var _this5 = this;
_.each(query.children, function (field) {
if (field.tagName.toLowerCase() === 'instructions') {
_this5.instructions = Strophe.getText(field);
return;
} else if (field.tagName.toLowerCase() === 'x') {
if (field.getAttribute('xmlns') === 'jabber:x:oob') {
_this5.urls.concat(_.map(field.querySelectorAll('url'), 'textContent'));
}
2017-07-22 22:21:05 +02:00
return;
}
2017-07-22 22:21:05 +02:00
_this5.fields[field.tagName.toLowerCase()] = Strophe.getText(field);
});
2017-07-22 22:21:05 +02:00
this.form_type = 'legacy';
},
_setFieldsFromXForm: function _setFieldsFromXForm(xform) {
var _this6 = this;
2017-07-22 22:21:05 +02:00
this.title = _.get(xform.querySelector('title'), 'textContent');
this.instructions = _.get(xform.querySelector('instructions'), 'textContent');
_.each(xform.querySelectorAll('field'), function (field) {
var _var = field.getAttribute('var');
2017-02-03 13:51:07 +01:00
if (_var) {
_this6.fields[_var.toLowerCase()] = _.get(field.querySelector('value'), 'textContent', '');
} else {
// TODO: other option seems to be type="fixed"
_converse.log("Found field we couldn't parse", Strophe.LogLevel.WARN);
}
});
2017-02-03 13:51:07 +01:00
this.form_type = 'xform';
},
_onRegisterIQ: function _onRegisterIQ(stanza) {
/* Callback method that gets called when a return IQ stanza
* is received from the XMPP server, after attempting to
* register a new user.
*
* Parameters:
* (XMLElement) stanza - The IQ stanza.
*/
if (stanza.getAttribute("type") === "error") {
_converse.log("Registration failed.", Strophe.LogLevel.ERROR);
this.reportErrors(stanza);
var error = stanza.getElementsByTagName("error");
if (error.length !== 1) {
_converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, "unknown");
2016-03-16 12:49:35 +01:00
return false;
}
2016-03-16 12:49:35 +01:00
error = error[0].firstChild.tagName.toLowerCase();
2017-02-03 13:51:07 +01:00
if (error === 'conflict') {
_converse.connection._changeConnectStatus(Strophe.Status.CONFLICT, error);
} else if (error === 'not-acceptable') {
_converse.connection._changeConnectStatus(Strophe.Status.NOTACCEPTABLE, error);
} else {
_converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, error);
}
} else {
_converse.connection._changeConnectStatus(Strophe.Status.REGISTERED, null);
}
2017-07-22 22:21:05 +02:00
return false;
}
});
}
});
});
//# sourceMappingURL=converse-register.js.map;
2017-02-03 13:51:07 +01:00
define('tpl!rooms_list', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
2018-05-23 04:27:33 +02:00
__p += '<a href="#" class="rooms-toggle open-rooms-toggle controlbox-padded" title="' +
__e(o.desc_rooms) +
'">\n <span class="fa ';
if (o.toggle_state === o._converse.OPENED) { ;
__p += ' fa-caret-down ';
} else { ;
__p += ' fa-caret-right ';
} ;
__p += '">\n </span> ' +
__e(o.label_rooms) +
2018-04-30 16:05:20 +02:00
'</a>\n<div class="items-list rooms-list open-rooms-list"></div>\n';
return __p
};});
2017-07-22 22:21:05 +02:00
2016-03-16 12:49:35 +01:00
define('tpl!rooms_list_item', ['lodash'], function(_) {return function(o) {
var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
function print() { __p += __j.call(arguments, '') }
2018-05-23 04:27:33 +02:00
__p += '<div class="list-item controlbox-padded available-chatroom d-flex flex-row ';
if (o.num_unread_general) { ;
__p += ' unread-msgs ';
} ;
__p += '" data-room-jid="' +
__e(o.jid) +
'">\n';
if (o.num_unread) { ;
__p += '\n <span class="msgs-indicator badge badge-info">' +
__e( o.num_unread ) +
'</span>\n';
} ;
__p += '\n<a class="open-room available-room w-100"\n data-room-jid="' +
__e(o.jid) +
'"\n title="' +
__e(o.open_title) +
'" href="#">' +
__e(o.name || o.jid) +
'</a>\n\n<a class="right close-room icon-leave"\n data-room-jid="' +
__e(o.jid) +
'"\n data-room-name="' +
__e(o.name || o.jid) +
'"\n title="' +
__e(o.info_leave_room) +
'" href="#">&nbsp;</a>\n\n';
if (o.allow_bookmarks) { ;
__p += '\n<a class="fa align-self-center ';
if (o.bookmarked) { ;
__p += ' fa-bookmark remove-bookmark button-on ';
} else { ;
__p += ' add-bookmark fa-bookmark-o ';
} ;
__p += '"\n data-room-jid="' +
__e(o.jid) +
'" data-bookmark-name="' +
__e(o.name) +
'"\n title="';
if (o.bookmarked) { ;
__p += ' ' +
__e(o.info_remove_bookmark) +
' ';
} else { ;
__p += ' ' +
__e(o.info_add_bookmark) +
' ';
} ;
__p += '"\n href="#">&nbsp;</a>\n';
} ;
__p += '\n<a class="room-info fa fa-info-circle align-self-center" data-room-jid="' +
__e(o.jid) +
'"\n title="' +
__e(o.info_title) +
2018-05-15 10:32:13 +02:00
'" href="#">&nbsp;</a>\n</div>\n';
return __p
};});
2016-03-16 12:49:35 +01:00
// Converse.js (A browser based XMPP chat client)
// http://conversejs.org
//
2017-02-13 17:16:13 +01:00
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
2016-03-16 12:49:35 +01:00
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define */
2017-07-22 22:21:05 +02:00
/* This is a non-core Converse.js plugin which shows a list of currently open
* rooms in the "Rooms Panel" of the ControlBox.
*/
(function (root, factory) {
define('converse-roomslist',["utils", "converse-core", "converse-muc", "tpl!rooms_list", "tpl!rooms_list_item"], factory);
})(this, function (utils, converse, muc, tpl_rooms_list, tpl_rooms_list_item) {
var _converse$env = converse.env,
Backbone = _converse$env.Backbone,
Promise = _converse$env.Promise,
Strophe = _converse$env.Strophe,
b64_sha1 = _converse$env.b64_sha1,
sizzle = _converse$env.sizzle,
_ = _converse$env._;
var u = converse.env.utils;
converse.plugins.add('converse-roomslist', {
/* Optional dependencies are other plugins which might be
2018-01-17 19:45:33 +01:00
* overridden or relied upon, and therefore need to be loaded before
* this plugin. They are called "optional" because they might not be
* available, in which case any overrides applicable to them will be
* ignored.
2018-01-17 19:45:33 +01:00
*
* It's possible however to make optional dependencies non-optional.
2018-01-17 19:45:33 +01:00
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found.
2018-01-17 19:45:33 +01:00
*
* NB: These plugins need to have already been loaded via require.js.
*/
dependencies: ["converse-controlbox", "converse-muc", "converse-bookmarks"],
initialize: function initialize() {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
var _converse = this._converse,
__ = _converse.__;
_converse.OpenRooms = Backbone.Collection.extend({
comparator: function comparator(room) {
if (room.get('bookmarked')) {
var bookmark = _.head(_converse.bookmarksview.model.where({
'jid': room.get('jid')
}));
return bookmark.get('name');
} else {
return room.get('name');
}
},
initialize: function initialize() {
this.browserStorage = new Backbone.BrowserStorage[_converse.storage](b64_sha1("converse.open-rooms-{_converse.bare_jid}"));
2017-07-22 22:21:05 +02:00
_converse.chatboxes.on('add', this.onChatBoxAdded, this);
_converse.chatboxes.on('change:bookmarked', this.onChatBoxChanged, this);
2016-03-16 12:49:35 +01:00
_converse.chatboxes.on('change:name', this.onChatBoxChanged, this);
2016-06-20 21:11:43 +02:00
_converse.chatboxes.on('change:num_unread', this.onChatBoxChanged, this);
2017-07-22 22:21:05 +02:00
_converse.chatboxes.on('change:num_unread_general', this.onChatBoxChanged, this);
_converse.chatboxes.on('remove', this.onChatBoxRemoved, this);
2017-02-03 13:51:07 +01:00
this.reset(_.map(_converse.chatboxes.where({
'type': 'chatroom'
}), 'attributes'));
},
onChatBoxAdded: function onChatBoxAdded(item) {
if (item.get('type') === 'chatroom') {
this.create(item.attributes);
}
},
onChatBoxChanged: function onChatBoxChanged(item) {
if (item.get('type') === 'chatroom') {
var room = this.get(item.get('jid'));
2017-07-22 22:21:05 +02:00
if (!_.isNil(room)) {
room.set(item.attributes);
}
}
},
onChatBoxRemoved: function onChatBoxRemoved(item) {
if (item.get('type') === 'chatroom') {
var room = this.get(item.get('jid'));
this.remove(room);
}
}
});
_converse.RoomsList = Backbone.Model.extend({
defaults: {
"toggle-state": _converse.OPENED
}
});
_converse.RoomsListElementView = Backbone.VDOMView.extend({
initialize: function initialize() {
this.model.on('destroy', this.remove, this);
this.model.on('remove', this.remove, this);
this.model.on('change:bookmarked', this.render, this);
this.model.on('change:name', this.render, this);
this.model.on('change:num_unread', this.render, this);
this.model.on('change:num_unread_general', this.render, this);
},
getRoomsListElementName: function getRoomsListElementName() {
if (this.model.get('bookmarked') && _converse.bookmarksview) {
var bookmark = _.head(_converse.bookmarksview.model.where({
'jid': this.model.get('jid')
}));
return bookmark.get('name');
} else {
return this.model.get('name');
}
},
toHTML: function toHTML() {
return tpl_rooms_list_item(_.extend(this.model.toJSON(), {
// XXX: By the time this renders, the _converse.bookmarks
// collection should already exist if bookmarks are
// supported by the XMPP server. So we can use it
// as a check for support (other ways of checking are async).
'allow_bookmarks': _converse.allow_bookmarks && _converse.bookmarks,
'info_leave_room': __('Leave this room'),
'info_remove_bookmark': __('Unbookmark this room'),
'info_add_bookmark': __('Bookmark this room'),
'info_title': __('Show more information on this room'),
'name': this.getRoomsListElementName(),
'open_title': __('Click to open this room')
}));
}
});
_converse.RoomsListView = Backbone.OrderedListView.extend({
tagName: 'div',
className: 'open-rooms-list list-container rooms-list-container',
events: {
'click .add-bookmark': 'addBookmark',
'click .close-room': 'closeRoom',
'click .rooms-toggle': 'toggleRoomsList',
'click .remove-bookmark': 'removeBookmark',
'click .open-room': 'openRoom'
},
listSelector: '.rooms-list',
ItemView: _converse.RoomsListElementView,
subviewIndex: 'jid',
initialize: function initialize() {
Backbone.OrderedListView.prototype.initialize.apply(this, arguments);
this.model.on('add', this.showOrHide, this);
this.model.on('remove', this.showOrHide, this);
var cachekey = "converse.roomslist".concat(_converse.bare_jid);
this.list_model = new _converse.RoomsList();
this.list_model.id = cachekey;
this.list_model.browserStorage = new Backbone.BrowserStorage[_converse.storage](b64_sha1(cachekey));
this.list_model.fetch();
this.render();
this.sortAndPositionAllItems();
},
render: function render() {
this.el.innerHTML = tpl_rooms_list({
'toggle_state': this.list_model.get('toggle-state'),
'desc_rooms': __('Click to toggle the rooms list'),
'label_rooms': __('Open Rooms'),
'_converse': _converse
});
if (this.list_model.get('toggle-state') !== _converse.OPENED) {
this.el.querySelector('.open-rooms-list').classList.add('collapsed');
}
this.showOrHide();
this.insertIntoControlBox();
return this;
},
insertIntoControlBox: function insertIntoControlBox() {
var controlboxview = _converse.chatboxviews.get('controlbox');
2017-02-03 13:51:07 +01:00
2018-05-23 04:27:33 +02:00
if (!_.isUndefined(controlboxview) && !u.rootContains(_converse.root, this.el)) {
var el = controlboxview.el.querySelector('.open-rooms-list');
if (!_.isNull(el)) {
el.parentNode.replaceChild(this.el, el);
}
}
},
hide: function hide() {
u.hideElement(this.el);
},
show: function show() {
u.showElement(this.el);
},
openRoom: function openRoom(ev) {
ev.preventDefault();
var name = ev.target.textContent;
var jid = ev.target.getAttribute('data-room-jid');
var data = {
'name': name || Strophe.unescapeNode(Strophe.getNodeFromJid(jid)) || jid
};
_converse.api.rooms.open(jid, data);
},
closeRoom: function closeRoom(ev) {
ev.preventDefault();
var name = ev.target.getAttribute('data-room-name');
var jid = ev.target.getAttribute('data-room-jid');
if (confirm(__("Are you sure you want to leave the room %1$s?", name))) {
// TODO: replace with API call
_converse.chatboxviews.get(jid).close();
}
},
showOrHide: function showOrHide(item) {
if (!this.model.models.length) {
u.hideElement(this.el);
} else {
u.showElement(this.el);
}
},
removeBookmark: _converse.removeBookmarkViaEvent,
addBookmark: _converse.addBookmarkViaEvent,
toggleRoomsList: function toggleRoomsList(ev) {
var _this = this;
if (ev && ev.preventDefault) {
ev.preventDefault();
}
var icon_el = ev.target.querySelector('.fa');
if (icon_el.classList.contains("fa-caret-down")) {
utils.slideIn(this.el.querySelector('.open-rooms-list')).then(function () {
_this.list_model.save({
'toggle-state': _converse.CLOSED
});
icon_el.classList.remove("fa-caret-down");
icon_el.classList.add("fa-caret-right");
});
} else {
utils.slideOut(this.el.querySelector('.open-rooms-list')).then(function () {
_this.list_model.save({
'toggle-state': _converse.OPENED
});
icon_el.classList.remove("fa-caret-right");
icon_el.classList.add("fa-caret-down");
});
}
}
});
var initRoomsListView = function initRoomsListView() {
_converse.rooms_list_view = new _converse.RoomsListView({
'model': new _converse.OpenRooms()
});
};
if (_converse.allow_bookmarks) {
u.onMultipleEvents([{
'object': _converse,
'event': 'chatBoxesFetched'
}, {
'object': _converse,
'event': 'roomsPanelRendered'
}, {
'object': _converse,
'event': 'bookmarksInitialized'
}], initRoomsListView);
} else {
u.onMultipleEvents([{
'object': _converse,
'event': 'chatBoxesFetched'
}, {
'object': _converse,
'event': 'roomsPanelRendered'
}], initRoomsListView);
}
2018-02-14 16:53:07 +01:00
_converse.api.listen.on('reconnected', initRoomsListView);
}
});
});
//# sourceMappingURL=converse-roomslist.js.map;
/*global define */
2016-03-07 18:54:07 +01:00
if (typeof define !== 'undefined') {
// The section below determines which plugins will be included in a build
define('converse',[
"converse-core",
2016-03-07 18:54:07 +01:00
/* START: Removable components
2017-02-01 12:05:32 +01:00
* --------------------
* Any of the following components may be removed if they're not needed.
*/
"converse-bookmarks", // XEP-0048 Bookmarks
2018-05-15 10:32:13 +02:00
"converse-caps",
"converse-chatview", // Renders standalone chat boxes for single user chat
"converse-controlbox", // The control box
"converse-dragresize", // Allows chat boxes to be resized by dragging them
2018-05-07 18:20:15 +02:00
"converse-embedded",
"converse-fullscreen",
"converse-headline", // Support for headline messages
"converse-mam", // XEP-0313 Message Archive Management
"converse-minimize", // Allows chat boxes to be minimized
"converse-muc", // XEP-0045 Multi-user chat
2018-03-25 21:21:43 +02:00
"converse-muc-views",
"converse-muc-views", // Views related to MUC
"converse-notification", // HTML5 Notifications
"converse-otr", // Off-the-record encryption for one-on-one messages
"converse-ping", // XEP-0199 XMPP Ping
2018-05-07 18:20:15 +02:00
"converse-roster",
"converse-register", // XEP-0077 In-band registration
"converse-roomslist", // Show currently open chat rooms
"converse-vcard", // XEP-0054 VCard-temp
2016-03-07 18:54:07 +01:00
/* END: Removable components */
2017-03-05 10:45:18 +01:00
], function (converse) {
2017-02-03 13:51:07 +01:00
return converse;
2016-03-07 18:54:07 +01:00
});
}
;
2017-03-05 10:45:18 +01:00
2018-01-04 17:58:16 +01:00
define('awesomplete', [], function () { return Awesomplete; });
2017-03-05 10:45:18 +01:00
define('lodash', [], function () { return _; });
2018-01-04 17:58:16 +01:00
define('underscore', [], function () { return _; });
define('lodash.converter', [], function () { return fp; });
2017-04-23 19:02:44 +02:00
define('lodash.noconflict', [], function () { return _; });
2018-01-04 17:58:16 +01:00
define('moment', [], function () { return moment; });
define('moment/locale/af', [], function () { return moment; });
define('moment/locale/ca', [], function () { return moment; });
define('moment/locale/de', [], function () { return moment; });
define('moment/locale/es', [], function () { return moment; });
define('moment/locale/fr', [], function () { return moment; });
define('moment/locale/he', [], function () { return moment; });
define('moment/locale/hu', [], function () { return moment; });
define('moment/locale/id', [], function () { return moment; });
define('moment/locale/it', [], function () { return moment; });
define('moment/locale/ja', [], function () { return moment; });
define('moment/locale/nb', [], function () { return moment; });
define('moment/locale/nl', [], function () { return moment; });
define('moment/locale/pl', [], function () { return moment; });
define('moment/locale/pt-br', [], function () { return moment; });
define('moment/locale/ru', [], function () { return moment; });
define('moment/locale/uk', [], function () { return moment; });
define('moment/moment', [], function () { return moment; });
define('i18n', [], function () { return; });
define('es6-promise', [], function () { return Promise; });
2017-03-05 10:45:18 +01:00
define('strophe', [], function () {
return {
'Strophe': Strophe,
'$build': $build,
'$iq': $iq,
'$msg': $msg,
'$pres': $pres,
'SHA1': SHA1,
'MD5': MD5,
'b64_hmac_sha1': SHA1.b64_hmac_sha1,
'b64_sha1': SHA1.b64_sha1,
'str_hmac_sha1': SHA1.str_hmac_sha1,
'str_sha1': SHA1.str_sha1
};
});
var strophePlugin = function () { return Strophe; };
var emptyFunction = function () { };
define('strophe.ping', ['strophe'], strophePlugin);
define('strophe.rsm', ['strophe'], strophePlugin);
2017-04-23 19:02:44 +02:00
define('backbone', [], function () { return Backbone; });
define('backbone.noconflict', [], function () { return Backbone; });
2017-03-05 10:45:18 +01:00
define('backbone.browserStorage', ['backbone'], emptyFunction);
define('backbone.overview', ['backbone'], emptyFunction);
define('otr', [], function () { return { 'DSA': DSA, 'OTR': OTR };});
return require('converse');
}));