fixes #337
It's now possible to set your VCard via the UI and via the API
This commit is contained in:
parent
6c513ad4be
commit
708b1dbe99
@ -5,6 +5,8 @@
|
||||
## New Features
|
||||
|
||||
- #161 XEP-0363: HTTP File Upload
|
||||
- #337 API call to update a VCard
|
||||
- It's now also possible to edit your VCard via the UI
|
||||
- Automatically grow/shrink input as text is entered/removed
|
||||
- MP4 and MP3 files when sent as XEP-0066 Out of Band Data, are now playable directly in chat
|
||||
- Support for rendering URLs sent according to XEP-0066 Out of Band Data.
|
||||
|
@ -4615,6 +4615,88 @@
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
background-color: #1d2124; }
|
||||
#conversejs .alert {
|
||||
position: relative;
|
||||
padding: 0.75rem 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.25rem; }
|
||||
#conversejs .alert-heading {
|
||||
color: inherit; }
|
||||
#conversejs .alert-link {
|
||||
font-weight: 700; }
|
||||
#conversejs .alert-dismissible {
|
||||
padding-right: 4rem; }
|
||||
#conversejs .alert-dismissible .close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 0.75rem 1.25rem;
|
||||
color: inherit; }
|
||||
#conversejs .alert-primary {
|
||||
color: #1d3d4c;
|
||||
background-color: #d7e3e9;
|
||||
border-color: #c7d8e0; }
|
||||
#conversejs .alert-primary hr {
|
||||
border-top-color: #b7cdd7; }
|
||||
#conversejs .alert-primary .alert-link {
|
||||
color: #0f1f27; }
|
||||
#conversejs .alert-secondary {
|
||||
color: #383d41;
|
||||
background-color: #e2e3e5;
|
||||
border-color: #d6d8db; }
|
||||
#conversejs .alert-secondary hr {
|
||||
border-top-color: #c8cbcf; }
|
||||
#conversejs .alert-secondary .alert-link {
|
||||
color: #202326; }
|
||||
#conversejs .alert-success {
|
||||
color: #1e5637;
|
||||
background-color: #d8ede1;
|
||||
border-color: #c8e6d5; }
|
||||
#conversejs .alert-success hr {
|
||||
border-top-color: #b6dec8; }
|
||||
#conversejs .alert-success .alert-link {
|
||||
color: #11301f; }
|
||||
#conversejs .alert-info {
|
||||
color: #0c5460;
|
||||
background-color: #d1ecf1;
|
||||
border-color: #bee5eb; }
|
||||
#conversejs .alert-info hr {
|
||||
border-top-color: #abdde5; }
|
||||
#conversejs .alert-info .alert-link {
|
||||
color: #062c33; }
|
||||
#conversejs .alert-warning {
|
||||
color: #856404;
|
||||
background-color: #fff3cd;
|
||||
border-color: #ffeeba; }
|
||||
#conversejs .alert-warning hr {
|
||||
border-top-color: #ffe8a1; }
|
||||
#conversejs .alert-warning .alert-link {
|
||||
color: #533f03; }
|
||||
#conversejs .alert-danger {
|
||||
color: #783a2a;
|
||||
background-color: #fae2dc;
|
||||
border-color: #f8d7ce; }
|
||||
#conversejs .alert-danger hr {
|
||||
border-top-color: #f5c5b8; }
|
||||
#conversejs .alert-danger .alert-link {
|
||||
color: #52281d; }
|
||||
#conversejs .alert-light {
|
||||
color: #818182;
|
||||
background-color: #fefefe;
|
||||
border-color: #fdfdfe; }
|
||||
#conversejs .alert-light hr {
|
||||
border-top-color: #ececf6; }
|
||||
#conversejs .alert-light .alert-link {
|
||||
color: #686868; }
|
||||
#conversejs .alert-dark {
|
||||
color: #1b1e21;
|
||||
background-color: #d6d8d9;
|
||||
border-color: #c6c8ca; }
|
||||
#conversejs .alert-dark hr {
|
||||
border-top-color: #b9bbbe; }
|
||||
#conversejs .alert-dark .alert-link {
|
||||
color: #040505; }
|
||||
#conversejs .media {
|
||||
display: flex;
|
||||
align-items: flex-start; }
|
||||
@ -7067,7 +7149,8 @@ body.reset {
|
||||
text-align: center;
|
||||
width: 100%; }
|
||||
#conversejs .avatar {
|
||||
border-radius: 10%; }
|
||||
border-radius: 10%;
|
||||
border: 1px solid lightgrey; }
|
||||
#conversejs .activated {
|
||||
display: block !important; }
|
||||
#conversejs .button-primary {
|
||||
@ -8417,8 +8500,7 @@ body.reset {
|
||||
margin-top: 0.5em;
|
||||
height: 36px;
|
||||
vertical-align: middle;
|
||||
width: 36px;
|
||||
border: 1px solid lightgrey; }
|
||||
width: 36px; }
|
||||
#conversejs .message.chat-msg .chat-msg-heading {
|
||||
margin-top: 0.5em;
|
||||
padding-right: 0.25rem;
|
||||
|
108
css/inverse.css
108
css/inverse.css
@ -4615,6 +4615,88 @@
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
background-color: #1d2124; }
|
||||
#conversejs .alert {
|
||||
position: relative;
|
||||
padding: 0.75rem 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.25rem; }
|
||||
#conversejs .alert-heading {
|
||||
color: inherit; }
|
||||
#conversejs .alert-link {
|
||||
font-weight: 700; }
|
||||
#conversejs .alert-dismissible {
|
||||
padding-right: 4rem; }
|
||||
#conversejs .alert-dismissible .close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 0.75rem 1.25rem;
|
||||
color: inherit; }
|
||||
#conversejs .alert-primary {
|
||||
color: #1d3d4c;
|
||||
background-color: #d7e3e9;
|
||||
border-color: #c7d8e0; }
|
||||
#conversejs .alert-primary hr {
|
||||
border-top-color: #b7cdd7; }
|
||||
#conversejs .alert-primary .alert-link {
|
||||
color: #0f1f27; }
|
||||
#conversejs .alert-secondary {
|
||||
color: #383d41;
|
||||
background-color: #e2e3e5;
|
||||
border-color: #d6d8db; }
|
||||
#conversejs .alert-secondary hr {
|
||||
border-top-color: #c8cbcf; }
|
||||
#conversejs .alert-secondary .alert-link {
|
||||
color: #202326; }
|
||||
#conversejs .alert-success {
|
||||
color: #1e5637;
|
||||
background-color: #d8ede1;
|
||||
border-color: #c8e6d5; }
|
||||
#conversejs .alert-success hr {
|
||||
border-top-color: #b6dec8; }
|
||||
#conversejs .alert-success .alert-link {
|
||||
color: #11301f; }
|
||||
#conversejs .alert-info {
|
||||
color: #0c5460;
|
||||
background-color: #d1ecf1;
|
||||
border-color: #bee5eb; }
|
||||
#conversejs .alert-info hr {
|
||||
border-top-color: #abdde5; }
|
||||
#conversejs .alert-info .alert-link {
|
||||
color: #062c33; }
|
||||
#conversejs .alert-warning {
|
||||
color: #856404;
|
||||
background-color: #fff3cd;
|
||||
border-color: #ffeeba; }
|
||||
#conversejs .alert-warning hr {
|
||||
border-top-color: #ffe8a1; }
|
||||
#conversejs .alert-warning .alert-link {
|
||||
color: #533f03; }
|
||||
#conversejs .alert-danger {
|
||||
color: #783a2a;
|
||||
background-color: #fae2dc;
|
||||
border-color: #f8d7ce; }
|
||||
#conversejs .alert-danger hr {
|
||||
border-top-color: #f5c5b8; }
|
||||
#conversejs .alert-danger .alert-link {
|
||||
color: #52281d; }
|
||||
#conversejs .alert-light {
|
||||
color: #818182;
|
||||
background-color: #fefefe;
|
||||
border-color: #fdfdfe; }
|
||||
#conversejs .alert-light hr {
|
||||
border-top-color: #ececf6; }
|
||||
#conversejs .alert-light .alert-link {
|
||||
color: #686868; }
|
||||
#conversejs .alert-dark {
|
||||
color: #1b1e21;
|
||||
background-color: #d6d8d9;
|
||||
border-color: #c6c8ca; }
|
||||
#conversejs .alert-dark hr {
|
||||
border-top-color: #b9bbbe; }
|
||||
#conversejs .alert-dark .alert-link {
|
||||
color: #040505; }
|
||||
#conversejs .media {
|
||||
display: flex;
|
||||
align-items: flex-start; }
|
||||
@ -7067,7 +7149,8 @@ body.reset {
|
||||
text-align: center;
|
||||
width: 100%; }
|
||||
#conversejs .avatar {
|
||||
border-radius: 10%; }
|
||||
border-radius: 10%;
|
||||
border: 1px solid lightgrey; }
|
||||
#conversejs .activated {
|
||||
display: block !important; }
|
||||
#conversejs .button-primary {
|
||||
@ -7203,17 +7286,15 @@ body {
|
||||
#conversejs.fullscreen .converse-chatboxes {
|
||||
width: 100vw;
|
||||
right: 15px; }
|
||||
#conversejs.fullscreen form.converse-form {
|
||||
margin: 1em; }
|
||||
#conversejs.fullscreen form.converse-form input[type=checkbox] {
|
||||
margin-left: 1em;
|
||||
display: inline;
|
||||
margin-bottom: 2em; }
|
||||
#conversejs.fullscreen form.converse-form input[type=button],
|
||||
#conversejs.fullscreen form.converse-form input[type=submit] {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
margin-right: 1em; }
|
||||
#conversejs.fullscreen form.converse-form input[type=checkbox] {
|
||||
margin-left: 1em;
|
||||
display: inline;
|
||||
margin-bottom: 2em; }
|
||||
#conversejs.fullscreen form.converse-form input[type=button],
|
||||
#conversejs.fullscreen form.converse-form input[type=submit] {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
margin-right: 1em; }
|
||||
|
||||
#conversejs #user-profile-modal label {
|
||||
font-weight: bold; }
|
||||
@ -8605,8 +8686,7 @@ body {
|
||||
margin-top: 0.5em;
|
||||
height: 36px;
|
||||
vertical-align: middle;
|
||||
width: 36px;
|
||||
border: 1px solid lightgrey; }
|
||||
width: 36px; }
|
||||
#conversejs .message.chat-msg .chat-msg-heading {
|
||||
margin-top: 0.5em;
|
||||
padding-right: 0.25rem;
|
||||
|
53
dev.html
53
dev.html
@ -9,46 +9,17 @@
|
||||
<meta name="author" content="JC Brand" />
|
||||
<meta name="keywords" content="xmpp chat webchat converse.js" />
|
||||
<link rel="shortcut icon" type="image/ico" href="css/images/favicon.ico"/>
|
||||
<link type="text/css" rel="stylesheet" media="screen" href="css/bootstrap.min.css" />
|
||||
<link type="text/css" rel="stylesheet" media="screen" href="css/font-awesome.min.css" />
|
||||
<link type="text/css" rel="stylesheet" media="screen" href="css/theme.css" />
|
||||
<link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" />
|
||||
<link type="text/css" rel="stylesheet" media="screen" href="css/inverse.css" />
|
||||
<script src="node_modules/requirejs/require.js"></script>
|
||||
<script src="src/config.js"></script>
|
||||
</head>
|
||||
|
||||
<body id="page-top" class="reset" data-target=".navbar-custom">
|
||||
<nav class="navbar navbar-custom navbar-fixed-top" role="navigation">
|
||||
<div class="container">
|
||||
<div class="navbar-header page-scroll">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-main-collapse">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse navbar-right navbar-main-collapse">
|
||||
<ul class="nav navbar-nav"><li> <a href="/docs/html/index.html">Documentation</a> </li>
|
||||
</ul>
|
||||
</div>
|
||||
<body class="reset">
|
||||
<div class="content">
|
||||
<div class="inner-content">
|
||||
<h1 class="brand-heading"><i class="icon-conversejs"></i> Converse</h1>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section class="intro">
|
||||
<div class="intro-body">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<h1 class="brand-heading"><i class="icon-conversejs"></i>converse</h1>
|
||||
<p class="intro-text">Developer page.</p>
|
||||
<p class="intro-text">
|
||||
Converse.js will only work on this page if you have
|
||||
<a href="https://conversejs.org/docs/html/development.html">set up the development environment</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
require(['converse'], function (converse) {
|
||||
@ -60,24 +31,18 @@
|
||||
// 'prosody@conference.prosody.im',
|
||||
// 'jdev@conference.jabber.org'
|
||||
// ],
|
||||
hide_open_bookmarks: true,
|
||||
view_mode: 'fullscreen',
|
||||
archived_messages_page_size: '500',
|
||||
allow_public_bookmarks: true,
|
||||
notify_all_room_messages: [
|
||||
'discuss@conference.conversejs.org'
|
||||
],
|
||||
auto_join_private_chats: [
|
||||
'opkode@jappix.com'
|
||||
],
|
||||
auto_reconnect: true,
|
||||
// bosh_service_url: 'http://chat.example.org:5280/http-bind/',
|
||||
bosh_service_url: 'https://conversejs.org/http-bind/', // Please use this connection manager only for testing purposes
|
||||
message_archiving: 'always',
|
||||
show_controlbox_by_default: true,
|
||||
strict_plugin_dependencies: false,
|
||||
chatstate_notification_blacklist: ['mulles@movim.eu'],
|
||||
xhr_user_search: false,
|
||||
debug: true
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1293,6 +1293,37 @@ Example:
|
||||
}
|
||||
});
|
||||
|
||||
set
|
||||
~~~
|
||||
|
||||
Parameters:
|
||||
|
||||
* ``data`` a map of VCard keys and values
|
||||
|
||||
Enables setting new values for a VCard.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
converse.plugins.add('myplugin', {
|
||||
initialize: function () {
|
||||
|
||||
_converse.api.waitUntil('rosterContactsFetched').then(() => {
|
||||
this._converse.api.vcard.set({
|
||||
'jid': 'someone@example.org',
|
||||
'fn': 'Someone Somewhere',
|
||||
'nickname': 'someone'
|
||||
}).then(() => {
|
||||
// Succes
|
||||
}).catch(() => {
|
||||
// Failure
|
||||
}).
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
update
|
||||
~~~~~~
|
||||
|
||||
|
@ -53,11 +53,10 @@
|
||||
</div>
|
||||
|
||||
<div class="message chat-msg">
|
||||
<div class="avatar montague"></div>
|
||||
<canvas data-avatar="/mockup/images/romeo.jpg" height="36" width="36" class="avatar"></canvas>
|
||||
<div class="chat-msg-content">
|
||||
<span class="chat-msg-heading">
|
||||
<span class="chat-msg-author">Romeo Montague</span>
|
||||
<span class="chat-msg-author">Romeo Montague <span class="badge badge-primary">Developer</span></span>
|
||||
<span class="chat-msg-time">15:31</span>
|
||||
</span>
|
||||
<span class="chat-msg-text">He jests at scars that never felt a wound.</span>
|
||||
|
@ -363,6 +363,7 @@ body.reset {
|
||||
|
||||
.avatar {
|
||||
border-radius: 10%;
|
||||
border: 1px solid lightgrey;
|
||||
}
|
||||
|
||||
.activated {
|
||||
|
@ -128,7 +128,6 @@
|
||||
height: 36px;
|
||||
vertical-align: middle;
|
||||
width: 36px;
|
||||
border: 1px solid lightgrey;
|
||||
}
|
||||
.chat-msg-heading {
|
||||
margin-top: 0.5em;
|
||||
|
@ -30,6 +30,7 @@
|
||||
@import "bootstrap/scss/card";
|
||||
@import "bootstrap/scss/breadcrumb";
|
||||
@import "bootstrap/scss/badge";
|
||||
@import "bootstrap/scss/alert";
|
||||
@import "bootstrap/scss/media";
|
||||
@import "bootstrap/scss/list-group";
|
||||
@import "bootstrap/scss/close";
|
||||
|
@ -29,6 +29,7 @@
|
||||
@import "bootstrap/scss/card";
|
||||
@import "bootstrap/scss/breadcrumb";
|
||||
@import "bootstrap/scss/badge";
|
||||
@import "bootstrap/scss/alert";
|
||||
@import "bootstrap/scss/media";
|
||||
@import "bootstrap/scss/list-group";
|
||||
@import "bootstrap/scss/close";
|
||||
|
@ -43,7 +43,6 @@ body {
|
||||
|
||||
form {
|
||||
&.converse-form {
|
||||
margin: 1em;
|
||||
input[type=checkbox] {
|
||||
margin-left: 1em;
|
||||
display: inline;
|
||||
|
@ -9,6 +9,7 @@
|
||||
(function (root, factory) {
|
||||
define(["converse-core",
|
||||
"bootstrap",
|
||||
"tpl!alert",
|
||||
"tpl!chat_status_modal",
|
||||
"tpl!profile_modal",
|
||||
"tpl!profile_view",
|
||||
@ -19,6 +20,7 @@
|
||||
}(this, function (
|
||||
converse,
|
||||
bootstrap,
|
||||
tpl_alert,
|
||||
tpl_chat_status_modal,
|
||||
tpl_profile_modal,
|
||||
tpl_profile_view,
|
||||
@ -32,7 +34,7 @@
|
||||
|
||||
converse.plugins.add('converse-profile', {
|
||||
|
||||
dependencies: ["converse-modal"],
|
||||
dependencies: ["converse-modal", "converse-vcard"],
|
||||
|
||||
initialize () {
|
||||
/* The initialize function gets called as soon as the plugin is
|
||||
@ -43,13 +45,96 @@
|
||||
|
||||
|
||||
_converse.ProfileModal = _converse.BootstrapModal.extend({
|
||||
events: {
|
||||
'click .change-avatar': "openFileSelection",
|
||||
'change input[type="file"': "updateFilePreview",
|
||||
'submit form': 'onFormSubmitted'
|
||||
},
|
||||
|
||||
initialize () {
|
||||
_converse.BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
this.model.on('change', this.render, this);
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_profile_modal(_.extend(this.model.toJSON(), {
|
||||
'heading_profile': __('Your Profile'),
|
||||
'label_close': __('Close')
|
||||
'label_close': __('Close'),
|
||||
'label_email': __('Email'),
|
||||
'label_fullname': __('Full Name'),
|
||||
'label_nickname': __('Nickname'),
|
||||
'label_jid': __('XMPP Address (JID)'),
|
||||
'label_role': __('Role'),
|
||||
'label_save': __('Save'),
|
||||
'label_url': __('URL'),
|
||||
'alt_avatar': __('Your avatar image')
|
||||
}));
|
||||
},
|
||||
|
||||
openFileSelection (ev) {
|
||||
ev.preventDefault();
|
||||
this.el.querySelector('input[type="file"]').click();
|
||||
},
|
||||
|
||||
updateFilePreview (ev) {
|
||||
const file = ev.target.files[0],
|
||||
reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
this.el.querySelector('.avatar').setAttribute('src', reader.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
},
|
||||
|
||||
setVCard (body, data) {
|
||||
_converse.api.vcard.set(data)
|
||||
.then(() => {
|
||||
_converse.api.vcard.update(this.model, true);
|
||||
|
||||
const html = tpl_alert({
|
||||
'message': __('Profile data succesfully saved'),
|
||||
'type': 'alert-primary'
|
||||
});
|
||||
body.insertAdjacentHTML('afterBegin', html);
|
||||
}).catch((err) => {
|
||||
_converse.log(err, Strophe.LogLevel.FATAL);
|
||||
const html = tpl_alert({
|
||||
'message': __('An error happened while trying to save your profile data'),
|
||||
'type': 'alert-danger'
|
||||
});
|
||||
body.insertAdjacentHTML('afterBegin', html);
|
||||
});
|
||||
},
|
||||
|
||||
onFormSubmitted (ev) {
|
||||
ev.preventDefault();
|
||||
const reader = new FileReader(),
|
||||
form_data = new FormData(ev.target),
|
||||
body = this.el.querySelector('.modal-body'),
|
||||
image_file = form_data.get('image');
|
||||
|
||||
const data = {
|
||||
'fn': form_data.get('fn'),
|
||||
'role': form_data.get('role'),
|
||||
'email': form_data.get('email'),
|
||||
'url': form_data.get('url'),
|
||||
};
|
||||
if (!image_file.size) {
|
||||
_.extend(data, {
|
||||
'image': this.model.get('image'),
|
||||
'image_type': this.model.get('image_type')
|
||||
});
|
||||
this.setVCard(body, data);
|
||||
} else {
|
||||
reader.onloadend = () => {
|
||||
_.extend(data, {
|
||||
'image': btoa(reader.result),
|
||||
'image_type': image_file.type
|
||||
});
|
||||
this.setVCard(body, data);
|
||||
};
|
||||
reader.readAsBinaryString(image_file);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
@ -5,68 +5,13 @@
|
||||
// Licensed under the Mozilla Public License (MPLv2)
|
||||
|
||||
(function (root, factory) {
|
||||
define(["converse-core", "crypto"], factory);
|
||||
}(this, function (converse, CryptoJS) {
|
||||
define(["converse-core", "crypto", "tpl!vcard"], factory);
|
||||
}(this, function (converse, CryptoJS, tpl_vcard) {
|
||||
"use strict";
|
||||
const { Backbone, Promise, Strophe, SHA1, _, $iq, b64_sha1, moment, sizzle } = converse.env;
|
||||
const { Backbone, Promise, Strophe, SHA1, _, $iq, $build, b64_sha1, moment, sizzle } = converse.env;
|
||||
const u = converse.env.utils;
|
||||
|
||||
|
||||
function onVCardData (_converse, jid, iq, callback) {
|
||||
const vcard = iq.querySelector('vCard');
|
||||
let result = {};
|
||||
if (!_.isNull(vcard)) {
|
||||
result = {
|
||||
'stanza': iq,
|
||||
'fullname': _.get(vcard.querySelector('FN'), 'textContent'),
|
||||
'image': _.get(vcard.querySelector('PHOTO BINVAL'), 'textContent'),
|
||||
'image_type': _.get(vcard.querySelector('PHOTO TYPE'), 'textContent'),
|
||||
'url': _.get(vcard.querySelector('URL'), 'textContent')
|
||||
};
|
||||
}
|
||||
if (result.image) {
|
||||
const word_array_from_b64 = CryptoJS.enc.Base64.parse(result['image']);
|
||||
result['image_type'] = CryptoJS.SHA1(word_array_from_b64).toString()
|
||||
}
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
}
|
||||
|
||||
function onVCardError (_converse, jid, iq, errback) {
|
||||
if (errback) {
|
||||
errback({'stanza': iq, 'jid': jid});
|
||||
}
|
||||
}
|
||||
|
||||
function createStanza (type, jid, vcard_el) {
|
||||
const iq = $iq(jid ? {'type': type, 'to': jid} : {'type': type});
|
||||
iq.c("vCard", {'xmlns': Strophe.NS.VCARD});
|
||||
if (vcard_el) {
|
||||
iq.cnode(vcard_el);
|
||||
}
|
||||
return iq;
|
||||
}
|
||||
|
||||
function getVCard (_converse, jid) {
|
||||
/* Request the VCard of another user. Returns a promise.
|
||||
*
|
||||
* Parameters:
|
||||
* (String) jid - The Jabber ID of the user whose VCard
|
||||
* is being requested.
|
||||
*/
|
||||
jid = Strophe.getBareJidFromJid(jid) === _converse.bare_jid ? null : jid;
|
||||
return new Promise((resolve, reject) => {
|
||||
_converse.connection.sendIQ(
|
||||
createStanza("get", jid),
|
||||
_.partial(onVCardData, _converse, jid, _, resolve),
|
||||
_.partial(onVCardError, _converse, jid, _, resolve),
|
||||
5000
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
converse.plugins.add('converse-vcard', {
|
||||
|
||||
initialize () {
|
||||
@ -84,6 +29,75 @@
|
||||
});
|
||||
|
||||
|
||||
function onVCardData (_converse, jid, iq, callback) {
|
||||
const vcard = iq.querySelector('vCard');
|
||||
let result = {};
|
||||
if (!_.isNull(vcard)) {
|
||||
result = {
|
||||
'stanza': iq,
|
||||
'fullname': _.get(vcard.querySelector('FN'), 'textContent'),
|
||||
'nickname': _.get(vcard.querySelector('NICKNAME'), 'textContent'),
|
||||
'image': _.get(vcard.querySelector('PHOTO BINVAL'), 'textContent'),
|
||||
'image_type': _.get(vcard.querySelector('PHOTO TYPE'), 'textContent'),
|
||||
'url': _.get(vcard.querySelector('URL'), 'textContent'),
|
||||
'role': _.get(vcard.querySelector('ROLE'), 'textContent'),
|
||||
'email': _.get(vcard.querySelector('EMAIL USERID'), 'textContent')
|
||||
};
|
||||
}
|
||||
if (result.image) {
|
||||
const word_array_from_b64 = CryptoJS.enc.Base64.parse(result['image']);
|
||||
result['image_type'] = CryptoJS.SHA1(word_array_from_b64).toString()
|
||||
}
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
}
|
||||
|
||||
function onVCardError (_converse, jid, iq, errback) {
|
||||
if (errback) {
|
||||
errback({'stanza': iq, 'jid': jid});
|
||||
}
|
||||
}
|
||||
|
||||
function createStanza (type, jid, vcard_el) {
|
||||
const iq = $iq(jid ? {'type': type, 'to': jid} : {'type': type});
|
||||
if (!vcard_el) {
|
||||
iq.c("vCard", {'xmlns': Strophe.NS.VCARD});
|
||||
} else {
|
||||
iq.cnode(vcard_el);
|
||||
}
|
||||
return iq;
|
||||
}
|
||||
|
||||
function setVCard (data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const vcard_el = Strophe.xmlHtmlNode(tpl_vcard(data)).firstElementChild;
|
||||
_converse.connection.sendIQ(
|
||||
createStanza("set", data.jid, vcard_el),
|
||||
resolve,
|
||||
reject
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function getVCard (_converse, jid) {
|
||||
/* Request the VCard of another user. Returns a promise.
|
||||
*
|
||||
* Parameters:
|
||||
* (String) jid - The Jabber ID of the user whose VCard
|
||||
* is being requested.
|
||||
*/
|
||||
jid = Strophe.getBareJidFromJid(jid) === _converse.bare_jid ? null : jid;
|
||||
return new Promise((resolve, reject) => {
|
||||
_converse.connection.sendIQ(
|
||||
createStanza("get", jid),
|
||||
_.partial(onVCardData, _converse, jid, _, resolve),
|
||||
_.partial(onVCardError, _converse, jid, _, resolve),
|
||||
5000
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/* Event handlers */
|
||||
_converse.initVCardCollection = function () {
|
||||
_converse.vcards = new _converse.VCards();
|
||||
@ -108,6 +122,8 @@
|
||||
|
||||
_.extend(_converse.api, {
|
||||
'vcard': {
|
||||
'set': setVCard,
|
||||
|
||||
'get' (model, force) {
|
||||
if (_.isString(model)) {
|
||||
return getVCard(_converse, model);
|
||||
@ -126,7 +142,7 @@
|
||||
return new Promise((resolve, reject) => {
|
||||
this.get(model, force).then((vcard) => {
|
||||
model.save(_.extend(
|
||||
_.pick(vcard, ['fullname', 'url', 'image_type', 'image', 'image_hash']),
|
||||
_.pick(vcard, ['fullname', 'nickname', 'email', 'url', 'role', 'image_type', 'image', 'image_hash']),
|
||||
{'vcard_updated': moment().format()}
|
||||
));
|
||||
resolve();
|
||||
|
1
src/templates/alert.html
Normal file
1
src/templates/alert.html
Normal file
@ -0,0 +1 @@
|
||||
<div class="alert {{{o.type}}}" role="alert">{{{o.message}}}</div>
|
@ -5,27 +5,53 @@
|
||||
<h5 class="modal-title" id="user-profile-modal-label">{{{o.heading_profile}}}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="{{{o.label_close}}}"><span aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
{[ if (o.image) { ]}
|
||||
<a class="show-profile" href="#">
|
||||
<img alt="User Avatar" class="img-thumbnail avatar align-self-center" height="100px" width="100px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
|
||||
</a>
|
||||
{[ } ]}
|
||||
<form class="converse-form">
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<a class="change-avatar" href="#">
|
||||
{[ if (o.image) { ]}
|
||||
<img alt="{{{o.alt_avatar}}}" class="img-thumbnail avatar align-self-center" height="100px" width="100px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
|
||||
{[ } ]}
|
||||
{[ if (!o.image) { ]}
|
||||
<canvas class="avatar" height="100px" width="100px"/>
|
||||
{[ } ]}
|
||||
</a>
|
||||
<input class="hidden" name="image" type="file">
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-group">
|
||||
<label class="col-form-label">{{{o.label_jid}}}:</label>
|
||||
<div>{{{o.jid}}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div classs="row w-100">
|
||||
<label>Fullname:</label>
|
||||
<span class="username">{{{o.fullname}}}</span>
|
||||
</div>
|
||||
<div classs="row w-100">
|
||||
<label>XMPP Address:</label>
|
||||
<span class="username">{{{o.jid}}}</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="vcard-fullname" class="col-form-label">{{{o.label_fullname}}}:</label>
|
||||
<input id="vcard-fullname" type="text" class="form-control" name="fn" value="{{{o.fullname}}}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="vcard-nickname" class="col-form-label">{{{o.label_nickname}}}:</label>
|
||||
<input id="vcard-nickname" type="text" class="form-control" name="nickname" value="{{{o.nickname}}}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="vcard-url" class="col-form-label">{{{o.label_url}}}:</label>
|
||||
<input id="vcard-url" type="url" class="form-control" name="url" value="{{{o.url}}}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="vcard-email" class="col-form-label">{{{o.label_email}}}:</label>
|
||||
<input id="vcard-email" type="email" class="form-control" name="email" value="{{{o.email}}}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="vcard-role" class="col-form-label">{{{o.label_role}}}:</label>
|
||||
<input id="vcard-role" type="text" class="form-control" name="role" value="{{{o.role}}}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="save-form btn btn-primary">{{{o.label_save}}}</button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{{o.label_close}}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,10 +1,8 @@
|
||||
<div class="userinfo">
|
||||
<div class="profile d-flex">
|
||||
{[ if (o.image) { ]}
|
||||
<a class="show-profile" href="#">
|
||||
<img alt="User Avatar" class="avatar align-self-center" height="40px" width="40px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
|
||||
</a>
|
||||
{[ } ]}
|
||||
<span class="username w-100 align-self-center">{{{o.fullname}}}</span>
|
||||
<!-- <a class="chatbox-btn fa fa-vcard align-self-center" title="{{{o.title_your_profile}}}" data-toggle="modal" data-target="#userProfileModal"></a> -->
|
||||
<!-- <a class="chatbox-btn fa fa-cog align-self-center" title="{{{o.title_change_status}}}" data-toggle="modal" data-target="#settingsModal"></a> -->
|
||||
|
11
src/templates/vcard.html
Normal file
11
src/templates/vcard.html
Normal file
@ -0,0 +1,11 @@
|
||||
<vCard xmlns="vcard-temp">
|
||||
<FN>{{{o.fn}}}</FN>
|
||||
<NICKNAME>{{{o.fn}}}</NICKNAME>
|
||||
<URL>{{{o.url}}}</URL>
|
||||
<ROLE>{{{o.role}}}</ROLE>
|
||||
<EMAIL><INTERNET/><PREF/><USERID>{{{o.email}}}</USERID></EMAIL>
|
||||
<PHOTO>
|
||||
<TYPE>{{{o.image_type}}}</TYPE>
|
||||
<BINVAL>{{{o.image}}}</BINVAL>
|
||||
</PHOTO>
|
||||
</vCard>
|
@ -40,6 +40,7 @@
|
||||
root.sizzle,
|
||||
root.Promise,
|
||||
root._,
|
||||
root.Backbone,
|
||||
Strophe
|
||||
);
|
||||
}
|
||||
@ -56,7 +57,6 @@
|
||||
tpl_video
|
||||
) {
|
||||
"use strict";
|
||||
const b64_sha1 = Strophe.SHA1.b64_sha1;
|
||||
Strophe = Strophe.Strophe;
|
||||
|
||||
const URL_REGEX = /\b(https?:\/\/|www\.|https?:\/\/www\.)[^\s<>]{2,200}\b\/?/g;
|
||||
|
Loading…
Reference in New Issue
Block a user