Comparar commits
11 Commits
v3.2.0
...
refactoring
| Autor | SHA1 | Data | |
|---|---|---|---|
| efb3dd025f | |||
| 457e175a41 | |||
| 757770b9f7 | |||
| 3dee2ef385 | |||
| e70b2481a8 | |||
| 9023a4c9c8 | |||
| c5ad776f8c | |||
| 2bb914cb9a | |||
| 9158727327 | |||
| e904bd8045 | |||
| 4fb2ea3ee3 |
+1
-1
@@ -18,7 +18,7 @@ linters:
|
||||
ImportantRule:
|
||||
enabled: false
|
||||
Indentation:
|
||||
width: 4
|
||||
width: 3
|
||||
LeadingZero:
|
||||
style: include_zero
|
||||
NameFormat:
|
||||
|
||||
@@ -9,3 +9,7 @@ before_install:
|
||||
|
||||
script:
|
||||
- ./node_modules/.bin/grunt pre-commit
|
||||
|
||||
branches:
|
||||
except:
|
||||
- refactoring
|
||||
|
||||
-402
@@ -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
@@ -1,4 +1,4 @@
|
||||
# JavaScript XMPP Client
|
||||
# JavaScript XMPP Client 4.0
|
||||
|
||||
[](https://travis-ci.org/jsxc/jsxc)
|
||||
[](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`.
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
externo
+7
@@ -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
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -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>
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
%fullscreen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9000;
|
||||
background-color: $fullscreen_bg;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
externo
+61
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import Options from './Options'
|
||||
|
||||
const EMOTICONS = [
|
||||
['O:-) O:)', 'innocent'],
|
||||
['>:-( >:( >:-( >:(', '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'],
|
||||
['@->-- @->--', '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');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
interface Identifiable {
|
||||
getId():string
|
||||
}
|
||||
|
||||
export default Identifiable;
|
||||
@@ -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, "\\");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>';
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import Connection from './connection/Connection'
|
||||
|
||||
export interface PluginInterface {
|
||||
constructor:(Connection);
|
||||
|
||||
};
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) + '"]';
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
var jsxc = null, RTC = null, RTCPeerconnection = null;
|
||||
|
||||
(function($) {
|
||||
"use strict";
|
||||
@@ -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';
|
||||
@@ -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
@@ -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(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
@@ -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';
|
||||
}
|
||||
};
|
||||
@@ -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
@@ -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;
|
||||
});
|
||||
};
|
||||
@@ -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);
|
||||
@@ -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 +0,0 @@
|
||||
}(jQuery));
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
// }
|
||||
// };
|
||||
@@ -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
Referência em uma Nova Issue
Bloquear um usuário