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
|
## New Features
|
||||||
|
|
||||||
- #161 XEP-0363: HTTP File Upload
|
- #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
|
- 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
|
- 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.
|
- Support for rendering URLs sent according to XEP-0066 Out of Band Data.
|
||||||
|
@ -4615,6 +4615,88 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
background-color: #1d2124; }
|
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 {
|
#conversejs .media {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start; }
|
align-items: flex-start; }
|
||||||
@ -7067,7 +7149,8 @@ body.reset {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%; }
|
width: 100%; }
|
||||||
#conversejs .avatar {
|
#conversejs .avatar {
|
||||||
border-radius: 10%; }
|
border-radius: 10%;
|
||||||
|
border: 1px solid lightgrey; }
|
||||||
#conversejs .activated {
|
#conversejs .activated {
|
||||||
display: block !important; }
|
display: block !important; }
|
||||||
#conversejs .button-primary {
|
#conversejs .button-primary {
|
||||||
@ -8417,8 +8500,7 @@ body.reset {
|
|||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 36px;
|
width: 36px; }
|
||||||
border: 1px solid lightgrey; }
|
|
||||||
#conversejs .message.chat-msg .chat-msg-heading {
|
#conversejs .message.chat-msg .chat-msg-heading {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
padding-right: 0.25rem;
|
padding-right: 0.25rem;
|
||||||
|
108
css/inverse.css
108
css/inverse.css
@ -4615,6 +4615,88 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
background-color: #1d2124; }
|
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 {
|
#conversejs .media {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start; }
|
align-items: flex-start; }
|
||||||
@ -7067,7 +7149,8 @@ body.reset {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%; }
|
width: 100%; }
|
||||||
#conversejs .avatar {
|
#conversejs .avatar {
|
||||||
border-radius: 10%; }
|
border-radius: 10%;
|
||||||
|
border: 1px solid lightgrey; }
|
||||||
#conversejs .activated {
|
#conversejs .activated {
|
||||||
display: block !important; }
|
display: block !important; }
|
||||||
#conversejs .button-primary {
|
#conversejs .button-primary {
|
||||||
@ -7203,17 +7286,15 @@ body {
|
|||||||
#conversejs.fullscreen .converse-chatboxes {
|
#conversejs.fullscreen .converse-chatboxes {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
right: 15px; }
|
right: 15px; }
|
||||||
#conversejs.fullscreen form.converse-form {
|
#conversejs.fullscreen form.converse-form input[type=checkbox] {
|
||||||
margin: 1em; }
|
margin-left: 1em;
|
||||||
#conversejs.fullscreen form.converse-form input[type=checkbox] {
|
display: inline;
|
||||||
margin-left: 1em;
|
margin-bottom: 2em; }
|
||||||
display: inline;
|
#conversejs.fullscreen form.converse-form input[type=button],
|
||||||
margin-bottom: 2em; }
|
#conversejs.fullscreen form.converse-form input[type=submit] {
|
||||||
#conversejs.fullscreen form.converse-form input[type=button],
|
padding-left: 1em;
|
||||||
#conversejs.fullscreen form.converse-form input[type=submit] {
|
padding-right: 1em;
|
||||||
padding-left: 1em;
|
margin-right: 1em; }
|
||||||
padding-right: 1em;
|
|
||||||
margin-right: 1em; }
|
|
||||||
|
|
||||||
#conversejs #user-profile-modal label {
|
#conversejs #user-profile-modal label {
|
||||||
font-weight: bold; }
|
font-weight: bold; }
|
||||||
@ -8605,8 +8686,7 @@ body {
|
|||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 36px;
|
width: 36px; }
|
||||||
border: 1px solid lightgrey; }
|
|
||||||
#conversejs .message.chat-msg .chat-msg-heading {
|
#conversejs .message.chat-msg .chat-msg-heading {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
padding-right: 0.25rem;
|
padding-right: 0.25rem;
|
||||||
|
53
dev.html
53
dev.html
@ -9,46 +9,17 @@
|
|||||||
<meta name="author" content="JC Brand" />
|
<meta name="author" content="JC Brand" />
|
||||||
<meta name="keywords" content="xmpp chat webchat converse.js" />
|
<meta name="keywords" content="xmpp chat webchat converse.js" />
|
||||||
<link rel="shortcut icon" type="image/ico" href="css/images/favicon.ico"/>
|
<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/inverse.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" />
|
|
||||||
<script src="node_modules/requirejs/require.js"></script>
|
<script src="node_modules/requirejs/require.js"></script>
|
||||||
<script src="src/config.js"></script>
|
<script src="src/config.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body id="page-top" class="reset" data-target=".navbar-custom">
|
<body class="reset">
|
||||||
<nav class="navbar navbar-custom navbar-fixed-top" role="navigation">
|
<div class="content">
|
||||||
<div class="container">
|
<div class="inner-content">
|
||||||
<div class="navbar-header page-scroll">
|
<h1 class="brand-heading"><i class="icon-conversejs"></i> Converse</h1>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</div>
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
require(['converse'], function (converse) {
|
require(['converse'], function (converse) {
|
||||||
@ -60,24 +31,18 @@
|
|||||||
// 'prosody@conference.prosody.im',
|
// 'prosody@conference.prosody.im',
|
||||||
// 'jdev@conference.jabber.org'
|
// 'jdev@conference.jabber.org'
|
||||||
// ],
|
// ],
|
||||||
hide_open_bookmarks: true,
|
view_mode: 'fullscreen',
|
||||||
|
archived_messages_page_size: '500',
|
||||||
allow_public_bookmarks: true,
|
allow_public_bookmarks: true,
|
||||||
notify_all_room_messages: [
|
notify_all_room_messages: [
|
||||||
'discuss@conference.conversejs.org'
|
'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: 'http://chat.example.org:5280/http-bind/',
|
||||||
bosh_service_url: 'https://conversejs.org/http-bind/', // Please use this connection manager only for testing purposes
|
bosh_service_url: 'https://conversejs.org/http-bind/', // Please use this connection manager only for testing purposes
|
||||||
message_archiving: 'always',
|
message_archiving: 'always',
|
||||||
show_controlbox_by_default: true,
|
|
||||||
strict_plugin_dependencies: false,
|
|
||||||
chatstate_notification_blacklist: ['mulles@movim.eu'],
|
|
||||||
xhr_user_search: false,
|
|
||||||
debug: true
|
debug: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
</body>
|
||||||
</html>
|
</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
|
update
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
||||||
|
@ -53,11 +53,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="message chat-msg">
|
<div class="message chat-msg">
|
||||||
<div class="avatar montague"></div>
|
|
||||||
<canvas data-avatar="/mockup/images/romeo.jpg" height="36" width="36" class="avatar"></canvas>
|
<canvas data-avatar="/mockup/images/romeo.jpg" height="36" width="36" class="avatar"></canvas>
|
||||||
<div class="chat-msg-content">
|
<div class="chat-msg-content">
|
||||||
<span class="chat-msg-heading">
|
<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 class="chat-msg-time">15:31</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="chat-msg-text">He jests at scars that never felt a wound.</span>
|
<span class="chat-msg-text">He jests at scars that never felt a wound.</span>
|
||||||
|
@ -363,6 +363,7 @@ body.reset {
|
|||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
border-radius: 10%;
|
border-radius: 10%;
|
||||||
|
border: 1px solid lightgrey;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activated {
|
.activated {
|
||||||
|
@ -128,7 +128,6 @@
|
|||||||
height: 36px;
|
height: 36px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 36px;
|
width: 36px;
|
||||||
border: 1px solid lightgrey;
|
|
||||||
}
|
}
|
||||||
.chat-msg-heading {
|
.chat-msg-heading {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
@import "bootstrap/scss/card";
|
@import "bootstrap/scss/card";
|
||||||
@import "bootstrap/scss/breadcrumb";
|
@import "bootstrap/scss/breadcrumb";
|
||||||
@import "bootstrap/scss/badge";
|
@import "bootstrap/scss/badge";
|
||||||
|
@import "bootstrap/scss/alert";
|
||||||
@import "bootstrap/scss/media";
|
@import "bootstrap/scss/media";
|
||||||
@import "bootstrap/scss/list-group";
|
@import "bootstrap/scss/list-group";
|
||||||
@import "bootstrap/scss/close";
|
@import "bootstrap/scss/close";
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
@import "bootstrap/scss/card";
|
@import "bootstrap/scss/card";
|
||||||
@import "bootstrap/scss/breadcrumb";
|
@import "bootstrap/scss/breadcrumb";
|
||||||
@import "bootstrap/scss/badge";
|
@import "bootstrap/scss/badge";
|
||||||
|
@import "bootstrap/scss/alert";
|
||||||
@import "bootstrap/scss/media";
|
@import "bootstrap/scss/media";
|
||||||
@import "bootstrap/scss/list-group";
|
@import "bootstrap/scss/list-group";
|
||||||
@import "bootstrap/scss/close";
|
@import "bootstrap/scss/close";
|
||||||
|
@ -43,7 +43,6 @@ body {
|
|||||||
|
|
||||||
form {
|
form {
|
||||||
&.converse-form {
|
&.converse-form {
|
||||||
margin: 1em;
|
|
||||||
input[type=checkbox] {
|
input[type=checkbox] {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
display: inline;
|
display: inline;
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
(function (root, factory) {
|
(function (root, factory) {
|
||||||
define(["converse-core",
|
define(["converse-core",
|
||||||
"bootstrap",
|
"bootstrap",
|
||||||
|
"tpl!alert",
|
||||||
"tpl!chat_status_modal",
|
"tpl!chat_status_modal",
|
||||||
"tpl!profile_modal",
|
"tpl!profile_modal",
|
||||||
"tpl!profile_view",
|
"tpl!profile_view",
|
||||||
@ -19,6 +20,7 @@
|
|||||||
}(this, function (
|
}(this, function (
|
||||||
converse,
|
converse,
|
||||||
bootstrap,
|
bootstrap,
|
||||||
|
tpl_alert,
|
||||||
tpl_chat_status_modal,
|
tpl_chat_status_modal,
|
||||||
tpl_profile_modal,
|
tpl_profile_modal,
|
||||||
tpl_profile_view,
|
tpl_profile_view,
|
||||||
@ -32,7 +34,7 @@
|
|||||||
|
|
||||||
converse.plugins.add('converse-profile', {
|
converse.plugins.add('converse-profile', {
|
||||||
|
|
||||||
dependencies: ["converse-modal"],
|
dependencies: ["converse-modal", "converse-vcard"],
|
||||||
|
|
||||||
initialize () {
|
initialize () {
|
||||||
/* The initialize function gets called as soon as the plugin is
|
/* The initialize function gets called as soon as the plugin is
|
||||||
@ -43,13 +45,96 @@
|
|||||||
|
|
||||||
|
|
||||||
_converse.ProfileModal = _converse.BootstrapModal.extend({
|
_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 () {
|
toHTML () {
|
||||||
return tpl_profile_modal(_.extend(this.model.toJSON(), {
|
return tpl_profile_modal(_.extend(this.model.toJSON(), {
|
||||||
'heading_profile': __('Your Profile'),
|
'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)
|
// Licensed under the Mozilla Public License (MPLv2)
|
||||||
|
|
||||||
(function (root, factory) {
|
(function (root, factory) {
|
||||||
define(["converse-core", "crypto"], factory);
|
define(["converse-core", "crypto", "tpl!vcard"], factory);
|
||||||
}(this, function (converse, CryptoJS) {
|
}(this, function (converse, CryptoJS, tpl_vcard) {
|
||||||
"use strict";
|
"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;
|
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', {
|
converse.plugins.add('converse-vcard', {
|
||||||
|
|
||||||
initialize () {
|
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 */
|
/* Event handlers */
|
||||||
_converse.initVCardCollection = function () {
|
_converse.initVCardCollection = function () {
|
||||||
_converse.vcards = new _converse.VCards();
|
_converse.vcards = new _converse.VCards();
|
||||||
@ -108,6 +122,8 @@
|
|||||||
|
|
||||||
_.extend(_converse.api, {
|
_.extend(_converse.api, {
|
||||||
'vcard': {
|
'vcard': {
|
||||||
|
'set': setVCard,
|
||||||
|
|
||||||
'get' (model, force) {
|
'get' (model, force) {
|
||||||
if (_.isString(model)) {
|
if (_.isString(model)) {
|
||||||
return getVCard(_converse, model);
|
return getVCard(_converse, model);
|
||||||
@ -126,7 +142,7 @@
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.get(model, force).then((vcard) => {
|
this.get(model, force).then((vcard) => {
|
||||||
model.save(_.extend(
|
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()}
|
{'vcard_updated': moment().format()}
|
||||||
));
|
));
|
||||||
resolve();
|
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>
|
<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>
|
<button type="button" class="close" data-dismiss="modal" aria-label="{{{o.label_close}}}"><span aria-hidden="true">×</span></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<form class="converse-form">
|
||||||
<div class="row">
|
<div class="modal-body">
|
||||||
<div class="col-auto">
|
<div class="row">
|
||||||
{[ if (o.image) { ]}
|
<div class="col-auto">
|
||||||
<a class="show-profile" href="#">
|
<a class="change-avatar" href="#">
|
||||||
<img alt="User Avatar" class="img-thumbnail avatar align-self-center" height="100px" width="100px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
|
{[ if (o.image) { ]}
|
||||||
</a>
|
<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>
|
||||||
<div class="col-auto">
|
<div class="form-group">
|
||||||
<div classs="row w-100">
|
<label for="vcard-fullname" class="col-form-label">{{{o.label_fullname}}}:</label>
|
||||||
<label>Fullname:</label>
|
<input id="vcard-fullname" type="text" class="form-control" name="fn" value="{{{o.fullname}}}">
|
||||||
<span class="username">{{{o.fullname}}}</span>
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
<div classs="row w-100">
|
<label for="vcard-nickname" class="col-form-label">{{{o.label_nickname}}}:</label>
|
||||||
<label>XMPP Address:</label>
|
<input id="vcard-nickname" type="text" class="form-control" name="nickname" value="{{{o.nickname}}}">
|
||||||
<span class="username">{{{o.jid}}}</span>
|
</div>
|
||||||
</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>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
<div class="userinfo">
|
<div class="userinfo">
|
||||||
<div class="profile d-flex">
|
<div class="profile d-flex">
|
||||||
{[ if (o.image) { ]}
|
|
||||||
<a class="show-profile" href="#">
|
<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}}}"/>
|
<img alt="User Avatar" class="avatar align-self-center" height="40px" width="40px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
|
||||||
</a>
|
</a>
|
||||||
{[ } ]}
|
|
||||||
<span class="username w-100 align-self-center">{{{o.fullname}}}</span>
|
<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-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> -->
|
<!-- <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.sizzle,
|
||||||
root.Promise,
|
root.Promise,
|
||||||
root._,
|
root._,
|
||||||
|
root.Backbone,
|
||||||
Strophe
|
Strophe
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -56,7 +57,6 @@
|
|||||||
tpl_video
|
tpl_video
|
||||||
) {
|
) {
|
||||||
"use strict";
|
"use strict";
|
||||||
const b64_sha1 = Strophe.SHA1.b64_sha1;
|
|
||||||
Strophe = Strophe.Strophe;
|
Strophe = Strophe.Strophe;
|
||||||
|
|
||||||
const URL_REGEX = /\b(https?:\/\/|www\.|https?:\/\/www\.)[^\s<>]{2,200}\b\/?/g;
|
const URL_REGEX = /\b(https?:\/\/|www\.|https?:\/\/www\.)[^\s<>]{2,200}\b\/?/g;
|
||||||
|
Loading…
Reference in New Issue
Block a user