Comparar commits

...

11 Commits

Autor SHA1 Mensagem Data
sualko efb3dd025f update dependencies 2017-06-30 13:41:36 +02:00
sualko 457e175a41 support XEP-0106: JID Escaping (fix #501) 2017-06-30 13:40:59 +02:00
sualko 757770b9f7 Update README.md 2017-06-20 16:47:35 +02:00
sualko 3dee2ef385 add first tests, omit external dependencies, minor improvements 2017-06-20 16:31:40 +02:00
sualko e70b2481a8 roster change, rename contact, presence, notices 2017-06-16 15:35:30 +02:00
sualko 9023a4c9c8 allow slave tabs to send xmpp stanzas 2017-06-08 16:58:36 +02:00
sualko c5ad776f8c various improvements
- role allocator
- sync chat windows
- avatar placeholder
- and more
2017-06-02 14:13:56 +02:00
sualko 2bb914cb9a disable travis for refactoring branch 2017-06-01 13:58:50 +02:00
sualko 9158727327 remove bower and grunt
use: npm run dev
2017-06-01 13:57:59 +02:00
sualko e904bd8045 work on chat window 2017-05-10 10:48:53 +02:00
sualko 4fb2ea3ee3 start switching to typescript and handlebars 2017-05-04 10:48:32 +02:00
161 arquivos alterados com 9841 adições e 14509 exclusões
+1 -1
Ver Arquivo
@@ -18,7 +18,7 @@ linters:
ImportantRule:
enabled: false
Indentation:
width: 4
width: 3
LeadingZero:
style: include_zero
NameFormat:
+4
Ver Arquivo
@@ -9,3 +9,7 @@ before_install:
script:
- ./node_modules/.bin/grunt pre-commit
branches:
except:
- refactoring
-402
Ver Arquivo
@@ -1,402 +0,0 @@
/* global module:false */
module.exports = function(grunt) {
var dep = grunt.file.readJSON('dep.json');
var dep_files = dep.map(function(el) {
return el.file;
});
dep_files.push('<%= target %>/lib/translation.js');
// Project configuration.
grunt.initConfig({
app: grunt.file.readJSON('package.json'),
meta: {
banner: grunt.file.read('banner.js')
},
target: 'dev',
jshint: {
options: {
jshintrc: '.jshintrc'
},
gruntfile: {
src: 'Gruntfile.js'
},
files: ['src/jsxc.lib.*.js']
},
copy: {
main: {
files: [{
expand: true,
src: ['lib/emojione/assets/svg/*.svg',
'lib/otr/build/**', 'lib/otr/lib/*.js',
'lib/otr/vendor/*.js', 'lib/*.js', 'LICENSE',
'img/**', 'sound/**'
],
dest: '<%= target %>/'
}, {
expand: true,
cwd: 'lib/',
src: ['*.css'],
dest: '<%= target %>/css/'
}, {
expand: true,
cwd: 'lib/magnific-popup/dist/',
src: ['*.css'],
dest: '<%= target %>/css/'
}]
}
},
clean: ['<%= target %>/'],
usebanner: {
dist: {
options: {
position: 'top',
banner: '<%= meta.banner %>'
},
files: {
src: ['<%= target %>/*.js']
}
}
},
replace: {
version: {
src: ['<%= target %>/jsxc.js'],
overwrite: true,
replacements: [{
from: '< $ app.version $ >',
to: "<%= app.version %>"
}]
},
libraries: {
src: ['<%= target %>/jsxc.js'],
overwrite: true,
replacements: [{
from: '<$ dep.libraries $>',
to: function() {
var i, d, libraries = '';
for (i = 0; i < dep.length; i++) {
d = dep[i];
if (typeof d.name === 'string') {
libraries += '<a href="' + d.url + '">' + d.name + '</a> (' + d.license + '), ';
}
}
return libraries.replace(/, $/, '');
}
}]
},
locales: {
src: ['<%= target %>/lib/translation.js'],
overwrite: true,
replacements: [{
from: /^{/g,
to: 'var I18next = {'
}, {
from: /}$/g,
to: '};'
}]
},
template: {
src: ['tmp/template.js'],
overwrite: true,
replacements: [{
from: 'var jsxc.gui.template = {};',
to: ''
}]
},
imageUrl: {
src: ['<%= target %>/css/*.css'],
overwrite: true,
replacements: [{
from: /image-url\(["'](.+)["']\)/g,
to: 'url(\'../img/$1\')'
}]
},
// IE 10 does not like comments starting with @
todo: {
src: ['build/jsxc.js'],
overwrite: true,
replacements: [{
from: /\/\/@(.*)/g,
to: '//$1'
}]
}
},
merge_data: {
target: {
src: ['locales/*.{json,y{,a}ml}'],
dest: '<%= target %>/lib/translation.js'
}
},
concat: {
dep: {
options: {
banner: '/*!\n' +
' * <%= app.name %> v<%= app.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' +
' * \n' +
' * This file concatenates all dependencies of <%= app.name %>.\n' +
' * \n' +
' */\n\n',
process: function(src, filepath) {
filepath = filepath.replace(/^[a-z]+\//i, '');
if (filepath.match(/crypto\.js$/)) {
src += ';';
}
var data = dep[dep_files.indexOf(filepath)];
if (data) {
return '\n/*!\n * Source: ' + filepath + ', license: ' + data.license + ', url: ' + data.url + '\n */\n' + src;
} else {
return src;
}
}
},
src: dep_files,
dest: '<%= target %>/lib/jsxc.dep.js',
filter: function (filepath) {
if (!grunt.file.exists(filepath)) {
grunt.fail.warn('Could not find: ' + filepath);
} else {
return true;
}
},
nonull: true,
},
jsxc: {
options: {
banner: '/*! This file is concatenated for the browser. */\n\n'
},
src: ['src/jsxc.intro.js', 'src/jsxc.lib.js', 'src/jsxc.lib.xmpp.js',
'src/jsxc.lib.*.js', 'tmp/template.js', 'src/jsxc.outro.js'
],
dest: '<%= target %>/jsxc.js'
}
},
uglify: {
jsxc: {
options: {
mangle: false,
sourceMap: true,
preserveComments: 'some'
},
files: {
'<%= target %>/lib/jsxc.dep.min.js': ['<%= target %>/lib/jsxc.dep.js'],
'<%= target %>/jsxc.min.js': ['<%= target %>/jsxc.js']
}
}
},
search: {
bower: {
files: {
src: ['bower.json']
},
options: {
searchString: "<%= app.version %>",
logFormat: 'console',
onComplete: function(m) {
if (m.numMatches === 0) {
grunt.fail.fatal('No entry in bower.json for current version found.');
}
}
}
},
console: {
files: {
src: ['src/*.js']
},
options: {
searchString: /console\.log\((?!'[<>]|msg)/g,
logFormat: 'console',
failOnMatch: true
}
},
changelog: {
files: {
src: ['CHANGELOG.md']
},
options: {
searchString: "<%= app.version %>",
logFormat: 'console',
onComplete: function(m) {
if (m.numMatches === 0) {
grunt.fail.fatal("No entry in CHANGELOG.md for current version found.");
}
}
}
}
},
compress: {
main: {
options: {
archive: "archives/jsxc-<%= app.version %>.zip"
},
files: [{
src: ['**'],
expand: true,
dest: 'jsxc/',
cwd: 'build/'
}]
}
},
dataUri: {
dist: {
src: '<%= target %>/css/*.css',
dest: '<%= target %>/css/',
options: {
target: ['<%= target %>/img/*.*', '<%= target %>/img/**/*.*'],
fixDirLevel: false,
maxBytes: 2048
}
}
},
jsdoc: {
dist: {
src: ['src/jsxc.lib.*'],
dest: 'doc'
}
},
autoprefixer: {
no_dest: {
src: '<%= target %>/css/*.css'
}
},
csslint: {
strict: {
options: {
import: 2
},
src: ['<%= target %>/css/*.css']
},
},
sass: {
dist: {
files: {
'<%= target %>/css/jsxc.css': 'scss/jsxc.scss',
'<%= target %>/css/jsxc.webrtc.css': 'scss/jsxc.webrtc.scss'
}
}
},
watch: {
locales: {
files: ['locales/*'],
tasks: ['merge_data', 'replace:locales', 'concat:dep']
},
css: {
files: ['scss/*'],
tasks: ['sass', 'autoprefixer', 'replace:imageUrl']
},
js: {
files: ['src/jsxc.lib.*'],
tasks: ['concat:jsxc']
},
template: {
files: ['template/*.html'],
tasks: ['htmlConvert', 'replace:template', 'concat:jsxc']
}
},
jsbeautifier: {
'default': {
src: ['Gruntfile.js', 'src/jsxc.lib.*', 'template/*.html',
'example/*.html', 'example/js/dev.js', 'example/js/example.js',
'example/css/example.css'
],
options: {
config: '.jsbeautifyrc'
}
},
'pre-commit': {
src: ['Gruntfile.js', 'src/jsxc.lib.*', 'template/*.html',
'example/*.html', 'example/js/dev.js', 'example/js/example.js',
'example/css/example.css'
],
options: {
config: '.jsbeautifyrc',
mode: 'VERIFY_ONLY'
}
}
},
prettysass: {
options: {
alphabetize: false,
indent: 4
},
jsxc: {
src: ['scss/*.scss']
}
},
htmlConvert: {
options: {
target: 'js',
rename: function(name) {
return name.match(/([-_0-9a-z]+)\.html$/i)[1];
},
quoteChar: '\'',
indentString: '',
indentGlobal: ''
},
'jsxc.gui.template': {
src: 'template/*.html',
dest: 'tmp/template.js'
}
},
scsslint: {
files: ['scss/*.scss'],
options: {
config: '.scss-lint.yml'
}
}
});
// These plugins provide necessary tasks.
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-banner');
grunt.loadNpmTasks('grunt-text-replace');
grunt.loadNpmTasks('grunt-search');
grunt.loadNpmTasks('grunt-contrib-compress');
grunt.loadNpmTasks('grunt-jsdoc');
grunt.loadNpmTasks('grunt-data-uri');
grunt.loadNpmTasks('grunt-merge-data');
grunt.loadNpmTasks('grunt-contrib-csslint');
grunt.loadNpmTasks('grunt-sass');
grunt.loadNpmTasks('grunt-autoprefixer');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-jsbeautifier');
grunt.loadNpmTasks('grunt-prettysass');
grunt.loadNpmTasks('grunt-html-convert');
grunt.loadNpmTasks('grunt-scss-lint');
//Default task
grunt.registerTask('default', ['build', 'watch']);
grunt.registerTask('build', ['jshint', 'clean', 'sass', 'replace:imageUrl',
'autoprefixer', 'copy', 'merge_data', 'replace:locales', 'htmlConvert',
'replace:template', 'concat'
]);
grunt.registerTask('build:prerelease', 'Build a new pre-release', function() {
grunt.config.set('target', 'build');
grunt.task.run(['search:console', 'search:bower', 'build', 'usebanner',
'replace:version', 'replace:libraries', 'replace:todo',
'uglify', 'compress'
]);
});
grunt.registerTask('build:release', 'Build a new release', function() {
grunt.config.set('target', 'build');
grunt.task.run(['search:changelog', 'build:prerelease', 'jsdoc']);
});
// before commit
grunt.registerTask('pre-commit', ['search:console', 'jsbeautifier:pre-commit', 'scsslint', 'jshint']);
grunt.registerTask('beautify', ['jsbeautifier', 'prettysass']);
};
+6 -1
Ver Arquivo
@@ -1,4 +1,4 @@
# JavaScript XMPP Client
# JavaScript XMPP Client 4.0
[![Build Status](https://travis-ci.org/jsxc/jsxc.svg?branch=master)](https://travis-ci.org/jsxc/jsxc)
[![Dependency Status](https://dependencyci.com/github/jsxc/jsxc/badge)](https://dependencyci.com/github/jsxc/jsxc)
@@ -8,3 +8,8 @@ Real-time chat app. This app requires an external XMPP server (openfire, ejabber
You find a full list of features, supported protocols and browsers on [our homepage](http://www.jsxc.org).
If you are looking for install instructions or developer notes, please also checkout our [wiki](https://github.com/jsxc/jsxc/wiki/).
## Rewrite / Refactoring
:warning: This branch is under heavy construction and definitely not ready for production.
This next big step for JSXC uses [Typescript](http://www.typescriptlang.org/index.html), [Webpack](https://webpack.github.io), [Handlebars](http://handlebarsjs.com), [Karma](http://karma-runner.github.io), [Mocha](https://mochajs.org), [Chai](http://chaijs.com) and [Sinon](http://sinonjs.org) to bring the best open XMPP chat experience to you. Currently we ship no packed version, so install all dependencies with `npm install` and execute `npm run dev` to test the current state. An example application is available at `example/ts.html`. To run all tests, enter `npm test`.
-39
Ver Arquivo
@@ -1,39 +0,0 @@
{
"name": "jsxc",
"version": "3.1.1",
"homepage": "https://www.jsxc.org",
"authors": [
"sualko <klaus@jsxc.org>"
],
"description": "Real-time chat app",
"keywords": [
"javascript",
"xmpp",
"webrtc",
"otr",
"chat",
"realtime"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"lib/",
"test",
"tests"
],
"private": true,
"devDependencies": {
"emojione": "~2.2.7",
"favico.js": "^0.3.10",
"strophe.bookmarks": "strophe/strophejs-plugin-bookmarks#f51c60629cb0ad278f92bfb8d8dbb8cb455d9c98",
"strophe.js": "strophejs#a11ebefa3db1b6712d51d0d309b9f68f8e391d1b",
"strophe.vcard": "strophe/strophejs-plugin-vcard#de3a0c97a2c520ed900ee15b04a0c16d5c663891",
"strophe.x": "strophe/strophejs-plugin-dataforms",
"strophe.chatstates": "strophe/strophejs-plugin-chatstates",
"jquery-i18next": "^1.2.0",
"i18next": "^5.0.0",
"magnific-popup": "^1.1.0"
}
}
+7
Ver Arquivo
@@ -0,0 +1,7 @@
declare module '*.hbs' {
// const content: any;
// export default content;
export default function template(options:any);
}
declare function require(file:string):(context?:any, options?:any)=>string;
-116
Ver Arquivo
@@ -1,116 +0,0 @@
[
{
"name": "strophe.js",
"file": "lib/strophe.js/strophe.js",
"license": "multiple",
"url": "http://strophe.im/strophejs/"
},
{
"name": "strophe.js/muc",
"file": "lib/strophe.muc.js",
"license": "MIT",
"url": "https://github.com/strophe/strophejs-plugins"
},
{
"name": "strophe.js/disco",
"file": "lib/strophe.disco.js",
"license": "MIT",
"url": "https://github.com/strophe/strophejs-plugins"
},
{
"name": "strophe.js/caps",
"file": "lib/strophe.caps.js",
"license": "MIT",
"url": "https://github.com/strophe/strophejs-plugins"
},
{
"name": "strophe.js/vcard",
"file": "lib/strophe.vcard/strophe.vcard.js",
"license": "MIT",
"url": "https://github.com/strophe/strophejs-plugins"
},
{
"name": "strophe.js/bookmarks",
"file": "lib/strophe.bookmarks/strophe.bookmarks.js",
"license": "MIT",
"url": "https://github.com/strophe/strophejs-plugins/tree/master/bookmarks"
},
{
"name": "strophe.js/x",
"file": "lib/strophe.x/src/strophe.x.js",
"license": "MIT",
"url": "https://github.com/strophe/strophejs-plugins/tree/master/dataforms"
},
{
"name": "strophe.js/chatstates",
"file": "lib/strophe.chatstates/strophe.chatstates.js",
"license": "MIT",
"url": "https://github.com/strophe/strophejs-plugins/tree/master/chatstates"
},
{
"name": "strophe.jinglejs",
"file": "lib/strophe.jinglejs/strophe.jinglejs-bundle.js",
"license": "MIT",
"url": "https://github.com/sualko/strophe.jinglejs"
},
{
"name": "Salsa20",
"file": "lib/otr/build/dep/salsa20.js",
"license": "AGPL3",
"url": "https://github.com/neoatlantis/node-salsa20"
},
{
"name": "bigint",
"file": "lib/otr/build/dep/bigint.js",
"license": "public domain",
"url": "www.leemon.com"
},
{
"name": "cryptojs",
"file": "lib/otr/build/dep/crypto.js",
"license": "code.google.com/p/crypto-js/wiki/license",
"url": "code.google.com/p/crypto-js"
},
{
"name": "eventemitter",
"file": "lib/otr/build/dep/eventemitter.js",
"license": "MIT",
"url": "http://git.io/ee"
},
{
"name": "otr.js",
"file": "lib/otr/build/otr.js",
"license": "MPL v2.0",
"url": "https://arlolra.github.io/otr/"
},
{
"name": "i18next",
"file": "lib/i18next/i18next.min.js",
"license": "MIT",
"url": "http://i18next.com/"
},
{
"name": "jquery-i18next",
"file": "lib/jquery-i18next/jquery-i18next.min.js",
"license": "MIT",
"url": "http://i18next.com/"
},
{
"name": "Magnific Popup",
"file": "lib/magnific-popup/dist/jquery.magnific-popup.min.js",
"license": "MIT",
"url": "http://dimsemenov.com/plugins/magnific-popup/"
},
{
"name": "favico.js",
"file": "lib/favico.js/favico.js",
"license": "MIT",
"url": "https://github.com/ejci/favico.js"
},
{
"name": "emoji one",
"file": "lib/emojione/lib/js/emojione.js",
"license": "CC-BY 4.0",
"url": "http://emojione.com"
}
]
+92
Ver Arquivo
@@ -0,0 +1,92 @@
<!DOCTYPE HTML>
<html>
<head>
<title>JSXC example application</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
<link href="css/bootstrap.min.css" media="all" rel="stylesheet" type="text/css" />
<!-- require:dependencies -->
<link href="../build/css/jquery-ui.min.css" media="all" rel="stylesheet" type="text/css" />
<link href="../css/bundle.css" media="all" rel="stylesheet" type="text/css" />
<!-- endrequire -->
<link href="../node_modules/simplebar/dist/simplebar.css" media="all" rel="stylesheet" type="text/css" />
<link href="css/example.css" media="all" rel="stylesheet" type="text/css" />
<style>
#jsxc-role {
width: 50px;
height: 50px;
background-color: #a1a1a1;
border-radius: 50%;
position: absolute;
top: 15px;
left: 15px;
}
.jsxc-master #jsxc-role {
background-color: green;
}
.jsxc-slave #jsxc-role {
background-color: orange;
}
</style>
</head>
<body>
<div id="jsxc-role"></div>
<form id="loginForm" style="margin-top: 100px; padding:15px;" class="form-inline">
<div class="form-group">
<input class="form-control" type="text" name="url" placeholder="Bosh url" value="/http-bind/" />
</div>
<div class="form-group">
<input class="form-control" type="text" name="jid" placeholder="Jabber Id" value="" />
</div>
<div class="form-group">
<input class="form-control" type="text" name="password" placeholder="Password" value="" />
</div>
<button class="btn btn-primary">Login</button>
</form>
<p style="padding:15px"><a class="btn btn-danger" href="javascript:jsxc.deleteAllData()">Delete all Data</a>
<a class="btn btn-default" href="javascript:jsxc.enableDebugMode()">Enable Debug Mode</a>
<a class="btn btn-default" href="javascript:jsxc.disableDebugMode()">Disable Debug Mode</a></p>
<!-- require:dependencies -->
<script src="js/jquery.min.js"></script>
<script src="js/jquery-ui.min.js"></script>
<script src="../build/lib/jquery.slimscroll.js"></script>
<script src="../build/lib/jquery.fullscreen.js"></script>
<!-- <script src="../build/lib/jsxc.dep.js"></script> -->
<!-- endrequire -->
<script src="../node_modules/simplebar/dist/simplebar.js"></script>
<script src="js/bootstrap.min.js"></script>
<!-- jsxc library -->
<script src="../bundle.js"></script>
<script>
$('#loginForm').submit(function(ev){
ev.preventDefault();
var url = $(this).find('[name="url"]').val();
var jid = $(this).find('[name="jid"]').val();
var password = $(this).find('[name="password"]').val();
jsxc.start(url, jid, password);
});
</script>
</body>
</html>
+51
Ver Arquivo
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg8"
version="1.1"
viewBox="0 0 3.1749999 3.1750001"
height="12"
width="12"
inkscape:version="0.91 r13725"
sodipodi:docname="presence_online.svg">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1879"
inkscape:window-height="1176"
id="namedview5307"
showgrid="false"
inkscape:zoom="55.625733"
inkscape:cx="3.7142348"
inkscape:cy="6.3732363"
inkscape:window-x="1241"
inkscape:window-y="24"
inkscape:window-maximized="1"
inkscape:current-layer="svg8" />
<defs
id="defs2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

Depois

Largura:  |  Altura:  |  Tamanho: 1.4 KiB

+100
Ver Arquivo
@@ -0,0 +1,100 @@
// jshint node:true
// var path = require('path');
var webpackConfig = require('./webpack.config.js');
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: [ /*'karma-typescript',*/ 'mocha', 'chai'],
// list of files / patterns to load in the browser
files: [
'./node_modules/jquery/dist/jquery.min.js',
'./node_modules/strophe.js/strophe.js',
'./node_modules/es6-promise/dist/es6-promise.js',
'test/*.spec.ts',
'test/**/*.spec.ts'
],
// list of files to exclude
exclude: [],
// webpack configuration
webpack: {
devtool: 'eval-source-map',
module: webpackConfig.module,
resolve: webpackConfig.resolve,
//externals: webpackConfig.externals,
// target: 'node',
node: {
fs: 'empty'
}
},
webpackMiddleware: {
quiet: false,
stats: {
colors: true
}
},
mime: {
'text/x-typescript': ['ts', 'tsx']
},
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'test/**/*.spec.ts': ['webpack' /*'karma-typescript'*/ ]
},
karmaTypescriptConfig: {
include: ['test/**/*.spec.ts']
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: [ /*'progress', 'karma-typescript',*/ 'mocha'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_DEBUG,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: false,
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity
});
};
+51 -22
Ver Arquivo
@@ -10,30 +10,59 @@
"type": "git",
"url": "https://github.com/jsxc/jsxc"
},
"scripts": {
"start": "webpack --config webpack.config.js",
"dev": "webpack --config webpack.config.js --watch",
"test": "karma start"
},
"devDependencies": {
"@types/chai": "^4.0.0",
"@types/jquery": "^3.2.3",
"@types/mocha": "^2.2.41",
"@types/simplebar": "^2.4.0",
"@types/sinon": "^2.3.1",
"autoprefixer-core": "^6.0.1",
"bower": "^1.8.0",
"grunt": "^1.0.1",
"grunt-autoprefixer": "^3.0.4",
"grunt-banner": "~0.6.0",
"grunt-contrib-clean": "~1.0.0",
"grunt-contrib-compress": "^1.4.1",
"grunt-contrib-concat": "^1.0.1",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-csslint": "^2.0.0",
"grunt-contrib-jshint": "~1.1.0",
"grunt-contrib-uglify": "^2.0.0",
"grunt-contrib-watch": "^1.0.0",
"grunt-data-uri": "^0.3.0",
"grunt-html-convert": "0.0.2",
"grunt-jsbeautifier": "^0.2.13",
"grunt-jsdoc": "^2.1.0",
"grunt-merge-data": "^0.4.5",
"grunt-prettysass": "^0.2.3",
"grunt-sass": "2.0.0",
"grunt-scss-lint": "^0.5.0",
"grunt-search": "^0.1.8",
"grunt-text-replace": "~0.4.0",
"node-sass": "4.3.0"
"chai": "^4.0.2",
"css-loader": "^0.28.4",
"es6-promise": "^4.1.0",
"extract-text-webpack-plugin": "^2.1.2",
"handlebars-loader": "^1.5.0",
"handlebars-runtime": "^1.0.12",
"i18next": "^8.4.2",
"karma": "^1.7.0",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^2.1.1",
"karma-firefox-launcher": "^1.0.1",
"karma-mocha": "^1.3.0",
"karma-mocha-reporter": "^2.2.3",
"karma-phantomjs-launcher": "^1.0.4",
"karma-sinon": "^1.0.5",
"karma-spec-reporter": "0.0.31",
"karma-typescript": "^3.0.4",
"karma-webpack": "^2.0.3",
"mocha": "^3.4.2",
"node-sass": "^4.5.3",
"precompile-handlebars": "^1.0.5",
"resolve-url-loader": "^2.0.3",
"sass-loader": "^6.0.6",
"sinon": "^2.3.5",
"strophe": "^1.2.4",
"ts-loader": "^2.2.0",
"ts-node": "^3.1.0",
"typescript": "^2.3.4",
"webpack": "^3.0.0"
},
"dependencies": {
"@types/emojione": "^2.2.1",
"@types/handlebars": "^4.0.33",
"@types/strophe": "^1.2.28",
"i18next": "^7.1.1",
"jquery": "^3.2.1",
"magnific-popup": "^1.1.0",
"moment": "^2.18.1",
"simplebar": "^2.4.3",
"strophe.js": "^1.2.14",
"tslib": "^1.7.1"
}
}
-72
Ver Arquivo
@@ -1,72 +0,0 @@
@import "modules";
#jsxc_buddylist {
list-style: none;
padding: 0;
margin: 0;
width: 204px;
z-index: 85;
.jsxc_unreadMsg {
.jsxc_name {
padding-right: 0;
}
}
.jsxc_oneway {
.jsxc_avatar, .jsxc_caption {
opacity: 0.7;
}
}
.jsxc_right {
float: right;
margin-right: 6px;
div {
font-weight: bold;
text-align: center;
font-size: 13px;
line-height: 20px;
color: $white;
&:hover {
opacity: 1;
}
}
}
.jsxc_more {
margin-right: 6px;
z-index: 10;
position: relative;
}
.jsxc_options {
height: 20px;
float: left;
border-radius: 2px;
background-color: $roster_icon_bg;
> div {
height: 20px;
width: 20px;
float: left;
margin-right: 0 1px;
background-repeat: no-repeat;
background-position: center center;
opacity: 0.6;
cursor: pointer;
&:hover {
opacity: 1;
}
}
}
&.jsxc_hideOffline {
.jsxc_rosteritem[data-status='offline'] {
display: none;
}
}
}
-57
Ver Arquivo
@@ -1,57 +0,0 @@
@import "../lib/magnific-popup/src/css/main";
@import "../lib/emojione/assets/css/emojione";
// BEGIN: bootstrap
@import "../lib/bootstrap/assets/stylesheets/bootstrap/variables";
@import "../lib/bootstrap/assets/stylesheets/bootstrap/mixins";
// Spec and IE10+
@keyframes progress-bar-stripes {
from {
background-position: 40px 0;
}
to {
background-position: 0 0;
}
}
#jsxc_dialog {
@import "../lib/bootstrap/assets/stylesheets/bootstrap/progress-bars";
}
#jsxc_dialog, #jsxc_webrtc {
@import "../lib/bootstrap/assets/stylesheets/bootstrap/utilities";
@import "../lib/bootstrap/assets/stylesheets/bootstrap/code";
@import "../lib/bootstrap/assets/stylesheets/bootstrap/grid";
@import "../lib/bootstrap/assets/stylesheets/bootstrap/alerts";
@import "../lib/bootstrap/assets/stylesheets/bootstrap/buttons";
@import "../lib/bootstrap/assets/stylesheets/bootstrap/button-groups";
@import "../lib/bootstrap/assets/stylesheets/bootstrap/forms";
.progress {
margin-bottom: 0;
.progress-bar {
width: 100%;
}
}
.mfp-close {
font-size: 23px;
}
}
.mfp-bg {
z-index: 9000;
}
.mfp-wrap {
z-index: 9010;
}
.mfp-content {
text-align: center;
}
// END: bootstrap
-530
Ver Arquivo
@@ -1,530 +0,0 @@
.jsxc_right {
text-align: right;
}
.jsxc_center {
text-align: center;
}
.jsxc_hidden {
display: none;
}
.jsxc_clear {
clear: both;
}
.jsxc_uppercase {
text-transform: uppercase;
}
.jsxc_sep {
border-top: 1px solid $separator;
}
.jsxc_name {
overflow: hidden;
cursor: pointer;
text-overflow: ellipsis;
white-space: nowrap;
}
.jsxc_maxWidth {
max-width: 500px;
}
.jsxc_meta {
text-align: right;
font-style: italic;
}
#jsxc_dialog {
padding: 20px;
min-width: 320px;
max-width: 100%;
display: inline-block;
text-align: left;
position: relative;
background: #FFF;
width: auto;
* {
box-sizing: border-box;
}
ul {
list-style: none;
margin: 0;
padding: 0;
}
li {
margin: 0;
padding: 0;
}
p {
margin-bottom: 1em;
input {
margin-bottom: 5px;
width: 60%;
outline: none;
}
input[type='submit'] {
width: auto;
}
}
table {
margin-bottom: 1em;
}
hr {
border: 0;
border-top: 1px solid #eee;
margin-top: 20px;
margin-bottom: 20px;
}
h3 {
font-size: 120%;
font-weight: bold;
margin-bottom: 10px;
margin-top: 20px;
}
.jsxc_right {
margin-top: 20px;
}
form {
fieldset {
margin-bottom: 30px;
padding: 0 30px;
border: 1px solid #d9d9d9;
h3 {
font-size: 15px;
color: #000;
background-color: #f2f2f2;
padding: 10px;
margin: 0 -30px 10px;
}
}
}
legend {
border: 0;
font-size: 20px;
}
input {
outline: none;
&:invalid {
border: 1px solid $dialog_input_invalid;
}
}
.btn-group button {
margin-right: 0;
}
input[readonly] {
background-color: $dialog_input_readonly_bg;
}
.jsxc_inputinfo {
padding: 0;
font-style: italic;
margin: 0;
}
.jsxc_waiting {
&:before {
content: " ";
width: 1em;
height: 1em;
display: inline-block;
background-size: 100%;
margin: 0 3px 0 0;
background-image: image-url("loading.gif");
}
}
.jsxc_libraries, .jsxc_credits {
max-width: 300px;
}
.jsxc_warning {
display: block;
background-color: #fbfe7a;
padding: 3px 10px;
border-radius: 3px;
}
}
.jsxc_avatar {
width: 36px;
height: 36px;
line-height: 36px;
margin: 0 5px;
background-color: $avatar_bg;
border-radius: 50%;
float: left;
text-align: center;
font-weight: bold;
font-size: 30px;
color: $avatar_color;
position: relative;
font-family: $font_sans;
background-size: cover;
background-position: center center;
img {
display: block;
width: 25px;
height: 25px;
position: absolute;
top: 0;
left: 0;
}
&:before {
position: absolute;
top: -2px;
left: -6px;
border: 2px solid $roster_bg;
}
}
ul.jsxc_vCard {
min-width: 400px;
ul {
margin-left: 20px;
}
li {
cursor: default !important;
}
}
// Spot which is attached to xmpp: uris
.jsxc_spot {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
text-indent: -99999em;
margin-top: 3px;
margin-right: 5px;
line-height: 100%;
cursor: pointer;
border: 1px solid $spot_border;
background-color: $white;
&:before {
position: absolute;
}
&.jsxc_online, &.jsxc_chat, &.jsxc_away, &.jsxc_xa, &.jsxc_dnd, &.jsxc_offline {
border: 0;
}
}
.jsxc_unread {
display: none;
}
.jsxc_unreadMsg {
.jsxc_name {
font-style: italic;
}
.jsxc_unread {
display: block;
background-color: $unread_bg;
border-radius: 11px;
color: $unread_color;
font-size: 80%;
padding: 2px;
line-height: 15px;
float: right;
margin-right: 3px;
margin-top: 4px;
}
}
// TODO: check if required
.jsxc_list {
.jsxc_inner {
box-sizing: border-box;
max-height: 0;
transition: max-height 0.5s;
overflow: hidden;
visibility: hidden;
position: absolute;
bottom: 100%;
left: 0;
}
&.jsxc_opened {
.jsxc_inner {
max-height: 1000px;
visibility: visible;
display: block;
}
}
}
#cboxWrapper {
outline: none;
}
.jsxc_loading {
margin: 0 auto;
width: 32px;
height: 32px;
border: 0;
background-size: 32px 32px !important;
background: image-url("loading.gif");
}
// @TODO: check
#jsxc_loginForm input[type='submit'] {
height: 34px;
display: inline-block;
padding: 6px 12px;
margin-bottom: 0;
font-size: 14px;
font-weight: normal;
line-height: 1.428571429;
text-align: center;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
border: 1px solid transparent;
border-radius: 4px;
user-select: none;
color: $white;
background-color: $loginForm_bg;
border-color: $loginForm_border;
}
.jsxc_oneway .jsxc_avatar {
filter: grayscale(100%);
}
img.jsxc_vCard {
float: right;
max-width: 200px;
max-height: 200px;
border: 5px solid $white;
border-radius: 2px;
}
.jsxc_alert {
padding: 15px;
margin-bottom: 20px;
border: 1px solid transparent;
border-radius: 4px;
&.jsxc_alert-warning {
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #faebcc;
}
}
.jsxc_btn {
width: auto;
min-width: 25px;
display: inline-block;
padding: 6px 12px;
margin: 0 2px;
font-size: 14px;
font-weight: 400;
line-height: 1.42857143;
text-align: center;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
user-select: none;
background-image: none;
border: 1px solid transparent;
border-radius: 4px;
transition: background-color 0.5s;
&.jsxc_btn-default {
border-color: #ccc;
color: #555;
background-color: rgba(240, 240, 240, 0.9);
&:hover {
background-color: #d6d6d6;
}
}
&.jsxc_btn-primary {
color: #fff;
background-color: #337ab7;
border-color: #2e6da4;
&:hover {
background-color: #296496;
}
}
&[disabled], &[disabled]:hover {
opacity: 0.65;
cursor: not-allowed;
color: #fff;
background-color: #337ab7;
border-color: #2e6da4;
}
}
.jsxc_menu {
display: none;
position: absolute;
background-color: #FFF;
color: #333;
border-radius: 3px;
z-index: 110;
margin: 8px 2px 5px 10px;
right: 0;
filter: drop-shadow(0 0 5px rgba(150, 150, 150, 0.75));
padding: 4px 12px;
padding-left: 5px;
&:after {
bottom: 100%;
right: 6px;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-color: rgba(238, 238, 238, 0);
border-bottom-color: #fff;
border-width: 10px;
}
&.jsxc_open {
display: block;
}
ul {
list-style: none;
margin: 0;
padding: 0;
}
li {
border: 0;
cursor: auto;
}
a {
color: #000;
opacity: 0.5;
white-space: nowrap;
&:hover {
text-decoration: none;
opacity: 1;
}
&.jsxc_disabled {
text-decoration: line-through;
opacity: 0.5;
&:hover {
text-decoration: line-through;
opacity: 0.5;
}
span {
cursor: default;
}
}
}
.jsxc_icon {
width: 16px;
height: 16px;
margin-right: 8px;
display: inline-block;
background-repeat: no-repeat;
background-size: contain;
background-position: center;
vertical-align: sub;
}
}
.jsxc_editicon {
background-image: image-url("edit_black.svg");
}
.jsxc_deleteicon {
background-image: image-url("delete_black.svg");
}
.jsxc_chaticon {
background-image: image-url("speech_balloon_black.svg");
}
.jsxc_videoicon {
background-image: image-url("camera_icon_black.svg");
}
.jsxc_infoicon {
background-image: image-url("info_black.svg");
}
.jsxc_settingsicon {
background-image: image-url("gear_black.svg");
}
.jsxc_helpicon {
background-image: image-url("help_black.svg");
}
.jsxc_contacticon {
background-image: image-url("contact_black.svg");
}
.jsxc_groupcontacticon {
background-image: image-url("groupcontact_black.svg");
}
.jsxc_bookmarkicon {
background-image: image-url("bookmark_black.svg");
}
.jsxc_announcementicon {
background-image: image-url("megaphone_icon_black.svg");
}
.jsxc_more {
float: right;
width: 44px;
height: 100%;
cursor: pointer;
background-image: image-url("more_black.svg");
background-repeat: no-repeat;
background-position: center;
opacity: 0.4;
&:hover {
opacity: 1;
}
@media (min-width: 768px) {
width: 25px;
}
}
-399
Ver Arquivo
@@ -1,399 +0,0 @@
#jsxc_roster {
position: fixed;
top: 0;
bottom: 0;
right: 0;
width: 200px;
overflow: visible;
border-left: 1px solid #e1e1e1;
display: none;
// border-left: 1px solid $roster_border_left;
z-index: 80;
margin-left: 10px;
background-color: $roster_bg;
a {
cursor: pointer;
}
.slimScrollDiv {
margin-bottom: 30px;
z-index: 40;
}
.jsxc_wait {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 160px;
padding: 20px;
background-color: $white;
z-index: 60;
img {
padding: 5px;
}
h3 {
margin-bottom: 5px;
font-size: 1.13em;
font-weight: bold;
}
}
input {
position: absolute;
margin: 0;
height: 35px;
padding: 7px 6px 5px;
font-size: 13px;
width: 145px;
border: 1px solid #ddd;
box-sizing: border-box;
background-image: none;
background-color: $roster_input_bg;
border-radius: 3px;
box-shadow: inner 0 0 5px $roster_input_shadow;
outline: none;
}
p {
color: $roster_color;
padding: 10px;
a {
color: $roster_a;
text-decoration: underline;
}
}
.jsxc_avatar {
position: relative;
cursor: pointer;
img {
cursor: pointer;
}
}
.jsxc_expand input {
left: 51px;
width: 137px;
}
&.jsxc_noConnection {
.slimScrollDiv {
display: none;
}
> .jsxc_bottom {
display: none;
}
}
&.jsxc_state_hidden {
display: block;
right: -200px;
transition: right 0.5s;
#jsxc_toggleRoster {
&:before {
transform: rotate(0deg);
}
}
}
&.jsxc_state_shown {
display: block;
right: 0;
transition: right 0.5s;
}
> .jsxc_bottom {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
line-height: 34px;
background-color: $roster_bottom_bg;
z-index: 50;
padding-right: 4px;
&:hover {
background-color: $roster_bottom_bg_hover;
}
.jsxc_inner {
width: 100%;
}
ul {
padding: 0;
margin: 0;
width: 100%;
border-top: 1px solid $roster_bottom_border_top;
background-color: $roster_bottom_bg;
li:last-child {
border-bottom: 1px solid $roster_bottom_border_top;
}
}
li {
height: 44px;
background-color: $roster_bottom_bg;
color: $roster_bottom_color;
cursor: pointer;
width: 100%;
padding-left: 44px;
line-height: 44px;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
background-repeat: no-repeat;
background-position: 15px center;
background-size: 16px 16px;
opacity: 0.8;
&.jsxc_disabled {
color: $roster_bottom_disabled;
cursor: default;
}
&:hover:not(.jsxc_disabled) {
color: $roster_bottom_color_hover;
background-color: $roster_bottom_bg_hover;
}
&.jsxc_warning {
background-color: $warning_bg;
&:hover {
background-color: $warning_bg_hover;
}
}
}
> div > span {
cursor: pointer;
}
}
}
#jsxc_toggleRoster {
width: 14px;
height: 100%;
position: absolute;
left: -14px !important;
top: 0;
z-index: 110;
background-color: transparent;
cursor: pointer;
&:hover {
background-color: $roster_toggle_hover;
}
&:before {
content: " ";
position: absolute;
display: block;
width: 0;
top: 50%;
right: 0;
border-style: solid;
border-width: 6px 4px 6px 0;
border-color: transparent $roster_bg;
transform: rotate(180deg);
}
}
.jsxc_rosteritem {
padding: 0;
margin: 0;
height: 44px;
border-bottom: 1px solid $roster_bottom_border_top;
cursor: pointer;
width: 100%;
position: relative;
color: $roster_color;
font-family: $font_sans;
line-height: 44px;
padding-left: 6px;
padding-top: 4px;
padding-bottom: 4px;
box-sizing: border-box;
&:hover {
background-color: $roster_bg_hover;
}
&.jsxc_bookmarked {
.jsxc_avatar:after {
content: " ";
width: 20%;
height: 30%;
position: absolute;
top: 0;
right: 2px;
background-size: contain;
background-repeat: no-repeat;
background-image: image-url("bookmark_red.svg");
}
}
}
.jsxc_caption {
padding-right: 30px;
height: 100%;
line-height: 100%;
// padding-top: 4px;
box-sizing: border-box;
* {
cursor: pointer;
}
.jsxc_name {
height: 100%;
line-height: 40px;
.jsxc_min & {
height: 50%;
line-height: 20px;
}
.jsxc_rosteritem & {
height: 50%;
line-height: 20px;
}
}
.jsxc_lastmsg {
font-size: 12px;
display: none;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
.jsxc_min & {
display: block;
height: 50%;
line-height: 17.5px;
}
.jsxc_rosteritem & {
display: block;
height: 50%;
line-height: 17.5px;
}
.jsxc_text {
opacity: 0.6;
}
.jsxc_unread {
line-height: 100%;
font-size: 8px;
color: #fff;
text-align: center;
display: none;
height: 1em;
width: 1em;
border-radius: 50%;
background-color: orange;
vertical-align: top;
margin: 0;
float: none;
.jsxc_unreadMsg & {
display: inline-block !important;
}
}
.jsxc_emoticon {
vertical-align: middle;
}
}
}
#jsxc_avatar {
cursor: default !important;
}
#jsxc_presence {
cursor: pointer;
padding-left: 2px;
overflow: hidden;
> span {
opacity: 0.8;
}
li {
position: relative;
&:before {
// Presence indicator
position: absolute;
top: 50%;
left: 10px;
margin-top: -8px;
border: 2px solid whitesmoke;
}
}
}
#jsxc_menu {
height: 44px;
width: 44px;
cursor: pointer;
float: right;
text-align: center;
&:hover > span {
opacity: 1;
}
> span {
opacity: 0.5;
display: block;
width: 100%;
height: 100%;
background-image: image-url("menu_black.svg");
background-repeat: no-repeat;
background-position: center 10px;
background-size: 17px;
}
@media (min-width: 768px) {
height: 30px;
width: 30px;
}
}
#jsxc_notice {
height: 30px;
width: 30px;
float: right;
text-align: center;
line-height: 30px;
span {
background-color: $notice_bg;
border-radius: 11px;
color: $notice_color;
font-size: 80%;
padding: 2px;
position: relative;
animation: bounce 2s 1s infinite;
}
> span:empty {
display: none;
}
}
-49
Ver Arquivo
@@ -1,49 +0,0 @@
.jsxc_online, .jsxc_chat, .jsxc_away, .jsxc_xa, .jsxc_dnd {
&:before {
content: " ";
display: block;
width: 12px;
height: 12px;
border-radius: 100%;
line-height: 12px;
text-align: center;
color: #fff;
z-index: 99;
background-repeat: no-repeat;
background-position: center;
background-size: 100%;
box-sizing: content-box;
}
}
.jsxc_online:before {
background-color: $state_online;
}
.jsxc_chat:before {
background-image: image-url("presence_chat.svg");
background-color: $state_chat;
}
.jsxc_away:before {
background-image: image-url("presence_away.svg");
background-color: $state_away;
}
.jsxc_xa:before {
background-image: image-url("presence_xa.svg");
background-color: $state_xa;
}
.jsxc_dnd:before {
background-image: image-url("presence_dnd.svg");
background-color: $state_dnd;
}
.jsxc_hidden {
display: none;
}
.jsxc_invalid {
border: 2px solid $invalid_border !important;
}
-706
Ver Arquivo
@@ -1,706 +0,0 @@
#jsxc_windowList {
position: fixed;
bottom: 0;
right: 210px;
left: 0;
z-index: 50;
transition: right 0.5s;
&.jsxc_roster_hidden {
right: 10px;
}
@media (min-width: 768px) {
clip: rect(-10000px, 10000px, 30px, 30px);
}
> ul {
list-style: none;
padding: 0;
margin: 0;
position: absolute;
bottom: 0;
right: 0;
height: 44px;
overflow: visible;
white-space: nowrap;
transition: right 0.5s;
> li {
padding: 0;
margin: 0;
display: inline-block;
height: 44px;
width: 46px;
position: relative;
overflow: visible;
margin-right: 5px;
cursor: pointer;
white-space: normal;
&.jsxc_normal {
transition: width 0.2s;
width: 250px;
}
&.jsxc_min {
transition: width 0.2s;
width: 46px !important;
// overwrite resizeable width
@media (min-width: 768px) {
width: 200px !important;
}
.jsxc_emoticons {
display: none;
}
.jsxc_tools {
display: none;
}
}
}
}
}
#jsxc_windowListSB {
position: fixed;
left: 0;
bottom: 0;
width: 30px;
height: 30px;
@media (max-width: 768px) {
display: none;
}
> {
div {
box-sizing: border-box;
width: 14px;
height: 100%;
background-color: $windowListSB_bg;
color: $windowListSB_color;
text-align: center;
line-height: 30px;
float: left;
cursor: pointer;
user-select: none;
&:hover {
background-color: $windowListSB_bg_hover;
}
}
.jsxc_disabled {
background-color: $windowListSB_bg_disabled !important;
color: $windowListSB_color_disabled;
cursor: default !important;
display: none;
}
}
}
.jsxc_bar {
background-color: $window_bar_bg;
cursor: pointer;
height: 44px;
line-height: 26px;
padding: 2px;
color: $window_bar_color;
width: 100%;
box-sizing: border-box;
position: relative;
&:hover {
.jsxc_normal & {
color: $window_bar_color_hover;
}
}
.jsxc_tools {
&:hover {
.jsxc_normal & {
color: #000;
}
}
}
.jsxc_min & {
background-color: $window_min_bar_bg;
color: $window_min_bar_color;
}
}
.jsxc_window {
position: absolute;
bottom: -284px;
top: auto;
left: 0;
right: 0;
height: auto;
background-color: $window_bg;
z-index: 80;
cursor: default;
border: 1px solid $window_border;
border-bottom: 0;
.jsxc_min & {
transition: bottom 0.2s;
}
.jsxc_normal & {
transition: bottom 0.2s;
}
.jsxc_showOverlay & {
.jsxc_overlay {
display: block !important;
}
}
.jsxc_emoticons {
height: 44px;
width: 44px;
position: absolute;
bottom: 0;
left: 0;
cursor: pointer;
&:after {
content: " ";
background-image: image-url("smiley.svg");
background-position: center center;
background-repeat: no-repeat;
background-size: 30px 30px;
opacity: 0.3;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.jsxc_inner {
left: 5px;
}
ul {
width: 210px;
margin-bottom: 8px;
background-color: $emoticon_selection_bg;
border-radius: 3px;
z-index: 200;
list-style-type: none;
padding: 3px;
position: relative;
&:after {
content: " ";
position: absolute;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-top: 8px solid $emoticon_selection_bg;
display: block;
width: 0;
z-index: 1;
left: 7px;
top: 100%;
}
}
li {
div {
float: left;
cursor: pointer;
border-radius: 3px;
background-size: 30px 30px;
width: 30px;
height: 30px;
&:hover {
background-color: $emoticon_selection_hover;
}
}
}
&:hover:after {
opacity: 0.5;
}
}
.jsxc_fade {
position: relative;
.jsxc_overlay {
display: none;
background-color: rgba(0, 0, 0, 0.5);
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 100;
overflow-y: scroll;
> div {
background-color: #fff;
margin: 30px 10px;
padding: 5px;
border-radius: 3px;
text-align: center;
position: relative;
.jsxc_close {
position: absolute;
top: 0;
right: 0;
height: 44px;
width: 44px;
&:after {
content: "×";
position: absolute;
top: 4px;
right: 4px;
font-size: 20px;
font-family: Arial, sans-serif;
cursor: pointer;
color: #000;
opacity: 0.4;
}
&:hover {
&:after {
opacity: 1;
}
}
@media (min-width: 768px) {
width: 30px;
height: 30px;
}
}
.jsxc_body {
margin-top: 20px;
}
p {
margin-bottom: 10px;
}
li {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
a:hover {
text-decoration: underline;
}
}
}
}
.jsxc_avatar {
margin-top: 1px;
}
.jsxc_textarea {
width: 100%;
overflow: hidden;
padding: 3px;
}
.slimScrollDiv {
margin: 0 0 6px;
left: auto !important;
top: auto !important;
}
textarea {
&.jsxc_textinput {
width: 100%;
height: 44px;
margin: 0;
padding: 14px 40px 12px;
outline: none;
border-radius: 0;
box-sizing: border-box;
border: 0;
display: block;
resize: none;
transition: height 0.5s;
font-size: 13px;
}
&::placeholder {
color: $window_placeholder;
opacity: 0.3;
}
}
.jsxc_tools {
float: right;
> .jsxc_disabled {
opacity: 0.3;
cursor: default !important;
}
> div {
width: 25px;
height: 40px;
display: block;
float: left;
color: $tools_color;
opacity: 0.4;
font-family: $font_sans;
line-height: 40px;
cursor: pointer;
text-align: center;
&.jsxc_settings {
opacity: 1;
}
}
}
.jsxc_close {
font-size: 20px;
&:hover {
color: $window_close_hover;
opacity: 1;
}
}
.jsxc_more {
background-image: image-url("more_white.svg");
opacity: 0.4;
}
.ui-resizable-w {
left: 0;
}
.ui-resizable-nw {
top: 0;
left: 0;
width: 15px;
height: 15px;
z-index: 95 !important;
background-image: image-url("resize_gray.svg");
}
.ui-resizable-n {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 15px;
z-index: 100;
}
}
.jsxc_chatmessage {
margin: 3px;
padding: 4px;
word-wrap: break-word;
background-color: $chatmessage_bg;
position: relative;
outline: none;
clear: both;
&.jsxc_error {
opacity: 0.7;
&:before {
content: " ";
position: absolute;
top: 3px;
right: 3px;
width: 8px;
height: 8px;
background-color: yellow;
}
}
a {
color: $chatmessage_a;
text-decoration: underline;
display: inline-block;
max-width: 100%;
position: relative;
&[download]:before {
content: " ";
position: absolute;
top: 0;
right: 0;
bottom: 5px;
left: 0;
border-radius: 3px;
background-color: rgba(255, 255, 255, 0.7);
background-image: url("../img/download_icon_black.svg");
background-size: contain;
background-position: center center;
background-repeat: no-repeat;
opacity: 0;
transition: opacity 0.5s;
}
&[download]:hover {
&:before {
opacity: 0.6;
}
}
}
img {
max-width: 100%;
}
.jsxc_avatar {
display: none;
}
.jsxc_attachment {
border-radius: 3px;
background-color: #fff;
padding: 3px;
padding-left: 30px;
min-height: 30px;
margin-bottom: 5px;
background-position: 3px center;
background-size: 25px 25px;
background-repeat: no-repeat;
background-image: image-url("filetypes/file.svg");
img {
border-radius: 3px;
}
&.jsxc_image {
line-height: 0;
padding: 0;
background-image: url("");
display: inline-block;
}
&.jsxc_application {
background-image: image-url("filetypes/application.svg");
}
&.jsxc_application-pdf {
background-image: image-url("filetypes/application-pdf.svg");
}
&.jsxc_audio {
background-image: image-url("filetypes/audio.svg");
}
&.jsxc_video {
background-image: image-url("filetypes/video.svg");
opacity: 1;
}
&.jsxc_text {
background-image: image-url("filetypes/text.svg");
}
}
}
.jsxc_timestamp {
font-size: 8px;
color: $chatmessage_timestamp;
line-height: 8px;
overflow: hidden;
white-space: nowrap;
max-width: 100%;
text-overflow: ellipsis;
clear: both;
}
.jsxc_encrypted {
&.jsxc_received.jsxc_out .jsxc_timestamp {
margin-right: 1px;
}
.jsxc_timestamp:after {
content: " ";
display: inline-block;
width: 10px;
height: 8px;
margin-left: 2px;
background-image: image-url("padlock_close_grey.svg");
background-size: contain;
background-repeat: no-repeat;
}
}
.jsxc_in {
float: left;
position: relative;
max-width: 76%;
margin-left: 10px;
border-radius: 3px;
background-color: $chatmessage_in_bg;
&:after {
content: " ";
position: absolute;
border-style: solid;
border-width: 5px 6px 5px 0;
border-color: transparent $chatmessage_in_bg;
display: block;
width: 0;
z-index: 1;
left: -6px;
bottom: 10px;
}
.jsxc_timestamp {
float: left;
}
}
.jsxc_out {
float: right;
position: relative;
max-width: 76%;
margin-right: 10px;
padding-right: 10px;
border-radius: 3px;
background-color: $chatmessage_out_bg;
&:after {
content: " ";
position: absolute;
border-style: solid;
border-width: 5px 0 5px 6px;
border-color: transparent $chatmessage_out_bg;
display: block;
width: 0;
z-index: 1;
right: -6px;
bottom: 10px;
}
&.jsxc_received {
&:before {
content: "";
position: absolute;
bottom: 2px;
right: 2px;
font-size: 12px;
line-height: 12px;
color: $chatmessage_received;
}
.jsxc_timestamp {
margin-right: 4px;
}
}
.jsxc_timestamp {
float: right;
}
}
.jsxc_sys {
width: auto;
max-width: none;
padding-right: 4px;
box-sizing: border-box;
margin-right: 3px;
border-radius: 3px;
background-color: transparent;
font-size: 0.8em;
font-style: italic;
.jsxc_emoticon {
width: 1.2em;
height: 1.2em;
vertical-align: middle;
}
&.jsxc_composing {
text-align: center;
font-size: 0.9em;
font-style: italic;
display: block;
opacity: 0;
overflow: hidden;
transition: opacity 0.6s;
&:before {
content: " ";
width: 1.5em;
height: 1em;
display: inline-block;
background-size: 80%;
background-repeat: no-repeat;
margin: 0 3px 0 0;
background-image: image-url("composing.png");
}
&.jsxc_fadein {
opacity: 1;
}
}
}
div.jsxc_settings {
position: relative;
.jsxc_inner {
left: auto;
top: 100%;
right: -6px;
}
}
div.jsxc_transfer {
background-image: image-url("padlock_open_black.svg");
background-repeat: no-repeat;
background-position: center center;
background-size: 14px 14px;
opacity: 0.3;
height: 44px;
width: 44px;
position: absolute;
bottom: 0;
right: 0;
cursor: pointer;
&:hover {
opacity: 1;
}
&.jsxc_disabled {
background-image: image-url("padlock_open_disabled_black.svg");
cursor: default;
&:hover {
opacity: 0.3;
}
}
&.jsxc_fin {
opacity: 1;
background-image: image-url("padlock_close_grey.svg");
}
&.jsxc_enc {
opacity: 1;
background-image: image-url("padlock_close_orange.svg");
&.jsxc_trust {
background-image: image-url("padlock_close_green.svg");
}
}
}
-17
Ver Arquivo
@@ -1,17 +0,0 @@
@import "colors";
@import "dep";
//fonts
$font_sans: Arial, sans-serif;
$font_serif: serif;
@import "modules";
@import "buddylist";
@import "state";
@import "emoticons";
@import "roster";
@import "window";
@import "muc";
@import "_jsxc";
@import "webrtc";
+20
Ver Arquivo
@@ -0,0 +1,20 @@
@import "modules/all";
@import "vendor/all";
//fonts
$font_sans: Arial, sans-serif;
$font_serif: serif;
@import "partials/button";
@import "partials/dialog";
@import "partials/emoticons";
@import "partials/icon";
@import "partials/jsxc";
@import "partials/menu";
@import "partials/roster";
@import "partials/webrtc";
@import "partials/window-list";
@import "partials/window";
@import "partials/jsxc";
@import "partials/webrtc";
+5
Ver Arquivo
@@ -0,0 +1,5 @@
@import "animation";
@import "colors";
@import "muc";
@import "webrtc";
@import "presence";
@@ -25,12 +25,23 @@ $dialog_input_invalid: $red;
$dialog_input_readonly_bg: $gray90;
$spot_bg: $white;
$spot_border: $black;
$state_online: green;
$state_chat: green;
$state_away: $orange;
$state_xa: $orange;
$state_dnd: $red;
$state_offline: $gray60;
$presenceColors: (
online: green,
chat: green,
away: $orange,
xa: $orange,
dnd: $red,
offline: $gray60
);
$notice_bg: $orange;
$notice_color: $black;
$window_unread_cycle: $orange;
+6
Ver Arquivo
@@ -0,0 +1,6 @@
%muc-avatar-icon {
text-indent: 999px;
background-image: url("../img/group_white.svg");
background-size: 70% 70% !important;
background-repeat: no-repeat;
}
+26
Ver Arquivo
@@ -0,0 +1,26 @@
$presences: online chat away xa dnd;
@mixin presenceIndicator($target) {
@each $presence in $presences {
[data-presence="#{$presence}"] #{$target} {
&:before {
content: "";
display: block;
width: 12px;
height: 12px;
border-radius: 100%;
line-height: 12px;
text-align: center;
color: #fff;
z-index: 99;
background-repeat: no-repeat;
background-position: center;
background-size: 100%;
box-sizing: content-box;
background-image: url("../img/presence_#{$presence}.svg");
background-color: map-get($presenceColors, $presence);
}
}
}
}
+9
Ver Arquivo
@@ -0,0 +1,9 @@
%fullscreen {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 9000;
background-color: $fullscreen_bg;
}
+47
Ver Arquivo
@@ -0,0 +1,47 @@
.jsxc-btn {
width: auto;
min-width: 25px;
display: inline-block;
padding: 6px 12px;
margin: 0 2px;
font-size: 14px;
font-weight: 400;
line-height: 1.42857143;
text-align: center;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
user-select: none;
background-image: none;
border: 1px solid transparent;
border-radius: 4px;
transition: background-color 0.5s;
&.jsxc-btn-default {
border-color: #ccc;
color: #555;
background-color: rgba(240, 240, 240, 0.9);
&:hover {
background-color: #d6d6d6;
}
}
&.jsxc-btn-primary {
color: #fff;
background-color: #337ab7;
border-color: #2e6da4;
&:hover {
background-color: #296496;
}
}
&[disabled], &[disabled]:hover {
opacity: 0.65;
cursor: not-allowed;
color: #fff;
background-color: #337ab7;
border-color: #2e6da4;
}
}
+127
Ver Arquivo
@@ -0,0 +1,127 @@
.jsxc-dialog {
padding: 20px;
min-width: 320px;
max-width: 100%;
display: inline-block;
text-align: left;
position: relative;
background: #FFF;
width: auto;
* {
box-sizing: border-box;
}
ul {
list-style: none;
margin: 0;
padding: 0;
}
li {
margin: 0;
padding: 0;
}
p {
margin-bottom: 1em;
input {
margin-bottom: 5px;
width: 60%;
outline: none;
}
input[type='submit'] {
width: auto;
}
}
table {
margin-bottom: 1em;
}
hr {
border: 0;
border-top: 1px solid #eee;
margin-top: 20px;
margin-bottom: 20px;
}
h3 {
font-size: 120%;
font-weight: bold;
margin-bottom: 10px;
margin-top: 20px;
}
.jsxc-right {
margin-top: 20px;
}
form {
fieldset {
margin-bottom: 30px;
padding: 0 30px;
border: 1px solid #d9d9d9;
h3 {
font-size: 15px;
color: #000;
background-color: #f2f2f2;
padding: 10px;
margin: 0 -30px 10px;
}
}
}
legend {
border: 0;
font-size: 20px;
}
input {
outline: none;
&:invalid {
border: 1px solid $dialog_input_invalid;
}
}
.btn-group button {
margin-right: 0;
}
input[readonly] {
background-color: $dialog_input_readonly_bg;
}
.jsxc-inputinfo {
padding: 0;
font-style: italic;
margin: 0;
}
.jsxc-waiting {
&:before {
content: " ";
width: 1em;
height: 1em;
display: inline-block;
background-size: 100%;
margin: 0 3px 0 0;
background-image: url("../img/loading.gif");
}
}
.jsxc-libraries, .jsxc-credits {
max-width: 300px;
}
.jsxc-warning {
display: block;
background-color: #fbfe7a;
padding: 3px 10px;
border-radius: 3px;
}
}
@@ -1,4 +1,4 @@
.jsxc_emoticon {
.jsxc-emoticon {
display: inline-block;
width: 19px;
height: 19px;
@@ -6,14 +6,14 @@
border: 0;
vertical-align: bottom;
&.jsxc_large {
&.jsxc-large {
width: 40px;
height: 40px;
margin-bottom: 7px;
}
}
#jsxc_roster .jsxc_emoticon.jsxc_large {
#jsxc-roster .jsxc-emoticon.jsxc-large {
width: 19px;
height: 19px;
}
@@ -21,7 +21,7 @@
$emoticons: angel, angry, smile, grin, sad, wink, tonguesmile, surpised, kiss, sunglassess, crysad, doubt, zip, thumbsup, thumbsdown, beer, devil, kissing, rose, music, love, tired, surprised;
@each $emoticon in $emoticons {
.jsxc_#{$emoticon} {
background: image-url("emotions/#{$emoticon}.png");
.jsxc-#{$emoticon} {
background: url("../img/emotions/#{$emoticon}.png");
}
}
+43
Ver Arquivo
@@ -0,0 +1,43 @@
.jsxc-icon-edit {
background-image: url("../img/edit_black.svg");
}
.jsxc-icon-delete {
background-image: url("../img/delete_black.svg");
}
.jsxc-icon-chat {
background-image: url("../img/speech_balloon_black.svg");
}
.jsxc-icon-video {
background-image: url("../img/camera_icon_black.svg");
}
.jsxc-icon-info {
background-image: url("../img/info_black.svg");
}
.jsxc-icon-setting {
background-image: url("../img/gear_black.svg");
}
.jsxc-icon-help {
background-image: url("../img/help_black.svg");
}
.jsxc-icon-contact {
background-image: url("../img/contact_black.svg");
}
.jsxc-icon-groupcontact {
background-image: url("../img/groupcontact_black.svg");
}
.jsxc-icon-bookmark {
background-image: url("../img/bookmark_black.svg");
}
.jsxc-icon-announcement {
background-image: url("../img/megaphone_icon_black.svg");
}
+209
Ver Arquivo
@@ -0,0 +1,209 @@
.jsxc-right {
text-align: right;
}
.jsxc-center {
text-align: center;
}
.jsxc-hidden {
display: none;
}
.jsxc-clear {
clear: both;
}
.jsxc-uppercase {
text-transform: uppercase;
}
.jsxc-separator {
border-top: 1px solid $separator;
}
.jsxc-name {
overflow: hidden;
cursor: pointer;
text-overflow: ellipsis;
white-space: nowrap;
}
.jsxc-max-width {
max-width: 500px;
}
.jsxc-meta {
text-align: right;
font-style: italic;
}
.jsxc-invalid {
border: 2px solid $invalid_border !important;
}
.jsxc-avatar {
width: 36px;
height: 36px;
line-height: 36px;
margin: 0 5px;
background-color: $avatar_bg;
border-radius: 50%;
float: left;
text-align: center;
font-weight: bold;
font-size: 30px;
color: $avatar_color;
position: relative;
font-family: $font_sans;
background-size: cover;
background-position: center center;
img {
display: block;
width: 25px;
height: 25px;
position: absolute;
top: 0;
left: 0;
}
&:before {
position: absolute;
top: -2px;
left: -6px;
border: 2px solid $roster_bg;
}
}
ul.jsxc-vCard {
min-width: 400px;
ul {
margin-left: 20px;
}
li {
cursor: default !important;
}
}
// Spot which is attached to xmpp: uris
.jsxc-spot {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
text-indent: -99999em;
margin-top: 3px;
margin-right: 5px;
line-height: 100%;
cursor: pointer;
border: 1px solid $spot_border;
background-color: $white;
&:before {
position: absolute;
}
&.jsxc-online, &.jsxc-chat, &.jsxc-away, &.jsxc-xa, &.jsxc-dnd, &.jsxc-offline {
border: 0;
}
}
.jsxc-unread {
display: none;
}
.jsxc-unreadMsg {
.jsxc-name {
font-style: italic;
}
.jsxc-unread {
display: block;
background-color: $unread_bg;
border-radius: 11px;
color: $unread_color;
font-size: 80%;
padding: 2px;
line-height: 15px;
float: right;
margin-right: 3px;
margin-top: 4px;
}
}
.jsxc-loading {
margin: 0 auto;
width: 32px;
height: 32px;
border: 0;
background-size: 32px 32px !important;
background: url("../img/loading.gif");
}
// @TODO: check
#jsxc-login-form input[type='submit'] {
height: 34px;
display: inline-block;
padding: 6px 12px;
margin-bottom: 0;
font-size: 14px;
font-weight: normal;
line-height: 1.428571429;
text-align: center;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
border: 1px solid transparent;
border-radius: 4px;
user-select: none;
color: $white;
background-color: $loginForm_bg;
border-color: $loginForm_border;
}
.jsxc-oneway .jsxc-avatar {
filter: grayscale(100%);
}
img.jsxc-vCard {
float: right;
max-width: 200px;
max-height: 200px;
border: 5px solid $white;
border-radius: 2px;
}
.jsxc-alert {
padding: 15px;
margin-bottom: 20px;
border: 1px solid transparent;
border-radius: 4px;
&.jsxc-alert-warning {
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #faebcc;
}
}
.jsxc-more {
float: right;
width: 44px;
height: 100%;
cursor: pointer;
background-image: url("../img/more_black.svg");
background-repeat: no-repeat;
background-position: center;
opacity: 0.4;
&:hover {
opacity: 1;
}
@media (min-width: 768px) {
width: 25px;
}
}
+102
Ver Arquivo
@@ -0,0 +1,102 @@
.jsxc-menu {
.jsxc-inner {
box-sizing: border-box;
max-height: 0;
transition: max-height 0.5s;
overflow: hidden;
visibility: hidden;
position: absolute;
bottom: 100%;
left: 0;
}
&.jsxc-opened {
.jsxc-inner {
max-height: 1000px;
visibility: visible;
display: block;
}
}
ul {
list-style: none;
margin: 0;
padding: 0;
}
li {
border: 0;
cursor: auto;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
.jsxc-menu-dropdown {
.jsxc-inner {
display: none;
position: absolute;
background-color: #FFF;
color: #333;
border-radius: 3px;
z-index: 110;
margin: 8px 2px 5px 10px;
right: 0;
filter: drop-shadow(0 0 5px rgba(150, 150, 150, 0.75));
padding: 4px 12px 4px 5px;
top: 100%;
bottom: auto;
overflow: visible;
&:after {
bottom: 100%;
right: 6px;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-color: rgba(238, 238, 238, 0);
border-bottom-color: #fff;
border-width: 10px;
}
}
a {
color: #000;
opacity: 0.5;
white-space: nowrap;
&:hover {
text-decoration: none;
opacity: 1;
}
&.jsxc-disabled {
text-decoration: line-through;
opacity: 0.5;
&:hover {
text-decoration: line-through;
opacity: 0.5;
}
span {
cursor: default;
}
}
}
.jsxc-icon {
width: 16px;
height: 16px;
margin-right: 8px;
display: inline-block;
background-repeat: no-repeat;
background-size: contain;
background-position: center;
vertical-align: sub;
}
}
+18 -25
Ver Arquivo
@@ -1,22 +1,15 @@
%muc-avatar-icon {
text-indent: 999px;
background-image: image-url("group_white.svg");
background-size: 70% 70% !important;
background-repeat: no-repeat;
}
.jsxc_windowItem {
&.jsxc_groupchat.jsxc_normal {
.jsxc_fade {
.jsxc-window-item {
&.jsxc-groupchat.jsxc-normal {
.jsxc-fade {
padding-top: 44px;
}
.jsxc_fingerprints, .jsxc_verification, .jsxc_transfer, .jsxc_video, .jsxc_sendFile {
.jsxc-fingerprints, .jsxc-verification, .jsxc-transfer, .jsxc-video, .jsxc-sendFile {
display: none;
}
.jsxc_members {
background-image: image-url("group_white.svg");
.jsxc-members {
background-image: url("../img/group_white.svg");
background-size: 15px 15px;
background-repeat: no-repeat;
background-position: center;
@@ -26,10 +19,10 @@
}
}
.jsxc_chatmessage.jsxc_in {
.jsxc-chatmessage.jsxc-in {
margin-left: 50px;
.jsxc_avatar {
.jsxc-avatar {
display: block;
position: absolute;
bottom: 0;
@@ -42,14 +35,14 @@
}
}
.jsxc_bar {
.jsxc_avatar {
.jsxc-bar {
.jsxc-avatar {
@extend %muc-avatar-icon;
}
}
}
.jsxc_memberlist {
.jsxc-memberlist {
height: 44px;
width: 100%;
background-color: $window_bar_bg;
@@ -79,18 +72,18 @@
margin-right: 2px;
}
.jsxc_name {
.jsxc-name {
display: none;
}
.jsxc_avatar {
.jsxc-avatar {
margin-left: 2px;
margin-right: 0;
}
}
}
&.jsxc_expand {
&.jsxc-expand {
ul {
white-space: normal;
@@ -100,7 +93,7 @@
height: 40px;
line-height: 40px;
.jsxc_name {
.jsxc-name {
display: block;
cursor: default;
@@ -109,7 +102,7 @@
}
}
.jsxc_avatar {
.jsxc-avatar {
margin-right: 4px;
}
}
@@ -119,11 +112,11 @@
}
li[data-type='groupchat'] {
.jsxc_avatar {
.jsxc-avatar {
@extend %muc-avatar-icon;
}
.jsxc_video {
.jsxc-video {
display: none;
}
}
+492
Ver Arquivo
@@ -0,0 +1,492 @@
#jsxc-roster {
position: fixed;
top: 0;
bottom: 0;
right: 0;
width: 200px;
overflow: visible;
border-left: 1px solid #e1e1e1;
z-index: 80;
margin-left: 10px;
background-color: $roster_bg;
transition: right 0.5s;
.jsxc-roster-hidden & {
right: -200px;
}
a {
cursor: pointer;
}
.jsxc-avatar {
position: relative;
cursor: pointer;
img {
cursor: pointer;
}
}
.jsxc-wait {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 160px;
padding: 20px;
background-color: $white;
z-index: 60;
img {
padding: 5px;
}
h3 {
margin-bottom: 5px;
font-size: 1.13em;
font-weight: bold;
}
}
input {
position: absolute;
margin: 0;
height: 35px;
padding: 7px 6px 5px;
font-size: 13px;
width: 145px;
border: 1px solid #ddd;
box-sizing: border-box;
background-image: none;
background-color: $roster_input_bg;
border-radius: 3px;
box-shadow: inner 0 0 5px $roster_input_shadow;
outline: none;
}
p {
color: $roster_color;
padding: 10px;
a {
color: $roster_a;
text-decoration: underline;
}
}
.jsxc-expand input {
left: 51px;
width: 137px;
}
&.jsxc-state_hidden {
display: block;
right: -200px;
transition: right 0.5s;
}
&.jsxc-state_shown {
display: block;
right: 0;
transition: right 0.5s;
}
> .jsxc-bottom {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
line-height: 34px;
background-color: $roster_bottom_bg;
z-index: 50;
padding-right: 4px;
&:hover {
background-color: $roster_bottom_bg_hover;
}
.jsxc-inner {
width: 100%;
}
ul {
padding: 0;
margin: 0;
width: 100%;
border-top: 1px solid $roster_bottom_border_top;
background-color: $roster_bottom_bg;
li:last-child {
border-bottom: 1px solid $roster_bottom_border_top;
}
}
li {
height: 44px;
background-color: $roster_bottom_bg;
color: $roster_bottom_color;
cursor: pointer;
width: 100%;
padding-left: 44px;
line-height: 44px;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
background-repeat: no-repeat;
background-position: 15px center;
background-size: 16px 16px;
opacity: 0.8;
&.jsxc-disabled {
color: $roster_bottom_disabled;
cursor: default;
}
&:hover:not(.jsxc-disabled) {
color: $roster_bottom_color_hover;
background-color: $roster_bottom_bg_hover;
}
&.jsxc-warning {
background-color: $warning_bg;
&:hover {
background-color: $warning_bg_hover;
}
}
}
> div > span {
cursor: pointer;
}
}
form {
padding: 15px;
button,
input {
width: 100%;
margin: 0 0 5px;
border-radius: 0;
}
.btn-primary {
background-color: #dadada;
border-color: #c1c1c1;
transition: background-color 0.5s;
&:hover {
background-color: #a2a2a2;
}
}
label {
display: block;
}
input {
position: static;
}
}
.jsxc-roster-status {
display: none;
position: absolute;
bottom: 0;
}
&.jsxc-status-show {
.jsxc-roster-status {
display: block;
}
.jsxc-bottom, .jsxc-contact-list {
display: none;
}
}
}
.jsxc-roster-toggle {
width: 14px;
height: 100%;
position: absolute;
left: -14px;
top: 0;
z-index: 110;
background-color: transparent;
cursor: pointer;
&:hover {
background-color: $roster_toggle_hover;
}
}
.jsxc-roster-item {
padding: 0;
margin: 0;
height: 44px;
border-bottom: 1px solid $roster_bottom_border_top;
cursor: pointer;
width: 100%;
position: relative;
color: $roster_color;
font-family: $font_sans;
line-height: 44px;
padding-left: 6px;
padding-top: 4px;
padding-bottom: 4px;
box-sizing: border-box;
&:hover {
background-color: $roster_bg_hover;
}
&.jsxc-bookmarked {
.jsxc-avatar:after {
content: " ";
width: 20%;
height: 30%;
position: absolute;
top: 0;
right: 2px;
background-size: contain;
background-repeat: no-repeat;
background-image: url("../img/bookmark_red.svg");
}
}
}
.jsxc-caption {
padding-right: 30px;
height: 100%;
line-height: 100%;
// padding-top: 4px;
box-sizing: border-box;
* {
cursor: pointer;
}
.jsxc-name {
height: 100%;
line-height: 40px;
.jsxc-min & {
height: 50%;
line-height: 20px;
}
.jsxc-rosteritem & {
height: 50%;
line-height: 20px;
}
}
.jsxc-lastmsg {
font-size: 12px;
display: none;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
.jsxc-min & {
display: block;
height: 50%;
line-height: 17.5px;
}
.jsxc-rosteritem & {
display: block;
height: 50%;
line-height: 17.5px;
}
.jsxc-text {
opacity: 0.6;
}
.jsxc-unread {
line-height: 100%;
font-size: 8px;
color: #fff;
text-align: center;
display: none;
height: 1em;
width: 1em;
border-radius: 50%;
background-color: orange;
vertical-align: top;
margin: 0;
float: none;
.jsxc-unreadMsg & {
display: inline-block !important;
}
}
.jsxc-emoticon {
vertical-align: middle;
}
}
}
.jsxc-menu-presence {
cursor: pointer;
padding-left: 2px;
overflow: hidden;
> span {
opacity: 0.8;
}
li {
position: relative;
&:before {
// Presence indicator
position: absolute;
top: 50%;
left: 10px;
margin-top: -8px;
border: 2px solid whitesmoke;
}
}
}
.jsxc-menu-main {
height: 44px;
width: 44px;
cursor: pointer;
float: right;
text-align: center;
&:hover > span {
opacity: 1;
}
> span {
opacity: 0.5;
display: block;
width: 100%;
height: 100%;
background-image: url("../img/menu_black.svg");
background-repeat: no-repeat;
background-position: center 10px;
background-size: 17px;
}
@media (min-width: 768px) {
height: 30px;
width: 30px;
}
}
#jsxc-notice {
height: 30px;
width: 30px;
float: right;
text-align: center;
line-height: 30px;
span {
background-color: $notice_bg;
border-radius: 11px;
color: $notice_color;
font-size: 80%;
padding: 2px;
position: relative;
animation: bounce 2s 1s infinite;
}
> span:empty {
display: none;
}
}
.jsxc-contact-list {
@include presenceIndicator(".jsxc-avatar");
list-style: none;
padding: 0;
margin: 0;
width: 204px;
z-index: 85;
&.jsxc-hide-offline {
.jsxc-roster-item[data-status='offline'] {
display: none;
}
}
.jsxc-unreadMsg {
.jsxc-name {
padding-right: 0;
}
}
.jsxc-oneway {
.jsxc-avatar,
.jsxc-caption {
opacity: 0.7;
}
}
.jsxc-right {
float: right;
margin-right: 6px;
div {
font-weight: bold;
text-align: center;
font-size: 13px;
line-height: 20px;
color: $white;
&:hover {
opacity: 1;
}
}
}
.jsxc-menu {
float: right;
height: 100%;
width: 42px;
> span {
display: block;
height: 100%;
width: 100%;
background-image: url("../img/more_black.svg");
background-repeat: no-repeat;
background-position: center center;
opacity: 0.6;
cursor: pointer;
&:hover {
opacity: 1;
}
}
.jsxc-inner {
left: auto;
right: 5px;
padding: 4px;
overflow: visible;
a {
display: block;
line-height: 42px;
width: 42px;
text-align: center;
span {
margin: 0;
}
}
}
}
}
+49 -60
Ver Arquivo
@@ -1,16 +1,5 @@
@import "colors";
%fullscreen {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 9000;
background-color: $fullscreen_bg;
}
#jsxc_webrtc {
#jsxc-webrtc {
position: fixed;
top: 0;
bottom: 0;
@@ -19,7 +8,7 @@
z-index: 9999;
background-color: black;
.jsxc_status {
.jsxc-status {
z-index: 9999;
border-radius: 20px;
display: none;
@@ -37,7 +26,7 @@
opacity: 1 !important;
}
li .jsxc_name {
li .jsxc-name {
cursor: auto;
&:hover {
@@ -46,7 +35,7 @@
}
}
.jsxc_establishing, .jsxc_ringing {
.jsxc-establishing, .jsxc-ringing {
&:after {
content: " ";
position: absolute;
@@ -62,14 +51,14 @@
$establishingColor1: #a1a1a1;
$establishingColor2: #f1f1f1;
.jsxc_establishing:before {
.jsxc-establishing:before {
content: " ";
display: block;
width: 40px;
height: 10px;
box-sizing: border-box;
background-color: $establishingColor1;
animation-name: jsxc_establishing;
animation-name: jsxc-establishing;
animation-duration: 2s;
animation-iteration-count: infinite;
position: absolute;
@@ -80,7 +69,7 @@ $establishingColor2: #f1f1f1;
margin-top: -5px;
}
@keyframes jsxc_establishing {
@keyframes jsxc-establishing {
0% {
border-width: 0;
background-color: $establishingColor1;
@@ -105,7 +94,7 @@ $establishingColor2: #f1f1f1;
$ringingColor1: #98d48f;
$ringingColor2: #76ba6c;
.jsxc_ringing:before {
.jsxc-ringing:before {
content: " ";
display: block;
width: 20px;
@@ -113,7 +102,7 @@ $ringingColor2: #76ba6c;
box-sizing: border-box;
background-color: $ringingColor1;
border-radius: 50%;
animation-name: jsxc_ringing;
animation-name: jsxc-ringing;
animation-duration: 2s;
animation-iteration-count: infinite;
position: absolute;
@@ -124,7 +113,7 @@ $ringingColor2: #76ba6c;
margin-top: -10px;
}
@keyframes jsxc_ringing {
@keyframes jsxc-ringing {
0% {
background-color: $ringingColor1;
width: 20px;
@@ -150,17 +139,17 @@ $ringingColor2: #76ba6c;
}
}
.jsxc_bell:before {
.jsxc-bell:before {
content: " ";
display: block;
width: 80px;
height: 80px;
box-sizing: border-box;
background-image: image-url("bell.svg");
background-image: url("../img/bell.svg");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
animation-name: jsxc_ringing;
animation-name: jsxc-ringing;
animation-duration: 1.5s;
animation-iteration-count: infinite;
position: absolute;
@@ -171,7 +160,7 @@ $ringingColor2: #76ba6c;
margin-top: -40px;
}
@keyframes jsxc_bell {
@keyframes jsxc-bell {
0% {
margin-left: -50px;
}
@@ -205,7 +194,7 @@ $ringingColor2: #76ba6c;
}
}
.jsxc_videoContainer {
.jsxc-videoContainer {
position: absolute;
top: 0;
left: 0;
@@ -213,7 +202,7 @@ $ringingColor2: #76ba6c;
bottom: 0;
background-color: $video_bg;
&.jsxc_minimized {
&.jsxc-minimized {
position: fixed;
top: 10px;
left: 10px;
@@ -223,7 +212,7 @@ $ringingColor2: #76ba6c;
background-color: transparent;
box-shadow: 0 0 10px #a1a1a1;
.jsxc_localvideo {
.jsxc-localvideo {
position: static;
display: block;
}
@@ -233,30 +222,30 @@ $ringingColor2: #76ba6c;
display: none;
}
.jsxc_noRemoteVideo {
.jsxc-noRemoteVideo {
display: none;
}
@media (min-width: 768px) {
right: 250px;
.jsxc_controlbar {
.jsxc-controlbar {
opacity: 0;
}
&:hover {
.jsxc_controlbar {
.jsxc-controlbar {
opacity: 1;
}
}
}
}
.jsxc_remotevideo {
.jsxc-remotevideo {
@extend %fullscreen;
}
.jsxc_noRemoteVideo {
.jsxc-noRemoteVideo {
@extend %fullscreen;
p {
@@ -303,7 +292,7 @@ $ringingColor2: #76ba6c;
}
}
.jsxc_localvideo {
.jsxc-localvideo {
width: 160px;
height: 120px;
position: absolute;
@@ -320,7 +309,7 @@ div {
height: 100%;
background-color: $black;
&.jsxc_localvideo {
&.jsxc-localvideo {
border: 1px solid $white;
}
}
@@ -330,23 +319,23 @@ div {
height: 100%;
background-color: $black;
&.jsxc_localvideo {
&.jsxc-localvideo {
border: 1px solid $white;
}
}
&.jsxc_video {
background-image: image-url("camera_icon_white.svg");
&.jsxc-video {
background-image: url("../img/camera_icon_white.svg");
background-repeat: no-repeat;
background-position: center center;
background-size: 15px 15px;
opacity: 0.4;
&.jsxc_disabled {
background-image: image-url("camera_disabled_icon_white.svg");
&.jsxc-disabled {
background-image: url("../img/camera_disabled_icon_white.svg");
}
&:not(.jsxc_disabled) {
&:not(.jsxc-disabled) {
&:hover {
opacity: 1;
}
@@ -354,7 +343,7 @@ div {
}
}
.jsxc_controlbar {
.jsxc-controlbar {
position: absolute;
top: 50px;
left: 0;
@@ -369,7 +358,7 @@ div {
top: initial;
}
&.jsxc_visible {
&.jsxc-visible {
opacity: 1;
}
@@ -381,7 +370,7 @@ div {
display: inline-block;
}
.jsxc_videoControl {
.jsxc-videoControl {
height: 44px;
width: 44px;
margin: 0 5px;
@@ -397,24 +386,24 @@ div {
}
}
.jsxc_hangUp {
background-image: image-url("hang_up_red.svg");
.jsxc-hangUp {
background-image: url("../img/hang_up_red.svg");
}
.jsxc_fullscreen {
background-image: image-url("fullscreen_white.svg");
.jsxc-fullscreen {
background-image: url("../img/fullscreen_white.svg");
}
.jsxc_showchat {
.jsxc-showchat {
float: right;
}
}
.jsxc_multi > div {
.jsxc-multi > div {
display: none;
}
.jsxc_snapshotbar {
.jsxc-snapshotbar {
width: 100%;
display: none;
@@ -423,7 +412,7 @@ div {
}
}
.jsxc_buttongroup {
.jsxc-buttongroup {
display: inline;
button {
@@ -441,7 +430,7 @@ div {
}
}
.jsxc_chatarea {
.jsxc-chatarea {
position: absolute;
top: 0;
right: 0;
@@ -454,21 +443,21 @@ div {
display: block;
}
.jsxc_settings {
.jsxc-settings {
display: none !important;
}
.jsxc_close {
.jsxc-close {
display: none !important;
}
.jsxc_video {
.jsxc-video {
display: none !important;
}
.jsxc_bar {}
.jsxc-bar {}
.jsxc_window {
.jsxc-window {
bottom: 0;
box-shadow: none;
}
@@ -481,10 +470,10 @@ div {
}
}
.jsxc_fullscreen.jsxc_localvideo {
.jsxc-fullscreen.jsxc-localvideo {
border: 1px solid $white;
}
.jsxc_videoSuitable .jsxc_name {
.jsxc-videoSuitable .jsxc-name {
font-style: italic;
}
+104
Ver Arquivo
@@ -0,0 +1,104 @@
#jsxc-window-list {
@include presenceIndicator(".jsxc-window-bar .jsxc-avatar");
position: fixed;
bottom: 0;
right: 210px;
left: 0;
z-index: 50;
transition: right 0.5s;
.jsxc-roster-hidden & {
right: 10px;
}
@media (min-width: 768px) {
clip: rect(-10000px, 10000px, 30px, 30px);
}
> ul {
list-style: none;
padding: 0;
margin: 0;
position: absolute;
bottom: 0;
right: 0;
height: 44px;
overflow: visible;
white-space: nowrap;
transition: right 0.5s;
> li {
padding: 0;
margin: 0;
display: inline-block;
height: 44px;
width: 46px;
position: relative;
overflow: visible;
margin-right: 5px;
cursor: pointer;
white-space: normal;
&.jsxc-normal {
transition: width 0.2s;
width: 250px;
}
&.jsxc-minimized {
transition: width 0.2s;
width: 46px !important;
// overwrite resizeable width
@media (min-width: 768px) {
width: 200px !important;
}
.jsxc-emoticons {
display: none;
}
.jsxc-tools {
display: none;
}
}
}
}
}
#jsxc-window-list-handler {
position: fixed;
left: 0;
bottom: 0;
width: 30px;
height: 30px;
@media (max-width: 768px) {
display: none;
}
> {
div {
box-sizing: border-box;
width: 14px;
height: 100%;
background-color: $windowListSB_bg;
color: $windowListSB_color;
text-align: center;
line-height: 30px;
float: left;
cursor: pointer;
user-select: none;
&:hover {
background-color: $windowListSB_bg_hover;
}
}
.jsxc-disabled {
background-color: $windowListSB_bg_disabled !important;
color: $windowListSB_color_disabled;
cursor: default !important;
display: none;
}
}
}
+657
Ver Arquivo
@@ -0,0 +1,657 @@
.jsxc-window {
position: absolute;
bottom: 0;
top: auto;
left: 0;
right: 0;
height: auto;
background-color: $window_bg;
z-index: 80;
cursor: default;
border: 1px solid $window_border;
border-bottom: 0;
.jsxc-showOverlay & {
.jsxc-overlay {
display: block !important;
}
}
.jsxc-avatar {
margin-top: 1px;
}
.jsxc-message-area {
position: absolute;
top: 0;
right: 2px;
bottom: 44px;
left: 0;
overflow: auto;
padding: 3px;
z-index: 10;
&::-webkit-scrollbar {
width: 2px;
height: 2px;
}
&::-webkit-scrollbar-button {
width: 0;
height: 0;
}
&::-webkit-scrollbar-thumb {
background: #d1d1d1;
border: 0;
border-right: 3px solid transparent;
border-radius: 1px;
&:hover {
background: #c1c1c1;
}
&:active {
background: #b1b1b1;
}
}
&::-webkit-scrollbar-track {
background: transparent;
border: 0 none #fff;
border-radius: 50px;
&:hover {
background: transparent;
}
&:active {
background: transparent;
}
}
&::-webkit-scrollbar-corner {
background: transparent;
}
}
// .slimScrollDiv {
// margin: 0 0 6px;
// left: auto !important;
// top: auto !important;
// }
textarea {
&.jsxc-message-input {
width: 100%;
height: 44px;
margin: 0;
padding: 14px 40px 12px;
outline: none;
border-radius: 0;
box-sizing: border-box;
border: 0;
display: block;
resize: none;
transition: height 0.2s;
font-size: 13px;
position: absolute;
bottom: 0;
}
&::placeholder {
color: $window_placeholder;
opacity: 0.3;
}
}
.jsxc-tools {
float: right;
> .jsxc-disabled {
opacity: 0.3;
cursor: default !important;
}
> div {
width: 25px;
height: 40px;
display: block;
float: left;
color: $tools_color;
opacity: 0.4;
font-family: $font_sans;
line-height: 40px;
cursor: pointer;
text-align: center;
&.jsxc-menu {
opacity: 1;
}
}
}
.jsxc-close {
font-size: 20px;
&:hover {
color: $window_close_hover;
opacity: 1;
}
}
.jsxc-more {
background-image: url("../img/more_white.svg");
opacity: 0.4;
}
// .ui-resizable-w {
// left: 0;
// }
//
// .ui-resizable-nw {
// top: 0;
// left: 0;
// width: 15px;
// height: 15px;
// z-index: 95 !important;
// background-image: url("../img/resize_gray.svg");
// }
//
// .ui-resizable-n {
// position: absolute;
// top: 0;
// left: 0;
// right: 0;
// height: 15px;
// z-index: 100;
// }
}
.jsxc-window-bar {
background-color: $window_bar_bg;
cursor: pointer;
height: 44px;
line-height: 26px;
padding: 2px;
color: $window_bar_color;
width: 100%;
box-sizing: border-box;
position: relative;
transition: background-color 0.3s;
&:hover {
.jsxc-normal & {
color: $window_bar_color_hover;
}
}
.jsxc-tools {
&:hover {
.jsxc-normal & {
color: #000;
}
}
}
.jsxc-minimized & {
background-color: $window_min_bar_bg;
color: $window_min_bar_color;
}
.jsxc-highlight & {
background-color: orange;
}
}
.jsxc-window-fade {
position: relative;
height: 320px;
transition: height 0.2s;
overflow: hidden;
.jsxc-normal & {}
.jsxc-minimized & {
height: 0;
}
.jsxc-overlay {
display: none;
background-color: rgba(0, 0, 0, 0.5);
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 100;
overflow-y: scroll;
> div {
background-color: #fff;
margin: 30px 10px;
padding: 5px;
border-radius: 3px;
text-align: center;
position: relative;
.jsxc-close {
position: absolute;
top: 0;
right: 0;
height: 44px;
width: 44px;
&:after {
content: "×";
position: absolute;
top: 4px;
right: 4px;
font-size: 20px;
font-family: Arial, sans-serif;
cursor: pointer;
color: #000;
opacity: 0.4;
}
&:hover {
&:after {
opacity: 1;
}
}
@media (min-width: 768px) {
width: 30px;
height: 30px;
}
}
.jsxc-body {
margin-top: 20px;
}
p {
margin-bottom: 10px;
}
li {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
a:hover {
text-decoration: underline;
}
}
}
}
.jsxc-menu-emoticons {
height: 44px;
width: 44px;
position: absolute;
bottom: 0;
left: 0;
cursor: pointer;
z-index: 30;
&:after {
content: " ";
background-image: url("../img/smiley.svg");
background-position: center center;
background-repeat: no-repeat;
background-size: 30px 30px;
opacity: 0.3;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.jsxc-inner {
left: 5px;
}
ul {
width: 217px;
margin-bottom: 8px;
background-color: $emoticon_selection_bg;
border-radius: 3px;
z-index: 200;
list-style-type: none;
padding: 3px;
position: relative;
&:after {
content: " ";
position: absolute;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-top: 8px solid $emoticon_selection_bg;
display: block;
width: 0;
z-index: 1;
left: 7px;
top: 100%;
}
}
li {
display: inline-block;
cursor: pointer;
border-radius: 3px;
width: 30px;
height: 30px;
&:hover {
background-color: $emoticon_selection_hover;
}
.jsxc-emoticon {
width: 100%;
height: 100%;
background-size: contain;
}
}
&:hover:after {
opacity: 0.5;
}
}
.jsxc-chatmessage {
margin: 3px;
padding: 4px;
word-wrap: break-word;
background-color: $chatmessage_bg;
position: relative;
outline: none;
clear: both;
&.jsxc-error {
opacity: 0.7;
&:before {
content: " ";
position: absolute;
top: 3px;
right: 3px;
width: 8px;
height: 8px;
background-color: yellow;
}
}
a {
color: $chatmessage_a;
text-decoration: underline;
display: inline-block;
max-width: 100%;
position: relative;
&[download]:before {
content: " ";
position: absolute;
top: 0;
right: 0;
bottom: 5px;
left: 0;
border-radius: 3px;
background-color: rgba(255, 255, 255, 0.7);
background-image: url("../img/download_icon_black.svg");
background-size: contain;
background-position: center center;
background-repeat: no-repeat;
opacity: 0;
transition: opacity 0.5s;
}
&[download]:hover {
&:before {
opacity: 0.6;
}
}
}
img {
max-width: 100%;
}
.jsxc-avatar {
display: none;
}
.jsxc-attachment {
border-radius: 3px;
background-color: #fff;
padding: 3px 3px 3px 30px;
min-height: 30px;
margin-bottom: 5px;
background-position: 3px center;
background-size: 25px 25px;
background-repeat: no-repeat;
background-image: url("../img/filetypes/file.svg");
img {
border-radius: 3px;
}
&.jsxc-image {
line-height: 0;
padding: 0;
background-image: url("");
display: inline-block;
}
&.jsxc-application {
background-image: url("../img/filetypes/application.svg");
}
&.jsxc-application-pdf {
background-image: url("../img/filetypes/application-pdf.svg");
}
&.jsxc-audio {
background-image: url("../img/filetypes/audio.svg");
}
&.jsxc-video {
background-image: url("../img/filetypes/video.svg");
opacity: 1;
}
&.jsxc-text {
background-image: url("../img/filetypes/text.svg");
}
}
}
.jsxc-timestamp {
font-size: 8px;
color: $chatmessage_timestamp;
line-height: 8px;
overflow: hidden;
white-space: nowrap;
max-width: 100%;
text-overflow: ellipsis;
clear: both;
}
.jsxc-encrypted {
&.jsxc-received.jsxc-out .jsxc-timestamp {
margin-right: 1px;
}
.jsxc-timestamp:after {
content: " ";
display: inline-block;
width: 10px;
height: 8px;
margin-left: 2px;
background-image: url("../img/padlock_close_grey.svg");
background-size: contain;
background-repeat: no-repeat;
}
}
.jsxc-in {
float: left;
position: relative;
max-width: 76%;
margin-left: 10px;
border-radius: 3px;
background-color: $chatmessage_in_bg;
&:after {
content: " ";
position: absolute;
border-style: solid;
border-width: 5px 6px 5px 0;
border-color: transparent $chatmessage_in_bg;
display: block;
width: 0;
z-index: 1;
left: -6px;
bottom: 10px;
}
.jsxc-timestamp {
float: left;
}
}
.jsxc-out {
float: right;
position: relative;
max-width: 76%;
margin-right: 10px;
padding-right: 10px;
border-radius: 3px;
background-color: $chatmessage_out_bg;
&:after {
content: " ";
position: absolute;
border-style: solid;
border-width: 5px 0 5px 6px;
border-color: transparent $chatmessage_out_bg;
display: block;
width: 0;
z-index: 1;
right: -6px;
bottom: 10px;
}
&.jsxc-received {
&:before {
content: "";
position: absolute;
bottom: 2px;
right: 2px;
font-size: 12px;
line-height: 12px;
color: $chatmessage_received;
}
.jsxc-timestamp {
margin-right: 4px;
}
}
.jsxc-timestamp {
float: right;
}
}
.jsxc-sys {
width: auto;
max-width: none;
padding-right: 4px;
box-sizing: border-box;
margin-right: 3px;
border-radius: 3px;
background-color: transparent;
font-size: 0.8em;
font-style: italic;
.jsxc-emoticon {
width: 1.2em;
height: 1.2em;
vertical-align: middle;
}
&.jsxc-composing {
text-align: center;
font-size: 0.9em;
font-style: italic;
display: block;
opacity: 0;
overflow: hidden;
transition: opacity 0.6s;
&:before {
content: " ";
width: 1.5em;
height: 1em;
display: inline-block;
background-size: 80%;
background-repeat: no-repeat;
margin: 0 3px 0 0;
background-image: url("../img/composing.png");
}
&.jsxc-fadein {
opacity: 1;
}
}
}
.jsxc-menu-settings {
position: relative;
.jsxc-inner {
left: auto;
top: 100%;
right: -6px;
}
}
div.jsxc-transfer {
background-image: url("../img/padlock_open_black.svg");
background-repeat: no-repeat;
background-position: center center;
background-size: 14px 14px;
opacity: 0.3;
height: 44px;
width: 44px;
position: absolute;
bottom: 0;
right: 0;
cursor: pointer;
z-index: 20;
&:hover {
opacity: 1;
}
&.jsxc-disabled {
background-image: url("../img/padlock_open_disabled_black.svg");
cursor: default;
&:hover {
opacity: 0.3;
}
}
&.jsxc-fin {
opacity: 1;
background-image: url("../img/padlock_close_grey.svg");
}
&.jsxc-enc {
opacity: 1;
background-image: url("../img/padlock_close_orange.svg");
&.jsxc-trust {
background-image: url("../img/padlock_close_green.svg");
}
}
}
+61
Ver Arquivo
@@ -0,0 +1,61 @@
@import "../../lib/magnific-popup/src/css/main";
@import "../../lib/emojione/assets/css/emojione";
// BEGIN: bootstrap
@import "../../lib/bootstrap/assets/stylesheets/bootstrap/variables";
@import "../../lib/bootstrap/assets/stylesheets/bootstrap/mixins";
// Spec and IE10+
@keyframes progress-bar-stripes {
from {
background-position: 40px 0;
}
to {
background-position: 0 0;
}
}
#jsxc-dialog {
@import "../../lib/bootstrap/assets/stylesheets/bootstrap/progress-bars";
}
#jsxc-dialog, #jsxc-webrtc {
@import "../../lib/bootstrap/assets/stylesheets/bootstrap/utilities";
@import "../../lib/bootstrap/assets/stylesheets/bootstrap/code";
@import "../../lib/bootstrap/assets/stylesheets/bootstrap/grid";
@import "../../lib/bootstrap/assets/stylesheets/bootstrap/alerts";
@import "../../lib/bootstrap/assets/stylesheets/bootstrap/buttons";
@import "../../lib/bootstrap/assets/stylesheets/bootstrap/button-groups";
@import "../../lib/bootstrap/assets/stylesheets/bootstrap/forms";
.progress {
margin-bottom: 0;
.progress-bar {
width: 100%;
}
}
.mfp-close {
font-size: 23px;
}
}
.mfp-bg {
z-index: 9000;
}
.mfp-wrap {
z-index: 9010;
}
.mfp-content {
text-align: center;
}
// END: bootstrap
#cboxWrapper {
outline: none;
}
+334
Ver Arquivo
@@ -0,0 +1,334 @@
import Storage from './Storage'
import {IConnection} from './connection/ConnectionInterface'
import * as Connector from './connection/xmpp/connector'
import XMPPConnection from './connection/xmpp/Connection'
import StorageConnection from './connection/storage/Connection'
import JID from './JID'
import Contact from './Contact'
import ContactData from './ContactData'
import Roster from './ui/Roster'
import ChatWindow from './ui/ChatWindow'
import ChatWindowList from './ui/ChatWindowList'
import SortedPersistentMap from './SortedPersistentMap'
import PersistentMap from './PersistentMap'
import Log from './util/Log'
import {Presence} from './connection/AbstractConnection'
import Client from './Client'
import {Notice, NoticeData, TYPE as NOTICETYPE} from './Notice'
interface IConnectionParameters {
url:string,
jid: string,
sid?:string,
rid?:string,
timestamp?:number,
inactivity?:number
};
export default class Account {
private storage:Storage;
private uid:string;
private connection:IConnection;
private connectionArguments;
private connectionParameters:IConnectionParameters;
private contacts = {};
private windows:SortedPersistentMap;
private notices:SortedPersistentMap;
private contact:Contact;
constructor(boshUrl: string, jid: string, sid: string, rid:string);
constructor(boshUrl: string, jid: string, password: string);
constructor(uid:string);
constructor() {
if (arguments.length === 1) {
this.uid = arguments[0];
} else if (arguments.length === 3 || arguments.length === 4) {
this.uid = (new JID(arguments[1])).bare;
this.connectionArguments = arguments;
}
this.connection = new StorageConnection(this);
this.contact = new Contact(this, new ContactData({
jid: new JID(this.uid),
name: this.uid
}));
Roster.get().setRosterAvatar(this.contact);
this.restoreContacts();
this.initNotices();
this.initWindows();
this.getStorage().registerHook('contact:', (contactData) => {
let contact = new Contact(this, contactData.jid);
if (typeof this.contacts[contact.getId()] === 'undefined') { console.log('add', contactData.jid)
this.contacts[contact.getId()] = contact;
Roster.get().add(contact);
}
});
}
public connect() {
let self = this;
if (!this.connectionArguments) {
this.reloadConnectionData();
}
if (self.connectionParameters && self.connectionParameters.inactivity && (new Date()).getTime() - self.connectionParameters.timestamp > self.connectionParameters.inactivity) {
Log.warn('Credentials expired')
this.closeAllChatWindows();
return Promise.reject('Credentials expired');
}
return Connector.login.apply(this, this.connectionArguments).then(this.successfulConnected);
}
public getContact(jid:JID):Contact {
return this.contacts[jid.bare];
}
public addContact(data:ContactData):Contact {
let contact = new Contact(this, data);
contact.save();
this.contacts[contact.getId()] = contact;
this.save();
return contact;
}
public removeContact(contact:Contact) {
let id = contact.getId();
if (this.contacts[id]) {
delete this.contacts[id];
Roster.get().remove(contact);
//@REVIEW contact.getChatWindow would be nice
let chatWindow = this.windows.get(id);
if (chatWindow) {
this.closeChatWindow(chatWindow);
}
}
}
public removeAllContacts() {
for(let id in this.contacts) {
let contact = this.contacts[id];
delete this.contacts[id];
Roster.get().remove(contact);
}
}
public openChatWindow(contact:Contact) {
let chatWindow = new ChatWindow(this, contact);
chatWindow = ChatWindowList.get().add(chatWindow);
this.windows.push(chatWindow);
this.save();
return chatWindow;
}
public closeChatWindow(chatWindow:ChatWindow) {
// let id = chatWindow.getContact().getId();
console.log('chatWindow', chatWindow)
ChatWindowList.get().remove(chatWindow);
this.windows.remove(chatWindow);
this.save();
}
public closeAllChatWindows() {
this.windows.empty((id, chatWindow) => {
ChatWindowList.get().remove(chatWindow);
});
}
public addNotice(noticeData:NoticeData) {
let notice = new Notice(this.getStorage(), noticeData);
if (this.notices.get(notice.getId())) {
return;
}
//Roster.get().addNotice(this, notice);
this.notices.push(notice);
}
public removeNotice(notice:Notice) { console.log('removeNotice', notice);
//Roster.get().removeNotice(this, notice);
this.notices.remove(notice);
}
public getStorage() {
if(!this.storage) {
this.storage = new Storage(this.uid);
}
return this.storage;
}
public getConnection():IConnection {
return this.connection;
}
public getUid() {
return this.uid;
}
public getJID():JID {
let storedAccountData = this.getStorage().getItem('account') || {};
let jidString = (storedAccountData.connectionParameters) ? storedAccountData.connectionParameters.jid : this.getUid();
//@REVIEW maybe promise?
return new JID(jidString);
}
public remove() {
this.removeAllContacts();
this.closeAllChatWindows();
Client.removeAccount(this);
}
private successfulConnected = (data) => {
let connection = data.connection;
let status = data.status;
this.connectionParameters = $.extend(this.connectionParameters, {
url: connection.service,
jid: connection.jid,
sid: connection._proto.sid,
rid: connection._proto.rid,
timestamp: (new Date()).getTime()
});
if (connection._proto.inactivity) {
this.connectionParameters.inactivity = connection._proto.inactivity * 1000;
}
this.save();
connection.connect_callback = (status) => {
if (status === Strophe.Status.DISCONNECTED) {
this.connectionDisconnected();
}
}
connection.nextValidRid = (rid) => {
this.connectionParameters.timestamp = (new Date()).getTime();
this.connectionParameters.rid = rid;
this.save();
};
this.connection = new XMPPConnection(this, connection);
if (status === Strophe.Status.CONNECTED) {
Roster.get().setPresence(Presence.online);
Roster.get().refreshOwnPresenceIndicator();
this.connection.getRoster().then(() => {
this.connection.sendPresence();
});
} else {
this.connection.sendPresence();
}
}
private connectionDisconnected() {
console.log('disconnected');
this.remove();
}
private save() {
this.getStorage().setItem('account', {
connectionParameters: this.connectionParameters,
contacts: Object.keys(this.contacts),
// windows: Object.keys(this.windows)
});
}
private reloadConnectionData() {
let storedAccountData = this.getStorage().getItem('account') || {};
console.log('storedAccountData', storedAccountData)
this.connectionParameters = storedAccountData.connectionParameters;
let p = this.connectionParameters;
this.connectionArguments = [p.url, (new JID(p.jid)).full, p.sid, p.rid];
}
private restoreContacts() {
let storedAccountData = this.getStorage().getItem('account');
storedAccountData.contacts.forEach((id) => {
this.contacts[id] = new Contact(this, id);
Roster.get().add(this.contacts[id]);
});
}
private initWindows() {
this.windows = new SortedPersistentMap(this.getStorage(), 'windows');
this.windows.setRemoveHook((id, chatWindow) => {
console.log('remove hook', id, chatWindow);
if (chatWindow) {
ChatWindowList.get().remove(chatWindow);
}
});
this.windows.setPushHook((id) => {
let chatWindow = new ChatWindow(this, this.contacts[id]);
this.windows[id] = chatWindow;
ChatWindowList.get().add(chatWindow);
return chatWindow;
});
this.windows.init();
}
private initNotices() {
this.notices = new SortedPersistentMap(this.getStorage(), 'notices');
this.notices.setRemoveHook((id) => {
Roster.get().removeNotice(this, id);
});
this.notices.setPushHook((id) => {
let notice = new Notice(this.getStorage(), id);
Roster.get().addNotice(this, notice);
return notice;
});
this.notices.init();
}
}
+112
Ver Arquivo
@@ -0,0 +1,112 @@
import Options from './Options';
import Log from './util/Log';
export default class Attachment {
private mimeType:string;
private data:any;
private thumbnailData:any;
private size:number;
private persistent:boolean;
private name:string;
constructor(name:string, mimeType:string, data:any);
constructor(id:string);
constructor() {
// @TODO
}
public save():boolean {
if (this.isImage() && this.data && !this.thumbnailData) {
this.generateThumbnail();
}
if (this.size > Options.get('maxStorableSize')) {
Log.debug('Attachment to large to store');
this.persistent = false;
return false;
//@TODO delete data and store thumbnailData
}
//@TODO save to storage
return true;
}
public getSize():number {
return this.size;
}
public getMimeType():string {
return this.mimeType;
}
public getThumbnailData() {
return this.thumbnailData;
}
public getName() {
return this.name;
}
public isPersistent():boolean {
return this.persistent;
}
public isImage():boolean {
return /^image\//i.test(this.mimeType);
}
public hasThumbnailData():boolean {
return !!this.thumbnailData;
}
public hasData():boolean {
return !!this.data;
}
public clearData() {
this.data = null;
}
private generateThumbnail():void {
if(typeof Image === 'undefined') {
return;
}
var sHeight, sWidth, sx, sy;
var dHeight = 100,
dWidth = 100;
var canvas = <HTMLCanvasElement> $("<canvas>").get(0);
canvas.width = dWidth;
canvas.height = dHeight;
var ctx = canvas.getContext("2d");
var img = new Image();
img.src = this.data;
if (img.height > img.width) {
sHeight = img.width;
sWidth = img.width;
sx = 0;
sy = (img.height - img.width) / 2;
} else {
sHeight = img.height;
sWidth = img.height;
sx = (img.width - img.height) / 2;
sy = 0;
}
ctx.drawImage(img, sx, sy, sWidth, sHeight, 0, 0, dWidth, dHeight);
this.thumbnailData = canvas.toDataURL();
}
}
+20
Ver Arquivo
@@ -0,0 +1,20 @@
export let NOTIFICATION_DEFAULT = 'default';
export let NOTIFICATION_GRANTED = 'granted';
export let NOTIFICATION_DENIED = 'denied';
export let STATUS = ['offline', 'dnd', 'xa', 'away', 'chat', 'online'];
export let SOUNDS = {
MSG: 'incomingMessage.wav',
CALL: 'Rotary-Phone6.mp3',
NOTICE: 'Ping1.mp3'
};
export let REGEX = {
JID: new RegExp('\\b[^"&\'\\/:<>@\\s]+@[\\w-_.]+\\b', 'ig'),
URL: new RegExp(/(https?:\/\/|www\.)[^\s<>'"]+/gi)
};
export let NS = {
CARBONS: 'urn:xmpp:carbons:2',
FORWARD: 'urn:xmpp:forward:0'
};
export let HIDDEN = 'hidden';
export let SHOWN = 'shown';
+116
Ver Arquivo
@@ -0,0 +1,116 @@
import Account from './Account'
import Message from './Message'
import {PluginInterface} from './PluginInterface'
import Storage from './Storage';
import * as UI from './ui/web'
import JID from './JID'
import Roster from './ui/Roster'
import ChatWindowList from './ui/ChatWindowList'
import RoleAllocator from './RoleAllocator'
export default class Client implements ClientInterface {
private static storage;
private static accounts = {};
public static init() {
let roleAllocator = RoleAllocator.get();
let accountIds = Client.getStorage().getItem('accounts') || [];
accountIds.forEach(function(id) {
Client.accounts[id] = new Account(id);
roleAllocator.waitUntilMaster().then(function(){
return Client.accounts[id].connect();
}).then(function(){
}).catch(function(msg){
Client.accounts[id].remove();
console.warn(msg)
});
});
}
public static addConnectionPlugin(plugin:PluginInterface) {
}
public static addPreSendMessageHook(hook:(Message, Builder)=>void, position?:number) {
}
public static hasFocus() {
}
public static isExtraSmallDevice():boolean {
return $(window).width() < 500;
}
public static isDebugMode():boolean {
return Client.getStorage().getItem('debug') === true;
}
public static getStorage() {
if (!Client.storage) {
Client.storage = new Storage();
}
return Client.storage;
}
public static getAccout(jid:JID):Account;
public static getAccout(uid?:string):Account;
public static getAccout() {
let uid;
if (arguments[0] instanceof JID) {
uid = arguments[0].bare;
} else if (arguments[0]) {
uid = arguments[0];
} else {
uid = Object.keys(Client.accounts)[0];
}
return Client.accounts[uid];
}
public static createAccount(boshUrl: string, jid: string, sid: string, rid:string);
public static createAccount(boshUrl: string, jid: string, password: string);
public static createAccount() {
let account;
if (arguments.length === 4) {
account = new Account(arguments[0], arguments[1], arguments[2], arguments[3]);
} else if (arguments.length === 3) {
account = new Account(arguments[0], arguments[1], arguments[2]);
} else {
return Promise.reject(null);
}
return account.connect().then(function(){
Client.addAccount(account);
});
}
public static removeAccount(account:Account) {
delete Client.accounts[account.getUid()];
Client.save();
if (Object.keys(Client.accounts).length === 0) {
Roster.get().setNoConnection();
}
}
private static addAccount(account:Account) {
Client.accounts[account.getUid()] = account;
Client.save()
}
private static save() {
Client.getStorage().setItem('accounts', Object.keys(this.accounts));
}
}
+26
Ver Arquivo
@@ -0,0 +1,26 @@
import {JIDInterface} from './JIDInterface'
import {PluginInterface} from './PluginInterface'
export interface ClientInterface {
init();
addConnectionPlugin(plugin:PluginInterface);
addPreSendMessageHook(hook:(Message, Builder)=>void, position?:number);
hasFocus();
isExtraSmallDevice():boolean;
isDebugMode():boolean;
getStorage();
getAccout(jid:JIDInterface):Account;
getAccout(uid?:string):Account;
createAccount(boshUrl: string, jid: string, sid: string, rid:string);
createAccount(boshUrl: string, jid: string, password: string);
removeAccount(account:Account);
}
+228
Ver Arquivo
@@ -0,0 +1,228 @@
import Storage from './Storage'
import JID from './JID'
import Message from './Message'
import Notification from './Notification'
import Translation from './util/Translation'
import Account from './Account'
import ContactData from './ContactData'
import PersistentMap from './PersistentMap'
import IdentifiableInterface from './IdentifiableInterface'
import Log from './util/Log'
import {Presence} from './connection/AbstractConnection'
export default class Contact implements IdentifiableInterface {
private storage: Storage;
private readonly account:Account;
// @REVIEW Data to own object/type?
private data:PersistentMap;
private jid:JID;
constructor(account:Account, data: ContactData);
constructor(account:Account, id:string);
constructor() {
this.account = arguments[0];
this.storage = this.account.getStorage();
if (typeof arguments[1] === 'string') {
let id = arguments[1]; console.log('id', id)
this.data = new PersistentMap(this.storage, 'contact', id);
this.jid = new JID(this.data.get('jid'));
return;
}
let data = arguments[1] || {};
if (!data.jid) {
throw 'Jid missing';
} else if (typeof data.jid === 'string') {
this.jid = new JID(data.jid);
} else {
this.jid = data.jid;
data.jid = this.jid.full;
}
this.data = new PersistentMap(this.storage, 'contact', this.jid.bare);
data.rnd = Math.random() // force storage event
this.data.set(data);
}
public save() {
// if (this.storage.getItem('contact', this.getId())) {
// this.storage.updateItem('contact', this.getId(), this.data);
//
// return 'updated';
// }
//
// this.storage.setItem('contact', this.getId(), this.data);
//
// return 'created';
}
public openWindow = () => {
return this.account.openChatWindow(this);
}
public addResource(resource:string) {
let resources = this.data.get('resources') || [];
if (resource && resources.indexOf(resource) < 0) {
resources.push(resource);
this.data.set('resources', resources);
}
}
public removeResource(resource:string) {
let resources = this.data.get('resources') || [];
resources = $.grep(resources, function(r) {
return resource !== r;
});
this.data.set('resources', resources);
}
public setResource = (resource:string) => {
//this.addResource(resource);
console.log('setResource', this.jid.bare + '/' + resource)
this.jid = new JID(this.jid.bare + '/' + resource);
this.data.set('jid', this.jid.full);
}
public setPresence(resource:string, presence:Presence) {
Log.debug('set presence for ' + this.jid.bare + ' / ' + resource, presence);
let resources = this.data.get('resources') || {};
if (presence === Presence.offline) {
delete resources[resource];
} else if (resource) {
resources[resource] = presence;
}
if (this.getType() === 'groupchat') {
// group chat doesn't have a presence
return;
}
presence = this.getHighestPresence();
console.log('highest presence', presence);
if (this.data.get('presence') === Presence.offline && presence !== Presence.offline) {
// buddy has come online
// @TODO
// Notification.notify({
// title: this.getName(),
// message: Translation.t('has_come_online'),
// source: this.getId()
// });
}
this.data.set('presence', presence);
}
public sendMessage(message:Message) {
// message.bid = this.getId();
}
public getId():string {
return this.jid.bare;
}
public getJid():JID {
return this.jid;
}
public getFingerprint() {
return this.data.get('fingerprint');
}
public getMsgState() {
return this.data.get('msgstate');
}
public getPresence() {
return this.data.get('presence');
}
public getType() {
return this.data.get('type');
}
public getNumberOfUnreadMessages():number {
}
public getName():string {
return this.data.get('name') || this.jid.bare;
}
public getAvatar():Promise<{}> {
}
public getSubscription() {
return this.data.get('subscription');
}
public getCapabilitiesByRessource():Promise<{}> {
// @TODO
return Promise.resolve({});
}
public getVcard():Promise<{}> {
return this.account.getConnection().loadVcard(this.getJid());
}
public isEncrypted() {
}
public getStatus():string {
return this.data.get('status');
}
public setStatus(status:string) { console.trace(this.getId() + ', setStatus: ' + status)
return this.data.set('status', status);
}
public setTrust(trust:boolean) {
this.data.set('trust', trust);
}
public setName(name:string) {
let oldName = this.getName();
this.data.set('name', name);
if (oldName !== name) {
this.account.getConnection().setDisplayName(this.jid, name);
}
}
public setSubscription(subscription:string) {
this.data.set('subscription', subscription);
}
public registerHook(property:string, func:(newValue:any, oldValue:any)=>void) {
this.data.registerHook(property, func);
}
private getHighestPresence() {
let maxPresence = Presence.offline;
let resources = this.data.get('resources');
for (let resource in resources) {
if(resources[resource] < maxPresence) {
maxPresence = resources[resource];
}
}
return maxPresence;
}
}
+26
Ver Arquivo
@@ -0,0 +1,26 @@
//@TODO duplicate of AbstractConnection
enum Presence {
online,
chat,
away,
xa,
dnd,
offline
}
export default class ContactData {
public jid;
public name;
public presence:Presence = Presence.offline;
public status:string = '';
public subscription = 'none';
public msgstate = 0;
public trust:boolean = false;
public fingerprint:string = null;
public resources = {};
public type = 'chat'
constructor(data:any) {
$.extend(this, data);
}
}
+55
Ver Arquivo
@@ -0,0 +1,55 @@
import {MessageInterface} from './MessageInterface'
import {JIDInterface} from './JIDInterface'
import {Presence} from './connection/AbstractConnection'
export interface ContactInterface {
openWindow();
addResource(resource:string);
removeResource(resource:string);
setResource(resource:string);
setPresence(resource:string, presence:Presence);
sendMessage(message:MessageInterface);
getId():string;
getJid():JIDInterface;
getFingerprint();
getMsgState();
getPresence();
getType();
getNumberOfUnreadMessages():number;
getName():string;
getAvatar():Promise<{}>;
getSubscription();
getCapabilitiesByRessource():Promise<{}>;
getVcard():Promise<{}>;
isEncrypted();
getStatus():string;
setStatus(status:string);
setTrust(trust:boolean);
setName(name:string);
setSubscription(subscription:string);
registerHook(property:string, func:(newValue:any, oldValue:any)=>void);
}
Ver Arquivo
+127
Ver Arquivo
@@ -0,0 +1,127 @@
import Options from './Options'
const EMOTICONS = [
['O:-) O:)', 'innocent'],
['>:-( >:( &gt;:-( &gt;:(', 'angry'],
[':-) :)', 'slight_smile'],
[':-D :D', 'grin'],
[':-( :(', 'disappointed'],
[';-) ;)', 'wink'],
[':-P :P', 'stuck_out_tongue'],
['=-O', 'astonished'],
[':kiss: :-*', 'kissing_heart'],
['8-) :cool:', 'sunglasses'],
[':-X :X', 'zipper_mouth'],
[':yes:', 'thumbsup'],
[':no:', 'thumbsdown'],
[':beer:', 'beer'],
[':coffee:', 'coffee'],
[':devil:', 'smiling_imp'],
[':kiss: :kissing:', 'kissing'],
['@->-- @-&gt;--', 'rose'],
[':music:', 'musical_note'],
[':love:', 'heart_eyes'],
[':heart:', 'heart'],
[':brokenheart:', 'broken_heart'],
[':zzz:', 'zzz'],
[':wait:', 'hand_splayed']
]
import * as emojione from '../lib/emojione/lib/js/emojione.js';
const EMOTICON_LIST = {
'core': {
':klaus:': ['klaus'],
':jabber:': ['jabber'],
':xmpp:': ['xmpp'],
':jsxc:': ['jsxc'],
':owncloud:': ['owncloud'],
':nextcloud:': ['nextcloud']
},
'emojione': emojione.emojioneList
}
export default class Emoticons {
private static initialised = false;
private static shortRegex = new RegExp(emojione.regShortNames.source + '|(' + Object.keys(EMOTICON_LIST.core).join('|') + ')', 'gi');
public static getDefaultEmoticonList() {
let list = [];
EMOTICONS.forEach(emoticon => {
list.push(emoticon[0].split(' ')[0]);
});
return list;
}
public static toImage(text:string):string {
Emoticons.init();
text = Emoticons.standardToImage(text);
text = Emoticons.shortnameToImage(text);
return text;
}
private static init() {
if (Emoticons.initialised) {
return;
}
$.each(EMOTICONS, function(i, val) {
// escape characters
var reg = val[0].replace(/(\/|\||\*|\.|\+|\?|\^|\$|\(|\)|\[|\]|\{|\})/g, '\\$1');
reg = '(' + reg.split(' ').join('|') + ')';
EMOTICONS[i][2] = new RegExp(reg, 'g');
});
Emoticons.initialised = true;
}
private static standardToImage(text:string):string {
// replace emoticons from XEP-0038 and pidgin with shortnames
$.each(EMOTICONS, function(i, val) {
text = text.replace(val[2], ':' + val[1] + ':');
});
return text;
}
private static shortnameToImage(text:string):string {
text = text.replace(this.shortRegex, Emoticons.replaceShortnameWithImage);
var wrapper = $('<div>' + text + '</div>');
if (wrapper.find('.jsxc_emoticon').length === 1 && wrapper.text().replace(/ /, '').length === 0 && wrapper.find('*').length === 1) {
wrapper.find('.jsxc_emoticon').addClass('jsxc_large');
text = wrapper.html();
}
return text;
}
private static replaceShortnameWithImage = (shortname) => {
if (typeof shortname === 'undefined' || shortname === '' || (!(shortname in EMOTICON_LIST.emojione) && !(shortname in EMOTICON_LIST.core))) {
return shortname;
}
var src, filename;
if (EMOTICON_LIST.core[shortname]) {
filename = EMOTICON_LIST.core[shortname][EMOTICON_LIST.core[shortname].length - 1].replace(/^:([^:]+):$/, '$1');
src = Options.get('root') + '/img/emotions/' + filename + '.svg';
} else if (EMOTICON_LIST.emojione[shortname]) {
filename = EMOTICON_LIST.emojione[shortname].fname;
src = Options.get('root') + '/lib/emojione/assets/svg/' + filename + '.svg';
}
var div = $('<div>');
div.addClass('jsxc-emoticon');
div.css('background-image', 'url(' + src + ')');
div.attr('title', shortname);
return div.prop('outerHTML');
}
}
+6
Ver Arquivo
@@ -0,0 +1,6 @@
interface Identifiable {
getId():string
}
export default Identifiable;
+65
Ver Arquivo
@@ -0,0 +1,65 @@
import {JIDInterface} from './JIDInterface'
export default class JID implements JIDInterface {
public readonly full:string;
public readonly bare:string;
public readonly node:string;
public readonly domain:string;
public readonly resource:string;
constructor(full:string) {
let matches = /([^@]+)@([^/]+)(?:\/(.+))?/.exec(full);
this.node = this.unescapeNode(matches[1].toLowerCase());
this.domain = matches[2].toLowerCase();
this.resource = matches[3];
this.bare = this.node + '@' + this.domain;
this.full = this.bare + ((this.resource) ? '/' + this.resource : '');
}
public toString():string {
return this.full;
}
public toEscapedString():string {
let bare = this.escapeNode(this.node) + '@' + this.domain;
return bare + ((this.resource) ? '/' + this.resource : '');
}
public isBare():boolean {
return this.full === this.bare;
}
private escapeNode(node:string) {
return node.replace(/^\s+|\s+$/g, '')
.replace(/\\/g, "\\5c")
.replace(/ /g, "\\20")
.replace(/\"/g, "\\22")
.replace(/\&/g, "\\26")
.replace(/\'/g, "\\27")
.replace(/\//g, "\\2f")
.replace(/:/g, "\\3a")
.replace(/</g, "\\3c")
.replace(/>/g, "\\3e")
.replace(/@/g, "\\40");
}
private unescapeNode(node:string) {
return node.replace(/\\20/g, " ")
.replace(/\\22/g, '"')
.replace(/\\26/g, "&")
.replace(/\\27/g, "'")
.replace(/\\2f/g, "/")
.replace(/\\3a/g, ":")
.replace(/\\3c/g, "<")
.replace(/\\3e/g, ">")
.replace(/\\40/g, "@")
.replace(/\\5c/g, "\\");
}
}
+16
Ver Arquivo
@@ -0,0 +1,16 @@
export interface JIDInterface {
readonly full:string;
readonly bare:string;
readonly node:string;
readonly domain:string;
readonly resource:string;
toString():string;
isBare():boolean;
}
+244
Ver Arquivo
@@ -0,0 +1,244 @@
import Storage from './Storage';
import Log from './util/Log';
import Options from './Options';
import Attachment from './Attachment';
import StorageSingleton from './StorageSingleton';
import JID from './JID'
import * as CONST from './CONST'
import Emoticons from './Emoticons'
import Translation from './util/Translation'
import Identifiable from './IdentifiableInterface'
import Client from './Client'
import Utils from './util/Utils'
import {MessageInterface, DIRECTION, MSGTYPE} from './MessageInterface'
const MSGPOSTFIX = ':msg';
const ATREGEX = new RegExp('(xmpp:)?(' + CONST.REGEX.JID.source + ')(\\?[^\\s]+\\b)?', 'i');
interface MessagePayload {
peer:JID,
direction:DIRECTION,
plaintextMessage?:string,
htmlMessage?:string,
errorMessage?:string,
attachment?:Attachment,
received?:boolean,
encrypted?:boolean,
forwarded?:boolean,
stamp?:number,
type?:MSGTYPE,
}
export default class Message implements Identifiable, MessageInterface {
private uid:string;
private payload:MessagePayload = {
received: false,
encrypted: null,
forwarded: false,
stamp: new Date().getTime(),
type: MSGTYPE.CHAT
} as any;
static readonly DIRECTION = DIRECTION;
static readonly MSGTYPE = MSGTYPE;
private storage:Storage;
constructor(uid:string);
constructor(data:MessagePayload);
constructor() {
this.storage = Client.getStorage();
if (typeof arguments[0] === 'string' && arguments[0].length > 0 && arguments.length === 1) {
this.uid = arguments[0];
this.load(this.uid);
} else if (typeof arguments[0] === 'object' && arguments[0] !== null) { console.log('arg', arguments[0])
$.extend(this.payload, arguments[0]);
}
if (!this.uid) {
this.uid = new Date().getTime() + MSGPOSTFIX;
}
}
public getId() {
return this.uid;
}
public save() {
let attachment = this.getAttachment();
if (attachment) {
if (this.getDirection() === DIRECTION.OUT) {
// save storage
attachment.clearData();
}
let saved = attachment.save();
if (!saved && this.getDirection() === DIRECTION.IN) {
//@TODO inform user
}
}
let payloadCopy = $.extend({}, this.payload); //Object.assign
if (payloadCopy.attachment) {
payloadCopy.attachment = payloadCopy.attachment.getId();
}
this.storage.setItem('msg', this.uid, {
payload: payloadCopy
});
return this;
}
public delete() {
var data = this.storage.getItem('msg', this.uid);
if (data) {
this.storage.removeItem('msg', this.uid);
}
}
public getCssId() {
return this.uid.replace(/:/g, '-');
}
public getDOM() {
return $('#' + this.getCssId());
}
public getStamp() {
return this.payload.stamp;
}
public getDirection():DIRECTION {
return this.payload.direction;
}
public getDirectionString():string {
return DIRECTION[this.payload.direction].toLowerCase();
}
public getAttachment():Attachment {
return this.payload.attachment;
}
public getPeer():JID {
return this.payload.peer;
}
public getType():MSGTYPE {
return this.payload.type;
}
public getTypeString():string {
return MSGTYPE[this.payload.type].toLowerCase();
}
public getHtmlMessage():string {
return this.payload.htmlMessage;
}
public getPlaintextMessage():string {
return this.payload.plaintextMessage;
}
public received() {
this.payload.received = true;
this.save();
this.getDOM().addClass('jsxc_received');
}
public isReceived():boolean {
return this.payload.received;
}
public isForwarded():boolean {
return this.payload.forwarded;
}
public isEncrypted():boolean {
return this.payload.encrypted;
}
public hasAttachment():boolean {
return !!this.payload.attachment;
}
public setUnread() {
}
public getProcessedBody():string {
let body = this.payload.plaintextMessage;
body = this.convertUrlToLink(body);
body = this.convertEmailToLink(body);
body = Emoticons.toImage(body);
body = this.replaceLineBreaks(body);
// hide unprocessed otr messages
if (body.match(/^\?OTR([:,|?]|[?v0-9x]+)/)) {
body = '<i title="' + body + '">' + Translation.t('Unreadable_OTR_message') + '</i>';
}
return body;
}
public getErrorMessage():string {
return this.payload.errorMessage;
}
private load(uid:string):void {
var data = this.storage.getItem('msg', uid);
window._storage = this.storage;
if (!data) {
Log.debug('Could not load message with uid ' + uid);
throw new Error('Could not load message with uid ' + uid)
}
$.extend(this.payload, data.payload);
if (data.attachment) {
this.payload.attachment = new Attachment(data.attachment);
}
}
private replaceLineBreaks(text) {
return text.replace(/(\r\n|\r|\n)/g, '<br />');
}
private convertUrlToLink(text) {
return text.replace(CONST.REGEX.URL, function(url) {
let href = (url.match(/^https?:\/\//i)) ? url : 'http://' + url;
return '<a href="' + href + '" target="_blank">' + url + '</a>';
});
}
private convertEmailToLink(text) {
return text.replace(ATREGEX, function(undefined, protocol, jid, action) {
if (protocol === 'xmpp:') {
if (typeof action === 'string') {
jid += action;
}
return '<a href="xmpp:' + jid + '">xmpp:' + jid + '</a>';
}
return '<a href="mailto:' + jid + '" target="_blank">mailto:' + jid + '</a>';
});
}
}
+55
Ver Arquivo
@@ -0,0 +1,55 @@
import {JIDInterface} from './JIDInterface'
export enum DIRECTION {
IN, OUT, SYS
};
export enum MSGTYPE {
CHAT, GROUPCHAT
};
export interface MessageInterface {
getId();
save();
delete();
getCssId();
getDOM();
getStamp();
getDirection():DIRECTION;
getDirectionString():string;
getAttachment():Attachment;
getPeer():JIDInterface;
getType():MSGTYPE;
getTypeString():string;
getHtmlMessage():string;
getPlaintextMessage():string;
received();
isReceived():boolean;
isForwarded():boolean;
isEncrypted():boolean;
hasAttachment():boolean;
setUnread();
getProcessedBody():string;
getErrorMessage():string;
}
+52
Ver Arquivo
@@ -0,0 +1,52 @@
import Storable from './StorableAbstract';
import Identifiable from './IdentifiableInterface'
import Storage from './Storage'
const SEP = ':';
export default class ModelManager <Element extends Storable> {
private id:string;
private elementIds;
constructor(private owner:Identifiable, private ElementClass:any, private storage:Storage) { //@REVIEW any -> Element?
this.id = owner.getId() + SEP + ElementClass.constructor.name;
this.elementIds = this.storage.getItem(this.id);
}
public get(id:string):Element {
let data = this.storage.getItem(this.id + SEP + id);
return new this.ElementClass(data);
}
public getAll():Element[] {
let elements:Element[] = [];
for(let elementId in this.elementIds) {
elements.push(this.get(elementId));
}
return elements;
}
public add(element:Element) {
this.elementIds.push(element.getId());
this.save();
}
public remove(element:Element) {
this.elementIds = $.grep(this.elementIds, function(e) {
return e !== element.getId();
})
this.save();
}
private save() {
this.storage.setItem(this.id, this.elementIds);
}
}
+68
Ver Arquivo
@@ -0,0 +1,68 @@
import IdentifiableInterface from './IdentifiableInterface'
import Storage from './Storage'
import ContactDialog from './ui/dialogs/contact'
export const enum TYPE {
normal, announcement, contact
}
export const enum FUNCTION {
contactRequest
}
let functions = {};
functions[FUNCTION.contactRequest] = ContactDialog;
export interface NoticeData {
title:string;
description:string;
fnName: FUNCTION;
fnParams?:Array<string>;
type?:TYPE;
}
export class Notice implements IdentifiableInterface {
private storage:Storage;
private data:NoticeData;
constructor(storage:Storage, data:NoticeData);
constructor(storage:Storage, id:string);
constructor() {
this.storage = arguments[0];
if (arguments.length === 2 && typeof arguments[1] === 'string') {
this.data = this.storage.getItem(arguments[1]);
} else {
this.data = arguments[1];
this.data.fnParams = this.data.fnParams || [];
this.data.type = this.data.type || TYPE.normal;
this.storage.setItem(this.getId(), this.data);
}
}
public getId():string {
return this.data.fnName + '|' + this.data.fnParams.toString();
}
public getTitle():string {
return this.data.title;
}
public getDescription():string {
return this.data.description;
}
public getFnParams():Array<string> {
return this.data.fnParams;
}
public getType():TYPE {
return this.data.type;
}
public callFunction() {
return functions[this.data.fnName].apply(this, this.data.fnParams);
}
}
+178
Ver Arquivo
@@ -0,0 +1,178 @@
import Options from './Options'
import Contact from './Contact'
import Translation from './util/Translation'
import Client from './Client'
import * as CONST from './CONST'
interface NotificationSettings {
title:string,
message:string,
duration?:number,
force?:boolean,
soundFile?:string,
loop?:boolean,
source?:string,
icon?:string
};
export default class Notification {
private static inited = false;
private static popupTimeout;
private static popupDelay = 1000;
private static audioObject;
public static init() {
if(Notification.inited) {
return;
}
$(document).on('postmessagein.jsxc', function(event, bid, msg) {
msg = (msg && msg.match(/^\?OTR/)) ? $.t('Encrypted_message') : msg;
var data = jsxc.storage.getUserItem('buddy', bid);
Notification.notify({
title: Translation.t('New_message_from'),
message: msg,
soundFile: CONST.SOUNDS.MSG,
source: bid
});
});
$(document).on('callincoming.jingle', function() {
Notification.playSound(CONST.SOUNDS.CALL, true, true);
});
$(document).on('accept.call.jsxc reject.call.jsxc', function() {
Notification.stopSound();
});
if (!Notice.has('gui.showRequestNotification')) {
Notice.add({
msg: Translation.t('Notifications') + '?',
description: Translation.t('Should_we_notify_you_')
}, 'gui.showRequestNotification');
}
}
public static muteSound(external?) {
$('#jsxc-menu .jsxc-muteNotification').text(Translation.t('Unmute'));
if (external !== true) {
Options.set('muteNotification', true);
}
}
public static unmuteSound(external?) {
$('#jsxc-menu .jsxc-muteNotification').text(Translation.t('Mute'));
if (external !== true) {
Options.set('muteNotification', false);
}
}
public static notify(settings:NotificationSettings) {
if (!Options.get('notification') || !Notification.hasPermission()) {
return; // notifications disabled
}
if (Client.hasFocus() && !settings.force) {
return; // Tab is visible
}
settings.icon = settings.icon || Options.get('root') + '/img/XMPP_logo.png';
if (typeof settings.source === 'string') {
let contact = new Contact(settings.source);
let avatar = contact.getAvatar();
if (typeof avatar === 'string' && avatar !== '0') {
settings.icon = avatar;
}
}
settings.duration = settings.duration || Options.get('notification').duration;
settings.title = Translation.t(settings.title);
settings.message = Translation.t(settings.message);
Notification.popupTimeout = setTimeout(function() {
Notification.showPopup(settings);
}, Notification.popupDelay);
}
private static showPopup(settings:NotificationSettings) {
if (typeof settings.soundFile === 'string') {
Notification.playSound(settings.soundFile, settings.loop, settings.force);
}
var popup = new window.Notification(settings.title, {
body: settings.message,
icon: settings.icon
});
if (settings.duration > 0) {
setTimeout(function() {
popup.close();
}, settings.duration);
}
}
private static hasSupport() {
return !!window.Notification;
}
private static requestPermission() {
window.Notification.requestPermission(function(status) {
if (window.Notification.permission !== status) {
window.Notification.permission = status;
}
if (Notification.hasPermission()) {
$(document).trigger('notificationready.jsxc');
} else {
$(document).trigger('notificationfailure.jsxc');
}
});
}
private static hasPermission() {
return window.Notification.permission === CONST.NOTIFICATION_GRANTED;
}
private static playSound(soundFile:string, loop?:boolean, force?:boolean) {
if (!jsxc.master) {
// only master plays sound
return;
}
if (Options.get('muteNotification') || jsxc.storage.getUserItem('presence') === 'dnd') {
// sound mute or own presence is dnd
return;
}
if (Client.hasFocus() && !force) {
// tab is visible
return;
}
// stop current audio file
Notification.stopSound();
var audio = new Audio(Options.get('root') + '/sound/' + soundFile);
audio.loop = loop || false;
audio.play();
Notification.audioObject = audio;
}
private static stopSound() {
var audio = Notification.audioObject;
if (typeof audio !== 'undefined' && audio !== null) {
audio.pause();
Notification.audioObject = null;
}
}
}
+64 -53
Ver Arquivo
@@ -1,30 +1,39 @@
/**
* Set some options for the chat.
*
* @namespace jsxc.options
*/
jsxc.options = {
export default class Options {
public static get(key) {
if (jsxc && jsxc.bid) {
var local = jsxc.storage.getUserItem('options') || {};
return (typeof local[key] !== 'undefined') ? local[key] : Options[key];
}
return (typeof Options[key] !== 'undefined') ? Options[key] : {};
};
public static set(key, value) {
jsxc.storage.updateItem('options', key, value, true);
};
/** name of container application (e.g. owncloud or SOGo) */
app_name: 'web applications',
private static app_name = 'web applications';
/** Timeout for the keepalive signal */
timeout: 3000,
private static timeout = 3000;
/** Timeout for the keepalive signal if the master is busy */
busyTimeout: 15000,
private static busyTimeout = 15000;
/** OTR options */
otr: {
private static otr = {
enable: true,
ERROR_START_AKE: false,
debug: false,
SEND_WHITESPACE_TAG: false,
WHITESPACE_START_AKE: true
},
};
/** xmpp options */
xmpp: {
private static xmpp = {
/** BOSH url */
url: null,
@@ -48,16 +57,16 @@ jsxc.options = {
/** @deprecated since v2.1.0. Use now loginForm.enable. */
onlogin: null
},
};
/** default xmpp priorities */
priority: {
private static priority = {
online: 0,
chat: 0,
away: 0,
xa: 0,
dnd: 0
},
};
/**
* This function is called if a login form was found, but before any
@@ -66,10 +75,10 @@ jsxc.options = {
* @memberOf jsxc.options
* @function
*/
formFound: null,
private static formFound = null;
/** If all 3 properties are set and enable is true, the login form is used */
loginForm: {
private static loginForm = {
/** False, disables login through login form */
enable: true,
@@ -128,45 +137,45 @@ jsxc.options = {
* roster state will be used.
*/
startMinimized: false
},
};
/** jquery object from logout element */
logoutElement: null,
private static logoutElement = null;
/** How many messages should be logged? */
numberOfMsg: 10,
private static numberOfMsg = 10;
/** Default language */
defaultLang: 'en',
private static defaultLang = 'en';
/** auto language detection */
autoLang: true,
private static autoLang = true;
/** Place for roster */
rosterAppend: 'body',
private static rosterAppend = 'body';
/** Should we use the HTML5 notification API? */
notification: true,
private static notification = true;
/** duration for notification */
popupDuration: 6000,
private static popupDuration = 6000;
/** Absolute path root of JSXC installation */
root: '',
private static root = '/jsxc4.0/';
/**
* This function decides wether the roster will be displayed or not if no
* connection is found.
*/
displayRosterMinimized: function() {
private static displayRosterMinimized = function() {
return false;
},
};
/** Set to true if you want to hide offline buddies. */
hideOffline: false,
private static hideOffline = false;
/** Mute notification sound? */
muteNotification: false,
private static muteNotification = false;
/**
* If no avatar is found, this function is called.
@@ -174,9 +183,9 @@ jsxc.options = {
* @param jid Jid of that user.
* @this {jQuery} Elements to update with probable .jsxc_avatar elements
*/
defaultAvatar: function(jid) {
private static defaultAvatar = function(jid) {
jsxc.gui.avatarPlaceholder($(this).find('.jsxc_avatar'), jid);
},
};
/**
* This callback processes all settings.
@@ -193,7 +202,7 @@ jsxc.options = {
* @param password {string} password
* @param cb {loadSettingsCallback} Callback that handles the result
*/
loadSettings: null,
private static loadSettings = null;
/**
* Call this function to save user settings permanent.
@@ -202,14 +211,14 @@ jsxc.options = {
* @param data Holds all data as key/value
* @param cb Called with true on success, false otherwise
*/
saveSettinsPermanent: function(data, cb) {
private static saveSettinsPermanent = function(data, cb) {
cb(true);
},
};
carbons: {
private static carbons = {
/** Enable carbon copies? */
enable: true
},
};
/**
* Processes user list.
@@ -226,10 +235,10 @@ jsxc.options = {
* @param {string} search Search token (start with)
* @param {getUsers-cb} cb Called with list of users
*/
getUsers: null,
private static getUsers = null;
/** Options for info in favicon */
favicon: {
private static favicon = {
enable: true,
/** Favicon info background color */
@@ -237,13 +246,13 @@ jsxc.options = {
/** Favicon info text color */
textColor: '#fff'
},
};
/** @deprecated since v2.1.0. Use now RTCPeerConfig.url. */
turnCredentialsPath: null,
private static turnCredentialsPath = null;
/** RTCPeerConfiguration used for audio/video calls. */
RTCPeerConfig: {
private static RTCPeerConfig = {
/** Time-to-live for config from url */
ttl: 3600,
@@ -257,12 +266,12 @@ jsxc.options = {
iceServers: [{
urls: 'stun:stun.stunprotocol.org'
}]
},
};
/** Link to an online user manual */
onlineHelp: 'http://www.jsxc.org/manual.html',
private static onlineHelp = 'http://www.jsxc.org/manual.html';
viewport: {
private static viewport = {
getSize: function() {
var w = $(window).width() - $('#jsxc_windowListSB').width();
var h = $(window).height();
@@ -276,23 +285,23 @@ jsxc.options = {
height: h
};
}
},
};
/** Maximal storage size for attachments received via data channels (webrtc). */
maxStorableSize: 1000000,
private static maxStorableSize = 1000000;
/** Options for file transfer. */
fileTransfer: {
private static fileTransfer = {
httpUpload: {
enable: true
},
// @TODO add option to enable/disable data channels
},
};
/** Default option for chat state notifications */
chatState: {
private static chatState = {
enable: true
},
};
/**
* Download urls to screen media extensions.
@@ -300,8 +309,10 @@ jsxc.options = {
* @type {Object}
* @see example extensions {@link https://github.com/otalk/getScreenMedia}
*/
screenMediaExtension: {
private static screenMediaExtension = {
firefox: '',
chrome: ''
}
};
private static storage;
};
+92
Ver Arquivo
@@ -0,0 +1,92 @@
import Identifiable from './IdentifiableInterface'
import Storage from './Storage'
export default class PersistentMap {
private map = {};
private key:string;
private initialized = false;
constructor(private storage:Storage, ...identifier:string[]) {
this.key = storage.generateKey.apply(storage, identifier);
this.map = this.storage.getItem(this.key) || {};
this.storage.registerHook(this.key, (newValue) => {
this.map = newValue;
});
}
public get(id:string) {
return this.map[id];
}
public set(id:string, value:any);
public set(value:any);
public set() {
if (typeof arguments[0] === 'string'){
let id = arguments[0];
let value = arguments[1];
this.map[id] = value;
} else if(typeof arguments[0] === 'object' && arguments[0] !== null) {
$.extend(this.map, arguments[0]);
}
this.save();
}
public empty() {
this.map = {};
this.save();
}
public remove(id:Identifiable);
public remove(id:string);
public remove() {
let id;
if (typeof arguments[0] === 'string') {
id = arguments[0];
} else if(typeof arguments[0].getId === 'function') {
id = arguments[0].getId();
} else {
//@TODO error
return;
}
delete this.map[id];
this.save();
}
public registerHook(id:string, func: (newValue: any, oldValue: any, key: string) => void);
public registerHook(func: (newValue: any, oldValue: any, key: string) => void);
public registerHook() {
if (typeof arguments[0] === 'string' && typeof arguments[1] === 'function') {
let id = arguments[0];
let func = arguments[1];
this.storage.registerHook(this.key, function(newData, oldData) {
if (newData && !oldData) {
func(newData[id]);
} else if (newData[id] !== oldData[id]) {
func(newData[id], oldData[id]);
}
});
} else {
let func = arguments[0];
this.storage.registerHook(this.key, func);
}
}
private save() {
this.initialized = true;
this.storage.setItem(this.key, this.map);
}
}
+6
Ver Arquivo
@@ -0,0 +1,6 @@
import Connection from './connection/Connection'
export interface PluginInterface {
constructor:(Connection);
};
+145
Ver Arquivo
@@ -0,0 +1,145 @@
import Storage from './Storage'
import Client from './Client'
import Log from './util/Log'
export default class RoleAllocator {
private storage:Storage;
private master:boolean = null;
private keepAliveInterval;
private resolveTimeout;
private observationTimeout;
private resolves = [];
// private reject;
private slaveValue;
private masterValue;
private static instance;
private constructor() {
this.storage = Client.getStorage();
this.storage.registerHook('master', (value) => {
if (this.masterValue === value) {
return;
}
if (this.master === null) {
Log.debug('i am slave');
this.master = false;
clearTimeout(this.resolveTimeout);
$('body').addClass('jsxc-slave').removeClass('jsxc-master');
// if (typeof this.reject === 'function') {
// //this.reject();
// this.reject = null;
// }
}
if (this.master !== true) {
this.masterIsStillAlive();
} else {
Log.error('Something went wrong. We have another master.');
}
});
this.storage.registerHook('slave', (value) => {
if (this.slaveValue === value) {
return;
}
if (this.master === true) {
this.stillAlive();
}
});
}
public static get() {
if (!RoleAllocator.instance) {
RoleAllocator.instance = new RoleAllocator();
}
return RoleAllocator.instance;
}
public isMaster() {
return this.master;
}
public waitUntilMaster() {
return new Promise((resolve) => {
if (this.master === true || typeof this.storage.getItem('master') === 'undefined') {
resolve();
} else {
this.resolves.push(resolve);
if (this.master !== false){
this.queryMaster();
}
}
});
}
private queryMaster() {
Log.debug('query master');
let self = this;
this.resolveTimeout = setTimeout(() => {
this.master = true;
Log.debug('no one responded, i am master')
$('body').addClass('jsxc-master').removeClass('jsxc-slave');
this.startKeepAliveSignal();
this.resolveAll();
}, 1000);
this.storage.setItem('slave', this.slaveValue = Math.random());
}
private masterIsStillAlive() {
clearTimeout(this.observationTimeout);
let randomTime = (Math.random() * 1000) % 500;
this.observationTimeout = setTimeout(this.masterProbablyDied, 2000 + randomTime)
}
private masterProbablyDied = () => {
this.master = null;
this.queryMaster();
}
private startKeepAliveSignal() {
this.stillAlive();
this.keepAliveInterval = window.setInterval(this.stillAlive, 1000);
}
private stopKeepAliveSignal() {
window.clearInterval(this.keepAliveInterval);
}
private resolveAll() {
for(let resolveIndex in this.resolves) {
this.resolves[resolveIndex]();
delete this.resolves[resolveIndex];
}
}
private stillAlive = () => {
this.storage.setItem('master', this.masterValue = Math.random());
}
}
+153
Ver Arquivo
@@ -0,0 +1,153 @@
import Identifiable from './IdentifiableInterface'
import Storage from './Storage'
import Log from './util/Log'
export default class SortedPersistentMap {
private map = {};
private list;
private key: string;
private initialized = false;
private pushHook;
private removeHook;
constructor(private storage: Storage, ...identifier: string[]) {
this.key = storage.generateKey.apply(storage, identifier);
this.list = this.storage.getItem(this.key) || [];
this.storage.registerHook(this.key, this.onStorage);
}
public init() {
if (this.initialized) {
return;
}
if (typeof this.pushHook !== 'function') {
Log.error('push hook required');
return;
}
this.list.forEach(id => {
try {
this.map[id] = this.pushHook(id);
} catch(err) {
Log.error('Push hook threw the following error', err);
}
});
this.initialized = true;
}
public get(id: string) {
return this.map[id];
}
public push(element: Identifiable) {
let id = element.getId();
if (typeof this.map[id] !== 'undefined') {
//@TODO error reporting
return;
}
this.map[id] = element;
this.list.push(id);
this.save();
}
public empty(callback) {
this.list.forEach(id => {
callback(id, this.map[id]);
});
this.map = {};
this.list = [];
this.save();
}
public remove(id: Identifiable);
public remove(id: string);
public remove() {
let id;
if (typeof arguments[0] === 'string') {
id = arguments[0];
} else if (typeof arguments[0].getId === 'function') {
id = arguments[0].getId();
} else {
//@TODO error
return;
}
this.list = $.grep(this.list, function(i) {
return id !== i;
});
delete this.map[id];
this.save();
}
public registerHook(func: (newValue: any, oldValue: any, key: string) => void) {
this.storage.registerHook(this.key, func);
}
public setPushHook(func: (newValue: any, oldValue: any, key: string) => void) {
this.pushHook = func;
}
public setRemoveHook(func: (newValue: any, oldValue: any, key: string) => void) {
this.removeHook = func;
}
private onStorage = (newValue: any, oldValue: any, key: string) => {
oldValue = oldValue || [];
if (newValue.length === oldValue.length) {
return;
}
let pushDiff: string[] = newValue.filter(id => oldValue.indexOf(id) < 0);
for (let value of pushDiff) {
// call push hook
if (typeof this.pushHook === 'function') {
let result = this.pushHook(value);
if (typeof this.get(value) === 'undefined') {
this.map[value] = result;
}
}
}
let removeDiff: string[] = oldValue.filter(id => newValue.indexOf(id) < 0);
for (let value of removeDiff) {
// call remove hook
if (typeof this.removeHook === 'function') {
this.removeHook(value, this.map[value]);
}
delete this.map[value];
}
this.list = newValue;
}
private save() {
this.initialized = true;
this.storage.setItem(this.key, this.list);
}
}
+37
Ver Arquivo
@@ -0,0 +1,37 @@
import Log from './util/Log'
export default class StateMachine{
public static STATE = {
INITIATING: 0,
PREVCONFOUND: 1,
SUSPEND: 2,
TRYTOINTERCEPT: 3,
INTERCEPTED: 4,
ESTABLISHING: 5,
READY: 6
};
public static UISTATE = {
INITIATING: 0,
READY: 1
}
private static currentState;
private static currentUIState;
public static changeState(state:number) {
StateMachine.currentState = state;
Log.debug('State changed to ' + Object.keys(StateMachine.STATE)[state]);
$(document).trigger('stateChange.jsxc', state);
}
public static changeUIState(state:number) {
StateMachine.currentUIState = state;
Log.debug('UI State changed to ' + Object.keys(StateMachine.UISTATE)[state]);
$(document).trigger('stateUIChange.jsxc', state);
}
}
+15
Ver Arquivo
@@ -0,0 +1,15 @@
import Identifiable from './IdentifiableInterface'
abstract class Storable implements Identifiable {
constructor(data:any) {
$.extend(this, data);
}
public abstract getId():string;
public abstract save();
public abstract remove();
}
export default Storable;
+273
Ver Arquivo
@@ -0,0 +1,273 @@
import Log from './util/Log'
const PREFIX = 'jsxc2';
const SEP = ':';
const IGNORE_KEY = ['rid'];
const BACKEND = localStorage;
export default class Storage {
static storageNotConform: boolean = false;
static tested: boolean = false;
private hooks: any = {};
static toSNC: number;
constructor(private name: string = null) {
if (!Storage.tested) {
Storage.tested = true;
this.testStorage();
}
window.addEventListener('storage', this.onStorageEvent, false);
}
public generateKey(...args:string[]):string {
let key = '';
args.forEach(function(arg) {
if (key !== '') {
key += SEP;
}
key += arg;
})
return key;
}
private testStorage() {
let randomNumber = Math.round(Math.random() * 1000000000) + '';
let key = this.getPrefix() + randomNumber;
let timeout;
let listenerFunction = function(ev) {
if (ev.newValue === randomNumber) {
clearTimeout(timeout);
cleanup();
Storage.storageNotConform = true;
}
};
let cleanup = function() {
window.removeEventListener('storage', listenerFunction, false);
BACKEND.removeItem(key)
}
window.addEventListener('storage', listenerFunction, false);
timeout = setTimeout(function() {
cleanup();
}, 20);
BACKEND.setItem(key, randomNumber);
}
public getPrefix(): string {
let prefix = PREFIX + SEP;
if (this.name) {
prefix += this.name + SEP;
}
return prefix;
}
public getBackend() {
return BACKEND;
}
public setItem(type: string, key: string, value: any): void;
public setItem(key: string, value: any): void
public setItem(): void {
let key, value;
if (arguments.length === 2) {
key = arguments[0];
value = arguments[1];
} else if (arguments.length === 3) {
key = arguments[0] + SEP + arguments[1];
value = arguments[2];
}
//@REVIEW why do we just stringify objects?
if (typeof (value) === 'object') {
// exclude jquery objects, because otherwise safari will fail
try {
value = JSON.stringify(value, function(key, val) {
if (!(val instanceof jQuery)) {
return val;
}
});
} catch (err) {
console.warn('Could not stringify value', err);
}
}
let oldValue = BACKEND.getItem(this.getPrefix() + key);
BACKEND.setItem(this.getPrefix() + key, value);
if (!Storage.storageNotConform && oldValue !== value) {
this.onStorageEvent({
key: this.getPrefix() + key,
oldValue: oldValue,
newValue: value
});
}
}
public getItem(type: string, key: string): any;
public getItem(key: string): any;
public getItem(): any {
let key;
if (arguments.length === 1) {
key = arguments[0];
} else if (arguments.length === 2) {
key = arguments[0] + SEP + arguments[1];
}
key = this.getPrefix() + key;
var value = BACKEND.getItem(key);
return this.parseValue(value);
}
public removeItem(type, key): void;
public removeItem(key): void;
public removeItem(): void {
let key;
if (arguments.length === 1) {
key = arguments[0];
} else if (arguments.length === 2) {
key = arguments[0] + SEP + arguments[1];
}
BACKEND.removeItem(this.getPrefix() + key);
}
public updateItem(type, key, variable, value): void;
public updateItem(key, variable, value): void;
public updateItem(): void {
let key, variable, value;
if (arguments.length === 4 || (arguments.length === 3 && typeof variable === 'object')) {
key = arguments[0] + SEP + arguments[1];
variable = arguments[2];
value = arguments[3];
} else {
key = arguments[0];
variable = arguments[1];
value = arguments[2];
}
var data = this.getItem(key) || {};
if (typeof (variable) === 'object') {
$.each(variable, function(key, val) {
if (typeof (data[key]) === 'undefined') {
Log.debug('Variable ' + key + ' doesn\'t exist in ' + variable + '. It was created.');
}
data[key] = val;
});
} else {
if (typeof data[variable] === 'undefined') {
Log.debug('Variable ' + variable + ' doesn\'t exist. It was created.');
}
data[variable] = value;
}
this.setItem(key, data);
}
public increment(key: string): void {
let value = Number(this.getItem(key));
this.setItem(key, value + 1);
}
public removeElement(type, key, name): void;
public removeElement(key, name): void;
public removeElement(): void {
let key, name;
if (arguments.length === 2) {
key = arguments[0];
name = arguments[1];
} else if (arguments.length === 3) {
key = arguments[0] + SEP + arguments[1];
name = arguments[2];
}
var item = this.getItem(key);
if ($.isArray(item)) {
item = $.grep(item, function(e) {
return e !== name;
});
} else if (typeof (item) === 'object' && item !== null) {
delete item[name];
}
this.setItem(key, item);
}
public registerHook(eventName: string, func: (newValue: any, oldValue: any, key: string) => void) {
if (!this.hooks[eventName]) {
this.hooks[eventName] = [];
}
this.hooks[eventName].push(func);
}
public removeHook(eventName:string, func: (newValue: any, oldValue: any, key: string) => void) {
let eventNameList = this.hooks[eventName] || [];
if (eventNameList.indexOf(func) > -1) {
eventNameList = $.grep(eventNameList, function(i) {
return func !== i;
});
}
this.hooks[eventName] = eventNameList;
}
private onStorageEvent = (ev: any) => {
let hooks = this.hooks;
let key = ev.key.replace(new RegExp('^' + this.getPrefix()), '');
let oldValue = this.parseValue(ev.oldValue);
let newValue = this.parseValue(ev.newValue);
if (IGNORE_KEY.indexOf(key) > -1) {
return;
}
let eventNames = Object.keys(hooks);
eventNames.forEach(function(eventName) {
if (key.match(new RegExp('^' + eventName + '(:.+)?$'))) {
let eventNameHooks = hooks[eventName] || [];
eventNameHooks.forEach(function(hook) {
hook(newValue, oldValue, key);
});
}
});
}
private parseValue(value:string) {
try {
return JSON.parse(value);
} catch (e) {
return value;
}
}
}
+16
Ver Arquivo
@@ -0,0 +1,16 @@
import Storage from './Storage';
import User from './User';
export default class StorageSingleton {
private static globalStorage:Storage;
private static userStorage:Storage;
public static getGlobalStorage():Storage {
}
public static getUserStorage():Storage {
}
}
+241
Ver Arquivo
@@ -0,0 +1,241 @@
import Message from '../Message'
import JID from '../JID'
import * as NS from './xmpp/namespace'
import onRoster from './xmpp/handlers/roster'
import Log from '../util/Log'
enum Presence {
online,
chat,
away,
xa,
dnd,
offline
}
abstract class AbstractConnection {
protected abstract connection;
protected abstract send(stanzaElement:Element);
protected abstract send(stanzaElement:Strophe.Builder);
protected abstract sendIQ(stanzaElement:Element):Promise<{}>;
protected abstract sendIQ(stanzaElement:Strophe.Builder):Promise<{}>;
public getRoster() {
let iq = $iq({
type: 'get'
}).c('query', {
xmlns: 'jabber:iq:roster'
});
//@TODO use account.getStorage().getItem('roster', 'version'), maybe better as parameter
return this.sendIQ(iq).then(function() {
return onRoster.apply(this, arguments);
});
}
public sendMessage(message:Message) {
// @TODO pipes
if (message.getDirection() !== Message.DIRECTION.OUT) {
return;
}
let xmlMsg = $msg({
to: message.getPeer().full,
type: message.getTypeString(),
id: message.getId()
});
if (message.getHtmlMessage()) {
xmlMsg.c('html', {
xmlns: Strophe.NS.XHTML_IM
}).c('body', {
xmlns: Strophe.NS.XHTML
}).h(message.getHtmlMessage()).up();
}
if (message.getPlaintextMessage()) {
xmlMsg.c('body').t(message.getPlaintextMessage()).up();
}
// @TODO call pre send hook
this.send(xmlMsg);
}
public sendPresence(presence:Presence = Presence.online) {
if (this.connection.disco) {
this.connection.disco.addIdentity('client', 'web', 'JSXC');
this.connection.disco.addFeature(NS.get('DISCO_INFO'));
this.connection.disco.addFeature(NS.get('RECEIPTS'));
}
var presenceStanza = $pres();
if (this.connection.caps) {
presenceStanza.c('c', this.connection.caps.generateCapsAttrs()).up();
}
if (presence !== Presence.online) {
presenceStanza.c('show').t(Presence[presence]).up();
}
// var priority = Options.get('priority');
// if (priority && typeof priority[status] !== 'undefined' && parseInt(priority[status]) !== 0) {
// presenceStanza.c('priority').t(priority[status]).up();
// }
Log.debug('Send presence', presenceStanza.toString());
this.send(presenceStanza);
}
public removeContact(jid:JID) {
let self = this;
// Shortcut to remove buddy from roster and cancle all subscriptions
let iq = $iq({
type: 'set'
}).c('query', {
xmlns: NS.get('roster')
}).c('item', {
jid: jid.bare,
subscription: 'remove'
});
// @TODO
// jsxc.gui.roster.purge(bid);
return this.sendIQ(iq);
}
public addContact(jid:JID, alias:string) {
let waitForRoster = this.addContactToRoster(jid, alias);
this.sendSubscriptionRequest(jid);
return waitForRoster;
};
public loadVcard(jid:JID) {
let iq = $iq({
type: 'get',
to: jid.full
}).c('vCard', {
xmlns: NS.get('VCARD')
});
//@TODO register Namespace 'VCARD', 'vcard-temp'
return this.sendIQ(iq).then(this.parseVcard);
}
public getAvatar(jid:JID) {
return this.loadVcard(jid).then(function(vcard){
return new Promise(function(resolve, reject){
if (vcard.PHOTO && vcard.PHOTO.src) {
resolve(vcard.PHOTO);
} else {
reject();
}
});
});
}
public setDisplayName(jid:JID, displayName:string) {
var iq = $iq({
type: 'set'
}).c('query', {
xmlns: 'jabber:iq:roster'
}).c('item', {
jid: jid.bare,
name: displayName
});
this.sendIQ(iq);
}
public sendSubscriptionAnswer(to:JID, accept:boolean) {
let presenceStanza = $pres({
to: to.bare,
type: (accept) ? 'subscribed' : 'unsubscribed'
});
this.send(presenceStanza);
}
private addContactToRoster(jid:JID, alias:string) {
var iq = $iq({
type: 'set'
}).c('query', {
xmlns: 'jabber:iq:roster'
}).c('item', {
jid: jid.full,
name: alias || ''
});
return this.sendIQ(iq);
}
private sendSubscriptionRequest(jid:JID) {
// send subscription request to buddy (trigger onRosterChanged)
this.send($pres({
to: jid.full,
type: 'subscribe'
}));
}
private parseVcard = (stanza) => {
let data:any = {};
let vcard = $(stanza).find('vCard');
if (!vcard.length) {
return data;
}
return this.parseVcardChildren(vcard);
}
private parseVcardChildren(stanza) {
let data:any = {};
let children = stanza.children();
children.each(function(){
let item = $(this);
let children = item.children();
let itemName = item[0].tagName;
let value = null;
if (itemName === 'PHOTO') {
let img = item.find('BINVAL').text();
let type = item.find('TYPE').text();
let src = 'data:' + type + ';base64,' + img;
if (item.find('EXTVAL').length > 0) {
src = item.find('EXTVAL').text();
}
// concat chunks
src = src.replace(/[\t\r\n\f]/gi, '');
value = {
type: type,
src: src
};
} else if (children.length > 0) {
value = this.parseVcardChildren(children);
} else {
value = item.text();
}
data[itemName] = value;
});
return data;
}
}
export {AbstractConnection, Presence};
+27
Ver Arquivo
@@ -0,0 +1,27 @@
import JID from './../JID';
import Message from './../Message';
export interface IConnection {
loadVcard(jid:JID):any;
getCapabilitiesByJid(jid:JID):any;
addContact(jid:JID, alias:string);
removeContact(jid:JID);
sendMessage(message:Message);
sendPresence();
getAvatar(jid:JID);
setDisplayName(jid:JID, displayName:string);
hasFeatureByJid(jid:JID, feature:string);
hasFeatureByJid(jid:JID, feature:string[]);
getRoster();
logout();
}
+107
Ver Arquivo
@@ -0,0 +1,107 @@
import JID from '../../JID';
import Message from '../../Message';
import {IConnection} from '../ConnectionInterface';
import Log from '../../util/Log'
import Account from '../../Account'
import {AbstractConnection, Presence} from '../AbstractConnection'
import * as StropheLib from 'strophe.js'
let Strophe = StropheLib.Strophe;
export default class StorageConnection extends AbstractConnection implements IConnection {
protected connection:any = {};
constructor(private account: Account) {
super();
window.storageConnection = this;
this.connection = {
jid: account.getJID().full,
send: this.send,
sendIQ: (elem, success, error) => {
this.sendIQ(elem).then(success).catch(error);
},
addHandler: () => {}
}
for (var k in (<any>Strophe)._connectionPlugins) {
if ((<any>Strophe)._connectionPlugins.hasOwnProperty(k)) {
var ptype = (<any>Strophe)._connectionPlugins[k];
// jslint complaints about the below line, but this is fine
var F = function() { }; // jshint ignore:line
F.prototype = ptype;
this.connection[k] = new F();
this.connection[k].init(this.connection);
}
}
}
public getCapabilitiesByJid(jid: JID): any {
Log.info('[SC] getCapabilitiesByJid');
};
public hasFeatureByJid(jid: JID, feature: string);
public hasFeatureByJid(jid: JID, feature: string[]);
public hasFeatureByJid() {
Log.info('[SC] has feature by jid');
}
public logout() {
Log.info('[SC] logout');
}
protected send(stanzaElement: Element);
protected send(stanzaElement: Strophe.Builder);
protected send() {
let storage = this.account.getStorage();
let stanzaString = this.stanzaElementToString(arguments[0]);
let key = storage.generateKey(
'stanza',
stanzaString.length + '',
new Date().getTime() + ''
);
storage.setItem(key, stanzaString);
}
protected sendIQ(stanzaElement:Element):Promise<{}>;
protected sendIQ(stanzaElement:Strophe.Builder):Promise<{}>;
protected sendIQ():Promise<{}> {
let storage = this.account.getStorage();
let stanzaString = this.stanzaElementToString(arguments[0]);
let key = storage.generateKey(
'stanzaIQ',
stanzaString.length + '',
new Date().getTime() + ''
);
storage.setItem(key, stanzaString);
return new Promise(function(resolve, reject) {
storage.registerHook(key, function(newValue) { console.log('got an answer', newValue)
storage.removeItem(key);
if (newValue.type === 'success') {
resolve(newValue.stanza);
} else if (newValue.type === 'error') {
reject(newValue.stanza);
}
});
});
}
private stanzaElementToString(stanzaElement: Element):string;
private stanzaElementToString(stanzaElement: Strophe.Builder):string;
private stanzaElementToString() {
let stanzaString: string;
let stanzaElement = arguments[0] || {};
if (typeof stanzaElement.innerHTML === 'string') {
stanzaString = stanzaElement.innerHTML;
} else {
stanzaString = stanzaElement.toString();
}
return stanzaString;
}
}
+124
Ver Arquivo
@@ -0,0 +1,124 @@
import JID from '../../JID';
import Message from '../../Message';
import Options from '../../Options'
import * as CONST from '../../CONST';
import {IConnection} from '../ConnectionInterface';
import {Strophe} from 'strophe';
import * as NS from './namespace'
import XMPPHandler from './handler'
import Log from '../../util/Log'
import Account from '../../Account'
import {AbstractConnection, Presence} from '../AbstractConnection'
import Roster from '../../ui/Roster'
export default class XMPPConnection extends AbstractConnection implements IConnection {
private handler;
constructor(private account:Account, private connection:Strophe.Connection) {
super();
window._conn = connection;
this.handler = new XMPPHandler(connection);
this.handler.registerHandler();
this.account.getStorage().registerHook('stanza', (newValue, oldValue, key) => {
if (newValue && !oldValue) {
this.onIncomingStorageStanza(newValue);
this.account.getStorage().removeItem(key);
}
});
this.account.getStorage().registerHook('stanzaIQ', (newValue, oldValue, key) => {
if (newValue && !oldValue) {
this.onIncomingStorageStanzaIQ(key, newValue);
}
});
Roster.get().registerHook('presence', (presence) => {
if (presence === Presence.offline) {
this.connection.disconnect('');
}
});
}
public getCapabilitiesByJid(jid:JID):any {
};
public renameContact(jid:JID, name:string) {
//@TODO maybe replace jid with contact?
if (d.type === 'chat') {
var iq = $iq({
type: 'set'
}).c('query', {
xmlns: NS.get('roster')
}).c('item', {
jid: jid.bare,
name: name
});
this.connection.sendIQ(iq);
} else if (d.type === 'groupchat') {
jsxc.xmpp.bookmarks.add(bid, newname, d.nickname, d.autojoin);
}
}
public hasFeatureByJid(jid:JID, feature:string);
public hasFeatureByJid(jid:JID, feature:string[]);
public hasFeatureByJid() {
}
public logout() {
}
protected send(stanzaElement:Element);
protected send(stanzaElement:Strophe.Builder);
protected send() {
this.connection.send(arguments[0]);
}
protected sendIQ(stanzaElement:Element):Promise<{}>;
protected sendIQ(stanzaElement:Strophe.Builder):Promise<{}>;
protected sendIQ():Promise<{}> {
let stanzaElement = arguments[0];
return new Promise((resolve, reject) => {
this.connection.sendIQ(stanzaElement, resolve, reject);
});
}
private onIncomingStorageStanza(stanzaString:string) {
let stanzaElement = new DOMParser().parseFromString(stanzaString, 'text/xml').documentElement
if ($(stanzaElement).find('parsererror').length > 0) {
Log.error('Could not parse stanza string from storage.');
return;
}
this.send(stanzaElement);
}
private onIncomingStorageStanzaIQ(key:string, stanzaString:string) {
let stanzaElement = new DOMParser().parseFromString(stanzaString, 'text/xml').documentElement
if ($(stanzaElement).find('parsererror').length > 0) {
Log.error('Could not parse stanza string from storage.');
return;
}
this.sendIQ(stanzaElement).then((stanza) => {
this.account.getStorage().setItem(key, {
type: 'success',
stanza: stanza.outerHTML
});
}).catch((stanza) => {
this.account.getStorage().setItem(key, {
type: 'error',
stanza: stanza.outerHTML
});
});
}
}
+151
Ver Arquivo
@@ -0,0 +1,151 @@
import {$iq,Strophe} from 'strophe.js';
import Options from '../../Options';
import Log from '../../util/Log';
import SM from '../../StateMachine'
import Client from '../../Client'
export function login(url:string, jid:string, sid:string, rid:string);
export function login(url:string, jid:string, password:string);
export function login() {
if (arguments.length === 3) {
return loginWithPassword(arguments[0], arguments[1], arguments[2]);
} else if (arguments.length === 4) {
return attachConnection(arguments[0], arguments[1], arguments[2], arguments[3]);
} else {
Log.warn('This should not happen');
//@TODO this should be moved to some connection parameter detection method
// let jid;
// let storage = StorageSingleton.getGlobalStorage();
//
// let sid = storage.getItem('sid');
// let rid = storage.getItem('rid');
//
// if (sid !== null && rid !== null) {
// jid = storage.getItem('jid');
// } else {
// let xmppOptions = Options.get('xmpp') || {};
//
// sid = xmppOptions.sid || null;
// rid = xmppOptions.rid || null;
// jid = xmppOptions.jid;
// }
//
// return attachConnection(arguments[0], jid, sid, rid);
}
}
function loginWithPassword(url:string, jid:string, password:string):Promise<{}> {
testBasicConnectionParameters(url, jid);
let connection = prepareConnection(url);
Log.debug('Try to establish a new connection.');
return new Promise(function(resolve, reject) {
//@TODO don't forget password from options
connection.connect(jid, password, function(status){
if(status === Strophe.Status.CONNFAIL || status === Strophe.Status.AUTHFAIL) {
reject.call(this, {
connection: connection,
status: status
});
} else if(status === Strophe.Status.CONNECTED) {
resolve.call(this, {
connection: connection,
status: status
});
}
connectionCallback.apply(this, arguments);
});
});
}
function attachConnection(url:string, jid:string, sid:string, rid:string) {
testBasicConnectionParameters(url, jid);
let connection = prepareConnection(url);
Log.debug('Try to attach old connection.');
// jsxc.reconnect = true;
return new Promise(function(resolve, reject) {
connection.attach(jid, sid, rid, function(status){
if(status === Strophe.Status.CONNFAIL || status === Strophe.Status.AUTHFAIL) {
reject.call(this, {
connection: connection,
status: status
});
} else if(status === Strophe.Status.ATTACHED) {
resolve.call(this, {
connection: connection,
status: status
});
}
connectionCallback.apply(this, arguments);
});
})
}
function testBasicConnectionParameters(url:string, jid:string) {
if (!jid)
throw new Error('I can not log in without a jid.');
if (!url)
throw new Error('I can not log in without an URL.');
}
function registerXMPPNamespaces() {
Strophe.addNamespace('RECEIPTS', 'urn:xmpp:receipts');
}
function prepareConnection(url:string):Strophe.Connection {
let connection = new Strophe.Connection(url);
if (Options.get('debug')) {
connection.xmlInput = function(data) {
Log.debug('<', data);
};
connection.xmlOutput = function(data) {
Log.debug('>', data);
};
}
// connection.nextValidRid = onRidChange;
if (connection.caps) {
connection.caps.node = 'http://jsxc.org/';
}
SM.changeState(SM.STATE.ESTABLISHING);
return connection;
}
function connectionCallback(status, condition) {
Log.debug(Object.getOwnPropertyNames(Strophe.Status)[status] + ': ' + condition);
switch (status) {
case Strophe.Status.CONNECTING:
$(document).trigger('connecting.jsxc');
break;
case Strophe.Status.CONNECTED:
//jsxc.bid = jsxc.jidToBid(jsxc.xmpp.conn.jid.toLowerCase());
$(document).trigger('connected.jsxc');
break;
case Strophe.Status.ATTACHED:
$(document).trigger('attached.jsxc');
break;
case Strophe.Status.DISCONNECTED:
$(document).trigger('disconnected.jsxc');
break;
case Strophe.Status.CONNFAIL:
$(document).trigger('connfail.jsxc');
break;
case Strophe.Status.AUTHFAIL:
$(document).trigger('authfail.jsxc');
break;
}
}
+33
Ver Arquivo
@@ -0,0 +1,33 @@
import Log from '../../util/Log';
import JID from '../../JID';
import Contact from '../../Contact';
import onPresence from './handlers/presence';
import onRoster from './handlers/roster'
import onRosterChange from './handlers/rosterChange'
import onChatMessage from './handlers/chatMessage'
import onHeadlineMessage from './handlers/headlineMessage'
let PRESERVE_HANDLER = true;
let REMOVE_HANDLER = false;
let SUBSCRIPTION = {
REMOVE: 'remove',
FROM: 'from',
BOTH: 'both'
};
export default class XMPPHandler {
private connectionJid: JID;
constructor(private connection: Strophe.Connection) {
this.connectionJid = new JID(connection.jid);
}
private registerHandler() {
this.connection.addHandler(onRosterChange, 'jabber:iq:roster', 'iq', 'set');
this.connection.addHandler(onChatMessage, null, 'message', 'chat');
this.connection.addHandler(onHeadlineMessage, null, 'message', 'headline');
this.connection.addHandler(onPresence, null, 'presence');
// this.connection.conn.addHandler(this.onReceived, null, 'message');
}
}
+192
Ver Arquivo
@@ -0,0 +1,192 @@
//import {} from '../handler'
import * as NS from '../namespace'
import Log from '../../../util/Log'
import JID from '../../../JID'
import Message from '../../../Message'
import Utils from '../../../util/Utils'
import Translation from '../../../util/Translation'
import Client from '../../../Client'
import Contact from '../../../Contact'
// body.replace(/^\/me /, '<i title="/me">' + Utils.removeHTML(this.sender.getName()) + '</i> ');
const PRESERVE_HANDLER = true;
export default function onChatMessage(stanza: Element): boolean {
let messageElement = $(stanza);
let forwardedStanza = $(stanza).find('forwarded' + NS.getFilter('FORWARD'));
let from = new JID(messageElement.attr('from'));
let to = new JID(messageElement.attr('to'));
let account = Client.getAccout(to); //@TODO test if we got an account
let forwarded = false;
let carbonCopy = false;
let carbonStanza;
if (forwardedStanza.length > 0) {
messageElement = forwardedStanza.find('> message');
forwarded = true;
carbonStanza = $(stanza).find('> ' + NS.getFilter('CARBONS'));
if (carbonStanza.length === 0) {
carbonCopy = false;
} else if (from.bare !== to.bare) {
// ignore this carbon copy
return PRESERVE_HANDLER;
} else {
carbonCopy = true;
}
Log.debug('Incoming forwarded message', messageElement);
} else {
Log.debug('Incoming message', messageElement);
}
let plaintextBody = Utils.removeHTML(messageElement.find('> body').text());
let htmlBody = messageElement.find('html body[xmlns="' + Strophe.NS.XHTML + '"]');
if (!plaintextBody || (plaintextBody.match(/\?OTR/i) && forwarded)) {
return PRESERVE_HANDLER;
}
let messageType = messageElement.attr('type');
let messageFrom = messageElement.attr('from');
let messageTo = messageElement.attr('from');
let messageId = messageElement.attr('id');
let receiptsRequestElement = messageElement.find("request[xmlns='urn:xmpp:receipts']");
let delayElement = messageElement.find('delay[xmlns="urn:xmpp:delay"]');
let stamp = (delayElement.length > 0) ? new Date(delayElement.attr('stamp')) : new Date();
let peer:JID = from; //@REVIEW do we need peer?
if (carbonCopy) {
let direction = (carbonStanza.prop('tagName') === 'sent') ? Message.DIRECTION.OUT : Message.DIRECTION.IN;
peer = new JID(direction === Message.DIRECTION.OUT ? messageTo : messageFrom);
let message = new Message({
peer: peer,
direction: direction,
plaintextMessage: plaintextBody,
htmlMessage: htmlBody.html(),
forwarded: forwarded,
stamp: stamp.getTime()
});
message.save();
//@TODO
return PRESERVE_HANDLER;
} else if (forwarded) {
// Someone forwarded a message to us
//@REVIEW
plaintextBody = from + ' ' + Translation.t('to') + ' ' + $(stanza).attr('to') + '"' + plaintextBody + '"';
messageFrom = $(stanza).attr('from');
}
let contact:Contact = account.getContact(from);
if (typeof contact === 'undefined') {
// jid not in roster
// var chat = jsxc.storage.getUserItem('chat', bid) || [];
//
// if (chat.length === 0) {
// jsxc.notice.add({
// msg: $.t('Unknown_sender'),
// description: $.t('You_received_a_message_from_an_unknown_sender') + ' (' + bid + ').'
// }, 'gui.showUnknownSender', [bid]);
// }
//
// var msg = jsxc.removeHTML(plaintextBody);
// msg = jsxc.escapeHTML(msg);
//
// jsxc.storage.saveMessage(bid, 'in', msg, false, forwarded, stamp);
return PRESERVE_HANDLER;
}
// If we now the full jid, we use it
contact.setResource(from.resource);
$(document).trigger('message.jsxc', [from, plaintextBody]);
let message = new Message({
peer: peer,
direction: Message.DIRECTION.IN,
plaintextMessage: plaintextBody,
htmlMessage: htmlBody.html(),
forwarded: forwarded,
stamp: stamp.getTime(),
// attachment:
});
message.save();
let chatWindow = account.openChatWindow(contact);
chatWindow.receiveIncomingMessage(message);
// create related otr object
// if (jsxc.master && !jsxc.otr.objects[bid]) {
// jsxc.otr.create(bid);
// }
// if (!forwarded && mid !== null && receiptsRequestElement.length && data !== null && (data.sub === 'both' || data.sub === 'from') && messageType === 'chat') {
// // Send received according to XEP-0184
// jsxc.xmpp.conn.send($msg({
// to: from
// }).c('received', {
// xmlns: 'urn:xmpp:receipts',
// id: mid
// }));
// }
// var attachment;
// if (htmlBody.length === 1) {
// var httpUploadElement = htmlBody.find('a[data-type][data-name][data-size]');
//
// if (httpUploadElement.length === 1) {
// attachment = {
// type: httpUploadElement.attr('data-type'),
// name: httpUploadElement.attr('data-name'),
// size: httpUploadElement.attr('data-size'),
// };
//
// if (httpUploadElement.attr('data-thumbnail') && httpUploadElement.attr('data-thumbnail').match(/^\s*data:[a-z]+\/[a-z0-9-+.*]+;base64,[a-z0-9=+/]+$/i)) {
// attachment.thumbnail = httpUploadElement.attr('data-thumbnail');
// }
//
// if (httpUploadElement.attr('href') && httpUploadElement.attr('href').match(/^https:\/\//)) {
// attachment.data = httpUploadElement.attr('href');
// plaintextBody = null;
// }
//
// if (!attachment.type.match(/^[a-z]+\/[a-z0-9-+.*]+$/i) || !attachment.name.match(/^[\s\w.,-]+$/i) || !attachment.size.match(/^\d+$/i)) {
// attachment = undefined;
//
// jsxc.warn('Invalid file type, name or size.');
// }
// }
// }
// if (jsxc.otr.objects.hasOwnProperty(bid) && plaintextBody) {
// // @TODO check for file upload url after decryption
// jsxc.otr.objects[bid].receiveMsg(plaintextBody, {
// stamp: stamp,
// forwarded: forwarded,
// attachment: attachment
// });
// } else {
// jsxc.gui.window.postMessage({
// bid: bid,
// direction: jsxc.Message.IN,
// msg: plaintextBody,
// encrypted: false,
// forwarded: forwarded,
// stamp: stamp,
// attachment: attachment
// });
// }
return PRESERVE_HANDLER;
}
@@ -0,0 +1,4 @@
export default function onHeadlineMessage(stanza: Element): boolean {
}
+147
Ver Arquivo
@@ -0,0 +1,147 @@
import Log from '../../../util/Log';
import JID from '../../../JID';
import {ContactInterface} from '../../../ContactInterface';
import Client from '../../../Client';
import {Notice, TYPE as NOTICETYPE, FUNCTION as NOTICEFUNCTION} from '../../../Notice';
import Roster from '../../../ui/Roster';
import {Presence} from '../../AbstractConnection'
import 'jquery'
let PRESERVE_HANDLER = true;
let REMOVE_HANDLER = false;
let ERROR_RETURN = 1;
let SUBSCRIPTION = {
REMOVE: 'remove',
FROM: 'from',
BOTH: 'both',
TO: 'to'
};
let PRESENCE = {
ERROR: 'error',
SUBSCRIBE: 'subscribe',
UNAVAILABLE: 'unavailable',
UNSUBSCRIBED: 'unsubscribed'
};
let account;
export default function(stanza: Element): boolean {
Log.debug('onPresence', stanza);
//@TODO use sid to retrieve the correct account
account = Client.getAccout();
let presence = {
type: $(stanza).attr('type'),
from: new JID($(stanza).attr('from')),
show: $(stanza).find('show').text(),
status: $(stanza).find('status').text()
}
let status:Presence = determinePresenceStatus(presence);
if (presence.from.bare === account.getJID().bare) {
Log.debug('Ignore own presence notification');
return 1;
}
if (presence.type === PRESENCE.ERROR) {
var error = $(stanza).find('error');
var errorCode = error.attr('code') || '';
var errorType = error.attr('type') || '';
var errorReason = error.find(">:first-child").prop('tagName');
var errorText = error.find('text').text();
//@TODO display error message
Log.error('[XMPP] ' + errorType + ', ' + errorCode + ', ' + errorReason + ', ' + errorText);
return 2;
}
let xVCard = $(stanza).find('x[xmlns="vcard-temp:x:update"]');
let contact = account.getContact(presence.from);
if (typeof contact === 'undefined') {
Log.warn('Could not find contact object for ' + presence.from.full);
return PRESERVE_HANDLER;
}
//@REVIEW we can't process a contact request from an unknown contact, because getContact would return undefined
// incoming friendship request
if (presence.type === PRESENCE.SUBSCRIBE) {
Log.debug('received subscription request');
processSubscribtionRequest(presence.from, contact);
return PRESERVE_HANDLER;
}
contact.setStatus(presence.status);
contact.setPresence(presence.from.resource, status);
contact.setResource(''); // reset jid, so new messages go to the bare jid
// if (data.type === 'groupchat') {
// data.status = status;
// } else {
// data.status = max;
// }
//
// data.res = maxVal;
// data.jid = jid;
//
// // Looking for avatar
// if (xVCard.length > 0 && data.type !== 'groupchat') {
// var photo = xVCard.find('photo');
//
// if (photo.length > 0 && photo.text() !== data.avatar) {
// jsxc.storage.removeUserItem('avatar', data.avatar);
// data.avatar = photo.text();
// }
// }
Log.debug('Presence (' + presence.from.full + '): ' + Presence[status]);
// preserve handler
return PRESERVE_HANDLER;
};
function processSubscribtionRequest(jid:JID, contact:ContactInterface) {
if (contact) {
Log.debug('Auto approve contact request, because he is already in our contact list.');
account.getConnection().sendSubscriptionAnswer(contact.getJid(), true);
if (contact.getSubscription() !== SUBSCRIPTION.TO) {
Roster.get().add(contact);
}
return PRESERVE_HANDLER;
}
account.addNotice({
title: 'Friendship_request',
description: 'from ' + jid.bare,
type: NOTICETYPE.contact,
fnName: NOTICEFUNCTION.contactRequest,
fnParams: [jid.bare]
});
}
function determinePresenceStatus(presence):Presence {
let status;
if (presence.type === PRESENCE.UNAVAILABLE || presence.type === PRESENCE.UNSUBSCRIBED) {
status = Presence['offline'];
} else {
let show = presence.show;
if (show === '') {
status = Presence['online'];
} else {
status = Presence[show];
}
}
return status;
}
+46
Ver Arquivo
@@ -0,0 +1,46 @@
import Log from '../../../util/Log'
import JID from '../../../JID'
import Client from '../../../Client'
import Account from '../../../Account'
import ContactData from '../../../ContactData'
import Roster from '../../../ui/Roster'
const REMOVE_HANDLER = false;
export default function onRoster(stanzaElement: Element): boolean {
Log.debug('Load roster', stanzaElement);
let stanza = $(stanzaElement);
let toJid = new JID(stanza.attr('to'));
let account:Account = Client.getAccout(toJid);
if (stanza.find('query').length === 0) {
Log.debug('Use cached roster');
// jsxc.restoreRoster();
return REMOVE_HANDLER;
}
stanza.find('item').each(function() {
let item = $(this);
let jid = new JID(item.attr('jid'));
let name = item.attr('name') || jid.bare;
let subscription = item.attr('subscription');
let contact = account.addContact(new ContactData({
jid: jid,
name: name,
subscription: subscription
}));
Roster.get().add(contact);
});
let rosterVersion = $(stanza).find('query').attr('ver');
if (rosterVersion) {
account.getStorage().setItem('roster', 'version', rosterVersion);
}
return REMOVE_HANDLER;
}
+100
Ver Arquivo
@@ -0,0 +1,100 @@
import Log from '../../../util/Log';
import JID from '../../../JID';
import Client from '../../../Client';
import ContactData from '../../../ContactData'
import Roster from '../../../ui/Roster'
let PRESERVE_HANDLER = true;
let REMOVE_HANDLER = false;
let SUBSCRIPTION = {
REMOVE: 'remove',
FROM: 'from',
BOTH: 'both'
};
let PRESENCE = {
ERROR: 'error',
SUBSCRIBE: 'subscribe',
UNAVAILABLE: 'unavailable',
UNSUBSCRIBED: 'unsubscribed'
};
export default function onRosterChange(stanza: Element): boolean {
let fromString = $(stanza).attr('from');
let fromJid;
if (fromString) {
fromJid = new JID(fromString);
}
//@TODO use sid to retrieve the correct account
let account = Client.getAccout();
if (fromJid && fromJid.bare !== account.getJID().bare) {
Log.info('Ignore roster change with wrong sender jid.');
return PRESERVE_HANDLER;
}
Log.debug('Process roster change.');
let itemElement = $(stanza).find('item');
if (itemElement.length !== 1) {
Log.info('Ignore roster change with more than one item element.');
return PRESERVE_HANDLER;
}
let jid = new JID($(itemElement).attr('jid'));
let name = $(itemElement).attr('name') || jid.bare;
let subscription = $(itemElement).attr('subscription');
let contact = account.getContact(jid);
if (contact) {
if (subscription === SUBSCRIPTION.REMOVE) {
account.removeContact(contact);
} else {
contact.setName(name);
contact.setSubscription(subscription);
//@TODO refresh roster position
}
} else {
//@REVIEW DRY same code as in roster handler
contact = account.addContact(new ContactData({
jid: jid,
name: name,
subscription: subscription
}));
Roster.get().add(contact);
}
// Remove pending friendship request from notice list
if (subscription === SUBSCRIPTION.FROM || subscription === SUBSCRIPTION.BOTH) {
// var notices = jsxc.storage.getUserItem('notices');
// var noticeKey = null,
// notice;
//
// for (noticeKey in notices) {
// notice = notices[noticeKey];
//
// if (notice.fnName === 'gui.showApproveDialog' && notice.fnParams[0] === jid) {
// jsxc.debug('Remove notice with key ' + noticeKey);
//
// jsxc.notice.remove(noticeKey);
// }
// }
}
//@REVIEW DRY roster handler
let rosterVersion = $(stanza).find('query').attr('ver');
if (rosterVersion) {
account.getStorage().setItem('roster', 'version', rosterVersion);
}
return PRESERVE_HANDLER;
}
+19
Ver Arquivo
@@ -0,0 +1,19 @@
import Log from '../../util/Log'
export function register(name:string, value:string):void {
Strophe.addNamespace(name, value);
}
export function get(name:string) {
let value = Strophe.NS[name];
if (!value) {
Log.debug('Can not resolve requested namespace ' + name);
}
return value;
}
export function getFilter(name:string) {
return '[xmlns="' + get(name) + '"]';
}
+73
Ver Arquivo
@@ -0,0 +1,73 @@
import Log from './util/Log';
import * as UI from './ui/web'
import Client from './Client'
Client.init();
export function start(boshUrl: string, jid: string, sid: string, rid: string);
export function start(boshUrl: string, jid: string, password: string);
export function start();
export function start() { console.log('api.start', arguments)
switch (arguments.length) {
case 0: startUI();
break;
case 3: startWithCredentials(arguments[0], arguments[1], arguments[2]);
break;
case 4: startWithBoshParameters(arguments[0], arguments[1], arguments[2], arguments[3]);
break;
default:
Log.warn('Wrong number of parameters.');
}
}
function startUI() {
UI.init();
}
function startWithCredentials(boshUrl: string, jid: string, password: string) {
let connectionPromise = Client.createAccount.apply(this, arguments);
return connectionPromise.then(function() {
UI.init();
});
}
function startWithBoshParameters(boshUrl: string, jid: string, sid: string, rid: string) {
}
export function enableDebugMode() {
let storage = Client.getStorage();
storage.setItem('debug', true);
}
export function disableDebugMode() {
let storage = Client.getStorage();
storage.setItem('debug', false);
}
export function deleteAllData() {
if (!Client.isDebugMode()) {
Log.warn('This action is only available in debug mode.');
return 0;
}
let storage = Client.getStorage();
let prefix = storage.getPrefix();
let prefixRegex = new RegExp('^' + prefix);
let backend = storage.getBackend();
let keys = Object.keys(backend);
let count = 0;
for(let key of keys) {
if (prefixRegex.test(key) && key !== prefix + 'debug') {
backend.removeItem(key);
count++;
}
}
return count;
}
-4
Ver Arquivo
@@ -1,4 +0,0 @@
var jsxc = null, RTC = null, RTCPeerconnection = null;
(function($) {
"use strict";
-267
Ver Arquivo
@@ -1,267 +0,0 @@
/**
* Load message object with given uid.
*
* @class Message
* @memberOf jsxc
* @param {string} uid Unified identifier from message object
*/
/**
* Create new message object.
*
* @class Message
* @memberOf jsxc
* @param {object} args New message properties
* @param {string} args.bid
* @param {direction} args.direction
* @param {string} args.msg
* @param {boolean} args.encrypted
* @param {boolean} args.forwarded
* @param {boolean} args.sender
* @param {integer} args.stamp
* @param {object} args.attachment Attached data
* @param {string} args.attachment.name File name
* @param {string} args.attachment.size File size
* @param {string} args.attachment.type File type
* @param {string} args.attachment.data File data
*/
jsxc.Message = function() {
/** @member {string} */
this._uid = null;
/** @member {boolean} */
this._received = false;
/** @member {boolean} */
this.encrypted = null;
/** @member {boolean} */
this.forwarded = false;
/** @member {integer} */
this.stamp = new Date().getTime();
this.type = jsxc.Message.PLAIN;
if (typeof arguments[0] === 'string' && arguments[0].length > 0 && arguments.length === 1) {
this._uid = arguments[0];
this.load(this._uid);
} else if (typeof arguments[0] === 'object' && arguments[0] !== null) {
$.extend(this, arguments[0]);
}
if (!this._uid) {
this._uid = new Date().getTime() + ':msg';
}
};
/**
* Load message properties.
*
* @memberof jsxc.Message
* @param {string} uid
*/
jsxc.Message.prototype.load = function(uid) {
var data = jsxc.storage.getUserItem('msg', uid);
if (!data) {
jsxc.debug('Could not load message with uid ' + uid);
}
$.extend(this, data);
};
/**
* Save message properties and create thumbnail.
*
* @memberOf jsxc.Message
* @return {Message} this object
*/
jsxc.Message.prototype.save = function() {
var history;
if (this.bid) {
history = jsxc.storage.getUserItem('history', this.bid) || [];
if (history.indexOf(this._uid) < 0) {
if (history.length > jsxc.options.get('numberOfMsg')) {
jsxc.Message.delete(history.pop());
}
} else {
history = null;
}
}
if (Image && this.attachment && this.attachment.type.match(/^image\//i) && this.attachment.data && !this.attachment.thumbnail) {
var sHeight, sWidth, sx, sy;
var dHeight = 100,
dWidth = 100;
var canvas = $("<canvas>").get(0);
canvas.width = dWidth;
canvas.height = dHeight;
var ctx = canvas.getContext("2d");
var img = new Image();
img.src = this.attachment.data;
if (img.height > img.width) {
sHeight = img.width;
sWidth = img.width;
sx = 0;
sy = (img.height - img.width) / 2;
} else {
sHeight = img.height;
sWidth = img.height;
sx = (img.width - img.height) / 2;
sy = 0;
}
ctx.drawImage(img, sx, sy, sWidth, sHeight, 0, 0, dWidth, dHeight);
this.attachment.thumbnail = canvas.toDataURL();
if (this.direction === 'out') {
// save storage
this.attachment.data = null;
}
}
var data;
if (this.attachment && this.attachment.size > jsxc.options.maxStorableSize && this.direction === 'in') {
jsxc.debug('Attachment to large to store');
data = this.attachment.data;
this.attachment.data = null;
this.attachment.persistent = false;
//@TODO inform user
}
jsxc.storage.setUserItem('msg', this._uid, this);
if (history) {
history.unshift(this._uid);
jsxc.storage.setUserItem('history', this.bid, history);
}
if (data && this.attachment) {
this.attachment.data = data;
}
return this;
};
/**
* Remove object from storage.
*
* @memberOf jsxc.Message
*/
jsxc.Message.prototype.delete = function() {
jsxc.Message.delete(this._uid);
};
/**
* Returns object as jquery object.
*
* @memberOf jsxc.Message
* @return {jQuery} Representation in DOM
*/
jsxc.Message.prototype.getDOM = function() {
return jsxc.Message.getDOM(this._uid);
};
/**
* Mark message as received.
*
* @memberOf jsxc.Message
*/
jsxc.Message.prototype.received = function() {
this._received = true;
this.save();
this.getDOM().addClass('jsxc_received');
};
/**
* Returns true if the message was already received.
*
* @memberOf jsxc.Message
* @return {boolean} true means received
*/
jsxc.Message.prototype.isReceived = function() {
return this._received;
};
/**
* Remove message with uid.
*
* @memberOf jsxc.Message
* @static
* @param {string} uid message uid
*/
jsxc.Message.delete = function(uid) {
var data = jsxc.storage.getUserItem('msg', uid);
if (data) {
jsxc.storage.removeUserItem('msg', uid);
if (data.bid) {
var history = jsxc.storage.getUserItem('history', data.bid) || [];
history = $.grep(history, function(el) {
return el !== uid;
});
jsxc.storage.setUserItem('history', data.bid, history);
}
}
};
/**
* Returns message object as jquery object.
*
* @memberOf jsxc.Message
* @static
* @param {string} uid message uid
* @return {jQuery} jQuery representation in DOM
*/
jsxc.Message.getDOM = function(uid) {
return $('#' + uid.replace(/:/g, '-'));
};
/**
* Message direction can be incoming, outgoing or system.
*
* @typedef {(jsxc.Message.IN|jsxc.Message.OUT|jsxc.Message.SYS)} direction
*/
/**
* @constant
* @type {string}
* @default
*/
jsxc.Message.IN = 'in';
/**
* @constant
* @type {string}
* @default
*/
jsxc.Message.OUT = 'out';
/**
* @constant
* @type {string}
* @default
*/
jsxc.Message.SYS = 'sys';
jsxc.Message.HTML = 'html';
jsxc.Message.PLAIN = 'plain';
-249
Ver Arquivo
@@ -1,249 +0,0 @@
/**
* @namespace jsxc.fileTransfer
* @type {Object}
*/
jsxc.fileTransfer = {};
/**
* Make bytes more human readable.
*
* @memberOf jsxc.fileTransfer
* @param {Integer} byte
* @return {String}
*/
jsxc.fileTransfer.formatByte = function(byte) {
var s = ['', 'KB', 'MB', 'GB', 'TB'];
var i;
for (i = 1; i < s.length; i++) {
if (byte < 1024) {
break;
}
byte /= 1024;
}
return (Math.round(byte * 10) / 10) + s[i - 1];
};
/**
* Start file transfer dialog.
*
* @memberOf jsxc.fileTransfer
* @param {String} jid
*/
jsxc.fileTransfer.startGuiAction = function(jid) {
var bid = jsxc.jidToBid(jid);
var res = Strophe.getResourceFromJid(jid);
if (!res && !jsxc.xmpp.httpUpload.ready) {
jsxc.fileTransfer.selectResource(bid, jsxc.fileTransfer.startGuiAction);
return;
}
jsxc.fileTransfer.showFileSelection(jid);
};
/**
* Show select dialog for file transfer capable resources.
*
* @memberOf jsxc.fileTransfer
* @param {String} bid
* @param {Function} success_cb Called if user selects resource
* @param {Function} error_cb Called if no resource was found or selected
*/
jsxc.fileTransfer.selectResource = function(bid, success_cb, error_cb) {
var win = jsxc.gui.window.get(bid);
var jid = win.data('jid');
var res = Strophe.getResourceFromJid(jid);
var fileCapableRes = jsxc.webrtc.getCapableRes(jid, jsxc.webrtc.reqFileFeatures);
var resources = Object.keys(jsxc.storage.getUserItem('res', bid)) || [];
if (res === null && resources.length === 1 && fileCapableRes.length === 1) {
// only one resource is available and this resource is also capable to receive files
res = fileCapableRes[0];
jid = bid + '/' + res;
success_cb(jid);
} else if (fileCapableRes.indexOf(res) >= 0) {
// currently used resource is capable to receive files
success_cb(bid + '/' + res);
} else if (fileCapableRes.indexOf(res) < 0) {
// show selection dialog
jsxc.gui.window.selectResource(bid, $.t('Your_contact_uses_multiple_clients_'), function(data) {
if (data.status === 'unavailable') {
jsxc.gui.window.hideOverlay(bid);
if (typeof error_cb === 'function') {
error_cb();
}
} else if (data.status === 'selected') {
success_cb(bid + '/' + data.result);
}
}, fileCapableRes);
}
};
/**
* Show file selector.
*
* @memberOf jsxc.fileTransfer
* @param {String} jid
*/
jsxc.fileTransfer.showFileSelection = function(jid) {
var bid = jsxc.jidToBid(jid);
var msg = $('<div><div><label><input type="file" name="files" /><label></div></div>');
msg.addClass('jsxc_chatmessage');
jsxc.gui.window.showOverlay(bid, msg, true);
// open file selection for user
msg.find('label').click();
msg.find('[type="file"]').change(function(ev) {
var file = ev.target.files[0]; // FileList object
if (!file) {
return;
}
jsxc.fileTransfer.fileSelected(jid, msg, file);
});
};
/**
* Callback for file selector.
*
* @memberOf jsxc.fileTransfer
* @param {String} jid
* @param {jQuery} msg jQuery object of temporary file message
* @param {File} file selected file
*/
jsxc.fileTransfer.fileSelected = function(jid, msg, file) {
var bid = jsxc.jidToBid(jid);
if (file.transportMethod !== 'webrtc' && jsxc.xmpp.httpUpload.ready && file.size > jsxc.options.get('httpUpload').maxSize) {
jsxc.debug('File too large for http upload.');
file.transportMethod = 'webrtc';
jsxc.fileTransfer.selectResource(bid, function(jid) {
jsxc.fileTransfer.fileSelected(jid, msg, file);
}, function() {
var maxSize = jsxc.fileTransfer.formatByte(jsxc.options.get('httpUpload').maxSize);
var fileSize = jsxc.fileTransfer.formatByte(file.size);
jsxc.gui.window.postMessage({
bid: bid,
direction: jsxc.Message.SYS,
msg: $.t('File_too_large') + ' (' + fileSize + ' > ' + maxSize + ')'
});
jsxc.gui.window.hideOverlay(bid);
});
return;
} else if (!jsxc.xmpp.httpUpload.ready && Strophe.getResourceFromJid(jid)) {
// http upload not available
file.transportMethod = 'webrtc';
}
var attachment = $('<div>');
attachment.addClass('jsxc_attachment');
attachment.addClass('jsxc_' + file.type.replace(/\//, '-'));
attachment.addClass('jsxc_' + file.type.replace(/^([^/]+)\/.*/, '$1'));
msg.empty().append(attachment);
if (FileReader && file.type.match(/^image\//)) {
// show image preview
var img = $('<img alt="preview">').attr('title', file.name);
img.attr('src', jsxc.options.get('root') + '/img/loading.gif');
img.appendTo(attachment);
var reader = new FileReader();
reader.onload = function() {
img.attr('src', reader.result);
};
reader.readAsDataURL(file);
} else {
attachment.text(file.name + ' (' + file.size + ' byte)');
}
$('<button>').addClass('jsxc_btn jsxc_btn-primary').text($.t('Send')).click(function() {
// user confirmed file transfer
jsxc.gui.window.hideOverlay(bid);
msg.remove();
var message = jsxc.gui.window.postMessage({
bid: bid,
direction: 'out',
attachment: {
name: file.name,
size: file.size,
type: file.type,
data: (file.type.match(/^image\//)) ? img.attr('src') : null
}
});
if (file.transportMethod === 'webrtc') {
var sess = jsxc.webrtc.sendFile(jid, file);
sess.sender.on('progress', function(sent, size) {
jsxc.gui.window.updateProgress(message, sent, size);
if (sent === size) {
message.received();
}
});
} else {
// progress is updated in xmpp.httpUpload.uploadFile
jsxc.xmpp.httpUpload.sendFile(file, message);
}
}).appendTo(msg);
$('<button>').addClass('jsxc_btn jsxc_btn-default').text($.t('Abort')).click(function() {
// user aborted file transfer
jsxc.gui.window.hideOverlay(bid);
}).appendTo(msg);
};
/**
* Enable/disable icons for file transfer.
*
* @memberOf jsxc.fileTransfer
* @param {String} bid
*/
jsxc.fileTransfer.updateIcons = function(bid) {
var win = jsxc.gui.window.get(bid);
if (!win || win.length === 0 || !jsxc.xmpp.conn) {
return;
}
jsxc.debug('Update file transfer icons for ' + bid);
if (jsxc.xmpp.httpUpload.ready) {
win.find('.jsxc_sendFile').removeClass('jsxc_disabled');
return;
}
var jid = win.data('jid');
var res = Strophe.getResourceFromJid(jid);
var fileCapableRes = jsxc.webrtc.getCapableRes(bid, jsxc.webrtc.reqFileFeatures);
var resources = Object.keys(jsxc.storage.getUserItem('res', bid) || {}) || [];
if (fileCapableRes.indexOf(res) > -1 || (res === null && fileCapableRes.length === 1 && resources.length === 1)) {
win.find('.jsxc_sendFile').removeClass('jsxc_disabled');
} else {
win.find('.jsxc_sendFile').addClass('jsxc_disabled');
}
};
$(document).on('update.gui.jsxc', function(ev, bid) {
jsxc.fileTransfer.updateIcons(bid);
});
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
-965
Ver Arquivo
@@ -1,965 +0,0 @@
/**
* JavaScript Xmpp Chat namespace
*
* @namespace jsxc
*/
jsxc = {
/** Version of jsxc */
version: '< $ app.version $ >',
/** True if i'm the master */
master: false,
/** True if the role allocation is finished */
role_allocation: false,
/** Timeout for keepalive */
to: [],
/** Timeout after normal keepalive starts */
toBusy: null,
/** Timeout for notification */
toNotification: null,
/** Timeout delay for notification */
toNotificationDelay: 500,
/** Interval for keep-alive */
keepaliveInterval: null,
/** True if jid, sid and rid was used to connect */
reconnect: false,
/** True if restore is complete */
restoreCompleted: false,
/** True if login through box */
triggeredFromBox: false,
/** True if logout through element click */
triggeredFromElement: false,
/** True if logout through logout click */
triggeredFromLogout: false,
/** last values which we wrote into localstorage (IE workaround) */
ls: [],
/**
* storage event is even fired if I write something into storage (IE
* workaround) 0: conform, 1: not conform, 2: not shure
*/
storageNotConform: null,
/** Timeout for storageNotConform test */
toSNC: null,
/** My bar id */
bid: null,
/** Current state */
currentState: null,
/** Current UI state */
currentUIState: null,
/** Some constants */
CONST: {
NOTIFICATION_DEFAULT: 'default',
NOTIFICATION_GRANTED: 'granted',
NOTIFICATION_DENIED: 'denied',
STATUS: ['offline', 'dnd', 'xa', 'away', 'chat', 'online'],
SOUNDS: {
MSG: 'incomingMessage.wav',
CALL: 'Rotary-Phone6.mp3',
NOTICE: 'Ping1.mp3'
},
REGEX: {
JID: new RegExp('\\b[^"&\'\\/:<>@\\s]+@[\\w-_.]+\\b', 'ig'),
URL: new RegExp(/(https?:\/\/|www\.)[^\s<>'"]+/gi)
},
NS: {
CARBONS: 'urn:xmpp:carbons:2',
FORWARD: 'urn:xmpp:forward:0'
},
HIDDEN: 'hidden',
SHOWN: 'shown',
STATE: {
INITIATING: 0,
PREVCONFOUND: 1,
SUSPEND: 2,
TRYTOINTERCEPT: 3,
INTERCEPTED: 4,
ESTABLISHING: 5,
READY: 6
},
UISTATE: {
INITIATING: 0,
READY: 1
}
},
/**
* Parse a unix timestamp and return a formatted time string
*
* @memberOf jsxc
* @param {Object} unixtime
* @returns time of day and/or date
*/
getFormattedTime: function(unixtime) {
var msgDate = new Date(parseInt(unixtime));
var day = ('0' + msgDate.getDate()).slice(-2);
var month = ('0' + (msgDate.getMonth() + 1)).slice(-2);
var year = msgDate.getFullYear();
var hours = ('0' + msgDate.getHours()).slice(-2);
var minutes = ('0' + msgDate.getMinutes()).slice(-2);
var dateNow = new Date();
var date = (typeof msgDate.toLocaleDateString === 'function') ? msgDate.toLocaleDateString() : day + '.' + month + '.' + year;
var time = (typeof msgDate.toLocaleTimeString === 'function') ? msgDate.toLocaleTimeString() : hours + ':' + minutes;
// compare dates only
dateNow.setHours(0, 0, 0, 0);
msgDate.setHours(0, 0, 0, 0);
if (dateNow.getTime() !== msgDate.getTime()) {
return date + ' ' + time;
}
return time;
},
/**
* Write debug message to console and to log.
*
* @memberOf jsxc
* @param {String} msg Debug message
* @param {Object} data
* @param {String} Could be warn|error|null
*/
debug: function(msg, data, level) {
if (level) {
msg = '[' + level + '] ' + msg;
}
if (data) {
if (jsxc.storage.getItem('debug') === true) {
console.log(msg, data);
}
// try to convert data to string
var d;
try {
// clone html snippet
d = $("<span>").prepend($(data).clone()).html();
} catch (err) {
try {
d = JSON.stringify(data);
} catch (err2) {
d = 'see js console';
}
}
jsxc.log = jsxc.log + '$ ' + msg + ': ' + d + '\n';
} else {
console.log(msg);
jsxc.log = jsxc.log + '$ ' + msg + '\n';
}
},
/**
* Write warn message.
*
* @memberOf jsxc
* @param {String} msg Warn message
* @param {Object} data
*/
warn: function(msg, data) {
jsxc.debug(msg, data, 'WARN');
},
/**
* Write error message.
*
* @memberOf jsxc
* @param {String} msg Error message
* @param {Object} data
*/
error: function(msg, data) {
jsxc.debug(msg, data, 'ERROR');
},
/** debug log */
log: '',
/**
* This function initializes important core functions and event handlers.
* Afterwards it performs the following actions in the given order:
*
* <ol>
* <li>If (loginForm.ifFound = 'force' and form was found) or (jid or rid or
* sid was not found) intercept form, and listen for credentials.</li>
* <li>Attach with jid, rid and sid from storage, if no form was found or
* loginForm.ifFound = 'attach'</li>
* <li>Attach with jid, rid and sid from options.xmpp, if no form was found or
* loginForm.ifFound = 'attach'</li>
* </ol>
*
* @memberOf jsxc
* @param {object} options See {@link jsxc.options}
*/
init: function(options) {
jsxc.changeState(jsxc.CONST.STATE.INITIATING);
if (options && options.loginForm && typeof options.loginForm.attachIfFound === 'boolean' && !options.loginForm.ifFound) {
// translate deprated option attachIfFound found to new ifFound
options.loginForm.ifFound = (options.loginForm.attachIfFound) ? 'attach' : 'pause';
}
if (options) {
// override default options
$.extend(true, jsxc.options, options);
}
// Check localStorage
if (typeof(localStorage) === 'undefined') {
jsxc.warn("Browser doesn't support localStorage.");
return;
}
/**
* Getter method for options. Saved options will override default one.
*
* @param {string} key option key
* @returns default or saved option value
*/
jsxc.options.get = function(key) {
if (jsxc.bid) {
var local = jsxc.storage.getUserItem('options') || {};
return (typeof local[key] !== 'undefined') ? local[key] : jsxc.options[key];
}
return jsxc.options[key];
};
/**
* Setter method for options. Will write into localstorage.
*
* @param {string} key option key
* @param {object} value option value
*/
jsxc.options.set = function(key, value) {
jsxc.storage.updateItem('options', key, value, true);
};
jsxc.storageNotConform = jsxc.storage.getItem('storageNotConform');
if (jsxc.storageNotConform === null) {
jsxc.storageNotConform = 2;
}
// detect language
var lang;
if (jsxc.storage.getItem('lang') !== null) {
lang = jsxc.storage.getItem('lang');
} else if (jsxc.options.autoLang && navigator.languages && navigator.languages.length > 0) {
lang = navigator.languages[0].substr(0, 2);
} else if (jsxc.options.autoLang && navigator.language) {
lang = navigator.language.substr(0, 2);
} else {
lang = jsxc.options.defaultLang;
}
// initialize i18next translator
window.i18next.init({
lng: lang,
fallbackLng: 'en',
resources: I18next,
debug: jsxc.storage.getItem('debug') === true,
interpolation: {
prefix: '__',
suffix: '__'
}
}, function() {
window.jqueryI18next.init(window.i18next, $, {
tName: 't',
i18nName: 'i18next',
handleName: 'localize',
});
});
if (jsxc.storage.getItem('debug') === true) {
jsxc.options.otr.debug = true;
}
// Register event listener for the storage event
window.addEventListener('storage', jsxc.storage.onStorage, false);
$(document).on('attached.jsxc', jsxc.registerLogout);
var isStorageAttachParameters = jsxc.storage.getItem('rid') && jsxc.storage.getItem('sid') && jsxc.storage.getItem('jid');
var isOptionsAttachParameters = jsxc.options.xmpp.rid && jsxc.options.xmpp.sid && jsxc.options.xmpp.jid;
var isForceLoginForm = jsxc.options.loginForm && jsxc.options.loginForm.ifFound === 'force' && jsxc.isLoginForm();
// Check if we have to establish a new connection
if ((!isStorageAttachParameters && !isOptionsAttachParameters) || isForceLoginForm) {
// clean up rid and sid
jsxc.storage.removeItem('rid');
jsxc.storage.removeItem('sid');
// Looking for a login form
if (!jsxc.isLoginForm()) {
jsxc.changeState(jsxc.CONST.STATE.SUSPEND);
if (jsxc.options.displayRosterMinimized()) {
// Show minimized roster
jsxc.storage.setUserItem('roster', 'hidden');
jsxc.gui.roster.init();
jsxc.gui.roster.noConnection();
}
return;
}
jsxc.changeState(jsxc.CONST.STATE.TRYTOINTERCEPT);
if (typeof jsxc.options.formFound === 'function') {
jsxc.options.formFound.call();
}
// create jquery object
var form = jsxc.options.loginForm.form = $(jsxc.options.loginForm.form);
var events = form.data('events') || {
submit: []
};
var submits = [];
// save attached submit events and remove them. Will be reattached
// in jsxc.submitLoginForm
$.each(events.submit, function(index, val) {
submits.push(val.handler);
});
form.data('submits', submits);
form.off('submit');
// Add jsxc login action to form
form.submit(function(ev) {
ev.preventDefault();
jsxc.prepareLogin(function(settings) {
if (settings !== false) {
// settings.xmpp.onlogin is deprecated since v2.1.0
var enabled = (settings.loginForm && settings.loginForm.enable) || (settings.xmpp && settings.xmpp.onlogin);
enabled = enabled === "true" || enabled === true;
if (enabled) {
jsxc.options.loginForm.triggered = true;
jsxc.xmpp.login(jsxc.options.xmpp.jid, jsxc.options.xmpp.password);
return;
}
}
jsxc.submitLoginForm();
});
// Trigger submit in jsxc.xmpp.connected()
return false;
});
jsxc.changeState(jsxc.CONST.STATE.INTERCEPTED);
} else if (!jsxc.isLoginForm() || (jsxc.options.loginForm && jsxc.options.loginForm.ifFound === 'attach')) {
// Restore old connection
jsxc.changeState(jsxc.CONST.STATE.PREVCONFOUND);
if (typeof jsxc.storage.getItem('alive') === 'undefined') {
jsxc.onMaster();
} else {
jsxc.checkMaster();
}
}
},
/**
* Attach to previous session if jid, sid and rid are available
* in storage or options (default behaviour also for {@link jsxc.init}).
*
* @memberOf jsxc
*/
/**
* Start new chat session with given jid and password.
*
* @memberOf jsxc
* @param {string} jid Jabber Id
* @param {string} password Jabber password
*/
/**
* Attach to new chat session with jid, sid and rid.
*
* @memberOf jsxc
* @param {string} jid Jabber Id
* @param {string} sid Session Id
* @param {string} rid Request Id
*/
start: function() {
var args = arguments;
if (jsxc.role_allocation && !jsxc.master) {
jsxc.debug('There is an other master tab');
return false;
}
if (jsxc.xmpp.conn && jsxc.xmpp.connected) {
jsxc.debug('We are already connected');
return false;
}
if (args.length === 3) {
$(document).one('attached.jsxc', function() {
// save rid after first attachment
jsxc.xmpp.onRidChange(jsxc.xmpp.conn._proto.rid);
jsxc.onMaster();
});
}
jsxc.checkMaster(function() {
jsxc.xmpp.login.apply(this, args);
});
},
registerLogout: function() {
// Looking for logout element
if (jsxc.options.logoutElement !== null && $(jsxc.options.logoutElement).length > 0) {
var logout = function(ev) {
ev.stopPropagation();
ev.preventDefault();
jsxc.options.logoutElement = $(this);
jsxc.triggeredFromLogout = true;
jsxc.xmpp.logout();
};
jsxc.options.logoutElement = $(jsxc.options.logoutElement);
jsxc.options.logoutElement.off('click', null, logout).one('click', logout);
}
},
/**
* Returns true if login form is found.
*
* @memberOf jsxc
* @returns {boolean} True if login form was found.
*/
isLoginForm: function() {
return jsxc.options.loginForm.form && jsxc.el_exists(jsxc.options.loginForm.form) && jsxc.el_exists(jsxc.options.loginForm.jid) && jsxc.el_exists(jsxc.options.loginForm.pass);
},
/**
* Load settings and prepare jid.
*
* @memberOf jsxc
* @param {string} username
* @param {string} password
* @param {function} cb Called after login is prepared with result as param
*/
prepareLogin: function(username, password, cb) {
if (typeof username === 'function') {
cb = username;
username = null;
}
username = username || $(jsxc.options.loginForm.jid).val();
password = password || $(jsxc.options.loginForm.pass).val();
if (!jsxc.triggeredFromBox && (jsxc.options.loginForm.onConnecting === 'dialog' || typeof jsxc.options.loginForm.onConnecting === 'undefined')) {
jsxc.gui.showWaitAlert($.t('Logging_in'));
}
var settings;
if (typeof jsxc.options.loadSettings === 'function') {
settings = jsxc.options.loadSettings.call(this, username, password, function(s) {
jsxc._prepareLogin(username, password, cb, s);
});
if (typeof settings !== 'undefined') {
jsxc._prepareLogin(username, password, cb, settings);
}
} else {
jsxc._prepareLogin(username, password, cb);
}
},
/**
* Process xmpp settings and save loaded settings.
*
* @private
* @memberOf jsxc
* @param {string} username
* @param {string} password
* @param {function} cb Called after login is prepared with result as param
* @param {object} [loadedSettings] additonal options
*/
_prepareLogin: function(username, password, cb, loadedSettings) {
if (loadedSettings === false) {
jsxc.warn('No settings provided');
cb(false);
return;
}
// prevent to modify the original object
var settings = $.extend(true, {}, jsxc.options);
if (loadedSettings) {
// overwrite current options with loaded settings;
settings = $.extend(true, settings, loadedSettings);
} else {
loadedSettings = {};
}
if (typeof settings.xmpp.username === 'string') {
username = settings.xmpp.username;
}
var resource = (settings.xmpp.resource) ? '/' + settings.xmpp.resource : '';
var domain = settings.xmpp.domain;
var jid;
if (username.match(/@(.*)$/)) {
jid = (username.match(/\/(.*)$/)) ? username : username + resource;
} else {
jid = username + '@' + domain + resource;
}
if (typeof jsxc.options.loginForm.preJid === 'function') {
jid = jsxc.options.loginForm.preJid(jid);
}
jsxc.bid = jsxc.jidToBid(jid);
settings.xmpp.username = jid.split('@')[0];
settings.xmpp.domain = jid.split('@')[1].split('/')[0];
settings.xmpp.resource = jid.split('@')[1].split('/')[1] || "";
if (!loadedSettings.xmpp) {
// force xmpp settings to be saved to storage
loadedSettings.xmpp = {};
}
// save loaded settings to storage
$.each(loadedSettings, function(key) {
var old = jsxc.options.get(key);
var val = settings[key];
val = $.extend(true, old, val);
jsxc.options.set(key, val);
});
jsxc.options.xmpp.jid = jid;
jsxc.options.xmpp.password = password;
cb(settings);
},
/**
* Called if the script is a slave
*/
onSlave: function() {
jsxc.debug('I am the slave.');
jsxc.role_allocation = true;
jsxc.bid = jsxc.jidToBid(jsxc.storage.getItem('jid'));
jsxc.gui.init();
$('#jsxc_roster').removeClass('jsxc_noConnection');
jsxc.registerLogout();
jsxc.gui.updateAvatar($('#jsxc_roster > .jsxc_bottom'), jsxc.jidToBid(jsxc.storage.getItem('jid')), 'own');
jsxc.gui.restore();
},
/**
* Called if the script is the master
*/
onMaster: function() {
jsxc.debug('I am master.');
jsxc.master = true;
// Init local storage
jsxc.storage.setItem('alive', 0);
jsxc.storage.setItem('alive_busy', 0);
// Sending keepalive signal
jsxc.startKeepAlive();
jsxc.role_allocation = true;
jsxc.xmpp.login();
},
/**
* Checks if there is a master
*
* @param {function} [cb] Called if no master was found.
*/
checkMaster: function(cb) {
jsxc.debug('check master');
cb = (cb && typeof cb === 'function') ? cb : jsxc.onMaster;
if (typeof jsxc.storage.getItem('alive') === 'undefined') {
cb.call();
} else {
jsxc.to.push(window.setTimeout(cb, 1000));
jsxc.keepAlive('slave');
}
},
masterActions: function() {
if (!jsxc.xmpp.conn || !jsxc.xmpp.conn.authenticated) {
return;
}
//prepare notifications
var noti = jsxc.storage.getUserItem('notification');
noti = (typeof noti === 'number') ? noti : 2;
if (jsxc.options.notification && noti > 0 && jsxc.notification.hasSupport()) {
if (jsxc.notification.hasPermission()) {
jsxc.notification.init();
} else {
jsxc.notification.prepareRequest();
}
} else {
// No support => disable
jsxc.options.notification = false;
}
if (jsxc.options.get('otr').enable) {
// create or load DSA key
jsxc.otr.createDSA();
}
jsxc.gui.updateAvatar($('#jsxc_roster > .jsxc_bottom'), jsxc.jidToBid(jsxc.storage.getItem('jid')), 'own');
},
/**
* Start sending keep-alive signal
*/
startKeepAlive: function() {
jsxc.keepaliveInterval = window.setInterval(jsxc.keepAlive, jsxc.options.timeout - 1000);
},
/**
* Sends the keep-alive signal to signal that the master is still there.
*/
keepAlive: function(role) {
var next = parseInt(jsxc.storage.getItem('alive')) + 1;
role = role || 'master';
jsxc.storage.setItem('alive', next + ':' + role);
},
/**
* Send one keep-alive signal with higher timeout, and than resume with
* normal signal
*/
keepBusyAlive: function() {
if (jsxc.toBusy) {
window.clearTimeout(jsxc.toBusy);
}
if (jsxc.keepaliveInterval) {
window.clearInterval(jsxc.keepaliveInterval);
}
jsxc.storage.ink('alive_busy');
jsxc.toBusy = window.setTimeout(jsxc.startKeepAlive, jsxc.options.busyTimeout - 1000);
},
/**
* Generates a random integer number between 0 and max
*
* @param {Integer} max
* @return {Integer} random integer between 0 and max
*/
random: function(max) {
return Math.floor(Math.random() * max);
},
/**
* Checks if there is a element with the given selector
*
* @param {String} selector jQuery selector
* @return {Boolean}
*/
el_exists: function(selector) {
return $(selector).length > 0;
},
/**
* Creates a CSS compatible string from a JID
*
* @param {type} jid Valid Jabber ID
* @returns {String} css Compatible string
*/
jidToCid: function(jid) {
jsxc.warn('jsxc.jidToCid is deprecated!');
var cid = Strophe.getBareJidFromJid(jid).replace('@', '-').replace(/\./g, '-').toLowerCase();
return cid;
},
/**
* Create comparable bar jid.
*
* @memberOf jsxc
* @param jid
* @returns comparable bar jid
*/
jidToBid: function(jid) {
return Strophe.unescapeNode(Strophe.getBareJidFromJid(jid).toLowerCase());
},
/**
* Restore roster
*/
restoreRoster: function() {
var buddies = jsxc.storage.getUserItem('buddylist');
if (!buddies || buddies.length === 0) {
jsxc.debug('No saved buddylist.');
jsxc.gui.roster.empty();
return;
}
$.each(buddies, function(index, value) {
jsxc.gui.roster.add(value);
});
jsxc.gui.roster.loaded = true;
$(document).trigger('cloaded.roster.jsxc');
},
/**
* Restore all windows
*/
restoreWindows: function() {
var windows = jsxc.storage.getUserItem('windowlist');
if (windows === null) {
return;
}
$.each(windows, function(index, bid) {
var win = jsxc.storage.getUserItem('window', bid);
if (!win) {
jsxc.debug('Associated window-element is missing: ' + bid);
return true;
}
jsxc.gui.window.init(bid);
if (!win.minimize) {
jsxc.gui.window.show(bid);
} else {
jsxc.gui.window.hide(bid);
}
jsxc.gui.window.setText(bid, win.text);
});
},
/**
* This method submits the specified login form.
*/
submitLoginForm: function() {
var form = $(jsxc.options.loginForm.form).off('submit');
// Attach original events
var submits = form.data('submits') || [];
$.each(submits, function(index, val) {
form.submit(val);
});
if (form.find('#submit').length > 0) {
form.find('#submit').click();
} else if (form.get(0) && typeof form.get(0).submit === 'function') {
form.submit();
} else if (form.find('[type="submit"]').length > 0) {
form.find('[type="submit"]').click();
} else {
jsxc.warn('Could not submit login form.');
}
},
/**
* Escapes some characters to HTML character
*/
escapeHTML: function(text) {
text = text.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>');
return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
},
/**
* Removes all html tags.
*
* @memberOf jsxc
* @param text
* @returns stripped text
*/
removeHTML: function(text) {
return $('<span>').html(text).text();
},
/**
* Executes only one of the given events
*
* @param {string} obj.key event name
* @param {function} obj.value function to execute
* @returns {string} namespace of all events
*/
switchEvents: function(obj) {
var ns = Math.random().toString(36).substr(2, 12);
var self = this;
$.each(obj, function(key, val) {
$(document).one(key + '.' + ns, function() {
$(document).off('.' + ns);
val.apply(self, arguments);
});
});
return ns;
},
/**
* Checks if tab is hidden.
*
* @returns {boolean} True if tab is hidden
*/
isHidden: function() {
var hidden = false;
if (typeof document.hidden !== 'undefined') {
hidden = document.hidden;
} else if (typeof document.webkitHidden !== 'undefined') {
hidden = document.webkitHidden;
} else if (typeof document.mozHidden !== 'undefined') {
hidden = document.mozHidden;
} else if (typeof document.msHidden !== 'undefined') {
hidden = document.msHidden;
}
// handle multiple tabs
if (hidden && jsxc.master) {
jsxc.storage.ink('hidden', 0);
} else if (!hidden && !jsxc.master) {
jsxc.storage.ink('hidden');
}
return hidden;
},
/**
* Checks if tab has focus.
*
* @returns {boolean} True if tabs has focus
*/
hasFocus: function() {
var focus = true;
if (typeof document.hasFocus === 'function') {
focus = document.hasFocus();
}
if (!focus && jsxc.master) {
jsxc.storage.ink('focus', 0);
} else if (focus && !jsxc.master) {
jsxc.storage.ink('focus');
}
return focus;
},
/**
* Executes the given function in jsxc namespace.
*
* @memberOf jsxc
* @param {string} fnName Function name
* @param {array} fnParams Function parameters
* @returns Function return value
*/
exec: function(fnName, fnParams) {
var fnList = fnName.split('.');
var fn = jsxc[fnList[0]];
var i;
for (i = 1; i < fnList.length; i++) {
fn = fn[fnList[i]];
}
if (typeof fn === 'function') {
return fn.apply(null, fnParams);
}
},
/**
* Hash string into 32-bit signed integer.
*
* @memberOf jsxc
* @param {string} str input string
* @returns {integer} 32-bit signed integer
*/
hashStr: function(str) {
var hash = 0,
i;
if (str.length === 0) {
return hash;
}
for (i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0; // Convert to 32bit integer
}
return hash;
},
isExtraSmallDevice: function() {
return $(window).width() < 500;
},
changeState: function(state) {
jsxc.currentState = state;
jsxc.debug('State changed to ' + Object.keys(jsxc.CONST.STATE)[state]);
$(document).trigger('stateChange.jsxc', state);
},
changeUIState: function(state) {
jsxc.currentUIState = state;
jsxc.debug('UI State changed to ' + Object.keys(jsxc.CONST.UISTATE)[state]);
$(document).trigger('stateUIChange.jsxc', state);
}
};
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
-153
Ver Arquivo
@@ -1,153 +0,0 @@
/**
* This namespace handle the notice system.
*
* @namspace jsxc.notice
* @memberOf jsxc
*/
jsxc.notice = {
/** Number of notices. */
_num: 0,
/**
* Loads the saved notices.
*
* @memberOf jsxc.notice
*/
load: function() {
// reset list
$('#jsxc_notice ul li').remove();
$('#jsxc_notice > span').text('');
jsxc.notice._num = 0;
var saved = jsxc.storage.getUserItem('notices') || [];
var key = null;
for (key in saved) {
if (saved.hasOwnProperty(key)) {
var val = saved[key];
jsxc.notice.add(val, val.fnName, val.fnParams, key);
}
}
},
/**
* Add a new notice to the stack;
*
* @memberOf jsxc.notice
* @param {Object} data
* @param {String} data.msg Header message
* @param {String} data.description Notice description
* @param {String} fnName Function name to be called if you open the notice
* @param fnParams Array of params for function
* @param {String} id Notice id
*/
add: function(data, fnName, fnParams, id) {
var nid = id || Date.now();
var list = $('#jsxc_notice ul');
var notice = $('<li/>');
var msg = data.msg;
var description = data.description;
notice.click(function() {
jsxc.notice.remove(nid);
jsxc.exec(fnName, fnParams);
return false;
});
if (data.type) {
notice.addClass('jsxc_' + data.type + 'icon');
}
notice.text(msg);
notice.attr('title', description || '');
notice.attr('data-nid', nid);
list.append(notice);
$('#jsxc_notice > span').text(++jsxc.notice._num);
var saved = jsxc.storage.getUserItem('notices') || {};
if (!id) {
saved[nid] = {
msg: msg,
description: description,
type: data.type,
fnName: fnName,
fnParams: fnParams
};
jsxc.storage.setUserItem('notices', saved);
jsxc.notification.notify(msg, description || '', null, true, jsxc.CONST.SOUNDS.NOTICE);
}
if (Object.keys(saved).length > 3 && list.find('.jsxc_closeAll').length === 0) {
// add close all button
var closeAll = $('<li>');
closeAll.addClass('jsxc_closeAll jsxc_deleteicon jsxc_warning');
closeAll.text($.t('Close_all'));
closeAll.prependTo(list);
closeAll.click(jsxc.notice.removeAll);
} else if (Object.keys(saved).length <= 3 && list.find('.jsxc_closeAll').length !== 0) {
// remove close all button
list.find('.jsxc_closeAll').remove();
}
},
/**
* Removes notice from stack
*
* @memberOf jsxc.notice
* @param nid The notice id
*/
remove: function(nid) {
var el = $('#jsxc_notice li[data-nid=' + nid + ']');
el.remove();
$('#jsxc_notice > span').text(--jsxc.notice._num || '');
var s = jsxc.storage.getUserItem('notices') || {};
delete s[nid];
jsxc.storage.setUserItem('notices', s);
if (Object.keys(s).length <= 3 && $('#jsxc_notice .jsxc_closeAll').length !== 0) {
// remove close all button
$('#jsxc_notice .jsxc_closeAll').remove();
}
},
/**
* Remove all notices.
*/
removeAll: function() {
jsxc.notice._num = 0;
jsxc.storage.setUserItem('notices', {});
$('#jsxc_notice ul').empty();
$('#jsxc_notice > span').text('');
},
/**
* Check if there is already a notice for the given function name.
*
* @memberOf jsxc.notice
* @param {string} fnName Function name
* @returns {boolean} True if there is >0 functions with the given name
*/
has: function(fnName) {
var saved = jsxc.storage.getUserItem('notices') || [];
var has = false;
$.each(saved, function(index, val) {
if (val.fnName === fnName) {
has = true;
return false;
}
});
return has;
}
};
-277
Ver Arquivo
@@ -1,277 +0,0 @@
/**
* This namespace handles the Notification API.
*
* @namespace jsxc.notification
*/
jsxc.notification = {
/** Current audio file. */
audio: null,
/**
* Register notification on incoming messages.
*
* @memberOf jsxc.notification
*/
init: function() {
$(document).on('postmessagein.jsxc', function(event, bid, msg) {
msg = (msg && msg.match(/^\?OTR/)) ? $.t('Encrypted_message') : msg;
var data = jsxc.storage.getUserItem('buddy', bid);
jsxc.notification.notify({
title: $.t('New_message_from', {
name: data.name
}),
msg: msg,
soundFile: jsxc.CONST.SOUNDS.MSG,
source: bid
});
});
$(document).on('callincoming.jingle', function() {
jsxc.notification.playSound(jsxc.CONST.SOUNDS.CALL, true, true);
});
$(document).on('accept.call.jsxc reject.call.jsxc', function() {
jsxc.notification.stopSound();
});
},
/**
* Shows a pop up notification and optional play sound.
*
* @param title Title
* @param msg Message
* @param d Duration
* @param force Should message also shown, if tab is visible?
* @param soundFile Playing given sound file
* @param loop Loop sound file?
* @param source Bid which triggered this notification
*/
notify: function(title, msg, d, force, soundFile, loop, source) {
if (!jsxc.options.notification || !jsxc.notification.hasPermission()) {
return; // notifications disabled
}
var o;
if (title !== null && typeof title === 'object') {
o = title;
} else {
o = {
title: title,
msg: msg,
duration: d,
force: force,
soundFile: soundFile,
loop: loop,
source: source
};
}
if (jsxc.hasFocus() && !o.force) {
return; // Tab is visible
}
var icon = o.icon || jsxc.options.root + '/img/XMPP_logo.png';
if (typeof o.source === 'string') {
var data = jsxc.storage.getUserItem('buddy', o.source);
var src = jsxc.storage.getUserItem('avatar', data.avatar);
if (typeof src === 'string' && src !== '0') {
icon = src;
}
}
jsxc.toNotification = setTimeout(function() {
if (typeof o.soundFile === 'string') {
jsxc.notification.playSound(o.soundFile, o.loop, o.force);
}
var popup = new Notification($.t(o.title), {
body: $.t(o.msg),
icon: icon
});
var duration = o.duration || jsxc.options.popupDuration;
if (duration > 0) {
setTimeout(function() {
popup.close();
}, duration);
}
}, jsxc.toNotificationDelay);
},
/**
* Checks if browser has support for notifications and add on chrome to the
* default api.
*
* @returns {Boolean} True if the browser has support.
*/
hasSupport: function() {
if (window.webkitNotifications) {
// prepare chrome
window.Notification = function(title, opt) {
var popup = window.webkitNotifications.createNotification(null, title, opt.body);
popup.show();
popup.close = function() {
popup.cancel();
};
return popup;
};
var permission;
switch (window.webkitNotifications.checkPermission()) {
case 0:
permission = jsxc.CONST.NOTIFICATION_GRANTED;
break;
case 2:
permission = jsxc.CONST.NOTIFICATION_DENIED;
break;
default: // 1
permission = jsxc.CONST.NOTIFICATION_DEFAULT;
}
window.Notification.permission = permission;
window.Notification.requestPermission = function(func) {
window.webkitNotifications.requestPermission(func);
};
return true;
} else if (window.Notification) {
return true;
} else {
return false;
}
},
/**
* Ask user on first incoming message if we should inform him about new
* messages.
*/
prepareRequest: function() {
if (jsxc.notice.has('gui.showRequestNotification')) {
return;
}
$(document).one('postmessagein.jsxc', function() {
setTimeout(function() {
jsxc.notice.add({
msg: $.t('Notifications') + '?',
description: $.t('Should_we_notify_you_')
}, 'gui.showRequestNotification');
}, 1000);
});
},
/**
* Request notification permission.
*/
requestPermission: function() {
window.Notification.requestPermission(function(status) {
if (window.Notification.permission !== status) {
window.Notification.permission = status;
}
if (jsxc.notification.hasPermission()) {
$(document).trigger('notificationready.jsxc');
} else {
$(document).trigger('notificationfailure.jsxc');
}
});
},
/**
* Check permission.
*
* @returns {Boolean} True if we have the permission
*/
hasPermission: function() {
return window.Notification.permission === jsxc.CONST.NOTIFICATION_GRANTED;
},
/**
* Plays the given file.
*
* @memberOf jsxc.notification
* @param {string} soundFile File relative to the sound directory
* @param {boolean} loop True for loop
* @param {boolean} force Play even if a tab is visible. Default: false.
*/
playSound: function(soundFile, loop, force) {
if (!jsxc.master) {
// only master plays sound
return;
}
if (jsxc.options.get('muteNotification') || jsxc.storage.getUserItem('presence') === 'dnd') {
// sound mute or own presence is dnd
return;
}
if (jsxc.hasFocus() && !force) {
// tab is visible
return;
}
// stop current audio file
jsxc.notification.stopSound();
var audio = new Audio(jsxc.options.root + '/sound/' + soundFile);
audio.loop = loop || false;
audio.play();
jsxc.notification.audio = audio;
},
/**
* Stop/remove current sound.
*
* @memberOf jsxc.notification
*/
stopSound: function() {
var audio = jsxc.notification.audio;
if (typeof audio !== 'undefined' && audio !== null) {
audio.pause();
jsxc.notification.audio = null;
}
},
/**
* Mute sound.
*
* @memberOf jsxc.notification
* @param {boolean} external True if triggered from external tab. Default:
* false.
*/
muteSound: function(external) {
$('#jsxc_menu .jsxc_muteNotification').text($.t('Unmute'));
if (external !== true) {
jsxc.options.set('muteNotification', true);
}
},
/**
* Unmute sound.
*
* @memberOf jsxc.notification
* @param {boolean} external True if triggered from external tab. Default:
* false.
*/
unmuteSound: function(external) {
$('#jsxc_menu .jsxc_muteNotification').text($.t('Mute'));
if (external !== true) {
jsxc.options.set('muteNotification', false);
}
}
};
-532
Ver Arquivo
@@ -1,532 +0,0 @@
/**
* @namespace jsxc.otr
*/
jsxc.otr = {
/** list of otr objects */
objects: {},
dsaFallback: null,
/**
* Handler for otr receive event
*
* @memberOf jsxc.otr
* @param {Object} d
* @param {string} d.bid
* @param {string} d.msg received message
* @param {boolean} d.encrypted True, if msg was encrypted.
* @param {boolean} d.forwarded
* @param {string} d.stamp timestamp
*/
receiveMessage: function(d) {
var bid = d.bid;
if (jsxc.otr.objects[bid].msgstate !== OTR.CONST.MSGSTATE_PLAINTEXT) {
jsxc.otr.backup(bid);
}
if (jsxc.otr.objects[bid].msgstate !== OTR.CONST.MSGSTATE_PLAINTEXT && !d.encrypted) {
jsxc.gui.window.postMessage({
bid: bid,
direction: jsxc.Message.SYS,
msg: $.t('Received_an_unencrypted_message') + '. [' + d.msg + ']',
encrypted: d.encrypted,
forwarded: d.forwarded,
stamp: d.stamp
});
} else {
jsxc.gui.window.postMessage({
bid: bid,
direction: jsxc.Message.IN,
msg: d.msg,
encrypted: d.encrypted,
forwarded: d.forwarded,
stamp: d.stamp,
attachment: d.attachment
});
}
},
/**
* Handler for otr send event
*
* @param {string} jid
* @param {string} msg message to be send
*/
sendMessage: function(jid, msg, message) {
if (jsxc.otr.objects[jsxc.jidToBid(jid)].msgstate !== 0) {
jsxc.otr.backup(jsxc.jidToBid(jid));
}
jsxc.xmpp._sendMessage(jid, msg, message);
},
/**
* Create new otr instance
*
* @param {type} bid
* @returns {undefined}
*/
create: function(bid) {
if (jsxc.otr.objects.hasOwnProperty(bid)) {
return;
}
if (!jsxc.options.otr.priv) {
return;
}
// save list of otr objects
var ol = jsxc.storage.getUserItem('otrlist') || [];
if (ol.indexOf(bid) < 0) {
ol.push(bid);
jsxc.storage.setUserItem('otrlist', ol);
}
jsxc.otr.objects[bid] = new OTR(jsxc.options.otr);
if (jsxc.options.otr.SEND_WHITESPACE_TAG) {
jsxc.otr.objects[bid].SEND_WHITESPACE_TAG = true;
}
if (jsxc.options.otr.WHITESPACE_START_AKE) {
jsxc.otr.objects[bid].WHITESPACE_START_AKE = true;
}
jsxc.otr.objects[bid].on('status', function(status) {
var data = jsxc.storage.getUserItem('buddy', bid);
if (data === null) {
return;
}
switch (status) {
case OTR.CONST.STATUS_SEND_QUERY:
jsxc.gui.window.postMessage({
bid: bid,
direction: jsxc.Message.SYS,
msg: $.t('trying_to_start_private_conversation')
});
break;
case OTR.CONST.STATUS_AKE_SUCCESS:
data.fingerprint = jsxc.otr.objects[bid].their_priv_pk.fingerprint();
data.msgstate = OTR.CONST.MSGSTATE_ENCRYPTED;
var msg_state = jsxc.otr.objects[bid].trust ? 'Verified' : 'Unverified';
var msg = $.t(msg_state + '_private_conversation_started');
jsxc.gui.window.postMessage({
bid: bid,
direction: 'sys',
msg: msg
});
break;
case OTR.CONST.STATUS_END_OTR:
data.fingerprint = null;
if (jsxc.otr.objects[bid].msgstate === OTR.CONST.MSGSTATE_PLAINTEXT) {
// we abort the private conversation
data.msgstate = OTR.CONST.MSGSTATE_PLAINTEXT;
jsxc.gui.window.postMessage({
bid: bid,
direction: jsxc.Message.SYS,
msg: $.t('private_conversation_aborted')
});
} else {
// the buddy abort the private conversation
data.msgstate = OTR.CONST.MSGSTATE_FINISHED;
jsxc.gui.window.postMessage({
bid: bid,
direction: jsxc.Message.SYS,
msg: $.t('your_buddy_closed_the_private_conversation_you_should_do_the_same')
});
}
break;
case OTR.CONST.STATUS_SMP_HANDLE:
jsxc.keepBusyAlive();
break;
}
jsxc.storage.setUserItem('buddy', bid, data);
// for encryption and verification state
jsxc.gui.update(bid);
});
jsxc.otr.objects[bid].on('smp', function(type, data) {
switch (type) {
case 'question': // verification request received
jsxc.gui.window.postMessage({
bid: bid,
direction: jsxc.Message.SYS,
msg: $.t('Authentication_request_received')
});
jsxc.gui.window.smpRequest(bid, data);
jsxc.storage.setUserItem('smp', bid, {
data: data || null
});
break;
case 'trust': // verification completed
jsxc.otr.objects[bid].trust = data;
jsxc.storage.updateUserItem('buddy', bid, 'trust', data);
jsxc.otr.backup(bid);
jsxc.gui.update(bid);
if (data) {
jsxc.gui.window.postMessage({
bid: bid,
direction: jsxc.Message.SYS,
msg: $.t('conversation_is_now_verified')
});
} else {
jsxc.gui.window.postMessage({
bid: bid,
direction: jsxc.Message.SYS,
msg: $.t('authentication_failed')
});
}
jsxc.storage.removeUserItem('smp', bid);
jsxc.gui.dialog.close('smp');
break;
case 'abort':
jsxc.gui.window.hideOverlay(bid);
jsxc.gui.window.postMessage({
bid: bid,
direction: jsxc.Message.SYS,
msg: $.t('Authentication_aborted')
});
break;
default:
jsxc.debug('[OTR] sm callback: Unknown type: ' + type);
}
});
// Receive message
jsxc.otr.objects[bid].on('ui', function(msg, encrypted, meta) {
jsxc.otr.receiveMessage({
bid: bid,
msg: msg,
encrypted: encrypted === true,
stamp: meta.stamp,
forwarded: meta.forwarded,
attachment: meta.attachment
});
});
// Send message
jsxc.otr.objects[bid].on('io', function(msg, message) {
var jid = jsxc.gui.window.get(bid).data('jid') || jsxc.otr.objects[bid].jid;
jsxc.otr.objects[bid].jid = jid;
jsxc.otr.sendMessage(jid, msg, message);
});
jsxc.otr.objects[bid].on('error', function(err) {
// Handle this case in jsxc.otr.receiveMessage
if (err !== 'Received an unencrypted message.') {
jsxc.gui.window.postMessage({
bid: bid,
direction: jsxc.Message.SYS,
msg: '[OTR] ' + $.t(err)
});
}
jsxc.error('[OTR] ' + err);
});
jsxc.otr.restore(bid);
},
/**
* show verification dialog with related part (secret or question)
*
* @param {type} bid
* @param {string} [data]
* @returns {undefined}
*/
onSmpQuestion: function(bid, data) {
jsxc.gui.showVerification(bid);
$('#jsxc_dialog select').prop('selectedIndex', (data ? 2 : 3)).change();
$('#jsxc_dialog > div:eq(0)').hide();
if (data) {
$('#jsxc_dialog > div:eq(2)').find('#jsxc_quest').val(data).prop('disabled', true);
$('#jsxc_dialog > div:eq(2)').find('.jsxc_submit').text($.t('Answer'));
$('#jsxc_dialog > div:eq(2)').find('.jsxc_explanation').text($.t('onsmp_explanation_question'));
$('#jsxc_dialog > div:eq(2)').show();
} else {
$('#jsxc_dialog > div:eq(3)').find('.jsxc_explanation').text($.t('onsmp_explanation_secret'));
$('#jsxc_dialog > div:eq(3)').show();
}
$('#jsxc_dialog .jsxc_close').click(function() {
jsxc.storage.removeUserItem('smp', bid);
if (jsxc.master) {
jsxc.otr.objects[bid].sm.abort();
}
});
},
/**
* Send verification request to buddy
*
* @param {string} bid
* @param {string} sec secret
* @param {string} [quest] question
* @returns {undefined}
*/
sendSmpReq: function(bid, sec, quest) {
jsxc.keepBusyAlive();
jsxc.otr.objects[bid].smpSecret(sec, quest || '');
},
/**
* Toggle encryption state
*
* @param {type} bid
* @returns {undefined}
*/
toggleTransfer: function(bid) {
if (typeof OTR !== 'function') {
return;
}
if (jsxc.storage.getUserItem('buddy', bid).msgstate === 0) {
jsxc.otr.goEncrypt(bid);
} else {
jsxc.otr.goPlain(bid);
}
},
/**
* Send request to encrypt the session
*
* @param {type} bid
* @returns {undefined}
*/
goEncrypt: function(bid) {
if (jsxc.master) {
if (jsxc.otr.objects.hasOwnProperty(bid)) {
jsxc.otr.objects[bid].sendQueryMsg();
}
} else {
jsxc.storage.updateUserItem('buddy', bid, 'transferReq', 1);
}
},
/**
* Abort encryptet session
*
* @param {type} bid
* @param cb callback
* @returns {undefined}
*/
goPlain: function(bid, cb) {
if (jsxc.master) {
if (jsxc.otr.objects.hasOwnProperty(bid)) {
jsxc.otr.objects[bid].endOtr.call(jsxc.otr.objects[bid], cb);
jsxc.otr.objects[bid].init.call(jsxc.otr.objects[bid]);
jsxc.otr.backup(bid);
}
} else {
jsxc.storage.updateUserItem('buddy', bid, 'transferReq', 0);
}
},
/**
* Backups otr session
*
* @param {string} bid
*/
backup: function(bid) {
var o = jsxc.otr.objects[bid]; // otr object
var r = {}; // return value
if (o === null) {
return;
}
// all variables which should be saved
var savekey = ['jid', 'our_instance_tag', 'msgstate', 'authstate', 'fragment', 'their_y', 'their_old_y', 'their_keyid', 'their_instance_tag', 'our_dh', 'our_old_dh', 'our_keyid', 'sessKeys', 'storedMgs', 'oldMacKeys', 'trust', 'transmittedRS', 'ssid', 'receivedPlaintext', 'authstate', 'send_interval'];
var i;
for (i = 0; i < savekey.length; i++) {
r[savekey[i]] = JSON.stringify(o[savekey[i]]);
}
if (o.their_priv_pk !== null) {
r.their_priv_pk = JSON.stringify(o.their_priv_pk.packPublic());
}
if (o.ake.otr_version && o.ake.otr_version !== '') {
r.otr_version = JSON.stringify(o.ake.otr_version);
}
jsxc.storage.setUserItem('otr', bid, r);
},
/**
* Restore old otr session
*
* @param {string} bid
*/
restore: function(bid) {
var o = jsxc.otr.objects[bid];
var d = jsxc.storage.getUserItem('otr', bid);
if (o !== null || d !== null) {
var key;
for (key in d) {
if (d.hasOwnProperty(key)) {
var val = JSON.parse(d[key]);
if (key === 'their_priv_pk' && val !== null) {
val = DSA.parsePublic(val);
}
if (key === 'otr_version' && val !== null) {
o.ake.otr_version = val;
} else {
o[key] = val;
}
}
}
jsxc.otr.objects[bid] = o;
if (o.msgstate === 1 && o.their_priv_pk !== null) {
o._smInit.call(jsxc.otr.objects[bid]);
}
}
jsxc.otr.enable(bid);
},
/**
* Create or load DSA key
*
* @returns {unresolved}
*/
createDSA: function() {
if (jsxc.options.otr.priv) {
return;
}
if (typeof OTR !== 'function') {
jsxc.warn('OTR support disabled');
OTR = {};
OTR.CONST = {
MSGSTATE_PLAINTEXT: 0,
MSGSTATE_ENCRYPTED: 1,
MSGSTATE_FINISHED: 2
};
return;
}
if (jsxc.storage.getUserItem('key') === null) {
var msg = $.t('Creating_your_private_key_');
var worker = null;
if (Worker) {
// try to create web-worker
try {
worker = new Worker(jsxc.options.root + '/lib/otr/lib/dsa-webworker.js');
} catch (err) {
jsxc.warn('Couldn\'t create web-worker.', err);
}
}
jsxc.otr.dsaFallback = (worker === null);
if (!jsxc.otr.dsaFallback) {
// create DSA key in background
worker.onmessage = function(e) {
var type = e.data.type;
var val = e.data.val;
if (type === 'debug') {
jsxc.debug(val);
} else if (type === 'data') {
jsxc.otr.DSAready(DSA.parsePrivate(val));
}
};
jsxc.debug('DSA key creation started.');
// start worker
worker.postMessage({
imports: [jsxc.options.root + '/lib/otr/vendor/salsa20.js', jsxc.options.root + '/lib/otr/vendor/bigint.js', jsxc.options.root + '/lib/otr/vendor/crypto.js', jsxc.options.root + '/lib/otr/vendor/eventemitter.js', jsxc.options.root + '/lib/otr/lib/const.js', jsxc.options.root + '/lib/otr/lib/helpers.js', jsxc.options.root + '/lib/otr/lib/dsa.js'],
seed: BigInt.getSeed(),
debug: true
});
} else {
// fallback
jsxc.xmpp.conn.pause();
jsxc.gui.dialog.open(jsxc.gui.template.get('waitAlert', null, msg), {
noClose: true
});
jsxc.debug('DSA key creation started in fallback mode.');
// wait until the wait alert is opened
setTimeout(function() {
var dsa = new DSA();
jsxc.otr.DSAready(dsa);
}, 500);
}
} else {
jsxc.debug('DSA key loaded');
jsxc.options.otr.priv = DSA.parsePrivate(jsxc.storage.getUserItem('key'));
jsxc.otr._createDSA();
}
},
/**
* Ending of createDSA().
*/
_createDSA: function() {
jsxc.storage.setUserItem('priv_fingerprint', jsxc.options.otr.priv.fingerprint());
$.each(jsxc.storage.getUserItem('windowlist') || [], function(index, val) {
jsxc.otr.create(val);
});
},
/**
* Ending of DSA key generation.
*
* @param {DSA} dsa DSA object
*/
DSAready: function(dsa) {
jsxc.storage.setUserItem('key', dsa.packPrivate());
jsxc.options.otr.priv = dsa;
// close wait alert
if (jsxc.otr.dsaFallback) {
jsxc.xmpp.conn.resume();
jsxc.gui.dialog.close();
}
jsxc.otr._createDSA();
},
enable: function(bid) {
jsxc.gui.window.get(bid).find('.jsxc_otr').removeClass('jsxc_disabled');
}
};
-618
Ver Arquivo
@@ -1,618 +0,0 @@
/**
* Handle long-live data
*
* @namespace jsxc.storage
*/
jsxc.storage = {
/**
* Prefix for localstorage
*
* @privat
*/
PREFIX: 'jsxc',
SEP: ':',
/**
* @param {type} uk Should we generate a user prefix?
* @returns {String} prefix
* @memberOf jsxc.storage
*/
getPrefix: function(uk) {
var self = jsxc.storage;
if (uk && !jsxc.bid) {
jsxc.warn('Unable to create user prefix');
}
return self.PREFIX + self.SEP + ((uk && jsxc.bid) ? jsxc.bid + self.SEP : '');
},
/**
* Save item to storage
*
* @function
* @param {String} key variablename
* @param {Object} value value
* @param {String} uk Userkey? Should we add the bid as prefix?
*/
setItem: function(key, value, uk) {
// Workaround for non-conform browser
if (jsxc.storageNotConform > 0 && key !== 'rid') {
if (jsxc.storageNotConform > 1 && jsxc.toSNC === null) {
jsxc.toSNC = window.setTimeout(function() {
jsxc.storageNotConform = 0;
jsxc.storage.setItem('storageNotConform', 0);
}, 1000);
}
jsxc.ls.push(JSON.stringify({
key: key,
value: value
}));
}
if (typeof(value) === 'object') {
// exclude jquery objects, because otherwise safari will fail
value = JSON.stringify(value, function(key, val) {
if (!(val instanceof jQuery)) {
return val;
}
});
}
localStorage.setItem(jsxc.storage.getPrefix(uk) + key, value);
},
setUserItem: function(type, key, value) {
var self = jsxc.storage;
if (arguments.length === 2) {
value = key;
key = type;
type = '';
} else if (arguments.length === 3) {
key = type + self.SEP + key;
}
return jsxc.storage.setItem(key, value, true);
},
/**
* Load item from storage
*
* @function
* @param {String} key variablename
* @param {String} uk Userkey? Should we add the bid as prefix?
*/
getItem: function(key, uk) {
key = jsxc.storage.getPrefix(uk) + key;
var value = localStorage.getItem(key);
try {
return JSON.parse(value);
} catch (e) {
return value;
}
},
/**
* Get a user item from storage.
*
* @param key
* @returns user item
*/
getUserItem: function(type, key) {
var self = jsxc.storage;
if (arguments.length === 1) {
key = type;
} else if (arguments.length === 2) {
key = type + self.SEP + key;
}
return jsxc.storage.getItem(key, true);
},
/**
* Remove item from storage
*
* @function
* @param {String} key variablename
* @param {String} uk Userkey? Should we add the bid as prefix?
*/
removeItem: function(key, uk) {
// Workaround for non-conforming browser
if (jsxc.storageNotConform && key !== 'rid') {
jsxc.ls.push(JSON.stringify({
key: jsxc.storage.prefix + key,
value: ''
}));
}
localStorage.removeItem(jsxc.storage.getPrefix(uk) + key);
},
/**
* Remove user item from storage.
*
* @param key
*/
removeUserItem: function(type, key) {
var self = jsxc.storage;
if (arguments.length === 1) {
key = type;
} else if (arguments.length === 2) {
key = type + self.SEP + key;
}
jsxc.storage.removeItem(key, true);
},
/**
* Updates value of a variable in a saved object.
*
* @function
* @param {String} key variablename
* @param {String|object} variable variablename in object or object with
* variable/key pairs
* @param {Object} [value] value
* @param {String} uk Userkey? Should we add the bid as prefix?
*/
updateItem: function(key, variable, value, uk) {
var data = jsxc.storage.getItem(key, uk) || {};
if (typeof(variable) === 'object') {
$.each(variable, function(key, val) {
if (typeof(data[key]) === 'undefined') {
jsxc.debug('Variable ' + key + ' doesn\'t exist in ' + variable + '. It was created.');
}
data[key] = val;
});
} else {
if (typeof(data[variable]) === 'undefined') {
jsxc.debug('Variable ' + variable + ' doesn\'t exist. It was created.');
}
data[variable] = value;
}
jsxc.storage.setItem(key, data, uk);
},
/**
* Updates value of a variable in a saved user object.
*
* @param {String} type variable type (a prefix)
* @param {String} key variable name
* @param {String|object} variable variable name in object or object with
* variable/key pairs
* @param {Object} [value] value (not used if the variable was an object)
*/
updateUserItem: function(type, key, variable, value) {
var self = jsxc.storage;
if (arguments.length === 4 || (arguments.length === 3 && typeof variable === 'object')) {
key = type + self.SEP + key;
} else {
value = variable;
variable = key;
key = type;
}
return jsxc.storage.updateItem(key, variable, value, true);
},
/**
* Increments value
*
* @function
* @param {String} key variablename
* @param {String} uk Userkey? Should we add the bid as prefix?
*/
ink: function(key, uk) {
jsxc.storage.setItem(key, Number(jsxc.storage.getItem(key, uk)) + 1, uk);
},
/**
* Remove element from array or object
*
* @param {string} key name of array or object
* @param {string} name name of element in array or object
* @param {String} uk Userkey? Should we add the bid as prefix?
* @returns {undefined}
*/
removeElement: function(key, name, uk) {
var item = jsxc.storage.getItem(key, uk);
if ($.isArray(item)) {
item = $.grep(item, function(e) {
return e !== name;
});
} else if (typeof(item) === 'object' && item !== null) {
delete item[name];
}
jsxc.storage.setItem(key, item, uk);
},
removeUserElement: function(type, key, name) {
var self = jsxc.storage;
if (arguments.length === 2) {
name = key;
key = type;
} else if (arguments.length === 3) {
key = type + self.SEP + key;
}
return jsxc.storage.removeElement(key, name, true);
},
/**
* Triggered if changes are recognized
*
* @function
* @param {event} e Storage event
* @param {String} e.key Key name which triggered event
* @param {Object} e.oldValue Old Value for key
* @param {Object} e.newValue New Value for key
* @param {String} e.url
*/
onStorage: function(e) {
// skip
if (e.key === jsxc.storage.PREFIX + jsxc.storage.SEP + 'rid' || !e.key) {
return;
}
var re = new RegExp('^' + jsxc.storage.PREFIX + jsxc.storage.SEP + '(?:[^' + jsxc.storage.SEP + ']+@[^' + jsxc.storage.SEP + ']+' + jsxc.storage.SEP + ')?(.*)', 'i');
var key = e.key.replace(re, '$1');
// Workaround for non-conforming browser, which trigger
// events on every page (notably IE): Ignore own writes
// (own)
if (jsxc.storageNotConform > 0 && jsxc.ls.length > 0) {
var val = e.newValue;
try {
val = JSON.parse(val);
} catch (err) {}
var index = $.inArray(JSON.stringify({
key: key,
value: val
}), jsxc.ls);
if (index >= 0) {
// confirm that the storage event is not fired regularly
if (jsxc.storageNotConform > 1) {
window.clearTimeout(jsxc.toSNC);
jsxc.storageNotConform = 1;
jsxc.storage.setItem('storageNotConform', 1);
}
jsxc.ls.splice(index, 1);
return;
}
}
// Workaround for non-conforming browser
if (e.oldValue === e.newValue) {
return;
}
var n, o;
var bid = key.replace(new RegExp('[^' + jsxc.storage.SEP + ']+' + jsxc.storage.SEP + '(.*)', 'i'), '$1');
// react if someone asks whether there is a master
if (jsxc.master && key === 'alive') {
jsxc.debug('Master request.');
if (e.newValue && e.newValue.match(/:master$/)) {
jsxc.warn('Master request from master. Something went wrong... :-(');
return;
}
jsxc.keepAlive();
return;
}
// master alive
if (!jsxc.master && (key === 'alive' || key === 'alive_busy')) {
// reset timeouts
jsxc.to = $.grep(jsxc.to, function(timeout) {
window.clearTimeout(timeout);
return false;
});
if (typeof e.newValue === 'undefined' || e.newValue === null) {
jsxc.xmpp.disconnected();
return;
}
jsxc.to.push(window.setTimeout(jsxc.checkMaster, ((key === 'alive') ? jsxc.options.timeout : jsxc.options.busyTimeout) + jsxc.random(60)));
// only call the first time
if (!jsxc.role_allocation) {
jsxc.onSlave();
}
return;
}
if (jsxc.master && key === 'sid' && !e.newValue) {
jsxc.xmpp.logout(false);
}
if (key.match(/^notices/)) {
jsxc.notice.load();
}
if (key.match(/^presence/)) {
jsxc.gui.changePresence(e.newValue, true);
}
if (key.match(/^options/) && e.newValue) {
n = JSON.parse(e.newValue);
if (typeof n.muteNotification !== 'undefined' && n.muteNotification) {
jsxc.notification.muteSound(true);
} else {
jsxc.notification.unmuteSound(true);
}
}
if (key.match(/^hidden/)) {
if (jsxc.master) {
clearTimeout(jsxc.toNotification);
} else {
jsxc.isHidden();
}
}
if (key.match(/^focus/)) {
if (jsxc.master) {
clearTimeout(jsxc.toNotification);
} else {
jsxc.hasFocus();
}
}
if (key.match(new RegExp('^history' + jsxc.storage.SEP))) {
var history = JSON.parse(e.newValue);
var uid, el, message;
while (history.length > 0) {
uid = history.pop();
message = new jsxc.Message(uid);
el = message.getDOM();
if (el.length === 0) {
if (jsxc.master && message.direction === jsxc.Message.OUT) {
jsxc.xmpp.sendMessage(message.bid, message.msg, message._uid);
}
jsxc.gui.window._postMessage(message, true);
} else if (message.isReceived()) {
el.addClass('jsxc_received');
}
}
return;
}
if (key.match(new RegExp('^window' + jsxc.storage.SEP))) {
if (!e.newValue) {
jsxc.gui.window._close(bid);
return;
}
if (!e.oldValue) {
jsxc.gui.window.open(bid);
return;
}
n = JSON.parse(e.newValue);
o = JSON.parse(e.oldValue);
if (n.minimize !== o.minimize) {
if (n.minimize) {
jsxc.gui.window._hide(bid);
} else {
jsxc.gui.window._show(bid);
}
}
jsxc.gui.window.setText(bid, n.text);
if (n.unread !== o.unread) {
if (n.unread === 0) {
jsxc.gui.readMsg(bid);
} else {
jsxc.gui._unreadMsg(bid, n.unread);
}
}
return;
}
if (key.match(/^unreadMsg/) && jsxc.gui.favicon) {
jsxc.gui.favicon.badge(parseInt(e.newValue) || 0);
}
if (key.match(new RegExp('^smp' + jsxc.storage.SEP))) {
if (!e.newValue) {
jsxc.gui.dialog.close('smp');
jsxc.gui.window.hideOverlay(bid);
if (jsxc.master) {
jsxc.otr.objects[bid].sm.abort();
}
return;
}
n = JSON.parse(e.newValue);
if (typeof(n.data) !== 'undefined') {
jsxc.gui.window.smpRequest(bid, n.data);
} else if (jsxc.master && n.sec) {
jsxc.gui.dialog.close('smp');
jsxc.gui.window.hideOverlay(bid);
jsxc.otr.sendSmpReq(bid, n.sec, n.quest);
}
}
if (!jsxc.master && key.match(new RegExp('^buddy' + jsxc.storage.SEP))) {
if (!e.newValue) {
jsxc.gui.roster.purge(bid);
return;
}
if (jsxc.gui.roster.getItem(bid).length === 0) {
jsxc.gui.roster.add(bid);
return;
}
n = JSON.parse(e.newValue);
o = JSON.parse(e.oldValue);
jsxc.gui.update(bid);
if (o.status !== n.status || o.sub !== n.sub) {
jsxc.gui.roster.reorder(bid);
}
}
if (jsxc.master && key.match(new RegExp('^deletebuddy' + jsxc.storage.SEP)) && e.newValue) {
n = JSON.parse(e.newValue);
jsxc.xmpp.removeBuddy(n.jid);
jsxc.storage.removeUserItem(key);
}
if (jsxc.master && key.match(new RegExp('^buddy' + jsxc.storage.SEP))) {
n = JSON.parse(e.newValue);
o = JSON.parse(e.oldValue);
if (o.transferReq !== n.transferReq) {
jsxc.storage.updateUserItem('buddy', bid, 'transferReq', -1);
if (n.transferReq === 0) {
jsxc.otr.goPlain(bid);
}
if (n.transferReq === 1) {
jsxc.otr.goEncrypt(bid);
}
}
if (o.name !== n.name) {
jsxc.gui.roster._rename(bid, n.name);
}
}
if (key === 'friendReq') {
n = JSON.parse(e.newValue);
if (jsxc.master && n.approve >= 0) {
jsxc.xmpp.resFriendReq(n.jid, n.approve);
}
}
if (jsxc.master && key.match(new RegExp('^add' + jsxc.storage.SEP))) {
n = JSON.parse(e.newValue);
jsxc.xmpp.addBuddy(n.username, n.alias);
}
if (key === 'roster') {
jsxc.gui.roster.toggle(e.newValue);
}
if (jsxc.master && key.match(new RegExp('^vcard' + jsxc.storage.SEP)) && e.newValue !== null && e.newValue.match(/^request:/)) {
jsxc.xmpp.loadVcard(bid, function(stanza) {
jsxc.storage.setUserItem('vcard', bid, {
state: 'success',
data: $('<div>').append(stanza).html()
});
}, function() {
jsxc.storage.setUserItem('vcard', bid, {
state: 'error'
});
});
}
if (!jsxc.master && key.match(new RegExp('^vcard' + jsxc.storage.SEP)) && e.newValue !== null && !e.newValue.match(/^request:/)) {
n = JSON.parse(e.newValue);
if (typeof n.state !== 'undefined') {
$(document).trigger('loaded.vcard.jsxc', n);
}
jsxc.storage.removeUserItem('vcard', bid);
}
if (key === '_cmd' && e.newValue) {
n = JSON.parse(e.newValue) || {};
jsxc.storage.removeUserItem('_cmd');
if (n.cmd && n.target === jsxc.tab.CONST[jsxc.master ? 'MASTER' : 'SLAVE']) {
jsxc.debug('Execute tab cmd: ' + n.cmd);
jsxc.exec(n.cmd, n.params);
}
}
},
/**
* Save or update buddy data.
*
* @memberOf jsxc.storage
* @param bid
* @param data
* @returns {String} Updated or created
*/
saveBuddy: function(bid, data) {
if (jsxc.storage.getUserItem('buddy', bid)) {
jsxc.storage.updateUserItem('buddy', bid, data);
return 'updated';
}
jsxc.storage.setUserItem('buddy', bid, $.extend({
jid: '',
name: '',
status: 0,
sub: 'none',
msgstate: 0,
transferReq: -1,
trust: false,
fingerprint: null,
res: [],
type: 'chat'
}, data));
return 'created';
}
};
-60
Ver Arquivo
@@ -1,60 +0,0 @@
/**
* Provides communication between tabs.
*
* @namespace jsxc.tab
*/
jsxc.tab = {
CONST: {
MASTER: 'master',
SLAVE: 'slave'
},
exec: function(target, cmd, params) {
params = Array.prototype.slice.call(arguments, 2);
if (params.length === 1 && $.isArray(params[0])) {
params = params[0];
}
if (target === jsxc.tab.CONST[jsxc.master ? 'MASTER' : 'SLAVE']) {
jsxc.exec(cmd, params);
if (jsxc.master) {
return;
}
}
jsxc.storage.setUserItem('_cmd', {
target: target,
cmd: cmd,
params: params,
rnd: Math.random() // force storage event
});
},
/**
* Execute command in master tab.
*
* @param {String} cmd Command
* @param {String[]} params List of parameters
*/
execMaster: function() {
var args = Array.prototype.slice.call(arguments);
args.unshift(jsxc.tab.CONST.MASTER);
jsxc.tab.exec.apply(this, args);
},
/**
* Execute command in all slave tabs.
*
* @param {String} cmd Command
* @param {String[]} params List of parameters
*/
execSlave: function() {
var args = Array.prototype.slice.call(arguments);
args.unshift(jsxc.tab.CONST.SLAVE);
jsxc.tab.exec.apply(this, args);
}
};
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
-345
Ver Arquivo
@@ -1,345 +0,0 @@
/**
* Load and save bookmarks according to XEP-0048.
*
* @namespace jsxc.xmpp.bookmarks
*/
jsxc.xmpp.bookmarks = {};
/**
* Determines if server is able to store bookmarks.
*
* @return {boolean} True: Server supports bookmark storage
*/
jsxc.xmpp.bookmarks.remote = function() {
return jsxc.xmpp.conn.caps && jsxc.xmpp.hasFeatureByJid(jsxc.xmpp.conn.domain, Strophe.NS.PUBSUB + "#publish");
};
/**
* Load bookmarks from pubsub.
*
* @memberOf jsxc.xmpp.bookmarks
*/
jsxc.xmpp.bookmarks.load = function() {
var caps = jsxc.xmpp.conn.caps;
var ver = caps._jidVerIndex[jsxc.xmpp.conn.domain];
if (!ver || !caps._knownCapabilities[ver]) {
// wait until we know server capabilities
$(document).on('caps.strophe', function(ev, from) {
if (from === jsxc.xmpp.conn.domain) {
jsxc.xmpp.bookmarks.load();
$(document).off(ev);
}
});
}
if (jsxc.xmpp.bookmarks.remote()) {
jsxc.xmpp.bookmarks.loadFromRemote();
} else {
jsxc.xmpp.bookmarks.loadFromLocal();
}
};
/**
* Load bookmarks from local storage.
*
* @private
*/
jsxc.xmpp.bookmarks.loadFromLocal = function() {
jsxc.debug('Load bookmarks from local storage');
var bookmarks = jsxc.storage.getUserItem('bookmarks') || [];
var bl = jsxc.storage.getUserItem('buddylist') || [];
$.each(bookmarks, function() {
var room = this;
var roomdata = jsxc.storage.getUserItem('buddy', room) || {};
bl.push(room);
jsxc.gui.roster.add(room);
if (roomdata.autojoin) {
jsxc.debug('auto join ' + room);
jsxc.xmpp.conn.muc.join(room, roomdata.nickname);
}
});
jsxc.storage.setUserItem('buddylist', bl);
};
/**
* Load bookmarks from remote storage.
*
* @private
*/
jsxc.xmpp.bookmarks.loadFromRemote = function() {
jsxc.debug('Load bookmarks from pubsub');
var bookmarks = jsxc.xmpp.conn.bookmarks;
bookmarks.get(function(stanza) {
var bl = jsxc.storage.getUserItem('buddylist');
$(stanza).find('conference').each(function() {
var conference = $(this);
var room = conference.attr('jid');
var roomName = conference.attr('name') || room;
var autojoin = conference.attr('autojoin') || false;
var nickname = conference.find('nick').text();
nickname = (nickname.length > 0) ? nickname : Strophe.getNodeFromJid(jsxc.xmpp.conn.jid);
if (autojoin === 'true') {
autojoin = true;
} else if (autojoin === 'false') {
autojoin = false;
}
var data = jsxc.storage.getUserItem('buddy', room) || {};
data = $.extend(data, {
jid: room,
name: roomName,
sub: 'both',
status: 0,
type: 'groupchat',
state: jsxc.muc.CONST.ROOMSTATE.INIT,
subject: null,
bookmarked: true,
autojoin: autojoin,
nickname: nickname
});
jsxc.storage.setUserItem('buddy', room, data);
bl.push(room);
jsxc.gui.roster.add(room);
if (autojoin) {
jsxc.debug('auto join ' + room);
jsxc.xmpp.conn.muc.join(room, nickname);
}
});
jsxc.storage.setUserItem('buddylist', bl);
}, function(stanza) {
var err = jsxc.xmpp.bookmarks.parseErr(stanza);
if (err.reasons[0] === 'item-not-found') {
jsxc.debug('create bookmark node');
bookmarks.createBookmarksNode(function() {
jsxc.debug('Bookmark node created.');
}, function() {
jsxc.debug('Could not create bookmark node.');
});
} else {
jsxc.debug('[XMPP] Could not create bookmark: ' + err.type, err.reasons);
}
});
};
/**
* Parse received error.
*
* @param {string} stanza
* @return {object} err - The parsed error
* @return {string} err.type - XMPP error type
* @return {array} err.reasons - Array of error reasons
*/
jsxc.xmpp.bookmarks.parseErr = function(stanza) {
var error = $(stanza).find('error');
var type = error.attr('type');
var reasons = error.children().map(function() {
return $(this).prop('tagName');
});
return {
type: type,
reasons: reasons
};
};
/**
* Deletes the bookmark for the given room and removes it from the roster if soft is false.
*
* @param {string} room - room jid
* @param {boolean} [soft=false] - True: leave room in roster
*/
jsxc.xmpp.bookmarks.delete = function(room, soft) {
if (!soft) {
jsxc.gui.roster.purge(room);
}
if (jsxc.xmpp.bookmarks.remote()) {
jsxc.xmpp.bookmarks.deleteFromRemote(room, soft);
} else {
jsxc.xmpp.bookmarks.deleteFromLocal(room, soft);
}
};
/**
* Delete bookmark from remote storage.
*
* @private
* @param {string} room - room jid
* @param {boolean} [soft=false] - True: leave room in roster
*/
jsxc.xmpp.bookmarks.deleteFromRemote = function(room, soft) {
var bookmarks = jsxc.xmpp.conn.bookmarks;
bookmarks.delete(room, function() {
jsxc.debug('Bookmark deleted ' + room);
if (soft) {
jsxc.gui.roster.getItem(room).removeClass('jsxc_bookmarked');
jsxc.storage.updateUserItem('buddy', room, 'bookmarked', false);
jsxc.storage.updateUserItem('buddy', room, 'autojoin', false);
}
}, function(stanza) {
var err = jsxc.xmpp.bookmarks.parseErr(stanza);
jsxc.debug('[XMPP] Could not delete bookmark: ' + err.type, err.reasons);
});
};
/**
* Delete bookmark from local storage.
*
* @private
* @param {string} room - room jid
* @param {boolean} [soft=false] - True: leave room in roster
*/
jsxc.xmpp.bookmarks.deleteFromLocal = function(room, soft) {
var bookmarks = jsxc.storage.getUserItem('bookmarks');
var index = bookmarks.indexOf(room);
if (index > -1) {
bookmarks.splice(index, 1);
}
jsxc.storage.setUserItem('bookmarks', bookmarks);
if (soft) {
jsxc.gui.roster.getItem(room).removeClass('jsxc_bookmarked');
jsxc.storage.updateUserItem('buddy', room, 'bookmarked', false);
jsxc.storage.updateUserItem('buddy', room, 'autojoin', false);
}
};
/**
* Adds or overwrites bookmark for given room.
*
* @param {string} room - room jid
* @param {string} alias - room alias
* @param {string} nick - preferred user nickname
* @param {boolean} autojoin - should we join this room after login?
*/
jsxc.xmpp.bookmarks.add = function(room, alias, nick, autojoin) {
if (jsxc.xmpp.bookmarks.remote()) {
jsxc.xmpp.bookmarks.addToRemote(room, alias, nick, autojoin);
} else {
jsxc.xmpp.bookmarks.addToLocal(room, alias, nick, autojoin);
}
};
/**
* Adds or overwrites bookmark for given room in remote storage.
*
* @private
* @param {string} room - room jid
* @param {string} alias - room alias
* @param {string} nick - preferred user nickname
* @param {boolean} autojoin - should we join this room after login?
*/
jsxc.xmpp.bookmarks.addToRemote = function(room, alias, nick, autojoin) {
var bookmarks = jsxc.xmpp.conn.bookmarks;
var success = function() {
jsxc.debug('New bookmark created', room);
jsxc.gui.roster.getItem(room).addClass('jsxc_bookmarked');
jsxc.storage.updateUserItem('buddy', room, 'bookmarked', true);
jsxc.storage.updateUserItem('buddy', room, 'autojoin', autojoin);
jsxc.storage.updateUserItem('buddy', room, 'nickname', nick);
};
var error = function() {
jsxc.warn('Could not create bookmark', room);
};
bookmarks.add(room, alias, nick, autojoin, success, error);
};
/**
* Adds or overwrites bookmark for given room in local storage.
*
* @private
* @param {string} room - room jid
* @param {string} alias - room alias
* @param {string} nick - preferred user nickname
* @param {boolean} autojoin - should we join this room after login?
*/
jsxc.xmpp.bookmarks.addToLocal = function(room, alias, nick, autojoin) {
jsxc.gui.roster.getItem(room).addClass('jsxc_bookmarked');
jsxc.storage.updateUserItem('buddy', room, 'bookmarked', true);
jsxc.storage.updateUserItem('buddy', room, 'autojoin', autojoin);
jsxc.storage.updateUserItem('buddy', room, 'nickname', nick);
var bookmarks = jsxc.storage.getUserItem('bookmarks') || [];
if (bookmarks.indexOf(room) < 0) {
bookmarks.push(room);
jsxc.storage.setUserItem('bookmarks', bookmarks);
}
};
/**
* Show dialog to edit bookmark.
*
* @param {string} room - room jid
*/
jsxc.xmpp.bookmarks.showDialog = function(room) {
var dialog = jsxc.gui.dialog.open(jsxc.gui.template.get('bookmarkDialog'));
var data = jsxc.storage.getUserItem('buddy', room);
$('#jsxc_room').val(room);
$('#jsxc_nickname').val(data.nickname);
$('#jsxc_bookmark').change(function() {
if ($(this).prop('checked')) {
$('#jsxc_nickname').prop('disabled', false);
$('#jsxc_autojoin').prop('disabled', false);
$('#jsxc_autojoin').parent('.checkbox').removeClass('disabled');
} else {
$('#jsxc_nickname').prop('disabled', true);
$('#jsxc_autojoin').prop('disabled', true).prop('checked', false);
$('#jsxc_autojoin').parent('.checkbox').addClass('disabled');
}
});
$('#jsxc_bookmark').prop('checked', data.bookmarked);
$('#jsxc_autojoin').prop('checked', data.autojoin);
$('#jsxc_bookmark').change();
dialog.find('form').submit(function(ev) {
ev.preventDefault();
var bookmarked = $('#jsxc_bookmark').prop('checked');
var autojoin = $('#jsxc_autojoin').prop('checked');
var nickname = $('#jsxc_nickname').val();
if (bookmarked) {
jsxc.xmpp.bookmarks.add(room, data.name, nickname, autojoin);
} else if (data.bookmarked) {
// bookmarked === false
jsxc.xmpp.bookmarks.delete(room, true);
}
jsxc.gui.dialog.close();
return false;
});
};
-248
Ver Arquivo
@@ -1,248 +0,0 @@
/**
* Implements XEP-0085: Chat State Notifications.
*
* @namespace jsxc.xmpp.chatState
* @see {@link http://xmpp.org/extensions/xep-0085.html}
*/
jsxc.xmpp.chatState = {
conn: null,
/** Delay between two notification on the message composing */
toComposingNotificationDelay: 900,
};
jsxc.xmpp.chatState.init = function() {
var self = jsxc.xmpp.chatState;
if (!jsxc.xmpp.conn || !jsxc.xmpp.connected) {
$(document).on('attached.jsxc', self.init);
return;
}
// prevent double execution after reconnect
$(document).off('composing.chatstates', jsxc.xmpp.chatState.onComposing);
$(document).off('paused.chatstates', jsxc.xmpp.chatState.onPaused);
$(document).off('active.chatstates', jsxc.xmpp.chatState.onActive);
if (self.isDisabled()) {
jsxc.debug('chat state notification disabled');
return;
}
self.conn = jsxc.xmpp.conn;
$(document).on('composing.chatstates', jsxc.xmpp.chatState.onComposing);
$(document).on('paused.chatstates', jsxc.xmpp.chatState.onPaused);
$(document).on('active.chatstates', jsxc.xmpp.chatState.onActive);
};
/**
* Composing event received. Display message.
*
* @memberOf jsxc.xmpp.chatState
* @param {Event} ev
* @param {String} jid
*/
jsxc.xmpp.chatState.onComposing = function(ev, jid) {
var self = jsxc.xmpp.chatState;
var bid = jsxc.jidToBid(jid);
var data = jsxc.storage.getUserItem('buddy', bid) || null;
if (!data || jsxc.xmpp.chatState.isDisabled()) {
return;
}
// ignore own notifications in groupchat
if (data.type === 'groupchat' &&
Strophe.getResourceFromJid(jid) === Strophe.getNodeFromJid(self.conn.jid)) {
return;
}
var user = data.type === 'groupchat' ? Strophe.getResourceFromJid(jid) : data.name;
var win = jsxc.gui.window.get(bid);
if (win.length === 0) {
return;
}
clearTimeout(win.data('composing-timeout'));
// add user in array if necessary
var usersComposing = win.data('composing') || [];
if (usersComposing.indexOf(user) === -1) {
usersComposing.push(user);
win.data('composing', usersComposing);
}
var textarea = win.find('.jsxc_textarea');
var composingNotif = textarea.find('.jsxc_composing');
if (composingNotif.length < 1) {
// notification not present, add it
composingNotif = $('<div>').addClass('jsxc_composing')
.addClass('jsxc_chatmessage')
.addClass('jsxc_sys')
.appendTo(textarea);
}
var msg = self._genComposingMsg(usersComposing);
composingNotif.text(msg);
// scroll to bottom
jsxc.gui.window.scrollDown(bid);
// show message
composingNotif.addClass('jsxc_fadein');
};
/**
* Pause event receive. Remove or update composing message.
*
* @memberOf jsxc.xmpp.chatState
* @param {Event} ev
* @param {String} jid
*/
jsxc.xmpp.chatState.onPaused = function(ev, jid) {
var self = jsxc.xmpp.chatState;
var bid = jsxc.jidToBid(jid);
var data = jsxc.storage.getUserItem('buddy', bid) || null;
if (!data || jsxc.xmpp.chatState.isDisabled()) {
return;
}
var user = data.type === 'groupchat' ? Strophe.getResourceFromJid(jid) : data.name;
var win = jsxc.gui.window.get(bid);
if (win.length === 0) {
return;
}
var el = win.find('.jsxc_composing');
var usersComposing = win.data('composing') || [];
if (usersComposing.indexOf(user) >= 0) {
// remove user from list
usersComposing.splice(usersComposing.indexOf(user), 1);
win.data('composing', usersComposing);
}
if (usersComposing.length === 0) {
var durationValue = el.css('transition-duration') || '0s';
var duration = parseFloat(durationValue) || 0;
if (durationValue.match(/[^m]s$/)) {
duration *= 1000;
}
el.removeClass('jsxc_fadein');
var to = setTimeout(function() {
el.remove();
}, duration);
win.data('composing-timeout', to);
} else {
// update message
el.text(self._genComposingMsg(usersComposing));
}
};
/**
* Active event received.
*
* @memberOf jsxc.xmpp.chatState
* @param {Event} ev
* @param {String} jid
*/
jsxc.xmpp.chatState.onActive = function(ev, jid) {
jsxc.xmpp.chatState.onPaused(ev, jid);
};
/**
* Send composing event.
*
* @memberOf jsxc.xmpp.chatState
* @param {String} bid
*/
jsxc.xmpp.chatState.startComposing = function(bid) {
var self = jsxc.xmpp.chatState;
if (!jsxc.xmpp.conn || !jsxc.xmpp.conn.chatstates || jsxc.xmpp.chatState.isDisabled()) {
return;
}
var win = jsxc.gui.window.get(bid);
var timeout = win.data('composing-timeout');
var type = win.hasClass('jsxc_groupchat') ? 'groupchat' : 'chat';
if (timeout) {
// @REVIEW page reload?
clearTimeout(timeout);
} else {
jsxc.xmpp.conn.chatstates.sendComposing(bid, type);
}
timeout = setTimeout(function() {
self.pauseComposing(bid, type);
win.data('composing-timeout', null);
}, self.toComposingNotificationDelay);
win.data('composing-timeout', timeout);
};
/**
* Send pause event.
*
* @memberOf jsxc.xmpp.chatState
* @param {String} bid
*/
jsxc.xmpp.chatState.pauseComposing = function(bid, type) {
if (jsxc.xmpp.chatState.isDisabled()) {
return;
}
jsxc.xmpp.conn.chatstates.sendPaused(bid, type);
};
/**
* End composing without sending a pause event.
*
* @memberOf jsxc.xmpp.chatState
* @param {String} bid
*/
jsxc.xmpp.chatState.endComposing = function(bid) {
var win = jsxc.gui.window.get(bid);
if (win.data('composing-timeout')) {
clearTimeout(win.data('composing-timeout'));
}
};
/**
* Generate composing message.
*
* @memberOf jsxc.xmpp.chatState
* @param {Array} usersComposing List of users which are currently composing a message
*/
jsxc.xmpp.chatState._genComposingMsg = function(usersComposing) {
if (!usersComposing || usersComposing.length === 0) {
jsxc.debug('usersComposing array is empty?');
return '';
} else {
return usersComposing.length > 1 ? usersComposing.join(', ') + $.t('_are_composing') :
usersComposing[0] + $.t('_is_composing');
}
};
jsxc.xmpp.chatState.isDisabled = function() {
var options = jsxc.options.get('chatState') || {};
return !options.enable;
};
$(document).on('attached.jsxc', jsxc.xmpp.chatState.init);
-330
Ver Arquivo
@@ -1,330 +0,0 @@
/**
* Implements Http File Upload (XEP-0363)
*
* @namespace jsxc.xmpp.httpUpload
* @see {@link http://xmpp.org/extensions/xep-0363.html}
*/
jsxc.xmpp.httpUpload = {
conn: null,
ready: false,
CONST: {
NS: {
HTTPUPLOAD: 'urn:xmpp:http:upload'
}
}
};
/**
* Set up http file upload.
*
* @memberOf jsxc.xmpp.httpUpload
* @param {Object} o options
*/
jsxc.xmpp.httpUpload.init = function(o) {
var self = jsxc.xmpp.httpUpload;
self.conn = jsxc.xmpp.conn;
var fileTransferOptions = jsxc.options.get('fileTransfer') || {};
var options = o || jsxc.options.get('httpUpload');
if (!fileTransferOptions.httpUpload.enable) {
jsxc.debug('http upload disabled');
jsxc.options.set('httpUpload', false);
return;
}
if (options && options.server) {
self.ready = true;
return;
}
var caps = jsxc.xmpp.conn.caps;
var domain = jsxc.xmpp.conn.domain;
if (!caps || !domain || typeof caps._knownCapabilities[caps._jidVerIndex[domain]] === 'undefined') {
jsxc.debug('Waiting for server capabilities');
$(document).on('caps.strophe', function onCaps(ev, from) {
if (from !== domain) {
return;
}
self.init();
$(document).off('caps.strophe', onCaps);
});
return;
}
if (caps.hasFeatureByJid(domain, self.CONST.NS.HTTPUPLOAD)) {
self.discoverUploadService();
} else {
jsxc.debug(domain + ' does not support http upload');
}
};
/**
* Discover upload service for http upload.
*
* @memberOf jsxc.xmpp.httpUpload
*/
jsxc.xmpp.httpUpload.discoverUploadService = function() {
var self = jsxc.xmpp.httpUpload;
jsxc.debug('discover http upload service');
self.queryItemForUploadService(self.conn.domain);
self.conn.disco.items(self.conn.domain, null, function(items) {
$(items).find('item').each(function() {
var jid = $(this).attr('jid');
if (self.ready) {
// abort, because we already found a service
return false;
}
self.queryItemForUploadService(jid);
});
});
};
/**
* Query item for upload service.
*
* @param {String} jid
* @param {Function} cb Callback on success
* @memberOf jsxc.xmpp.httpUpload
*/
jsxc.xmpp.httpUpload.queryItemForUploadService = function(jid, cb) {
var self = jsxc.xmpp.httpUpload;
jsxc.debug('query ' + jid + ' for upload service');
self.conn.disco.info(jid, null, function(info) {
var httpUploadFeature = $(info).find('feature[var="' + self.CONST.NS.HTTPUPLOAD + '"]');
var httpUploadMaxSize = $(info).find('field[var="max-file-size"]');
if (httpUploadFeature.length > 0) {
jsxc.debug('http upload service found on ' + jid);
jsxc.options.set('httpUpload', {
server: jid,
name: $(info).find('identity').attr('name'),
maxSize: parseInt(httpUploadMaxSize.text())
});
self.ready = true;
if (typeof cb === 'function') {
cb.call(info);
}
}
});
};
/**
* Upload file and send link to peer.
*
* @memberOf jsxc.xmpp.httpUpload
* @param {File} file
* @param {Message} message Preview message
*/
jsxc.xmpp.httpUpload.sendFile = function(file, message) {
jsxc.debug('Send file via http upload');
var self = jsxc.xmpp.httpUpload;
// even if the link is encrypted the file isn't
message.encrypted = false;
self.requestSlot(file, function(data) {
if (!data) {
// general error
jsxc.warn('Unknown error occured. Please check the debug log.');
} else if (data.error) {
// specific error
jsxc.warn('The xmpp server responded with an error of the type "' + data.error.type + '"');
message.getDOM().remove();
jsxc.gui.window.postMessage({
bid: message.bid,
direction: jsxc.Message.SYS,
msg: data.error.text
});
message.delete();
} else if (data.get && data.put) {
// slot received, start upload
self.uploadFile(data.put, file, message, function() {
var a = $('<a>');
a.attr('href', data.get);
a.attr('data-name', message.attachment.name);
a.attr('data-type', message.attachment.type);
a.attr('data-size', message.attachment.size);
if (message.attachment.thumbnail) {
a.attr('data-thumbnail', message.attachment.thumbnail);
}
a.text(data.get);
message.attachment.data = data.get;
message.msg = $('<span>').append(a).html();
message.type = jsxc.Message.HTML;
jsxc.gui.window.postMessage(message);
});
}
});
};
/**
* Upload the given file to the given url.
*
* @memberOf jsxc.xmpp.httpUpload
* @param {String} url upload url
* @param {File} file
* @param {Message} message preview message
* @param {Function} success_cb callback on successful transition
*/
jsxc.xmpp.httpUpload.uploadFile = function(url, file, message, success_cb) {
$.ajax({
url: url,
type: 'PUT',
contentType: 'application/octet-stream',
data: file,
processData: false,
xhr: function() {
var xhr = $.ajaxSettings.xhr();
// track upload progress
xhr.upload.onprogress = function(ev) {
if (ev.lengthComputable) {
jsxc.gui.window.updateProgress(message, ev.loaded, ev.total);
}
};
return xhr;
},
success: function() {
jsxc.debug('file successful uploaded');
// In case that upload progress is not available, inform user
jsxc.gui.window.updateProgress(message, 1, 1);
if (success_cb) {
success_cb();
}
},
error: function() {
jsxc.warn('error while uploading file to ' + url);
message.error = 'Could not upload file';
jsxc.gui.window.postMessage(message);
}
});
};
/**
* Request upload slot.
*
* @memberOf jsxc.xmpp.httpUpload
* @param {File} file
* @param {Function} cb Callback after finished request
*/
jsxc.xmpp.httpUpload.requestSlot = function(file, cb) {
var self = jsxc.xmpp.httpUpload;
var options = jsxc.options.get('httpUpload');
if (!options || !options.server) {
jsxc.warn('could not request upload slot, because I am not aware of a server or http upload is disabled');
return;
}
var iq = $iq({
to: options.server,
type: 'get'
}).c('request', {
xmlns: self.CONST.NS.HTTPUPLOAD
}).c('filename').t(file.name)
.up()
.c('size').t(file.size);
self.conn.sendIQ(iq, function(stanza) {
self.successfulRequestSlotCB(stanza, cb);
}, function(stanza) {
self.failedRequestSlotCB(stanza, cb);
});
};
/**
* Process successful response to slot request.
*
* @memberOf jsxc.xmpp.httpUpload
* @param {String} stanza
* @param {Function} cb
*/
jsxc.xmpp.httpUpload.successfulRequestSlotCB = function(stanza, cb) {
var self = jsxc.xmpp.httpUpload;
var slot = $(stanza).find('slot[xmlns="' + self.CONST.NS.HTTPUPLOAD + '"]');
if (slot.length > 0) {
var put = slot.find('put').text();
var get = slot.find('get').text();
cb({
put: put,
get: get
});
} else {
self.failedRequestSlotCB(stanza, cb);
}
};
/**
* Process failed response to slot request.
*
* @memberOf jsxc.xmpp.httpUpload
* @param {String} stanza
* @param {Function} cb
*/
jsxc.xmpp.httpUpload.failedRequestSlotCB = function(stanza, cb) {
if ($(stanza).find('error').length <= 0) {
jsxc.warn('response does not contain a slot element');
cb();
return;
}
var error = {
type: $(stanza).find('error').attr('type') || 'unknown',
text: $(stanza).find('error text').text()
};
if ($(stanza).find('error not-acceptable')) {
error.reason = 'not-acceptable';
} else if ($(stanza).find('error resource-constraint')) {
error.reason = 'resource-constraint';
} else if ($(stanza).find('error not-allowed')) {
error.reason = 'not-allowed';
}
cb({
error: error
});
};
$(document).on('stateChange.jsxc', function(ev, state) {
if (state === jsxc.CONST.STATE.READY) {
jsxc.xmpp.httpUpload.init();
}
});
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
-1
Ver Arquivo
@@ -1 +0,0 @@
}(jQuery));
+16
Ver Arquivo
@@ -0,0 +1,16 @@
import Client from '../Client'
import Options from '../Options'
import * as CONST from '../CONST'
import Message from '../Message'
function addPrivateCarbonHint(message:Message, xmlMsg:Strophe.Builder):void {
let body = xmlMsg.node.textContent;
if (Options.get('carbons').enabled && body.match(/^\?OTR/)) {
xmlMsg.up().c("private", {
xmlns: CONST.NS.CARBONS
});
}
}
Client.addPreSendMessageHook(addPrivateCarbonHint);
+221
Ver Arquivo
@@ -0,0 +1,221 @@
/**
* Implements XEP-0085: Chat State Notifications.
*
* @namespace chatState
* @see {@link http://xmpp.org/extensions/xep-0085.html}
*/
import Client from '../Client'
import Options from '../Options'
import Log from '../util/Log'
import JID from '../JID'
import Connection from '../connection/Connection'
import {PluginInterface} from '../PluginInterface'
import Message from '../Message'
function addReceiptsRequest(message:Message, xmlMsg:Strophe.Builder):void {
if (!jsxc.xmpp.chatState.isDisabled()) {
// send active event (XEP-0085)
xmlMsg.up().c('active', {
xmlns: Strophe.NS.CHATSTATES
});
}
}
Client.addPreSendMessageHook(addReceiptsRequest);
let chatState:any = {
conn: null,
/** Delay between two notification on the message composing */
toComposingNotificationDelay: 900,
};
Client.addConnectionPlugin(ChatState);
class ChatState implements PluginInterface {
constructor(private connection:Connection) {
// prevent double execution after reconnect
$(document).off('composing.chatstates', this.onComposing);
$(document).off('paused.chatstates', this.onPaused);
$(document).off('active.chatstates', this.onActive);
if (this.isDisabled()) {
Log.debug('chat state notification disabled');
return;
}
$(document).on('composing.chatstates', this.onComposing);
$(document).on('paused.chatstates', this.onPaused);
$(document).on('active.chatstates', this.onActive);
}
public isDisabled():boolean {
var options = Options.get('chatState') || {};
return !options.enable;
}
private onComposing(ev, jidString) {
let jid = new JID(jidString);
if (this.isDisabled()) {
return;
}
// @TODO ignore own notifications in groupchat
let chatWindow = ChatWindow.get(jid.bare);
if (!chatWindow) {
return;
}
let user = chatWindow.type === 'groupchat' ? Strophe.getResourceFromJid(jid.full) : data.name;
clearTimeout(chatWindow.data('composing-timeout'));
// add user in array if necessary
var usersComposing = chatWindow.data('composing') || [];
if (usersComposing.indexOf(user) === -1) {
usersComposing.push(user);
chatWindow.data('composing', usersComposing);
}
var textarea = chatWindow.find('.jsxc_textarea');
var composingNotif = textarea.find('.jsxc_composing');
if (composingNotif.length < 1) {
// notification not present, add it
composingNotif = $('<div>').addClass('jsxc_composing')
.addClass('jsxc_chatmessage')
.addClass('jsxc_sys')
.appendTo(textarea);
}
var msg = this._genComposingMsg(usersComposing);
composingNotif.text(msg);
// scroll to bottom
jsxc.gui.window.scrollDown(bid);
// show message
composingNotif.addClass('jsxc_fadein');
}
private onPaused(ev, jid) {
var self = chatState;
var bid = jsxc.jidToBid(jid);
var data = jsxc.storage.getUserItem('buddy', bid) || null;
if (!data || chatState.isDisabled()) {
return;
}
var user = data.type === 'groupchat' ? Strophe.getResourceFromJid(jid) : data.name;
var win = jsxc.gui.window.get(bid);
if (win.length === 0) {
return;
}
var el = win.find('.jsxc_composing');
var usersComposing = win.data('composing') || [];
if (usersComposing.indexOf(user) >= 0) {
// remove user from list
usersComposing.splice(usersComposing.indexOf(user), 1);
win.data('composing', usersComposing);
}
if (usersComposing.length === 0) {
var durationValue = el.css('transition-duration') || '0s';
var duration = parseFloat(durationValue) || 0;
if (durationValue.match(/[^m]s$/)) {
duration *= 1000;
}
el.removeClass('jsxc_fadein');
var to = setTimeout(function() {
el.remove();
}, duration);
win.data('composing-timeout', to);
} else {
// update message
el.text(self._genComposingMsg(usersComposing));
}
}
private onActive(ev, jid) {
chatState.onPaused(ev, jid);
}
private startComposing(bid) {
var self = chatState;
if (!conn || !conn.chatstates || chatState.isDisabled()) {
return;
}
var win = jsxc.gui.window.get(bid);
var timeout = win.data('composing-timeout');
var type = win.hasClass('jsxc_groupchat') ? 'groupchat' : 'chat';
if (timeout) {
// @REVIEW page reload?
clearTimeout(timeout);
} else {
conn.chatstates.sendComposing(bid, type);
}
timeout = setTimeout(function() {
self.pauseComposing(bid, type);
win.data('composing-timeout', null);
}, self.toComposingNotificationDelay);
win.data('composing-timeout', timeout);
}
private pauseComposing(bid, type) {
if (chatState.isDisabled()) {
return;
}
conn.chatstates.sendPaused(bid, type);
}
private endComposing(bid) {
var win = jsxc.gui.window.get(bid);
if (win.data('composing-timeout')) {
clearTimeout(win.data('composing-timeout'));
}
}
private _genComposingMsg(usersComposing) {
if (!usersComposing || usersComposing.length === 0) {
jsxc.debug('usersComposing array is empty?');
return '';
} else {
return usersComposing.length > 1 ? usersComposing.join(', ') + $.t('_are_composing') :
usersComposing[0] + $.t('_is_composing');
}
}
}
keypress = {
// I'm composing a message
if (ev.which !== 13) {
jsxc.xmpp.chatState.startComposing(bid);
}
if (ev.which === 13 && !ev.shiftKey) {
jsxc.xmpp.chatState.endComposing(bid);
}
}
+15
Ver Arquivo
@@ -0,0 +1,15 @@
import Client from '../Client'
import Options from '../Options'
import * as CONST from '../CONST'
import Message from '../Message'
function addReceiptsRequest(message:Message, xmlMsg:Strophe.Builder):void {
if (message.type === Message.CHAT && (message.receiver.isBare() || this.hasFeatureByJid(message.receiver, Strophe.NS.RECEIPTS))) {
// Add request according to XEP-0184
xmlMsg.up().c('request', {
xmlns: Strophe.NS.RECEIPTS
});
}
}
Client.addPreSendMessageHook(addReceiptsRequest);
+60
Ver Arquivo
@@ -0,0 +1,60 @@
import Options from '../Options'
import Hash from '../util/Hash'
import Contact from '../Contact'
export default class Avatar {
private elements = [];
private static avatars = {};
public static get(contact:Contact) {
let avatar = Avatar.avatars[contact.getId()];
if (!avatar) {
avatar = Avatar.avatars[contact.getId()] = new Avatar(contact);
}
return avatar;
}
public addElement(element) {
this.elements.push(element);
this.placeholder(element, this.contact.getName());
}
public reload() {
this.placeholder(this.elements, this.contact.getName());
}
private constructor(private contact:Contact) {
this.contact.registerHook('name', (name) => {
this.reload();
})
}
private placeholder(elements, text) {
var options = Options.get('avatarPlaceholder') || {};
var hash = Hash.String(text);
var hue = Math.abs(hash) % 360;
var saturation = options.saturation || 90;
var lightness = options.lightness || 65;
$(elements).each(function(){
let element = $(this);
element.css({
'background-color': 'hsl(' + hue + ', ' + saturation + '%, ' + lightness + '%)',
'color': '#fff',
'font-weight': 'bold',
'text-align': 'center',
'line-height': '36px', // element.height() + 'px',
'font-size': '22px', //element.height() * 0.6 + 'px'
});
element.text(text[0].toUpperCase());
});
}
}
+701
Ver Arquivo
@@ -0,0 +1,701 @@
import Storage from '../Storage';
import Log from '../util/Log';
import Contact from '../Contact'
import Menu from './util/Menu'
import Options from '../Options'
import Message from '../Message'
import Client from '../Client'
import Account from '../Account'
import * as CONST from '../CONST'
import DateTime from './util/DateTime'
import showVerificationDialog from './dialogs/verification'
import showFingerprintsDialog from './dialogs/fingerprints'
import Emoticons from '../Emoticons'
import SortedPersistentMap from '../SortedPersistentMap'
import PersistentMap from '../PersistentMap'
import Avatar from './Avatar'
import 'simplebar'
let chatWindowTemplate = require('../../template/chatWindow.hbs');
//@TODO duplicate of AbstractConnection
enum Status {
online,
chat,
away,
xa,
dnd,
offline
}
const ENTER_KEY = 13;
const ESC_KEY = 27;
export default class ChatWindow {
private element:JQuery;
private inputElement:JQuery;
private inputBlurTimeout:number;
private storage;
private readonly INPUT_RESIZE_DELAY = 1200;
private readonly HIGHTLIGHT_DURATION = 600;
private properties:PersistentMap;
private messages:SortedPersistentMap;
constructor(private account:Account, private contact:Contact) {
let template = chatWindowTemplate({
accountId: account.getUid(),
contactId: contact.getId(),
name: contact.getName()
});
this.element = $(template);
this.inputElement = this.element.find('.jsxc-message-input');
this.storage = account.getStorage();
Menu.init(this.element.find('.jsxc-menu'));
this.initResizableWindow();
this.initEmoticonMenu();
this.restoreLocalHistory();
this.registerHandler();
this.registerInputHandler();
this.element.find('.jsxc-name').disableSelection();
this.element.find('.jsxc-window').css('bottom', -1 * this.element.find('.jsxc-window-fade').height());
this.messages.registerHook((newMessages, oldMessages) => {
oldMessages = oldMessages || [];
if (newMessages.length === 0 && oldMessages.length > 0) {
this.clear();
} // else if(newMessages.length > oldMessages.length) {
// let diff = $(newMessages).not(oldMessages).get();
//
// for (let messageId of diff) {
// this.postMessage(new Message(messageId));
// }
// }
});
this.properties = new PersistentMap(this.storage, 'chatWindow', this.contact.getId());
if (this.properties.get('minimized') === false) {
this.unminimize();
} else {
this.minimize();
}
this.properties.registerHook('minimized', (minimized) => { console.log('properties.minimized')
if (minimized) {
this.minimize();
} else {
this.unminimize();
}
});
let avatar = Avatar.get(contact);
avatar.addElement(this.element.find('.jsxc-avatar'));
// @TODO update gui
this.contact.registerHook('name', (newName) => {
this.element.find('.jsxc-name').text(newName);
});
// @TODO init otr
this.element.attr('data-presence', Status[this.contact.getPresence()]);
this.contact.registerHook('status', (newStatus) => {
this.element.attr('data-presence', Status[newStatus]);
});
// let simpleBar = new SimpleBar(this.element.find('.jsxc-message-area')[0]);
$(document).trigger('init.window.jsxc', [this.element]);
}
public getId() {
return /*this.account.getUid() + '@' +*/ this.contact.getId();
}
public getAccount() {
return this.account;
}
public getContact() {
return this.contact;
}
public getDom() {
return this.element;
}
public close() {
this.element.remove();
}
public minimize(ev?) {
this.element.removeClass('jsxc-normal').addClass('jsxc-minimized');
this.properties.set('minimized', true);
//@TODO replace this with max-height css property
//win.find('.jsxc-window').css('bottom', -1 * win.find('.jsxc-window-fade').height());
}
public unminimize(ev?) {
let element = this.element;
if (Client.isExtraSmallDevice()) {
if (parseFloat($('#jsxc-roster').css('right')) >= 0) {
// duration = jsxc.gui.roster.toggle();
}
//@TODO hide all other windows
//@TODO fullscreen this window
}
element.removeClass('jsxc-minimized').addClass('jsxc-normal');
this.properties.set('minimized', false);
// @REVIEW is this still required?
//element.find('.jsxc-window').css('bottom', '0');
//@TODO scroll message list, so that this window is in the view port
this.scrollMessageAreaToBottom();
if (ev && ev.target) {
element.find('.jsxc-textinput').focus();
}
}
public clear() {
this.messages.empty(function(id, message){ console.log(id, message)
message.delete();
})
this.element.find('.jsxc-message-area').empty();
}
public highlight() {
let element = this.element;
if (!element.hasClass('jsxc-highlight')) {
element.addClass('jsxc-highlight');
setTimeout(function(){
element.removeClass('jsxc-highlight');
}, this.HIGHTLIGHT_DURATION)
}
}
public receiveIncomingMessage(message:Message) {
this.messages.push(message);
}
public postMessage(message:Message) {
if (message.getDirection() === Message.DIRECTION.IN && !this.inputElement.is(':focus')) {
message.setUnread();
}
let messageElement = $('<div>');
messageElement.addClass('jsxc-chatmessage jsxc-' + message.getDirectionString());
messageElement.attr('id', message.getCssId());
messageElement.html('<div>' + message.getProcessedBody() + '</div>');
let timestampElement = $('<div>');
timestampElement.addClass('jsxc-timestamp');
DateTime.stringify(message.getStamp(), timestampElement);
messageElement.append(timestampElement);
if (message.isReceived()) {
messageElement.addClass('jsxc-received');
} else {
messageElement.removeClass('jsxc-received');
}
if (message.isForwarded()) {
messageElement.addClass('jsxc-forwarded');
} else {
messageElement.removeClass('jsxc-forwarded');
}
if (message.isEncrypted()) {
messageElement.addClass('jsxc-encrypted');
} else {
messageElement.removeClass('jsxc-encrypted');
}
if (message.getErrorMessage()) {
messageElement.addClass('jsxc-error');
messageElement.attr('title', message.getErrorMessage());
} else {
messageElement.removeClass('jsxc-error');
}
if (message.hasAttachment()) {
let attachment = message.getAttachment();
let mimeType = attachment.getMimeType();
let attachmentElement = $('<div>');
attachmentElement.addClass('jsxc-attachment');
attachmentElement.addClass('jsxc-' + mimeType.replace(/\//, '-'));
attachmentElement.addClass('jsxc-' + mimeType.replace(/^([^/]+)\/.*/, '$1'));
if (attachment.isPersistent()) {
attachmentElement.addClass('jsxc-persistent');
}
if (attachment.isImage() && attachment.hasThumbnailData()) {
$('<img>')
.attr('alt', 'preview')
.attr('src', attachment.getThumbnailData())
// .attr('title', message.getName())
.appendTo(attachmentElement);
} else {
attachmentElement.text(attachment.getName());
}
if (message.hasData()) {
attachmentElement = $('<a>').append(attachmentElement);
attachmentElement.attr('href', attachment.getData());
attachmentElement.attr('download', attachment.getName());
}
messageElement.find('div').first().append(attachmentElement);
}
if (message.getDirection() === Message.DIRECTION.SYS) {
this.element.find('.jsxc-message-area').append('<div class="jsxc-clear"/>');
} else {
//@TODO update last message
//$('[data-bid="' + bid + '"]').find('.jsxc-lastmsg .jsxc-text').html(msg);
}
if (message.getDOM().length > 0) {
message.getDOM().replaceWith(messageElement);
} else {
this.element.find('.jsxc-message-area').append(messageElement);
}
// if (typeof message.sender === 'object' && message.sender !== null) {
// var title = '';
// var avatarDiv = $('<div>');
// avatarDiv.addClass('jsxc-avatar').prependTo(messageElement);
//
// if (typeof message.sender.jid === 'string') {
// messageElement.attr('data-bid', jsxc.jidToBid(message.sender.jid));
//
// var data = jsxc.storage.getUserItem('buddy', jsxc.jidToBid(message.sender.jid)) || {};
// jsxc.gui.updateAvatar(messageElement, jsxc.jidToBid(message.sender.jid), data.avatar);
//
// title = jsxc.jidToBid(message.sender.jid);
// }
//
// if (typeof message.sender.name === 'string') {
// messageElement.attr('data-name', message.sender.name);
//
// if (typeof message.sender.jid !== 'string') {
// jsxc.gui.avatarPlaceholder(avatarDiv, message.sender.name);
// }
//
// if (title !== '') {
// title = '\n' + title;
// }
//
// title = message.sender.name + title;
//
// timestampElement.text(timestampElement.text() + ' ' + message.sender.name);
// }
//
// avatarDiv.attr('title', jsxc.escapeHTML(title));
//
// if (messageElement.prev().length > 0 && messageElement.prev().find('.jsxc-avatar').attr('title') === avatarDiv.attr('title')) {
// avatarDiv.css('visibility', 'hidden');
// }
// }
this.scrollMessageAreaToBottom();
}
private registerHandler() {
let self = this;
let contact = this.contact;
this.element.find('.jsxc-verification').click(function() {
showVerificationDialog(contact);
});
this.element.find('.jsxc-fingerprints').click(function() {
showFingerprintsDialog(contact);
});
this.element.find('.jsxc-transfer').click(function() {
// jsxc.otr.toggleTransfer(bid);
});
this.element.find('.jsxc-window-bar').click(() => {
this.toggle();
});
this.element.find('.jsxc-close').click(() => {
this.account.closeChatWindow(this);
});
this.element.find('.jsxc-clear').click(() => {
this.clear();
});
this.element.find('.jsxc-sendFile').click(function() {
$('body').click();
// jsxc.gui.window.sendFile(bid);
});
this.element.find('.jsxc-message-area').click(function() {
// check if user clicks element or selects text
if (typeof getSelection === 'function' && !getSelection().toString()) {
self.inputElement.focus();
}
});
}
private registerInputHandler() {
let self = this;
var textinputBlurTimeout;
let inputElement = this.inputElement;
inputElement.keyup(self.onInputKeyUp);
inputElement.keypress(self.onInputKeyPress);
inputElement.focus(this.onInputFocus);
inputElement.blur(this.onInputBlur);
// @REVIEW
inputElement.mouseenter(function() {
$('#jsxc-window-list').data('isHover', true);
}).mouseleave(function() {
$('#jsxc-window-list').data('isHover', false);
});
}
private onInputKeyUp = (ev) => {
var message = $(ev.target).val();
if (ev.which === ENTER_KEY && !ev.shiftKey) {
message = '';
} else {
this.resizeInputArea();
}
if (ev.which === ESC_KEY) {
this.close();
}
}
private onInputKeyPress = (ev) => {
let message = $(ev.target).val();
if (ev.which !== ENTER_KEY || ev.shiftKey || !message) {
return;
}
this.sendOutgoingMessage(message);
// reset textarea
$(ev.target).css('height', '').val('');
ev.preventDefault();
}
private onInputFocus = () => {
if (this.inputBlurTimeout) {
clearTimeout(this.inputBlurTimeout);
}
// remove unread flag
//jsxc.gui.readMsg(bid);
this.resizeInputArea();
}
private onInputBlur = (ev) => {
this.inputBlurTimeout = setTimeout(function() {
$(ev.target).css('height', '');
}, this.INPUT_RESIZE_DELAY);
}
private sendOutgoingMessage(messageString:string) {
if (this.contact.isEncrypted()) {
//@TODO send sys $.t('your_message_wasnt_send_please_end_your_private_conversation');
return;
}
//@TODO we need a full jid
let message = new Message({
peer: this.contact.getJid(),
direction: Message.DIRECTION.OUT,
plaintextMessage: messageString
});
message.save();
this.messages.push(message);
this.getAccount().getConnection().sendMessage(message);
if (messageString === '?' && Options.get('theAnswerToAnything') !== false) {
if (typeof Options.get('theAnswerToAnything') === 'undefined' || (Math.random() * 100 % 42) < 1) {
Options.set('theAnswerToAnything', true);
(new Message({
peer: this.contact.getJid(),
direction: Message.DIRECTION.SYS,
plaintextMessage: '42'
})).save();
}
}
}
private toggle = (ev?) => {
if (this.element.hasClass('jsxc-minimized')) {
this.unminimize(ev);
} else {
this.minimize(ev);
}
}
private resizeInputArea() {
let inputElement = this.inputElement;
if (!inputElement.data('originalHeight')) {
inputElement.data('originalHeight', inputElement.outerHeight());
}
// compensate rounding error
if (inputElement.outerHeight() < (inputElement[0].scrollHeight - 1) && inputElement.val()) {
inputElement.height(inputElement.data('originalHeight') * 1.5);
}
}
private initResizableWindow() {
let element = this.element;
element.find('.jsxc-message-area').resizable({
handles: 'w, nw, n',
minHeight: 234,
minWidth: 250,
resize: function(ev, ui) {
//jsxc.gui.window.resize(element, ui);
},
start: function() {
element.removeClass('jsxc-normal');
},
stop: function() {
element.addClass('jsxc-normal');
}
});
}
private initEmoticonMenu() {
let inputElement = this.element.find('.jsxc-textinput');
let emoticonListElement = this.element.find('.jsxc-menu-emoticons ul');
let emoticonList = Emoticons.getDefaultEmoticonList();
emoticonList.forEach(emoticon => {
var li = $('<li>');
li.append(Emoticons.toImage(emoticon));
li.find('div').attr('title', emoticon);
li.click(function() {
inputElement.val(inputElement.val() + emoticon);
inputElement.focus();
});
emoticonListElement.prepend(li);
});
}
private restoreLocalHistory() {
this.messages = new SortedPersistentMap(this.storage, 'history', this.contact.getId());
this.messages.setPushHook(uid => {
let message = new Message(uid);
this.postMessage(message);
return message;
});
this.messages.init();
}
private resizeMessageArea(width?:number, height?:number, outer?) {
let element = this.element;
if (!element.attr('data-default-height')) {
element.attr('data-default-height', element.find('.ui-resizable').height());
}
if (!element.attr('data-default-width')) {
element.attr('data-default-width', element.find('.ui-resizable').width());
}
//@REVIEW ???
var outerHeightDiff = (outer) ? element.find('.jsxc-window').outerHeight() - element.find('.ui-resizable').height() : 0;
width = width || parseInt(element.attr('data-default-width'));
height = height || parseInt(element.attr('data-default-height')) + outerHeightDiff;
if (outer) {
height -= outerHeightDiff;
}
element.width(width);
// @TODO we don't use slimscroll anymore
element.find('.jsxc-message-area').slimScroll({
height: height
});
$(document).trigger('resize.window.jsxc', [this]);
}
private fullsizeMessageArea() {
let size:{width:number, height:number} = Options.get('viewport').getSize();
let barHeight = this.element.find('.jsxc-window-bar').outerHeight();
let inputHeight = this.inputElement.outerHeight();
size.width -= 10;
size.height -= barHeight + inputHeight;
this.resizeMessageArea(size.width, size.height);
}
private scrollMessageAreaToBottom() {
let messageArea = this.element.find('.jsxc-message-area');
messageArea[0].scrollTop = messageArea[0].scrollHeight;
}
}
// w = {
//
// updateProgress: function(message, sent, size) {
// var div = message.getDOM();
// var span = div.find('.jsxc-timestamp span');
//
// if (span.length === 0) {
// div.find('.jsxc-timestamp').append('<span>');
// span = div.find('.jsxc-timestamp span');
// }
//
// span.text(' ' + Math.round(sent / size * 100) + '%');
//
// if (sent === size) {
// span.remove();
// }
// },
//
// showOverlay: function(bid, content, allowClose) {
// var win = jsxc.gui.window.get(bid);
//
// win.find('.jsxc-overlay .jsxc-body').empty().append(content);
// win.find('.jsxc-overlay .jsxc-close').off('click').click(function() {
// jsxc.gui.window.hideOverlay(bid);
// });
//
// if (allowClose !== true) {
// win.find('.jsxc-overlay .jsxc-close').hide();
// } else {
// win.find('.jsxc-overlay .jsxc-close').show();
// }
//
// win.addClass('jsxc-showOverlay');
// },
//
// hideOverlay: function(bid) {
// var win = jsxc.gui.window.get(bid);
//
// win.removeClass('jsxc-showOverlay');
// },
//
// selectResource: function(bid, text, cb, res) {
// res = res || jsxc.storage.getUserItem('res', bid) || [];
// cb = cb || function() {};
//
// if (res.length > 0) {
// var content = $('<div>');
// var list = $('<ul>'),
// i, li;
//
// for (i = 0; i < res.length; i++) {
// li = $('<li>');
//
// li.append($('<a>').text(res[i]));
// li.appendTo(list);
// }
//
// list.find('a').click(function(ev) {
// ev.preventDefault();
//
// jsxc.gui.window.hideOverlay(bid);
//
// cb({
// status: 'selected',
// result: $(this).text()
// });
// });
//
// if (text) {
// $('<p>').text(text).appendTo(content);
// }
//
// list.appendTo(content);
//
// jsxc.gui.window.showOverlay(bid, content);
// } else {
// cb({
// status: 'unavailable'
// });
// }
// },
//
// smpRequest: function(bid, question) {
// var content = $('<div>');
//
// var p = $('<p>');
// p.text($.t('smpRequestReceived'));
// p.appendTo(content);
//
// var abort = $('<button>');
// abort.text($.t('Abort'));
// abort.click(function() {
// jsxc.gui.window.hideOverlay(bid);
// jsxc.storage.removeUserItem('smp', bid);
//
// if (jsxc.master && jsxc.otr.objects[bid]) {
// jsxc.otr.objects[bid].sm.abort();
// }
// });
// abort.appendTo(content);
//
// var verify = $('<button>');
// verify.text($.t('Verify'));
// verify.addClass('jsxc-btn jsxc-btn-primary');
// verify.click(function() {
// jsxc.gui.window.hideOverlay(bid);
//
// jsxc.otr.onSmpQuestion(bid, question);
// });
// verify.appendTo(content);
//
// jsxc.gui.window.showOverlay(bid, content);
// },
//
// sendFile: function(jid) {
// jsxc.fileTransfer.startGuiAction(jid);
// }
// };
+132
Ver Arquivo
@@ -0,0 +1,132 @@
import Templates from "../util/Templates";
import ChatWindow from "./ChatWindow"
import Client from "../Client"
let chatWindowListTemplate = require('../../template/chatWindowList.hbs');
export default class ChatWindowList {
private element;
private chatWindowList:any = {};
private static instance:ChatWindowList;
public static init():void {
ChatWindowList.get();
}
public static get():ChatWindowList {
if(!ChatWindowList.instance) {
ChatWindowList.instance = new ChatWindowList();
}
return ChatWindowList.instance;
}
private constructor() {
let template = chatWindowListTemplate();
this.element = $(template);
$('body').append(this.element);
let storage = Client.getStorage();
// $(window).resize(jsxc.gui.updateWindowListSB);
// $('#jsxc_windowList').resize(jsxc.gui.updateWindowListSB);
// $('#jsxc_windowListSB .jsxc_scrollLeft').click(function() {
// jsxc.gui.scrollWindowListBy(-200);
// });
// $('#jsxc_windowListSB .jsxc_scrollRight').click(function() {
// jsxc.gui.scrollWindowListBy(200);
// });
// $('#jsxc_windowList').on('wheel', function(ev) {
// if ($('#jsxc_windowList').data('isOver')) {
// jsxc.gui.scrollWindowListBy((ev.originalEvent.wheelDelta > 0) ? 200 : -200);
// }
// });
}
public closeAll() {
for (let chatWindowId in this.chatWindowList) {
let chatWindow:ChatWindow = this.chatWindowList[chatWindowId];
chatWindow.close();
}
}
public minimizeAll() {
for (let chatWindowId in this.chatWindowList) {
let chatWindow:ChatWindow = this.chatWindowList[chatWindowId];
chatWindow.minimize();
}
}
public add(chatWindow:ChatWindow) { console.log('add', chatWindow)
let chatWindowIds = this.getChatWindowIds();
if (chatWindowIds.indexOf(chatWindow.getId()) < 0) {
this.chatWindowList[chatWindow.getId()] = chatWindow;
// chatWindow.unminimize();
this.element.find('> ul').append(chatWindow.getDom());
} else {
chatWindow = this.chatWindowList[chatWindow.getId()];
}
this.updateWindowListSB();
return chatWindow;
}
public remove(chatWindow:ChatWindow) {
let chatWindowIds = this.getChatWindowIds();
if (chatWindowIds.indexOf(chatWindow.getId()) > -1) {
chatWindow.close();
delete this.chatWindowList[chatWindow.getId()];
}
this.updateWindowListSB();
}
private updateWindowListSB() {
if ($('#jsxc_windowList>ul').width() > $('#jsxc_windowList').width()) {
$('#jsxc_windowListSB > div').removeClass('jsxc_disabled');
} else {
$('#jsxc_windowListSB > div').addClass('jsxc_disabled');
$('#jsxc_windowList>ul').css('right', '0px');
}
}
private scrollWindowListBy(offset) {
var scrollWidth = $('#jsxc_windowList>ul').width();
var width = $('#jsxc_windowList').width();
var el = $('#jsxc_windowList>ul');
var right = parseInt(el.css('right')) - offset;
var padding = $("#jsxc_windowListSB").width();
if (scrollWidth < width) {
return;
}
if (right > 0) {
right = 0;
}
if (right < width - scrollWidth - padding) {
right = width - scrollWidth - padding;
}
el.css('right', right + 'px');
}
private getChatWindowIds() {
return Object.keys(this.chatWindowList || {});
}
}

Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais